/**
 * mass-tools
 * @version v7.4.0
 * @link https://github.com/cheminfo/mass-tools#readme
 * @license MIT
 */
(function (global, factory) {
    typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
    typeof define === 'function' && define.amd ? define(['exports'], factory) :
    (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.MassTools = {}));
})(this, (function (exports) { 'use strict';

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

    var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};

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

    function commonjsRequire (path) {
    	throw new Error('Could not dynamically require "' + path + '". Please configure the dynamicRequireTargets or/and ignoreDynamicRequires option of @rollup/plugin-commonjs appropriately for this require call to work.');
    }

    /**
     * Calculates the median of an array
     *
     * @param input - Array containing values
     * @returns - median
     */
    function xMedian(input) {
      if (!isAnyArray$3(input)) {
        throw new TypeError('input must be an array');
      }
      if (input.length === 0) {
        throw new TypeError('input must not be empty');
      }
      const array = input.slice();
      let low = 0;
      let high = array.length - 1;
      let middle = 0;
      let currentLow = 0;
      let currentHigh = 0;
      let median = calcMiddle(low, high);
      while (true) {
        if (high <= low) {
          return array[median];
        }
        if (high === low + 1) {
          if (array[low] > array[high]) {
            swap(array, low, high);
          }
          return array[median];
        }
        // Find median of low, middle and high items; swap into position low
        middle = calcMiddle(low, high);
        if (array[middle] > array[high]) swap(array, middle, high);
        if (array[low] > array[high]) swap(array, low, high);
        if (array[middle] > array[low]) swap(array, middle, low);
        // Swap low item (now in position middle) into position (low+1)
        swap(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(array, currentLow, currentHigh);
        }
        // Swap middle item (in position low) back into correct position
        swap(array, low, currentHigh);
        // Re-set active partition
        if (currentHigh <= median) {
          low = currentLow;
        }
        if (currentHigh >= median) {
          high = currentHigh - 1;
        }
      }
    }
    function swap(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
     * @param output - undefined or a new array
     * @param length - length of the output array
     * @returns
     */
    function getOutputArray$1(output, length) {
      if (output !== undefined) {
        if (!isAnyArray$3(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);
      }
    }

    /**
     * Checks if input is of type array
     *
     * @param input - input
     */
    function xCheck$3(input) {
      let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      const {
        minLength
      } = options;
      if (!isAnyArray$3(input)) {
        throw new TypeError('input must be an array');
      }
      if (input.length === 0) {
        throw new TypeError('input must not be empty');
      }
      if (minLength && input.length < minLength) {
        throw new Error(`input must have a length of at least ${minLength}`);
      }
    }

    /**
     * Returns the closest index of a `target`
     *
     * @param array - array of numbers
     * @param target - target
     * @returns - closest index
     */
    function xFindClosestIndex$4(array, target) {
      let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
      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$3(x) {
      let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      let {
        fromIndex,
        toIndex,
        from,
        to
      } = options;
      if (fromIndex === undefined) {
        if (from !== undefined) {
          fromIndex = xFindClosestIndex$4(x, from);
        } else {
          fromIndex = 0;
        }
      }
      if (toIndex === undefined) {
        if (to !== undefined) {
          toIndex = xFindClosestIndex$4(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
      };
    }

    /**
     * Computes the maximal value of an array of values
     *
     * @param array - array of numbers
     * @param options - options
     */
    function xMaxValue$3(array) {
      let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      xCheck$3(array);
      const {
        fromIndex,
        toIndex
      } = xGetFromToIndex$3(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$1(array) {
      let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      xCheck$3(array);
      const {
        fromIndex,
        toIndex
      } = xGetFromToIndex$3(array, options);
      let minValue = array[fromIndex];
      for (let i = fromIndex + 1; i <= toIndex; i++) {
        if (array[i] < minValue) {
          minValue = array[i];
        }
      }
      return minValue;
    }

    /**
     * Check if the values are separated always by the same difference
     *
     * @param array - Monotone growing array of number
     */
    function xIsEquallySpaced(array) {
      let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      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) {
        let 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 monotone
     *
     * @param array - array of numbers
     */
    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[array.length - 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;
      }
    }

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

    /**
     * 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$3(array);
      let min = array[0];
      let max = array[0];
      for (let value of array) {
        if (value < min) min = value;
        if (value > max) max = value;
      }
      return {
        min,
        max
      };
    }

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

    /**
     * Calculate the sum of the values
     *
     * @param array - Object that contains property x (an ordered increasing array) and y (an array).
     * @param options - Options.
     * @returns XSum value on the specified range.
     */
    function xSum$1(array) {
      let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      xCheck$3(array);
      const {
        fromIndex,
        toIndex
      } = xGetFromToIndex$3(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 array - Array containing values
     * @param options - options
     * @returns - normalized data
     */
    function xNormed$1(input) {
      let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      const {
        algorithm = 'absolute',
        value = 1
      } = options;
      xCheck$3(input);
      const output = getOutputArray$1(options.output, input.length);
      if (input.length === 0) {
        throw new Error('input must not be empty');
      }
      switch (algorithm.toLowerCase()) {
        case 'absolute':
          {
            let absoluteSumValue = absoluteSum$1(input) / value;
            if (absoluteSumValue === 0) {
              throw new Error('xNormed: trying to divide by 0');
            }
            for (let i = 0; i < input.length; i++) {
              output[i] = input[i] / absoluteSumValue;
            }
            return output;
          }
        case 'max':
          {
            let currentMaxValue = xMaxValue$3(input);
            if (currentMaxValue === 0) {
              throw new Error('xNormed: 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':
          {
            let sumFactor = xSum$1(input) / value;
            if (sumFactor === 0) {
              throw new Error('xNormed: trying to divide by 0');
            }
            for (let i = 0; i < input.length; i++) {
              output[i] = input[i] / sumFactor;
            }
            return output;
          }
        default:
          throw new Error(`norm: unknown algorithm: ${algorithm}`);
      }
    }
    function absoluteSum$1(input) {
      let sumValue = 0;
      for (let i = 0; i < input.length; i++) {
        sumValue += Math.abs(input[i]);
      }
      return sumValue;
    }

    /**
     * Throw an error in no an object of x,y arrays
     *
     * @param data - array of points {x,y,z}
     */
    function xyCheck(data) {
      let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      const {
        minLength
      } = options;
      if (typeof data !== 'object' || !isAnyArray$3(data.x) || !isAnyArray$3(data.y)) {
        throw new Error('Data must be an object of x and y arrays');
      }
      if (data.x.length !== data.y.length) {
        throw new Error('The x and y arrays must have the same length');
      }
      if (minLength && data.x.length < minLength) {
        throw new Error(`data.x must have a length of at least ${minLength}`);
      }
    }

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

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

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

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

    /**
     * Join x / y values when difference in X is closer than delta.
     * When joining, y values are summed and x values are weighted average
     *
     * @param data - Object that contains property x (an ordered increasing array) and y (an array)
     * @param options - Options
     * @returns - An object with the xyIntegration function
     */
    function xyJoinX(data) {
      let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      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++) {
        let difference = x[i] - x[i - 1];
        let 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
      };
    }

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

    /**
     * GetSlots.
     *
     * @param data - data.
     * @param options - Options.
     */
    function getSlots(data) {
      let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      const {
        delta = 1
      } = options;
      const deltaIsFunction = typeof delta === 'function';
      let possibleXs = Float64Array.from([].concat(...data.map(spectrum => spectrum.x))).sort();
      if (possibleXs.length === 0) {
        throw new Error('xyArrayMerge can not process empty arrays');
      }
      let currentSlot = {
        from: possibleXs[0],
        to: possibleXs[0],
        average: possibleXs[0],
        sum: possibleXs[0],
        number: 1
      };
      let slots = [currentSlot];
      for (let i = 1; i < possibleXs.length; i++) {
        let 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) {
      let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      const {
        delta = 1,
        requiredY = false
      } = options;
      data = data.map(spectrum => xyJoinX(spectrum, {
        delta
      }));
      const slots = getSlots(data, options);
      let x = Float64Array.from(slots.map(slot => slot.average));
      let ys = new Array(data.length).fill(0).map(() => new Float64Array(x.length));
      let positions = new Uint32Array(data.length);
      for (let i = 0; i < slots.length; i++) {
        let slot = slots[i];
        for (let j = 0; j < data.length; j++) {
          let 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 = new Array(ys.length).fill(0).map(() => []);
      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
      };
    }

    /**
     * Throw an error in no an object of x,y arrays
     *
     * @param points - list of points
     */
    function xyObjectCheck() {
      let points = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
      if (!Array.isArray(points)) {
        throw new Error('ArrayPoints must be an array of {x,y} object');
      }
      if (points.length > 0 && (points[0].x === undefined || points[0].y === undefined)) {
        throw new Error('ArrayPoints must be an array of {x,y} object');
      }
    }

    /**
     * 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() {
      let points = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
      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() {
      let points = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
      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;
    }

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

    /**
     * Filter the array by taking the higher peaks and only
     * keep one per slot.
     * There are 2 different slots, the smallest one will have the
     * extra annotation `close` to true
     * @param {array} peaks - array of all the peaks
     * @param {object} [options={}]
     * @param {number} [options.from] - min X value of the window to consider
     * @param {number} [options.to] - max X value of the window to consider
     * @param {number} [options.searchMonoisotopicRatio=0] - search previous peaks with at least ratio height
     * @param {number} [options.limit=20] - max number of peaks
     * @param {number} [options.threshold=0.01] - minimal intensity compare to base peak
     * @param {number} [options.numberSlots=10] - define the number of slots and indirectly the slot width
     * @param {number} [options.numberCloseSlots=50]
     * @returns {array} - copy of peaks with 'close' annotation
     */

    function getBestPeaks$1(peaks) {
      let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      const {
        searchMonoisotopicRatio = 0,
        from = xyObjectMinXPoint(peaks).x,
        to = xyObjectMaxXPoint(peaks).x,
        limit = 20,
        threshold = 0.01,
        numberCloseSlots = 50,
        numberSlots = 10
      } = options;
      let slot = (to - from) / numberSlots;
      let closeSlot = (to - from) / numberCloseSlots;
      let selected = peaks.filter(peak => peak.x >= from && peak.x <= to).map(peak => {
        return {
          peak,
          monoisotopic: false
        };
      });
      if (searchMonoisotopicRatio) {
        selected = selected.sort((a, b) => b.peak.x - a.peak.x);
        for (let i = 0; i < selected.length; i++) {
          let item = selected[i];
          for (let j = i + 1; j < selected.length; j++) {
            let nextItem = selected[j];
            if (item.peak.x - nextItem.peak.x < 0.09) continue;
            if (item.peak.x - nextItem.peak.x > 1.1) break;
            if (nextItem.peak.y > item.peak.y * searchMonoisotopicRatio) {
              item.monoisotopic = false;
              nextItem.monoisotopic = true;
              break;
            }
          }
        }
      }
      selected = selected.sort((a, b) => {
        if (a.monoisotopic && !b.monoisotopic) return -1;
        if (b.monoisotopic && !a.monoisotopic) return 1;
        return b.peak.y - a.peak.y;
      });
      let toReturn = [];
      if (selected.length === 0) return [];
      let minY = selected[0].peak.y * threshold;
      peakLoop: for (let item of selected) {
        if (item.peak.y < minY) {
          if (item.monoisotopic) {
            continue;
          } else {
            break;
          }
        }
        let close = false;
        for (let existing of toReturn) {
          if (Math.abs(existing.x - item.peak.x) < closeSlot) {
            continue peakLoop;
          }
          if (Math.abs(existing.x - item.peak.x) < slot) {
            close = true;
          }
        }
        let newPeak = JSON.parse(JSON.stringify(item.peak));
        newPeak.close = close;
        toReturn.push(newPeak);
        if (toReturn.length === limit) break;
      }
      return toReturn.sort((a, b) => a.x - b.x);
    }

    /**
     * Define static variable corresponding to the various Kinds of a molecular formula part.
     */

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

    /**
     * Parse a string to extract the charge
     * The charge may be in the form --, +++, +3, -2, 4+, 2-
     * @param {*} charge
     */

    function parseCharge(charge) {
      charge = charge.replace(/[()]/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(/^[0-9]+[+-]$/)) {
        chargeNumber = Number(charge.charAt(charge.length - 1) + charge.substring(0, charge.length - 1));
      } else {
        chargeNumber = Number(charge);
      }
      return chargeNumber;
    }

    /**
     * Parse a mf to an array of kind / value
     * @param {String} mf
     */

    function parse(mf) {
      return new MFParser().parse(mf);
    }
    class MFParser {
      parse() {
        let mf = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
        this.mf = mf;
        this.i = 0;
        this.result = [];
        let lastKind = Kind.BEGIN;
        while (this.i < mf.length) {
          if (this.result.length > 0 && this.result[this.result.length - 1].kind !== Kind.TEXT) {
            lastKind = this.result[this.result.length - 1].kind;
          }
          let char = mf.charAt(this.i);
          let ascii = mf.charCodeAt(this.i);
          let nextAscii = 0;
          if (this.i + 1 < mf.length) nextAscii = mf.charCodeAt(this.i + 1);
          if (ascii > 47 && ascii < 58 || char === '-' && nextAscii > 47 && nextAscii < 58) {
            // a number
            let value = this.getNumber(ascii);
            if (lastKind === Kind.SALT || lastKind === Kind.BEGIN || lastKind === Kind.OPENING_PARENTHESIS) {
              if (value.to) {
                throw new MFError(this.mf, this.i, 'Premultiplier may not contain a -');
              }
              this.result.push({
                kind: Kind.PRE_MULTIPLIER,
                value: value.from
              });
            } else if (lastKind === Kind.ANCHOR) {
              if (value.to) {
                throw new MFError(this.mf, this.i, 'Anchor ID may not contain -');
              }
              this.result[this.result.length - 1].value = value.from;
            } else if (value.to) {
              this.result.push({
                kind: Kind.MULTIPLIER_RANGE,
                value: {
                  from: Math.min(value.from, value.to),
                  to: Math.max(value.from, value.to)
                }
              });
            } else {
              this.result.push({
                kind: Kind.MULTIPLIER,
                value: value.from
              });
            }
            continue;
          } else if (char === '.') {
            // a point
            this.result.push({
              kind: 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.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
            let value = this.getAtom(ascii);
            this.result.push({
              kind: Kind.ATOM,
              value
            });
            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.CHARGE,
                value: charge
              });
            } else {
              this.result.push({
                kind: Kind.OPENING_PARENTHESIS,
                value: '('
              });
            }
          } else if (char === ')') {
            this.result.push({
              kind: Kind.CLOSING_PARENTHESIS,
              value: ')'
            });
          } else if (char === '[') {
            // defines an isotope
            let isotope = this.getIsotope(ascii);
            this.result.push({
              kind: 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.ATOM) {
              let lastResult = this.result[this.result.length - 1];
              lastResult.kind = 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.CHARGE,
              value: charge
            });
          } else if (char === '-') {
            // charge not in parenthesis
            let charge = this.getNonParenthesisCharge(ascii);
            this.result.push({
              kind: Kind.CHARGE,
              value: charge
            });
          } else if (char === '$') {
            // it is a comment after
            this.result.push({
              kind: Kind.COMMENT,
              value: this.mf.substring(this.i + 1)
            });
            break;
          } else {
            this.result.push({
              kind: Kind.TEXT,
              value: char
            });
          }
          this.i++;
        }
        this.checkParenthesis();
        return this.result;
      }
      checkParenthesis() {
        let counter = 0;
        for (let line of this.result) {
          if (line.kind === Kind.OPENING_PARENTHESIS) counter++;
          if (line.kind === 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.substr(0, indexOfDash)),
            to: parseNumberWithDivision(number.substr(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);
        return atom;
      }
      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.replace(/[^a-zA-Z]/g, '');
        let isotope = Number(substring.replace(/[^0-9]/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(a => Number(a));
        }
        throw new MFError(this.mf, this.i, 'Curly brackets should contain only number 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(/^\([0-9+-]+$/)) {
          return parseCharge(substring.substring(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 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);
      }
    }

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

    /**
     * Defines static variables corresponding to the various formatting possibilities
     */

    const Format = {
      SUBSCRIPT: 'subscript',
      SUPERSCRIPT: 'superscript',
      SUPERIMPOSE: 'superimpose',
      TEXT: 'text'
    };

    function formatCharge(charge) {
      if (charge === 1) return '+';
      if (charge > 1) return `+${charge}`;
      if (charge < 0) return String(charge);
      return '';
    }

    /**
     * Converts an array of mf elements to an array of formatting information
     * @param {object[]} lines of the parse method
     */

    function toDisplay(lines) {
      let results = [];
      let result = {};
      for (let line of lines) {
        switch (line.kind) {
          case Kind.MULTIPLIER:
            if (line.value !== 1) {
              result = {
                kind: Format.SUBSCRIPT,
                value: String(line.value)
              };
              results.push(result);
            }
            break;
          case Kind.MULTIPLIER_RANGE:
            result = {
              kind: Format.SUBSCRIPT,
              value: `${String(line.value.from)}-${line.value.to}`
            };
            results.push(result);
            break;
          case Kind.CHARGE:
            if (result.kind === Format.SUBSCRIPT) {
              result.kind = Format.SUPERIMPOSE;
              result.over = formatCharge(line.value);
              result.under = result.value;
              result.value = undefined;
            } else {
              result = {
                kind: Format.SUPERSCRIPT,
                value: formatCharge(line.value)
              };
              results.push(result);
            }
            break;
          case Kind.ISOTOPE:
            result = {
              kind: Format.SUPERSCRIPT,
              value: line.value.isotope
            };
            results.push(result);
            result = {
              kind: Format.TEXT,
              value: line.value.atom
            };
            results.push(result);
            break;
          case Kind.ISOTOPE_RATIO:
            if (result.kind === Format.TEXT) {
              result.value += line.value.atom;
            } else {
              result = {
                kind: Format.TEXT,
                value: line.value.atom
              };
              results.push(result);
            }
            result = {
              kind: Format.SUPERSCRIPT,
              value: `{${line.value.ratio.join(',')}}`
            };
            results.push(result);
            break;
          case Kind.SALT:
            if (result.kind === Format.TEXT) {
              result.value += ' • ';
            } else {
              result = {
                kind: Format.TEXT,
                value: ' • '
              };
              results.push(result);
            }
            break;
          default:
            if (result.kind === Format.TEXT) {
              result.value += line.value;
            } else {
              result = {
                kind: Format.TEXT,
                value: line.value
              };
              results.push(result);
            }
        }
      }
      return results;
    }

    const Style$1 = {
      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%'
    };

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

    const ELECTRON_MASS$1 = 5.4857990907e-4;

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

    const elements$2 = elementsAndIsotopes.map(element => ({
      number: element.number,
      symbol: element.symbol,
      mass: element.mass,
      name: element.name,
      monoisotopicMass: element.monoisotopicMass
    }));

    const elementsAndIsotopesObject = {};
    elementsAndIsotopes.forEach(element => {
      elementsAndIsotopesObject[element.symbol] = element;
    });

    const elementsAndStableIsotopes = JSON.parse(JSON.stringify(elementsAndIsotopes));
    elementsAndStableIsotopes.forEach(element => {
      element.isotopes = element.isotopes.filter(i => i.abundance > 0);
    });

    const elementsAndStableIsotopesObject = {};
    elementsAndStableIsotopes.forEach(element => {
      elementsAndStableIsotopesObject[element.symbol] = element;
    });

    const elementsObject = {};
    elements$2.forEach(element => {
      elementsObject[element.symbol] = element;
    });

    const stableIsotopesObject = {};
    for (const element of elementsAndIsotopes) {
      let abundance = 0;
      let mostAbundant = 0;
      for (const isotope of element.isotopes) {
        if (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
        };
        if (isotope.nominal === mostAbundant) {
          entry.mostAbundant = true;
        }
        stableIsotopesObject[isotope.nominal + element.symbol] = entry;
      }
    }

    const isotopesObject = {};
    Object.keys(elementsAndIsotopesObject).forEach(key => {
      let e = elementsAndIsotopesObject[key];
      e.isotopes.forEach(i => {
        isotopesObject[i.nominal + key] = {
          abundance: i.abundance,
          mass: i.mass
        };
      });
    });

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

    const elements$1 = Object.keys(elementsObject).sort((a, b) => b.length - a.length);

    /**
     * Ensure that the mf has been entered with capital letters and not only lowercase
     * If there is only lowercase we try to capitalize the mf
     * @param {string} mf
     */

    function ensureCase(mf) {
      for (let i = 0; i < mf.length; i++) {
        if (mf.charCodeAt(i) > 64 && mf.charCodeAt(i) < 91) {
          return mf;
        }
      }
      let parts = mf.replace(/([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].substr(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$1.includes(two)) {
                newPart += two;
                j++;
              } else if (elements$1.includes(one)) {
                newPart += one;
              } else {
                return mf;
              }
            }
          }
          parts[i] = newPart;
        }
      }
      return parts.join('');
    }

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

    const groupsObject = {};
    groups.forEach(e => {
      if (groupsObject[e.symbol]) {
        console.log('The symbol ' + e.symbol + ' is used more than once');
      }
      groupsObject[e.symbol] = e;
    });

    /**
     * Recreate a one letter sequence
     * @param {object} mf
     */

    function groupsToSequence(mf) {
      mf = mf.replace(/\([^(]*\)/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[part] && groupsObject[part].oneLetter) {
          usefulParts.push(groupsObject[part].oneLetter);
        } else {
          usefulParts.push('?');
        }
      }
      return usefulParts.join('').replace(/ +/g, ' ').trim();
    }

    function getIsotopeRatioInfo(value) {
      let result = {
        mass: 0,
        monoisotopicMass: 0
      };
      let element = elementsAndStableIsotopesObject[value.atom];
      if (!element) throw new Error(`Element not found: ${value.atom}`);
      let isotopesArray = element.isotopes;
      let ratios = normalize$3(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$3(array) {
      let sum = array.reduce((prev, current) => prev + current, 0);
      return array.map(a => a / sum);
    }

    /**
     *
     * @param {*} parts
     * @param {*} [options={}]
     */
    function getEA(parts) {
      let results = {};
      for (let part of parts) {
        for (let line of part) {
          switch (line.kind) {
            case Kind.ISOTOPE:
              {
                let isotope = 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.ISOTOPE_RATIO:
              {
                let isotopeRatioInfo = getIsotopeRatioInfo(line.value);
                addMass(results, line.value.atom, isotopeRatioInfo.mass * line.multiplier);
                break;
              }
            case Kind.ATOM:
              {
                let element = elementsObject[line.value];
                if (!element) {
                  element = groupsObject[line.value];
                  if (!element) throw Error(`Unknown element: ${line.value}`);
                  // need to explode group ????
                }

                addMass(results, line.value, element.mass * line.multiplier);
                break;
              }
            case 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]
        });
      }
      eas.forEach(ea => {
        ea.ratio = ea.mass / sum;
      });
      return eas;
    }
    function addMass(results, atom, mass) {
      if (!results[atom]) results[atom] = 0;
      results[atom] += mass;
    }

    /**
     *
     * @param {*} parts
     * @param {*} [options={}]
     */
    function getElements(parts) {
      const elements = [];
      for (const part of parts) {
        for (const line of part) {
          let number = line.multiplier;
          switch (line.kind) {
            case Kind.ATOM:
              {
                let symbol = line.value;
                let element = elementsObject[symbol];
                if (!element) {
                  throw new Error(`element unknown: ${symbol} - ${line}`);
                }
                addElement(elements, {
                  symbol,
                  number
                });
                break;
              }
            case Kind.ISOTOPE:
              {
                let element = elementsAndIsotopesObject[line.value.atom];
                if (!element) {
                  throw new Error(`element unknown: ${part.value.atom} - ${line}`);
                }
                let isotope = element.isotopes.filter(a => a.nominal === line.value.isotope)[0];
                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);
    }

    /**
     * Convert a MF part to an array of atoms
     * This procedure will suppress the isotopes !
     * This is mainly used to make queries
     */

    function partToAtoms(part) {
      let atoms = {};
      for (let line of part) {
        switch (line.kind) {
          case Kind.ISOTOPE:
            if (!atoms[line.value.atom]) atoms[line.value.atom] = 0;
            atoms[line.value.atom] += line.multiplier;
            break;
          case Kind.ISOTOPE_RATIO:
            if (!atoms[line.value.atom]) atoms[line.value.atom] = 0;
            atoms[line.value.atom] += line.multiplier;
            break;
          case Kind.ATOM:
            if (!atoms[line.value]) atoms[line.value] = 0;
            atoms[line.value] += line.multiplier;
            break;
          case Kind.CHARGE:
            break;
          case Kind.ANCHOR:
            break;
          default:
            throw new Error('partToMF unhandled Kind: ', line.kind);
        }
      }
      return atoms;
    }

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

    /**
     *
     * @param {*} parts
     * @param {*} [options={}]
     */
    function getInfo$1(parts) {
      let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      let {
        customUnsaturations = {},
        emFieldName = 'monoisotopicMass',
        msemFieldName = 'observedMonoisotopicMass'
      } = options;
      if (parts.length === 0) return {};
      if (parts.length === 1) {
        return getProcessedPart$1(parts[0], {
          customUnsaturations,
          emFieldName,
          msemFieldName
        });
      }
      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];
        }
      }
      return result;
    }
    function getProcessedPart$1(part, options) {
      let {
        customUnsaturations,
        emFieldName,
        msemFieldName
      } = options;
      let currentPart = {
        mass: 0,
        charge: 0,
        mf: '',
        atoms: partToAtoms(part)
      };
      currentPart[emFieldName] = 0;
      let unsaturation = 0;
      let validUnsaturation = true;
      currentPart.mf = partToMF(part);
      for (let line of part) {
        let currentElement = '';
        switch (line.kind) {
          case Kind.ATOM:
            {
              currentElement = line.value;
              let element = elementsAndIsotopesObject[line.value];

              // todo should we have a kind GROUP ?
              if (!element) {
                element = groupsObject[line.value];
                if (!element) throw 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.ISOTOPE:
            {
              currentElement = line.value.atom;
              let isotope = 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.ISOTOPE_RATIO:
            {
              currentElement = line.value.atom;
              let isotopeRatioInfo = getIsotopeRatioInfo(line.value);
              currentPart[emFieldName] += isotopeRatioInfo[emFieldName] * line.multiplier;
              currentPart.mass += isotopeRatioInfo.mass * line.multiplier;
              break;
            }
          case 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 (unsaturationsObject[currentElement] !== undefined) {
            unsaturation += unsaturationsObject[currentElement] * line.multiplier;
          } else {
            validUnsaturation = false;
          }
        }
      }

      // need to calculate the observedMonoisotopicMass
      if (currentPart.charge) {
        currentPart[msemFieldName] = (currentPart[emFieldName] - currentPart.charge * ELECTRON_MASS$1) / Math.abs(currentPart.charge);
      }
      if (validUnsaturation) {
        currentPart.unsaturation = unsaturation / 2 + 1;
      }
      return currentPart;
    }

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

    /**
     * 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.SALT,
          value: '•'
        });
        for (let partLine of part) {
          lines.push(partLine);
          if (partLine.multiplier) {
            lines.push({
              kind: Kind.MULTIPLIER,
              value: partLine.multiplier
            });
          }
        }
      }
      return toDisplay(lines);
    }

    function partsToMF(parts, options) {
      let mf = [];
      for (let part of parts) {
        mf.push(partToMF(part, options));
      }
      return mf.join(' . ');
    }

    /**
     * Implementation of the Hill system for sorting atoms
     * https://en.wikipedia.org/wiki/Chemical_formula#Hill_system
     * @param {string} a - first atom to compare
     * @param {string} b - second atom to compare
     * @returns
     */

    function atomSorter$1(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;
    }

    /**
     *
     * @param {*} lines
     * @param {object} [options={}]
     * @param {boolean} [options.expand=true] - Should we expand the groupsObject
     */

    function toParts(lines) {
      let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      const {
        expand: shouldExpandgroupsObject = true
      } = options;
      let parts = [];
      let currentPart = createNewPart();
      let previousKind = Kind.BEGIN;
      parts.push(currentPart);
      for (let line of lines) {
        switch (line.kind) {
          case Kind.ATOM:
          case Kind.ISOTOPE_RATIO:
          case Kind.ISOTOPE:
          case Kind.CHARGE:
            currentPart.lines.push({
              ...line,
              multiplier: 1
            });
            break;
          case Kind.OPENING_PARENTHESIS:
            openingParenthesis(currentPart);
            break;
          case Kind.CLOSING_PARENTHESIS:
            closingParenthesis(currentPart);
            break;
          case Kind.PRE_MULTIPLIER:
            preMultiplier(currentPart, line);
            break;
          case Kind.MULTIPLIER:
            postMultiplier(currentPart, line.value, previousKind);
            break;
          case Kind.SALT:
            globalPartMultiplier(currentPart);
            currentPart = createNewPart();
            parts.push(currentPart);
            break;
          case Kind.ANCHOR:
            // we ignore anchors to create the parts and canonized MF
            break;
          case Kind.COMMENT:
            // we ignore comments to create the parts and canonized MF
            break;
          case 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.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[currentPart.lines.length - 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.ATOM) {
            let group = 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(a => a);
      }
    }
    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.CHARGE) {
            if (currentKey !== key.key) {
              result.push({
                kind: Kind.CHARGE,
                value: key.value.value * key.value.multiplier
              });
            } else {
              result[result.length - 1].value += key.value.value * key.value.multiplier;
            }
          } else if (currentKey !== key.key) {
            result.push(key.value);
          } else {
            result[result.length - 1].multiplier += key.value.multiplier;
          }
          currentKey = key.key;
        }
        result.sort((a, b) => {
          if (a.kind === Kind.CHARGE) return 1;
          if (b.kind === Kind.CHARGE) return -1;
          let atomA = a.kind === Kind.ATOM ? a.value : a.value.atom;
          let atomB = b.kind === Kind.ATOM ? b.value : b.value.atom;
          if (atomA !== atomB) return atomSorter$1(atomA, atomB);
          // same atome but some isotopes ...
          if (a.kind === Kind.ATOM) return -1;
          if (b.kind === Kind.ATOM) return 1;
          if (a.kind === Kind.ISOTOPE) return -1;
          if (b.kind === Kind.ISOTOPE) return 1;
          if (a.kind === Kind.ISOTOPE_RATIO) return -1;
          if (b.kind === 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.CHARGE:
          break;
        default:
          if (typeof line.value === 'string') {
            key.push(line.value);
          } else {
            for (let prop of Object.keys(line.value).sort()) {
              key.push(line.value[prop]);
            }
          }
      }
      return key.join('-');
    }
    function stringComparator(a, b) {
      if (a < b) return -1;
      if (a > b) return 1;
      return 0;
    }

    function toText(lines) {
      let text = [];
      for (let line of lines) {
        switch (line.kind) {
          case Format.SUBSCRIPT:
            {
              const value = String(line.value);
              for (let i = 0; i < value.length; i++) {
                const char = value[i];
                if (subscript[char]) {
                  text.push(subscript[char]);
                } else {
                  throw new Error(`Subscript problem with: ${char}`);
                }
              }
            }
            break;
          case Format.SUPERSCRIPT:
            {
              const value = String(line.value);
              for (let i = 0; i < value.length; i++) {
                const char = value[i];
                if (superscript[char]) {
                  text.push(superscript[char]);
                } else {
                  throw new Error(`Superscript problem with: ${char}`);
                }
              }
              break;
            }
          case Format.SUPERIMPOSE:
            {
              const under = String(line.under);
              for (let i = 0; i < under.length; i++) {
                const char = under[i];
                if (subscript[char]) {
                  text.push(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 (superscript[char]) {
                  text.push(superscript[char]);
                } else {
                  throw new Error(`Superscript problem with: ${char}`);
                }
              }
              break;
            }
          default:
            text.push(line.value);
        }
      }
      return text.join('');
    }

    /**
     * Class allowing to deal with molecular formula and derived information
     */
    class MF {
      constructor(mf) {
        let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
        if (options.ensureCase) {
          mf = ensureCase(mf);
        }
        this.parsed = parse(mf);
        this.cache = {};
      }
      toDisplay() {
        if (!this.cache.displayed) this.cache.displayed = toDisplay(this.parsed);
        return this.cache.displayed;
      }
      toHtml() {
        if (!this.cache.html) {
          this.toDisplay();
          this.cache.html = toHtml(this.cache.displayed);
        }
        return this.cache.html;
      }
      toText() {
        if (!this.cache.text) {
          this.toDisplay();
          this.cache.text = toText(this.cache.displayed);
        }
        return this.cache.text;
      }
      toCanonicText() {
        if (!this.cache.canonicText) {
          this.cache.canonicText = new MF(this.toMF()).toText(this.cache.displayed);
        }
        return this.cache.canonicText;
      }
      toParts(options) {
        if (!this.cache.parts) {
          this.cache.parts = 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
       */
      getInfo() {
        let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
        if (!this.cache.info) {
          this.toParts();
          this.cache.info = getInfo$1(this.cache.parts, options);
        }
        return this.cache.info;
      }

      /**
       * Returns an object with the elemental analysis
       */
      getEA() {
        if (!this.cache.ea) {
          this.toParts();
          this.cache.ea = getEA(this.cache.parts);
        }
        return this.cache.ea;
      }

      /**
       * Get the different elements for each part
       * @returns an array
       */
      getElements() {
        if (!this.cache.elements) {
          this.toParts();
          this.cache.elements = getElements(this.cache.parts);
        }
        return this.cache.elements;
      }

      /**
       * Returns an array with each atom and isotopic composition
       */
      getIsotopesInfo() {
        if (!this.cache.isotopesInfo) {
          this.toParts();
          this.cache.isotopesInfo = getIsotopesInfo(this.cache.parts);
        }
        return this.cache.isotopesInfo;
      }

      /**
       * Get a canonized MF
       */
      toMF() {
        if (!this.cache.mf) {
          this.toParts();
          this.cache.mf = partsToMF(this.cache.parts);
        }
        return this.cache.mf;
      }

      /**
       * Get a canonized MF
       */
      toNeutralMF() {
        if (!this.cache.neutralMF) {
          this.toParts();
          this.cache.neutralMF = partsToMF(this.cache.parts, {
            neutral: true
          });
        }
        return this.cache.neutralMF;
      }
      canonize() {
        this.toParts();
        this.cache.displayed = partsToDisplay(this.cache.parts);
        this.cache.html = undefined;
      }
    }

    /**
     * Filter the array of peaks
     * @param {array} peaks - array of all the peaks
     * @param {object} [options={}]
     * @param {number} [options.from] - min X value of the window to consider
     * @param {number} [options.to] - max X value of the window to consider
     * @param {number} [options.threshold=0.01] - minimal intensity compare to base peak
     * @param {number} [options.limit=undefined] - maximal number of peaks (based on intensity)
     * @param {number} [options.sumValue] // if sumValue is defined, maxValue is ignored
     * @returns {array} - copy of peaks with 'close' annotation
     */

    function getPeaks(peaks) {
      let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      const {
        from = xyObjectMinXPoint(peaks).x,
        to = xyObjectMaxXPoint(peaks).x,
        threshold = 0.01,
        limit,
        sumValue
      } = options;
      let maxY = Number.MIN_SAFE_INTEGER;
      for (let peak of peaks) {
        if (peak.y > maxY) maxY = peak.y;
      }
      let minY = maxY * threshold;
      peaks = peaks.filter(peak => peak.x >= from && peak.x <= to && peak.y >= minY);
      if (limit && peaks.length > limit) {
        peaks.sort((a, b) => b.y - a.y);
        peaks = peaks.slice(0, limit);
      }
      if (sumValue) {
        peaks = JSON.parse(JSON.stringify(peaks));
        const currentSum = xyObjectSumY(peaks);
        const ratio = sumValue / currentSum;
        peaks.forEach(peak => peak.y *= ratio);
      }
      return peaks.sort((a, b) => a.x - b.x);
    }

    /**
     * Filter the array of peaks
     * @param {array} peaks - array of all the peaks
     * @param {string} mf - Molecular formula of the parent molecule
     * @param {object} [options={}]
     * @param {number} [options.from] - min X value of the window to consider
     * @param {number} [options.to] - max X value of the window to consider
     * @param {number} [options.threshold=0.01] - minimal intensity compare to base peak
     * @param {number} [options.limit=undefined] - maximal number of peaks (based on intensity)
     * @param {string} [options.ionizations]
     * @param {number} [options.precision]
     * @returns {array} - copy of peaks with 'close' annotation
     */

    async function getFragmentPeaks(peaks, mf) {
      let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
      const emdb = new EMDB$1();
      const {
        ionizations = '',
        precision
      } = options;
      const mfInfo = new MF(mf).getInfo();
      const ranges = Object.keys(mfInfo.atoms).map(key => `${key}0-${mfInfo.atoms[key]}`).join(' ');
      peaks = getPeaks(peaks, options);
      for (let peak of peaks) {
        peak.mfs = (await emdb.fromMonoisotopicMass(peak.x, {
          precision,
          ranges,
          ionizations
        })).mfs;
      }
      peaks = peaks.filter(peak => peak.mfs.length > 0);
      return peaks;
    }

    /**
     * Remove an integer number of time the specifiedd monoisotopic mass
     * Mass remainder analysis (MARA): https://doi.org/10.1021/acs.analchem.7b04730
     * @param {object} spectrum
     * @param {number} mass
     * @param {object} [options={}
     * @param {number} [options.delta=0.001]
     */
    function getMassRemainder(spectrum, mass) {
      let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
      const {
        delta = 0.001
      } = options;
      const x = spectrum.x.slice();
      const y = spectrum.y;
      for (let i = 0; i < x.length; i++) {
        const factor = Math.floor(x[i] / mass);
        x[i] = x[i] - factor * mass;
      }
      // we sort and join
      return xyJoinX(xySortX({
        x,
        y
      }), {
        delta
      });
    }

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

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

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

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

    var mlStat$1 = {};

    var array$4 = {};

    (function (exports) {

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    var matrix$1 = {};

    (function (exports) {

      var arrayStat = array$4;
      function compareNumbers(a, b) {
        return a - b;
      }
      exports.max = function max(matrix) {
        var max = -Infinity;
        for (var i = 0; i < matrix.length; i++) {
          for (var j = 0; j < matrix[i].length; j++) {
            if (matrix[i][j] > max) max = matrix[i][j];
          }
        }
        return max;
      };
      exports.min = function min(matrix) {
        var min = Infinity;
        for (var i = 0; i < matrix.length; i++) {
          for (var j = 0; j < matrix[i].length; j++) {
            if (matrix[i][j] < min) min = matrix[i][j];
          }
        }
        return min;
      };
      exports.minMax = function minMax(matrix) {
        var min = Infinity;
        var max = -Infinity;
        for (var i = 0; i < matrix.length; i++) {
          for (var j = 0; j < matrix[i].length; j++) {
            if (matrix[i][j] < min) min = matrix[i][j];
            if (matrix[i][j] > max) max = matrix[i][j];
          }
        }
        return {
          min: min,
          max: max
        };
      };
      exports.entropy = function entropy(matrix, eps) {
        if (typeof eps === 'undefined') {
          eps = 0;
        }
        var sum = 0,
          l1 = matrix.length,
          l2 = matrix[0].length;
        for (var i = 0; i < l1; i++) {
          for (var j = 0; j < l2; j++) {
            sum += matrix[i][j] * Math.log(matrix[i][j] + eps);
          }
        }
        return -sum;
      };
      exports.mean = function mean(matrix, dimension) {
        if (typeof dimension === 'undefined') {
          dimension = 0;
        }
        var rows = matrix.length,
          cols = matrix[0].length,
          theMean,
          N,
          i,
          j;
        if (dimension === -1) {
          theMean = [0];
          N = rows * cols;
          for (i = 0; i < rows; i++) {
            for (j = 0; j < cols; j++) {
              theMean[0] += matrix[i][j];
            }
          }
          theMean[0] /= N;
        } else if (dimension === 0) {
          theMean = new Array(cols);
          N = rows;
          for (j = 0; j < cols; j++) {
            theMean[j] = 0;
            for (i = 0; i < rows; i++) {
              theMean[j] += matrix[i][j];
            }
            theMean[j] /= N;
          }
        } else if (dimension === 1) {
          theMean = new Array(rows);
          N = cols;
          for (j = 0; j < rows; j++) {
            theMean[j] = 0;
            for (i = 0; i < cols; i++) {
              theMean[j] += matrix[j][i];
            }
            theMean[j] /= N;
          }
        } else {
          throw new Error('Invalid dimension');
        }
        return theMean;
      };
      exports.sum = function sum(matrix, dimension) {
        if (typeof dimension === 'undefined') {
          dimension = 0;
        }
        var rows = matrix.length,
          cols = matrix[0].length,
          theSum,
          i,
          j;
        if (dimension === -1) {
          theSum = [0];
          for (i = 0; i < rows; i++) {
            for (j = 0; j < cols; j++) {
              theSum[0] += matrix[i][j];
            }
          }
        } else if (dimension === 0) {
          theSum = new Array(cols);
          for (j = 0; j < cols; j++) {
            theSum[j] = 0;
            for (i = 0; i < rows; i++) {
              theSum[j] += matrix[i][j];
            }
          }
        } else if (dimension === 1) {
          theSum = new Array(rows);
          for (j = 0; j < rows; j++) {
            theSum[j] = 0;
            for (i = 0; i < cols; i++) {
              theSum[j] += matrix[j][i];
            }
          }
        } else {
          throw new Error('Invalid dimension');
        }
        return theSum;
      };
      exports.product = function product(matrix, dimension) {
        if (typeof dimension === 'undefined') {
          dimension = 0;
        }
        var rows = matrix.length,
          cols = matrix[0].length,
          theProduct,
          i,
          j;
        if (dimension === -1) {
          theProduct = [1];
          for (i = 0; i < rows; i++) {
            for (j = 0; j < cols; j++) {
              theProduct[0] *= matrix[i][j];
            }
          }
        } else if (dimension === 0) {
          theProduct = new Array(cols);
          for (j = 0; j < cols; j++) {
            theProduct[j] = 1;
            for (i = 0; i < rows; i++) {
              theProduct[j] *= matrix[i][j];
            }
          }
        } else if (dimension === 1) {
          theProduct = new Array(rows);
          for (j = 0; j < rows; j++) {
            theProduct[j] = 1;
            for (i = 0; i < cols; i++) {
              theProduct[j] *= matrix[j][i];
            }
          }
        } else {
          throw new Error('Invalid dimension');
        }
        return theProduct;
      };
      exports.standardDeviation = function standardDeviation(matrix, means, unbiased) {
        var vari = exports.variance(matrix, means, unbiased),
          l = vari.length;
        for (var i = 0; i < l; i++) {
          vari[i] = Math.sqrt(vari[i]);
        }
        return vari;
      };
      exports.variance = function variance(matrix, means, unbiased) {
        if (typeof unbiased === 'undefined') {
          unbiased = true;
        }
        means = means || exports.mean(matrix);
        var rows = matrix.length;
        if (rows === 0) return [];
        var cols = matrix[0].length;
        var vari = new Array(cols);
        for (var j = 0; j < cols; j++) {
          var sum1 = 0,
            sum2 = 0,
            x = 0;
          for (var i = 0; i < rows; i++) {
            x = matrix[i][j] - means[j];
            sum1 += x;
            sum2 += x * x;
          }
          if (unbiased) {
            vari[j] = (sum2 - sum1 * sum1 / rows) / (rows - 1);
          } else {
            vari[j] = (sum2 - sum1 * sum1 / rows) / rows;
          }
        }
        return vari;
      };
      exports.median = function median(matrix) {
        var rows = matrix.length,
          cols = matrix[0].length;
        var medians = new Array(cols);
        for (var i = 0; i < cols; i++) {
          var data = new Array(rows);
          for (var j = 0; j < rows; j++) {
            data[j] = matrix[j][i];
          }
          data.sort(compareNumbers);
          var N = data.length;
          if (N % 2 === 0) {
            medians[i] = (data[N / 2] + data[N / 2 - 1]) * 0.5;
          } else {
            medians[i] = data[Math.floor(N / 2)];
          }
        }
        return medians;
      };
      exports.mode = function mode(matrix) {
        var rows = matrix.length,
          cols = matrix[0].length,
          modes = new Array(cols),
          i,
          j;
        for (i = 0; i < cols; i++) {
          var itemCount = new Array(rows);
          for (var k = 0; k < rows; k++) {
            itemCount[k] = 0;
          }
          var itemArray = new Array(rows);
          var count = 0;
          for (j = 0; j < rows; j++) {
            var index = itemArray.indexOf(matrix[j][i]);
            if (index >= 0) {
              itemCount[index]++;
            } else {
              itemArray[count] = matrix[j][i];
              itemCount[count] = 1;
              count++;
            }
          }
          var maxValue = 0,
            maxIndex = 0;
          for (j = 0; j < count; j++) {
            if (itemCount[j] > maxValue) {
              maxValue = itemCount[j];
              maxIndex = j;
            }
          }
          modes[i] = itemArray[maxIndex];
        }
        return modes;
      };
      exports.skewness = function skewness(matrix, unbiased) {
        if (typeof unbiased === 'undefined') unbiased = true;
        var means = exports.mean(matrix);
        var n = matrix.length,
          l = means.length;
        var skew = new Array(l);
        for (var j = 0; j < l; j++) {
          var s2 = 0,
            s3 = 0;
          for (var i = 0; i < n; i++) {
            var dev = matrix[i][j] - means[j];
            s2 += dev * dev;
            s3 += dev * dev * dev;
          }
          var m2 = s2 / n;
          var m3 = s3 / n;
          var g = m3 / Math.pow(m2, 3 / 2);
          if (unbiased) {
            var a = Math.sqrt(n * (n - 1));
            var b = n - 2;
            skew[j] = a / b * g;
          } else {
            skew[j] = g;
          }
        }
        return skew;
      };
      exports.kurtosis = function kurtosis(matrix, unbiased) {
        if (typeof unbiased === 'undefined') unbiased = true;
        var means = exports.mean(matrix);
        var n = matrix.length,
          m = matrix[0].length;
        var kurt = new Array(m);
        for (var j = 0; j < m; j++) {
          var s2 = 0,
            s4 = 0;
          for (var i = 0; i < n; i++) {
            var dev = matrix[i][j] - means[j];
            s2 += dev * dev;
            s4 += dev * dev * dev * dev;
          }
          var m2 = s2 / n;
          var m4 = s4 / n;
          if (unbiased) {
            var v = s2 / (n - 1);
            var a = n * (n + 1) / ((n - 1) * (n - 2) * (n - 3));
            var b = s4 / (v * v);
            var c = (n - 1) * (n - 1) / ((n - 2) * (n - 3));
            kurt[j] = a * b - 3 * c;
          } else {
            kurt[j] = m4 / (m2 * m2) - 3;
          }
        }
        return kurt;
      };
      exports.standardError = function standardError(matrix) {
        var samples = matrix.length;
        var standardDeviations = exports.standardDeviation(matrix);
        var l = standardDeviations.length;
        var standardErrors = new Array(l);
        var sqrtN = Math.sqrt(samples);
        for (var i = 0; i < l; i++) {
          standardErrors[i] = standardDeviations[i] / sqrtN;
        }
        return standardErrors;
      };
      exports.covariance = function covariance(matrix, dimension) {
        return exports.scatter(matrix, undefined, dimension);
      };
      exports.scatter = function scatter(matrix, divisor, dimension) {
        if (typeof dimension === 'undefined') {
          dimension = 0;
        }
        if (typeof divisor === 'undefined') {
          if (dimension === 0) {
            divisor = matrix.length - 1;
          } else if (dimension === 1) {
            divisor = matrix[0].length - 1;
          }
        }
        var means = exports.mean(matrix, dimension);
        var rows = matrix.length;
        if (rows === 0) {
          return [[]];
        }
        var cols = matrix[0].length,
          cov,
          i,
          j,
          s,
          k;
        if (dimension === 0) {
          cov = new Array(cols);
          for (i = 0; i < cols; i++) {
            cov[i] = new Array(cols);
          }
          for (i = 0; i < cols; i++) {
            for (j = i; j < cols; j++) {
              s = 0;
              for (k = 0; k < rows; k++) {
                s += (matrix[k][j] - means[j]) * (matrix[k][i] - means[i]);
              }
              s /= divisor;
              cov[i][j] = s;
              cov[j][i] = s;
            }
          }
        } else if (dimension === 1) {
          cov = new Array(rows);
          for (i = 0; i < rows; i++) {
            cov[i] = new Array(rows);
          }
          for (i = 0; i < rows; i++) {
            for (j = i; j < rows; j++) {
              s = 0;
              for (k = 0; k < cols; k++) {
                s += (matrix[j][k] - means[j]) * (matrix[i][k] - means[i]);
              }
              s /= divisor;
              cov[i][j] = s;
              cov[j][i] = s;
            }
          }
        } else {
          throw new Error('Invalid dimension');
        }
        return cov;
      };
      exports.correlation = function correlation(matrix) {
        var means = exports.mean(matrix),
          standardDeviations = exports.standardDeviation(matrix, true, means),
          scores = exports.zScores(matrix, means, standardDeviations),
          rows = matrix.length,
          cols = matrix[0].length,
          i,
          j;
        var cor = new Array(cols);
        for (i = 0; i < cols; i++) {
          cor[i] = new Array(cols);
        }
        for (i = 0; i < cols; i++) {
          for (j = i; j < cols; j++) {
            var c = 0;
            for (var k = 0, l = scores.length; k < l; k++) {
              c += scores[k][j] * scores[k][i];
            }
            c /= rows - 1;
            cor[i][j] = c;
            cor[j][i] = c;
          }
        }
        return cor;
      };
      exports.zScores = function zScores(matrix, means, standardDeviations) {
        means = means || exports.mean(matrix);
        if (typeof standardDeviations === 'undefined') standardDeviations = exports.standardDeviation(matrix, true, means);
        return exports.standardize(exports.center(matrix, means, false), standardDeviations, true);
      };
      exports.center = function center(matrix, means, inPlace) {
        means = means || exports.mean(matrix);
        var result = matrix,
          l = matrix.length,
          i,
          j,
          jj;
        if (!inPlace) {
          result = new Array(l);
          for (i = 0; i < l; i++) {
            result[i] = new Array(matrix[i].length);
          }
        }
        for (i = 0; i < l; i++) {
          var row = result[i];
          for (j = 0, jj = row.length; j < jj; j++) {
            row[j] = matrix[i][j] - means[j];
          }
        }
        return result;
      };
      exports.standardize = function standardize(matrix, standardDeviations, inPlace) {
        if (typeof standardDeviations === 'undefined') standardDeviations = exports.standardDeviation(matrix);
        var result = matrix,
          l = matrix.length,
          i,
          j,
          jj;
        if (!inPlace) {
          result = new Array(l);
          for (i = 0; i < l; i++) {
            result[i] = new Array(matrix[i].length);
          }
        }
        for (i = 0; i < l; i++) {
          var resultRow = result[i];
          var sourceRow = matrix[i];
          for (j = 0, jj = resultRow.length; j < jj; j++) {
            if (standardDeviations[j] !== 0 && !isNaN(standardDeviations[j])) {
              resultRow[j] = sourceRow[j] / standardDeviations[j];
            }
          }
        }
        return result;
      };
      exports.weightedVariance = function weightedVariance(matrix, weights) {
        var means = exports.mean(matrix);
        var rows = matrix.length;
        if (rows === 0) return [];
        var cols = matrix[0].length;
        var vari = new Array(cols);
        for (var j = 0; j < cols; j++) {
          var sum = 0;
          var a = 0,
            b = 0;
          for (var i = 0; i < rows; i++) {
            var z = matrix[i][j] - means[j];
            var w = weights[i];
            sum += w * (z * z);
            b += w;
            a += w * w;
          }
          vari[j] = sum * (b / (b * b - a));
        }
        return vari;
      };
      exports.weightedMean = function weightedMean(matrix, weights, dimension) {
        if (typeof dimension === 'undefined') {
          dimension = 0;
        }
        var rows = matrix.length;
        if (rows === 0) return [];
        var cols = matrix[0].length,
          means,
          i,
          ii,
          j,
          w,
          row;
        if (dimension === 0) {
          means = new Array(cols);
          for (i = 0; i < cols; i++) {
            means[i] = 0;
          }
          for (i = 0; i < rows; i++) {
            row = matrix[i];
            w = weights[i];
            for (j = 0; j < cols; j++) {
              means[j] += row[j] * w;
            }
          }
        } else if (dimension === 1) {
          means = new Array(rows);
          for (i = 0; i < rows; i++) {
            means[i] = 0;
          }
          for (j = 0; j < rows; j++) {
            row = matrix[j];
            w = weights[j];
            for (i = 0; i < cols; i++) {
              means[j] += row[i] * w;
            }
          }
        } else {
          throw new Error('Invalid dimension');
        }
        var weightSum = arrayStat.sum(weights);
        if (weightSum !== 0) {
          for (i = 0, ii = means.length; i < ii; i++) {
            means[i] /= weightSum;
          }
        }
        return means;
      };
      exports.weightedCovariance = function weightedCovariance(matrix, weights, means, dimension) {
        dimension = dimension || 0;
        means = means || exports.weightedMean(matrix, weights, dimension);
        var s1 = 0,
          s2 = 0;
        for (var i = 0, ii = weights.length; i < ii; i++) {
          s1 += weights[i];
          s2 += weights[i] * weights[i];
        }
        var factor = s1 / (s1 * s1 - s2);
        return exports.weightedScatter(matrix, weights, means, factor, dimension);
      };
      exports.weightedScatter = function weightedScatter(matrix, weights, means, factor, dimension) {
        dimension = dimension || 0;
        means = means || exports.weightedMean(matrix, weights, dimension);
        if (typeof factor === 'undefined') {
          factor = 1;
        }
        var rows = matrix.length;
        if (rows === 0) {
          return [[]];
        }
        var cols = matrix[0].length,
          cov,
          i,
          j,
          k,
          s;
        if (dimension === 0) {
          cov = new Array(cols);
          for (i = 0; i < cols; i++) {
            cov[i] = new Array(cols);
          }
          for (i = 0; i < cols; i++) {
            for (j = i; j < cols; j++) {
              s = 0;
              for (k = 0; k < rows; k++) {
                s += weights[k] * (matrix[k][j] - means[j]) * (matrix[k][i] - means[i]);
              }
              cov[i][j] = s * factor;
              cov[j][i] = s * factor;
            }
          }
        } else if (dimension === 1) {
          cov = new Array(rows);
          for (i = 0; i < rows; i++) {
            cov[i] = new Array(rows);
          }
          for (i = 0; i < rows; i++) {
            for (j = i; j < rows; j++) {
              s = 0;
              for (k = 0; k < cols; k++) {
                s += weights[k] * (matrix[j][k] - means[j]) * (matrix[i][k] - means[i]);
              }
              cov[i][j] = s * factor;
              cov[j][i] = s * factor;
            }
          }
        } else {
          throw new Error('Invalid dimension');
        }
        return cov;
      };
    })(matrix$1);

    var array$3 = mlStat$1.array = array$4;
    mlStat$1.matrix = matrix$1;

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

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

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

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

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

    /**
     * @param {object}   [options={}]
     * @param {object}   [options.minCharge=1]
     * @param {object}   [options.maxCharge=10]
     * @param {object}   [options.similarity={}]
     * @param {object}   [options.similarity.widthBottom]
     * @param {object}   [options.similarity.widthTop]
     * @param {object}   [options.similarity.widthFunction] - function called with mass that should return an object width containing top and bottom
     * @param {object}   [options.similarity.zone={}]
     * @param {object}   [options.similarity.zone.low=-0.5] - window shift based on observed monoisotopic mass
     * @param {object}   [options.similarity.zone.high=2.5] - to value for the comparison window
     * @param {object}   [options.similarity.common]
     */

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

    /**
     * When a spectrum is continous ?
     * - has more than 100 points
     * - deltaX change can not be more than a factor 2
     * - deltaX may not be larger than 0.1
     * - if y is zero it does not count
     * @param {object} spectrum
     * @param {object} [options={}]
     * @param {number} [options.minLength=100]
     * @param {number} [options.relativeHeightThreshold=0.001] // Under this value the
     * @param {number} [options.maxDeltaRatio=3]
     */

    function isContinuous(spectrum) {
      let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      const {
        minLength = 100,
        maxDeltaRatio = 3,
        relativeHeightThreshold = 0.001
      } = options;
      const minHeight = xMaxValue$3(spectrum.data.y) * relativeHeightThreshold;
      const minRadio = 1 / maxDeltaRatio;
      const maxRatio = 1 * maxDeltaRatio;
      if (spectrum.continuous === undefined) {
        let xs = spectrum.data.x;
        let ys = spectrum.data.y;
        if (xs.length < minLength) {
          spectrum.continuous = false;
        } else {
          let previousDelta = xs[1] - xs[0];
          spectrum.continuous = true;
          let success = 0;
          let failed = 0;
          for (let i = 0; i < xs.length - 1; i++) {
            if (ys[i] < minHeight || ys[i + 1] < minHeight) {
              previousDelta = 0;
              continue;
            }
            let delta = xs[i + 1] - xs[i];
            if (previousDelta) {
              let ratio = delta / previousDelta;
              if ((Math.abs(delta) > 0.1 || ratio < minRadio || ratio > maxRatio) && ys[i] !== 0 && ys[i + 1] !== 0) {
                failed++;
              } else {
                success++;
              }
            }
            previousDelta = delta;
          }
          if (success / failed < 10) {
            spectrum.continuous = false;
          }
        }
      }
      return spectrum.continuous;
    }

    var IDX = 256,
      HEX = [],
      BUFFER;
    while (IDX--) HEX[IDX] = (IDX + 256).toString(16).substring(1);
    function v4() {
      var i = 0,
        num,
        out = '';
      if (!BUFFER || IDX + 16 > 256) {
        BUFFER = Array(i = 256);
        while (i--) BUFFER[i] = 256 * Math.random() | 0;
        i = IDX = 0;
      }
      for (; i < 16; i++) {
        num = BUFFER[IDX + i];
        if (i == 6) out += HEX[num & 15 | 64];else if (i == 8) out += HEX[num & 63 | 128];else out += HEX[num];
        if (i & 1 && i > 1 && i < 11) out += '-';
      }
      IDX++;
      return out;
    }

    /**
     * Apply Savitzky Golay algorithm
     * @param [ys] Array of y values
     * @param [xs] Array of X or deltaX
     * @return  Array containing the new ys (same length)
     */
    function sgg(ys, xs) {
      let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
      let {
        windowSize = 9,
        derivative = 0,
        polynomial = 3
      } = options;
      if (windowSize % 2 === 0 || windowSize < 5 || !Number.isInteger(windowSize)) {
        throw new RangeError('Invalid window size (should be odd and at least 5 integer number)');
      }
      if (!isAnyArray$3(ys)) {
        throw new TypeError('Y values must be an array');
      }
      if (typeof xs === 'undefined') {
        throw new TypeError('X must be defined');
      }
      if (windowSize > ys.length) {
        throw new RangeError(`Window size is higher than the data length ${windowSize}>${ys.length}`);
      }
      if (derivative < 0 || !Number.isInteger(derivative)) {
        throw new RangeError('Derivative should be a positive integer');
      }
      if (polynomial < 1 || !Number.isInteger(polynomial)) {
        throw new RangeError('Polynomial should be a positive integer');
      }
      if (polynomial >= 6) {
        // eslint-disable-next-line no-console
        console.warn('You should not use polynomial grade higher than 5 if you are' + ' not sure that your data arises from such a model. Possible polynomial oscillation problems');
      }
      let half = Math.floor(windowSize / 2);
      let np = ys.length;
      let ans = new Float64Array(np);
      let weights = fullWeights(windowSize, polynomial, derivative);
      let hs = 0;
      let constantH = true;
      if (isAnyArray$3(xs)) {
        constantH = false;
      } else {
        hs = Math.pow(xs, derivative);
      }
      //For the borders
      for (let i = 0; i < half; i++) {
        let wg1 = weights[half - i - 1];
        let wg2 = weights[half + i + 1];
        let d1 = 0;
        let d2 = 0;
        for (let l = 0; l < windowSize; l++) {
          d1 += wg1[l] * ys[l];
          d2 += wg2[l] * ys[np - windowSize + l];
        }
        if (constantH) {
          ans[half - i - 1] = d1 / hs;
          ans[np - half + i] = d2 / hs;
        } else {
          hs = getHs(xs, half - i - 1, half, derivative);
          ans[half - i - 1] = d1 / hs;
          hs = getHs(xs, np - half + i, half, derivative);
          ans[np - half + i] = d2 / hs;
        }
      }
      //For the internal points
      let wg = weights[half];
      for (let i = windowSize; i <= np; i++) {
        let d = 0;
        for (let l = 0; l < windowSize; l++) d += wg[l] * ys[l + i - windowSize];
        if (!constantH) {
          hs = getHs(xs, i - half - 1, half, derivative);
        }
        ans[i - half - 1] = d / hs;
      }
      return ans;
    }
    function getHs(h, center, half, derivative) {
      let hs = 0;
      let count = 0;
      for (let i = center - half; i < center + half; i++) {
        if (i >= 0 && i < h.length - 1) {
          hs += h[i + 1] - h[i];
          count++;
        }
      }
      return Math.pow(hs / count, derivative);
    }
    function gramPoly(i, m, k, s) {
      let Grampoly = 0;
      if (k > 0) {
        Grampoly = (4 * k - 2) / (k * (2 * m - k + 1)) * (i * gramPoly(i, m, k - 1, s) + s * gramPoly(i, m, k - 1, s - 1)) - (k - 1) * (2 * m + k) / (k * (2 * m - k + 1)) * gramPoly(i, m, k - 2, s);
      } else {
        if (k === 0 && s === 0) {
          Grampoly = 1;
        } else {
          Grampoly = 0;
        }
      }
      return Grampoly;
    }
    function genFact(a, b) {
      let gf = 1;
      if (a >= b) {
        for (let j = a - b + 1; j <= a; j++) {
          gf *= j;
        }
      }
      return gf;
    }
    function weight(i, t, m, n, s) {
      let sum = 0;
      for (let k = 0; k <= n; k++) {
        sum += (2 * k + 1) * (genFact(2 * m, k) / genFact(2 * m + k + 1, k + 1)) * gramPoly(i, m, k, 0) * gramPoly(t, m, k, s);
      }
      return sum;
    }
    /**
     * @private
     * @param m  Number of points
     * @param n  Polynomial grade
     * @param s  Derivative
     */
    function fullWeights(m, n, s) {
      let weights = new Array(m);
      let 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;
    }

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

    /**
     * Global spectra deconvolution
     * @param  data - Object data with x and y arrays. Values in x has to be growing
     * @param {number} [options.broadRatio = 0.00] - If `broadRatio` is higher than 0, then all the peaks which second derivative
     * smaller than `broadRatio * maxAbsSecondDerivative` will be marked with the soft mask equal to true.

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

    /**
     * @param {object}   [options={}]
     * @param {object}   [options.min=1]
     * @param {object}   [options.max=10]
     * @param {object}   [options.low=-1]
     * @param {object}   [options.high=1]
     * @param {object}   [options.precision=100]
     */

    const NEUTRON_MASS = 1;
    function appendPeaksCharge(peaks) {
      let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      let {
        precision = 100,
        low = -1,
        high = 1,
        min: minCharge = 1,
        max: maxCharge = 10
      } = options;
      let fromCharge = minCharge * maxCharge > 0 ? Math.round(Math.min(Math.abs(minCharge), Math.abs(maxCharge))) : 1;
      let toCharge = Math.round(Math.max(Math.abs(minCharge), Math.abs(maxCharge)));
      let fromIsotope = Math.ceil(low);
      let toIsotope = Math.floor(high);
      let numberIsotopes = toIsotope - fromIsotope + 1;
      let isotopeIntensity = 1 / numberIsotopes;
      let fromIndex = 0;
      let localFromIndex = 0;
      let localToIndex = 0;
      for (let peakIndex = 0; peakIndex < peaks.length; peakIndex++) {
        let peak = peaks[peakIndex];
        let targetMass = peak.x;
        localFromIndex = fromIndex;
        let bestCharge = fromCharge;
        let bestChargeMatch = 0;
        for (let charge = fromCharge; charge < toCharge + 1; charge++) {
          let theoreticalPositions = {
            x: [],
            y: new Array(numberIsotopes).fill(isotopeIntensity)
          };
          let massRange = precision * 1e-6 * targetMass;
          for (let isotopePosition = fromIsotope; isotopePosition < toIsotope + 1; isotopePosition++) {
            theoreticalPositions.x.push(targetMass + isotopePosition * NEUTRON_MASS / charge);
          }
          let fromMass = targetMass + low / Math.abs(charge) - massRange;
          let toMass = targetMass + high / Math.abs(charge) + massRange;
          if (charge === 1) {
            // we may move the fromIndex
            while (peaks[fromIndex].x < fromMass) {
              fromIndex++;
            }
          }

          /*
           * Find the from / to index for the specific peak and specific charge
           */
          while (peaks[localFromIndex].x < fromMass) {
            localFromIndex++;
          }
          localToIndex = localFromIndex;
          let localHeightSum = 0;
          while (localToIndex < peaks.length && peaks[localToIndex].x < toMass) {
            localHeightSum += peaks[localToIndex].y;
            localToIndex++;
          }
          localToIndex--;

          //  console.log({ localFromIndex, localToIndex });
          /*
            Calculate the overlap for a specific peak and specific charge
          */
          let currentTheoreticalPosition = 0;
          let theoreticalMaxValue = 1 / numberIsotopes;
          let totalMatch = 0;
          for (let index = localFromIndex; index <= localToIndex; index++) {
            let minMass = theoreticalPositions.x[currentTheoreticalPosition] - massRange / charge;
            let maxMass = theoreticalPositions.x[currentTheoreticalPosition] + massRange / charge;
            while (maxMass < peaks[index].x) {
              currentTheoreticalPosition++;
              theoreticalMaxValue = 1 / numberIsotopes;
              minMass = theoreticalPositions.x[currentTheoreticalPosition] - massRange / charge;
              maxMass = theoreticalPositions.x[currentTheoreticalPosition] + massRange / charge;
            }
            while (index < peaks.length && peaks[index].x < minMass) {
              index++;
            }

            //    console.log({ index, minMass, maxMass, massRange, localHeightSum });
            if (index < peaks.length && peaks[index].x <= maxMass) {
              while (index < peaks.length && peaks[index].x <= maxMass) {
                if (peaks[index].x >= minMass && peaks[index].x <= maxMass) {
                  let value = peaks[index].y / localHeightSum;
                  //      console.log({ theoreticalMaxValue, value });
                  value = Math.min(theoreticalMaxValue, value);
                  theoreticalMaxValue -= value;
                  totalMatch += value;
                }
                index++;
              }
              index--;
            }
            if (totalMatch > bestChargeMatch) {
              bestCharge = charge;
              bestChargeMatch = totalMatch;
            }
          }
        }
        peak.charge = bestCharge;
      }
      return peaks;
    }

    /**
     * Filter the array of peaks
     * @param {Spectrum} spectrum - array of all the peaks
     * @param {object} [options={}]
     * @param {object} [options.charge={}]
     * @param {number} [options.charge.min=1]
     * @param {number} [options.charge.max=10]
     * @param {number} [options.charge.low=-1]
     * @param {number} [options.charge.high=1]
     * @param {number} [options.charge.precision=30]
     * @returns {array} - copy of peaks with 'close' annotation
     */

    function peakPicking(spectrum) {
      let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      const {
        charge: chargeOptions = {}
      } = options;
      if (!spectrum.peaks || spectrum.peaks.length === 0) {
        spectrum.peaks = [];
        const keys = Object.keys(spectrum.data).filter(key => key !== 'x' && key !== 'y');
        if (spectrum.isContinuous()) {
          // some experimental data are really problematic and we need to add this line
          const data = xyEnsureGrowingX(spectrum.data);
          const gsdPeaks = gsd(data, {
            minMaxRatio: 0.00025,
            // Threshold to determine if a given peak should be considered as a noise
            realTopDetection: true,
            smoothY: false,
            sgOptions: {
              windowSize: 7,
              polynomial: 3
            }
          });
          for (let gsdPeak of gsdPeaks) {
            const peak = {
              x: gsdPeak.x,
              y: gsdPeak.y,
              width: gsdPeak.width
            };
            for (let key of keys) {
              peak[key] = spectrum.data[key][gsdPeak.index];
            }
            spectrum.peaks.push(peak);
          }
        } else {
          spectrum.peaks = [];
          let data = spectrum.data;
          for (let i = 0; i < data.x.length; i++) {
            const peak = {
              x: data.x[i],
              y: data.y[i],
              width: 0
            };
            for (let key of keys) {
              peak[key] = spectrum.data[key][i];
            }
            spectrum.peaks.push(peak);
          }
        }
        // required and linked to https://github.com/mljs/global-spectral-deconvolution/issues/17
        spectrum.peaks = spectrum.peaks.filter(peak => !isNaN(peak.x) && !isNaN(peak.y));
        appendPeaksCharge(spectrum.peaks, chargeOptions);
      }
      return spectrum.peaks;
    }

    function maybeToPrecision$1(value, digits) {
      if (value < 0) {
        value = 0 - value;
        if (typeof digits === 'number') {
          return `- ${value.toPrecision(digits)}`;
        } else {
          return `- ${value.toString()}`;
        }
      } else {
        if (typeof digits === 'number') {
          return value.toPrecision(digits);
        } else {
          return value.toString();
        }
      }
    }

    function checkArraySize$1(x, y) {
      if (!isAnyArray$3(x) || !isAnyArray$3(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$1 {
      constructor() {
        if (new.target === BaseRegression$1) {
          throw new Error('BaseRegression must be subclassed');
        }
      }
      predict(x) {
        if (typeof x === 'number') {
          return this._predict(x);
        } else if (isAnyArray$3(x)) {
          const y = [];
          for (let i = 0; i < x.length; i++) {
            y.push(this._predict(x[i]));
          }
          return y;
        } else {
          throw new TypeError('x must be a number or array');
        }
      }
      _predict() {
        throw new Error('_predict must be implemented');
      }
      train() {
        // Do nothing for this package
      }
      toString() {
        return '';
      }
      toLaTeX() {
        return '';
      }

      /**
       * Return the correlation coefficient of determination (r) and chi-square.
       * @param {Array<number>} x
       * @param {Array<number>} y
       * @return {object}
       */
      score(x, y) {
        if (!isAnyArray$3(x) || !isAnyArray$3(y) || x.length !== y.length) {
          throw new Error('x and y must be arrays of the same length');
        }
        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: r,
          r2: r * r,
          chi2: chi2,
          rmsd: Math.sqrt(rmsd / n)
        };
      }
    }

    class SimpleLinearRegression extends BaseRegression$1 {
      constructor(x, y) {
        super();
        if (x === true) {
          this.slope = y.slope;
          this.intercept = y.intercept;
          this.coefficients = [y.intercept, y.slope];
        } else {
          checkArraySize$1(x, y);
          regress$1(this, x, y);
        }
      }
      toJSON() {
        return {
          name: 'simpleLinearRegression',
          slope: this.slope,
          intercept: this.intercept
        };
      }
      _predict(x) {
        return this.slope * x + this.intercept;
      }
      computeX(y) {
        return (y - this.intercept) / this.slope;
      }
      toString(precision) {
        let result = 'f(x) = ';
        if (this.slope !== 0) {
          const xFactor = maybeToPrecision$1(this.slope, precision);
          result += `${xFactor === '1' ? '' : `${xFactor} * `}x`;
          if (this.intercept !== 0) {
            const absIntercept = Math.abs(this.intercept);
            const operator = absIntercept === this.intercept ? '+' : '-';
            result += ` ${operator} ${maybeToPrecision$1(absIntercept, precision)}`;
          }
        } else {
          result += maybeToPrecision$1(this.intercept, precision);
        }
        return result;
      }
      toLaTeX(precision) {
        return this.toString(precision);
      }
      static load(json) {
        if (json.name !== 'simpleLinearRegression') {
          throw new TypeError('not a SLR model');
        }
        return new SimpleLinearRegression(true, json);
      }
    }
    function regress$1(slr, x, y) {
      const n = x.length;
      let xSum = 0;
      let ySum = 0;
      let xSquared = 0;
      let xY = 0;
      for (let i = 0; i < n; i++) {
        xSum += x[i];
        ySum += y[i];
        xSquared += x[i] * x[i];
        xY += x[i] * y[i];
      }
      const numerator = n * xY - xSum * ySum;
      slr.slope = numerator / (n * xSquared - xSum * xSum);
      slr.intercept = 1 / n * ySum - slr.slope * (1 / n) * xSum;
      slr.coefficients = [slr.intercept, slr.slope];
    }

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

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

    /**
     * Class dealing with mass spectra and peak picking
     */
    class Spectrum$1 {
      /**
       *
       * @param {import('cheminfo-types').DataXY} data
       */
      constructor() {
        let data = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {
          x: [],
          y: []
        };
        if (typeof data !== 'object' || !isAnyArray$3(data.x) || !isAnyArray$3(data.y)) {
          throw new TypeError('Spectrum data must be an object with x:[], y:[]');
        }
        this.data = {
          ...data
        };
        Object.defineProperty(this.data, 'xOriginal', {
          enumerable: false,
          writable: true
        });
        if (this.data && this.data.x.length > 0) {
          this.info = {
            minX: xMinValue$1(this.data.x),
            maxX: xMaxValue$3(this.data.x),
            minY: xMinValue$1(this.data.y),
            maxY: xMaxValue$3(this.data.y)
          };
        } else {
          this.info = {
            minX: NaN,
            maxX: NaN,
            minY: NaN,
            maxY: NaN
          };
        }
        this.cache = {};
        this.peaks = [];
      }
      minMaxX() {
        return xMinMaxValues(this.data.x);
      }
      maxY() {
        return xMaxValue$3(this.data.y);
      }
      sumY() {
        if (!this.cache.sumY) {
          this.cache.sumY = xSum$1(this.data.y);
        }
        return this.cache.sumY;
      }
      scaleY() {
        let intensity = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1;
        this.data.y = Array.from(xNormed$1(this.data.y, {
          value: intensity,
          algorithm: 'max'
        }));
        return this;
      }
      rescaleX(callback) {
        this.ensureOriginalX();
        for (let i = 0; i < this.data.x.length; i++) {
          this.data.x[i] = callback(this.data.xOriginal[i]);
        }
        return this;
      }
      ensureOriginalX() {
        if (!this.data.xOriginal) {
          this.data.xOriginal = this.data.x.slice(0);
        }
      }
      normedY() {
        let total = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1;
        this.data.y = xNormed$1(this.data.y, {
          value: total
        });
        return this;
      }
      peakPicking() {
        peakPicking(this);
        return this.peaks;
      }
      peaksWidth() {
        peakPicking(this);
        return peaksWidth(this.peaks);
      }
      getBestPeaks(options) {
        peakPicking(this);
        return getBestPeaks$1(this.peaks, options);
      }
      getPeakChargeBySimilarity(targetMass, options) {
        return getPeakChargeBySimilarity(this, targetMass, options);
      }
      getPeaks(options) {
        peakPicking(this);
        return getPeaks(this.peaks, options);
      }
      getPeaksAsDataXY(options) {
        peakPicking(this);
        const peaks = getPeaks(this.peaks, options);
        return {
          x: peaks.map(peak => peak.x),
          y: peaks.map(peak => peak.y)
        };
      }

      /**
       * Returns also peaks possible for a specific molecular formula
       * @example
       *  const spectrum = new Spectrum({x:[], y:[]})
       *  await spectrum.getFragmentPeaks();
       * @param {string} mf
       * @param {object} options
       * @returns
       */
      getFragmentPeaksFct(mf, options) {
        peakPicking(this);
        return getFragmentPeaks(this.peaks, mf, options);
      }
      isContinuous() {
        return isContinuous(this);
      }

      /**
       * Remove an integer number of time the specifiedd monoisotopic mass
       * Mass remainder analysis (MARA): https://doi.org/10.1021/acs.analchem.7b04730
       */
      getMassRemainderFct(mass) {
        let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
        return getMassRemainder(this.data, mass, options);
      }
    }

    /**
     * Returns the average of cosine distances between vectors a and b
     * @param a - first vector
     * @param b - second vector
     *
     */
    function cosine(a, b) {
      let p = 0;
      let p2 = 0;
      let q2 = 0;
      for (let i = 0; i < a.length; i++) {
        p += a[i] * b[i];
        p2 += a[i] * a[i];
        q2 += b[i] * b[i];
      }
      return p / (Math.sqrt(p2) * Math.sqrt(q2));
    }

    class MSComparator$1 {
      /**
       * Create a class that will be able to get the similarity between 2 spectra
       * The similarity is based on 'cosine' similarity. The goal is 2 prepare 2 vectors
       * on which the similarity is calculated.
       * The vectors are created by taking the mass and the intensity of the peaks.
       * @param {object} [options={}]
       * @param {number} [options.nbPeaks] - Before comparing spectra how many peaks should be kept
       * @param {number} [options.minNbCommonPeaks] - Minimum number of peaks in common to consider any similarity
       * @param {number} [options.minIntensity] - What is the minimal relative intensity to keep a peak
       * @param {number} [options.massPower=3] - High power will give more weight to the mass. If you would prefer to observe fragments you should use a number less than 1
       * @param {number} [options.intensityPower=0.6] - How important is the intensity. By default we don't give to much importance to it
       * @param {number[]} [options.selectedMasses] - List of allowed masses.
       * @param {number|Function} [options.delta=0.1] - Tolerance in Da (u) to consider 2 peaks as aligned. If a function is provided it will be called with the mass of the peak
       */
      constructor() {
        let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
        this.options = {
          massPower: 3,
          intensityPower: 0.6,
          delta: 0.1,
          ...options
        };
        this.cache = new WeakMap();
      }

      /**
       *
       * @param {import('cheminfo-types').DataXY} dataXY1
       * @param {import('cheminfo-types').DataXY} dataXY2
       */
      getSimilarity(dataXY1, dataXY2) {
        const data1 = normalizeAndCacheData(this.cache, dataXY1, this.options);
        const data2 = normalizeAndCacheData(this.cache, dataXY2, this.options);
        let aligned;
        if (this.options.selectedMasses?.length > 0) {
          aligned = xyArrayAlign([data1, data2, {
            x: Float64Array.from(this.options.selectedMasses),
            y: new Float64Array(this.options.selectedMasses.length).fill(1)
          }], {
            delta: this.options.delta,
            requiredY: true
          });
        } else {
          aligned = xyArrayAlign([data1, data2], {
            delta: this.options.delta
          });
        }
        if (this.options.minNbCommonPeaks) {
          let commonPeaks = 0;
          for (let i = 0; i < aligned.ys[0].length; i++) {
            if (aligned.ys[0][i] !== 0 && aligned.ys[1][i] !== 0) {
              commonPeaks++;
            }
          }
          if (commonPeaks < this.options.minNbCommonPeaks) return 0;
        }
        const vector1 = new Float64Array(aligned.x.length);
        const vector2 = new Float64Array(aligned.x.length);
        for (let i = 0; i < aligned.x.length; i++) {
          vector1[i] = aligned.x[i] ** this.options.massPower * aligned.ys[0][i] ** this.options.intensityPower;
          vector2[i] = aligned.x[i] ** this.options.massPower * aligned.ys[1][i] ** this.options.intensityPower;
        }
        return cosine(vector1, vector2);
      }
    }

    /**
     *
     * @param {WeakMap} cache
     * @param {import('cheminfo-types').DataXY} dataXY
     * @param {object} [options={}]
     * @param {number} [options.nbPeaks]
     * @param {number} [options.minIntensity]
     */
    function normalizeAndCacheData(cache, dataXY) {
      let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
      const {
        nbPeaks,
        minIntensity
      } = options;
      if (cache.has(dataXY)) return cache.get(dataXY);
      let data = {
        ...dataXY
      };
      if (xIsMonotonic(data.x) !== 1) {
        data = xySortX(data);
      }
      if (minIntensity !== undefined) {
        data = xyFilterMinYValue(data, minIntensity);
      }
      if (nbPeaks !== undefined) {
        data = xyFilterTopYValues(data, nbPeaks);
      }
      cache.set(dataXY, data);
      return data;
    }

    /**
     *
     * @param {array} bestPeaks
     * @param {object} [options={}]
     * @param {array} [options.mfColors={}]
     * @param {number} [options.numberDigits=5]
     * @param {number} [options.numberMFs=0]
     * @param {boolean} [options.showMF=false]
     * @param {array} [options.mfColors={}]
     * @param {number} [options.charge=1]
     * @param {number} [options.shift=0]
     * @param {object} [options.mfPrefs]
     * @param {number} [options.displayCharge=true]
     * @param {number} [options.displayProperties=[]] Array of properties name to display
     * @returns {Promise}
     */

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

    const JsGraph = {
      getPeaksAnnotation
    };

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

    function unsaturationMatcher(entry) {
      let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      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;
    }

    /**
     * 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) {
      let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      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) {
        if (entry.mw < minMW || entry.mw > maxMW) return false;
      }
      if (entry.em !== undefined) {
        if (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) {
        if (!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;
    }

    function getMsem(em, charge) {
      if (charge > 0) {
        return em / charge - ELECTRON_MASS$1;
      } else if (charge < 0) {
        return em / (charge * -1) + ELECTRON_MASS$1;
      } else {
        return 0;
      }
    }

    /**
     * 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) {
      let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      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 = 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
      };
    }

    function mfDiff(mfString1, mfString2) {
      let mf1 = new MF(mfString1).getInfo().atoms;
      let mf2 = new MF(mfString2).getInfo().atoms;
      let atoms = Object.keys(mf1);
      Object.keys(mf2).forEach(atom => {
        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(mf).toMF();
    }

    function processRange(string, comment) {
      let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
      const {
        limit
      } = options;
      let results = [];
      let parts = string.split(/(-?[0-9]+--?[0-9]+)/).filter(v => v); // remove empty parts

      let position = -1;
      let mfs = [];
      for (let i = 0; i < parts.length; i++) {
        let part = parts[i];
        if (!~part.search(/-?[0-9]--?[0-9]/)) {
          position++;
          mfs[position] = {
            mf: part,
            min: 1,
            max: 1
          };
        } else {
          let min = part.replace(/^(-?[0-9]*)-(-?[0-9]*)/, '$1') >> 0;
          let max = part.replace(/^(-?[0-9]*)-(-?[0-9]*)/, '$2') >> 0;
          mfs[position].min = Math.min(min, max);
          mfs[position].max = Math.max(min, max);
        }
      }
      let currents = new Array(mfs.length);
      for (let i = 0; i < currents.length; i++) {
        currents[i] = mfs[i].min;
      }
      position = 0;
      while (position < currents.length) {
        if (currents[position] < mfs[position].max) {
          results.push(getMF$1(mfs, currents, comment));
          currents[position]++;
          for (let i = 0; i < position; i++) {
            currents[i] = mfs[i].min;
          }
          position = 0;
        } else {
          position++;
        }
        if (results.length > limit) {
          throw Error(`processRange generates to many fragments (over ${limit})`);
        }
      }
      results.push(getMF$1(mfs, currents, comment));
      return results;
    }
    function getMF$1(mfs, currents, comment) {
      let mf = '';
      for (let i = 0; i < mfs.length; i++) {
        if (currents[i] === 0) {
          // TODO we need to remove from currents[i] till we reach another part of the MF
          mf += removeMFLastPart(mfs[i].mf);
        } else {
          mf += mfs[i].mf;
          if (currents[i] !== 1) {
            mf += currents[i];
          }
        }
      }
      if (comment) mf += `$${comment}`;
      return mf;
    }

    /*
       Allows to remove the last part of a MF. Useful when you have something with '0' times.
       C10H -> C10
       C10((Me)N) -> C10
       C10Ala -> C10
       C10Ala((Me)N) -> C10Ala
       */
    function removeMFLastPart(mf) {
      let parenthesis = 0;
      let start = true;
      for (let i = mf.length - 1; i >= 0; i--) {
        let ascii = mf.charCodeAt(i);
        if (ascii > 96 && ascii < 123) {
          // lowercase
          if (!start && !parenthesis) {
            return mf.substr(0, i + 1);
          }
        } else if (ascii > 64 && ascii < 91) {
          // uppercase
          if (!start && !parenthesis) {
            return mf.substr(0, i + 1);
          }
          start = false;
        } else if (ascii === 40) {
          // (
          parenthesis--;
          if (!parenthesis) return mf.substr(0, i);
        } else if (ascii === 41) {
          // )
          parenthesis++;
        } else {
          start = false;
          if (!parenthesis) return mf.substr(0, i + 1);
        }
      }
      return '';
    }

    function preprocessIonizations() {
      let ionizationsString = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
      if (Array.isArray(ionizationsString)) return ionizationsString;
      let ionizations = ionizationsString.split(/ *[.,;\t\r\n]+ */);

      // it is allowed to have ranges in Ionizations. We need to explode them.

      let results = [];
      for (let ionization of ionizations) {
        let parts = processRange(ionization);
        for (let part of parts) {
          let info = new MF(part).getInfo();
          results.push({
            mf: part,
            em: info.monoisotopicMass,
            charge: info.charge,
            atoms: info.atoms
          });
        }
      }
      return results;
    }

    function preprocessRanges(ranges) {
      ranges = JSON.parse(JSON.stringify(ranges));
      if (typeof ranges === 'string') {
        // need to convert to ranges
        let parsed = parse(ranges.replace(/[\r\n\t ]/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
        // 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 Kind.ATOM:
              if (parenthesisLevel === 0 && currentMF) {
                current.mf += currentMF;
                currentMF = '';
              }
              currentMF += item.value;
              break;
            case Kind.ISOTOPE:
              if (parenthesisLevel === 0 && currentMF) {
                current.mf += currentMF;
                currentMF = '';
              }
              currentMF += `[${item.value.isotope}${item.value.atom}]`;
              break;
            case Kind.MULTIPLIER:
              if (parenthesisLevel === 0 && currentMF) {
                current.mf += currentMF + item.value;
                currentMF = '';
              } else {
                currentMF += item.value;
              }
              break;
            case 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 Kind.OPENING_PARENTHESIS:
              parenthesisLevel++;
              currentMF += '(';
              break;
            case Kind.CLOSING_PARENTHESIS:
              parenthesisLevel--;
              currentMF += ')';
              break;
            default:
              throw 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(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 getRangesForFragment(ranges) {
      ranges = JSON.parse(JSON.stringify(ranges));
      if (typeof ranges === 'string') {
        // need to convert to ranges
        let parsed = parse(ranges.replace(/[\r\n\t ]/g, ''));
        let newRanges = [];

        // example ClBr2(CH2)0-2NO
        // the idea is that has 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 Kind.ATOM:
              if (parenthesisLevel === 0 && currentMF) {
                newRanges.push({
                  mf: currentMF
                });
                currentMF = '';
              }
              currentMF += item.value;
              break;
            case Kind.ISOTOPE:
              if (parenthesisLevel === 0 && currentMF) {
                newRanges.push({
                  mf: currentMF
                });
                currentMF = '';
              }
              currentMF += `[${item.value.isotope}${item.value.atom}]`;
              break;
            case Kind.MULTIPLIER:
              if (parenthesisLevel === 0 && currentMF) {
                newRanges.push({
                  mf: currentMF,
                  max: item.value
                });
                currentMF = '';
              } else {
                currentMF += item.value;
              }
              break;
            case 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 Kind.OPENING_PARENTHESIS:
              if (parenthesisLevel === 0 && currentMF) {
                newRanges.push({
                  mf: currentMF
                });
                currentMF = '';
              }
              parenthesisLevel++;
              currentMF += '(';
              break;
            case Kind.CLOSING_PARENTHESIS:
              parenthesisLevel--;
              currentMF += ')';
              break;
            default:
              throw 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(' ');
    }

    /**
     * Returns the closest index of a `target`
     *
     * @param array - array of numbers
     * @param target - target
     * @returns - closest index
     */
    function xFindClosestIndex$3(array, target) {
      let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
      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;
      }
    }

    /**
     * @param {object}         [entry={}]
     * @param {object}         [options={}]
     * @param {object}         [options.ionization={ mf: '', em: 0, charge: 0 }] - ionization method
     * @param {boolean}        [options.forceIonization=false] - If true ignore existing ionizations
     * @param {number}         [options.precision=1000] - The precision on the experimental mass
     * @param {number}         [options.targetMass] - Target mass, allows to calculate error and filter results
     * @param {number[]}       [options.targetMasses] - Target masses: SORTED array of numbers
     * @param {number[]}       [options.targetIntensities] - Target intensities: SORTED array of numbers
     * @param {number}         [options.minEM=0] - Minimal monoisotopic mass
     * @param {number}         [options.maxEM=+Infinity] - Maximal monoisotopic mass
     * @param {number}         [options.minMSEM=0] - Minimal monoisotopic mass observed by mass
     * @param {number}         [options.maxMSEM=+Infinity] - Maximal monoisotopic mass observed by 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 {boolean}        [options.allowNegativeAtoms=false] - Allow to have negative number of atoms
     * @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 {boolean}        [options.atoms] - object of atom:{min, max}
     * @param {Function}       [options.callback] - a function that contains information about the current MF
     * @return {boolean}
     */

    /**
     * We always recalculate msem
     */

    function msemMatcher(entry) {
      let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      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,
        allowNegativeAtoms = false,
        atoms,
        callback
      } = options;
      let msInfo = getMsInfo(entry, {
        ionization,
        forceIonization,
        targetMass
      });
      let ms = msInfo.ms;
      if (entry.em !== undefined) {
        if (entry.em < minEM || entry.em > maxEM) return false;
      }
      if (ms.em !== undefined) {
        if (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) {
        if (!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 = xFindClosestIndex$3(targetMasses, ms.em);
        let closestMass = targetMasses[index];
        msInfo = 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) {
        if (!callback(entry)) return false;
      }
      return msInfo;
    }

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

    /**
     * @param {number}        targetMass - Monoisotopic mass
     * @param {object}        [options={}]
     * @param {number}        [options.maxIterations=10000000] - Maximum number of iterations
     * @param {boolean}       [options.allowNeutral=true]
     * @param {boolean}       [options.uniqueMFs=true]
     * @param {number}        [options.limit=1000] - Maximum number of results
     * @param {string}        [options.ionizations=''] - string containing a comma separated list of modifications
     * @param {string}        [options.ranges='C0-100 H0-100 O0-100 N0-100'] - range of mfs to search
     * @param {number}        [options.precision=100] - Allowed mass range based on precision
     * @param {object}        [options.filter={}]
     * @param {number}        [options.filter.minCharge=-Infinity] - Minimal charge
     * @param {number}        [options.filter.maxCharge=+Infinity] - Maximal charge
     * @param {boolean}       [options.filter.absoluteCharge=false] - If true, the charge is absolute (so between 0 and +Infinity by default)
     * @param {object}        [options.filter.unsaturation={}]
     * @param {number}        [options.filter.unsaturation.min=-Infinity] - Minimal unsaturation
     * @param {number}        [options.filter.unsaturation.max=+Infinity] - Maximal unsaturation
     * @param {boolean}       [options.filter.unsaturation.onlyInteger=false] - Integer unsaturation
     * @param {boolean}       [options.filter.unsaturation.onlyNonInteger=false] - Non integer unsaturation
     * @param {object}        [options.filter.atoms] - object of atom:{min, max}
     * @param {function}      [options.filter.callback] - a function to filter the MF
     * @returns {object}
     */

    function findMFsSync(targetMass) {
      let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      const {
        filter = {},
        maxIterations = 1e8,
        limit = 1000,
        allowNeutral = true,
        // if there is no msem we use em !
        uniqueMFs = false,
        // if there is no msem we use em !
        ranges = [{
          mf: 'C',
          min: 0,
          max: 100
        }, {
          mf: 'H',
          min: 0,
          max: 100
        }, {
          mf: 'O',
          min: 0,
          max: 100
        }, {
          mf: 'N',
          min: 0,
          max: 100
        }]
      } = options;
      let targetMassCache;
      const {
        minCharge = Number.MIN_SAFE_INTEGER,
        maxCharge = Number.MAX_SAFE_INTEGER,
        unsaturation = {}
      } = filter;
      let filterUnsaturation = !!unsaturation;
      // we calculate not the real unsaturation but the one before dividing by 2 + 1
      let fakeMinUnsaturation = unsaturation.min === undefined ? Number.MIN_SAFE_INTEGER : (unsaturation.min - 1) * 2;
      let fakeMaxUnsaturation = unsaturation.max === undefined ? Number.MAX_SAFE_INTEGER : (unsaturation.max - 1) * 2;
      let filterCharge = minCharge !== Number.MIN_SAFE_INTEGER || maxCharge !== Number.MAX_SAFE_INTEGER;
      let advancedFilter;
      if (filter.atoms || filter.callback) {
        advancedFilter = {
          atoms: filter.atoms,
          callback: filter.callback
        };
      }
      let result = {
        mfs: [],
        info: {
          numberMFEvaluated: 0,
          numberResults: 0
        }
      };
      let orderMapping = []; // used to sort the atoms

      // we need to make the processing for all the ionizations
      let ionizations = preprocessIonizations(options.ionizations);
      for (let ionization of ionizations) {
        let currentIonization = {
          currentMonoisotopicMass: ionization.em || 0,
          currentCharge: ionization.charge,
          currentUnsaturation: 0 // we don't take into account the unsaturation of the ionization agent
        };
        // if (DEBUG) console.log('new ionization', ionization.mf, ionization.em, ionization.charge);
        // ionization em and charge will be used to set the first atom value
        let possibilities = preprocessRanges(ranges);
        orderMapping = getOrderMapping$1(possibilities);
        if (possibilities.length === 0) return {
          mfs: []
        };
        targetMassCache = new TargetMassCache(targetMass, possibilities, {
          ...options,
          charge: ionization.charge
        });
        let theEnd = false;
        let maxPosition = possibilities.length;
        let lastPosition = possibilities.length - 1;
        let currentPosition = 0;
        let currentAtom;
        let previousAtom;
        let lastPossibility = possibilities[lastPosition];
        initializePossibilities(possibilities, currentIonization, targetMassCache);

        //  if (DEBUG) console.log('possibilities', possibilities.map((a) => `${a.mf + a.originalMinCount}-${a.originalMaxCount}`));

        let isValid = false; // designed so that the first time it is not a valid solution
        while (!theEnd) {
          if (result.info.numberMFEvaluated++ > maxIterations) {
            throw Error(`Iteration number is over the current maximum of: ${maxIterations}`);
          }
          if (filterUnsaturation) {
            let unsaturationValue = lastPossibility.currentUnsaturation;
            let isOdd = Math.abs(unsaturationValue % 2);
            if (unsaturation.onlyInteger && isOdd === 1 || unsaturation.onlyNonInteger && isOdd === 0 || fakeMinUnsaturation > unsaturationValue || fakeMaxUnsaturation < unsaturationValue) {
              isValid = false;
            }
          }
          if (filterCharge && (lastPossibility.currentCharge < minCharge || lastPossibility.currentCharge > maxCharge)) {
            isValid = false;
          }
          if (isValid) {
            let minMass = targetMassCache.getMinMass(lastPossibility.currentCharge);
            let maxMass = targetMassCache.getMaxMass(lastPossibility.currentCharge);
            if (lastPossibility.currentMonoisotopicMass < minMass || lastPossibility.currentMonoisotopicMass > maxMass) {
              isValid = false;
            }
          }
          if (isValid) {
            result.info.numberResults++;
            let newResult = getResult$1(possibilities, targetMass, allowNeutral, ionization, orderMapping);
            if (advancedFilter) {
              isValid = msemMatcher(newResult, advancedFilter) !== false;
            }
            if (isValid) {
              result.mfs.push(newResult);
              if (result.mfs.length > 2 * limit) {
                if (uniqueMFs) ensureUniqueMF(result);
                result.mfs.sort((a, b) => Math.abs(a.ms.ppm) - Math.abs(b.ms.ppm));
                result.mfs.length = limit;
              }
            }
          }
          isValid = true;
          // we need to setup all the arrays if possible
          while (currentPosition < maxPosition && currentPosition >= 0) {
            currentAtom = possibilities[currentPosition];
            previousAtom = currentPosition === 0 ? currentIonization : possibilities[currentPosition - 1];
            if (currentAtom.currentCount < currentAtom.currentMaxCount) {
              currentAtom.currentCount++;
              updateCurrentAtom(currentAtom, previousAtom);
              if (currentPosition < lastPosition) {
                currentPosition++;
                setCurrentMinMax(possibilities[currentPosition], possibilities[currentPosition - 1], targetMassCache);
              } else {
                break;
              }
            } else {
              currentPosition--;
            }
          }
          if (currentPosition < 0) {
            theEnd = true;
          }
        }
      }
      if (uniqueMFs) ensureUniqueMF(result);
      result.mfs.sort((a, b) => Math.abs(a.ms.ppm) - Math.abs(b.ms.ppm));
      if (result.mfs.length > limit) {
        result.mfs.length = limit;
      }
      result.mfs.forEach(mf => delete mf.currentCounts);
      return result;
    }

    /**
     * Ensure that we have only once the same MF
     * In order to improve the speed we just consider the em
     * @param {object} result
     */
    function ensureUniqueMF(result) {
      result.mfs.sort((a, b) => a.em - b.em);
      let previousEM = 0;
      let bestCounts = [];
      const mfs = [];
      next: for (let current of result.mfs) {
        if (current.em - previousEM > 1e-8) {
          previousEM = current.em;
          bestCounts = current.currentCounts;
          mfs.push(current);
        } else {
          for (let i = 0; i < current.currentCounts.length; i++) {
            // better priority ???
            if (current.currentCounts[i] > bestCounts[i]) {
              mfs.pop();
              mfs.push(current);
              bestCounts = current.currentCounts;
              continue;
            } else if (current.currentCounts[i] < bestCounts[i]) {
              continue next;
            }
          }
        }
      }
      result.mfs = mfs;
    }
    function updateCurrentAtom(currentAtom, previousAtom) {
      currentAtom.currentMonoisotopicMass = previousAtom.currentMonoisotopicMass + currentAtom.em * currentAtom.currentCount;
      currentAtom.currentCharge = previousAtom.currentCharge + currentAtom.charge * currentAtom.currentCount;
      currentAtom.currentUnsaturation = previousAtom.currentUnsaturation + currentAtom.unsaturation * currentAtom.currentCount;
    }
    function getResult$1(possibilities, targetMass, allowNeutralMolecules, ionization, orderMapping) {
      let lastPossibility = possibilities[possibilities.length - 1];
      let result = {
        em: lastPossibility.currentMonoisotopicMass - ionization.em,
        unsaturation: lastPossibility.currentUnsaturation,
        mf: '',
        charge: lastPossibility.currentCharge - ionization.charge,
        ionization,
        atoms: {},
        groups: {},
        currentCounts: possibilities.map(possibility => possibility.currentCount)
      };

      // we check that the first time we meet the ionization group it does not end
      // in the final result

      for (let i = 0; i < possibilities.length; i++) {
        let possibility = possibilities[orderMapping[i]];
        if (possibility.currentCount !== 0) {
          if (possibility.isGroup) {
            if (possibility.currentCount === 1) {
              result.mf += `${possibility.mf}`;
            } else if (possibility.mf.match(/^\([^()]*\)$/)) {
              result.mf += `${possibility.mf}${possibility.currentCount}`;
            } else {
              result.mf += `(${possibility.mf})${possibility.currentCount}`;
            }
            if (result.groups[possibility.mf]) {
              result.groups[possibility.mf] += possibility.currentCount;
            } else {
              result.groups[possibility.mf] = possibility.currentCount;
            }
          } else {
            result.mf += possibility.mf;
            if (possibility.currentCount !== 1) {
              result.mf += possibility.currentCount;
            }
          }
          for (let atom in possibility.atoms) {
            if (result.atoms[atom]) {
              result.atoms[atom] += possibility.atoms[atom] * possibility.currentCount;
            } else {
              result.atoms[atom] = possibility.atoms[atom] * possibility.currentCount;
            }
          }
        }
      }
      result.unsaturation = (result.unsaturation + Math.abs(result.charge)) / 2 + 1;
      result.ms = getMsInfo(result, {
        targetMass,
        allowNeutralMolecules
      }).ms;
      return result;
    }
    function setCurrentMinMax(currentAtom, previousAtom, targetMassCache) {
      // the current min max can only be optimize if the charge will not change anymore
      if (currentAtom.innerCharge === true || currentAtom.charge !== 0) {
        currentAtom.currentMinCount = currentAtom.originalMinCount;
        currentAtom.currentMaxCount = currentAtom.originalMaxCount;
        currentAtom.currentCount = currentAtom.currentMinCount - 1;
      } else {
        // no more change of charge, we can optimize
        let currentMass = previousAtom !== undefined ? previousAtom.currentMonoisotopicMass : 0;
        let currentCharge = previousAtom !== undefined ? previousAtom.currentCharge : 0;
        currentAtom.currentMinCount = Math.max(Math.floor((targetMassCache.getMinMass(currentCharge) - currentMass - currentAtom.maxInnerMass) / currentAtom.em), currentAtom.originalMinCount);
        currentAtom.currentMaxCount = Math.min(Math.floor((targetMassCache.getMaxMass(currentCharge) - currentMass - currentAtom.minInnerMass) / currentAtom.em), currentAtom.originalMaxCount);
        currentAtom.currentCount = currentAtom.currentMinCount - 1;
      }
    }
    function initializePossibilities(possibilities, currentIonization, targetMassCache) {
      for (let i = 0; i < possibilities.length; i++) {
        if (i === 0) {
          updateCurrentAtom(possibilities[i], currentIonization);
          setCurrentMinMax(possibilities[i], currentIonization, targetMassCache);
        } else {
          updateCurrentAtom(possibilities[i], possibilities[i - 1]);
        }
      }
    }
    function getOrderMapping$1(possibilities) {
      let mapping = possibilities.map((p, i) => ({
        atom: p.mf,
        index: i
      }));
      mapping.sort((a, b) => {
        return atomSorter$1(a.atom, b.atom);
      });
      return mapping.map(a => a.index);
    }

    /**
     * @param {number}        targetMass - Monoisotopic mass
     * @param {object}        [options={}]
     * @param {number}        [options.maxIterations=10000000] - Maximum number of iterations
     * @param {boolean}       [options.allowNeutral=true]
     * @param {boolean}       [options.uniqueMFs=true]
     * @param {number}        [options.limit=1000] - Maximum number of results
     * @param {string}        [options.ionizations=''] - string containing a comma separated list of modifications
     * @param {string}        [options.ranges='C0-100 H0-100 O0-100 N0-100'] - range of mfs to search
     * @param {number}        [options.precision=100] - Allowed mass range based on precision
     * @param {object}        [options.filter={}]
     * @param {number}        [options.filter.minCharge=-Infinity] - Minimal charge
     * @param {number}        [options.filter.maxCharge=+Infinity] - Maximal charge
     * @param {boolean}       [options.filter.absoluteCharge=false] - If true, the charge is absolute (so between 0 and +Infinity by default)
     * @param {object}        [options.filter.unsaturation={}]
     * @param {number}        [options.filter.unsaturation.min=-Infinity] - Minimal unsaturation
     * @param {number}        [options.filter.unsaturation.max=+Infinity] - Maximal unsaturation
     * @param {boolean}       [options.filter.unsaturation.onlyInteger=false] - Integer unsaturation
     * @param {boolean}       [options.filter.unsaturation.onlyNonInteger=false] - Non integer unsaturation
     * @param {object}        [options.filter.atoms] - object of atom:{min, max}
     * @param {function}      [options.filter.callback] - a function to filter the MF
     * @returns {Promise}
     */

    async function findMFs(targetMass) {
      let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      return findMFsSync(targetMass, options);
    }

    /**
     *
     * @param {object}   experimentalSpectrum
     * @param {object}   database
     * @param {object}   [options={}]
     * @param {function} [options.onStep] - Callback to do after each step
     * @param {string}   [options.ionizations=''] - string containing a comma separated list of modifications
     * @param {number}   [options.precision=100] - Allowed mass range based on precision
     */
    async function appendFragmentsInfo(experimentalSpectrum, database) {
      let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
      const {
        ionizations,
        onStep,
        precision
      } = options;
      if (!experimentalSpectrum) {
        throw new Error('Experimental spectrum is not defined');
      }
      if (!database) {
        throw new Error('Database is not defined');
      }
      const peaks = experimentalSpectrum.getPeaks({
        sumValue: 1
      });
      for (let entry of database) {
        const ranges = Object.keys(entry.atoms).map(atom => `${atom}0-${entry.atoms[atom]}`).join(' ');
        entry.fragments = {
          nbFound: 0,
          intensityFound: 0,
          assignments: []
        };
        for (let i = 0; i < peaks.length; i++) {
          if (onStep) await onStep(i);
          const peak = peaks[i];
          const possibleMFs = await findMFs(peak.x, {
            ionizations,
            precision,
            ranges
          });
          if (possibleMFs.mfs.length > 0) {
            entry.fragments.nbFound++;
            entry.fragments.intensityFound += peak.y;
            entry.fragments.assignments.push({
              peak,
              bestMF: possibleMFs.mfs[0]
            });
          }
        }
      }
    }

    /**
     * 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 sum$1 = 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 {object}        [options.filter={}]
     * @param {number}        [options.filter.minMass=0] - Minimal monoisotopic mass
     * @param {number}        [options.filter.maxMass=+Infinity] - Maximal monoisotopic mass
     * @param {number}        [options.filter.minEM=0] - Minimal neutral monoisotopic mass
     * @param {number}        [options.filter.maxEM=+Infinity] - Maximal neutral monoisotopic mass
     * @param {number}        [options.filter.precision=1000] - The precision on the experimental mass
     * @param {number}        [options.filter.targetMass] - Target mass, allows to calculate error and filter results
     * @param {number[]}      [options.filter.targetMasses] - Target masses: SORTED array of numbers
     * @param {number}        [options.filter.precision=1000] - Precision
     * @param {number}        [options.filter.minCharge=-Infinity] - Minimal charge
     * @param {number}        [options.filter.maxCharge=+Infinity] - Maximal charge
     * @param {boolean}       [options.filter.absoluteCharge=false] - If true, the charge is absolute (so between 0 and +Infinity by default)
     * @param {boolean}       [options.filter.allowNegativeAtoms=false] - Allow to have negative number of atoms
     * @param {object}        [options.filter.unsaturation={}]
     * @param {number}        [options.filter.unsaturation.min=-Infinity] - Minimal unsaturation
     * @param {number}        [options.filter.unsaturation.max=+Infinity] - Maximal unsaturation
     * @param {boolean}       [options.filter.unsaturation.onlyInteger=false] - Integer unsaturation
     * @param {boolean}       [options.filter.unsaturation.onlyNonInteger=false] - Non integer unsaturation
     * @param {object}        [options.filter.atoms] - object of atom:{min, max}
     * @param {function}      [options.filter.callback] - a function to filter the MF
     * @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) {
      let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      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
      } = options;
      options.filterFctVariables = {};
      for (let i = 0; i < ranges.length; i++) {
        const range = ranges[i];
        if (typeof range === 'object' && range.name) {
          options.filterFctVariables[range.name] = i;
          ranges[i] = range.value;
        }
      }
      if (options.filterFct) {
        // we create a real javascript function
        let variables = Object.keys(options.filterFctVariables);
        variables.push('mm', 'mz', 'charge', 'unsaturation', 'atoms');
        // eslint-disable-next-line no-new-func
        options.filterFct = new Function(...variables, `return ${options.filterFct}`);
      }
      if (uniqueMFs === true) options.canonizeMF = true;
      if (options.canonizeMF === undefined) options.canonizeMF = true;
      options.ionizations = preprocessIonizations(options.ionizations);
      if (!Array.isArray(ranges)) {
        throw new Error('You need to specify an array of strings or arrays');
      }

      // 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];
          let comment = part.replace(/^([^$]*\$|.*)/, '');
          part = part.replace(/\$.*/, '').replace(/\s/g, '');
          if (part.match(/[0-9]-[0-9-]/)) {
            // deal with negative numbers
            // there are ranges ... we are in trouble !
            newParts = newParts.concat(processRange(part, comment, {
              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 * options.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 = {};
        results.forEach(result => {
          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(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;
          sum$1(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) {
      let options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
      const {
        canonizeMF,
        filter,
        ionizations,
        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(/#[0-9]+/g);
          if (anchors) sharps.push(...anchors);
        }
        if (sharps.length % 2 === 1) return;
        sharps = sharps.sort();
        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 (options.filterFct) {
          let variables = [];
          for (let key in options.filterFctVariables) {
            variables.push(currents[options.filterFctVariables[key]]);
          }
          variables.push(result.em, (result.em + ionization.em - ionization.charge * ELECTRON_MASS$1) / Math.abs(ionization.charge), result.charge + result.ionization.charge, result.unsaturation, result.atoms);
          if (!options.filterFct.apply(null, 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.indexOf('$') > -1) {
              comments.push(key.replace(/^[^$]*\$/, ''));
              key = key.replace(/\$.*/, '');
            }
            result.parts[i] = key;
            result.mf += key;
          }
        }
        if (comments.length > 0) {
          result.comment = comments.join(' ');
        }
        let match = msemMatcher(result, filter);
        if (!match) continue;
        result.ms = match.ms;
        result.ionization = match.ionization;
        if (canonizeMF) {
          result.mf = new MF(result.mf).toMF();
        }
        results.push(result);
      }
    }

    /**
     * Generates a database 'generated' from an array of molecular formula
     * @param {array} mfsArray - Array of string or Array of array containing the parts to combine
     * @param {object} [options={}]
     * @param {boolean} [options.estimate=false] - estimate the number of MF without filters
     * @param {string} [options.databaseName='generated']
     * @param {function} [options.onStep] - Callback to do after each step
     * @param {number} [options.limit=10000000] - Maximum number of results
     * @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 {object} [options.filter={}]
     * @param {number} [options.filter.minMass=0] - Minimal monoisotopic mass
     * @param {number} [options.filter.maxMass=+Infinity] - Maximal monoisotopic mass
     * @param {number} [options.filter.minEM=0] - Minimal neutral monoisotopic mass
     * @param {number} [options.filter.maxEM=+Infinity] - Maximal neutral monoisotopic mass
     * @param {number} [options.filter.minMSEM=0] - Minimal observed monoisotopic mass
     * @param {number} [options.filter.maxMSEM=+Infinity] - Maximal observed monoisotopic mass
     * @param {number} [options.filter.minCharge=-Infinity] - Minimal charge
     * @param {number} [options.filter.maxCharge=+Infinity] - Maximal charge
     * @param {boolean}  [options.filter.absoluteCharge=false] - If true, the charge is absolute (so between 0 and +Infinity by default)
     * @param {object} [options.filter.unsaturation={}]
     * @param {number} [options.filter.unsaturation.min=-Infinity] - Minimal unsaturation
     * @param {number} [options.filter.unsaturation.max=+Infinity] - Maximal unsaturation
     * @param {boolean} [options.filter.unsaturation.onlyInteger=false] - Integer unsaturation
     * @param {boolean} [options.filter.unsaturation.onlyNonInteger=false] - Non integer unsaturation
     * @param {object} [options.filter.atoms] - object of atom:{min, max}
     * @param {function} [options.filter.callback] - a function to filter the MF
     * @param {string}  [options.filterFct] - A string representing a function
     *
     * @example
     *
     * const {EMDB} = require('emdb');
     * let emdb = new EMDB();
     * let array = ['C1-10', 'H1-10'];
     * emdb.fromArray(array); // create a database 'generated' combining all possibilies
     * console.log(emdb.get('generated').length); // 100
     *
     * @example
     * const {EMDB} = require('emdb');
     * let emdb = new EMDB();
     * let array = ['C1-10 H1-10'];
     * emdb.fromArray(array); // create a database 'generated' combining all possibilies
     * console.log(emdb.get('generated').length); // 100
     *
     * @example
     * const {EMDB} = require('emdb');
     * let emdb = new EMDB();
     * // in case of an array of array, one of the group is allwed
     * let array = [['C1-10','H1-10'],'Cl0-1 Br0-1'];
     * emdb.fromArray(array); // create a database 'generated' combining all possibilies
     * console.log(emdb.get('generated').length); // 80
     *
     * @example
     * <script src="https://www.lactame.com/lib/mass-tools/HEAD/mass-tools.js" />
     * <script>
     *   let emdb = new MassTools.EMDB();
     *   let array = ['C1-10', 'H1-10'];
     *   emdb.fromArray(array); // create a database 'generated' combining all possibilities
     *   console.log(emdb.get('generated').length); // 100
     * </script>
     *
     * // from the browser
     */

    async function fromArray(mfsArray) {
      let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      return generateMFs(mfsArray, options);
    }

    let xAtomicNumber = 0;

    /**
     * Tag an atom to be able to visualize it
     * @param {import('openchemlib').Molecule} molecule
     * @param {number} iAtom
     */
    function tagAtom(molecule, iAtom) {
      let customLabel = `${molecule.getAtomLabel(iAtom)}*`;
      molecule.setAtomCustomLabel(iAtom, customLabel);
      if (molecule.getAtomicNo(iAtom) === 1) {
        molecule.setAtomicNo(iAtom, getXAtomicNumber(molecule));
      } else {
        // we can not use X because we would have problems with valencies if it is
        // expanded hydrogens or not
        // we can not only use a custom label because it does not count for the canonisation
        molecule.setAtomMass(iAtom, molecule.getAtomMass(iAtom) + 5);
      }
      return customLabel;
    }
    function getXAtomicNumber(molecule) {
      if (!xAtomicNumber) {
        const OCL = molecule.getOCL();
        xAtomicNumber = OCL.Molecule.getAtomicNoFromLabel('X', OCL.Molecule.cPseudoAtomX);
      }
      return xAtomicNumber;
    }

    /**
     *
     * @param {import('openchemlib').Molecule} [molecule] An instance of a molecule
     * @param {object} [options={}]
     * @param {object} [options.OCL] openchemlib library
     */
    function makeRacemic(molecule) {
      const {
        Molecule
      } = molecule.getOCL();

      // if we don't calculate this we have 2 epimers
      molecule.ensureHelperArrays(Molecule.cHelperCIP);

      // we need to make one group "AND" for chiral (to force to racemic, this means diastereotopic and not enantiotopic)
      for (let i = 0; i < molecule.getAllAtoms(); i++) {
        if (molecule.getAtomParity(i) !== Molecule.cAtomParityNone) {
          molecule.setAtomESR(i, Molecule.cESRTypeAnd, 0); // changed to group 0; TLS 9.Nov.2015
        }
      }
    }

    /**
     * Check if a specific atom is a sp3 carbon
     * @param {import('openchemlib').Molecule} molecule
     * @param {number} atomID
     */

    function isCsp3(molecule, atomID) {
      if (molecule.getAtomicNo(atomID) !== 6) return false;
      if (molecule.getAtomCharge(atomID) !== 0) return false;
      if (molecule.getImplicitHydrogens(atomID) + molecule.getConnAtoms(atomID) !== 4) {
        return false;
      }
      return true;
    }

    const FULL_HOSE_CODE = 1;
    const HOSE_CODE_CUT_C_SP3_SP3 = 2;

    /**
     * Returns the hose code for a specific atom number
     * @param {import('openchemlib').Molecule} originalMolecule - The OCL molecule to be fragmented
     * @param {number[]} rootAtoms
     * @param {object} [options={}]
     * @param {boolean} [options.isTagged] Specify is the atoms are already tagged
     * @param {number} [options.minSphereSize=0] Smallest hose code sphere
     * @param {number} [options.maxSphereSize=4] Largest hose code sphere
     * @param {number} [options.kind=FULL_HOSE_CODE] Kind of hose code, default usual sphere
     */
    function getHoseCodesForAtoms(originalMolecule) {
      let rootAtoms = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
      let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
      const OCL = originalMolecule.getOCL();
      const {
        minSphereSize = 0,
        maxSphereSize = 4,
        kind = FULL_HOSE_CODE,
        isTagged = false
      } = options;
      const molecule = originalMolecule.getCompactCopy();
      if (!isTagged) {
        const tags = [];
        for (let i = 0; i < rootAtoms.length; i++) {
          let rootAtom = rootAtoms[i];
          tags.push(tagAtom(molecule, rootAtom));
          molecule.addImplicitHydrogens();
          molecule.addMissingChirality();
          molecule.ensureHelperArrays(OCL.Molecule.cHelperNeighbours);
          // because ensuring helper reorder atoms we need to look again for it
        }

        rootAtoms.length = 0;
        for (let j = 0; j < molecule.getAllAtoms(); j++) {
          if (tags.includes(molecule.getAtomCustomLabel(j))) {
            rootAtoms.push(j);
          }
        }
      }
      let fragment = new OCL.Molecule(0, 0);
      let results = [];
      let min = 0;
      let max = 0;
      let atomMask = new Array(molecule.getAllAtoms());
      let atomList = new Array(molecule.getAllAtoms());
      for (let sphere = 0; sphere <= maxSphereSize; sphere++) {
        if (max === 0) {
          for (let rootAtom of rootAtoms) {
            atomList[max] = rootAtom;
            atomMask[rootAtom] = true;
            max++;
          }
        } else {
          let newMax = max;
          for (let i = min; i < max; i++) {
            let atom = atomList[i];
            for (let j = 0; j < molecule.getAllConnAtoms(atom); j++) {
              let connAtom = molecule.getConnAtom(atom, j);
              if (!atomMask[connAtom]) {
                switch (kind) {
                  case FULL_HOSE_CODE:
                    atomMask[connAtom] = true;
                    atomList[newMax++] = connAtom;
                    break;
                  case HOSE_CODE_CUT_C_SP3_SP3:
                    if (!(isCsp3(molecule, atom) && isCsp3(molecule, connAtom))) {
                      atomMask[connAtom] = true;
                      atomList[newMax++] = connAtom;
                    }
                    break;
                  default:
                    throw new Error('getHoseCoesForAtom unknown kind');
                }
              }
            }
          }
          min = max;
          max = newMax;
        }
        molecule.copyMoleculeByAtoms(fragment, atomMask, true, null);
        if (sphere >= minSphereSize) {
          makeRacemic(fragment);
          results.push(fragment.getCanonizedIDCode(OCL.Molecule.CANONIZER_ENCODE_ATOM_CUSTOM_LABELS));
        }
      }
      return results;
    }

    /**
     * Implementation of the Hill system for sorting atoms
     * https://en.wikipedia.org/wiki/Chemical_formula#Hill_system
     * @param {string} a - first atom to compare
     * @param {string} b - second atom to compare
     * @returns
     */

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

    /**
     * Calculate the molecular formula in 'chemcalc' notation taking into account fragments, isotopes and charges
     * {OCL.Molecule} [molecule] an instance of OCL.Molecule
     * @returns {object}
     */

    function getMF(molecule) {
      let entries = molecule.getFragments();
      let result = {};
      let parts = [];
      let allAtoms = [];
      entries.forEach(entry => {
        let mf = getFragmentMF(entry, allAtoms);
        parts.push(mf);
      });
      let counts = {};
      for (let part of parts) {
        if (!counts[part]) counts[part] = 0;
        counts[part]++;
      }
      parts = [];
      for (let key of Object.keys(counts).sort()) {
        if (counts[key] > 1) {
          parts.push(counts[key] + key);
        } else {
          parts.push(key);
        }
      }
      result.parts = parts;
      result.mf = toMFString(allAtoms);
      return result;
    }
    function getFragmentMF(molecule, allAtoms) {
      let atoms = [];
      for (let i = 0; i < molecule.getAllAtoms(); i++) {
        let atom = {};
        atom.charge = molecule.getAtomCharge(i);
        atom.label = molecule.getAtomLabel(i);
        atom.mass = molecule.getAtomMass(i);
        atom.implicitHydrogens = molecule.getImplicitHydrogens(i);
        atoms.push(atom);
        allAtoms.push(atom);
      }
      return toMFString(atoms);
    }
    function toMFString(atoms) {
      let charge = 0;
      let mfs = {};
      for (let atom of atoms) {
        let label = atom.label;
        charge += atom.charge;
        if (atom.mass) {
          label = `[${atom.mass}${label}]`;
        }
        let mfAtom = mfs[label];
        if (!mfAtom) {
          mfs[label] = 0;
        }
        mfs[label] += 1;
        if (atom.implicitHydrogens) {
          if (!mfs.H) mfs.H = 0;
          mfs.H += atom.implicitHydrogens;
        }
      }
      let mf = '';
      let keys = Object.keys(mfs).sort(atomSorter);
      for (let key of keys) {
        mf += key;
        if (mfs[key] > 1) mf += mfs[key];
      }
      if (charge > 0) {
        mf += `(+${charge > 1 ? charge : ''})`;
      } else if (charge < 0) {
        mf += `(${charge < -1 ? charge : '-'})`;
      }
      return mf;
    }

    /**
     * The function performs the fragmentation of all single linear bonds
     * @param {import('openchemlib').Molecule} molecule - The OCL molecule to be fragmented
     * @param {object} [options={}]
     * @param {boolean} [options.calculateHoseCodes=false] - calculating hose code for bonds is quite time consuming
     * @param {string} [options.parentIDCode=molecule.getIDCode()]
     * @returns Results fragmentation of acyclic bonds
     */

    function fragmentAcyclicBonds(molecule) {
      let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      const {
        Molecule
      } = molecule.getOCL();
      const {
        calculateHoseCodes,
        parentIDCode = molecule.getIDCode()
      } = options;
      let atoms = [];
      // Prepare object with lenght equal to number of atoms
      for (let i = 0; i < molecule.getAllAtoms(); i++) {
        let atom = {};
        atoms.push(atom);
        atom.i = i;
        atom.links = [];
      }
      let bonds = [];
      for (let i = 0; i < molecule.getAllBonds(); i++) {
        let bond = {};
        // get informations of bonds
        bond.index = i;
        bond.order = molecule.getBondOrder(i); // dative, single , double, triple
        bond.atom1 = molecule.getBondAtom(0, i); // atom 1 index
        bond.atom2 = molecule.getBondAtom(1, i); // atom 2 index

        bond.type = molecule.getBondType(i); // cBondTypeSingle,cBondTypeDouble,cBondTypeTriple,cBondTypeDelocalized
        bond.isAromatic = molecule.isAromaticBond(i);
        bond.isRingBond = molecule.isRingBond(i);

        // Mapping of bonds to be fragmented, only if they are single bond not aromatic and cyclic the mapping occurs
        if (bond.isAromatic || bond.type > 1 || bond.isRingBond || bond.order !== 1) {
          continue;
        } else {
          bond.selected = true;
          atoms[bond.atom1].links.push(bond.atom2);
          atoms[bond.atom2].links.push(bond.atom1);
        }
        bonds.push(bond);
      }
      let brokenMolecule = {};
      let fragmentMap = [];
      let nbFragments = [];
      let results = [];
      for (let bond of bonds) {
        if (bond.selected) {
          // if bond.selected is true (line 46) the molecule will be fragmented
          brokenMolecule[bond.index] = molecule.getCompactCopy(); // get a copy of the molecule
          brokenMolecule[bond.index].setAtomCustomLabel(bond.atom1, '*');
          brokenMolecule[bond.index].setAtomCustomLabel(bond.atom2, '*');
          brokenMolecule[bond.index].markBondForDeletion(bond.index); //mark bond to be deleted
          // the function returns an array of map

          brokenMolecule[bond.index].deleteMarkedAtomsAndBonds(); // delete marked bonds
        }

        nbFragments = brokenMolecule[bond.index].getFragmentNumbers(fragmentMap);
        // only if there are 2 fragments code can continue
        if (nbFragments === 2) {
          for (let i = 0; i < nbFragments; i++) {
            const result = {};
            if (calculateHoseCodes) {
              result.hoses = getHoseCodesForAtoms(molecule, [bond.atom1, bond.atom2]);
            }
            result.atomMap = [];

            // assign fragment id to index of for loop
            let includeAtom = fragmentMap.map(id => {
              return id === i;
            });
            let fragment = new Molecule(100, 100);
            let atomMap = [];
            brokenMolecule[bond.index].copyMoleculeByAtoms(fragment, includeAtom, false, atomMap);
            for (let j = 0; j < atomMap.length; j++) {
              if (fragment.getAtomCustomLabel(atomMap[j]) === '*') {
                result.atomMap.push(j);
                if (atoms[j].links.length > 0) {
                  fragment.addBond(atomMap[j], fragment.addAtom(154));
                }
              }
            }
            fragment.removeAtomCustomLabels();
            fragment.setFragment(false);
            result.idCode = fragment.getIDCode();
            result.parentIDCode = parentIDCode;
            result.cleavedBonds = [{
              index: bond.index,
              order: bond.order,
              atom1: bond.atom1,
              atom2: bond.atom2
            }];
            result.mfInfo = new MF(getMF(fragment).mf.replace(/R[1-9]?/, '')).getInfo();
            result.fragmentType = 'acyclic';
            results.push(result);
          }
        }
      }
      // sort result in order fragment 1-2; 3-4; ...
      results = results.sort((a, b) => {
        return a.mfInfo.monoisotopicMass - b.mfInfo.monoisotopicMass;
      });
      return results;
    }

    /**
     * This function returns ringBond, and object that contains information about the bonds of each ring
     * @param {import('openchemlib').Molecule} molecule - The OCL molecule to be fragmented
     * @returns Information of ring bonds for each ring in the molecule
     */

    function getRingsInfo(molecule) {
      const ringSet = molecule.getRingSet();
      let ringBonds = [];
      // create a new array with the length of the number of bonds in the molecule and fills it with 0
      let nbRingForBonds = new Array(molecule.getAllBonds()).fill(0);
      for (let i = 0; i < ringSet.getSize(); i++) {
        for (let bond of ringSet.getRingBonds(i)) {
          nbRingForBonds[bond]++;
        }
      }
      for (let i = 0; i < ringSet.getSize(); i++) {
        ringBonds.push({
          bonds: ringSet.getRingBonds(i).map(bondIndex => ({
            index: bondIndex,
            ringIndex: i,
            nbRings: nbRingForBonds[bondIndex],
            // in how many rings this bond is included
            order: molecule.getBondOrder(bondIndex),
            isAromatic: ringSet.isAromatic(i),
            atom1: molecule.getBondAtom(0, bondIndex),
            atom2: molecule.getBondAtom(1, bondIndex)
          }))
        });
      }
      return ringBonds;
    }

    /**
     * This function returns an array of objects with all combination of 2 bonds who can be fragmented in the same ring
     * @param {import('openchemlib').Molecule} molecule - The OCL molecule to be fragmented
     * @returns All combination of 2 bonds who can be fragmented in the same ring
     */
    function getFragmentableRings(molecule) {
      let ringsInfo = getRingsInfo(molecule);
      let fragmentableRingBonds = [];
      for (let ring = 0; ring < ringsInfo.length; ring++) {
        let bonds = ringsInfo[ring].bonds;
        // we prevent to consecutive bonds to be cleaved
        for (let first = 0; first < bonds.length; first++) {
          let end = first === 0 ? bonds.length - 1 : bonds.length;
          for (let second = first + 2; second < end; second++) {
            if (bonds[first].order === 1 && bonds[second].order === 1 && !bonds[first].isAromatic && !bonds[second].isAromatic && bonds[first].nbRings < 2 && bonds[second].nbRings < 2) {
              fragmentableRingBonds.push({
                bonds: [bonds[first], bonds[second]]
              });
            }
          }
        }
      }
      return fragmentableRingBonds;
    }

    /**
     * The function performs the fragmentation of all single ring bonds not belonging to aromatic rings
     * @param {import('openchemlib').Molecule} molecule - The OCL molecule to be fragmented
     * @param {object} [options={}]
     * @param {boolean} [options.calculateHoseCodes=false] - calculating hose code for bonds is quite time consuming
     * @param {string} [options.parentIDCode=molecule.getIDCode()]
     * @returns  Array with results for the fragmentation of ring bonds
     */

    function fragmentRings(molecule) {
      let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      const {
        Molecule
      } = molecule.getOCL();
      const {
        calculateHoseCodes,
        parentIDCode = molecule.getIDCode()
      } = options;
      const fragmentableRingBonds = getFragmentableRings(molecule);
      let fragmentationResults = [];
      for (let ringBonds of fragmentableRingBonds) {
        const brokenMolecule = molecule.getCompactCopy();
        let fragmentMap = [];
        let atoms = [];
        let rLinks = {};
        for (let bond of ringBonds.bonds) {
          brokenMolecule.markBondForDeletion(bond.index);
          brokenMolecule.setAtomCustomLabel(bond.atom1, '*');
          brokenMolecule.setAtomCustomLabel(bond.atom2, '*');
          atoms.push(bond.atom1);
          atoms.push(bond.atom2);
          rLinks[bond.atom1] = bond.atom2;
          rLinks[bond.atom2] = bond.atom1;
        }
        brokenMolecule.deleteMarkedAtomsAndBonds();
        const nbFragments = brokenMolecule.getFragmentNumbers(fragmentMap);
        for (let i = 0; i < nbFragments; i++) {
          const result = {};
          if (calculateHoseCodes) {
            result.hoses = getHoseCodesForAtoms(molecule, atoms);
          }
          result.atomMap = [];
          let includeAtom = fragmentMap.map(id => {
            return id === i;
          });
          let fragment = new Molecule(0, 0);
          let atomMap = [];
          brokenMolecule.copyMoleculeByAtoms(fragment, includeAtom, false, atomMap);
          // if includeAtom has more then 3 true all true should become false and all false should become true

          for (let j = 0; j < atomMap.length; j++) {
            if (fragment.getAtomCustomLabel(atomMap[j]) === '*') {
              result.atomMap.push(j);
              if (rLinks[j] !== undefined) {
                fragment.addBond(atomMap[j], fragment.addAtom(154));
              }
            }
          }
          fragment.removeAtomCustomLabels();
          fragment.setFragment(false);
          //      console.log(fragment.getIDCode(), getMF(fragment).mf);
          result.idCode = fragment.getIDCode();
          result.parentIDCode = parentIDCode;
          result.cleavedBonds = ringBonds.bonds;
          result.mfInfo = new MF(getMF(fragment).mf.replace(/R[1-9]?/, '')).getInfo();
          result.fragmentType = 'cyclic';
          fragmentationResults.push(result);
        }
      }
      fragmentationResults = fragmentationResults.sort((a, b) => {
        return a.mfInfo.monoisotopicMass - b.mfInfo.monoisotopicMass;
      });
      return fragmentationResults;
    }

    /**
     * This function fragment both acyclic and cyclic bonds of the molecule
     * @param {import('openchemlib').Molecule} molecule - The OCL molecule to be fragmented
     * @param {object} [options={}]
     * @param {boolean} [options.calculateHoseCodes=false] - calculating hose code for bonds is quite time consuming
     * @param {boolean} [options.cyclic=true] - calculate cyclic fragmentation
     * @param {boolean} [options.acyclic=true] - calculate acyclic fragmentation
     * @param {boolean} [options.full=true] - calculate the molecular formula of the full molecule
     * @returns {object} In-Silico fragmentation results
     */
    function fragment(molecule) {
      let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      const {
        cyclic = true,
        acyclic = true,
        full = true,
        calculateHoseCodes = false
      } = options;
      const parentIDCode = molecule.getIDCode();
      let molecularIon = full ? [{
        idCode: parentIDCode,
        parentIDCode,
        mfInfo: new MF(getMF(molecule).mf).getInfo(),
        fragmentType: 'molecule'
      }] : [];
      let acyclicBonds = acyclic ? fragmentAcyclicBonds(molecule, {
        calculateHoseCodes,
        parentIDCode
      }) : [];
      let cyclicBonds = cyclic ? fragmentRings(molecule, {
        calculateHoseCodes,
        parentIDCode
      }) : [];
      let result = [...molecularIon, ...acyclicBonds, ...cyclicBonds];
      return result.sort((a, b) => a.mfInfo.monoisotopicMass - b.mfInfo.monoisotopicMass);
    }

    /** * Generates a database 'monoisotopic' from a monoisotopic mass and various options
     * @param {{smiles?:string,molecule?:string,idCode?:string}[]}    entries - Array of object containing a property to recreate the molecule
     * @param {import('openchemlib')} ocl - The OCL library
     * @param {object}    [options={}]
     * @param {function}  [options.onStep] - Callback to do after each step
     * @param {boolean}   [options.allowNeutral=true]
     * @param {string}    [options.ionizations=''] - string containing a comma separated list of modifications
     * @param {number}    [options.precision=100] - Allowed mass range based on precision
     * @param {boolean}   [options.groupResults=false] - Should we group the results if they have the same monoisotopic mass and experimental mass
     * @param {object}    [options.fragmentation={}]
     * @param {object}    [options.fragmentation.acyclic=false]
     * @param {object}    [options.fragmentation.cyclic=false]
     * @param {object}    [options.fragmentation.full=true]
     * @param {object}    [options.filter={}]
     * @param {number}    [options.filter.minCharge=-Infinity] - Minimal charge
     * @param {number}    [options.filter.maxCharge=+Infinity] - Maximal charge
     * @param {boolean}   [options.filter.absoluteCharge=false] - If true, the charge is absolute (so between 0 and +Infinity by default)
     * @param {object}    [options.filter.unsaturation={}]
     * @param {number}    [options.filter.unsaturation.min=-Infinity] - Minimal unsaturation
     * @param {number}    [options.filter.unsaturation.max=+Infinity] - Maximal unsaturation
     * @param {boolean}   [options.filter.unsaturation.onlyInteger=false] - Integer unsaturation
     * @param {boolean}   [options.filter.unsaturation.onlyNonInteger=false] - Non integer unsaturation
     * @param {number}    [options.filter.targetMass] - Target mass, allows to calculate error and filter results
     * @param {number[]}  [options.filter.targetMasses] - Target masses: SORTED array of numbers
     * @param {number[]}  [options.filter.targetIntensities] - Target intensities: SORTED array of numbers
     * @param {object}    [options.filter.atoms] - object of atom:{min, max}
     * @param {function}  [options.filter.callback] - a function to filter the MF
     * @returns {Promise}
     */

    async function fromMolecules(entries, ocl) {
      let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
      let {
        onStep,
        ionizations,
        filter,
        fragmentation = {
          acyclic: false,
          cyclic: false,
          full: true
        },
        groupResults = false
      } = options;
      ionizations = preprocessIonizations(ionizations);
      let results = [];
      for (let i = 0; i < entries.length; i++) {
        const entry = entries[i];
        const molecule = getMolecule(entry, ocl);
        if (!molecule) continue;
        const {
          acyclic = false,
          cyclic = false,
          full = true
        } = fragmentation;
        const allFragments = fragment(molecule, {
          acyclic,
          cyclic,
          full
        });
        for (const oneFragment of allFragments) {
          appendResults$1(results, oneFragment, {
            entry,
            ionizations,
            filter
          });
          if (onStep) onStep(i);
        }
      }
      if (groupResults) {
        results = groupFragmentationResults(results);
      }
      return results.sort((a, b) => a.em - b.em);
    }

    /**
     * We could group the results and replace the property 'fragment' by 'fragments'
     * @param {*} results
     */
    function groupFragmentationResults(results) {
      const sortedResults = results.slice().sort((a, b) => {
        if (a.em !== b.em) {
          return a.em - b.em;
        }
        if (a.ms.em !== b.ms.em) {
          return a.ms.em - b.ms.em;
        }
        if (a.fragment.idCode < b.fragment.idCode) return -1;
        return 1;
      });
      const groupedResults = [];
      let currentResult = {};
      for (let result of sortedResults) {
        if (result.em !== currentResult.em || result.ms.em !== currentResult.ms.em) {
          currentResult = {
            ...result
          };
          currentResult.fragments = [{
            idCode: result.fragment.idCode,
            type: result.fragment.type,
            count: 1,
            parents: [{
              ...result.fragment.parent,
              count: 1
            }]
          }];
          delete currentResult.fragment;
          groupedResults.push(currentResult);
        } else {
          const lastFragment = currentResult.fragments[currentResult.fragments.length - 1];
          if (lastFragment.idCode === result.fragment.idCode) {
            lastFragment.count++;
            if (lastFragment.parents[lastFragment.parents.length - 1].idCode === result.fragment.parent.idCode) {
              lastFragment.parents[lastFragment.parents.length - 1].count++;
            } else {
              lastFragment.parents.push({
                ...result.fragment.parent,
                count: 1
              });
            }
          } else {
            currentResult.fragments.push({
              idCode: result.fragment.idCode,
              type: result.fragment.type,
              count: 1,
              parents: [{
                ...result.fragment.parent,
                count: 1
              }]
            });
          }
        }
      }
      for (let group of groupedResults) {
        group.fragments = group.fragments.sort((a, b) => b.count - a.count);
      }
      return groupedResults;
    }

    /**
     *
     * @param {object} entry
     * @param {import('openchemlib')} ocl - The OCL library
     */
    function getMolecule(entry, ocl) {
      if (entry.idCode) {
        return ocl.Molecule.fromIDCode(entry.idCode);
      }
      if (entry.ocl && entry.ocl.idCode) {
        return ocl.Molecule.fromIDCode(entry.ocl.idCode);
      }
      if (entry.smiles) {
        return ocl.Molecule.fromSmiles(entry.smiles);
      }
      if (entry.molfile) {
        return ocl.Molecule.fromMolfile(entry.molfile);
      }
      return undefined;
    }
    function appendResults$1(results, oneFragment, options) {
      const mf = oneFragment.mfInfo.mf;
      const {
        ionizations,
        filter,
        entry
      } = options;
      const mfInfo = new MF(mf).getInfo();
      for (let ionization of ionizations) {
        const result = {
          charge: mfInfo.charge,
          em: mfInfo.monoisotopicMass,
          mw: mfInfo.mass,
          mf: mfInfo.mf,
          ionization,
          unsaturation: mfInfo.unsaturation,
          atoms: mfInfo.atoms,
          fragment: {
            parent: {
              ...entry,
              idCode: oneFragment.parentIDCode
            },
            idCode: oneFragment.idCode,
            type: oneFragment.fragmentType
          }
        };
        let match = msemMatcher(result, filter);
        if (!match) continue;
        result.ms = match.ms;
        result.ionization = match.ionization;
        results.push(result);
      }
    }

    /**
     * Generates a database 'monoisotopic' from a monoisotopic mass and various options
     * @param {number|string|array}    masses - Monoisotopic mass
     * @param {object}    [options={}]
     * @param {number}    [options.maxIterations=10000000] - Maximum number of iterations
     * @param {function}  [options.onStep] - Callback to do after each step
     * @param {boolean}   [options.allowNeutral=true]
     * @param {boolean}   [options.uniqueMFs=true]
     * @param {number}    [options.limit=1000] - Maximum number of results
     * @param {string}    [options.ionizations=''] - string containing a comma separated list of modifications
     * @param {string}    [options.ranges='C0-100 H0-100 O0-100 N0-100'] - range of mfs to search
     * @param {number}    [options.precision=100] - Allowed mass range based on precision
     * @param {object}    [options.filter={}]
     * @param {number}    [options.filter.minCharge=-Infinity] - Minimal charge
     * @param {number}    [options.filter.maxCharge=+Infinity] - Maximal charge
     * @param {boolean}   [options.filter.absoluteCharge=false] - If true, the charge is absolute (so between 0 and +Infinity by default)
     * @param {object}    [options.filter.unsaturation={}]
     * @param {number}    [options.filter.unsaturation.min=-Infinity] - Minimal unsaturation
     * @param {number}    [options.filter.unsaturation.max=+Infinity] - Maximal unsaturation
     * @param {boolean}    [options.filter.unsaturation.onlyInteger=false] - Integer unsaturation
     * @param {boolean}    [options.filter.unsaturation.onlyNonInteger=false] - Non integer unsaturation
     * @param {object}    [options.filter.atoms] - object of atom:{min, max}
     * @param {function}  [options.filter.callback] - a function to filter the MF
     * @returns {Promise}
     */

    async function fromMonoisotopicMass(masses) {
      let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      if (typeof masses === 'string') {
        masses = masses.split(/[ ,;\r\n\t]/).map(Number);
      }
      if (typeof masses === 'number') {
        masses = [masses];
      }
      let results = [];
      for (let mass of masses) {
        results.push(await findMFs(mass, options));
      }
      return {
        mfs: results.map(entry => entry.mfs).flat(),
        info: {
          numberMFEvaluated: results.reduce((sum, current) => sum += current.info.numberMFEvaluated, 0),
          numberResults: results.reduce((sum, current) => sum += current.info.numberResults, 0)
        }
      };
    }

    /**
     * Ensure that the sequence is in uppercase taking into account possible modifications
     * @param {string} [options.circular=false]
     */

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

    /**
     * Convert a nucleic sequence to a MF
     * @param {String} sequence
     * @param {object} [options={}]
     * @param {string} [options.kind] - rna, ds-dna or dna. Default if contains U: rna, otherwise ds-dna
     * @param {string} [options.fivePrime=monophosphate] - alcohol, monophosphate, diphosphate, triphosphate
     * @param {string} [options.circular=false]
     */

    function sequenceToMF$1(sequence) {
      let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      let fivePrimeTerminal = 'HO';
      let threePrimeTerminal = 'H';
      sequence = sequence.replace(/^HO/, '');
      sequence = sequence.replace(/H$/, '');
      sequence = sequence.trim();
      if (sequence === '') return '';
      sequence = ensureUppercaseSequence(sequence);

      // if the sequence is in lowercase but the parenthesis we should convert it to uppercase

      if (sequence.match(/^[a-z]+$/)) {
        sequence = sequence.toUpperCase();
      }
      let {
        kind,
        circular,
        fivePrime = 'monophosphate'
      } = options;
      fivePrime = fivePrime.replace(/[^a-zA-Z]/g, '').toLowerCase();
      if (!kind) {
        if (sequence.includes('U')) {
          kind = 'rna';
        } else {
          kind = 'ds-dna';
        }
      }
      kind = kind.replace(/[^A-Za-z]/g, '').toLowerCase();
      if (sequence.includes('(') && kind === 'dsdna') {
        throw new Error('Nucleotide sequenceToMF: modifications not allowed for ds-DNA');
      }
      let results = [[]];
      if (kind === 'dsdna') results.push([]);
      let parenthesisCounter = 0;
      for (let i = 0; i < sequence.length; i++) {
        let currentSymbol = sequence[i];
        while (sequence[i + 1] && sequence[i + 1].match(/[a-z]/)) {
          i++;
          currentSymbol += sequence[i];
        }
        if (currentSymbol.length > 1) {
          results[0].push(currentSymbol);
          continue;
        }
        if (currentSymbol === '(' || currentSymbol === ')' || parenthesisCounter > 0) {
          if (currentSymbol === '(') {
            parenthesisCounter++;
            if (i === 0) fivePrimeTerminal = '';
          }
          if (currentSymbol === ')') {
            parenthesisCounter--;
            if (i === sequence.length - 1) threePrimeTerminal = '';
          }
          switch (kind) {
            case 'dna':
            case 'rna':
              results[0].push(currentSymbol);
              break;
            default:
              // eslint-disable-next-line no-console
              console.warn(`Nucleotide sequenceToMF with modification: unknown kind: ${kind}`);
          }
          continue;
        }
        let nucleotideType = i === 0 ? fivePrime : 'monophosphate';
        currentSymbol = currentSymbol.replace(/[ \t\r\n]/, '');
        if (!currentSymbol) continue;
        switch (kind) {
          case 'dna':
            results[0].push(desoxyNucleotides[nucleotideType][currentSymbol]);
            break;
          case 'rna':
            results[0].push(oxyNucleotides[nucleotideType][currentSymbol]);
            break;
          case 'dsdna':
            results[0].push(desoxyNucleotides[nucleotideType][currentSymbol]);
            results[1].unshift(desoxyNucleotides[nucleotideType][complementary[currentSymbol]]);
            break;
          default:
            // eslint-disable-next-line no-console
            console.warn(`Nucleotide sequenceToMF: unknown kind: ${kind}`);
        }
      }
      if (!circular) {
        results.forEach(result => result.unshift(fivePrimeTerminal));
        results.forEach(result => result.push(threePrimeTerminal));
      }
      return results.map(result => result.join('')).join('.');
    }
    const complementary = {
      A: 'T',
      T: 'A',
      C: 'G',
      G: 'C'
    };
    const desoxyNucleotides = {
      alcohol: {},
      monophosphate: {},
      diphosphate: {},
      triphosphate: {}
    };
    groups.filter(group => group.kind === 'DNA').forEach(group => {
      if (group.oneLetter) {
        desoxyNucleotides.alcohol[group.oneLetter] = group.symbol;
      }
    });
    groups.filter(group => group.kind === 'DNAp').forEach(group => {
      if (group.oneLetter) {
        desoxyNucleotides.monophosphate[group.oneLetter] = group.symbol;
      }
    });
    groups.filter(group => group.kind === 'NucleotideP').forEach(group => {
      if (group.oneLetter) {
        desoxyNucleotides.monophosphate[group.oneLetter] = group.symbol;
      }
    });
    groups.filter(group => group.kind === 'DNApp').forEach(group => {
      if (group.oneLetter) {
        desoxyNucleotides.diphosphate[group.oneLetter] = group.symbol;
      }
    });
    groups.filter(group => group.kind === 'DNAppp').forEach(group => {
      if (group.oneLetter) {
        desoxyNucleotides.triphosphate[group.oneLetter] = group.symbol;
      }
    });
    const oxyNucleotides = {
      alcohol: {},
      monophosphate: {},
      diphosphate: {},
      triphosphate: {}
    };
    groups.filter(group => group.kind === 'RNA').forEach(group => {
      if (group.oneLetter) {
        oxyNucleotides.alcohol[group.oneLetter] = group.symbol;
      }
    });
    groups.filter(group => group.kind === 'RNAp').forEach(group => {
      if (group.oneLetter) {
        oxyNucleotides.monophosphate[group.oneLetter] = group.symbol;
      }
    });
    groups.filter(group => group.kind === 'NucleotideP').forEach(group => {
      if (group.oneLetter) {
        oxyNucleotides.monophosphate[group.oneLetter] = group.symbol;
      }
    });
    groups.filter(group => group.kind === 'RNApp').forEach(group => {
      if (group.oneLetter) {
        oxyNucleotides.diphosphate[group.oneLetter] = group.symbol;
      }
    });
    groups.filter(group => group.kind === 'RNAppp').forEach(group => {
      if (group.oneLetter) {
        oxyNucleotides.triphosphate[group.oneLetter] = group.symbol;
      }
    });

    function furanThreeTerm(nucleotide) {
      // last residue should become a furan
      let parts = nucleotide.replace(/ /g, '').replace(/([a-z)0-9])([A-Z][a-z](?=[a-z]))/g, '$1 $2').split(/ /);
      let last = parts.pop();
      if (!last.match(/D[atcg]mp(.*)$/)) {
        // eslint-disable-next-line no-console
        console.warn(`furanThreeTerm can not remove a non monophosphate nucleic acid: ${last}`);
        return parts.join('') + last;
      }
      return parts.join('') + last.replace(/D[atcg]mp(.*)$/, 'Furp');
    }

    function addFiveTerm(mfs, fiveTerm, i, options) {
      if (options.a) mfs.push(`${fiveTerm}O-1H-1$a${i}`); // neutral ok
      if (options.ab && i > 1) mfs.push(`${furanThreeTerm(fiveTerm)}$a${i}-B`); // A minus base
      if (options.b) mfs.push(`${fiveTerm}H$b${i}`); // need to add an hydrogen, see: https://books.google.ch/books?id=B57e37bJjqAC&pg=PA172&lpg=PA172&dq=oligonucleotide+b+fragmentation&source=bl&ots=mRr29Pexx2&sig=1NUQcWV-wuj6o9q81my86AVoRto&hl=fr&sa=X&ved=2ahUKEwjI5M3yn-7fAhUJMewKHQR6Bcs4ChDoATADegQIBhAB#v=onepage&q&f=true
      if (options.c) mfs.push(`${fiveTerm}PO2$c${i}`); // neutral ok
      if (options.d) mfs.push(`${fiveTerm}PO3H2$d${i}`);
      if (options.dh2o) mfs.push(`${fiveTerm}PO2$d${i}-H2O`);
    }

    const mfLosses = {};
    ['Amp', 'Tmp', 'Cmp', 'Gmp', 'Ump'].forEach(nucleotide => {
      mfLosses[nucleotide] = {
        code: nucleotide.charAt(0),
        diff: mfDiff('Rmp', nucleotide)
      };
    });
    ['Damp', 'Dtmp', 'Dcmp', 'Dgmp', 'Dump'].forEach(nucleotide => {
      mfLosses[nucleotide] = {
        code: nucleotide.charAt(1).toUpperCase(),
        diff: mfDiff('Drmp', nucleotide)
      };
    });
    function baseLoss(nucleotide) {
      // any residue can loose a base
      let results = [];
      for (let key in mfLosses) {
        const base = mfLosses[key];
        if (nucleotide.includes(key)) {
          results.push(`${nucleotide}(${base.diff})$${base.code}*`);
        }
      }
      return results;
    }

    function addFiveTermBaseLoss(mfs, fiveTerm, i, options) {
      if (!options.abcdBaseLoss) return;
      let loss = baseLoss(fiveTerm);
      loss.forEach(mf => {
        if (options.a) {
          mfs.push(`${mf}`.replace('$', `O-1H-1$a${i} `));
        }
        if (options.b) {
          mfs.push(`${mf}`.replace('$', `H-1$b${i} `));
        }
        if (options.c) {
          mfs.push(`${mf}`.replace('$', `PO2$c${i} `));
        }
        if (options.d) {
          mfs.push(`${mf}`.replace('$', `PO3H2$d${i} `));
        }
      });
    }

    // https://books.google.ch/books?id=B57e37bJjqAC&pg=PA172&lpg=PA172&dq=oligonucleotide+b+fragmentation&source=bl&ots=mRr29Pexx2&sig=1NUQcWV-wuj6o9q81my86AVoRto&hl=fr&sa=X&ved=2ahUKEwjI5M3yn-7fAhUJMewKHQR6Bcs4ChDoATADegQIBhAB#v=onepage&q=oligonucleotide%20b%20fragmentation&f=false

    function addInternalTerm(mfs, internal, ter3, ter5) {
      let options = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : {};
      if (options.aw) {
        // without base loss
        mfs.push(`HO${internal}O-1H-1$w${ter3}:a${ter5}`); // A W
      }

      if (options.bw) {
        // without base loss
        mfs.push(`HO${internal}H$w${ter3}:b${ter5}`); // B W
      }

      if (options.abw) {
        // with base loss
        let fragment = furanThreeTerm(internal);
        mfs.push(`HO${fragment}$w${ter3}:a${ter5}-B`); // A minus base - W
      }

      if (options.aby) {
        // with base loss
        let fragment = furanThreeTerm(internal);
        mfs.push(`O-2P-1${fragment}$y${ter3}:a${ter5}-B`); // A minus base - Y
      }
    }

    function addThreeTerm(mfs, threeTerm, i, options) {
      if (options.w) mfs.push(`HO${threeTerm}$w${i}`); // neutral ok
      if (options.x) mfs.push(`H-1${threeTerm}$x${i}`); // neutral ok
      if (options.y) mfs.push(`O-2P-1${threeTerm}$y${i}`); // neutral ok
      if (options.z) mfs.push(`O-3H-2P-1${threeTerm}$z${i}`); // neutral ok
      if (options.zch2) mfs.push(`O-3H-4C-1P-1${threeTerm}$z${i}-CH2`); // TODO to confirm
    }

    function addThreeTermBaseLoss(mfs, threeTerm, i, options) {
      if (!options.wxyzBaseLoss) return;
      let loss = baseLoss(threeTerm);
      loss.forEach(mf => {
        if (options.w) {
          mfs.push(`HO${mf}`.replace('$', `$w${i} `));
        }
        if (options.x) {
          mfs.push(`H-1${mf}`.replace('$', `$x${i} `));
        }
        if (options.y) {
          mfs.push(`O-2P-1${mf}`.replace('$', `$y${i} `));
        }
        if (options.z) {
          mfs.push(`O-3H-1P-1(+)${mf}`.replace('$', `$z${i} `));
        }
      });
    }

    function generateFragments(mf, options) {
      if (options === undefined) {
        options = {
          a: false,
          ab: false,
          b: false,
          c: false,
          d: false,
          dh2o: false,
          w: false,
          x: false,
          y: false,
          z: false,
          zch2: false,
          aw: false,
          bw: false,
          abw: false,
          aby: false,
          abcdBaseLoss: false,
          wxyzBaseLoss: false
        };
      }
      let mfs = [];
      // need to allow 0-9 to deal with neutral loss
      let mfparts = mf.replace(/([a-z)0-9])([A-Z][a-z](?=[a-z]))/g, '$1 $2').split(/ /);
      let fiveTerm = '';
      let threeTerm = '';
      if (mfparts[0].startsWith('(')) {
        fiveTerm += mfparts[0];
        mfparts = mfparts.splice(1);
      }
      if (mfparts[mfparts.length - 1].includes('(')) {
        threeTerm += mfparts[mfparts.length - 1].replace(/^[^()]*/, '');
        mfparts[mfparts.length - 1] = mfparts[mfparts.length - 1].replace(/\(.*/, '');
      }
      for (let ter5 = 1; ter5 < mfparts.length; ter5++) {
        fiveTerm += mfparts[ter5 - 1];
        threeTerm = mfparts[mfparts.length - ter5] + threeTerm;
        addFiveTerm(mfs, fiveTerm, ter5, options);
        addFiveTermBaseLoss(mfs, fiveTerm, ter5, options);
        addThreeTerm(mfs, threeTerm, ter5, options);
        addThreeTermBaseLoss(mfs, threeTerm, ter5, options);
      }
      for (let i = 1; i < mfparts.length - 1; i++) {
        let internal = '';
        for (let j = i; j < mfparts.length - 1; j++) {
          internal += mfparts[j];
          if (j > i) {
            addInternalTerm(mfs, internal, mfparts.length - i, j + 1, options);
          }
        }
      }
      return mfs;
    }

    var NucleotidePkg = /*#__PURE__*/Object.freeze({
        __proto__: null,
        sequenceToMF: sequenceToMF$1,
        generateFragments: generateFragments,
        furanThreeTerm: furanThreeTerm,
        baseLoss: baseLoss
    });

    /**
     * Add a database starting from a peptidic sequence
     *
     * @param {string} [sequencesString] Sequence as a string of 1 letter or 3 letters code. Could also be a correct molecular formula respecting uppercase, lowercase
     * @param {object} [options={}]
     * @param {boolean} [options.estimate=false] - estimate the number of MF without filters
     * @param {function} [options.onStep] - Callback to do after each step
     * @param {number} [options.limit=100000]
     * @param {string} [options.ionizations='']
     * @param {object} [options.info={}]
     * @param {string} [options.info.kind] - rna, ds-dna or dna. Default if contains U: rna, otherwise ds-dna
     * @param {string} [options.info.fivePrime=monophosphate] - alcohol, monophosphate, diphosphate, triphosphate
     * @param {string} [options.info.circular=false]
     * @param {array}   [options.mfsArray=[]]
     * @param {object}  [options.fragmentation={}] Object defining options for fragmentation
     * @param {boolean} [options.fragmentation.a=false] If true allow fragments of type 'a'
     * @param {boolean} [options.fragmentation.ab=false] If true allow fragments of type 'a' minus base
     * @param {boolean} [options.fragmentation.b=false] If true allow fragments of type 'b'
     * @param {boolean} [options.fragmentation.c=false] If true allow fragments of type 'c'
     * @param {boolean} [options.fragmentation.d=false] If true allow fragments of type 'd'
     * @param {boolean} [options.fragmentation.dh2o=false] If true allow fragments of type 'd' with water loss
     * @param {boolean} [options.fragmentation.w=false] If true allow fragments of type 'w'
     * @param {boolean} [options.fragmentation.x=false] If true allow fragments of type 'x'
     * @param {boolean} [options.fragmentation.y=false] If true allow fragments of type 'y'
     * @param {boolean} [options.fragmentation.z=false] If true allow fragments of type 'z'
     * @param {boolean} [options.baseLoss=false] If true allow base loss at all the positions
     *
     * @param {object} [options.filter={}] Object defining options for molecular formula filter
     * @param {number} [options.filter.minMass=0] - Minimal monoisotopic mass
     * @param {number} [options.filter.maxMass=+Infinity] - Maximal monoisotopic mass
     * @param {number} [options.filter.minEM=0] - Minimal neutral monoisotopic mass
     * @param {number} [options.filter.maxEM=+Infinity] - Maximal neutral monoisotopic mass
     * @param {number} [options.filter.minMSEM=0] - Minimal observed monoisotopic mass
     * @param {number} [options.filter.maxMSEM=+Infinity] - Maximal observed monoisotopic mass
     * @param {number} [options.filter.minCharge=-Infinity] - Minimal charge
     * @param {number} [options.filter.maxCharge=+Infinity] - Maximal charge
     * @param {boolean}  [options.filter.absoluteCharge=false] - If true, the charge is absolute (so between 0 and +Infinity by default)
     * @param {object} [options.filter.unsaturation={}]
     * @param {number} [options.filter.unsaturation.min=-Infinity] - Minimal unsaturation
     * @param {number} [options.filter.unsaturation.max=+Infinity] - Maximal unsaturation
     * @param {boolean} [options.filter.unsaturation.onlyInteger=false] - Integer unsaturation
     * @param {boolean} [options.filter.unsaturation.onlyNonInteger=false] - Non integer unsaturation
     * @returns {Promise}
     */

    async function fromNucleicSequence(sequencesString) {
      let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      const {
        mfsArray = [],
        fragmentation = {},
        filter = {},
        ionizations = '',
        info = {},
        estimate = false,
        limit = 100000,
        onStep
      } = options;
      let sequences = sequenceToMF$1(sequencesString, info).split('.');
      let fragmentsArray = sequences.slice();

      // calculate fragmentation
      for (let i = 0; i < sequences.length; i++) {
        let sequence = sequences[i];
        let fragments = generateFragments(sequence, fragmentation);
        if (i === 1) {
          // complementary sequence
          fragments = fragments.map(fragment => fragment.replace(/\$/g, '$cmp-'));
        }
        fragmentsArray = fragmentsArray.concat(fragments);
        if (fragmentation.baseLoss) {
          fragmentsArray = fragmentsArray.concat(baseLoss(sequence));
        }
      }
      mfsArray.push(fragmentsArray);
      let combined = await generateMFs(mfsArray, {
        ionizations,
        filter,
        uniqueMFs: false,
        estimate,
        onStep,
        limit
      });
      if (Array.isArray(combined)) {
        // not an estimation
        combined.forEach(result => {
          result.sequence = groupsToSequence(result.parts.filter(part => part).join(' '));
        });
      }
      return combined;
    }

    // SOURCE: https://en.wikipedia.org/wiki/Amino_acid
    // Link for UTF8 code for modified: https://codepoints.net/search?sc=Grek
    const aminoAcids = [
    // Standard amino acids
    {
      name: 'Alanine',
      aa3: 'Ala',
      aa1: 'A',
      sc: {
        type: 'hydrophobic'
      },
      pKaC: 2.33,
      pKaN: 9.71
    }, {
      name: 'Arginine',
      aa3: 'Arg',
      aa1: 'R',
      sc: {
        type: 'positive',
        pKa: 12.1
      },
      pKaC: 2.03,
      pKaN: 9.0
    }, {
      name: 'Asparagine',
      aa3: 'Asn',
      aa1: 'N',
      sc: {
        type: 'polar'
      },
      pKaC: 2.13,
      pKaN: 9.05
    }, {
      name: 'Aspartic acid',
      aa3: 'Asp',
      aa1: 'D',
      sc: {
        type: 'negative',
        pKa: 3.71
      },
      pKaC: 1.95,
      pKaN: 9.66
    }, {
      name: 'Cysteine',
      aa3: 'Cys',
      aa1: 'C',
      sc: {
        type: 'special',
        pKa: 8.14
      },
      pKaC: 1.91,
      pKaN: 10.28
    }, {
      name: 'Glutamic acid',
      aa3: 'Glu',
      aa1: 'E',
      sc: {
        type: 'negative',
        pKa: 4.15
      },
      pKaC: 2.16,
      pKaN: 9.58
    }, {
      name: 'Glutamine',
      aa3: 'Gln',
      aa1: 'Q',
      sc: {
        type: 'polar'
      },
      pKaC: 2.18,
      pKaN: 9.0
    }, {
      name: 'Glycine',
      aa3: 'Gly',
      aa1: 'G',
      sc: {
        type: 'special'
      },
      pKaC: 2.34,
      pKaN: 9.58
    }, {
      name: 'Histidine',
      aa3: 'His',
      aa1: 'H',
      sc: {
        type: 'positive',
        pKa: 6.04
      },
      pKaC: 1.7,
      pKaN: 9.09
    }, {
      name: 'Isoleucine',
      aa3: 'Ile',
      aa1: 'I',
      sc: {
        type: 'hydrophobic'
      },
      pKaC: 2.26,
      pKaN: 9.6
    }, {
      name: 'Leucine',
      aa3: 'Leu',
      aa1: 'L',
      sc: {
        type: 'hydrophobic'
      },
      pKaC: 2.32,
      pKaN: 9.58
    }, {
      name: 'Lysine',
      aa3: 'Lys',
      aa1: 'K',
      sc: {
        type: 'positive',
        pKa: 10.67
      },
      pKaC: 2.15,
      pKaN: 9.16
    }, {
      name: 'Methionine',
      aa3: 'Met',
      aa1: 'M',
      sc: {
        type: 'hydrophobic'
      },
      pKaC: 2.16,
      pKaN: 9.08
    }, {
      name: 'Phenylalanine',
      aa3: 'Phe',
      aa1: 'F',
      sc: {
        type: 'hydrophobic'
      },
      pKaC: 2.18,
      pKaN: 9.09
    }, {
      name: 'Proline',
      aa3: 'Pro',
      aa1: 'P',
      sc: {
        type: 'special'
      },
      pKaC: 1.95,
      pKaN: 10.47
    }, {
      name: 'Serine',
      aa3: 'Ser',
      aa1: 'S',
      sc: {
        type: 'polar'
      },
      pKaC: 2.13,
      pKaN: 9.05
    }, {
      name: 'Threonine',
      aa3: 'Thr',
      aa1: 'T',
      sc: {
        type: 'polar'
      },
      pKaC: 2.2,
      pKaN: 8.96
    }, {
      name: 'Tryptophan',
      aa3: 'Trp',
      aa1: 'W',
      sc: {
        type: 'hydrophobic'
      },
      pKaC: 2.38,
      pKaN: 9.34
    }, {
      name: 'Tyrosine',
      aa3: 'Tyr',
      aa1: 'Y',
      sc: {
        type: 'hydrophobic'
      },
      pKaC: 2.24,
      pKaN: 9.04
    }, {
      name: 'Valine',
      aa3: 'Val',
      aa1: 'V',
      sc: {
        type: 'hydrophobic'
      },
      pKaC: 2.27,
      pKaN: 9.52
    },
    // Additional
    {
      name: 'Selenocysteine',
      aa3: 'Sec',
      aa1: 'U'
    }, {
      name: 'Pyrrolysine',
      aa3: 'Pyl',
      aa1: 'O'
    },
    // Ambiguous
    {
      name: 'Asparagine or aspartic acid',
      aa3: 'Asx',
      aa1: 'B'
    }, {
      name: 'Glutamine or glutamic acid',
      aa3: 'Glx',
      aa1: 'Z'
    }, {
      name: 'Leucine or isoleucine',
      aa3: 'Xle',
      aa1: 'J'
    }, {
      name: 'Unspecified or unknown',
      aa3: 'Xaa',
      aa1: 'X'
    }];

    // we will convert the data to an object to be much faster
    let aaObject = {};
    for (let i = 0; i < aminoAcids.length; i++) {
      aaObject[aminoAcids[i].aa3] = aminoAcids[i];
    }
    function calculateCharge$1(aas) {
      let pH = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 7.0;
      let combined = combine(aas);
      if (!combined) return;
      let charge = calculateForPh(combined, pH);
      return Math.round(charge * 1000) / 1000;
    }

    // this methods required an array of aas

    function calculateIEP$1(aas) {
      let combined = combine(aas);
      if (!combined) return;
      let first = 0;
      let last = 14;
      let current = 14;
      let previous = 0;
      let currentCharge;
      while (Math.abs(current - previous) > 0.0001) {
        previous = current;
        current = (last + first) / 2;
        currentCharge = calculateForPh(combined, current);
        if (currentCharge > 0) {
          first = current;
        } else if (currentCharge < 0) {
          last = current;
        } else {
          previous = current;
        }
      }
      return Math.round(current * 1000) / 1000;
    }
    function calculateChart(aas) {
      let combined = combine(aas);
      if (!combined) return;
      let y = [];
      let x = [];
      let yAbs = [];
      for (let i = 0; i <= 14; i = i + 0.01) {
        let charge = calculateForPh(combined, i);
        x.push(i);
        y.push(charge);
        yAbs.push(Math.abs(charge));
      }
      combined.x = x;
      combined.y = y;
      combined.yAbs = yAbs;
      return combined;
    }
    function calculateForPh(combined, pH) {
      let total = 0;
      total += 1 / (1 + 10 ** (pH - combined.first));
      total += -1 / (1 + 10 ** (combined.last - pH));
      for (let key in combined.acid) {
        total += -combined.acid[key] / (1 + 10 ** (aaObject[key].sc.pKa - pH));
      }
      for (let key in combined.basic) {
        total += combined.basic[key] / (1 + 10 ** (pH - aaObject[key].sc.pKa));
      }
      return total;
    }

    // we will combine the amino acids
    function combine(aas) {
      let combined = {};
      if (aaObject[aas[0]]) {
        combined.first = aaObject[aas[0]].pKaN;
      } else {
        return;
      }
      if (aaObject[aas[aas.length - 1]]) {
        combined.last = aaObject[aas[aas.length - 1]].pKaC;
      } else {
        return;
      }
      combined.basic = {};
      combined.acid = {};
      for (let i = 0; i < aas.length; i++) {
        let currentAA = aas[i];
        if (!aaObject[currentAA]) return;
        if (aaObject[currentAA].sc && aaObject[currentAA].sc.type) {
          if (aaObject[currentAA].sc.type === 'positive') {
            if (!combined.basic[currentAA]) {
              combined.basic[currentAA] = 0;
            }
            combined.basic[currentAA]++;
          } else if (aaObject[currentAA].sc.type === 'negative') {
            if (!combined.acid[currentAA]) {
              combined.acid[currentAA] = 0;
            }
            combined.acid[currentAA]++;
          }
        }
      }
      return combined;
    }

    /*
     We can generate a color based on iep
     0 -> 7 means that at pH 7 it is charged negatively (blue)
     7 -> 14 means that at pH7 it is charged positively (red)
     */
    function getColor(iep) {
      if (iep < 7) {
        if (iep < 3) iep = 3;
        let white = Math.round(255 - (7 - iep) * (200 / 4));
        return `rgb(${white},${white},255)`;
      } else if (iep > 7) {
        if (iep > 11) iep = 11;
        let white = Math.round(255 - (iep - 7) * (200 / 4));
        return `rgb(255,${white},${white})`;
      }
      return 'rgb(255,255,255)';
    }

    function splitPeptide(sequence) {
      let aas = sequence.replace(/([A-Z])/g, ' $1').split(/ /);
      let begin = 0;
      while (aas[begin] === '' || aas[begin] === 'H') {
        begin++;
      }
      let end = aas.length - 1;
      while (aas[end] === 'O' || aas[end] === 'H') {
        end--;
      }
      aas = aas.slice(begin, end + 1);
      return aas;
    }

    /**
     * Add neutral loss on natural amino acids
     * @param {*} mf
     * @returns
     */
    function allowNeutralLoss(mf) {
      if (Array.isArray(mf)) {
        for (let i = 0; i < mf.length; i++) {
          mf[i] = allowOneNeutralLoss(mf[i]);
        }
        return mf;
      } else {
        return allowOneNeutralLoss(mf);
      }
    }
    function allowOneNeutralLoss(mf) {
      mf = mf.replace(/(Ser|Thr|Asp|Glu)(?!\()/g, '$1(H-2O-1)0-1');
      mf = mf.replace(/(Arg|Lys|Asn|Gln)(?!\()/g, '$1(N-1H-3)0-1');
      return mf;
    }

    function getAA(code) {
      if (code.length === 1) {
        for (let i = 0; i < aminoAcids.length; i++) {
          if (aminoAcids[i].aa1 === code) {
            return aminoAcids[i];
          }
        }
      }
      if (code.length === 3) {
        for (let i = 0; i < aminoAcids.length; i++) {
          if (aminoAcids[i].aa3 === code) {
            return aminoAcids[i];
          }
        }
      }
    }

    // SOURCE: https://en.wikipedia.org/wiki/Amino_acid

    function chargePeptide(mf) {
      let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      if (options.pH === undefined) options.pH = 0;
      if (Array.isArray(mf)) {
        for (let i = 0; i < mf.length; i++) {
          mf[i] = chargeOnePeptide(mf[i], options);
        }
        return mf;
      } else {
        return chargeOnePeptide(mf, options);
      }
    }
    function chargeOnePeptide(mf, options) {
      let pH = options.pH;
      // we will allow to charge the peptide at a specific pH

      // first amino acids (N-terminal)
      if (mf.match(/^H[A-Z][a-z]{2}/)) {
        let firstAA = mf.replace(/^H([A-Z][a-z]{2}).*/, '$1');
        if (getAA(firstAA) && pH < getAA(firstAA).pKaN) {
          mf = mf.replace(/^H([^+])/, 'H+H$1');
        }
      }

      // last amino acids (C-terminal)
      if (mf.match(/[A-Z][a-z]{2}OH$/)) {
        let lastAA = mf.replace(/.*([A-Z][a-z]{2})OH$/, '$1');
        if (getAA(lastAA) && pH > getAA(lastAA).pKaC) {
          mf = mf.replace(/OH$/, 'O-');
        }
      }

      // basic AA
      if (pH < getAA('Arg').sc.pKa) mf = mf.replace(/(Arg)(?!\()/g, '$1(H+)');
      if (pH < getAA('His').sc.pKa) mf = mf.replace(/(His)(?!\()/g, '$1(H+)');
      if (pH < getAA('Lys').sc.pKa) mf = mf.replace(/(Lys)(?!\()/g, '$1(H+)');

      // acid AA
      if (pH > getAA('Asp').sc.pKa) mf = mf.replace(/(Asp)(?!\()/g, '$1(H-1-)');
      if (pH > getAA('Glu').sc.pKa) mf = mf.replace(/(Glu)(?!\()/g, '$1(H-1-)');
      if (pH > getAA('Cys').sc.pKa) mf = mf.replace(/(Cys)(?!\()/g, '$1(H-1-)');
      return mf;
    }

    function sequenceToMF(mf) {
      // this function will check if it is a sequence of aa in 1 letter or 3 letters and convert them if it is the case
      // it could be a multiline mf !
      // if it is a multiline we could make some "tricks" ...

      let newMF = mf;
      // SEQRES   1 B  256  MET PRO VAL GLU ILE THR VAL LYS GLU LEU LEU GLU ALA
      // SEQRES   2 B  256  GLY VAL HIS PHE GLY HIS GLU ARG LYS ARG TRP ASN PRO
      // or
      // MET PRO VAL GLU ILE THR VAL LYS GLU LEU LEU GLU ALA
      // GLY VAL HIS PHE GLY HIS GLU ARG LYS ARG TRP ASN PRO
      if (mf.search(/[A-Z]{3} [A-Z]{3} [A-Z]{3}/) > -1) {
        // this is a PDB !
        let tmpmf = mf.replace(/[\r\n]+/g, ' ');
        tmpmf = tmpmf.replace(/(SEQRES|[0-9]+| [A-Z] | [0-9A-Z]{4-50})/g, '');
        // we need to correct the uppercase / lowercase
        let parts = tmpmf.split(' ');
        newMF = 'H';
        for (let i = 0; i < parts.length; i++) {
          newMF += parts[i].substr(0, 1) + parts[i].substr(1).toLowerCase();
        }
        newMF += 'OH';
      } else if (mf.includes('(') && isOneLetterCode(mf)) {
        // we expect one letter code with modification
        newMF = '';
        let nTerminal = 'H';
        let cTerminal = 'OH';
        let parenthesisCounter = 0;
        for (let i = 0; i < mf.length; i++) {
          let currentSymbol = mf[i];
          if (currentSymbol === '(' || currentSymbol === ')' || parenthesisCounter > 0) {
            if (currentSymbol === '(') {
              parenthesisCounter++;
              if (i === 0) nTerminal = '';
            }
            if (currentSymbol === ')') {
              parenthesisCounter--;
              if (i === mf.length - 1) cTerminal = '';
            }
            newMF += currentSymbol;
            continue;
          }
          newMF += convertAA1To3(currentSymbol);
        }
        newMF = nTerminal + newMF + cTerminal;
      } else if (mf.search(/[A-Z]{3}/) > -1 && mf.search(/[a-zA-Z][a-z0-9]/) === -1) {
        // UNIPROT
        //   370        380        390        400        410        420
        //GFKPNLRKTF VSGLFRESCG AHFYRGVDVK PFYIKKPVDN LFALMLILNR LRGWGVVGGM
        //
        //    430        440        450        460        470        480
        //SDPRLYKVWV RLSSQVPSMF FGGTDLAADY YVVSPPTAVS VYTKTPYGRL LADTRTSGFR
        // We remove all the number, all the spaces, etc
        newMF = `H${convertAA1To3(newMF.replace(/[^A-Z]/g, ''))}OH`;
      }
      return newMF;
    }
    function convertAA1To3(mf) {
      let newmf = '';
      for (let i = 0; i < mf.length; i++) {
        newmf += aa1To3(mf.charAt(i));
      }
      return newmf;
    }
    function aa1To3(code) {
      for (let i = 0; i < aminoAcids.length; i++) {
        if (aminoAcids[i].aa1 === code) {
          return aminoAcids[i].aa3;
        }
      }
      throw new Error(`Invalid 1 letter code: ${code}`);
    }

    // mf can contain as well parenthesis. We need to check if it is not yet a correct molecular formula
    function isOneLetterCode(mf) {
      let parenthesisLevel = 0;
      for (let char of mf) {
        if (parenthesisLevel === 0 && char.match(/[a-z]/)) return false;
        if (char === '(') parenthesisLevel++;
        if (char === ')') parenthesisLevel--;
      }
      return true;
    }

    function generatePeptideFragments(mf, options) {
      if (options === undefined) {
        options = {
          a: false,
          b: true,
          c: false,
          x: false,
          y: true,
          z: false,
          i: false,
          ya: false,
          yb: false,
          yc: false,
          zc: false
        };
      }
      options.maxInternal = options.maxInternal || Number.MAX_VALUE;
      options.minInternal = options.minInternal || 0;
      let mfs = [];
      // need to allow 0-9 to deal with neutral loss
      let mfparts = mf.replace(/([a-z)0-9])([A-Z][a-z](?=[a-z]))/g, '$1 $2').split(/ /);
      let nTerm = '';
      let cTerm = '';
      if (mfparts[0].startsWith('(')) {
        nTerm += mfparts[0];
        mfparts = mfparts.splice(1);
      }
      if (mfparts[mfparts.length - 1].includes('(')) {
        cTerm += mfparts[mfparts.length - 1].replace(/^[^()]*/, '');
        mfparts[mfparts.length - 1] = mfparts[mfparts.length - 1].replace(/\(.*/, '');
      }
      for (let i = 1; i < mfparts.length; i++) {
        nTerm += mfparts[i - 1];
        cTerm = mfparts[mfparts.length - i] + cTerm;
        addNTerm(mfs, nTerm, i, options);
        addCTerm(mfs, cTerm, i, options);
        if (options.i) mfs.push(`${mfparts[i]}HC-1O-1(+1)$i:${mfparts[i]}`);
        if (options.ya || options.yb || options.yc || options.zc) {
          // we have double fragmentations
          for (let j = i + 1; j < Math.min(mfparts.length, options.maxInternal + i + 1); j++) {
            let iTerm = '';
            if (j - i >= options.minInternal) {
              for (let k = i; k < j; k++) {
                iTerm += mfparts[k];
              }
              addITerm(mfs, iTerm, mfparts.length - i, j, options);
            }
          }
        }
      }

      // todo does this make sense ??? I think we should remote those 3 lines
      if (mfs.length === 0) {
        mfs = mfs.concat([mf]);
      }
      return mfs;
    }
    function addNTerm(mfs, nTerm, i, options) {
      if (options.a) mfs.push(`${nTerm}C-1O-1(+1)$a${i}`);
      if (options.b) mfs.push(`${nTerm}(+1)$b${i}`);
      if (options.c) mfs.push(`${nTerm}NH3(+1)$c${i}`);
    }
    function addITerm(mfs, iTerm, i, j, options) {
      if (options.ya) mfs.push(`H${iTerm}C-1O-1(+1)$a${j}y${i}`);
      if (options.yb) mfs.push(`H${iTerm}(+1)$b${j}y${i}`);
      if (options.yc) mfs.push(`H${iTerm}NH3(+1)$c${j}y${i}`);
      if (options.zc) mfs.push(`N-1${iTerm}NH3(+1)$c${j}z${i}`);
    }
    function addCTerm(mfs, cTerm, i, options) {
      if (options.x) mfs.push(`CO(+1)${cTerm}$x${i}`);
      if (options.y) mfs.push(`H2(+1)${cTerm}$y${i}`);
      if (options.z) mfs.push(`N-1H-1(+1)${cTerm}$z${i}`);
    }

    /*
    Iotuibs:
    * minMissed (default: 0)
    * maxMissed (default: 0)
    * minResidue: 0;
    * maxResidue: infinity
    * enzyme: chymotrypsin, trypsin, glucph4, glucph8, thermolysin, cyanogenbromide : Mandatory, no default value !
     */

    function digestPeptide(sequence) {
      let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      sequence = sequence.replace(/^H([^a-z])/, '$1').replace(/OH$/, '');
      options.enzyme = options.enzyme || 'trypsin';
      if (options.minMissed === undefined) options.minMissed = 0;
      if (options.maxMissed === undefined) options.maxMissed = 0;
      if (options.minResidue === undefined) options.minResidue = 0;
      if (options.maxResidue === undefined) options.maxResidue = Number.MAX_VALUE;
      let regexp = getRegexp(options.enzyme);
      let fragments = sequence.replace(regexp, '$1 ').split(/ /).filter(entry => entry);
      {
        let from = 0;
        for (let i = 0; i < fragments.length; i++) {
          let nbResidue = fragments[i].replace(/([A-Z][a-z][a-z])/g, ' $1').split(/ /).filter(entry => entry).length;
          fragments[i] = {
            sequence: fragments[i],
            nbResidue,
            from,
            to: from + nbResidue - 1
          };
          from += nbResidue;
        }
      }
      let results = [];
      for (let i = 0; i < fragments.length - options.minMissed; i++) {
        for (let j = options.minMissed; j <= Math.min(options.maxMissed, fragments.length - i - 1); j++) {
          let fragment = '';
          let nbResidue = 0;
          for (let k = i; k <= i + j; k++) {
            fragment += fragments[k].sequence;
            nbResidue += fragments[k].nbResidue;
          }
          let from = fragments[i].from + 1;
          let to = fragments[i + j].to + 1;
          if (fragment && nbResidue >= options.minResidue && nbResidue <= options.maxResidue) {
            results.push(`H${fragment}OH$D${from}>${to}`);
          }
        }
      }
      return results;
    }
    function getRegexp(enzyme) {
      switch (enzyme.toLowerCase().replace(/[^a-z0-9]/g, '')) {
        case 'chymotrypsin':
          return /(Phe|Tyr|Trp)(?!Pro)/g;
        case 'trypsin':
          return /(Lys|Arg)(?!Pro)/g;
        case 'lysc':
          return /(Lys)(?!Pro)/g;
        case 'glucph4':
          return /(Glu)(?!Pro|Glu)/g;
        case 'glucph8':
          return /(Asp|Glu)(?!Pro|Glu)/g;
        case 'thermolysin':
          // N-term of  Leu, Phe, Val, Ile, Ala, Met
          return /()(?=Ile|Leu|Val|Ala|Met|Phe)/g;
        case 'cyanogenbromide':
          return /(Met)/g;
        case 'any':
          return /()(?=[A-Z][a-z][a-z])/g;
        default:
          throw new Error(`Digestion enzyme: ${enzyme} is unknown`);
      }
    }

    function getInfo() {
      return aminoAcids;
    }

    // sequence should be in the "right" format like HAlaGlyProOH

    function calculateIEP(sequence) {
      let aas = splitPeptide(sequence);
      let result = calculateIEP$1(aas);
      return result;
    }
    function calculateIEPChart(sequence) {
      let aas = splitPeptide(sequence);
      let result = calculateChart(aas);
      return result;
    }
    function getColorForIEP(iep) {
      return getColor(iep);
    }
    function calculateCharge(sequence, ph) {
      let aas = splitPeptide(sequence);
      return calculateCharge$1(aas, ph);
    }

    var PeptidePkg = /*#__PURE__*/Object.freeze({
        __proto__: null,
        getInfo: getInfo,
        calculateIEP: calculateIEP,
        calculateIEPChart: calculateIEPChart,
        getColorForIEP: getColorForIEP,
        calculateCharge: calculateCharge,
        allowNeutralLoss: allowNeutralLoss,
        chargePeptide: chargePeptide,
        sequenceToMF: sequenceToMF,
        generatePeptideFragments: generatePeptideFragments,
        digestPeptide: digestPeptide,
        splitPeptide: splitPeptide
    });

    function fragmentPeptide(sequence) {
      let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      const {
        digestion = {},
        protonation,
        fragmentation,
        protonationPH
      } = options;
      sequence = sequenceToMF(sequence);
      let fragmentsArray = [sequence];
      // do we also have some digest fragments ?
      if (digestion.enzyme) {
        let digests = digestPeptide(sequence, digestion);
        if (options.protonation) {
          digests = chargePeptide(digests, {
            pH: options.protonationPH
          });
        }
        fragmentsArray = fragmentsArray.concat(digests);
      }

      // allow neutral loss
      if (options.allowNeutralLoss) {
        sequence = allowNeutralLoss(sequence);
      }

      // apply protonation
      if (protonation) {
        sequence = chargePeptide(sequence, {
          pH: protonationPH
        });
      }

      // calculate fragmentation
      let fragments = generatePeptideFragments(sequence, fragmentation);
      fragmentsArray = fragmentsArray.concat(fragments);
      return fragmentsArray;
    }

    /**
     * Add a database starting from a peptidic sequence
     *
     * @param {string}         [sequences] Sequence as a string of 1 letter or 3 letters code. Could also be a correct molecular formula respecting uppercase, lowercase. It can be comma separated if you have many peptide sequences
     * @param {object}         [options={}]
     * @param {boolean}        [options.estimate=false] - estimate the number of MF without filters
     * @param {string}         [options.ionizations='']
     * @param {function}       [options.onStep] - Callback to do after each step
     * @param {array}          [options.mfsArray=[]]
     * @param {boolean}        [options.protonation=false]
     * @param {number}         [options.protonationPH=7]
     * @param {boolean}        [options.allowNeutralLoss=false]
     * @param {number}         [options.limit=100000]
     *
     * @param {object}         [options.digestion={}] Object defining options for digestion
     * @param {number}         [options.digestion.minMissed=0] Minimal number of allowed missed cleavage
     * @param {number}         [options.digestion.maxMissed=0] Maximal number of allowed missed cleavage
     * @param {number}         [options.digestion.minResidue=0] Minimal number of residues
     * @param {number}         [options.digestion.maxResidue=+Infinity] Maximal number of residues
     * @param {string}         [options.digestion.enzyme] Mandatory field containing the name of the enzyme among: chymotrypsin, trypsin, glucph4, glucph8, thermolysin, cyanogenbromide
     *
     * @param {object}         [options.fragmentation={}] Object defining options for fragmentation
     * @param {boolean}        [options.fragmentation.a=false] If true allow fragments of type 'a'
     * @param {boolean}        [options.fragmentation.b=false] If true allow fragments of type 'b'
     * @param {boolean}        [options.fragmentation.c=false] If true allow fragments of type 'c'
     * @param {boolean}        [options.fragmentation.x=false] If true allow fragments of type 'x'
     * @param {boolean}        [options.fragmentation.y=false] If true allow fragments of type 'y'
     * @param {boolean}        [options.fragmentation.z=false] If true allow fragments of type 'z'
     * @param {boolean}        [options.fragmentation.ya=false] If true allow fragments of type 'ya'
     * @param {boolean}        [options.fragmentation.yb=false] If true allow fragments of type 'yb'
     * @param {boolean}        [options.fragmentation.yc=false] If true allow fragments of type 'yc'
     * @param {boolean}        [options.fragmentation.zc=false] If true allow fragments of type 'zc'
     * @param {number}         [options.fragmentation.minInternal=0] Minimal internal fragment length
     * @param {number}         [options.fragmentation.maxInternal=+Infinity] Maximal internal fragment length
     *
     * @param {object}         [options.filter={}] Object defining options for molecular formula filter
     * @param {number}         [options.filter.precision=1000] - The precision on the experimental mass
     * @param {number}         [options.filter.targetMass] - Target mass, allows to calculate error and filter results
     * @param {number[]}       [options.filter.targetMasses] - Target masses: SORTED array of numbers
     * @param {number[]}       [options.filter.targetIntensities] - Target intensities: SORTED array of numbers
     * @param {number}         [options.filter.minEM=0] - Minimal neutral monoisotopic mass
     * @param {number}         [options.filter.maxEM=+Infinity] - Maximal neutral monoisotopic mass
     * @param {number}         [options.filter.minMSEM=0] - Minimal observed monoisotopic mass
     * @param {number}         [options.filter.maxMSEM=+Infinity] - Maximal observed monoisotopic mass
     * @param {number}         [options.filter.minCharge=-Infinity] - Minimal charge
     * @param {number}         [options.filter.maxCharge=+Infinity] - Maximal charge
     * @param {boolean}        [options.filter.absoluteCharge=false] - If true, the charge is absolute (so between 0 and +Infinity by default)
     * @param {object}         [options.filter.unsaturation={}]
     * @param {number}         [options.filter.unsaturation.min=-Infinity] - Minimal unsaturation
     * @param {number}         [options.filter.unsaturation.max=+Infinity] - Maximal unsaturation
     * @param {boolean}        [options.filter.unsaturation.onlyInteger=false] - Integer unsaturation
     * @param {boolean}        [options.filter.unsaturation.onlyNonInteger=false] - Non integer unsaturation
     * @param {function}       [options.filter.callback] - a function to filter the MF
     * @returns {Promise}
     */

    async function fromPeptidicSequence(sequences) {
      let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      const {
        digestion = {},
        mfsArray: originalMFsArray = [],
        allowNeutralLoss = false,
        protonation = false,
        protonationPH = 7,
        fragmentation = {},
        filter = {},
        ionizations = '',
        limit = 100000,
        estimate = false,
        onStep,
        links = {}
      } = options;
      const hasLinked = sequences.includes('#');
      const mfsArrayLinked = JSON.parse(JSON.stringify(originalMFsArray));
      const mfsArrayUnlinked = JSON.parse(JSON.stringify(originalMFsArray));
      const unlinked = [];
      mfsArrayUnlinked.push(unlinked);
      for (const sequence of sequences.split(/[,:]/)) {
        let fragmentsArray = fragmentPeptide(sequence, {
          digestion,
          protonation,
          fragmentation,
          protonationPH,
          allowNeutralLoss
        });
        mfsArrayLinked.push(fragmentsArray.filter(fragment => fragment.includes('#')));
        unlinked.push(...fragmentsArray.filter(fragment => !fragment.includes('#')));
      }
      let combined = await generateMFs(mfsArrayUnlinked, {
        ionizations,
        filter,
        estimate,
        limit,
        onStep,
        links
      });
      if (hasLinked) {
        combined.push(...(await generateMFs(mfsArrayLinked, {
          ionizations,
          filter,
          estimate,
          limit,
          onStep,
          links
        })));
      }
      if (!estimate) {
        combined.forEach(result => {
          result.sequence = groupsToSequence(result.parts.filter(part => part).join(' '));
        });
      }
      return combined;
    }

    /**
     * Generates a database 'generated' from an array of molecular formula
     * @param {string} rangesString - a string representing the range to search
     * @param {object} [options={}]
     * @param {boolean} [options.estimate=false] - estimate the number of MF without filters
     * @param {function} [options.onStep] - Callback to do after each step
     * @param {string} [options.databaseName='generated']
     * @param {number} [options.limit=100000] - Maximum number of results
     * @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 {object} [options.filter={}]
     * @param {number} [options.filter.minMass=0] - Minimal monoisotopic mass
     * @param {number} [options.filter.maxMass=+Infinity] - Maximal monoisotopic mass
     * @param {number} [options.filter.minEM=0] - Minimal neutral monoisotopic mass
     * @param {number} [options.filter.maxEM=+Infinity] - Maximal neutral monoisotopic mass
     * @param {number} [options.filter.minMSEM=0] - Minimal observed monoisotopic mass
     * @param {number} [options.filter.maxMSEM=+Infinity] - Maximal observed monoisotopic mass
     * @param {number} [options.filter.minCharge=-Infinity] - Minimal charge
     * @param {number} [options.filter.maxCharge=+Infinity] - Maximal charge
     * @param {boolean}   [options.filter.absoluteCharge=false] - If true, the charge is absolute (so between 0 and +Infinity by default)
     * @param {object} [options.filter.unsaturation={}]
     * @param {number} [options.filter.unsaturation.min=-Infinity] - Minimal unsaturation
     * @param {number} [options.filter.unsaturation.max=+Infinity] - Maximal unsaturation
     * @param {boolean} [options.filter.unsaturation.onlyInteger=false] - Integer unsaturation
     * @param {boolean} [options.filter.unsaturation.onlyNonInteger=false] - Non integer unsaturation
     * @param {function} [options.filter.callback] - a function to filter the MF
     * @param {object} [options.filter.atoms] - object of atom:{min, max}
     *
     * @returns {Promise} - list of possible molecular formula
     *
     * @example
     * const {EMDB} = require('emdb');
     * let emdb = new EMDB();
     * // semi-columns separated for combination, comma for 'or'
     * emdb.fromRange('C1-10, H1-10; Cl0-1 Br0-1'); // create a database 'generated' combining all possibilies
     * console.log(emdb.get('generated').length); // 80
     */

    async function fromRange(rangesString) {
      let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      let ranges = rangesString.split(/ *[;\r\n] */);
      for (let i = 0; i < ranges.length; i++) {
        let range = ranges[i];
        if (range.includes(',')) {
          ranges[i] = range.split(/ *, */);
        }
      }
      return generateMFs(ranges, options);
    }

    var jszip_min = {exports: {}};

    /*!

    JSZip v3.10.1 - A JavaScript class for generating and reading zip files
    <http://stuartk.com/jszip>

    (c) 2009-2016 Stuart Knightley <stuart [at] stuartk.com>
    Dual licenced under the MIT license or GPLv3. See https://raw.github.com/Stuk/jszip/main/LICENSE.markdown.

    JSZip uses the library pako released under the MIT license :
    https://github.com/nodeca/pako/blob/main/LICENSE
    */
    (function (module, exports) {
      !function (e) {
        module.exports = e();
      }(function () {
        return function s(a, o, h) {
          function u(r, e) {
            if (!o[r]) {
              if (!a[r]) {
                var t = "function" == typeof commonjsRequire && commonjsRequire;
                if (!e && t) return t(r, !0);
                if (l) return l(r, !0);
                var n = new Error("Cannot find module '" + r + "'");
                throw n.code = "MODULE_NOT_FOUND", n;
              }
              var i = o[r] = {
                exports: {}
              };
              a[r][0].call(i.exports, function (e) {
                var t = a[r][1][e];
                return u(t || e);
              }, i, i.exports, s, a, o, h);
            }
            return o[r].exports;
          }
          for (var l = "function" == typeof commonjsRequire && commonjsRequire, e = 0; e < h.length; e++) u(h[e]);
          return u;
        }({
          1: [function (e, t, r) {

            var d = e("./utils"),
              c = e("./support"),
              p = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
            r.encode = function (e) {
              for (var t, r, n, i, s, a, o, h = [], u = 0, l = e.length, f = l, c = "string" !== d.getTypeOf(e); u < e.length;) f = l - u, n = c ? (t = e[u++], r = u < l ? e[u++] : 0, u < l ? e[u++] : 0) : (t = e.charCodeAt(u++), r = u < l ? e.charCodeAt(u++) : 0, u < l ? e.charCodeAt(u++) : 0), i = t >> 2, s = (3 & t) << 4 | r >> 4, a = 1 < f ? (15 & r) << 2 | n >> 6 : 64, o = 2 < f ? 63 & n : 64, h.push(p.charAt(i) + p.charAt(s) + p.charAt(a) + p.charAt(o));
              return h.join("");
            }, r.decode = function (e) {
              var t,
                r,
                n,
                i,
                s,
                a,
                o = 0,
                h = 0,
                u = "data:";
              if (e.substr(0, u.length) === u) throw new Error("Invalid base64 input, it looks like a data url.");
              var l,
                f = 3 * (e = e.replace(/[^A-Za-z0-9+/=]/g, "")).length / 4;
              if (e.charAt(e.length - 1) === p.charAt(64) && f--, e.charAt(e.length - 2) === p.charAt(64) && f--, f % 1 != 0) throw new Error("Invalid base64 input, bad content length.");
              for (l = c.uint8array ? new Uint8Array(0 | f) : new Array(0 | f); o < e.length;) t = p.indexOf(e.charAt(o++)) << 2 | (i = p.indexOf(e.charAt(o++))) >> 4, r = (15 & i) << 4 | (s = p.indexOf(e.charAt(o++))) >> 2, n = (3 & s) << 6 | (a = p.indexOf(e.charAt(o++))), l[h++] = t, 64 !== s && (l[h++] = r), 64 !== a && (l[h++] = n);
              return l;
            };
          }, {
            "./support": 30,
            "./utils": 32
          }],
          2: [function (e, t, r) {

            var n = e("./external"),
              i = e("./stream/DataWorker"),
              s = e("./stream/Crc32Probe"),
              a = e("./stream/DataLengthProbe");
            function o(e, t, r, n, i) {
              this.compressedSize = e, this.uncompressedSize = t, this.crc32 = r, this.compression = n, this.compressedContent = i;
            }
            o.prototype = {
              getContentWorker: function () {
                var e = new i(n.Promise.resolve(this.compressedContent)).pipe(this.compression.uncompressWorker()).pipe(new a("data_length")),
                  t = this;
                return e.on("end", function () {
                  if (this.streamInfo.data_length !== t.uncompressedSize) throw new Error("Bug : uncompressed data size mismatch");
                }), e;
              },
              getCompressedWorker: function () {
                return new i(n.Promise.resolve(this.compressedContent)).withStreamInfo("compressedSize", this.compressedSize).withStreamInfo("uncompressedSize", this.uncompressedSize).withStreamInfo("crc32", this.crc32).withStreamInfo("compression", this.compression);
              }
            }, o.createWorkerFrom = function (e, t, r) {
              return e.pipe(new s()).pipe(new a("uncompressedSize")).pipe(t.compressWorker(r)).pipe(new a("compressedSize")).withStreamInfo("compression", t);
            }, t.exports = o;
          }, {
            "./external": 6,
            "./stream/Crc32Probe": 25,
            "./stream/DataLengthProbe": 26,
            "./stream/DataWorker": 27
          }],
          3: [function (e, t, r) {

            var n = e("./stream/GenericWorker");
            r.STORE = {
              magic: "\0\0",
              compressWorker: function () {
                return new n("STORE compression");
              },
              uncompressWorker: function () {
                return new n("STORE decompression");
              }
            }, r.DEFLATE = e("./flate");
          }, {
            "./flate": 7,
            "./stream/GenericWorker": 28
          }],
          4: [function (e, t, r) {

            var n = e("./utils");
            var o = function () {
              for (var e, t = [], r = 0; r < 256; r++) {
                e = r;
                for (var n = 0; n < 8; n++) e = 1 & e ? 3988292384 ^ e >>> 1 : e >>> 1;
                t[r] = e;
              }
              return t;
            }();
            t.exports = function (e, t) {
              return void 0 !== e && e.length ? "string" !== n.getTypeOf(e) ? function (e, t, r, n) {
                var i = o,
                  s = n + r;
                e ^= -1;
                for (var a = n; a < s; a++) e = e >>> 8 ^ i[255 & (e ^ t[a])];
                return -1 ^ e;
              }(0 | t, e, e.length, 0) : function (e, t, r, n) {
                var i = o,
                  s = n + r;
                e ^= -1;
                for (var a = n; a < s; a++) e = e >>> 8 ^ i[255 & (e ^ t.charCodeAt(a))];
                return -1 ^ e;
              }(0 | t, e, e.length, 0) : 0;
            };
          }, {
            "./utils": 32
          }],
          5: [function (e, t, r) {

            r.base64 = !1, r.binary = !1, r.dir = !1, r.createFolders = !0, r.date = null, r.compression = null, r.compressionOptions = null, r.comment = null, r.unixPermissions = null, r.dosPermissions = null;
          }, {}],
          6: [function (e, t, r) {

            var n = null;
            n = "undefined" != typeof Promise ? Promise : e("lie"), t.exports = {
              Promise: n
            };
          }, {
            lie: 37
          }],
          7: [function (e, t, r) {

            var n = "undefined" != typeof Uint8Array && "undefined" != typeof Uint16Array && "undefined" != typeof Uint32Array,
              i = e("pako"),
              s = e("./utils"),
              a = e("./stream/GenericWorker"),
              o = n ? "uint8array" : "array";
            function h(e, t) {
              a.call(this, "FlateWorker/" + e), this._pako = null, this._pakoAction = e, this._pakoOptions = t, this.meta = {};
            }
            r.magic = "\b\0", s.inherits(h, a), h.prototype.processChunk = function (e) {
              this.meta = e.meta, null === this._pako && this._createPako(), this._pako.push(s.transformTo(o, e.data), !1);
            }, h.prototype.flush = function () {
              a.prototype.flush.call(this), null === this._pako && this._createPako(), this._pako.push([], !0);
            }, h.prototype.cleanUp = function () {
              a.prototype.cleanUp.call(this), this._pako = null;
            }, h.prototype._createPako = function () {
              this._pako = new i[this._pakoAction]({
                raw: !0,
                level: this._pakoOptions.level || -1
              });
              var t = this;
              this._pako.onData = function (e) {
                t.push({
                  data: e,
                  meta: t.meta
                });
              };
            }, r.compressWorker = function (e) {
              return new h("Deflate", e);
            }, r.uncompressWorker = function () {
              return new h("Inflate", {});
            };
          }, {
            "./stream/GenericWorker": 28,
            "./utils": 32,
            pako: 38
          }],
          8: [function (e, t, r) {

            function A(e, t) {
              var r,
                n = "";
              for (r = 0; r < t; r++) n += String.fromCharCode(255 & e), e >>>= 8;
              return n;
            }
            function n(e, t, r, n, i, s) {
              var a,
                o,
                h = e.file,
                u = e.compression,
                l = s !== O.utf8encode,
                f = I.transformTo("string", s(h.name)),
                c = I.transformTo("string", O.utf8encode(h.name)),
                d = h.comment,
                p = I.transformTo("string", s(d)),
                m = I.transformTo("string", O.utf8encode(d)),
                _ = c.length !== h.name.length,
                g = m.length !== d.length,
                b = "",
                v = "",
                y = "",
                w = h.dir,
                k = h.date,
                x = {
                  crc32: 0,
                  compressedSize: 0,
                  uncompressedSize: 0
                };
              t && !r || (x.crc32 = e.crc32, x.compressedSize = e.compressedSize, x.uncompressedSize = e.uncompressedSize);
              var S = 0;
              t && (S |= 8), l || !_ && !g || (S |= 2048);
              var z = 0,
                C = 0;
              w && (z |= 16), "UNIX" === i ? (C = 798, z |= function (e, t) {
                var r = e;
                return e || (r = t ? 16893 : 33204), (65535 & r) << 16;
              }(h.unixPermissions, w)) : (C = 20, z |= function (e) {
                return 63 & (e || 0);
              }(h.dosPermissions)), a = k.getUTCHours(), a <<= 6, a |= k.getUTCMinutes(), a <<= 5, a |= k.getUTCSeconds() / 2, o = k.getUTCFullYear() - 1980, o <<= 4, o |= k.getUTCMonth() + 1, o <<= 5, o |= k.getUTCDate(), _ && (v = A(1, 1) + A(B(f), 4) + c, b += "up" + A(v.length, 2) + v), g && (y = A(1, 1) + A(B(p), 4) + m, b += "uc" + A(y.length, 2) + y);
              var E = "";
              return E += "\n\0", E += A(S, 2), E += u.magic, E += A(a, 2), E += A(o, 2), E += A(x.crc32, 4), E += A(x.compressedSize, 4), E += A(x.uncompressedSize, 4), E += A(f.length, 2), E += A(b.length, 2), {
                fileRecord: R.LOCAL_FILE_HEADER + E + f + b,
                dirRecord: R.CENTRAL_FILE_HEADER + A(C, 2) + E + A(p.length, 2) + "\0\0\0\0" + A(z, 4) + A(n, 4) + f + b + p
              };
            }
            var I = e("../utils"),
              i = e("../stream/GenericWorker"),
              O = e("../utf8"),
              B = e("../crc32"),
              R = e("../signature");
            function s(e, t, r, n) {
              i.call(this, "ZipFileWorker"), this.bytesWritten = 0, this.zipComment = t, this.zipPlatform = r, this.encodeFileName = n, this.streamFiles = e, this.accumulate = !1, this.contentBuffer = [], this.dirRecords = [], this.currentSourceOffset = 0, this.entriesCount = 0, this.currentFile = null, this._sources = [];
            }
            I.inherits(s, i), s.prototype.push = function (e) {
              var t = e.meta.percent || 0,
                r = this.entriesCount,
                n = this._sources.length;
              this.accumulate ? this.contentBuffer.push(e) : (this.bytesWritten += e.data.length, i.prototype.push.call(this, {
                data: e.data,
                meta: {
                  currentFile: this.currentFile,
                  percent: r ? (t + 100 * (r - n - 1)) / r : 100
                }
              }));
            }, s.prototype.openedSource = function (e) {
              this.currentSourceOffset = this.bytesWritten, this.currentFile = e.file.name;
              var t = this.streamFiles && !e.file.dir;
              if (t) {
                var r = n(e, t, !1, this.currentSourceOffset, this.zipPlatform, this.encodeFileName);
                this.push({
                  data: r.fileRecord,
                  meta: {
                    percent: 0
                  }
                });
              } else this.accumulate = !0;
            }, s.prototype.closedSource = function (e) {
              this.accumulate = !1;
              var t = this.streamFiles && !e.file.dir,
                r = n(e, t, !0, this.currentSourceOffset, this.zipPlatform, this.encodeFileName);
              if (this.dirRecords.push(r.dirRecord), t) this.push({
                data: function (e) {
                  return R.DATA_DESCRIPTOR + A(e.crc32, 4) + A(e.compressedSize, 4) + A(e.uncompressedSize, 4);
                }(e),
                meta: {
                  percent: 100
                }
              });else for (this.push({
                data: r.fileRecord,
                meta: {
                  percent: 0
                }
              }); this.contentBuffer.length;) this.push(this.contentBuffer.shift());
              this.currentFile = null;
            }, s.prototype.flush = function () {
              for (var e = this.bytesWritten, t = 0; t < this.dirRecords.length; t++) this.push({
                data: this.dirRecords[t],
                meta: {
                  percent: 100
                }
              });
              var r = this.bytesWritten - e,
                n = function (e, t, r, n, i) {
                  var s = I.transformTo("string", i(n));
                  return R.CENTRAL_DIRECTORY_END + "\0\0\0\0" + A(e, 2) + A(e, 2) + A(t, 4) + A(r, 4) + A(s.length, 2) + s;
                }(this.dirRecords.length, r, e, this.zipComment, this.encodeFileName);
              this.push({
                data: n,
                meta: {
                  percent: 100
                }
              });
            }, s.prototype.prepareNextSource = function () {
              this.previous = this._sources.shift(), this.openedSource(this.previous.streamInfo), this.isPaused ? this.previous.pause() : this.previous.resume();
            }, s.prototype.registerPrevious = function (e) {
              this._sources.push(e);
              var t = this;
              return e.on("data", function (e) {
                t.processChunk(e);
              }), e.on("end", function () {
                t.closedSource(t.previous.streamInfo), t._sources.length ? t.prepareNextSource() : t.end();
              }), e.on("error", function (e) {
                t.error(e);
              }), this;
            }, s.prototype.resume = function () {
              return !!i.prototype.resume.call(this) && (!this.previous && this._sources.length ? (this.prepareNextSource(), !0) : this.previous || this._sources.length || this.generatedError ? void 0 : (this.end(), !0));
            }, s.prototype.error = function (e) {
              var t = this._sources;
              if (!i.prototype.error.call(this, e)) return !1;
              for (var r = 0; r < t.length; r++) try {
                t[r].error(e);
              } catch (e) {}
              return !0;
            }, s.prototype.lock = function () {
              i.prototype.lock.call(this);
              for (var e = this._sources, t = 0; t < e.length; t++) e[t].lock();
            }, t.exports = s;
          }, {
            "../crc32": 4,
            "../signature": 23,
            "../stream/GenericWorker": 28,
            "../utf8": 31,
            "../utils": 32
          }],
          9: [function (e, t, r) {

            var u = e("../compressions"),
              n = e("./ZipFileWorker");
            r.generateWorker = function (e, a, t) {
              var o = new n(a.streamFiles, t, a.platform, a.encodeFileName),
                h = 0;
              try {
                e.forEach(function (e, t) {
                  h++;
                  var r = function (e, t) {
                      var r = e || t,
                        n = u[r];
                      if (!n) throw new Error(r + " is not a valid compression method !");
                      return n;
                    }(t.options.compression, a.compression),
                    n = t.options.compressionOptions || a.compressionOptions || {},
                    i = t.dir,
                    s = t.date;
                  t._compressWorker(r, n).withStreamInfo("file", {
                    name: e,
                    dir: i,
                    date: s,
                    comment: t.comment || "",
                    unixPermissions: t.unixPermissions,
                    dosPermissions: t.dosPermissions
                  }).pipe(o);
                }), o.entriesCount = h;
              } catch (e) {
                o.error(e);
              }
              return o;
            };
          }, {
            "../compressions": 3,
            "./ZipFileWorker": 8
          }],
          10: [function (e, t, r) {

            function n() {
              if (!(this instanceof n)) return new n();
              if (arguments.length) throw new Error("The constructor with parameters has been removed in JSZip 3.0, please check the upgrade guide.");
              this.files = Object.create(null), this.comment = null, this.root = "", this.clone = function () {
                var e = new n();
                for (var t in this) "function" != typeof this[t] && (e[t] = this[t]);
                return e;
              };
            }
            (n.prototype = e("./object")).loadAsync = e("./load"), n.support = e("./support"), n.defaults = e("./defaults"), n.version = "3.10.1", n.loadAsync = function (e, t) {
              return new n().loadAsync(e, t);
            }, n.external = e("./external"), t.exports = n;
          }, {
            "./defaults": 5,
            "./external": 6,
            "./load": 11,
            "./object": 15,
            "./support": 30
          }],
          11: [function (e, t, r) {

            var u = e("./utils"),
              i = e("./external"),
              n = e("./utf8"),
              s = e("./zipEntries"),
              a = e("./stream/Crc32Probe"),
              l = e("./nodejsUtils");
            function f(n) {
              return new i.Promise(function (e, t) {
                var r = n.decompressed.getContentWorker().pipe(new a());
                r.on("error", function (e) {
                  t(e);
                }).on("end", function () {
                  r.streamInfo.crc32 !== n.decompressed.crc32 ? t(new Error("Corrupted zip : CRC32 mismatch")) : e();
                }).resume();
              });
            }
            t.exports = function (e, o) {
              var h = this;
              return o = u.extend(o || {}, {
                base64: !1,
                checkCRC32: !1,
                optimizedBinaryString: !1,
                createFolders: !1,
                decodeFileName: n.utf8decode
              }), l.isNode && l.isStream(e) ? i.Promise.reject(new Error("JSZip can't accept a stream when loading a zip file.")) : u.prepareContent("the loaded zip file", e, !0, o.optimizedBinaryString, o.base64).then(function (e) {
                var t = new s(o);
                return t.load(e), t;
              }).then(function (e) {
                var t = [i.Promise.resolve(e)],
                  r = e.files;
                if (o.checkCRC32) for (var n = 0; n < r.length; n++) t.push(f(r[n]));
                return i.Promise.all(t);
              }).then(function (e) {
                for (var t = e.shift(), r = t.files, n = 0; n < r.length; n++) {
                  var i = r[n],
                    s = i.fileNameStr,
                    a = u.resolve(i.fileNameStr);
                  h.file(a, i.decompressed, {
                    binary: !0,
                    optimizedBinaryString: !0,
                    date: i.date,
                    dir: i.dir,
                    comment: i.fileCommentStr.length ? i.fileCommentStr : null,
                    unixPermissions: i.unixPermissions,
                    dosPermissions: i.dosPermissions,
                    createFolders: o.createFolders
                  }), i.dir || (h.file(a).unsafeOriginalName = s);
                }
                return t.zipComment.length && (h.comment = t.zipComment), h;
              });
            };
          }, {
            "./external": 6,
            "./nodejsUtils": 14,
            "./stream/Crc32Probe": 25,
            "./utf8": 31,
            "./utils": 32,
            "./zipEntries": 33
          }],
          12: [function (e, t, r) {

            var n = e("../utils"),
              i = e("../stream/GenericWorker");
            function s(e, t) {
              i.call(this, "Nodejs stream input adapter for " + e), this._upstreamEnded = !1, this._bindStream(t);
            }
            n.inherits(s, i), s.prototype._bindStream = function (e) {
              var t = this;
              (this._stream = e).pause(), e.on("data", function (e) {
                t.push({
                  data: e,
                  meta: {
                    percent: 0
                  }
                });
              }).on("error", function (e) {
                t.isPaused ? this.generatedError = e : t.error(e);
              }).on("end", function () {
                t.isPaused ? t._upstreamEnded = !0 : t.end();
              });
            }, s.prototype.pause = function () {
              return !!i.prototype.pause.call(this) && (this._stream.pause(), !0);
            }, s.prototype.resume = function () {
              return !!i.prototype.resume.call(this) && (this._upstreamEnded ? this.end() : this._stream.resume(), !0);
            }, t.exports = s;
          }, {
            "../stream/GenericWorker": 28,
            "../utils": 32
          }],
          13: [function (e, t, r) {

            var i = e("readable-stream").Readable;
            function n(e, t, r) {
              i.call(this, t), this._helper = e;
              var n = this;
              e.on("data", function (e, t) {
                n.push(e) || n._helper.pause(), r && r(t);
              }).on("error", function (e) {
                n.emit("error", e);
              }).on("end", function () {
                n.push(null);
              });
            }
            e("../utils").inherits(n, i), n.prototype._read = function () {
              this._helper.resume();
            }, t.exports = n;
          }, {
            "../utils": 32,
            "readable-stream": 16
          }],
          14: [function (e, t, r) {

            t.exports = {
              isNode: "undefined" != typeof Buffer,
              newBufferFrom: function (e, t) {
                if (Buffer.from && Buffer.from !== Uint8Array.from) return Buffer.from(e, t);
                if ("number" == typeof e) throw new Error('The "data" argument must not be a number');
                return new Buffer(e, t);
              },
              allocBuffer: function (e) {
                if (Buffer.alloc) return Buffer.alloc(e);
                var t = new Buffer(e);
                return t.fill(0), t;
              },
              isBuffer: function (e) {
                return Buffer.isBuffer(e);
              },
              isStream: function (e) {
                return e && "function" == typeof e.on && "function" == typeof e.pause && "function" == typeof e.resume;
              }
            };
          }, {}],
          15: [function (e, t, r) {

            function s(e, t, r) {
              var n,
                i = u.getTypeOf(t),
                s = u.extend(r || {}, f);
              s.date = s.date || new Date(), null !== s.compression && (s.compression = s.compression.toUpperCase()), "string" == typeof s.unixPermissions && (s.unixPermissions = parseInt(s.unixPermissions, 8)), s.unixPermissions && 16384 & s.unixPermissions && (s.dir = !0), s.dosPermissions && 16 & s.dosPermissions && (s.dir = !0), s.dir && (e = g(e)), s.createFolders && (n = _(e)) && b.call(this, n, !0);
              var a = "string" === i && !1 === s.binary && !1 === s.base64;
              r && void 0 !== r.binary || (s.binary = !a), (t instanceof c && 0 === t.uncompressedSize || s.dir || !t || 0 === t.length) && (s.base64 = !1, s.binary = !0, t = "", s.compression = "STORE", i = "string");
              var o = null;
              o = t instanceof c || t instanceof l ? t : p.isNode && p.isStream(t) ? new m(e, t) : u.prepareContent(e, t, s.binary, s.optimizedBinaryString, s.base64);
              var h = new d(e, o, s);
              this.files[e] = h;
            }
            var i = e("./utf8"),
              u = e("./utils"),
              l = e("./stream/GenericWorker"),
              a = e("./stream/StreamHelper"),
              f = e("./defaults"),
              c = e("./compressedObject"),
              d = e("./zipObject"),
              o = e("./generate"),
              p = e("./nodejsUtils"),
              m = e("./nodejs/NodejsStreamInputAdapter"),
              _ = function (e) {
                "/" === e.slice(-1) && (e = e.substring(0, e.length - 1));
                var t = e.lastIndexOf("/");
                return 0 < t ? e.substring(0, t) : "";
              },
              g = function (e) {
                return "/" !== e.slice(-1) && (e += "/"), e;
              },
              b = function (e, t) {
                return t = void 0 !== t ? t : f.createFolders, e = g(e), this.files[e] || s.call(this, e, null, {
                  dir: !0,
                  createFolders: t
                }), this.files[e];
              };
            function h(e) {
              return "[object RegExp]" === Object.prototype.toString.call(e);
            }
            var n = {
              load: function () {
                throw new Error("This method has been removed in JSZip 3.0, please check the upgrade guide.");
              },
              forEach: function (e) {
                var t, r, n;
                for (t in this.files) n = this.files[t], (r = t.slice(this.root.length, t.length)) && t.slice(0, this.root.length) === this.root && e(r, n);
              },
              filter: function (r) {
                var n = [];
                return this.forEach(function (e, t) {
                  r(e, t) && n.push(t);
                }), n;
              },
              file: function (e, t, r) {
                if (1 !== arguments.length) return e = this.root + e, s.call(this, e, t, r), this;
                if (h(e)) {
                  var n = e;
                  return this.filter(function (e, t) {
                    return !t.dir && n.test(e);
                  });
                }
                var i = this.files[this.root + e];
                return i && !i.dir ? i : null;
              },
              folder: function (r) {
                if (!r) return this;
                if (h(r)) return this.filter(function (e, t) {
                  return t.dir && r.test(e);
                });
                var e = this.root + r,
                  t = b.call(this, e),
                  n = this.clone();
                return n.root = t.name, n;
              },
              remove: function (r) {
                r = this.root + r;
                var e = this.files[r];
                if (e || ("/" !== r.slice(-1) && (r += "/"), e = this.files[r]), e && !e.dir) delete this.files[r];else for (var t = this.filter(function (e, t) {
                    return t.name.slice(0, r.length) === r;
                  }), n = 0; n < t.length; n++) delete this.files[t[n].name];
                return this;
              },
              generate: function () {
                throw new Error("This method has been removed in JSZip 3.0, please check the upgrade guide.");
              },
              generateInternalStream: function (e) {
                var t,
                  r = {};
                try {
                  if ((r = u.extend(e || {}, {
                    streamFiles: !1,
                    compression: "STORE",
                    compressionOptions: null,
                    type: "",
                    platform: "DOS",
                    comment: null,
                    mimeType: "application/zip",
                    encodeFileName: i.utf8encode
                  })).type = r.type.toLowerCase(), r.compression = r.compression.toUpperCase(), "binarystring" === r.type && (r.type = "string"), !r.type) throw new Error("No output type specified.");
                  u.checkSupport(r.type), "darwin" !== r.platform && "freebsd" !== r.platform && "linux" !== r.platform && "sunos" !== r.platform || (r.platform = "UNIX"), "win32" === r.platform && (r.platform = "DOS");
                  var n = r.comment || this.comment || "";
                  t = o.generateWorker(this, r, n);
                } catch (e) {
                  (t = new l("error")).error(e);
                }
                return new a(t, r.type || "string", r.mimeType);
              },
              generateAsync: function (e, t) {
                return this.generateInternalStream(e).accumulate(t);
              },
              generateNodeStream: function (e, t) {
                return (e = e || {}).type || (e.type = "nodebuffer"), this.generateInternalStream(e).toNodejsStream(t);
              }
            };
            t.exports = n;
          }, {
            "./compressedObject": 2,
            "./defaults": 5,
            "./generate": 9,
            "./nodejs/NodejsStreamInputAdapter": 12,
            "./nodejsUtils": 14,
            "./stream/GenericWorker": 28,
            "./stream/StreamHelper": 29,
            "./utf8": 31,
            "./utils": 32,
            "./zipObject": 35
          }],
          16: [function (e, t, r) {

            t.exports = e("stream");
          }, {
            stream: void 0
          }],
          17: [function (e, t, r) {

            var n = e("./DataReader");
            function i(e) {
              n.call(this, e);
              for (var t = 0; t < this.data.length; t++) e[t] = 255 & e[t];
            }
            e("../utils").inherits(i, n), i.prototype.byteAt = function (e) {
              return this.data[this.zero + e];
            }, i.prototype.lastIndexOfSignature = function (e) {
              for (var t = e.charCodeAt(0), r = e.charCodeAt(1), n = e.charCodeAt(2), i = e.charCodeAt(3), s = this.length - 4; 0 <= s; --s) if (this.data[s] === t && this.data[s + 1] === r && this.data[s + 2] === n && this.data[s + 3] === i) return s - this.zero;
              return -1;
            }, i.prototype.readAndCheckSignature = function (e) {
              var t = e.charCodeAt(0),
                r = e.charCodeAt(1),
                n = e.charCodeAt(2),
                i = e.charCodeAt(3),
                s = this.readData(4);
              return t === s[0] && r === s[1] && n === s[2] && i === s[3];
            }, i.prototype.readData = function (e) {
              if (this.checkOffset(e), 0 === e) return [];
              var t = this.data.slice(this.zero + this.index, this.zero + this.index + e);
              return this.index += e, t;
            }, t.exports = i;
          }, {
            "../utils": 32,
            "./DataReader": 18
          }],
          18: [function (e, t, r) {

            var n = e("../utils");
            function i(e) {
              this.data = e, this.length = e.length, this.index = 0, this.zero = 0;
            }
            i.prototype = {
              checkOffset: function (e) {
                this.checkIndex(this.index + e);
              },
              checkIndex: function (e) {
                if (this.length < this.zero + e || e < 0) throw new Error("End of data reached (data length = " + this.length + ", asked index = " + e + "). Corrupted zip ?");
              },
              setIndex: function (e) {
                this.checkIndex(e), this.index = e;
              },
              skip: function (e) {
                this.setIndex(this.index + e);
              },
              byteAt: function () {},
              readInt: function (e) {
                var t,
                  r = 0;
                for (this.checkOffset(e), t = this.index + e - 1; t >= this.index; t--) r = (r << 8) + this.byteAt(t);
                return this.index += e, r;
              },
              readString: function (e) {
                return n.transformTo("string", this.readData(e));
              },
              readData: function () {},
              lastIndexOfSignature: function () {},
              readAndCheckSignature: function () {},
              readDate: function () {
                var e = this.readInt(4);
                return new Date(Date.UTC(1980 + (e >> 25 & 127), (e >> 21 & 15) - 1, e >> 16 & 31, e >> 11 & 31, e >> 5 & 63, (31 & e) << 1));
              }
            }, t.exports = i;
          }, {
            "../utils": 32
          }],
          19: [function (e, t, r) {

            var n = e("./Uint8ArrayReader");
            function i(e) {
              n.call(this, e);
            }
            e("../utils").inherits(i, n), i.prototype.readData = function (e) {
              this.checkOffset(e);
              var t = this.data.slice(this.zero + this.index, this.zero + this.index + e);
              return this.index += e, t;
            }, t.exports = i;
          }, {
            "../utils": 32,
            "./Uint8ArrayReader": 21
          }],
          20: [function (e, t, r) {

            var n = e("./DataReader");
            function i(e) {
              n.call(this, e);
            }
            e("../utils").inherits(i, n), i.prototype.byteAt = function (e) {
              return this.data.charCodeAt(this.zero + e);
            }, i.prototype.lastIndexOfSignature = function (e) {
              return this.data.lastIndexOf(e) - this.zero;
            }, i.prototype.readAndCheckSignature = function (e) {
              return e === this.readData(4);
            }, i.prototype.readData = function (e) {
              this.checkOffset(e);
              var t = this.data.slice(this.zero + this.index, this.zero + this.index + e);
              return this.index += e, t;
            }, t.exports = i;
          }, {
            "../utils": 32,
            "./DataReader": 18
          }],
          21: [function (e, t, r) {

            var n = e("./ArrayReader");
            function i(e) {
              n.call(this, e);
            }
            e("../utils").inherits(i, n), i.prototype.readData = function (e) {
              if (this.checkOffset(e), 0 === e) return new Uint8Array(0);
              var t = this.data.subarray(this.zero + this.index, this.zero + this.index + e);
              return this.index += e, t;
            }, t.exports = i;
          }, {
            "../utils": 32,
            "./ArrayReader": 17
          }],
          22: [function (e, t, r) {

            var n = e("../utils"),
              i = e("../support"),
              s = e("./ArrayReader"),
              a = e("./StringReader"),
              o = e("./NodeBufferReader"),
              h = e("./Uint8ArrayReader");
            t.exports = function (e) {
              var t = n.getTypeOf(e);
              return n.checkSupport(t), "string" !== t || i.uint8array ? "nodebuffer" === t ? new o(e) : i.uint8array ? new h(n.transformTo("uint8array", e)) : new s(n.transformTo("array", e)) : new a(e);
            };
          }, {
            "../support": 30,
            "../utils": 32,
            "./ArrayReader": 17,
            "./NodeBufferReader": 19,
            "./StringReader": 20,
            "./Uint8ArrayReader": 21
          }],
          23: [function (e, t, r) {

            r.LOCAL_FILE_HEADER = "PK", r.CENTRAL_FILE_HEADER = "PK", r.CENTRAL_DIRECTORY_END = "PK", r.ZIP64_CENTRAL_DIRECTORY_LOCATOR = "PK", r.ZIP64_CENTRAL_DIRECTORY_END = "PK", r.DATA_DESCRIPTOR = "PK\b";
          }, {}],
          24: [function (e, t, r) {

            var n = e("./GenericWorker"),
              i = e("../utils");
            function s(e) {
              n.call(this, "ConvertWorker to " + e), this.destType = e;
            }
            i.inherits(s, n), s.prototype.processChunk = function (e) {
              this.push({
                data: i.transformTo(this.destType, e.data),
                meta: e.meta
              });
            }, t.exports = s;
          }, {
            "../utils": 32,
            "./GenericWorker": 28
          }],
          25: [function (e, t, r) {

            var n = e("./GenericWorker"),
              i = e("../crc32");
            function s() {
              n.call(this, "Crc32Probe"), this.withStreamInfo("crc32", 0);
            }
            e("../utils").inherits(s, n), s.prototype.processChunk = function (e) {
              this.streamInfo.crc32 = i(e.data, this.streamInfo.crc32 || 0), this.push(e);
            }, t.exports = s;
          }, {
            "../crc32": 4,
            "../utils": 32,
            "./GenericWorker": 28
          }],
          26: [function (e, t, r) {

            var n = e("../utils"),
              i = e("./GenericWorker");
            function s(e) {
              i.call(this, "DataLengthProbe for " + e), this.propName = e, this.withStreamInfo(e, 0);
            }
            n.inherits(s, i), s.prototype.processChunk = function (e) {
              if (e) {
                var t = this.streamInfo[this.propName] || 0;
                this.streamInfo[this.propName] = t + e.data.length;
              }
              i.prototype.processChunk.call(this, e);
            }, t.exports = s;
          }, {
            "../utils": 32,
            "./GenericWorker": 28
          }],
          27: [function (e, t, r) {

            var n = e("../utils"),
              i = e("./GenericWorker");
            function s(e) {
              i.call(this, "DataWorker");
              var t = this;
              this.dataIsReady = !1, this.index = 0, this.max = 0, this.data = null, this.type = "", this._tickScheduled = !1, e.then(function (e) {
                t.dataIsReady = !0, t.data = e, t.max = e && e.length || 0, t.type = n.getTypeOf(e), t.isPaused || t._tickAndRepeat();
              }, function (e) {
                t.error(e);
              });
            }
            n.inherits(s, i), s.prototype.cleanUp = function () {
              i.prototype.cleanUp.call(this), this.data = null;
            }, s.prototype.resume = function () {
              return !!i.prototype.resume.call(this) && (!this._tickScheduled && this.dataIsReady && (this._tickScheduled = !0, n.delay(this._tickAndRepeat, [], this)), !0);
            }, s.prototype._tickAndRepeat = function () {
              this._tickScheduled = !1, this.isPaused || this.isFinished || (this._tick(), this.isFinished || (n.delay(this._tickAndRepeat, [], this), this._tickScheduled = !0));
            }, s.prototype._tick = function () {
              if (this.isPaused || this.isFinished) return !1;
              var e = null,
                t = Math.min(this.max, this.index + 16384);
              if (this.index >= this.max) return this.end();
              switch (this.type) {
                case "string":
                  e = this.data.substring(this.index, t);
                  break;
                case "uint8array":
                  e = this.data.subarray(this.index, t);
                  break;
                case "array":
                case "nodebuffer":
                  e = this.data.slice(this.index, t);
              }
              return this.index = t, this.push({
                data: e,
                meta: {
                  percent: this.max ? this.index / this.max * 100 : 0
                }
              });
            }, t.exports = s;
          }, {
            "../utils": 32,
            "./GenericWorker": 28
          }],
          28: [function (e, t, r) {

            function n(e) {
              this.name = e || "default", this.streamInfo = {}, this.generatedError = null, this.extraStreamInfo = {}, this.isPaused = !0, this.isFinished = !1, this.isLocked = !1, this._listeners = {
                data: [],
                end: [],
                error: []
              }, this.previous = null;
            }
            n.prototype = {
              push: function (e) {
                this.emit("data", e);
              },
              end: function () {
                if (this.isFinished) return !1;
                this.flush();
                try {
                  this.emit("end"), this.cleanUp(), this.isFinished = !0;
                } catch (e) {
                  this.emit("error", e);
                }
                return !0;
              },
              error: function (e) {
                return !this.isFinished && (this.isPaused ? this.generatedError = e : (this.isFinished = !0, this.emit("error", e), this.previous && this.previous.error(e), this.cleanUp()), !0);
              },
              on: function (e, t) {
                return this._listeners[e].push(t), this;
              },
              cleanUp: function () {
                this.streamInfo = this.generatedError = this.extraStreamInfo = null, this._listeners = [];
              },
              emit: function (e, t) {
                if (this._listeners[e]) for (var r = 0; r < this._listeners[e].length; r++) this._listeners[e][r].call(this, t);
              },
              pipe: function (e) {
                return e.registerPrevious(this);
              },
              registerPrevious: function (e) {
                if (this.isLocked) throw new Error("The stream '" + this + "' has already been used.");
                this.streamInfo = e.streamInfo, this.mergeStreamInfo(), this.previous = e;
                var t = this;
                return e.on("data", function (e) {
                  t.processChunk(e);
                }), e.on("end", function () {
                  t.end();
                }), e.on("error", function (e) {
                  t.error(e);
                }), this;
              },
              pause: function () {
                return !this.isPaused && !this.isFinished && (this.isPaused = !0, this.previous && this.previous.pause(), !0);
              },
              resume: function () {
                if (!this.isPaused || this.isFinished) return !1;
                var e = this.isPaused = !1;
                return this.generatedError && (this.error(this.generatedError), e = !0), this.previous && this.previous.resume(), !e;
              },
              flush: function () {},
              processChunk: function (e) {
                this.push(e);
              },
              withStreamInfo: function (e, t) {
                return this.extraStreamInfo[e] = t, this.mergeStreamInfo(), this;
              },
              mergeStreamInfo: function () {
                for (var e in this.extraStreamInfo) Object.prototype.hasOwnProperty.call(this.extraStreamInfo, e) && (this.streamInfo[e] = this.extraStreamInfo[e]);
              },
              lock: function () {
                if (this.isLocked) throw new Error("The stream '" + this + "' has already been used.");
                this.isLocked = !0, this.previous && this.previous.lock();
              },
              toString: function () {
                var e = "Worker " + this.name;
                return this.previous ? this.previous + " -> " + e : e;
              }
            }, t.exports = n;
          }, {}],
          29: [function (e, t, r) {

            var h = e("../utils"),
              i = e("./ConvertWorker"),
              s = e("./GenericWorker"),
              u = e("../base64"),
              n = e("../support"),
              a = e("../external"),
              o = null;
            if (n.nodestream) try {
              o = e("../nodejs/NodejsStreamOutputAdapter");
            } catch (e) {}
            function l(e, o) {
              return new a.Promise(function (t, r) {
                var n = [],
                  i = e._internalType,
                  s = e._outputType,
                  a = e._mimeType;
                e.on("data", function (e, t) {
                  n.push(e), o && o(t);
                }).on("error", function (e) {
                  n = [], r(e);
                }).on("end", function () {
                  try {
                    var e = function (e, t, r) {
                      switch (e) {
                        case "blob":
                          return h.newBlob(h.transformTo("arraybuffer", t), r);
                        case "base64":
                          return u.encode(t);
                        default:
                          return h.transformTo(e, t);
                      }
                    }(s, function (e, t) {
                      var r,
                        n = 0,
                        i = null,
                        s = 0;
                      for (r = 0; r < t.length; r++) s += t[r].length;
                      switch (e) {
                        case "string":
                          return t.join("");
                        case "array":
                          return Array.prototype.concat.apply([], t);
                        case "uint8array":
                          for (i = new Uint8Array(s), r = 0; r < t.length; r++) i.set(t[r], n), n += t[r].length;
                          return i;
                        case "nodebuffer":
                          return Buffer.concat(t);
                        default:
                          throw new Error("concat : unsupported type '" + e + "'");
                      }
                    }(i, n), a);
                    t(e);
                  } catch (e) {
                    r(e);
                  }
                  n = [];
                }).resume();
              });
            }
            function f(e, t, r) {
              var n = t;
              switch (t) {
                case "blob":
                case "arraybuffer":
                  n = "uint8array";
                  break;
                case "base64":
                  n = "string";
              }
              try {
                this._internalType = n, this._outputType = t, this._mimeType = r, h.checkSupport(n), this._worker = e.pipe(new i(n)), e.lock();
              } catch (e) {
                this._worker = new s("error"), this._worker.error(e);
              }
            }
            f.prototype = {
              accumulate: function (e) {
                return l(this, e);
              },
              on: function (e, t) {
                var r = this;
                return "data" === e ? this._worker.on(e, function (e) {
                  t.call(r, e.data, e.meta);
                }) : this._worker.on(e, function () {
                  h.delay(t, arguments, r);
                }), this;
              },
              resume: function () {
                return h.delay(this._worker.resume, [], this._worker), this;
              },
              pause: function () {
                return this._worker.pause(), this;
              },
              toNodejsStream: function (e) {
                if (h.checkSupport("nodestream"), "nodebuffer" !== this._outputType) throw new Error(this._outputType + " is not supported by this method");
                return new o(this, {
                  objectMode: "nodebuffer" !== this._outputType
                }, e);
              }
            }, t.exports = f;
          }, {
            "../base64": 1,
            "../external": 6,
            "../nodejs/NodejsStreamOutputAdapter": 13,
            "../support": 30,
            "../utils": 32,
            "./ConvertWorker": 24,
            "./GenericWorker": 28
          }],
          30: [function (e, t, r) {

            if (r.base64 = !0, r.array = !0, r.string = !0, r.arraybuffer = "undefined" != typeof ArrayBuffer && "undefined" != typeof Uint8Array, r.nodebuffer = "undefined" != typeof Buffer, r.uint8array = "undefined" != typeof Uint8Array, "undefined" == typeof ArrayBuffer) r.blob = !1;else {
              var n = new ArrayBuffer(0);
              try {
                r.blob = 0 === new Blob([n], {
                  type: "application/zip"
                }).size;
              } catch (e) {
                try {
                  var i = new (self.BlobBuilder || self.WebKitBlobBuilder || self.MozBlobBuilder || self.MSBlobBuilder)();
                  i.append(n), r.blob = 0 === i.getBlob("application/zip").size;
                } catch (e) {
                  r.blob = !1;
                }
              }
            }
            try {
              r.nodestream = !!e("readable-stream").Readable;
            } catch (e) {
              r.nodestream = !1;
            }
          }, {
            "readable-stream": 16
          }],
          31: [function (e, t, s) {

            for (var o = e("./utils"), h = e("./support"), r = e("./nodejsUtils"), n = e("./stream/GenericWorker"), u = new Array(256), i = 0; i < 256; i++) u[i] = 252 <= i ? 6 : 248 <= i ? 5 : 240 <= i ? 4 : 224 <= i ? 3 : 192 <= i ? 2 : 1;
            u[254] = u[254] = 1;
            function a() {
              n.call(this, "utf-8 decode"), this.leftOver = null;
            }
            function l() {
              n.call(this, "utf-8 encode");
            }
            s.utf8encode = function (e) {
              return h.nodebuffer ? r.newBufferFrom(e, "utf-8") : function (e) {
                var t,
                  r,
                  n,
                  i,
                  s,
                  a = e.length,
                  o = 0;
                for (i = 0; i < a; i++) 55296 == (64512 & (r = e.charCodeAt(i))) && i + 1 < a && 56320 == (64512 & (n = e.charCodeAt(i + 1))) && (r = 65536 + (r - 55296 << 10) + (n - 56320), i++), o += r < 128 ? 1 : r < 2048 ? 2 : r < 65536 ? 3 : 4;
                for (t = h.uint8array ? new Uint8Array(o) : new Array(o), i = s = 0; s < o; i++) 55296 == (64512 & (r = e.charCodeAt(i))) && i + 1 < a && 56320 == (64512 & (n = e.charCodeAt(i + 1))) && (r = 65536 + (r - 55296 << 10) + (n - 56320), i++), r < 128 ? t[s++] = r : (r < 2048 ? t[s++] = 192 | r >>> 6 : (r < 65536 ? t[s++] = 224 | r >>> 12 : (t[s++] = 240 | r >>> 18, t[s++] = 128 | r >>> 12 & 63), t[s++] = 128 | r >>> 6 & 63), t[s++] = 128 | 63 & r);
                return t;
              }(e);
            }, s.utf8decode = function (e) {
              return h.nodebuffer ? o.transformTo("nodebuffer", e).toString("utf-8") : function (e) {
                var t,
                  r,
                  n,
                  i,
                  s = e.length,
                  a = new Array(2 * s);
                for (t = r = 0; t < s;) if ((n = e[t++]) < 128) a[r++] = n;else if (4 < (i = u[n])) a[r++] = 65533, t += i - 1;else {
                  for (n &= 2 === i ? 31 : 3 === i ? 15 : 7; 1 < i && t < s;) n = n << 6 | 63 & e[t++], i--;
                  1 < i ? a[r++] = 65533 : n < 65536 ? a[r++] = n : (n -= 65536, a[r++] = 55296 | n >> 10 & 1023, a[r++] = 56320 | 1023 & n);
                }
                return a.length !== r && (a.subarray ? a = a.subarray(0, r) : a.length = r), o.applyFromCharCode(a);
              }(e = o.transformTo(h.uint8array ? "uint8array" : "array", e));
            }, o.inherits(a, n), a.prototype.processChunk = function (e) {
              var t = o.transformTo(h.uint8array ? "uint8array" : "array", e.data);
              if (this.leftOver && this.leftOver.length) {
                if (h.uint8array) {
                  var r = t;
                  (t = new Uint8Array(r.length + this.leftOver.length)).set(this.leftOver, 0), t.set(r, this.leftOver.length);
                } else t = this.leftOver.concat(t);
                this.leftOver = null;
              }
              var n = function (e, t) {
                  var r;
                  for ((t = t || e.length) > e.length && (t = e.length), r = t - 1; 0 <= r && 128 == (192 & e[r]);) r--;
                  return r < 0 ? t : 0 === r ? t : r + u[e[r]] > t ? r : t;
                }(t),
                i = t;
              n !== t.length && (h.uint8array ? (i = t.subarray(0, n), this.leftOver = t.subarray(n, t.length)) : (i = t.slice(0, n), this.leftOver = t.slice(n, t.length))), this.push({
                data: s.utf8decode(i),
                meta: e.meta
              });
            }, a.prototype.flush = function () {
              this.leftOver && this.leftOver.length && (this.push({
                data: s.utf8decode(this.leftOver),
                meta: {}
              }), this.leftOver = null);
            }, s.Utf8DecodeWorker = a, o.inherits(l, n), l.prototype.processChunk = function (e) {
              this.push({
                data: s.utf8encode(e.data),
                meta: e.meta
              });
            }, s.Utf8EncodeWorker = l;
          }, {
            "./nodejsUtils": 14,
            "./stream/GenericWorker": 28,
            "./support": 30,
            "./utils": 32
          }],
          32: [function (e, t, a) {

            var o = e("./support"),
              h = e("./base64"),
              r = e("./nodejsUtils"),
              u = e("./external");
            function n(e) {
              return e;
            }
            function l(e, t) {
              for (var r = 0; r < e.length; ++r) t[r] = 255 & e.charCodeAt(r);
              return t;
            }
            e("setimmediate"), a.newBlob = function (t, r) {
              a.checkSupport("blob");
              try {
                return new Blob([t], {
                  type: r
                });
              } catch (e) {
                try {
                  var n = new (self.BlobBuilder || self.WebKitBlobBuilder || self.MozBlobBuilder || self.MSBlobBuilder)();
                  return n.append(t), n.getBlob(r);
                } catch (e) {
                  throw new Error("Bug : can't construct the Blob.");
                }
              }
            };
            var i = {
              stringifyByChunk: function (e, t, r) {
                var n = [],
                  i = 0,
                  s = e.length;
                if (s <= r) return String.fromCharCode.apply(null, e);
                for (; i < s;) "array" === t || "nodebuffer" === t ? n.push(String.fromCharCode.apply(null, e.slice(i, Math.min(i + r, s)))) : n.push(String.fromCharCode.apply(null, e.subarray(i, Math.min(i + r, s)))), i += r;
                return n.join("");
              },
              stringifyByChar: function (e) {
                for (var t = "", r = 0; r < e.length; r++) t += String.fromCharCode(e[r]);
                return t;
              },
              applyCanBeUsed: {
                uint8array: function () {
                  try {
                    return o.uint8array && 1 === String.fromCharCode.apply(null, new Uint8Array(1)).length;
                  } catch (e) {
                    return !1;
                  }
                }(),
                nodebuffer: function () {
                  try {
                    return o.nodebuffer && 1 === String.fromCharCode.apply(null, r.allocBuffer(1)).length;
                  } catch (e) {
                    return !1;
                  }
                }()
              }
            };
            function s(e) {
              var t = 65536,
                r = a.getTypeOf(e),
                n = !0;
              if ("uint8array" === r ? n = i.applyCanBeUsed.uint8array : "nodebuffer" === r && (n = i.applyCanBeUsed.nodebuffer), n) for (; 1 < t;) try {
                return i.stringifyByChunk(e, r, t);
              } catch (e) {
                t = Math.floor(t / 2);
              }
              return i.stringifyByChar(e);
            }
            function f(e, t) {
              for (var r = 0; r < e.length; r++) t[r] = e[r];
              return t;
            }
            a.applyFromCharCode = s;
            var c = {};
            c.string = {
              string: n,
              array: function (e) {
                return l(e, new Array(e.length));
              },
              arraybuffer: function (e) {
                return c.string.uint8array(e).buffer;
              },
              uint8array: function (e) {
                return l(e, new Uint8Array(e.length));
              },
              nodebuffer: function (e) {
                return l(e, r.allocBuffer(e.length));
              }
            }, c.array = {
              string: s,
              array: n,
              arraybuffer: function (e) {
                return new Uint8Array(e).buffer;
              },
              uint8array: function (e) {
                return new Uint8Array(e);
              },
              nodebuffer: function (e) {
                return r.newBufferFrom(e);
              }
            }, c.arraybuffer = {
              string: function (e) {
                return s(new Uint8Array(e));
              },
              array: function (e) {
                return f(new Uint8Array(e), new Array(e.byteLength));
              },
              arraybuffer: n,
              uint8array: function (e) {
                return new Uint8Array(e);
              },
              nodebuffer: function (e) {
                return r.newBufferFrom(new Uint8Array(e));
              }
            }, c.uint8array = {
              string: s,
              array: function (e) {
                return f(e, new Array(e.length));
              },
              arraybuffer: function (e) {
                return e.buffer;
              },
              uint8array: n,
              nodebuffer: function (e) {
                return r.newBufferFrom(e);
              }
            }, c.nodebuffer = {
              string: s,
              array: function (e) {
                return f(e, new Array(e.length));
              },
              arraybuffer: function (e) {
                return c.nodebuffer.uint8array(e).buffer;
              },
              uint8array: function (e) {
                return f(e, new Uint8Array(e.length));
              },
              nodebuffer: n
            }, a.transformTo = function (e, t) {
              if (t = t || "", !e) return t;
              a.checkSupport(e);
              var r = a.getTypeOf(t);
              return c[r][e](t);
            }, a.resolve = function (e) {
              for (var t = e.split("/"), r = [], n = 0; n < t.length; n++) {
                var i = t[n];
                "." === i || "" === i && 0 !== n && n !== t.length - 1 || (".." === i ? r.pop() : r.push(i));
              }
              return r.join("/");
            }, a.getTypeOf = function (e) {
              return "string" == typeof e ? "string" : "[object Array]" === Object.prototype.toString.call(e) ? "array" : o.nodebuffer && r.isBuffer(e) ? "nodebuffer" : o.uint8array && e instanceof Uint8Array ? "uint8array" : o.arraybuffer && e instanceof ArrayBuffer ? "arraybuffer" : void 0;
            }, a.checkSupport = function (e) {
              if (!o[e.toLowerCase()]) throw new Error(e + " is not supported by this platform");
            }, a.MAX_VALUE_16BITS = 65535, a.MAX_VALUE_32BITS = -1, a.pretty = function (e) {
              var t,
                r,
                n = "";
              for (r = 0; r < (e || "").length; r++) n += "\\x" + ((t = e.charCodeAt(r)) < 16 ? "0" : "") + t.toString(16).toUpperCase();
              return n;
            }, a.delay = function (e, t, r) {
              setImmediate(function () {
                e.apply(r || null, t || []);
              });
            }, a.inherits = function (e, t) {
              function r() {}
              r.prototype = t.prototype, e.prototype = new r();
            }, a.extend = function () {
              var e,
                t,
                r = {};
              for (e = 0; e < arguments.length; e++) for (t in arguments[e]) Object.prototype.hasOwnProperty.call(arguments[e], t) && void 0 === r[t] && (r[t] = arguments[e][t]);
              return r;
            }, a.prepareContent = function (r, e, n, i, s) {
              return u.Promise.resolve(e).then(function (n) {
                return o.blob && (n instanceof Blob || -1 !== ["[object File]", "[object Blob]"].indexOf(Object.prototype.toString.call(n))) && "undefined" != typeof FileReader ? new u.Promise(function (t, r) {
                  var e = new FileReader();
                  e.onload = function (e) {
                    t(e.target.result);
                  }, e.onerror = function (e) {
                    r(e.target.error);
                  }, e.readAsArrayBuffer(n);
                }) : n;
              }).then(function (e) {
                var t = a.getTypeOf(e);
                return t ? ("arraybuffer" === t ? e = a.transformTo("uint8array", e) : "string" === t && (s ? e = h.decode(e) : n && !0 !== i && (e = function (e) {
                  return l(e, o.uint8array ? new Uint8Array(e.length) : new Array(e.length));
                }(e))), e) : u.Promise.reject(new Error("Can't read the data of '" + r + "'. Is it in a supported JavaScript type (String, Blob, ArrayBuffer, etc) ?"));
              });
            };
          }, {
            "./base64": 1,
            "./external": 6,
            "./nodejsUtils": 14,
            "./support": 30,
            setimmediate: 54
          }],
          33: [function (e, t, r) {

            var n = e("./reader/readerFor"),
              i = e("./utils"),
              s = e("./signature"),
              a = e("./zipEntry"),
              o = e("./support");
            function h(e) {
              this.files = [], this.loadOptions = e;
            }
            h.prototype = {
              checkSignature: function (e) {
                if (!this.reader.readAndCheckSignature(e)) {
                  this.reader.index -= 4;
                  var t = this.reader.readString(4);
                  throw new Error("Corrupted zip or bug: unexpected signature (" + i.pretty(t) + ", expected " + i.pretty(e) + ")");
                }
              },
              isSignature: function (e, t) {
                var r = this.reader.index;
                this.reader.setIndex(e);
                var n = this.reader.readString(4) === t;
                return this.reader.setIndex(r), n;
              },
              readBlockEndOfCentral: function () {
                this.diskNumber = this.reader.readInt(2), this.diskWithCentralDirStart = this.reader.readInt(2), this.centralDirRecordsOnThisDisk = this.reader.readInt(2), this.centralDirRecords = this.reader.readInt(2), this.centralDirSize = this.reader.readInt(4), this.centralDirOffset = this.reader.readInt(4), this.zipCommentLength = this.reader.readInt(2);
                var e = this.reader.readData(this.zipCommentLength),
                  t = o.uint8array ? "uint8array" : "array",
                  r = i.transformTo(t, e);
                this.zipComment = this.loadOptions.decodeFileName(r);
              },
              readBlockZip64EndOfCentral: function () {
                this.zip64EndOfCentralSize = this.reader.readInt(8), this.reader.skip(4), this.diskNumber = this.reader.readInt(4), this.diskWithCentralDirStart = this.reader.readInt(4), this.centralDirRecordsOnThisDisk = this.reader.readInt(8), this.centralDirRecords = this.reader.readInt(8), this.centralDirSize = this.reader.readInt(8), this.centralDirOffset = this.reader.readInt(8), this.zip64ExtensibleData = {};
                for (var e, t, r, n = this.zip64EndOfCentralSize - 44; 0 < n;) e = this.reader.readInt(2), t = this.reader.readInt(4), r = this.reader.readData(t), this.zip64ExtensibleData[e] = {
                  id: e,
                  length: t,
                  value: r
                };
              },
              readBlockZip64EndOfCentralLocator: function () {
                if (this.diskWithZip64CentralDirStart = this.reader.readInt(4), this.relativeOffsetEndOfZip64CentralDir = this.reader.readInt(8), this.disksCount = this.reader.readInt(4), 1 < this.disksCount) throw new Error("Multi-volumes zip are not supported");
              },
              readLocalFiles: function () {
                var e, t;
                for (e = 0; e < this.files.length; e++) t = this.files[e], this.reader.setIndex(t.localHeaderOffset), this.checkSignature(s.LOCAL_FILE_HEADER), t.readLocalPart(this.reader), t.handleUTF8(), t.processAttributes();
              },
              readCentralDir: function () {
                var e;
                for (this.reader.setIndex(this.centralDirOffset); this.reader.readAndCheckSignature(s.CENTRAL_FILE_HEADER);) (e = new a({
                  zip64: this.zip64
                }, this.loadOptions)).readCentralPart(this.reader), this.files.push(e);
                if (this.centralDirRecords !== this.files.length && 0 !== this.centralDirRecords && 0 === this.files.length) throw new Error("Corrupted zip or bug: expected " + this.centralDirRecords + " records in central dir, got " + this.files.length);
              },
              readEndOfCentral: function () {
                var e = this.reader.lastIndexOfSignature(s.CENTRAL_DIRECTORY_END);
                if (e < 0) throw !this.isSignature(0, s.LOCAL_FILE_HEADER) ? new Error("Can't find end of central directory : is this a zip file ? If it is, see https://stuk.github.io/jszip/documentation/howto/read_zip.html") : new Error("Corrupted zip: can't find end of central directory");
                this.reader.setIndex(e);
                var t = e;
                if (this.checkSignature(s.CENTRAL_DIRECTORY_END), this.readBlockEndOfCentral(), this.diskNumber === i.MAX_VALUE_16BITS || this.diskWithCentralDirStart === i.MAX_VALUE_16BITS || this.centralDirRecordsOnThisDisk === i.MAX_VALUE_16BITS || this.centralDirRecords === i.MAX_VALUE_16BITS || this.centralDirSize === i.MAX_VALUE_32BITS || this.centralDirOffset === i.MAX_VALUE_32BITS) {
                  if (this.zip64 = !0, (e = this.reader.lastIndexOfSignature(s.ZIP64_CENTRAL_DIRECTORY_LOCATOR)) < 0) throw new Error("Corrupted zip: can't find the ZIP64 end of central directory locator");
                  if (this.reader.setIndex(e), this.checkSignature(s.ZIP64_CENTRAL_DIRECTORY_LOCATOR), this.readBlockZip64EndOfCentralLocator(), !this.isSignature(this.relativeOffsetEndOfZip64CentralDir, s.ZIP64_CENTRAL_DIRECTORY_END) && (this.relativeOffsetEndOfZip64CentralDir = this.reader.lastIndexOfSignature(s.ZIP64_CENTRAL_DIRECTORY_END), this.relativeOffsetEndOfZip64CentralDir < 0)) throw new Error("Corrupted zip: can't find the ZIP64 end of central directory");
                  this.reader.setIndex(this.relativeOffsetEndOfZip64CentralDir), this.checkSignature(s.ZIP64_CENTRAL_DIRECTORY_END), this.readBlockZip64EndOfCentral();
                }
                var r = this.centralDirOffset + this.centralDirSize;
                this.zip64 && (r += 20, r += 12 + this.zip64EndOfCentralSize);
                var n = t - r;
                if (0 < n) this.isSignature(t, s.CENTRAL_FILE_HEADER) || (this.reader.zero = n);else if (n < 0) throw new Error("Corrupted zip: missing " + Math.abs(n) + " bytes.");
              },
              prepareReader: function (e) {
                this.reader = n(e);
              },
              load: function (e) {
                this.prepareReader(e), this.readEndOfCentral(), this.readCentralDir(), this.readLocalFiles();
              }
            }, t.exports = h;
          }, {
            "./reader/readerFor": 22,
            "./signature": 23,
            "./support": 30,
            "./utils": 32,
            "./zipEntry": 34
          }],
          34: [function (e, t, r) {

            var n = e("./reader/readerFor"),
              s = e("./utils"),
              i = e("./compressedObject"),
              a = e("./crc32"),
              o = e("./utf8"),
              h = e("./compressions"),
              u = e("./support");
            function l(e, t) {
              this.options = e, this.loadOptions = t;
            }
            l.prototype = {
              isEncrypted: function () {
                return 1 == (1 & this.bitFlag);
              },
              useUTF8: function () {
                return 2048 == (2048 & this.bitFlag);
              },
              readLocalPart: function (e) {
                var t, r;
                if (e.skip(22), this.fileNameLength = e.readInt(2), r = e.readInt(2), this.fileName = e.readData(this.fileNameLength), e.skip(r), -1 === this.compressedSize || -1 === this.uncompressedSize) throw new Error("Bug or corrupted zip : didn't get enough information from the central directory (compressedSize === -1 || uncompressedSize === -1)");
                if (null === (t = function (e) {
                  for (var t in h) if (Object.prototype.hasOwnProperty.call(h, t) && h[t].magic === e) return h[t];
                  return null;
                }(this.compressionMethod))) throw new Error("Corrupted zip : compression " + s.pretty(this.compressionMethod) + " unknown (inner file : " + s.transformTo("string", this.fileName) + ")");
                this.decompressed = new i(this.compressedSize, this.uncompressedSize, this.crc32, t, e.readData(this.compressedSize));
              },
              readCentralPart: function (e) {
                this.versionMadeBy = e.readInt(2), e.skip(2), this.bitFlag = e.readInt(2), this.compressionMethod = e.readString(2), this.date = e.readDate(), this.crc32 = e.readInt(4), this.compressedSize = e.readInt(4), this.uncompressedSize = e.readInt(4);
                var t = e.readInt(2);
                if (this.extraFieldsLength = e.readInt(2), this.fileCommentLength = e.readInt(2), this.diskNumberStart = e.readInt(2), this.internalFileAttributes = e.readInt(2), this.externalFileAttributes = e.readInt(4), this.localHeaderOffset = e.readInt(4), this.isEncrypted()) throw new Error("Encrypted zip are not supported");
                e.skip(t), this.readExtraFields(e), this.parseZIP64ExtraField(e), this.fileComment = e.readData(this.fileCommentLength);
              },
              processAttributes: function () {
                this.unixPermissions = null, this.dosPermissions = null;
                var e = this.versionMadeBy >> 8;
                this.dir = !!(16 & this.externalFileAttributes), 0 == e && (this.dosPermissions = 63 & this.externalFileAttributes), 3 == e && (this.unixPermissions = this.externalFileAttributes >> 16 & 65535), this.dir || "/" !== this.fileNameStr.slice(-1) || (this.dir = !0);
              },
              parseZIP64ExtraField: function () {
                if (this.extraFields[1]) {
                  var e = n(this.extraFields[1].value);
                  this.uncompressedSize === s.MAX_VALUE_32BITS && (this.uncompressedSize = e.readInt(8)), this.compressedSize === s.MAX_VALUE_32BITS && (this.compressedSize = e.readInt(8)), this.localHeaderOffset === s.MAX_VALUE_32BITS && (this.localHeaderOffset = e.readInt(8)), this.diskNumberStart === s.MAX_VALUE_32BITS && (this.diskNumberStart = e.readInt(4));
                }
              },
              readExtraFields: function (e) {
                var t,
                  r,
                  n,
                  i = e.index + this.extraFieldsLength;
                for (this.extraFields || (this.extraFields = {}); e.index + 4 < i;) t = e.readInt(2), r = e.readInt(2), n = e.readData(r), this.extraFields[t] = {
                  id: t,
                  length: r,
                  value: n
                };
                e.setIndex(i);
              },
              handleUTF8: function () {
                var e = u.uint8array ? "uint8array" : "array";
                if (this.useUTF8()) this.fileNameStr = o.utf8decode(this.fileName), this.fileCommentStr = o.utf8decode(this.fileComment);else {
                  var t = this.findExtraFieldUnicodePath();
                  if (null !== t) this.fileNameStr = t;else {
                    var r = s.transformTo(e, this.fileName);
                    this.fileNameStr = this.loadOptions.decodeFileName(r);
                  }
                  var n = this.findExtraFieldUnicodeComment();
                  if (null !== n) this.fileCommentStr = n;else {
                    var i = s.transformTo(e, this.fileComment);
                    this.fileCommentStr = this.loadOptions.decodeFileName(i);
                  }
                }
              },
              findExtraFieldUnicodePath: function () {
                var e = this.extraFields[28789];
                if (e) {
                  var t = n(e.value);
                  return 1 !== t.readInt(1) ? null : a(this.fileName) !== t.readInt(4) ? null : o.utf8decode(t.readData(e.length - 5));
                }
                return null;
              },
              findExtraFieldUnicodeComment: function () {
                var e = this.extraFields[25461];
                if (e) {
                  var t = n(e.value);
                  return 1 !== t.readInt(1) ? null : a(this.fileComment) !== t.readInt(4) ? null : o.utf8decode(t.readData(e.length - 5));
                }
                return null;
              }
            }, t.exports = l;
          }, {
            "./compressedObject": 2,
            "./compressions": 3,
            "./crc32": 4,
            "./reader/readerFor": 22,
            "./support": 30,
            "./utf8": 31,
            "./utils": 32
          }],
          35: [function (e, t, r) {

            function n(e, t, r) {
              this.name = e, this.dir = r.dir, this.date = r.date, this.comment = r.comment, this.unixPermissions = r.unixPermissions, this.dosPermissions = r.dosPermissions, this._data = t, this._dataBinary = r.binary, this.options = {
                compression: r.compression,
                compressionOptions: r.compressionOptions
              };
            }
            var s = e("./stream/StreamHelper"),
              i = e("./stream/DataWorker"),
              a = e("./utf8"),
              o = e("./compressedObject"),
              h = e("./stream/GenericWorker");
            n.prototype = {
              internalStream: function (e) {
                var t = null,
                  r = "string";
                try {
                  if (!e) throw new Error("No output type specified.");
                  var n = "string" === (r = e.toLowerCase()) || "text" === r;
                  "binarystring" !== r && "text" !== r || (r = "string"), t = this._decompressWorker();
                  var i = !this._dataBinary;
                  i && !n && (t = t.pipe(new a.Utf8EncodeWorker())), !i && n && (t = t.pipe(new a.Utf8DecodeWorker()));
                } catch (e) {
                  (t = new h("error")).error(e);
                }
                return new s(t, r, "");
              },
              async: function (e, t) {
                return this.internalStream(e).accumulate(t);
              },
              nodeStream: function (e, t) {
                return this.internalStream(e || "nodebuffer").toNodejsStream(t);
              },
              _compressWorker: function (e, t) {
                if (this._data instanceof o && this._data.compression.magic === e.magic) return this._data.getCompressedWorker();
                var r = this._decompressWorker();
                return this._dataBinary || (r = r.pipe(new a.Utf8EncodeWorker())), o.createWorkerFrom(r, e, t);
              },
              _decompressWorker: function () {
                return this._data instanceof o ? this._data.getContentWorker() : this._data instanceof h ? this._data : new i(this._data);
              }
            };
            for (var u = ["asText", "asBinary", "asNodeBuffer", "asUint8Array", "asArrayBuffer"], l = function () {
                throw new Error("This method has been removed in JSZip 3.0, please check the upgrade guide.");
              }, f = 0; f < u.length; f++) n.prototype[u[f]] = l;
            t.exports = n;
          }, {
            "./compressedObject": 2,
            "./stream/DataWorker": 27,
            "./stream/GenericWorker": 28,
            "./stream/StreamHelper": 29,
            "./utf8": 31
          }],
          36: [function (e, l, t) {
            (function (t) {

              var r,
                n,
                e = t.MutationObserver || t.WebKitMutationObserver;
              if (e) {
                var i = 0,
                  s = new e(u),
                  a = t.document.createTextNode("");
                s.observe(a, {
                  characterData: !0
                }), r = function () {
                  a.data = i = ++i % 2;
                };
              } else if (t.setImmediate || void 0 === t.MessageChannel) r = "document" in t && "onreadystatechange" in t.document.createElement("script") ? function () {
                var e = t.document.createElement("script");
                e.onreadystatechange = function () {
                  u(), e.onreadystatechange = null, e.parentNode.removeChild(e), e = null;
                }, t.document.documentElement.appendChild(e);
              } : function () {
                setTimeout(u, 0);
              };else {
                var o = new t.MessageChannel();
                o.port1.onmessage = u, r = function () {
                  o.port2.postMessage(0);
                };
              }
              var h = [];
              function u() {
                var e, t;
                n = !0;
                for (var r = h.length; r;) {
                  for (t = h, h = [], e = -1; ++e < r;) t[e]();
                  r = h.length;
                }
                n = !1;
              }
              l.exports = function (e) {
                1 !== h.push(e) || n || r();
              };
            }).call(this, "undefined" != typeof commonjsGlobal ? commonjsGlobal : "undefined" != typeof self ? self : "undefined" != typeof window ? window : {});
          }, {}],
          37: [function (e, t, r) {

            var i = e("immediate");
            function u() {}
            var l = {},
              s = ["REJECTED"],
              a = ["FULFILLED"],
              n = ["PENDING"];
            function o(e) {
              if ("function" != typeof e) throw new TypeError("resolver must be a function");
              this.state = n, this.queue = [], this.outcome = void 0, e !== u && d(this, e);
            }
            function h(e, t, r) {
              this.promise = e, "function" == typeof t && (this.onFulfilled = t, this.callFulfilled = this.otherCallFulfilled), "function" == typeof r && (this.onRejected = r, this.callRejected = this.otherCallRejected);
            }
            function f(t, r, n) {
              i(function () {
                var e;
                try {
                  e = r(n);
                } catch (e) {
                  return l.reject(t, e);
                }
                e === t ? l.reject(t, new TypeError("Cannot resolve promise with itself")) : l.resolve(t, e);
              });
            }
            function c(e) {
              var t = e && e.then;
              if (e && ("object" == typeof e || "function" == typeof e) && "function" == typeof t) return function () {
                t.apply(e, arguments);
              };
            }
            function d(t, e) {
              var r = !1;
              function n(e) {
                r || (r = !0, l.reject(t, e));
              }
              function i(e) {
                r || (r = !0, l.resolve(t, e));
              }
              var s = p(function () {
                e(i, n);
              });
              "error" === s.status && n(s.value);
            }
            function p(e, t) {
              var r = {};
              try {
                r.value = e(t), r.status = "success";
              } catch (e) {
                r.status = "error", r.value = e;
              }
              return r;
            }
            (t.exports = o).prototype.finally = function (t) {
              if ("function" != typeof t) return this;
              var r = this.constructor;
              return this.then(function (e) {
                return r.resolve(t()).then(function () {
                  return e;
                });
              }, function (e) {
                return r.resolve(t()).then(function () {
                  throw e;
                });
              });
            }, o.prototype.catch = function (e) {
              return this.then(null, e);
            }, o.prototype.then = function (e, t) {
              if ("function" != typeof e && this.state === a || "function" != typeof t && this.state === s) return this;
              var r = new this.constructor(u);
              this.state !== n ? f(r, this.state === a ? e : t, this.outcome) : this.queue.push(new h(r, e, t));
              return r;
            }, h.prototype.callFulfilled = function (e) {
              l.resolve(this.promise, e);
            }, h.prototype.otherCallFulfilled = function (e) {
              f(this.promise, this.onFulfilled, e);
            }, h.prototype.callRejected = function (e) {
              l.reject(this.promise, e);
            }, h.prototype.otherCallRejected = function (e) {
              f(this.promise, this.onRejected, e);
            }, l.resolve = function (e, t) {
              var r = p(c, t);
              if ("error" === r.status) return l.reject(e, r.value);
              var n = r.value;
              if (n) d(e, n);else {
                e.state = a, e.outcome = t;
                for (var i = -1, s = e.queue.length; ++i < s;) e.queue[i].callFulfilled(t);
              }
              return e;
            }, l.reject = function (e, t) {
              e.state = s, e.outcome = t;
              for (var r = -1, n = e.queue.length; ++r < n;) e.queue[r].callRejected(t);
              return e;
            }, o.resolve = function (e) {
              if (e instanceof this) return e;
              return l.resolve(new this(u), e);
            }, o.reject = function (e) {
              var t = new this(u);
              return l.reject(t, e);
            }, o.all = function (e) {
              var r = this;
              if ("[object Array]" !== Object.prototype.toString.call(e)) return this.reject(new TypeError("must be an array"));
              var n = e.length,
                i = !1;
              if (!n) return this.resolve([]);
              var s = new Array(n),
                a = 0,
                t = -1,
                o = new this(u);
              for (; ++t < n;) h(e[t], t);
              return o;
              function h(e, t) {
                r.resolve(e).then(function (e) {
                  s[t] = e, ++a !== n || i || (i = !0, l.resolve(o, s));
                }, function (e) {
                  i || (i = !0, l.reject(o, e));
                });
              }
            }, o.race = function (e) {
              var t = this;
              if ("[object Array]" !== Object.prototype.toString.call(e)) return this.reject(new TypeError("must be an array"));
              var r = e.length,
                n = !1;
              if (!r) return this.resolve([]);
              var i = -1,
                s = new this(u);
              for (; ++i < r;) a = e[i], t.resolve(a).then(function (e) {
                n || (n = !0, l.resolve(s, e));
              }, function (e) {
                n || (n = !0, l.reject(s, e));
              });
              var a;
              return s;
            };
          }, {
            immediate: 36
          }],
          38: [function (e, t, r) {

            var n = {};
            (0, e("./lib/utils/common").assign)(n, e("./lib/deflate"), e("./lib/inflate"), e("./lib/zlib/constants")), t.exports = n;
          }, {
            "./lib/deflate": 39,
            "./lib/inflate": 40,
            "./lib/utils/common": 41,
            "./lib/zlib/constants": 44
          }],
          39: [function (e, t, r) {

            var a = e("./zlib/deflate"),
              o = e("./utils/common"),
              h = e("./utils/strings"),
              i = e("./zlib/messages"),
              s = e("./zlib/zstream"),
              u = Object.prototype.toString,
              l = 0,
              f = -1,
              c = 0,
              d = 8;
            function p(e) {
              if (!(this instanceof p)) return new p(e);
              this.options = o.assign({
                level: f,
                method: d,
                chunkSize: 16384,
                windowBits: 15,
                memLevel: 8,
                strategy: c,
                to: ""
              }, e || {});
              var t = this.options;
              t.raw && 0 < t.windowBits ? t.windowBits = -t.windowBits : t.gzip && 0 < t.windowBits && t.windowBits < 16 && (t.windowBits += 16), this.err = 0, this.msg = "", this.ended = !1, this.chunks = [], this.strm = new s(), this.strm.avail_out = 0;
              var r = a.deflateInit2(this.strm, t.level, t.method, t.windowBits, t.memLevel, t.strategy);
              if (r !== l) throw new Error(i[r]);
              if (t.header && a.deflateSetHeader(this.strm, t.header), t.dictionary) {
                var n;
                if (n = "string" == typeof t.dictionary ? h.string2buf(t.dictionary) : "[object ArrayBuffer]" === u.call(t.dictionary) ? new Uint8Array(t.dictionary) : t.dictionary, (r = a.deflateSetDictionary(this.strm, n)) !== l) throw new Error(i[r]);
                this._dict_set = !0;
              }
            }
            function n(e, t) {
              var r = new p(t);
              if (r.push(e, !0), r.err) throw r.msg || i[r.err];
              return r.result;
            }
            p.prototype.push = function (e, t) {
              var r,
                n,
                i = this.strm,
                s = this.options.chunkSize;
              if (this.ended) return !1;
              n = t === ~~t ? t : !0 === t ? 4 : 0, "string" == typeof e ? i.input = h.string2buf(e) : "[object ArrayBuffer]" === u.call(e) ? i.input = new Uint8Array(e) : i.input = e, i.next_in = 0, i.avail_in = i.input.length;
              do {
                if (0 === i.avail_out && (i.output = new o.Buf8(s), i.next_out = 0, i.avail_out = s), 1 !== (r = a.deflate(i, n)) && r !== l) return this.onEnd(r), !(this.ended = !0);
                0 !== i.avail_out && (0 !== i.avail_in || 4 !== n && 2 !== n) || ("string" === this.options.to ? this.onData(h.buf2binstring(o.shrinkBuf(i.output, i.next_out))) : this.onData(o.shrinkBuf(i.output, i.next_out)));
              } while ((0 < i.avail_in || 0 === i.avail_out) && 1 !== r);
              return 4 === n ? (r = a.deflateEnd(this.strm), this.onEnd(r), this.ended = !0, r === l) : 2 !== n || (this.onEnd(l), !(i.avail_out = 0));
            }, p.prototype.onData = function (e) {
              this.chunks.push(e);
            }, p.prototype.onEnd = function (e) {
              e === l && ("string" === this.options.to ? this.result = this.chunks.join("") : this.result = o.flattenChunks(this.chunks)), this.chunks = [], this.err = e, this.msg = this.strm.msg;
            }, r.Deflate = p, r.deflate = n, r.deflateRaw = function (e, t) {
              return (t = t || {}).raw = !0, n(e, t);
            }, r.gzip = function (e, t) {
              return (t = t || {}).gzip = !0, n(e, t);
            };
          }, {
            "./utils/common": 41,
            "./utils/strings": 42,
            "./zlib/deflate": 46,
            "./zlib/messages": 51,
            "./zlib/zstream": 53
          }],
          40: [function (e, t, r) {

            var c = e("./zlib/inflate"),
              d = e("./utils/common"),
              p = e("./utils/strings"),
              m = e("./zlib/constants"),
              n = e("./zlib/messages"),
              i = e("./zlib/zstream"),
              s = e("./zlib/gzheader"),
              _ = Object.prototype.toString;
            function a(e) {
              if (!(this instanceof a)) return new a(e);
              this.options = d.assign({
                chunkSize: 16384,
                windowBits: 0,
                to: ""
              }, e || {});
              var t = this.options;
              t.raw && 0 <= t.windowBits && t.windowBits < 16 && (t.windowBits = -t.windowBits, 0 === t.windowBits && (t.windowBits = -15)), !(0 <= t.windowBits && t.windowBits < 16) || e && e.windowBits || (t.windowBits += 32), 15 < t.windowBits && t.windowBits < 48 && 0 == (15 & t.windowBits) && (t.windowBits |= 15), this.err = 0, this.msg = "", this.ended = !1, this.chunks = [], this.strm = new i(), this.strm.avail_out = 0;
              var r = c.inflateInit2(this.strm, t.windowBits);
              if (r !== m.Z_OK) throw new Error(n[r]);
              this.header = new s(), c.inflateGetHeader(this.strm, this.header);
            }
            function o(e, t) {
              var r = new a(t);
              if (r.push(e, !0), r.err) throw r.msg || n[r.err];
              return r.result;
            }
            a.prototype.push = function (e, t) {
              var r,
                n,
                i,
                s,
                a,
                o,
                h = this.strm,
                u = this.options.chunkSize,
                l = this.options.dictionary,
                f = !1;
              if (this.ended) return !1;
              n = t === ~~t ? t : !0 === t ? m.Z_FINISH : m.Z_NO_FLUSH, "string" == typeof e ? h.input = p.binstring2buf(e) : "[object ArrayBuffer]" === _.call(e) ? h.input = new Uint8Array(e) : h.input = e, h.next_in = 0, h.avail_in = h.input.length;
              do {
                if (0 === h.avail_out && (h.output = new d.Buf8(u), h.next_out = 0, h.avail_out = u), (r = c.inflate(h, m.Z_NO_FLUSH)) === m.Z_NEED_DICT && l && (o = "string" == typeof l ? p.string2buf(l) : "[object ArrayBuffer]" === _.call(l) ? new Uint8Array(l) : l, r = c.inflateSetDictionary(this.strm, o)), r === m.Z_BUF_ERROR && !0 === f && (r = m.Z_OK, f = !1), r !== m.Z_STREAM_END && r !== m.Z_OK) return this.onEnd(r), !(this.ended = !0);
                h.next_out && (0 !== h.avail_out && r !== m.Z_STREAM_END && (0 !== h.avail_in || n !== m.Z_FINISH && n !== m.Z_SYNC_FLUSH) || ("string" === this.options.to ? (i = p.utf8border(h.output, h.next_out), s = h.next_out - i, a = p.buf2string(h.output, i), h.next_out = s, h.avail_out = u - s, s && d.arraySet(h.output, h.output, i, s, 0), this.onData(a)) : this.onData(d.shrinkBuf(h.output, h.next_out)))), 0 === h.avail_in && 0 === h.avail_out && (f = !0);
              } while ((0 < h.avail_in || 0 === h.avail_out) && r !== m.Z_STREAM_END);
              return r === m.Z_STREAM_END && (n = m.Z_FINISH), n === m.Z_FINISH ? (r = c.inflateEnd(this.strm), this.onEnd(r), this.ended = !0, r === m.Z_OK) : n !== m.Z_SYNC_FLUSH || (this.onEnd(m.Z_OK), !(h.avail_out = 0));
            }, a.prototype.onData = function (e) {
              this.chunks.push(e);
            }, a.prototype.onEnd = function (e) {
              e === m.Z_OK && ("string" === this.options.to ? this.result = this.chunks.join("") : this.result = d.flattenChunks(this.chunks)), this.chunks = [], this.err = e, this.msg = this.strm.msg;
            }, r.Inflate = a, r.inflate = o, r.inflateRaw = function (e, t) {
              return (t = t || {}).raw = !0, o(e, t);
            }, r.ungzip = o;
          }, {
            "./utils/common": 41,
            "./utils/strings": 42,
            "./zlib/constants": 44,
            "./zlib/gzheader": 47,
            "./zlib/inflate": 49,
            "./zlib/messages": 51,
            "./zlib/zstream": 53
          }],
          41: [function (e, t, r) {

            var n = "undefined" != typeof Uint8Array && "undefined" != typeof Uint16Array && "undefined" != typeof Int32Array;
            r.assign = function (e) {
              for (var t = Array.prototype.slice.call(arguments, 1); t.length;) {
                var r = t.shift();
                if (r) {
                  if ("object" != typeof r) throw new TypeError(r + "must be non-object");
                  for (var n in r) r.hasOwnProperty(n) && (e[n] = r[n]);
                }
              }
              return e;
            }, r.shrinkBuf = function (e, t) {
              return e.length === t ? e : e.subarray ? e.subarray(0, t) : (e.length = t, e);
            };
            var i = {
                arraySet: function (e, t, r, n, i) {
                  if (t.subarray && e.subarray) e.set(t.subarray(r, r + n), i);else for (var s = 0; s < n; s++) e[i + s] = t[r + s];
                },
                flattenChunks: function (e) {
                  var t, r, n, i, s, a;
                  for (t = n = 0, r = e.length; t < r; t++) n += e[t].length;
                  for (a = new Uint8Array(n), t = i = 0, r = e.length; t < r; t++) s = e[t], a.set(s, i), i += s.length;
                  return a;
                }
              },
              s = {
                arraySet: function (e, t, r, n, i) {
                  for (var s = 0; s < n; s++) e[i + s] = t[r + s];
                },
                flattenChunks: function (e) {
                  return [].concat.apply([], e);
                }
              };
            r.setTyped = function (e) {
              e ? (r.Buf8 = Uint8Array, r.Buf16 = Uint16Array, r.Buf32 = Int32Array, r.assign(r, i)) : (r.Buf8 = Array, r.Buf16 = Array, r.Buf32 = Array, r.assign(r, s));
            }, r.setTyped(n);
          }, {}],
          42: [function (e, t, r) {

            var h = e("./common"),
              i = !0,
              s = !0;
            try {
              String.fromCharCode.apply(null, [0]);
            } catch (e) {
              i = !1;
            }
            try {
              String.fromCharCode.apply(null, new Uint8Array(1));
            } catch (e) {
              s = !1;
            }
            for (var u = new h.Buf8(256), n = 0; n < 256; n++) u[n] = 252 <= n ? 6 : 248 <= n ? 5 : 240 <= n ? 4 : 224 <= n ? 3 : 192 <= n ? 2 : 1;
            function l(e, t) {
              if (t < 65537 && (e.subarray && s || !e.subarray && i)) return String.fromCharCode.apply(null, h.shrinkBuf(e, t));
              for (var r = "", n = 0; n < t; n++) r += String.fromCharCode(e[n]);
              return r;
            }
            u[254] = u[254] = 1, r.string2buf = function (e) {
              var t,
                r,
                n,
                i,
                s,
                a = e.length,
                o = 0;
              for (i = 0; i < a; i++) 55296 == (64512 & (r = e.charCodeAt(i))) && i + 1 < a && 56320 == (64512 & (n = e.charCodeAt(i + 1))) && (r = 65536 + (r - 55296 << 10) + (n - 56320), i++), o += r < 128 ? 1 : r < 2048 ? 2 : r < 65536 ? 3 : 4;
              for (t = new h.Buf8(o), i = s = 0; s < o; i++) 55296 == (64512 & (r = e.charCodeAt(i))) && i + 1 < a && 56320 == (64512 & (n = e.charCodeAt(i + 1))) && (r = 65536 + (r - 55296 << 10) + (n - 56320), i++), r < 128 ? t[s++] = r : (r < 2048 ? t[s++] = 192 | r >>> 6 : (r < 65536 ? t[s++] = 224 | r >>> 12 : (t[s++] = 240 | r >>> 18, t[s++] = 128 | r >>> 12 & 63), t[s++] = 128 | r >>> 6 & 63), t[s++] = 128 | 63 & r);
              return t;
            }, r.buf2binstring = function (e) {
              return l(e, e.length);
            }, r.binstring2buf = function (e) {
              for (var t = new h.Buf8(e.length), r = 0, n = t.length; r < n; r++) t[r] = e.charCodeAt(r);
              return t;
            }, r.buf2string = function (e, t) {
              var r,
                n,
                i,
                s,
                a = t || e.length,
                o = new Array(2 * a);
              for (r = n = 0; r < a;) if ((i = e[r++]) < 128) o[n++] = i;else if (4 < (s = u[i])) o[n++] = 65533, r += s - 1;else {
                for (i &= 2 === s ? 31 : 3 === s ? 15 : 7; 1 < s && r < a;) i = i << 6 | 63 & e[r++], s--;
                1 < s ? o[n++] = 65533 : i < 65536 ? o[n++] = i : (i -= 65536, o[n++] = 55296 | i >> 10 & 1023, o[n++] = 56320 | 1023 & i);
              }
              return l(o, n);
            }, r.utf8border = function (e, t) {
              var r;
              for ((t = t || e.length) > e.length && (t = e.length), r = t - 1; 0 <= r && 128 == (192 & e[r]);) r--;
              return r < 0 ? t : 0 === r ? t : r + u[e[r]] > t ? r : t;
            };
          }, {
            "./common": 41
          }],
          43: [function (e, t, r) {

            t.exports = function (e, t, r, n) {
              for (var i = 65535 & e | 0, s = e >>> 16 & 65535 | 0, a = 0; 0 !== r;) {
                for (r -= a = 2e3 < r ? 2e3 : r; s = s + (i = i + t[n++] | 0) | 0, --a;);
                i %= 65521, s %= 65521;
              }
              return i | s << 16 | 0;
            };
          }, {}],
          44: [function (e, t, r) {

            t.exports = {
              Z_NO_FLUSH: 0,
              Z_PARTIAL_FLUSH: 1,
              Z_SYNC_FLUSH: 2,
              Z_FULL_FLUSH: 3,
              Z_FINISH: 4,
              Z_BLOCK: 5,
              Z_TREES: 6,
              Z_OK: 0,
              Z_STREAM_END: 1,
              Z_NEED_DICT: 2,
              Z_ERRNO: -1,
              Z_STREAM_ERROR: -2,
              Z_DATA_ERROR: -3,
              Z_BUF_ERROR: -5,
              Z_NO_COMPRESSION: 0,
              Z_BEST_SPEED: 1,
              Z_BEST_COMPRESSION: 9,
              Z_DEFAULT_COMPRESSION: -1,
              Z_FILTERED: 1,
              Z_HUFFMAN_ONLY: 2,
              Z_RLE: 3,
              Z_FIXED: 4,
              Z_DEFAULT_STRATEGY: 0,
              Z_BINARY: 0,
              Z_TEXT: 1,
              Z_UNKNOWN: 2,
              Z_DEFLATED: 8
            };
          }, {}],
          45: [function (e, t, r) {

            var o = function () {
              for (var e, t = [], r = 0; r < 256; r++) {
                e = r;
                for (var n = 0; n < 8; n++) e = 1 & e ? 3988292384 ^ e >>> 1 : e >>> 1;
                t[r] = e;
              }
              return t;
            }();
            t.exports = function (e, t, r, n) {
              var i = o,
                s = n + r;
              e ^= -1;
              for (var a = n; a < s; a++) e = e >>> 8 ^ i[255 & (e ^ t[a])];
              return -1 ^ e;
            };
          }, {}],
          46: [function (e, t, r) {

            var h,
              c = e("../utils/common"),
              u = e("./trees"),
              d = e("./adler32"),
              p = e("./crc32"),
              n = e("./messages"),
              l = 0,
              f = 4,
              m = 0,
              _ = -2,
              g = -1,
              b = 4,
              i = 2,
              v = 8,
              y = 9,
              s = 286,
              a = 30,
              o = 19,
              w = 2 * s + 1,
              k = 15,
              x = 3,
              S = 258,
              z = S + x + 1,
              C = 42,
              E = 113,
              A = 1,
              I = 2,
              O = 3,
              B = 4;
            function R(e, t) {
              return e.msg = n[t], t;
            }
            function T(e) {
              return (e << 1) - (4 < e ? 9 : 0);
            }
            function D(e) {
              for (var t = e.length; 0 <= --t;) e[t] = 0;
            }
            function F(e) {
              var t = e.state,
                r = t.pending;
              r > e.avail_out && (r = e.avail_out), 0 !== r && (c.arraySet(e.output, t.pending_buf, t.pending_out, r, e.next_out), e.next_out += r, t.pending_out += r, e.total_out += r, e.avail_out -= r, t.pending -= r, 0 === t.pending && (t.pending_out = 0));
            }
            function N(e, t) {
              u._tr_flush_block(e, 0 <= e.block_start ? e.block_start : -1, e.strstart - e.block_start, t), e.block_start = e.strstart, F(e.strm);
            }
            function U(e, t) {
              e.pending_buf[e.pending++] = t;
            }
            function P(e, t) {
              e.pending_buf[e.pending++] = t >>> 8 & 255, e.pending_buf[e.pending++] = 255 & t;
            }
            function L(e, t) {
              var r,
                n,
                i = e.max_chain_length,
                s = e.strstart,
                a = e.prev_length,
                o = e.nice_match,
                h = e.strstart > e.w_size - z ? e.strstart - (e.w_size - z) : 0,
                u = e.window,
                l = e.w_mask,
                f = e.prev,
                c = e.strstart + S,
                d = u[s + a - 1],
                p = u[s + a];
              e.prev_length >= e.good_match && (i >>= 2), o > e.lookahead && (o = e.lookahead);
              do {
                if (u[(r = t) + a] === p && u[r + a - 1] === d && u[r] === u[s] && u[++r] === u[s + 1]) {
                  s += 2, r++;
                  do {} while (u[++s] === u[++r] && u[++s] === u[++r] && u[++s] === u[++r] && u[++s] === u[++r] && u[++s] === u[++r] && u[++s] === u[++r] && u[++s] === u[++r] && u[++s] === u[++r] && s < c);
                  if (n = S - (c - s), s = c - S, a < n) {
                    if (e.match_start = t, o <= (a = n)) break;
                    d = u[s + a - 1], p = u[s + a];
                  }
                }
              } while ((t = f[t & l]) > h && 0 != --i);
              return a <= e.lookahead ? a : e.lookahead;
            }
            function j(e) {
              var t,
                r,
                n,
                i,
                s,
                a,
                o,
                h,
                u,
                l,
                f = e.w_size;
              do {
                if (i = e.window_size - e.lookahead - e.strstart, e.strstart >= f + (f - z)) {
                  for (c.arraySet(e.window, e.window, f, f, 0), e.match_start -= f, e.strstart -= f, e.block_start -= f, t = r = e.hash_size; n = e.head[--t], e.head[t] = f <= n ? n - f : 0, --r;);
                  for (t = r = f; n = e.prev[--t], e.prev[t] = f <= n ? n - f : 0, --r;);
                  i += f;
                }
                if (0 === e.strm.avail_in) break;
                if (a = e.strm, o = e.window, h = e.strstart + e.lookahead, u = i, l = void 0, l = a.avail_in, u < l && (l = u), r = 0 === l ? 0 : (a.avail_in -= l, c.arraySet(o, a.input, a.next_in, l, h), 1 === a.state.wrap ? a.adler = d(a.adler, o, l, h) : 2 === a.state.wrap && (a.adler = p(a.adler, o, l, h)), a.next_in += l, a.total_in += l, l), e.lookahead += r, e.lookahead + e.insert >= x) for (s = e.strstart - e.insert, e.ins_h = e.window[s], e.ins_h = (e.ins_h << e.hash_shift ^ e.window[s + 1]) & e.hash_mask; e.insert && (e.ins_h = (e.ins_h << e.hash_shift ^ e.window[s + x - 1]) & e.hash_mask, e.prev[s & e.w_mask] = e.head[e.ins_h], e.head[e.ins_h] = s, s++, e.insert--, !(e.lookahead + e.insert < x)););
              } while (e.lookahead < z && 0 !== e.strm.avail_in);
            }
            function Z(e, t) {
              for (var r, n;;) {
                if (e.lookahead < z) {
                  if (j(e), e.lookahead < z && t === l) return A;
                  if (0 === e.lookahead) break;
                }
                if (r = 0, e.lookahead >= x && (e.ins_h = (e.ins_h << e.hash_shift ^ e.window[e.strstart + x - 1]) & e.hash_mask, r = e.prev[e.strstart & e.w_mask] = e.head[e.ins_h], e.head[e.ins_h] = e.strstart), 0 !== r && e.strstart - r <= e.w_size - z && (e.match_length = L(e, r)), e.match_length >= x) {
                  if (n = u._tr_tally(e, e.strstart - e.match_start, e.match_length - x), e.lookahead -= e.match_length, e.match_length <= e.max_lazy_match && e.lookahead >= x) {
                    for (e.match_length--; e.strstart++, e.ins_h = (e.ins_h << e.hash_shift ^ e.window[e.strstart + x - 1]) & e.hash_mask, r = e.prev[e.strstart & e.w_mask] = e.head[e.ins_h], e.head[e.ins_h] = e.strstart, 0 != --e.match_length;);
                    e.strstart++;
                  } else e.strstart += e.match_length, e.match_length = 0, e.ins_h = e.window[e.strstart], e.ins_h = (e.ins_h << e.hash_shift ^ e.window[e.strstart + 1]) & e.hash_mask;
                } else n = u._tr_tally(e, 0, e.window[e.strstart]), e.lookahead--, e.strstart++;
                if (n && (N(e, !1), 0 === e.strm.avail_out)) return A;
              }
              return e.insert = e.strstart < x - 1 ? e.strstart : x - 1, t === f ? (N(e, !0), 0 === e.strm.avail_out ? O : B) : e.last_lit && (N(e, !1), 0 === e.strm.avail_out) ? A : I;
            }
            function W(e, t) {
              for (var r, n, i;;) {
                if (e.lookahead < z) {
                  if (j(e), e.lookahead < z && t === l) return A;
                  if (0 === e.lookahead) break;
                }
                if (r = 0, e.lookahead >= x && (e.ins_h = (e.ins_h << e.hash_shift ^ e.window[e.strstart + x - 1]) & e.hash_mask, r = e.prev[e.strstart & e.w_mask] = e.head[e.ins_h], e.head[e.ins_h] = e.strstart), e.prev_length = e.match_length, e.prev_match = e.match_start, e.match_length = x - 1, 0 !== r && e.prev_length < e.max_lazy_match && e.strstart - r <= e.w_size - z && (e.match_length = L(e, r), e.match_length <= 5 && (1 === e.strategy || e.match_length === x && 4096 < e.strstart - e.match_start) && (e.match_length = x - 1)), e.prev_length >= x && e.match_length <= e.prev_length) {
                  for (i = e.strstart + e.lookahead - x, n = u._tr_tally(e, e.strstart - 1 - e.prev_match, e.prev_length - x), e.lookahead -= e.prev_length - 1, e.prev_length -= 2; ++e.strstart <= i && (e.ins_h = (e.ins_h << e.hash_shift ^ e.window[e.strstart + x - 1]) & e.hash_mask, r = e.prev[e.strstart & e.w_mask] = e.head[e.ins_h], e.head[e.ins_h] = e.strstart), 0 != --e.prev_length;);
                  if (e.match_available = 0, e.match_length = x - 1, e.strstart++, n && (N(e, !1), 0 === e.strm.avail_out)) return A;
                } else if (e.match_available) {
                  if ((n = u._tr_tally(e, 0, e.window[e.strstart - 1])) && N(e, !1), e.strstart++, e.lookahead--, 0 === e.strm.avail_out) return A;
                } else e.match_available = 1, e.strstart++, e.lookahead--;
              }
              return e.match_available && (n = u._tr_tally(e, 0, e.window[e.strstart - 1]), e.match_available = 0), e.insert = e.strstart < x - 1 ? e.strstart : x - 1, t === f ? (N(e, !0), 0 === e.strm.avail_out ? O : B) : e.last_lit && (N(e, !1), 0 === e.strm.avail_out) ? A : I;
            }
            function M(e, t, r, n, i) {
              this.good_length = e, this.max_lazy = t, this.nice_length = r, this.max_chain = n, this.func = i;
            }
            function H() {
              this.strm = null, this.status = 0, this.pending_buf = null, this.pending_buf_size = 0, this.pending_out = 0, this.pending = 0, this.wrap = 0, this.gzhead = null, this.gzindex = 0, this.method = v, this.last_flush = -1, this.w_size = 0, this.w_bits = 0, this.w_mask = 0, this.window = null, this.window_size = 0, this.prev = null, this.head = null, this.ins_h = 0, this.hash_size = 0, this.hash_bits = 0, this.hash_mask = 0, this.hash_shift = 0, this.block_start = 0, this.match_length = 0, this.prev_match = 0, this.match_available = 0, this.strstart = 0, this.match_start = 0, this.lookahead = 0, this.prev_length = 0, this.max_chain_length = 0, this.max_lazy_match = 0, this.level = 0, this.strategy = 0, this.good_match = 0, this.nice_match = 0, this.dyn_ltree = new c.Buf16(2 * w), this.dyn_dtree = new c.Buf16(2 * (2 * a + 1)), this.bl_tree = new c.Buf16(2 * (2 * o + 1)), D(this.dyn_ltree), D(this.dyn_dtree), D(this.bl_tree), this.l_desc = null, this.d_desc = null, this.bl_desc = null, this.bl_count = new c.Buf16(k + 1), this.heap = new c.Buf16(2 * s + 1), D(this.heap), this.heap_len = 0, this.heap_max = 0, this.depth = new c.Buf16(2 * s + 1), D(this.depth), this.l_buf = 0, this.lit_bufsize = 0, this.last_lit = 0, this.d_buf = 0, this.opt_len = 0, this.static_len = 0, this.matches = 0, this.insert = 0, this.bi_buf = 0, this.bi_valid = 0;
            }
            function G(e) {
              var t;
              return e && e.state ? (e.total_in = e.total_out = 0, e.data_type = i, (t = e.state).pending = 0, t.pending_out = 0, t.wrap < 0 && (t.wrap = -t.wrap), t.status = t.wrap ? C : E, e.adler = 2 === t.wrap ? 0 : 1, t.last_flush = l, u._tr_init(t), m) : R(e, _);
            }
            function K(e) {
              var t = G(e);
              return t === m && function (e) {
                e.window_size = 2 * e.w_size, D(e.head), e.max_lazy_match = h[e.level].max_lazy, e.good_match = h[e.level].good_length, e.nice_match = h[e.level].nice_length, e.max_chain_length = h[e.level].max_chain, e.strstart = 0, e.block_start = 0, e.lookahead = 0, e.insert = 0, e.match_length = e.prev_length = x - 1, e.match_available = 0, e.ins_h = 0;
              }(e.state), t;
            }
            function Y(e, t, r, n, i, s) {
              if (!e) return _;
              var a = 1;
              if (t === g && (t = 6), n < 0 ? (a = 0, n = -n) : 15 < n && (a = 2, n -= 16), i < 1 || y < i || r !== v || n < 8 || 15 < n || t < 0 || 9 < t || s < 0 || b < s) return R(e, _);
              8 === n && (n = 9);
              var o = new H();
              return (e.state = o).strm = e, o.wrap = a, o.gzhead = null, o.w_bits = n, o.w_size = 1 << o.w_bits, o.w_mask = o.w_size - 1, o.hash_bits = i + 7, o.hash_size = 1 << o.hash_bits, o.hash_mask = o.hash_size - 1, o.hash_shift = ~~((o.hash_bits + x - 1) / x), o.window = new c.Buf8(2 * o.w_size), o.head = new c.Buf16(o.hash_size), o.prev = new c.Buf16(o.w_size), o.lit_bufsize = 1 << i + 6, o.pending_buf_size = 4 * o.lit_bufsize, o.pending_buf = new c.Buf8(o.pending_buf_size), o.d_buf = 1 * o.lit_bufsize, o.l_buf = 3 * o.lit_bufsize, o.level = t, o.strategy = s, o.method = r, K(e);
            }
            h = [new M(0, 0, 0, 0, function (e, t) {
              var r = 65535;
              for (r > e.pending_buf_size - 5 && (r = e.pending_buf_size - 5);;) {
                if (e.lookahead <= 1) {
                  if (j(e), 0 === e.lookahead && t === l) return A;
                  if (0 === e.lookahead) break;
                }
                e.strstart += e.lookahead, e.lookahead = 0;
                var n = e.block_start + r;
                if ((0 === e.strstart || e.strstart >= n) && (e.lookahead = e.strstart - n, e.strstart = n, N(e, !1), 0 === e.strm.avail_out)) return A;
                if (e.strstart - e.block_start >= e.w_size - z && (N(e, !1), 0 === e.strm.avail_out)) return A;
              }
              return e.insert = 0, t === f ? (N(e, !0), 0 === e.strm.avail_out ? O : B) : (e.strstart > e.block_start && (N(e, !1), e.strm.avail_out), A);
            }), new M(4, 4, 8, 4, Z), new M(4, 5, 16, 8, Z), new M(4, 6, 32, 32, Z), new M(4, 4, 16, 16, W), new M(8, 16, 32, 32, W), new M(8, 16, 128, 128, W), new M(8, 32, 128, 256, W), new M(32, 128, 258, 1024, W), new M(32, 258, 258, 4096, W)], r.deflateInit = function (e, t) {
              return Y(e, t, v, 15, 8, 0);
            }, r.deflateInit2 = Y, r.deflateReset = K, r.deflateResetKeep = G, r.deflateSetHeader = function (e, t) {
              return e && e.state ? 2 !== e.state.wrap ? _ : (e.state.gzhead = t, m) : _;
            }, r.deflate = function (e, t) {
              var r, n, i, s;
              if (!e || !e.state || 5 < t || t < 0) return e ? R(e, _) : _;
              if (n = e.state, !e.output || !e.input && 0 !== e.avail_in || 666 === n.status && t !== f) return R(e, 0 === e.avail_out ? -5 : _);
              if (n.strm = e, r = n.last_flush, n.last_flush = t, n.status === C) if (2 === n.wrap) e.adler = 0, U(n, 31), U(n, 139), U(n, 8), n.gzhead ? (U(n, (n.gzhead.text ? 1 : 0) + (n.gzhead.hcrc ? 2 : 0) + (n.gzhead.extra ? 4 : 0) + (n.gzhead.name ? 8 : 0) + (n.gzhead.comment ? 16 : 0)), U(n, 255 & n.gzhead.time), U(n, n.gzhead.time >> 8 & 255), U(n, n.gzhead.time >> 16 & 255), U(n, n.gzhead.time >> 24 & 255), U(n, 9 === n.level ? 2 : 2 <= n.strategy || n.level < 2 ? 4 : 0), U(n, 255 & n.gzhead.os), n.gzhead.extra && n.gzhead.extra.length && (U(n, 255 & n.gzhead.extra.length), U(n, n.gzhead.extra.length >> 8 & 255)), n.gzhead.hcrc && (e.adler = p(e.adler, n.pending_buf, n.pending, 0)), n.gzindex = 0, n.status = 69) : (U(n, 0), U(n, 0), U(n, 0), U(n, 0), U(n, 0), U(n, 9 === n.level ? 2 : 2 <= n.strategy || n.level < 2 ? 4 : 0), U(n, 3), n.status = E);else {
                var a = v + (n.w_bits - 8 << 4) << 8;
                a |= (2 <= n.strategy || n.level < 2 ? 0 : n.level < 6 ? 1 : 6 === n.level ? 2 : 3) << 6, 0 !== n.strstart && (a |= 32), a += 31 - a % 31, n.status = E, P(n, a), 0 !== n.strstart && (P(n, e.adler >>> 16), P(n, 65535 & e.adler)), e.adler = 1;
              }
              if (69 === n.status) if (n.gzhead.extra) {
                for (i = n.pending; n.gzindex < (65535 & n.gzhead.extra.length) && (n.pending !== n.pending_buf_size || (n.gzhead.hcrc && n.pending > i && (e.adler = p(e.adler, n.pending_buf, n.pending - i, i)), F(e), i = n.pending, n.pending !== n.pending_buf_size));) U(n, 255 & n.gzhead.extra[n.gzindex]), n.gzindex++;
                n.gzhead.hcrc && n.pending > i && (e.adler = p(e.adler, n.pending_buf, n.pending - i, i)), n.gzindex === n.gzhead.extra.length && (n.gzindex = 0, n.status = 73);
              } else n.status = 73;
              if (73 === n.status) if (n.gzhead.name) {
                i = n.pending;
                do {
                  if (n.pending === n.pending_buf_size && (n.gzhead.hcrc && n.pending > i && (e.adler = p(e.adler, n.pending_buf, n.pending - i, i)), F(e), i = n.pending, n.pending === n.pending_buf_size)) {
                    s = 1;
                    break;
                  }
                  s = n.gzindex < n.gzhead.name.length ? 255 & n.gzhead.name.charCodeAt(n.gzindex++) : 0, U(n, s);
                } while (0 !== s);
                n.gzhead.hcrc && n.pending > i && (e.adler = p(e.adler, n.pending_buf, n.pending - i, i)), 0 === s && (n.gzindex = 0, n.status = 91);
              } else n.status = 91;
              if (91 === n.status) if (n.gzhead.comment) {
                i = n.pending;
                do {
                  if (n.pending === n.pending_buf_size && (n.gzhead.hcrc && n.pending > i && (e.adler = p(e.adler, n.pending_buf, n.pending - i, i)), F(e), i = n.pending, n.pending === n.pending_buf_size)) {
                    s = 1;
                    break;
                  }
                  s = n.gzindex < n.gzhead.comment.length ? 255 & n.gzhead.comment.charCodeAt(n.gzindex++) : 0, U(n, s);
                } while (0 !== s);
                n.gzhead.hcrc && n.pending > i && (e.adler = p(e.adler, n.pending_buf, n.pending - i, i)), 0 === s && (n.status = 103);
              } else n.status = 103;
              if (103 === n.status && (n.gzhead.hcrc ? (n.pending + 2 > n.pending_buf_size && F(e), n.pending + 2 <= n.pending_buf_size && (U(n, 255 & e.adler), U(n, e.adler >> 8 & 255), e.adler = 0, n.status = E)) : n.status = E), 0 !== n.pending) {
                if (F(e), 0 === e.avail_out) return n.last_flush = -1, m;
              } else if (0 === e.avail_in && T(t) <= T(r) && t !== f) return R(e, -5);
              if (666 === n.status && 0 !== e.avail_in) return R(e, -5);
              if (0 !== e.avail_in || 0 !== n.lookahead || t !== l && 666 !== n.status) {
                var o = 2 === n.strategy ? function (e, t) {
                  for (var r;;) {
                    if (0 === e.lookahead && (j(e), 0 === e.lookahead)) {
                      if (t === l) return A;
                      break;
                    }
                    if (e.match_length = 0, r = u._tr_tally(e, 0, e.window[e.strstart]), e.lookahead--, e.strstart++, r && (N(e, !1), 0 === e.strm.avail_out)) return A;
                  }
                  return e.insert = 0, t === f ? (N(e, !0), 0 === e.strm.avail_out ? O : B) : e.last_lit && (N(e, !1), 0 === e.strm.avail_out) ? A : I;
                }(n, t) : 3 === n.strategy ? function (e, t) {
                  for (var r, n, i, s, a = e.window;;) {
                    if (e.lookahead <= S) {
                      if (j(e), e.lookahead <= S && t === l) return A;
                      if (0 === e.lookahead) break;
                    }
                    if (e.match_length = 0, e.lookahead >= x && 0 < e.strstart && (n = a[i = e.strstart - 1]) === a[++i] && n === a[++i] && n === a[++i]) {
                      s = e.strstart + S;
                      do {} while (n === a[++i] && n === a[++i] && n === a[++i] && n === a[++i] && n === a[++i] && n === a[++i] && n === a[++i] && n === a[++i] && i < s);
                      e.match_length = S - (s - i), e.match_length > e.lookahead && (e.match_length = e.lookahead);
                    }
                    if (e.match_length >= x ? (r = u._tr_tally(e, 1, e.match_length - x), e.lookahead -= e.match_length, e.strstart += e.match_length, e.match_length = 0) : (r = u._tr_tally(e, 0, e.window[e.strstart]), e.lookahead--, e.strstart++), r && (N(e, !1), 0 === e.strm.avail_out)) return A;
                  }
                  return e.insert = 0, t === f ? (N(e, !0), 0 === e.strm.avail_out ? O : B) : e.last_lit && (N(e, !1), 0 === e.strm.avail_out) ? A : I;
                }(n, t) : h[n.level].func(n, t);
                if (o !== O && o !== B || (n.status = 666), o === A || o === O) return 0 === e.avail_out && (n.last_flush = -1), m;
                if (o === I && (1 === t ? u._tr_align(n) : 5 !== t && (u._tr_stored_block(n, 0, 0, !1), 3 === t && (D(n.head), 0 === n.lookahead && (n.strstart = 0, n.block_start = 0, n.insert = 0))), F(e), 0 === e.avail_out)) return n.last_flush = -1, m;
              }
              return t !== f ? m : n.wrap <= 0 ? 1 : (2 === n.wrap ? (U(n, 255 & e.adler), U(n, e.adler >> 8 & 255), U(n, e.adler >> 16 & 255), U(n, e.adler >> 24 & 255), U(n, 255 & e.total_in), U(n, e.total_in >> 8 & 255), U(n, e.total_in >> 16 & 255), U(n, e.total_in >> 24 & 255)) : (P(n, e.adler >>> 16), P(n, 65535 & e.adler)), F(e), 0 < n.wrap && (n.wrap = -n.wrap), 0 !== n.pending ? m : 1);
            }, r.deflateEnd = function (e) {
              var t;
              return e && e.state ? (t = e.state.status) !== C && 69 !== t && 73 !== t && 91 !== t && 103 !== t && t !== E && 666 !== t ? R(e, _) : (e.state = null, t === E ? R(e, -3) : m) : _;
            }, r.deflateSetDictionary = function (e, t) {
              var r,
                n,
                i,
                s,
                a,
                o,
                h,
                u,
                l = t.length;
              if (!e || !e.state) return _;
              if (2 === (s = (r = e.state).wrap) || 1 === s && r.status !== C || r.lookahead) return _;
              for (1 === s && (e.adler = d(e.adler, t, l, 0)), r.wrap = 0, l >= r.w_size && (0 === s && (D(r.head), r.strstart = 0, r.block_start = 0, r.insert = 0), u = new c.Buf8(r.w_size), c.arraySet(u, t, l - r.w_size, r.w_size, 0), t = u, l = r.w_size), a = e.avail_in, o = e.next_in, h = e.input, e.avail_in = l, e.next_in = 0, e.input = t, j(r); r.lookahead >= x;) {
                for (n = r.strstart, i = r.lookahead - (x - 1); r.ins_h = (r.ins_h << r.hash_shift ^ r.window[n + x - 1]) & r.hash_mask, r.prev[n & r.w_mask] = r.head[r.ins_h], r.head[r.ins_h] = n, n++, --i;);
                r.strstart = n, r.lookahead = x - 1, j(r);
              }
              return r.strstart += r.lookahead, r.block_start = r.strstart, r.insert = r.lookahead, r.lookahead = 0, r.match_length = r.prev_length = x - 1, r.match_available = 0, e.next_in = o, e.input = h, e.avail_in = a, r.wrap = s, m;
            }, r.deflateInfo = "pako deflate (from Nodeca project)";
          }, {
            "../utils/common": 41,
            "./adler32": 43,
            "./crc32": 45,
            "./messages": 51,
            "./trees": 52
          }],
          47: [function (e, t, r) {

            t.exports = function () {
              this.text = 0, this.time = 0, this.xflags = 0, this.os = 0, this.extra = null, this.extra_len = 0, this.name = "", this.comment = "", this.hcrc = 0, this.done = !1;
            };
          }, {}],
          48: [function (e, t, r) {

            t.exports = function (e, t) {
              var r, n, i, s, a, o, h, u, l, f, c, d, p, m, _, g, b, v, y, w, k, x, S, z, C;
              r = e.state, n = e.next_in, z = e.input, i = n + (e.avail_in - 5), s = e.next_out, C = e.output, a = s - (t - e.avail_out), o = s + (e.avail_out - 257), h = r.dmax, u = r.wsize, l = r.whave, f = r.wnext, c = r.window, d = r.hold, p = r.bits, m = r.lencode, _ = r.distcode, g = (1 << r.lenbits) - 1, b = (1 << r.distbits) - 1;
              e: do {
                p < 15 && (d += z[n++] << p, p += 8, d += z[n++] << p, p += 8), v = m[d & g];
                t: for (;;) {
                  if (d >>>= y = v >>> 24, p -= y, 0 === (y = v >>> 16 & 255)) C[s++] = 65535 & v;else {
                    if (!(16 & y)) {
                      if (0 == (64 & y)) {
                        v = m[(65535 & v) + (d & (1 << y) - 1)];
                        continue t;
                      }
                      if (32 & y) {
                        r.mode = 12;
                        break e;
                      }
                      e.msg = "invalid literal/length code", r.mode = 30;
                      break e;
                    }
                    w = 65535 & v, (y &= 15) && (p < y && (d += z[n++] << p, p += 8), w += d & (1 << y) - 1, d >>>= y, p -= y), p < 15 && (d += z[n++] << p, p += 8, d += z[n++] << p, p += 8), v = _[d & b];
                    r: for (;;) {
                      if (d >>>= y = v >>> 24, p -= y, !(16 & (y = v >>> 16 & 255))) {
                        if (0 == (64 & y)) {
                          v = _[(65535 & v) + (d & (1 << y) - 1)];
                          continue r;
                        }
                        e.msg = "invalid distance code", r.mode = 30;
                        break e;
                      }
                      if (k = 65535 & v, p < (y &= 15) && (d += z[n++] << p, (p += 8) < y && (d += z[n++] << p, p += 8)), h < (k += d & (1 << y) - 1)) {
                        e.msg = "invalid distance too far back", r.mode = 30;
                        break e;
                      }
                      if (d >>>= y, p -= y, (y = s - a) < k) {
                        if (l < (y = k - y) && r.sane) {
                          e.msg = "invalid distance too far back", r.mode = 30;
                          break e;
                        }
                        if (S = c, (x = 0) === f) {
                          if (x += u - y, y < w) {
                            for (w -= y; C[s++] = c[x++], --y;);
                            x = s - k, S = C;
                          }
                        } else if (f < y) {
                          if (x += u + f - y, (y -= f) < w) {
                            for (w -= y; C[s++] = c[x++], --y;);
                            if (x = 0, f < w) {
                              for (w -= y = f; C[s++] = c[x++], --y;);
                              x = s - k, S = C;
                            }
                          }
                        } else if (x += f - y, y < w) {
                          for (w -= y; C[s++] = c[x++], --y;);
                          x = s - k, S = C;
                        }
                        for (; 2 < w;) C[s++] = S[x++], C[s++] = S[x++], C[s++] = S[x++], w -= 3;
                        w && (C[s++] = S[x++], 1 < w && (C[s++] = S[x++]));
                      } else {
                        for (x = s - k; C[s++] = C[x++], C[s++] = C[x++], C[s++] = C[x++], 2 < (w -= 3););
                        w && (C[s++] = C[x++], 1 < w && (C[s++] = C[x++]));
                      }
                      break;
                    }
                  }
                  break;
                }
              } while (n < i && s < o);
              n -= w = p >> 3, d &= (1 << (p -= w << 3)) - 1, e.next_in = n, e.next_out = s, e.avail_in = n < i ? i - n + 5 : 5 - (n - i), e.avail_out = s < o ? o - s + 257 : 257 - (s - o), r.hold = d, r.bits = p;
            };
          }, {}],
          49: [function (e, t, r) {

            var I = e("../utils/common"),
              O = e("./adler32"),
              B = e("./crc32"),
              R = e("./inffast"),
              T = e("./inftrees"),
              D = 1,
              F = 2,
              N = 0,
              U = -2,
              P = 1,
              n = 852,
              i = 592;
            function L(e) {
              return (e >>> 24 & 255) + (e >>> 8 & 65280) + ((65280 & e) << 8) + ((255 & e) << 24);
            }
            function s() {
              this.mode = 0, this.last = !1, this.wrap = 0, this.havedict = !1, this.flags = 0, this.dmax = 0, this.check = 0, this.total = 0, this.head = null, this.wbits = 0, this.wsize = 0, this.whave = 0, this.wnext = 0, this.window = null, this.hold = 0, this.bits = 0, this.length = 0, this.offset = 0, this.extra = 0, this.lencode = null, this.distcode = null, this.lenbits = 0, this.distbits = 0, this.ncode = 0, this.nlen = 0, this.ndist = 0, this.have = 0, this.next = null, this.lens = new I.Buf16(320), this.work = new I.Buf16(288), this.lendyn = null, this.distdyn = null, this.sane = 0, this.back = 0, this.was = 0;
            }
            function a(e) {
              var t;
              return e && e.state ? (t = e.state, e.total_in = e.total_out = t.total = 0, e.msg = "", t.wrap && (e.adler = 1 & t.wrap), t.mode = P, t.last = 0, t.havedict = 0, t.dmax = 32768, t.head = null, t.hold = 0, t.bits = 0, t.lencode = t.lendyn = new I.Buf32(n), t.distcode = t.distdyn = new I.Buf32(i), t.sane = 1, t.back = -1, N) : U;
            }
            function o(e) {
              var t;
              return e && e.state ? ((t = e.state).wsize = 0, t.whave = 0, t.wnext = 0, a(e)) : U;
            }
            function h(e, t) {
              var r, n;
              return e && e.state ? (n = e.state, t < 0 ? (r = 0, t = -t) : (r = 1 + (t >> 4), t < 48 && (t &= 15)), t && (t < 8 || 15 < t) ? U : (null !== n.window && n.wbits !== t && (n.window = null), n.wrap = r, n.wbits = t, o(e))) : U;
            }
            function u(e, t) {
              var r, n;
              return e ? (n = new s(), (e.state = n).window = null, (r = h(e, t)) !== N && (e.state = null), r) : U;
            }
            var l,
              f,
              c = !0;
            function j(e) {
              if (c) {
                var t;
                for (l = new I.Buf32(512), f = new I.Buf32(32), t = 0; t < 144;) e.lens[t++] = 8;
                for (; t < 256;) e.lens[t++] = 9;
                for (; t < 280;) e.lens[t++] = 7;
                for (; t < 288;) e.lens[t++] = 8;
                for (T(D, e.lens, 0, 288, l, 0, e.work, {
                  bits: 9
                }), t = 0; t < 32;) e.lens[t++] = 5;
                T(F, e.lens, 0, 32, f, 0, e.work, {
                  bits: 5
                }), c = !1;
              }
              e.lencode = l, e.lenbits = 9, e.distcode = f, e.distbits = 5;
            }
            function Z(e, t, r, n) {
              var i,
                s = e.state;
              return null === s.window && (s.wsize = 1 << s.wbits, s.wnext = 0, s.whave = 0, s.window = new I.Buf8(s.wsize)), n >= s.wsize ? (I.arraySet(s.window, t, r - s.wsize, s.wsize, 0), s.wnext = 0, s.whave = s.wsize) : (n < (i = s.wsize - s.wnext) && (i = n), I.arraySet(s.window, t, r - n, i, s.wnext), (n -= i) ? (I.arraySet(s.window, t, r - n, n, 0), s.wnext = n, s.whave = s.wsize) : (s.wnext += i, s.wnext === s.wsize && (s.wnext = 0), s.whave < s.wsize && (s.whave += i))), 0;
            }
            r.inflateReset = o, r.inflateReset2 = h, r.inflateResetKeep = a, r.inflateInit = function (e) {
              return u(e, 15);
            }, r.inflateInit2 = u, r.inflate = function (e, t) {
              var r,
                n,
                i,
                s,
                a,
                o,
                h,
                u,
                l,
                f,
                c,
                d,
                p,
                m,
                _,
                g,
                b,
                v,
                y,
                w,
                k,
                x,
                S,
                z,
                C = 0,
                E = new I.Buf8(4),
                A = [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15];
              if (!e || !e.state || !e.output || !e.input && 0 !== e.avail_in) return U;
              12 === (r = e.state).mode && (r.mode = 13), a = e.next_out, i = e.output, h = e.avail_out, s = e.next_in, n = e.input, o = e.avail_in, u = r.hold, l = r.bits, f = o, c = h, x = N;
              e: for (;;) switch (r.mode) {
                case P:
                  if (0 === r.wrap) {
                    r.mode = 13;
                    break;
                  }
                  for (; l < 16;) {
                    if (0 === o) break e;
                    o--, u += n[s++] << l, l += 8;
                  }
                  if (2 & r.wrap && 35615 === u) {
                    E[r.check = 0] = 255 & u, E[1] = u >>> 8 & 255, r.check = B(r.check, E, 2, 0), l = u = 0, r.mode = 2;
                    break;
                  }
                  if (r.flags = 0, r.head && (r.head.done = !1), !(1 & r.wrap) || (((255 & u) << 8) + (u >> 8)) % 31) {
                    e.msg = "incorrect header check", r.mode = 30;
                    break;
                  }
                  if (8 != (15 & u)) {
                    e.msg = "unknown compression method", r.mode = 30;
                    break;
                  }
                  if (l -= 4, k = 8 + (15 & (u >>>= 4)), 0 === r.wbits) r.wbits = k;else if (k > r.wbits) {
                    e.msg = "invalid window size", r.mode = 30;
                    break;
                  }
                  r.dmax = 1 << k, e.adler = r.check = 1, r.mode = 512 & u ? 10 : 12, l = u = 0;
                  break;
                case 2:
                  for (; l < 16;) {
                    if (0 === o) break e;
                    o--, u += n[s++] << l, l += 8;
                  }
                  if (r.flags = u, 8 != (255 & r.flags)) {
                    e.msg = "unknown compression method", r.mode = 30;
                    break;
                  }
                  if (57344 & r.flags) {
                    e.msg = "unknown header flags set", r.mode = 30;
                    break;
                  }
                  r.head && (r.head.text = u >> 8 & 1), 512 & r.flags && (E[0] = 255 & u, E[1] = u >>> 8 & 255, r.check = B(r.check, E, 2, 0)), l = u = 0, r.mode = 3;
                case 3:
                  for (; l < 32;) {
                    if (0 === o) break e;
                    o--, u += n[s++] << l, l += 8;
                  }
                  r.head && (r.head.time = u), 512 & r.flags && (E[0] = 255 & u, E[1] = u >>> 8 & 255, E[2] = u >>> 16 & 255, E[3] = u >>> 24 & 255, r.check = B(r.check, E, 4, 0)), l = u = 0, r.mode = 4;
                case 4:
                  for (; l < 16;) {
                    if (0 === o) break e;
                    o--, u += n[s++] << l, l += 8;
                  }
                  r.head && (r.head.xflags = 255 & u, r.head.os = u >> 8), 512 & r.flags && (E[0] = 255 & u, E[1] = u >>> 8 & 255, r.check = B(r.check, E, 2, 0)), l = u = 0, r.mode = 5;
                case 5:
                  if (1024 & r.flags) {
                    for (; l < 16;) {
                      if (0 === o) break e;
                      o--, u += n[s++] << l, l += 8;
                    }
                    r.length = u, r.head && (r.head.extra_len = u), 512 & r.flags && (E[0] = 255 & u, E[1] = u >>> 8 & 255, r.check = B(r.check, E, 2, 0)), l = u = 0;
                  } else r.head && (r.head.extra = null);
                  r.mode = 6;
                case 6:
                  if (1024 & r.flags && (o < (d = r.length) && (d = o), d && (r.head && (k = r.head.extra_len - r.length, r.head.extra || (r.head.extra = new Array(r.head.extra_len)), I.arraySet(r.head.extra, n, s, d, k)), 512 & r.flags && (r.check = B(r.check, n, d, s)), o -= d, s += d, r.length -= d), r.length)) break e;
                  r.length = 0, r.mode = 7;
                case 7:
                  if (2048 & r.flags) {
                    if (0 === o) break e;
                    for (d = 0; k = n[s + d++], r.head && k && r.length < 65536 && (r.head.name += String.fromCharCode(k)), k && d < o;);
                    if (512 & r.flags && (r.check = B(r.check, n, d, s)), o -= d, s += d, k) break e;
                  } else r.head && (r.head.name = null);
                  r.length = 0, r.mode = 8;
                case 8:
                  if (4096 & r.flags) {
                    if (0 === o) break e;
                    for (d = 0; k = n[s + d++], r.head && k && r.length < 65536 && (r.head.comment += String.fromCharCode(k)), k && d < o;);
                    if (512 & r.flags && (r.check = B(r.check, n, d, s)), o -= d, s += d, k) break e;
                  } else r.head && (r.head.comment = null);
                  r.mode = 9;
                case 9:
                  if (512 & r.flags) {
                    for (; l < 16;) {
                      if (0 === o) break e;
                      o--, u += n[s++] << l, l += 8;
                    }
                    if (u !== (65535 & r.check)) {
                      e.msg = "header crc mismatch", r.mode = 30;
                      break;
                    }
                    l = u = 0;
                  }
                  r.head && (r.head.hcrc = r.flags >> 9 & 1, r.head.done = !0), e.adler = r.check = 0, r.mode = 12;
                  break;
                case 10:
                  for (; l < 32;) {
                    if (0 === o) break e;
                    o--, u += n[s++] << l, l += 8;
                  }
                  e.adler = r.check = L(u), l = u = 0, r.mode = 11;
                case 11:
                  if (0 === r.havedict) return e.next_out = a, e.avail_out = h, e.next_in = s, e.avail_in = o, r.hold = u, r.bits = l, 2;
                  e.adler = r.check = 1, r.mode = 12;
                case 12:
                  if (5 === t || 6 === t) break e;
                case 13:
                  if (r.last) {
                    u >>>= 7 & l, l -= 7 & l, r.mode = 27;
                    break;
                  }
                  for (; l < 3;) {
                    if (0 === o) break e;
                    o--, u += n[s++] << l, l += 8;
                  }
                  switch (r.last = 1 & u, l -= 1, 3 & (u >>>= 1)) {
                    case 0:
                      r.mode = 14;
                      break;
                    case 1:
                      if (j(r), r.mode = 20, 6 !== t) break;
                      u >>>= 2, l -= 2;
                      break e;
                    case 2:
                      r.mode = 17;
                      break;
                    case 3:
                      e.msg = "invalid block type", r.mode = 30;
                  }
                  u >>>= 2, l -= 2;
                  break;
                case 14:
                  for (u >>>= 7 & l, l -= 7 & l; l < 32;) {
                    if (0 === o) break e;
                    o--, u += n[s++] << l, l += 8;
                  }
                  if ((65535 & u) != (u >>> 16 ^ 65535)) {
                    e.msg = "invalid stored block lengths", r.mode = 30;
                    break;
                  }
                  if (r.length = 65535 & u, l = u = 0, r.mode = 15, 6 === t) break e;
                case 15:
                  r.mode = 16;
                case 16:
                  if (d = r.length) {
                    if (o < d && (d = o), h < d && (d = h), 0 === d) break e;
                    I.arraySet(i, n, s, d, a), o -= d, s += d, h -= d, a += d, r.length -= d;
                    break;
                  }
                  r.mode = 12;
                  break;
                case 17:
                  for (; l < 14;) {
                    if (0 === o) break e;
                    o--, u += n[s++] << l, l += 8;
                  }
                  if (r.nlen = 257 + (31 & u), u >>>= 5, l -= 5, r.ndist = 1 + (31 & u), u >>>= 5, l -= 5, r.ncode = 4 + (15 & u), u >>>= 4, l -= 4, 286 < r.nlen || 30 < r.ndist) {
                    e.msg = "too many length or distance symbols", r.mode = 30;
                    break;
                  }
                  r.have = 0, r.mode = 18;
                case 18:
                  for (; r.have < r.ncode;) {
                    for (; l < 3;) {
                      if (0 === o) break e;
                      o--, u += n[s++] << l, l += 8;
                    }
                    r.lens[A[r.have++]] = 7 & u, u >>>= 3, l -= 3;
                  }
                  for (; r.have < 19;) r.lens[A[r.have++]] = 0;
                  if (r.lencode = r.lendyn, r.lenbits = 7, S = {
                    bits: r.lenbits
                  }, x = T(0, r.lens, 0, 19, r.lencode, 0, r.work, S), r.lenbits = S.bits, x) {
                    e.msg = "invalid code lengths set", r.mode = 30;
                    break;
                  }
                  r.have = 0, r.mode = 19;
                case 19:
                  for (; r.have < r.nlen + r.ndist;) {
                    for (; g = (C = r.lencode[u & (1 << r.lenbits) - 1]) >>> 16 & 255, b = 65535 & C, !((_ = C >>> 24) <= l);) {
                      if (0 === o) break e;
                      o--, u += n[s++] << l, l += 8;
                    }
                    if (b < 16) u >>>= _, l -= _, r.lens[r.have++] = b;else {
                      if (16 === b) {
                        for (z = _ + 2; l < z;) {
                          if (0 === o) break e;
                          o--, u += n[s++] << l, l += 8;
                        }
                        if (u >>>= _, l -= _, 0 === r.have) {
                          e.msg = "invalid bit length repeat", r.mode = 30;
                          break;
                        }
                        k = r.lens[r.have - 1], d = 3 + (3 & u), u >>>= 2, l -= 2;
                      } else if (17 === b) {
                        for (z = _ + 3; l < z;) {
                          if (0 === o) break e;
                          o--, u += n[s++] << l, l += 8;
                        }
                        l -= _, k = 0, d = 3 + (7 & (u >>>= _)), u >>>= 3, l -= 3;
                      } else {
                        for (z = _ + 7; l < z;) {
                          if (0 === o) break e;
                          o--, u += n[s++] << l, l += 8;
                        }
                        l -= _, k = 0, d = 11 + (127 & (u >>>= _)), u >>>= 7, l -= 7;
                      }
                      if (r.have + d > r.nlen + r.ndist) {
                        e.msg = "invalid bit length repeat", r.mode = 30;
                        break;
                      }
                      for (; d--;) r.lens[r.have++] = k;
                    }
                  }
                  if (30 === r.mode) break;
                  if (0 === r.lens[256]) {
                    e.msg = "invalid code -- missing end-of-block", r.mode = 30;
                    break;
                  }
                  if (r.lenbits = 9, S = {
                    bits: r.lenbits
                  }, x = T(D, r.lens, 0, r.nlen, r.lencode, 0, r.work, S), r.lenbits = S.bits, x) {
                    e.msg = "invalid literal/lengths set", r.mode = 30;
                    break;
                  }
                  if (r.distbits = 6, r.distcode = r.distdyn, S = {
                    bits: r.distbits
                  }, x = T(F, r.lens, r.nlen, r.ndist, r.distcode, 0, r.work, S), r.distbits = S.bits, x) {
                    e.msg = "invalid distances set", r.mode = 30;
                    break;
                  }
                  if (r.mode = 20, 6 === t) break e;
                case 20:
                  r.mode = 21;
                case 21:
                  if (6 <= o && 258 <= h) {
                    e.next_out = a, e.avail_out = h, e.next_in = s, e.avail_in = o, r.hold = u, r.bits = l, R(e, c), a = e.next_out, i = e.output, h = e.avail_out, s = e.next_in, n = e.input, o = e.avail_in, u = r.hold, l = r.bits, 12 === r.mode && (r.back = -1);
                    break;
                  }
                  for (r.back = 0; g = (C = r.lencode[u & (1 << r.lenbits) - 1]) >>> 16 & 255, b = 65535 & C, !((_ = C >>> 24) <= l);) {
                    if (0 === o) break e;
                    o--, u += n[s++] << l, l += 8;
                  }
                  if (g && 0 == (240 & g)) {
                    for (v = _, y = g, w = b; g = (C = r.lencode[w + ((u & (1 << v + y) - 1) >> v)]) >>> 16 & 255, b = 65535 & C, !(v + (_ = C >>> 24) <= l);) {
                      if (0 === o) break e;
                      o--, u += n[s++] << l, l += 8;
                    }
                    u >>>= v, l -= v, r.back += v;
                  }
                  if (u >>>= _, l -= _, r.back += _, r.length = b, 0 === g) {
                    r.mode = 26;
                    break;
                  }
                  if (32 & g) {
                    r.back = -1, r.mode = 12;
                    break;
                  }
                  if (64 & g) {
                    e.msg = "invalid literal/length code", r.mode = 30;
                    break;
                  }
                  r.extra = 15 & g, r.mode = 22;
                case 22:
                  if (r.extra) {
                    for (z = r.extra; l < z;) {
                      if (0 === o) break e;
                      o--, u += n[s++] << l, l += 8;
                    }
                    r.length += u & (1 << r.extra) - 1, u >>>= r.extra, l -= r.extra, r.back += r.extra;
                  }
                  r.was = r.length, r.mode = 23;
                case 23:
                  for (; g = (C = r.distcode[u & (1 << r.distbits) - 1]) >>> 16 & 255, b = 65535 & C, !((_ = C >>> 24) <= l);) {
                    if (0 === o) break e;
                    o--, u += n[s++] << l, l += 8;
                  }
                  if (0 == (240 & g)) {
                    for (v = _, y = g, w = b; g = (C = r.distcode[w + ((u & (1 << v + y) - 1) >> v)]) >>> 16 & 255, b = 65535 & C, !(v + (_ = C >>> 24) <= l);) {
                      if (0 === o) break e;
                      o--, u += n[s++] << l, l += 8;
                    }
                    u >>>= v, l -= v, r.back += v;
                  }
                  if (u >>>= _, l -= _, r.back += _, 64 & g) {
                    e.msg = "invalid distance code", r.mode = 30;
                    break;
                  }
                  r.offset = b, r.extra = 15 & g, r.mode = 24;
                case 24:
                  if (r.extra) {
                    for (z = r.extra; l < z;) {
                      if (0 === o) break e;
                      o--, u += n[s++] << l, l += 8;
                    }
                    r.offset += u & (1 << r.extra) - 1, u >>>= r.extra, l -= r.extra, r.back += r.extra;
                  }
                  if (r.offset > r.dmax) {
                    e.msg = "invalid distance too far back", r.mode = 30;
                    break;
                  }
                  r.mode = 25;
                case 25:
                  if (0 === h) break e;
                  if (d = c - h, r.offset > d) {
                    if ((d = r.offset - d) > r.whave && r.sane) {
                      e.msg = "invalid distance too far back", r.mode = 30;
                      break;
                    }
                    p = d > r.wnext ? (d -= r.wnext, r.wsize - d) : r.wnext - d, d > r.length && (d = r.length), m = r.window;
                  } else m = i, p = a - r.offset, d = r.length;
                  for (h < d && (d = h), h -= d, r.length -= d; i[a++] = m[p++], --d;);
                  0 === r.length && (r.mode = 21);
                  break;
                case 26:
                  if (0 === h) break e;
                  i[a++] = r.length, h--, r.mode = 21;
                  break;
                case 27:
                  if (r.wrap) {
                    for (; l < 32;) {
                      if (0 === o) break e;
                      o--, u |= n[s++] << l, l += 8;
                    }
                    if (c -= h, e.total_out += c, r.total += c, c && (e.adler = r.check = r.flags ? B(r.check, i, c, a - c) : O(r.check, i, c, a - c)), c = h, (r.flags ? u : L(u)) !== r.check) {
                      e.msg = "incorrect data check", r.mode = 30;
                      break;
                    }
                    l = u = 0;
                  }
                  r.mode = 28;
                case 28:
                  if (r.wrap && r.flags) {
                    for (; l < 32;) {
                      if (0 === o) break e;
                      o--, u += n[s++] << l, l += 8;
                    }
                    if (u !== (4294967295 & r.total)) {
                      e.msg = "incorrect length check", r.mode = 30;
                      break;
                    }
                    l = u = 0;
                  }
                  r.mode = 29;
                case 29:
                  x = 1;
                  break e;
                case 30:
                  x = -3;
                  break e;
                case 31:
                  return -4;
                case 32:
                default:
                  return U;
              }
              return e.next_out = a, e.avail_out = h, e.next_in = s, e.avail_in = o, r.hold = u, r.bits = l, (r.wsize || c !== e.avail_out && r.mode < 30 && (r.mode < 27 || 4 !== t)) && Z(e, e.output, e.next_out, c - e.avail_out) ? (r.mode = 31, -4) : (f -= e.avail_in, c -= e.avail_out, e.total_in += f, e.total_out += c, r.total += c, r.wrap && c && (e.adler = r.check = r.flags ? B(r.check, i, c, e.next_out - c) : O(r.check, i, c, e.next_out - c)), e.data_type = r.bits + (r.last ? 64 : 0) + (12 === r.mode ? 128 : 0) + (20 === r.mode || 15 === r.mode ? 256 : 0), (0 == f && 0 === c || 4 === t) && x === N && (x = -5), x);
            }, r.inflateEnd = function (e) {
              if (!e || !e.state) return U;
              var t = e.state;
              return t.window && (t.window = null), e.state = null, N;
            }, r.inflateGetHeader = function (e, t) {
              var r;
              return e && e.state ? 0 == (2 & (r = e.state).wrap) ? U : ((r.head = t).done = !1, N) : U;
            }, r.inflateSetDictionary = function (e, t) {
              var r,
                n = t.length;
              return e && e.state ? 0 !== (r = e.state).wrap && 11 !== r.mode ? U : 11 === r.mode && O(1, t, n, 0) !== r.check ? -3 : Z(e, t, n, n) ? (r.mode = 31, -4) : (r.havedict = 1, N) : U;
            }, r.inflateInfo = "pako inflate (from Nodeca project)";
          }, {
            "../utils/common": 41,
            "./adler32": 43,
            "./crc32": 45,
            "./inffast": 48,
            "./inftrees": 50
          }],
          50: [function (e, t, r) {

            var D = e("../utils/common"),
              F = [3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0],
              N = [16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18, 19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 16, 72, 78],
              U = [1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577, 0, 0],
              P = [16, 16, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, 25, 25, 26, 26, 27, 27, 28, 28, 29, 29, 64, 64];
            t.exports = function (e, t, r, n, i, s, a, o) {
              var h,
                u,
                l,
                f,
                c,
                d,
                p,
                m,
                _,
                g = o.bits,
                b = 0,
                v = 0,
                y = 0,
                w = 0,
                k = 0,
                x = 0,
                S = 0,
                z = 0,
                C = 0,
                E = 0,
                A = null,
                I = 0,
                O = new D.Buf16(16),
                B = new D.Buf16(16),
                R = null,
                T = 0;
              for (b = 0; b <= 15; b++) O[b] = 0;
              for (v = 0; v < n; v++) O[t[r + v]]++;
              for (k = g, w = 15; 1 <= w && 0 === O[w]; w--);
              if (w < k && (k = w), 0 === w) return i[s++] = 20971520, i[s++] = 20971520, o.bits = 1, 0;
              for (y = 1; y < w && 0 === O[y]; y++);
              for (k < y && (k = y), b = z = 1; b <= 15; b++) if (z <<= 1, (z -= O[b]) < 0) return -1;
              if (0 < z && (0 === e || 1 !== w)) return -1;
              for (B[1] = 0, b = 1; b < 15; b++) B[b + 1] = B[b] + O[b];
              for (v = 0; v < n; v++) 0 !== t[r + v] && (a[B[t[r + v]]++] = v);
              if (d = 0 === e ? (A = R = a, 19) : 1 === e ? (A = F, I -= 257, R = N, T -= 257, 256) : (A = U, R = P, -1), b = y, c = s, S = v = E = 0, l = -1, f = (C = 1 << (x = k)) - 1, 1 === e && 852 < C || 2 === e && 592 < C) return 1;
              for (;;) {
                for (p = b - S, _ = a[v] < d ? (m = 0, a[v]) : a[v] > d ? (m = R[T + a[v]], A[I + a[v]]) : (m = 96, 0), h = 1 << b - S, y = u = 1 << x; i[c + (E >> S) + (u -= h)] = p << 24 | m << 16 | _ | 0, 0 !== u;);
                for (h = 1 << b - 1; E & h;) h >>= 1;
                if (0 !== h ? (E &= h - 1, E += h) : E = 0, v++, 0 == --O[b]) {
                  if (b === w) break;
                  b = t[r + a[v]];
                }
                if (k < b && (E & f) !== l) {
                  for (0 === S && (S = k), c += y, z = 1 << (x = b - S); x + S < w && !((z -= O[x + S]) <= 0);) x++, z <<= 1;
                  if (C += 1 << x, 1 === e && 852 < C || 2 === e && 592 < C) return 1;
                  i[l = E & f] = k << 24 | x << 16 | c - s | 0;
                }
              }
              return 0 !== E && (i[c + E] = b - S << 24 | 64 << 16 | 0), o.bits = k, 0;
            };
          }, {
            "../utils/common": 41
          }],
          51: [function (e, t, r) {

            t.exports = {
              2: "need dictionary",
              1: "stream end",
              0: "",
              "-1": "file error",
              "-2": "stream error",
              "-3": "data error",
              "-4": "insufficient memory",
              "-5": "buffer error",
              "-6": "incompatible version"
            };
          }, {}],
          52: [function (e, t, r) {

            var i = e("../utils/common"),
              o = 0,
              h = 1;
            function n(e) {
              for (var t = e.length; 0 <= --t;) e[t] = 0;
            }
            var s = 0,
              a = 29,
              u = 256,
              l = u + 1 + a,
              f = 30,
              c = 19,
              _ = 2 * l + 1,
              g = 15,
              d = 16,
              p = 7,
              m = 256,
              b = 16,
              v = 17,
              y = 18,
              w = [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0],
              k = [0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13],
              x = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 7],
              S = [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15],
              z = new Array(2 * (l + 2));
            n(z);
            var C = new Array(2 * f);
            n(C);
            var E = new Array(512);
            n(E);
            var A = new Array(256);
            n(A);
            var I = new Array(a);
            n(I);
            var O,
              B,
              R,
              T = new Array(f);
            function D(e, t, r, n, i) {
              this.static_tree = e, this.extra_bits = t, this.extra_base = r, this.elems = n, this.max_length = i, this.has_stree = e && e.length;
            }
            function F(e, t) {
              this.dyn_tree = e, this.max_code = 0, this.stat_desc = t;
            }
            function N(e) {
              return e < 256 ? E[e] : E[256 + (e >>> 7)];
            }
            function U(e, t) {
              e.pending_buf[e.pending++] = 255 & t, e.pending_buf[e.pending++] = t >>> 8 & 255;
            }
            function P(e, t, r) {
              e.bi_valid > d - r ? (e.bi_buf |= t << e.bi_valid & 65535, U(e, e.bi_buf), e.bi_buf = t >> d - e.bi_valid, e.bi_valid += r - d) : (e.bi_buf |= t << e.bi_valid & 65535, e.bi_valid += r);
            }
            function L(e, t, r) {
              P(e, r[2 * t], r[2 * t + 1]);
            }
            function j(e, t) {
              for (var r = 0; r |= 1 & e, e >>>= 1, r <<= 1, 0 < --t;);
              return r >>> 1;
            }
            function Z(e, t, r) {
              var n,
                i,
                s = new Array(g + 1),
                a = 0;
              for (n = 1; n <= g; n++) s[n] = a = a + r[n - 1] << 1;
              for (i = 0; i <= t; i++) {
                var o = e[2 * i + 1];
                0 !== o && (e[2 * i] = j(s[o]++, o));
              }
            }
            function W(e) {
              var t;
              for (t = 0; t < l; t++) e.dyn_ltree[2 * t] = 0;
              for (t = 0; t < f; t++) e.dyn_dtree[2 * t] = 0;
              for (t = 0; t < c; t++) e.bl_tree[2 * t] = 0;
              e.dyn_ltree[2 * m] = 1, e.opt_len = e.static_len = 0, e.last_lit = e.matches = 0;
            }
            function M(e) {
              8 < e.bi_valid ? U(e, e.bi_buf) : 0 < e.bi_valid && (e.pending_buf[e.pending++] = e.bi_buf), e.bi_buf = 0, e.bi_valid = 0;
            }
            function H(e, t, r, n) {
              var i = 2 * t,
                s = 2 * r;
              return e[i] < e[s] || e[i] === e[s] && n[t] <= n[r];
            }
            function G(e, t, r) {
              for (var n = e.heap[r], i = r << 1; i <= e.heap_len && (i < e.heap_len && H(t, e.heap[i + 1], e.heap[i], e.depth) && i++, !H(t, n, e.heap[i], e.depth));) e.heap[r] = e.heap[i], r = i, i <<= 1;
              e.heap[r] = n;
            }
            function K(e, t, r) {
              var n,
                i,
                s,
                a,
                o = 0;
              if (0 !== e.last_lit) for (; n = e.pending_buf[e.d_buf + 2 * o] << 8 | e.pending_buf[e.d_buf + 2 * o + 1], i = e.pending_buf[e.l_buf + o], o++, 0 === n ? L(e, i, t) : (L(e, (s = A[i]) + u + 1, t), 0 !== (a = w[s]) && P(e, i -= I[s], a), L(e, s = N(--n), r), 0 !== (a = k[s]) && P(e, n -= T[s], a)), o < e.last_lit;);
              L(e, m, t);
            }
            function Y(e, t) {
              var r,
                n,
                i,
                s = t.dyn_tree,
                a = t.stat_desc.static_tree,
                o = t.stat_desc.has_stree,
                h = t.stat_desc.elems,
                u = -1;
              for (e.heap_len = 0, e.heap_max = _, r = 0; r < h; r++) 0 !== s[2 * r] ? (e.heap[++e.heap_len] = u = r, e.depth[r] = 0) : s[2 * r + 1] = 0;
              for (; e.heap_len < 2;) s[2 * (i = e.heap[++e.heap_len] = u < 2 ? ++u : 0)] = 1, e.depth[i] = 0, e.opt_len--, o && (e.static_len -= a[2 * i + 1]);
              for (t.max_code = u, r = e.heap_len >> 1; 1 <= r; r--) G(e, s, r);
              for (i = h; r = e.heap[1], e.heap[1] = e.heap[e.heap_len--], G(e, s, 1), n = e.heap[1], e.heap[--e.heap_max] = r, e.heap[--e.heap_max] = n, s[2 * i] = s[2 * r] + s[2 * n], e.depth[i] = (e.depth[r] >= e.depth[n] ? e.depth[r] : e.depth[n]) + 1, s[2 * r + 1] = s[2 * n + 1] = i, e.heap[1] = i++, G(e, s, 1), 2 <= e.heap_len;);
              e.heap[--e.heap_max] = e.heap[1], function (e, t) {
                var r,
                  n,
                  i,
                  s,
                  a,
                  o,
                  h = t.dyn_tree,
                  u = t.max_code,
                  l = t.stat_desc.static_tree,
                  f = t.stat_desc.has_stree,
                  c = t.stat_desc.extra_bits,
                  d = t.stat_desc.extra_base,
                  p = t.stat_desc.max_length,
                  m = 0;
                for (s = 0; s <= g; s++) e.bl_count[s] = 0;
                for (h[2 * e.heap[e.heap_max] + 1] = 0, r = e.heap_max + 1; r < _; r++) p < (s = h[2 * h[2 * (n = e.heap[r]) + 1] + 1] + 1) && (s = p, m++), h[2 * n + 1] = s, u < n || (e.bl_count[s]++, a = 0, d <= n && (a = c[n - d]), o = h[2 * n], e.opt_len += o * (s + a), f && (e.static_len += o * (l[2 * n + 1] + a)));
                if (0 !== m) {
                  do {
                    for (s = p - 1; 0 === e.bl_count[s];) s--;
                    e.bl_count[s]--, e.bl_count[s + 1] += 2, e.bl_count[p]--, m -= 2;
                  } while (0 < m);
                  for (s = p; 0 !== s; s--) for (n = e.bl_count[s]; 0 !== n;) u < (i = e.heap[--r]) || (h[2 * i + 1] !== s && (e.opt_len += (s - h[2 * i + 1]) * h[2 * i], h[2 * i + 1] = s), n--);
                }
              }(e, t), Z(s, u, e.bl_count);
            }
            function X(e, t, r) {
              var n,
                i,
                s = -1,
                a = t[1],
                o = 0,
                h = 7,
                u = 4;
              for (0 === a && (h = 138, u = 3), t[2 * (r + 1) + 1] = 65535, n = 0; n <= r; n++) i = a, a = t[2 * (n + 1) + 1], ++o < h && i === a || (o < u ? e.bl_tree[2 * i] += o : 0 !== i ? (i !== s && e.bl_tree[2 * i]++, e.bl_tree[2 * b]++) : o <= 10 ? e.bl_tree[2 * v]++ : e.bl_tree[2 * y]++, s = i, u = (o = 0) === a ? (h = 138, 3) : i === a ? (h = 6, 3) : (h = 7, 4));
            }
            function V(e, t, r) {
              var n,
                i,
                s = -1,
                a = t[1],
                o = 0,
                h = 7,
                u = 4;
              for (0 === a && (h = 138, u = 3), n = 0; n <= r; n++) if (i = a, a = t[2 * (n + 1) + 1], !(++o < h && i === a)) {
                if (o < u) for (; L(e, i, e.bl_tree), 0 != --o;);else 0 !== i ? (i !== s && (L(e, i, e.bl_tree), o--), L(e, b, e.bl_tree), P(e, o - 3, 2)) : o <= 10 ? (L(e, v, e.bl_tree), P(e, o - 3, 3)) : (L(e, y, e.bl_tree), P(e, o - 11, 7));
                s = i, u = (o = 0) === a ? (h = 138, 3) : i === a ? (h = 6, 3) : (h = 7, 4);
              }
            }
            n(T);
            var q = !1;
            function J(e, t, r, n) {
              P(e, (s << 1) + (n ? 1 : 0), 3), function (e, t, r, n) {
                M(e), n && (U(e, r), U(e, ~r)), i.arraySet(e.pending_buf, e.window, t, r, e.pending), e.pending += r;
              }(e, t, r, !0);
            }
            r._tr_init = function (e) {
              q || (function () {
                var e,
                  t,
                  r,
                  n,
                  i,
                  s = new Array(g + 1);
                for (n = r = 0; n < a - 1; n++) for (I[n] = r, e = 0; e < 1 << w[n]; e++) A[r++] = n;
                for (A[r - 1] = n, n = i = 0; n < 16; n++) for (T[n] = i, e = 0; e < 1 << k[n]; e++) E[i++] = n;
                for (i >>= 7; n < f; n++) for (T[n] = i << 7, e = 0; e < 1 << k[n] - 7; e++) E[256 + i++] = n;
                for (t = 0; t <= g; t++) s[t] = 0;
                for (e = 0; e <= 143;) z[2 * e + 1] = 8, e++, s[8]++;
                for (; e <= 255;) z[2 * e + 1] = 9, e++, s[9]++;
                for (; e <= 279;) z[2 * e + 1] = 7, e++, s[7]++;
                for (; e <= 287;) z[2 * e + 1] = 8, e++, s[8]++;
                for (Z(z, l + 1, s), e = 0; e < f; e++) C[2 * e + 1] = 5, C[2 * e] = j(e, 5);
                O = new D(z, w, u + 1, l, g), B = new D(C, k, 0, f, g), R = new D(new Array(0), x, 0, c, p);
              }(), q = !0), e.l_desc = new F(e.dyn_ltree, O), e.d_desc = new F(e.dyn_dtree, B), e.bl_desc = new F(e.bl_tree, R), e.bi_buf = 0, e.bi_valid = 0, W(e);
            }, r._tr_stored_block = J, r._tr_flush_block = function (e, t, r, n) {
              var i,
                s,
                a = 0;
              0 < e.level ? (2 === e.strm.data_type && (e.strm.data_type = function (e) {
                var t,
                  r = 4093624447;
                for (t = 0; t <= 31; t++, r >>>= 1) if (1 & r && 0 !== e.dyn_ltree[2 * t]) return o;
                if (0 !== e.dyn_ltree[18] || 0 !== e.dyn_ltree[20] || 0 !== e.dyn_ltree[26]) return h;
                for (t = 32; t < u; t++) if (0 !== e.dyn_ltree[2 * t]) return h;
                return o;
              }(e)), Y(e, e.l_desc), Y(e, e.d_desc), a = function (e) {
                var t;
                for (X(e, e.dyn_ltree, e.l_desc.max_code), X(e, e.dyn_dtree, e.d_desc.max_code), Y(e, e.bl_desc), t = c - 1; 3 <= t && 0 === e.bl_tree[2 * S[t] + 1]; t--);
                return e.opt_len += 3 * (t + 1) + 5 + 5 + 4, t;
              }(e), i = e.opt_len + 3 + 7 >>> 3, (s = e.static_len + 3 + 7 >>> 3) <= i && (i = s)) : i = s = r + 5, r + 4 <= i && -1 !== t ? J(e, t, r, n) : 4 === e.strategy || s === i ? (P(e, 2 + (n ? 1 : 0), 3), K(e, z, C)) : (P(e, 4 + (n ? 1 : 0), 3), function (e, t, r, n) {
                var i;
                for (P(e, t - 257, 5), P(e, r - 1, 5), P(e, n - 4, 4), i = 0; i < n; i++) P(e, e.bl_tree[2 * S[i] + 1], 3);
                V(e, e.dyn_ltree, t - 1), V(e, e.dyn_dtree, r - 1);
              }(e, e.l_desc.max_code + 1, e.d_desc.max_code + 1, a + 1), K(e, e.dyn_ltree, e.dyn_dtree)), W(e), n && M(e);
            }, r._tr_tally = function (e, t, r) {
              return e.pending_buf[e.d_buf + 2 * e.last_lit] = t >>> 8 & 255, e.pending_buf[e.d_buf + 2 * e.last_lit + 1] = 255 & t, e.pending_buf[e.l_buf + e.last_lit] = 255 & r, e.last_lit++, 0 === t ? e.dyn_ltree[2 * r]++ : (e.matches++, t--, e.dyn_ltree[2 * (A[r] + u + 1)]++, e.dyn_dtree[2 * N(t)]++), e.last_lit === e.lit_bufsize - 1;
            }, r._tr_align = function (e) {
              P(e, 2, 3), L(e, m, z), function (e) {
                16 === e.bi_valid ? (U(e, e.bi_buf), e.bi_buf = 0, e.bi_valid = 0) : 8 <= e.bi_valid && (e.pending_buf[e.pending++] = 255 & e.bi_buf, e.bi_buf >>= 8, e.bi_valid -= 8);
              }(e);
            };
          }, {
            "../utils/common": 41
          }],
          53: [function (e, t, r) {

            t.exports = function () {
              this.input = null, this.next_in = 0, this.avail_in = 0, this.total_in = 0, this.output = null, this.next_out = 0, this.avail_out = 0, this.total_out = 0, this.msg = "", this.state = null, this.data_type = 2, this.adler = 0;
            };
          }, {}],
          54: [function (e, t, r) {
            (function (e) {
              !function (r, n) {

                if (!r.setImmediate) {
                  var i,
                    s,
                    t,
                    a,
                    o = 1,
                    h = {},
                    u = !1,
                    l = r.document,
                    e = Object.getPrototypeOf && Object.getPrototypeOf(r);
                  e = e && e.setTimeout ? e : r, i = "[object process]" === {}.toString.call(r.process) ? function (e) {
                    process.nextTick(function () {
                      c(e);
                    });
                  } : function () {
                    if (r.postMessage && !r.importScripts) {
                      var e = !0,
                        t = r.onmessage;
                      return r.onmessage = function () {
                        e = !1;
                      }, r.postMessage("", "*"), r.onmessage = t, e;
                    }
                  }() ? (a = "setImmediate$" + Math.random() + "$", r.addEventListener ? r.addEventListener("message", d, !1) : r.attachEvent("onmessage", d), function (e) {
                    r.postMessage(a + e, "*");
                  }) : r.MessageChannel ? ((t = new MessageChannel()).port1.onmessage = function (e) {
                    c(e.data);
                  }, function (e) {
                    t.port2.postMessage(e);
                  }) : l && "onreadystatechange" in l.createElement("script") ? (s = l.documentElement, function (e) {
                    var t = l.createElement("script");
                    t.onreadystatechange = function () {
                      c(e), t.onreadystatechange = null, s.removeChild(t), t = null;
                    }, s.appendChild(t);
                  }) : function (e) {
                    setTimeout(c, 0, e);
                  }, e.setImmediate = function (e) {
                    "function" != typeof e && (e = new Function("" + e));
                    for (var t = new Array(arguments.length - 1), r = 0; r < t.length; r++) t[r] = arguments[r + 1];
                    var n = {
                      callback: e,
                      args: t
                    };
                    return h[o] = n, i(o), o++;
                  }, e.clearImmediate = f;
                }
                function f(e) {
                  delete h[e];
                }
                function c(e) {
                  if (u) setTimeout(c, 0, e);else {
                    var t = h[e];
                    if (t) {
                      u = !0;
                      try {
                        !function (e) {
                          var t = e.callback,
                            r = e.args;
                          switch (r.length) {
                            case 0:
                              t();
                              break;
                            case 1:
                              t(r[0]);
                              break;
                            case 2:
                              t(r[0], r[1]);
                              break;
                            case 3:
                              t(r[0], r[1], r[2]);
                              break;
                            default:
                              t.apply(n, r);
                          }
                        }(t);
                      } finally {
                        f(e), u = !1;
                      }
                    }
                  }
                }
                function d(e) {
                  e.source === r && "string" == typeof e.data && 0 === e.data.indexOf(a) && c(+e.data.slice(a.length));
                }
              }("undefined" == typeof self ? void 0 === e ? this : e : self);
            }).call(this, "undefined" != typeof commonjsGlobal ? commonjsGlobal : "undefined" != typeof self ? self : "undefined" != typeof window ? window : {});
          }, {}]
        }, {}, [10])(10);
      });
    })(jszip_min);
    var JSZip = jszip_min.exports;

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

    (function (module, exports) {
      var global = typeof self !== 'undefined' ? self : commonjsGlobal;
      var __self__ = function () {
        function F() {
          this.fetch = false;
          this.DOMException = global.DOMException;
        }
        F.prototype = global;
        return new F();
      }();
      (function (self) {
        (function (exports) {
          var support = {
            searchParams: 'URLSearchParams' in self,
            iterable: 'Symbol' in self && 'iterator' in Symbol,
            blob: 'FileReader' in self && 'Blob' in self && function () {
              try {
                new Blob();
                return true;
              } catch (e) {
                return false;
              }
            }(),
            formData: 'FormData' in self,
            arrayBuffer: 'ArrayBuffer' in self
          };
          function isDataView(obj) {
            return obj && DataView.prototype.isPrototypeOf(obj);
          }
          if (support.arrayBuffer) {
            var viewClasses = ['[object Int8Array]', '[object Uint8Array]', '[object Uint8ClampedArray]', '[object Int16Array]', '[object Uint16Array]', '[object Int32Array]', '[object Uint32Array]', '[object Float32Array]', '[object Float64Array]'];
            var isArrayBufferView = ArrayBuffer.isView || function (obj) {
              return obj && viewClasses.indexOf(Object.prototype.toString.call(obj)) > -1;
            };
          }
          function normalizeName(name) {
            if (typeof name !== 'string') {
              name = String(name);
            }
            if (/[^a-z0-9\-#$%&'*+.^_`|~]/i.test(name)) {
              throw new TypeError('Invalid character in header field name');
            }
            return name.toLowerCase();
          }
          function normalizeValue(value) {
            if (typeof value !== 'string') {
              value = String(value);
            }
            return value;
          }

          // Build a destructive iterator for the value list
          function iteratorFor(items) {
            var iterator = {
              next: function () {
                var value = items.shift();
                return {
                  done: value === undefined,
                  value: value
                };
              }
            };
            if (support.iterable) {
              iterator[Symbol.iterator] = function () {
                return iterator;
              };
            }
            return iterator;
          }
          function Headers(headers) {
            this.map = {};
            if (headers instanceof Headers) {
              headers.forEach(function (value, name) {
                this.append(name, value);
              }, this);
            } else if (Array.isArray(headers)) {
              headers.forEach(function (header) {
                this.append(header[0], header[1]);
              }, this);
            } else if (headers) {
              Object.getOwnPropertyNames(headers).forEach(function (name) {
                this.append(name, headers[name]);
              }, this);
            }
          }
          Headers.prototype.append = function (name, value) {
            name = normalizeName(name);
            value = normalizeValue(value);
            var oldValue = this.map[name];
            this.map[name] = oldValue ? oldValue + ', ' + value : value;
          };
          Headers.prototype['delete'] = function (name) {
            delete this.map[normalizeName(name)];
          };
          Headers.prototype.get = function (name) {
            name = normalizeName(name);
            return this.has(name) ? this.map[name] : null;
          };
          Headers.prototype.has = function (name) {
            return this.map.hasOwnProperty(normalizeName(name));
          };
          Headers.prototype.set = function (name, value) {
            this.map[normalizeName(name)] = normalizeValue(value);
          };
          Headers.prototype.forEach = function (callback, thisArg) {
            for (var name in this.map) {
              if (this.map.hasOwnProperty(name)) {
                callback.call(thisArg, this.map[name], name, this);
              }
            }
          };
          Headers.prototype.keys = function () {
            var items = [];
            this.forEach(function (value, name) {
              items.push(name);
            });
            return iteratorFor(items);
          };
          Headers.prototype.values = function () {
            var items = [];
            this.forEach(function (value) {
              items.push(value);
            });
            return iteratorFor(items);
          };
          Headers.prototype.entries = function () {
            var items = [];
            this.forEach(function (value, name) {
              items.push([name, value]);
            });
            return iteratorFor(items);
          };
          if (support.iterable) {
            Headers.prototype[Symbol.iterator] = Headers.prototype.entries;
          }
          function consumed(body) {
            if (body.bodyUsed) {
              return Promise.reject(new TypeError('Already read'));
            }
            body.bodyUsed = true;
          }
          function fileReaderReady(reader) {
            return new Promise(function (resolve, reject) {
              reader.onload = function () {
                resolve(reader.result);
              };
              reader.onerror = function () {
                reject(reader.error);
              };
            });
          }
          function readBlobAsArrayBuffer(blob) {
            var reader = new FileReader();
            var promise = fileReaderReady(reader);
            reader.readAsArrayBuffer(blob);
            return promise;
          }
          function readBlobAsText(blob) {
            var reader = new FileReader();
            var promise = fileReaderReady(reader);
            reader.readAsText(blob);
            return promise;
          }
          function readArrayBufferAsText(buf) {
            var view = new Uint8Array(buf);
            var chars = new Array(view.length);
            for (var i = 0; i < view.length; i++) {
              chars[i] = String.fromCharCode(view[i]);
            }
            return chars.join('');
          }
          function bufferClone(buf) {
            if (buf.slice) {
              return buf.slice(0);
            } else {
              var view = new Uint8Array(buf.byteLength);
              view.set(new Uint8Array(buf));
              return view.buffer;
            }
          }
          function Body() {
            this.bodyUsed = false;
            this._initBody = function (body) {
              this._bodyInit = body;
              if (!body) {
                this._bodyText = '';
              } else if (typeof body === 'string') {
                this._bodyText = body;
              } else if (support.blob && Blob.prototype.isPrototypeOf(body)) {
                this._bodyBlob = body;
              } else if (support.formData && FormData.prototype.isPrototypeOf(body)) {
                this._bodyFormData = body;
              } else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) {
                this._bodyText = body.toString();
              } else if (support.arrayBuffer && support.blob && isDataView(body)) {
                this._bodyArrayBuffer = bufferClone(body.buffer);
                // IE 10-11 can't handle a DataView body.
                this._bodyInit = new Blob([this._bodyArrayBuffer]);
              } else if (support.arrayBuffer && (ArrayBuffer.prototype.isPrototypeOf(body) || isArrayBufferView(body))) {
                this._bodyArrayBuffer = bufferClone(body);
              } else {
                this._bodyText = body = Object.prototype.toString.call(body);
              }
              if (!this.headers.get('content-type')) {
                if (typeof body === 'string') {
                  this.headers.set('content-type', 'text/plain;charset=UTF-8');
                } else if (this._bodyBlob && this._bodyBlob.type) {
                  this.headers.set('content-type', this._bodyBlob.type);
                } else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) {
                  this.headers.set('content-type', 'application/x-www-form-urlencoded;charset=UTF-8');
                }
              }
            };
            if (support.blob) {
              this.blob = function () {
                var rejected = consumed(this);
                if (rejected) {
                  return rejected;
                }
                if (this._bodyBlob) {
                  return Promise.resolve(this._bodyBlob);
                } else if (this._bodyArrayBuffer) {
                  return Promise.resolve(new Blob([this._bodyArrayBuffer]));
                } else if (this._bodyFormData) {
                  throw new Error('could not read FormData body as blob');
                } else {
                  return Promise.resolve(new Blob([this._bodyText]));
                }
              };
              this.arrayBuffer = function () {
                if (this._bodyArrayBuffer) {
                  return consumed(this) || Promise.resolve(this._bodyArrayBuffer);
                } else {
                  return this.blob().then(readBlobAsArrayBuffer);
                }
              };
            }
            this.text = function () {
              var rejected = consumed(this);
              if (rejected) {
                return rejected;
              }
              if (this._bodyBlob) {
                return readBlobAsText(this._bodyBlob);
              } else if (this._bodyArrayBuffer) {
                return Promise.resolve(readArrayBufferAsText(this._bodyArrayBuffer));
              } else if (this._bodyFormData) {
                throw new Error('could not read FormData body as text');
              } else {
                return Promise.resolve(this._bodyText);
              }
            };
            if (support.formData) {
              this.formData = function () {
                return this.text().then(decode);
              };
            }
            this.json = function () {
              return this.text().then(JSON.parse);
            };
            return this;
          }

          // HTTP methods whose capitalization should be normalized
          var methods = ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST', 'PUT'];
          function normalizeMethod(method) {
            var upcased = method.toUpperCase();
            return methods.indexOf(upcased) > -1 ? upcased : method;
          }
          function Request(input, options) {
            options = options || {};
            var body = options.body;
            if (input instanceof Request) {
              if (input.bodyUsed) {
                throw new TypeError('Already read');
              }
              this.url = input.url;
              this.credentials = input.credentials;
              if (!options.headers) {
                this.headers = new Headers(input.headers);
              }
              this.method = input.method;
              this.mode = input.mode;
              this.signal = input.signal;
              if (!body && input._bodyInit != null) {
                body = input._bodyInit;
                input.bodyUsed = true;
              }
            } else {
              this.url = String(input);
            }
            this.credentials = options.credentials || this.credentials || 'same-origin';
            if (options.headers || !this.headers) {
              this.headers = new Headers(options.headers);
            }
            this.method = normalizeMethod(options.method || this.method || 'GET');
            this.mode = options.mode || this.mode || null;
            this.signal = options.signal || this.signal;
            this.referrer = null;
            if ((this.method === 'GET' || this.method === 'HEAD') && body) {
              throw new TypeError('Body not allowed for GET or HEAD requests');
            }
            this._initBody(body);
          }
          Request.prototype.clone = function () {
            return new Request(this, {
              body: this._bodyInit
            });
          };
          function decode(body) {
            var form = new FormData();
            body.trim().split('&').forEach(function (bytes) {
              if (bytes) {
                var split = bytes.split('=');
                var name = split.shift().replace(/\+/g, ' ');
                var value = split.join('=').replace(/\+/g, ' ');
                form.append(decodeURIComponent(name), decodeURIComponent(value));
              }
            });
            return form;
          }
          function parseHeaders(rawHeaders) {
            var headers = new Headers();
            // Replace instances of \r\n and \n followed by at least one space or horizontal tab with a space
            // https://tools.ietf.org/html/rfc7230#section-3.2
            var preProcessedHeaders = rawHeaders.replace(/\r?\n[\t ]+/g, ' ');
            preProcessedHeaders.split(/\r?\n/).forEach(function (line) {
              var parts = line.split(':');
              var key = parts.shift().trim();
              if (key) {
                var value = parts.join(':').trim();
                headers.append(key, value);
              }
            });
            return headers;
          }
          Body.call(Request.prototype);
          function Response(bodyInit, options) {
            if (!options) {
              options = {};
            }
            this.type = 'default';
            this.status = options.status === undefined ? 200 : options.status;
            this.ok = this.status >= 200 && this.status < 300;
            this.statusText = 'statusText' in options ? options.statusText : 'OK';
            this.headers = new Headers(options.headers);
            this.url = options.url || '';
            this._initBody(bodyInit);
          }
          Body.call(Response.prototype);
          Response.prototype.clone = function () {
            return new Response(this._bodyInit, {
              status: this.status,
              statusText: this.statusText,
              headers: new Headers(this.headers),
              url: this.url
            });
          };
          Response.error = function () {
            var response = new Response(null, {
              status: 0,
              statusText: ''
            });
            response.type = 'error';
            return response;
          };
          var redirectStatuses = [301, 302, 303, 307, 308];
          Response.redirect = function (url, status) {
            if (redirectStatuses.indexOf(status) === -1) {
              throw new RangeError('Invalid status code');
            }
            return new Response(null, {
              status: status,
              headers: {
                location: url
              }
            });
          };
          exports.DOMException = self.DOMException;
          try {
            new exports.DOMException();
          } catch (err) {
            exports.DOMException = function (message, name) {
              this.message = message;
              this.name = name;
              var error = Error(message);
              this.stack = error.stack;
            };
            exports.DOMException.prototype = Object.create(Error.prototype);
            exports.DOMException.prototype.constructor = exports.DOMException;
          }
          function fetch(input, init) {
            return new Promise(function (resolve, reject) {
              var request = new Request(input, init);
              if (request.signal && request.signal.aborted) {
                return reject(new exports.DOMException('Aborted', 'AbortError'));
              }
              var xhr = new XMLHttpRequest();
              function abortXhr() {
                xhr.abort();
              }
              xhr.onload = function () {
                var options = {
                  status: xhr.status,
                  statusText: xhr.statusText,
                  headers: parseHeaders(xhr.getAllResponseHeaders() || '')
                };
                options.url = 'responseURL' in xhr ? xhr.responseURL : options.headers.get('X-Request-URL');
                var body = 'response' in xhr ? xhr.response : xhr.responseText;
                resolve(new Response(body, options));
              };
              xhr.onerror = function () {
                reject(new TypeError('Network request failed'));
              };
              xhr.ontimeout = function () {
                reject(new TypeError('Network request failed'));
              };
              xhr.onabort = function () {
                reject(new exports.DOMException('Aborted', 'AbortError'));
              };
              xhr.open(request.method, request.url, true);
              if (request.credentials === 'include') {
                xhr.withCredentials = true;
              } else if (request.credentials === 'omit') {
                xhr.withCredentials = false;
              }
              if ('responseType' in xhr && support.blob) {
                xhr.responseType = 'blob';
              }
              request.headers.forEach(function (value, name) {
                xhr.setRequestHeader(name, value);
              });
              if (request.signal) {
                request.signal.addEventListener('abort', abortXhr);
                xhr.onreadystatechange = function () {
                  // DONE (success or failure)
                  if (xhr.readyState === 4) {
                    request.signal.removeEventListener('abort', abortXhr);
                  }
                };
              }
              xhr.send(typeof request._bodyInit === 'undefined' ? null : request._bodyInit);
            });
          }
          fetch.polyfill = true;
          if (!self.fetch) {
            self.fetch = fetch;
            self.Headers = Headers;
            self.Request = Request;
            self.Response = Response;
          }
          exports.Headers = Headers;
          exports.Request = Request;
          exports.Response = Response;
          exports.fetch = fetch;
          Object.defineProperty(exports, '__esModule', {
            value: true
          });
          return exports;
        })({});
      })(__self__);
      __self__.fetch.ponyfill = true;
      // Remove "polyfill" property added by whatwg-fetch
      delete __self__.fetch.polyfill;
      // Choose between native implementation (global) or custom implementation (__self__)
      // var ctx = global.fetch ? global : __self__;
      var ctx = __self__; // this line disable service worker support temporarily
      exports = ctx.fetch; // To enable: import fetch from 'cross-fetch'
      exports.default = ctx.fetch; // For TypeScript consumers without esModuleInterop.
      exports.fetch = ctx.fetch; // To enable: import {fetch} from 'cross-fetch'
      exports.Headers = ctx.Headers;
      exports.Request = ctx.Request;
      exports.Response = ctx.Response;
      module.exports = exports;
    })(browserPonyfill$1, browserPonyfill$1.exports);
    var crossFetch$1 = /*@__PURE__*/getDefaultExportFromCjs(browserPonyfill$1.exports);

    async function fetchArrayBuffer(url) {
      const result = await crossFetch$1(url);
      return result.arrayBuffer();
    }

    const loadingPromises$1 = {};
    async function loadCommercials() {
      let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
      const {
        url = 'https://couch.cheminfo.org/cheminfo-public/d2eb480198c80275a1d05dd3609414f9/upload/commercials.zip'
      } = options;
      if (!loadingPromises$1[url]) {
        loadingPromises$1[url] = fetchArrayBuffer(url);
      }
      const buffer = await loadingPromises$1[url];
      const jsZip = new JSZip();
      let zip = await jsZip.loadAsync(buffer);
      let fileData = await zip.files['commercials.json'].async('string');
      let data = JSON.parse(fileData);
      data.sort((a, b) => a.em - b.em);
      return data;
    }

    var papaparse_min = {exports: {}};

    /* @license
    Papa Parse
    v5.4.1
    https://github.com/mholt/PapaParse
    License: MIT
    */
    (function (module, exports) {
      !function (e, t) {
        module.exports = t() ;
      }(commonjsGlobal, function s() {

        var f = "undefined" != typeof self ? self : "undefined" != typeof window ? window : void 0 !== f ? f : {};
        var n = !f.document && !!f.postMessage,
          o = f.IS_PAPA_WORKER || !1,
          a = {},
          u = 0,
          b = {
            parse: function (e, t) {
              var r = (t = t || {}).dynamicTyping || !1;
              J(r) && (t.dynamicTypingFunction = r, r = {});
              if (t.dynamicTyping = r, t.transform = !!J(t.transform) && t.transform, t.worker && b.WORKERS_SUPPORTED) {
                var i = function () {
                  if (!b.WORKERS_SUPPORTED) return !1;
                  var e = (r = f.URL || f.webkitURL || null, i = s.toString(), b.BLOB_URL || (b.BLOB_URL = r.createObjectURL(new Blob(["var global = (function() { if (typeof self !== 'undefined') { return self; } if (typeof window !== 'undefined') { return window; } if (typeof global !== 'undefined') { return global; } return {}; })(); global.IS_PAPA_WORKER=true; ", "(", i, ")();"], {
                      type: "text/javascript"
                    })))),
                    t = new f.Worker(e);
                  var r, i;
                  return t.onmessage = _, t.id = u++, a[t.id] = t;
                }();
                return i.userStep = t.step, i.userChunk = t.chunk, i.userComplete = t.complete, i.userError = t.error, t.step = J(t.step), t.chunk = J(t.chunk), t.complete = J(t.complete), t.error = J(t.error), delete t.worker, void i.postMessage({
                  input: e,
                  config: t,
                  workerId: i.id
                });
              }
              var n = null;
              b.NODE_STREAM_INPUT, "string" == typeof e ? (e = function (e) {
                if (65279 === e.charCodeAt(0)) return e.slice(1);
                return e;
              }(e), n = t.download ? new l(t) : new p(t)) : !0 === e.readable && J(e.read) && J(e.on) ? n = new g(t) : (f.File && e instanceof File || e instanceof Object) && (n = new c(t));
              return n.stream(e);
            },
            unparse: function (e, t) {
              var n = !1,
                _ = !0,
                m = ",",
                y = "\r\n",
                s = '"',
                a = s + s,
                r = !1,
                i = null,
                o = !1;
              !function () {
                if ("object" != typeof t) return;
                "string" != typeof t.delimiter || b.BAD_DELIMITERS.filter(function (e) {
                  return -1 !== t.delimiter.indexOf(e);
                }).length || (m = t.delimiter);
                ("boolean" == typeof t.quotes || "function" == typeof t.quotes || Array.isArray(t.quotes)) && (n = t.quotes);
                "boolean" != typeof t.skipEmptyLines && "string" != typeof t.skipEmptyLines || (r = t.skipEmptyLines);
                "string" == typeof t.newline && (y = t.newline);
                "string" == typeof t.quoteChar && (s = t.quoteChar);
                "boolean" == typeof t.header && (_ = t.header);
                if (Array.isArray(t.columns)) {
                  if (0 === t.columns.length) throw new Error("Option columns is empty");
                  i = t.columns;
                }
                void 0 !== t.escapeChar && (a = t.escapeChar + s);
                ("boolean" == typeof t.escapeFormulae || t.escapeFormulae instanceof RegExp) && (o = t.escapeFormulae instanceof RegExp ? t.escapeFormulae : /^[=+\-@\t\r].*$/);
              }();
              var u = new RegExp(Q(s), "g");
              "string" == typeof e && (e = JSON.parse(e));
              if (Array.isArray(e)) {
                if (!e.length || Array.isArray(e[0])) return h(null, e, r);
                if ("object" == typeof e[0]) return h(i || Object.keys(e[0]), e, r);
              } else if ("object" == typeof e) return "string" == typeof e.data && (e.data = JSON.parse(e.data)), Array.isArray(e.data) && (e.fields || (e.fields = e.meta && e.meta.fields || i), e.fields || (e.fields = Array.isArray(e.data[0]) ? e.fields : "object" == typeof e.data[0] ? Object.keys(e.data[0]) : []), Array.isArray(e.data[0]) || "object" == typeof e.data[0] || (e.data = [e.data])), h(e.fields || [], e.data || [], r);
              throw new Error("Unable to serialize unrecognized input");
              function h(e, t, r) {
                var i = "";
                "string" == typeof e && (e = JSON.parse(e)), "string" == typeof t && (t = JSON.parse(t));
                var n = Array.isArray(e) && 0 < e.length,
                  s = !Array.isArray(t[0]);
                if (n && _) {
                  for (var a = 0; a < e.length; a++) 0 < a && (i += m), i += v(e[a], a);
                  0 < t.length && (i += y);
                }
                for (var o = 0; o < t.length; o++) {
                  var u = n ? e.length : t[o].length,
                    h = !1,
                    f = n ? 0 === Object.keys(t[o]).length : 0 === t[o].length;
                  if (r && !n && (h = "greedy" === r ? "" === t[o].join("").trim() : 1 === t[o].length && 0 === t[o][0].length), "greedy" === r && n) {
                    for (var d = [], l = 0; l < u; l++) {
                      var c = s ? e[l] : l;
                      d.push(t[o][c]);
                    }
                    h = "" === d.join("").trim();
                  }
                  if (!h) {
                    for (var p = 0; p < u; p++) {
                      0 < p && !f && (i += m);
                      var g = n && s ? e[p] : p;
                      i += v(t[o][g], p);
                    }
                    o < t.length - 1 && (!r || 0 < u && !f) && (i += y);
                  }
                }
                return i;
              }
              function v(e, t) {
                if (null == e) return "";
                if (e.constructor === Date) return JSON.stringify(e).slice(1, 25);
                var r = !1;
                o && "string" == typeof e && o.test(e) && (e = "'" + e, r = !0);
                var i = e.toString().replace(u, a);
                return (r = r || !0 === n || "function" == typeof n && n(e, t) || Array.isArray(n) && n[t] || function (e, t) {
                  for (var r = 0; r < t.length; r++) if (-1 < e.indexOf(t[r])) return !0;
                  return !1;
                }(i, b.BAD_DELIMITERS) || -1 < i.indexOf(m) || " " === i.charAt(0) || " " === i.charAt(i.length - 1)) ? s + i + s : i;
              }
            }
          };
        if (b.RECORD_SEP = String.fromCharCode(30), b.UNIT_SEP = String.fromCharCode(31), b.BYTE_ORDER_MARK = "\ufeff", b.BAD_DELIMITERS = ["\r", "\n", '"', b.BYTE_ORDER_MARK], b.WORKERS_SUPPORTED = !n && !!f.Worker, b.NODE_STREAM_INPUT = 1, b.LocalChunkSize = 10485760, b.RemoteChunkSize = 5242880, b.DefaultDelimiter = ",", b.Parser = E, b.ParserHandle = r, b.NetworkStreamer = l, b.FileStreamer = c, b.StringStreamer = p, b.ReadableStreamStreamer = g, f.jQuery) {
          var d = f.jQuery;
          d.fn.parse = function (o) {
            var r = o.config || {},
              u = [];
            return this.each(function (e) {
              if (!("INPUT" === d(this).prop("tagName").toUpperCase() && "file" === d(this).attr("type").toLowerCase() && f.FileReader) || !this.files || 0 === this.files.length) return !0;
              for (var t = 0; t < this.files.length; t++) u.push({
                file: this.files[t],
                inputElem: this,
                instanceConfig: d.extend({}, r)
              });
            }), e(), this;
            function e() {
              if (0 !== u.length) {
                var e,
                  t,
                  r,
                  i,
                  n = u[0];
                if (J(o.before)) {
                  var s = o.before(n.file, n.inputElem);
                  if ("object" == typeof s) {
                    if ("abort" === s.action) return e = "AbortError", t = n.file, r = n.inputElem, i = s.reason, void (J(o.error) && o.error({
                      name: e
                    }, t, r, i));
                    if ("skip" === s.action) return void h();
                    "object" == typeof s.config && (n.instanceConfig = d.extend(n.instanceConfig, s.config));
                  } else if ("skip" === s) return void h();
                }
                var a = n.instanceConfig.complete;
                n.instanceConfig.complete = function (e) {
                  J(a) && a(e, n.file, n.inputElem), h();
                }, b.parse(n.file, n.instanceConfig);
              } else J(o.complete) && o.complete();
            }
            function h() {
              u.splice(0, 1), e();
            }
          };
        }
        function h(e) {
          this._handle = null, this._finished = !1, this._completed = !1, this._halted = !1, this._input = null, this._baseIndex = 0, this._partialLine = "", this._rowCount = 0, this._start = 0, this._nextChunk = null, this.isFirstChunk = !0, this._completeResults = {
            data: [],
            errors: [],
            meta: {}
          }, function (e) {
            var t = w(e);
            t.chunkSize = parseInt(t.chunkSize), e.step || e.chunk || (t.chunkSize = null);
            this._handle = new r(t), (this._handle.streamer = this)._config = t;
          }.call(this, e), this.parseChunk = function (e, t) {
            if (this.isFirstChunk && J(this._config.beforeFirstChunk)) {
              var r = this._config.beforeFirstChunk(e);
              void 0 !== r && (e = r);
            }
            this.isFirstChunk = !1, this._halted = !1;
            var i = this._partialLine + e;
            this._partialLine = "";
            var n = this._handle.parse(i, this._baseIndex, !this._finished);
            if (!this._handle.paused() && !this._handle.aborted()) {
              var s = n.meta.cursor;
              this._finished || (this._partialLine = i.substring(s - this._baseIndex), this._baseIndex = s), n && n.data && (this._rowCount += n.data.length);
              var a = this._finished || this._config.preview && this._rowCount >= this._config.preview;
              if (o) f.postMessage({
                results: n,
                workerId: b.WORKER_ID,
                finished: a
              });else if (J(this._config.chunk) && !t) {
                if (this._config.chunk(n, this._handle), this._handle.paused() || this._handle.aborted()) return void (this._halted = !0);
                n = void 0, this._completeResults = void 0;
              }
              return this._config.step || this._config.chunk || (this._completeResults.data = this._completeResults.data.concat(n.data), this._completeResults.errors = this._completeResults.errors.concat(n.errors), this._completeResults.meta = n.meta), this._completed || !a || !J(this._config.complete) || n && n.meta.aborted || (this._config.complete(this._completeResults, this._input), this._completed = !0), a || n && n.meta.paused || this._nextChunk(), n;
            }
            this._halted = !0;
          }, this._sendError = function (e) {
            J(this._config.error) ? this._config.error(e) : o && this._config.error && f.postMessage({
              workerId: b.WORKER_ID,
              error: e,
              finished: !1
            });
          };
        }
        function l(e) {
          var i;
          (e = e || {}).chunkSize || (e.chunkSize = b.RemoteChunkSize), h.call(this, e), this._nextChunk = n ? function () {
            this._readChunk(), this._chunkLoaded();
          } : function () {
            this._readChunk();
          }, this.stream = function (e) {
            this._input = e, this._nextChunk();
          }, this._readChunk = function () {
            if (this._finished) this._chunkLoaded();else {
              if (i = new XMLHttpRequest(), this._config.withCredentials && (i.withCredentials = this._config.withCredentials), n || (i.onload = v(this._chunkLoaded, this), i.onerror = v(this._chunkError, this)), i.open(this._config.downloadRequestBody ? "POST" : "GET", this._input, !n), this._config.downloadRequestHeaders) {
                var e = this._config.downloadRequestHeaders;
                for (var t in e) i.setRequestHeader(t, e[t]);
              }
              if (this._config.chunkSize) {
                var r = this._start + this._config.chunkSize - 1;
                i.setRequestHeader("Range", "bytes=" + this._start + "-" + r);
              }
              try {
                i.send(this._config.downloadRequestBody);
              } catch (e) {
                this._chunkError(e.message);
              }
              n && 0 === i.status && this._chunkError();
            }
          }, this._chunkLoaded = function () {
            4 === i.readyState && (i.status < 200 || 400 <= i.status ? this._chunkError() : (this._start += this._config.chunkSize ? this._config.chunkSize : i.responseText.length, this._finished = !this._config.chunkSize || this._start >= function (e) {
              var t = e.getResponseHeader("Content-Range");
              if (null === t) return -1;
              return parseInt(t.substring(t.lastIndexOf("/") + 1));
            }(i), this.parseChunk(i.responseText)));
          }, this._chunkError = function (e) {
            var t = i.statusText || e;
            this._sendError(new Error(t));
          };
        }
        function c(e) {
          var i, n;
          (e = e || {}).chunkSize || (e.chunkSize = b.LocalChunkSize), h.call(this, e);
          var s = "undefined" != typeof FileReader;
          this.stream = function (e) {
            this._input = e, n = e.slice || e.webkitSlice || e.mozSlice, s ? ((i = new FileReader()).onload = v(this._chunkLoaded, this), i.onerror = v(this._chunkError, this)) : i = new FileReaderSync(), this._nextChunk();
          }, this._nextChunk = function () {
            this._finished || this._config.preview && !(this._rowCount < this._config.preview) || this._readChunk();
          }, this._readChunk = function () {
            var e = this._input;
            if (this._config.chunkSize) {
              var t = Math.min(this._start + this._config.chunkSize, this._input.size);
              e = n.call(e, this._start, t);
            }
            var r = i.readAsText(e, this._config.encoding);
            s || this._chunkLoaded({
              target: {
                result: r
              }
            });
          }, this._chunkLoaded = function (e) {
            this._start += this._config.chunkSize, this._finished = !this._config.chunkSize || this._start >= this._input.size, this.parseChunk(e.target.result);
          }, this._chunkError = function () {
            this._sendError(i.error);
          };
        }
        function p(e) {
          var r;
          h.call(this, e = e || {}), this.stream = function (e) {
            return r = e, this._nextChunk();
          }, this._nextChunk = function () {
            if (!this._finished) {
              var e,
                t = this._config.chunkSize;
              return t ? (e = r.substring(0, t), r = r.substring(t)) : (e = r, r = ""), this._finished = !r, this.parseChunk(e);
            }
          };
        }
        function g(e) {
          h.call(this, e = e || {});
          var t = [],
            r = !0,
            i = !1;
          this.pause = function () {
            h.prototype.pause.apply(this, arguments), this._input.pause();
          }, this.resume = function () {
            h.prototype.resume.apply(this, arguments), this._input.resume();
          }, this.stream = function (e) {
            this._input = e, this._input.on("data", this._streamData), this._input.on("end", this._streamEnd), this._input.on("error", this._streamError);
          }, this._checkIsFinished = function () {
            i && 1 === t.length && (this._finished = !0);
          }, this._nextChunk = function () {
            this._checkIsFinished(), t.length ? this.parseChunk(t.shift()) : r = !0;
          }, this._streamData = v(function (e) {
            try {
              t.push("string" == typeof e ? e : e.toString(this._config.encoding)), r && (r = !1, this._checkIsFinished(), this.parseChunk(t.shift()));
            } catch (e) {
              this._streamError(e);
            }
          }, this), this._streamError = v(function (e) {
            this._streamCleanUp(), this._sendError(e);
          }, this), this._streamEnd = v(function () {
            this._streamCleanUp(), i = !0, this._streamData("");
          }, this), this._streamCleanUp = v(function () {
            this._input.removeListener("data", this._streamData), this._input.removeListener("end", this._streamEnd), this._input.removeListener("error", this._streamError);
          }, this);
        }
        function r(m) {
          var a,
            o,
            u,
            i = Math.pow(2, 53),
            n = -i,
            s = /^\s*-?(\d+\.?|\.\d+|\d+\.\d+)([eE][-+]?\d+)?\s*$/,
            h = /^((\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z)))$/,
            t = this,
            r = 0,
            f = 0,
            d = !1,
            e = !1,
            l = [],
            c = {
              data: [],
              errors: [],
              meta: {}
            };
          if (J(m.step)) {
            var p = m.step;
            m.step = function (e) {
              if (c = e, _()) g();else {
                if (g(), 0 === c.data.length) return;
                r += e.data.length, m.preview && r > m.preview ? o.abort() : (c.data = c.data[0], p(c, t));
              }
            };
          }
          function y(e) {
            return "greedy" === m.skipEmptyLines ? "" === e.join("").trim() : 1 === e.length && 0 === e[0].length;
          }
          function g() {
            return c && u && (k("Delimiter", "UndetectableDelimiter", "Unable to auto-detect delimiting character; defaulted to '" + b.DefaultDelimiter + "'"), u = !1), m.skipEmptyLines && (c.data = c.data.filter(function (e) {
              return !y(e);
            })), _() && function () {
              if (!c) return;
              function e(e, t) {
                J(m.transformHeader) && (e = m.transformHeader(e, t)), l.push(e);
              }
              if (Array.isArray(c.data[0])) {
                for (var t = 0; _() && t < c.data.length; t++) c.data[t].forEach(e);
                c.data.splice(0, 1);
              } else c.data.forEach(e);
            }(), function () {
              if (!c || !m.header && !m.dynamicTyping && !m.transform) return c;
              function e(e, t) {
                var r,
                  i = m.header ? {} : [];
                for (r = 0; r < e.length; r++) {
                  var n = r,
                    s = e[r];
                  m.header && (n = r >= l.length ? "__parsed_extra" : l[r]), m.transform && (s = m.transform(s, n)), s = v(n, s), "__parsed_extra" === n ? (i[n] = i[n] || [], i[n].push(s)) : i[n] = s;
                }
                return m.header && (r > l.length ? k("FieldMismatch", "TooManyFields", "Too many fields: expected " + l.length + " fields but parsed " + r, f + t) : r < l.length && k("FieldMismatch", "TooFewFields", "Too few fields: expected " + l.length + " fields but parsed " + r, f + t)), i;
              }
              var t = 1;
              !c.data.length || Array.isArray(c.data[0]) ? (c.data = c.data.map(e), t = c.data.length) : c.data = e(c.data, 0);
              m.header && c.meta && (c.meta.fields = l);
              return f += t, c;
            }();
          }
          function _() {
            return m.header && 0 === l.length;
          }
          function v(e, t) {
            return r = e, m.dynamicTypingFunction && void 0 === m.dynamicTyping[r] && (m.dynamicTyping[r] = m.dynamicTypingFunction(r)), !0 === (m.dynamicTyping[r] || m.dynamicTyping) ? "true" === t || "TRUE" === t || "false" !== t && "FALSE" !== t && (function (e) {
              if (s.test(e)) {
                var t = parseFloat(e);
                if (n < t && t < i) return !0;
              }
              return !1;
            }(t) ? parseFloat(t) : h.test(t) ? new Date(t) : "" === t ? null : t) : t;
            var r;
          }
          function k(e, t, r, i) {
            var n = {
              type: e,
              code: t,
              message: r
            };
            void 0 !== i && (n.row = i), c.errors.push(n);
          }
          this.parse = function (e, t, r) {
            var i = m.quoteChar || '"';
            if (m.newline || (m.newline = function (e, t) {
              e = e.substring(0, 1048576);
              var r = new RegExp(Q(t) + "([^]*?)" + Q(t), "gm"),
                i = (e = e.replace(r, "")).split("\r"),
                n = e.split("\n"),
                s = 1 < n.length && n[0].length < i[0].length;
              if (1 === i.length || s) return "\n";
              for (var a = 0, o = 0; o < i.length; o++) "\n" === i[o][0] && a++;
              return a >= i.length / 2 ? "\r\n" : "\r";
            }(e, i)), u = !1, m.delimiter) J(m.delimiter) && (m.delimiter = m.delimiter(e), c.meta.delimiter = m.delimiter);else {
              var n = function (e, t, r, i, n) {
                var s, a, o, u;
                n = n || [",", "\t", "|", ";", b.RECORD_SEP, b.UNIT_SEP];
                for (var h = 0; h < n.length; h++) {
                  var f = n[h],
                    d = 0,
                    l = 0,
                    c = 0;
                  o = void 0;
                  for (var p = new E({
                      comments: i,
                      delimiter: f,
                      newline: t,
                      preview: 10
                    }).parse(e), g = 0; g < p.data.length; g++) if (r && y(p.data[g])) c++;else {
                    var _ = p.data[g].length;
                    l += _, void 0 !== o ? 0 < _ && (d += Math.abs(_ - o), o = _) : o = _;
                  }
                  0 < p.data.length && (l /= p.data.length - c), (void 0 === a || d <= a) && (void 0 === u || u < l) && 1.99 < l && (a = d, s = f, u = l);
                }
                return {
                  successful: !!(m.delimiter = s),
                  bestDelimiter: s
                };
              }(e, m.newline, m.skipEmptyLines, m.comments, m.delimitersToGuess);
              n.successful ? m.delimiter = n.bestDelimiter : (u = !0, m.delimiter = b.DefaultDelimiter), c.meta.delimiter = m.delimiter;
            }
            var s = w(m);
            return m.preview && m.header && s.preview++, a = e, o = new E(s), c = o.parse(a, t, r), g(), d ? {
              meta: {
                paused: !0
              }
            } : c || {
              meta: {
                paused: !1
              }
            };
          }, this.paused = function () {
            return d;
          }, this.pause = function () {
            d = !0, o.abort(), a = J(m.chunk) ? "" : a.substring(o.getCharIndex());
          }, this.resume = function () {
            t.streamer._halted ? (d = !1, t.streamer.parseChunk(a, !0)) : setTimeout(t.resume, 3);
          }, this.aborted = function () {
            return e;
          }, this.abort = function () {
            e = !0, o.abort(), c.meta.aborted = !0, J(m.complete) && m.complete(c), a = "";
          };
        }
        function Q(e) {
          return e.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
        }
        function E(j) {
          var z,
            M = (j = j || {}).delimiter,
            P = j.newline,
            U = j.comments,
            q = j.step,
            N = j.preview,
            B = j.fastMode,
            K = z = void 0 === j.quoteChar || null === j.quoteChar ? '"' : j.quoteChar;
          if (void 0 !== j.escapeChar && (K = j.escapeChar), ("string" != typeof M || -1 < b.BAD_DELIMITERS.indexOf(M)) && (M = ","), U === M) throw new Error("Comment character same as delimiter");
          !0 === U ? U = "#" : ("string" != typeof U || -1 < b.BAD_DELIMITERS.indexOf(U)) && (U = !1), "\n" !== P && "\r" !== P && "\r\n" !== P && (P = "\n");
          var W = 0,
            H = !1;
          this.parse = function (i, t, r) {
            if ("string" != typeof i) throw new Error("Input must be a string");
            var n = i.length,
              e = M.length,
              s = P.length,
              a = U.length,
              o = J(q),
              u = [],
              h = [],
              f = [],
              d = W = 0;
            if (!i) return L();
            if (j.header && !t) {
              var l = i.split(P)[0].split(M),
                c = [],
                p = {},
                g = !1;
              for (var _ in l) {
                var m = l[_];
                J(j.transformHeader) && (m = j.transformHeader(m, _));
                var y = m,
                  v = p[m] || 0;
                for (0 < v && (g = !0, y = m + "_" + v), p[m] = v + 1; c.includes(y);) y = y + "_" + v;
                c.push(y);
              }
              if (g) {
                var k = i.split(P);
                k[0] = c.join(M), i = k.join(P);
              }
            }
            if (B || !1 !== B && -1 === i.indexOf(z)) {
              for (var b = i.split(P), E = 0; E < b.length; E++) {
                if (f = b[E], W += f.length, E !== b.length - 1) W += P.length;else if (r) return L();
                if (!U || f.substring(0, a) !== U) {
                  if (o) {
                    if (u = [], I(f.split(M)), F(), H) return L();
                  } else I(f.split(M));
                  if (N && N <= E) return u = u.slice(0, N), L(!0);
                }
              }
              return L();
            }
            for (var w = i.indexOf(M, W), R = i.indexOf(P, W), C = new RegExp(Q(K) + Q(z), "g"), S = i.indexOf(z, W);;) if (i[W] !== z) {
              if (U && 0 === f.length && i.substring(W, W + a) === U) {
                if (-1 === R) return L();
                W = R + s, R = i.indexOf(P, W), w = i.indexOf(M, W);
              } else if (-1 !== w && (w < R || -1 === R)) f.push(i.substring(W, w)), W = w + e, w = i.indexOf(M, W);else {
                if (-1 === R) break;
                if (f.push(i.substring(W, R)), D(R + s), o && (F(), H)) return L();
                if (N && u.length >= N) return L(!0);
              }
            } else for (S = W, W++;;) {
              if (-1 === (S = i.indexOf(z, S + 1))) return r || h.push({
                type: "Quotes",
                code: "MissingQuotes",
                message: "Quoted field unterminated",
                row: u.length,
                index: W
              }), T();
              if (S === n - 1) return T(i.substring(W, S).replace(C, z));
              if (z !== K || i[S + 1] !== K) {
                if (z === K || 0 === S || i[S - 1] !== K) {
                  -1 !== w && w < S + 1 && (w = i.indexOf(M, S + 1)), -1 !== R && R < S + 1 && (R = i.indexOf(P, S + 1));
                  var O = A(-1 === R ? w : Math.min(w, R));
                  if (i.substr(S + 1 + O, e) === M) {
                    f.push(i.substring(W, S).replace(C, z)), i[W = S + 1 + O + e] !== z && (S = i.indexOf(z, W)), w = i.indexOf(M, W), R = i.indexOf(P, W);
                    break;
                  }
                  var x = A(R);
                  if (i.substring(S + 1 + x, S + 1 + x + s) === P) {
                    if (f.push(i.substring(W, S).replace(C, z)), D(S + 1 + x + s), w = i.indexOf(M, W), S = i.indexOf(z, W), o && (F(), H)) return L();
                    if (N && u.length >= N) return L(!0);
                    break;
                  }
                  h.push({
                    type: "Quotes",
                    code: "InvalidQuotes",
                    message: "Trailing quote on quoted field is malformed",
                    row: u.length,
                    index: W
                  }), S++;
                }
              } else S++;
            }
            return T();
            function I(e) {
              u.push(e), d = W;
            }
            function A(e) {
              var t = 0;
              if (-1 !== e) {
                var r = i.substring(S + 1, e);
                r && "" === r.trim() && (t = r.length);
              }
              return t;
            }
            function T(e) {
              return r || (void 0 === e && (e = i.substring(W)), f.push(e), W = n, I(f), o && F()), L();
            }
            function D(e) {
              W = e, I(f), f = [], R = i.indexOf(P, W);
            }
            function L(e) {
              return {
                data: u,
                errors: h,
                meta: {
                  delimiter: M,
                  linebreak: P,
                  aborted: H,
                  truncated: !!e,
                  cursor: d + (t || 0)
                }
              };
            }
            function F() {
              q(L()), u = [], h = [];
            }
          }, this.abort = function () {
            H = !0;
          }, this.getCharIndex = function () {
            return W;
          };
        }
        function _(e) {
          var t = e.data,
            r = a[t.workerId],
            i = !1;
          if (t.error) r.userError(t.error, t.file);else if (t.results && t.results.data) {
            var n = {
              abort: function () {
                i = !0, m(t.workerId, {
                  data: [],
                  errors: [],
                  meta: {
                    aborted: !0
                  }
                });
              },
              pause: y,
              resume: y
            };
            if (J(r.userStep)) {
              for (var s = 0; s < t.results.data.length && (r.userStep({
                data: t.results.data[s],
                errors: t.results.errors,
                meta: t.results.meta
              }, n), !i); s++);
              delete t.results;
            } else J(r.userChunk) && (r.userChunk(t.results, n, t.file), delete t.results);
          }
          t.finished && !i && m(t.workerId, t.results);
        }
        function m(e, t) {
          var r = a[e];
          J(r.userComplete) && r.userComplete(t), r.terminate(), delete a[e];
        }
        function y() {
          throw new Error("Not implemented.");
        }
        function w(e) {
          if ("object" != typeof e || null === e) return e;
          var t = Array.isArray(e) ? [] : {};
          for (var r in e) t[r] = w(e[r]);
          return t;
        }
        function v(e, t) {
          return function () {
            e.apply(t, arguments);
          };
        }
        function J(e) {
          return "function" == typeof e;
        }
        return o && (f.onmessage = function (e) {
          var t = e.data;
          void 0 === b.WORKER_ID && t && (b.WORKER_ID = t.workerId);
          if ("string" == typeof t.input) f.postMessage({
            workerId: b.WORKER_ID,
            results: b.parse(t.input, t.config),
            finished: !0
          });else if (f.File && t.input instanceof File || t.input instanceof Object) {
            var r = b.parse(t.input, t.config);
            r && f.postMessage({
              workerId: b.WORKER_ID,
              results: r,
              finished: !0
            });
          }
        }), (l.prototype = Object.create(h.prototype)).constructor = l, (c.prototype = Object.create(h.prototype)).constructor = c, (p.prototype = Object.create(p.prototype)).constructor = p, (g.prototype = Object.create(h.prototype)).constructor = g, b;
      });
    })(papaparse_min);
    var Papa = papaparse_min.exports;

    var browserPonyfill = {exports: {}};

    (function (module, exports) {
      var global = typeof self !== 'undefined' ? self : commonjsGlobal;
      var __self__ = function () {
        function F() {
          this.fetch = false;
          this.DOMException = global.DOMException;
        }
        F.prototype = global;
        return new F();
      }();
      (function (self) {
        (function (exports) {
          var support = {
            searchParams: 'URLSearchParams' in self,
            iterable: 'Symbol' in self && 'iterator' in Symbol,
            blob: 'FileReader' in self && 'Blob' in self && function () {
              try {
                new Blob();
                return true;
              } catch (e) {
                return false;
              }
            }(),
            formData: 'FormData' in self,
            arrayBuffer: 'ArrayBuffer' in self
          };
          function isDataView(obj) {
            return obj && DataView.prototype.isPrototypeOf(obj);
          }
          if (support.arrayBuffer) {
            var viewClasses = ['[object Int8Array]', '[object Uint8Array]', '[object Uint8ClampedArray]', '[object Int16Array]', '[object Uint16Array]', '[object Int32Array]', '[object Uint32Array]', '[object Float32Array]', '[object Float64Array]'];
            var isArrayBufferView = ArrayBuffer.isView || function (obj) {
              return obj && viewClasses.indexOf(Object.prototype.toString.call(obj)) > -1;
            };
          }
          function normalizeName(name) {
            if (typeof name !== 'string') {
              name = String(name);
            }
            if (/[^a-z0-9\-#$%&'*+.^_`|~]/i.test(name)) {
              throw new TypeError('Invalid character in header field name');
            }
            return name.toLowerCase();
          }
          function normalizeValue(value) {
            if (typeof value !== 'string') {
              value = String(value);
            }
            return value;
          }

          // Build a destructive iterator for the value list
          function iteratorFor(items) {
            var iterator = {
              next: function () {
                var value = items.shift();
                return {
                  done: value === undefined,
                  value: value
                };
              }
            };
            if (support.iterable) {
              iterator[Symbol.iterator] = function () {
                return iterator;
              };
            }
            return iterator;
          }
          function Headers(headers) {
            this.map = {};
            if (headers instanceof Headers) {
              headers.forEach(function (value, name) {
                this.append(name, value);
              }, this);
            } else if (Array.isArray(headers)) {
              headers.forEach(function (header) {
                this.append(header[0], header[1]);
              }, this);
            } else if (headers) {
              Object.getOwnPropertyNames(headers).forEach(function (name) {
                this.append(name, headers[name]);
              }, this);
            }
          }
          Headers.prototype.append = function (name, value) {
            name = normalizeName(name);
            value = normalizeValue(value);
            var oldValue = this.map[name];
            this.map[name] = oldValue ? oldValue + ', ' + value : value;
          };
          Headers.prototype['delete'] = function (name) {
            delete this.map[normalizeName(name)];
          };
          Headers.prototype.get = function (name) {
            name = normalizeName(name);
            return this.has(name) ? this.map[name] : null;
          };
          Headers.prototype.has = function (name) {
            return this.map.hasOwnProperty(normalizeName(name));
          };
          Headers.prototype.set = function (name, value) {
            this.map[normalizeName(name)] = normalizeValue(value);
          };
          Headers.prototype.forEach = function (callback, thisArg) {
            for (var name in this.map) {
              if (this.map.hasOwnProperty(name)) {
                callback.call(thisArg, this.map[name], name, this);
              }
            }
          };
          Headers.prototype.keys = function () {
            var items = [];
            this.forEach(function (value, name) {
              items.push(name);
            });
            return iteratorFor(items);
          };
          Headers.prototype.values = function () {
            var items = [];
            this.forEach(function (value) {
              items.push(value);
            });
            return iteratorFor(items);
          };
          Headers.prototype.entries = function () {
            var items = [];
            this.forEach(function (value, name) {
              items.push([name, value]);
            });
            return iteratorFor(items);
          };
          if (support.iterable) {
            Headers.prototype[Symbol.iterator] = Headers.prototype.entries;
          }
          function consumed(body) {
            if (body.bodyUsed) {
              return Promise.reject(new TypeError('Already read'));
            }
            body.bodyUsed = true;
          }
          function fileReaderReady(reader) {
            return new Promise(function (resolve, reject) {
              reader.onload = function () {
                resolve(reader.result);
              };
              reader.onerror = function () {
                reject(reader.error);
              };
            });
          }
          function readBlobAsArrayBuffer(blob) {
            var reader = new FileReader();
            var promise = fileReaderReady(reader);
            reader.readAsArrayBuffer(blob);
            return promise;
          }
          function readBlobAsText(blob) {
            var reader = new FileReader();
            var promise = fileReaderReady(reader);
            reader.readAsText(blob);
            return promise;
          }
          function readArrayBufferAsText(buf) {
            var view = new Uint8Array(buf);
            var chars = new Array(view.length);
            for (var i = 0; i < view.length; i++) {
              chars[i] = String.fromCharCode(view[i]);
            }
            return chars.join('');
          }
          function bufferClone(buf) {
            if (buf.slice) {
              return buf.slice(0);
            } else {
              var view = new Uint8Array(buf.byteLength);
              view.set(new Uint8Array(buf));
              return view.buffer;
            }
          }
          function Body() {
            this.bodyUsed = false;
            this._initBody = function (body) {
              this._bodyInit = body;
              if (!body) {
                this._bodyText = '';
              } else if (typeof body === 'string') {
                this._bodyText = body;
              } else if (support.blob && Blob.prototype.isPrototypeOf(body)) {
                this._bodyBlob = body;
              } else if (support.formData && FormData.prototype.isPrototypeOf(body)) {
                this._bodyFormData = body;
              } else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) {
                this._bodyText = body.toString();
              } else if (support.arrayBuffer && support.blob && isDataView(body)) {
                this._bodyArrayBuffer = bufferClone(body.buffer);
                // IE 10-11 can't handle a DataView body.
                this._bodyInit = new Blob([this._bodyArrayBuffer]);
              } else if (support.arrayBuffer && (ArrayBuffer.prototype.isPrototypeOf(body) || isArrayBufferView(body))) {
                this._bodyArrayBuffer = bufferClone(body);
              } else {
                this._bodyText = body = Object.prototype.toString.call(body);
              }
              if (!this.headers.get('content-type')) {
                if (typeof body === 'string') {
                  this.headers.set('content-type', 'text/plain;charset=UTF-8');
                } else if (this._bodyBlob && this._bodyBlob.type) {
                  this.headers.set('content-type', this._bodyBlob.type);
                } else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) {
                  this.headers.set('content-type', 'application/x-www-form-urlencoded;charset=UTF-8');
                }
              }
            };
            if (support.blob) {
              this.blob = function () {
                var rejected = consumed(this);
                if (rejected) {
                  return rejected;
                }
                if (this._bodyBlob) {
                  return Promise.resolve(this._bodyBlob);
                } else if (this._bodyArrayBuffer) {
                  return Promise.resolve(new Blob([this._bodyArrayBuffer]));
                } else if (this._bodyFormData) {
                  throw new Error('could not read FormData body as blob');
                } else {
                  return Promise.resolve(new Blob([this._bodyText]));
                }
              };
              this.arrayBuffer = function () {
                if (this._bodyArrayBuffer) {
                  return consumed(this) || Promise.resolve(this._bodyArrayBuffer);
                } else {
                  return this.blob().then(readBlobAsArrayBuffer);
                }
              };
            }
            this.text = function () {
              var rejected = consumed(this);
              if (rejected) {
                return rejected;
              }
              if (this._bodyBlob) {
                return readBlobAsText(this._bodyBlob);
              } else if (this._bodyArrayBuffer) {
                return Promise.resolve(readArrayBufferAsText(this._bodyArrayBuffer));
              } else if (this._bodyFormData) {
                throw new Error('could not read FormData body as text');
              } else {
                return Promise.resolve(this._bodyText);
              }
            };
            if (support.formData) {
              this.formData = function () {
                return this.text().then(decode);
              };
            }
            this.json = function () {
              return this.text().then(JSON.parse);
            };
            return this;
          }

          // HTTP methods whose capitalization should be normalized
          var methods = ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST', 'PUT'];
          function normalizeMethod(method) {
            var upcased = method.toUpperCase();
            return methods.indexOf(upcased) > -1 ? upcased : method;
          }
          function Request(input, options) {
            options = options || {};
            var body = options.body;
            if (input instanceof Request) {
              if (input.bodyUsed) {
                throw new TypeError('Already read');
              }
              this.url = input.url;
              this.credentials = input.credentials;
              if (!options.headers) {
                this.headers = new Headers(input.headers);
              }
              this.method = input.method;
              this.mode = input.mode;
              this.signal = input.signal;
              if (!body && input._bodyInit != null) {
                body = input._bodyInit;
                input.bodyUsed = true;
              }
            } else {
              this.url = String(input);
            }
            this.credentials = options.credentials || this.credentials || 'same-origin';
            if (options.headers || !this.headers) {
              this.headers = new Headers(options.headers);
            }
            this.method = normalizeMethod(options.method || this.method || 'GET');
            this.mode = options.mode || this.mode || null;
            this.signal = options.signal || this.signal;
            this.referrer = null;
            if ((this.method === 'GET' || this.method === 'HEAD') && body) {
              throw new TypeError('Body not allowed for GET or HEAD requests');
            }
            this._initBody(body);
          }
          Request.prototype.clone = function () {
            return new Request(this, {
              body: this._bodyInit
            });
          };
          function decode(body) {
            var form = new FormData();
            body.trim().split('&').forEach(function (bytes) {
              if (bytes) {
                var split = bytes.split('=');
                var name = split.shift().replace(/\+/g, ' ');
                var value = split.join('=').replace(/\+/g, ' ');
                form.append(decodeURIComponent(name), decodeURIComponent(value));
              }
            });
            return form;
          }
          function parseHeaders(rawHeaders) {
            var headers = new Headers();
            // Replace instances of \r\n and \n followed by at least one space or horizontal tab with a space
            // https://tools.ietf.org/html/rfc7230#section-3.2
            var preProcessedHeaders = rawHeaders.replace(/\r?\n[\t ]+/g, ' ');
            preProcessedHeaders.split(/\r?\n/).forEach(function (line) {
              var parts = line.split(':');
              var key = parts.shift().trim();
              if (key) {
                var value = parts.join(':').trim();
                headers.append(key, value);
              }
            });
            return headers;
          }
          Body.call(Request.prototype);
          function Response(bodyInit, options) {
            if (!options) {
              options = {};
            }
            this.type = 'default';
            this.status = options.status === undefined ? 200 : options.status;
            this.ok = this.status >= 200 && this.status < 300;
            this.statusText = 'statusText' in options ? options.statusText : 'OK';
            this.headers = new Headers(options.headers);
            this.url = options.url || '';
            this._initBody(bodyInit);
          }
          Body.call(Response.prototype);
          Response.prototype.clone = function () {
            return new Response(this._bodyInit, {
              status: this.status,
              statusText: this.statusText,
              headers: new Headers(this.headers),
              url: this.url
            });
          };
          Response.error = function () {
            var response = new Response(null, {
              status: 0,
              statusText: ''
            });
            response.type = 'error';
            return response;
          };
          var redirectStatuses = [301, 302, 303, 307, 308];
          Response.redirect = function (url, status) {
            if (redirectStatuses.indexOf(status) === -1) {
              throw new RangeError('Invalid status code');
            }
            return new Response(null, {
              status: status,
              headers: {
                location: url
              }
            });
          };
          exports.DOMException = self.DOMException;
          try {
            new exports.DOMException();
          } catch (err) {
            exports.DOMException = function (message, name) {
              this.message = message;
              this.name = name;
              var error = Error(message);
              this.stack = error.stack;
            };
            exports.DOMException.prototype = Object.create(Error.prototype);
            exports.DOMException.prototype.constructor = exports.DOMException;
          }
          function fetch(input, init) {
            return new Promise(function (resolve, reject) {
              var request = new Request(input, init);
              if (request.signal && request.signal.aborted) {
                return reject(new exports.DOMException('Aborted', 'AbortError'));
              }
              var xhr = new XMLHttpRequest();
              function abortXhr() {
                xhr.abort();
              }
              xhr.onload = function () {
                var options = {
                  status: xhr.status,
                  statusText: xhr.statusText,
                  headers: parseHeaders(xhr.getAllResponseHeaders() || '')
                };
                options.url = 'responseURL' in xhr ? xhr.responseURL : options.headers.get('X-Request-URL');
                var body = 'response' in xhr ? xhr.response : xhr.responseText;
                resolve(new Response(body, options));
              };
              xhr.onerror = function () {
                reject(new TypeError('Network request failed'));
              };
              xhr.ontimeout = function () {
                reject(new TypeError('Network request failed'));
              };
              xhr.onabort = function () {
                reject(new exports.DOMException('Aborted', 'AbortError'));
              };
              xhr.open(request.method, request.url, true);
              if (request.credentials === 'include') {
                xhr.withCredentials = true;
              } else if (request.credentials === 'omit') {
                xhr.withCredentials = false;
              }
              if ('responseType' in xhr && support.blob) {
                xhr.responseType = 'blob';
              }
              request.headers.forEach(function (value, name) {
                xhr.setRequestHeader(name, value);
              });
              if (request.signal) {
                request.signal.addEventListener('abort', abortXhr);
                xhr.onreadystatechange = function () {
                  // DONE (success or failure)
                  if (xhr.readyState === 4) {
                    request.signal.removeEventListener('abort', abortXhr);
                  }
                };
              }
              xhr.send(typeof request._bodyInit === 'undefined' ? null : request._bodyInit);
            });
          }
          fetch.polyfill = true;
          if (!self.fetch) {
            self.fetch = fetch;
            self.Headers = Headers;
            self.Request = Request;
            self.Response = Response;
          }
          exports.Headers = Headers;
          exports.Request = Request;
          exports.Response = Response;
          exports.fetch = fetch;
          Object.defineProperty(exports, '__esModule', {
            value: true
          });
          return exports;
        })({});
      })(__self__);
      __self__.fetch.ponyfill = true;
      // Remove "polyfill" property added by whatwg-fetch
      delete __self__.fetch.polyfill;
      // Choose between native implementation (global) or custom implementation (__self__)
      // var ctx = global.fetch ? global : __self__;
      var ctx = __self__; // this line disable service worker support temporarily
      exports = ctx.fetch; // To enable: import fetch from 'cross-fetch'
      exports.default = ctx.fetch; // For TypeScript consumers without esModuleInterop.
      exports.fetch = ctx.fetch; // To enable: import {fetch} from 'cross-fetch'
      exports.Headers = ctx.Headers;
      exports.Request = ctx.Request;
      exports.Response = ctx.Response;
      module.exports = exports;
    })(browserPonyfill, browserPonyfill.exports);
    var crossFetch = /*@__PURE__*/getDefaultExportFromCjs(browserPonyfill.exports);

    async function fetchText(url) {
      const result = await crossFetch(url);
      if (result.status !== 200) {
        throw new Error(String(result.status));
      }
      return result.text();
    }

    async function mfFromGoogleSheet(url) {
      let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      let {
        urlReferences
      } = options;
      if (urlReferences) {
        let results = await Promise.all([fetchText(url), fetchText(urlReferences)]);
        return parse(results[0], results[1]);
      } else {
        let result = await fetchText(url);
        return parse(result);
      }
      async function parse(tsv, tsvReferences) {
        let parsed = Papa.parse(tsv, {
          delimiter: '\t',
          header: true
        });
        let fields = parsed.meta.fields;
        let infoFields = fields.filter(a => !['mf', 'modif', 'ESI', 'MALDI', 'positive', 'negative'].includes(a));
        let formulas = parsed.data;
        let references = {};
        if (tsvReferences) {
          let referencesArray = Papa.parse(tsvReferences, {
            delimiter: '\t',
            header: true
          }).data;
          referencesArray.forEach(r => {
            references[r.label] = r;
          });
        }
        let results = [];
        for (let formula of formulas) {
          if (tsvReferences) {
            // we add references
            let refs = formula.references.split(/[ ,]+/);
            formula.references = [];
            for (let ref of refs) {
              formula.references.push(references[ref]);
            }
          }
          // we need to calculate all the possibilities
          try {
            let mfs = await generateMFs([formula.mf], {
              ionizations: formula.modif
            });
            for (let mf of mfs) {
              mf.info = {};
              for (let infoField of infoFields) {
                mf.info[infoField] = formula[infoField];
              }
              if (!formula.ESI && !formula.MALDI && !formula.positive && !formula.negative) {
                mf.filter = {
                  ESI: true,
                  MALDI: true,
                  positive: true,
                  negative: true
                };
              } else {
                mf.filter = {
                  ESI: formula.ESI === 'X',
                  MALDI: formula.MALDI === 'X',
                  positive: formula.positive === 'X',
                  negative: formula.negative === 'X'
                };
              }
              mf.mf = new MF(mf.mf).toMF();
              results.push(mf);
            }
          } catch (e) {
            // eslint-disable-next-line no-console
            console.warn('Non parsable molecular formula: ', formula.mf, formula.modif, e.toString());
          }
        }
        results = results.filter(a => {
          return a.ms.em !== 0;
        });
        results.sort((a, b) => {
          return a.ms.em - b.ms.em;
        });
        let uniqueResults = [results[0]];
        for (let i = 1; i < results.length; i++) {
          if (results[i - 1].ms.em !== results[i].ms.em) {
            uniqueResults.push(results[i]);
          }
        }
        return uniqueResults;
      }
    }

    async function loadGoogleSheet() {
      let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
      let {
        refUUID = '1C_H9aiJyu9M9in7sHMOaz-d3Sv758rE72oLxEKH9ioA',
        uuid = '1LrJCl9-xSZKhGA9Y8nKVkYwB-mEOHBkTXg5qYXeFpZY'
      } = options;
      if (options.uuid && !options.refUUID) refUUID = '';
      let url = `https://googledocs.cheminfo.org/spreadsheets/d/${uuid}/export?format=tsv`;
      let refURL = refUUID ? `https://googledocs.cheminfo.org/spreadsheets/d/${refUUID}/export?format=tsv` : '';
      let data = await mfFromGoogleSheet(url, refURL);
      data.sort((a, b) => a.em - b.em);
      return data;
    }

    const loadingPromises = {};
    async function loadKnapSack() {
      let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
      const {
        url = 'https://couch.cheminfo.org/cheminfo-public/d2eb480198c80275a1d05dd3609414f9/upload/ms.zip'
      } = options;
      if (!loadingPromises[url]) {
        loadingPromises[url] = fetchArrayBuffer(url);
      }
      const buffer = await loadingPromises[url];
      const jsZip = new JSZip();
      let zip = await jsZip.loadAsync(buffer);
      let fileData = await zip.files['ms.json'].async('string');
      let data = JSON.parse(fileData);
      data.forEach(d => {
        d.url = `http://kanaya.naist.jp/knapsack_jsp/information.jsp?word=${d.id}`;
      });
      data.sort((a, b) => a.em - b.em);
      return data;
    }

    /**
        Searching by various criteria. This mass will not take into account the mass
        of the mass of the electron
    * @param {object}   [filter={}]
    * @param {number}   [filter.minMW=0] - Minimal molecular weight
    * @param {number}   [filter.maxMW=+Infinity] - Maximal molecular weight
    * @param {number}   [filter.minEM=0] - Minimal monoisotopic mass
    * @param {number}   [filter.maxEM=+Infinity] - Maximal monoisotopic mass
    * @param {number}   [filter.minCharge=-Infinity] - Minimal charge
    * @param {number}   [filter.maxCharge=+Infinity] - Maximal charge
    * @param {boolean}   [filter.absoluteCharge=false] - If true, the charge is absolute (so between 0 and +Infinity by default)
    * @param {object}   [filter.unsaturation={}]
    * @param {number}   [filter.unsaturation.min=-Infinity] - Minimal unsaturation
    * @param {number}   [filter.unsaturation.max=+Infinity] - Maximal unsaturation
    * @param {boolean}   [filter.unsaturation.onlyInteger=false] - Integer unsaturation
    * @param {boolean}   [filter.unsaturation.onlyNonInteger=false] - Non integer unsaturation
    * @param {object}   [filter.atoms] - object of atom:{min, max}

    * @param {object}   [options={}]
    * @param {array}    [options.databases] - an array containing the name of the databases so search, by default all
    * @param {boolean}  [options.flatten=false] - should we return the array as a flat result
    */

    function search$2(emdb, filter) {
      let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
      let {
        databases = Object.keys(emdb.databases),
        flatten = false
      } = options;
      let results = {};
      for (let database of databases) {
        results[database] = emdb.databases[database].filter(entry => generalMatcher(entry, filter));
      }
      if (flatten) {
        let flattenResults = [];
        for (let database of databases) {
          for (let entry of results[database]) {
            entry.database = database;
            flattenResults.push(entry);
          }
        }
        return flattenResults;
      } else {
        return results;
      }
    }

    /**
    Search for an experimental monoisotopic mass
    * @param {number}   msem - The observed monoisotopic mass
    * @param {object}   [options={}]
    * @param {array}    [options.databases] - an array containing the name of the databases so search, by default all
    * @param {boolean}  [options.flatten=false] - should we return the array as a flat result
    * @param {string}   [options.ionizations] - list the allowed ionizations possibilities
    * @param {object}   [options.filter={}]
    * @param {number}   [options.filter.targetMass] - Target mass, allows to calculate error and filter results
    * @param {number[]} [options.filter.targetMasses] - Target masses: SORTED array of numbers
    * @param {number[]} [options.filter.targetIntensities] - Target intensities: SORTED array of numbers
    * @param {number}   [options.filter.minEM=0] - Minimal monoisotopic mass
    * @param {number}         [options.filter.maxEM=+Infinity] - Maximal monoisotopic mass
    * @param {number}         [options.filter.minMSEM=0] - Minimal monoisotopic mass observed by mass
    * @param {number}         [options.filter.maxMSEM=+Infinity] - Maximal monoisotopic mass observed by mass
    * @param {number}         [options.filter.minCharge=-Infinity] - Minimal charge
    * @param {number}         [options.filter.maxCharge=+Infinity] - Maximal charge
    * @param {boolean}        [options.filter.absoluteCharge=false] - If true, the charge is absolute (so between 0 and +Infinity by default)
    * @param {object}         [options.filter.unsaturation={}]
    * @param {number}         [options.filter.unsaturation.min=-Infinity] - Minimal unsaturation
    * @param {number}         [options.filter.unsaturation.max=+Infinity] - Maximal unsaturation
    * @param {boolean}        [options.filter.unsaturation.onlyInteger=false] - Integer unsaturation
    * @param {boolean}        [options.filter.unsaturation.onlyNonInteger=false] - Non integer unsaturation
    * @param {object}         [options.filter.callback] - a function to filter the MF
    * @param {object}         [options.filter.atoms] - object of atom:{min, max}
    */

    function searchMSEM(emdb, msem) {
      let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
      let filter = {
        ...(options.filter || {}),
        targetMass: msem
      };
      let {
        databases = Object.keys(emdb.databases),
        flatten = false
      } = options;
      let ionizations = preprocessIonizations(options.ionizations);
      let results = {};
      for (let database of databases) {
        results[database] = [];
      }
      for (let ionization of ionizations) {
        filter.ionization = ionization;
        for (let database of databases) {
          for (let entry of emdb.databases[database]) {
            let match = msemMatcher(entry, filter);
            if (match) {
              results[database].push({
                ...entry,
                ms: match.ms,
                ionization: match.ionization
              });
            }
          }
        }
      }
      if (flatten) {
        let flattenResults = [];
        for (let database of databases) {
          for (let entry of results[database]) {
            entry.database = database;
            flattenResults.push(entry);
          }
        }
        flattenResults.sort((a, b) => Math.abs(a.ms.ppm) - Math.abs(b.ms.ppm));
        return flattenResults;
      } else {
        Object.keys(results).forEach(k => results[k].sort((a, b) => Math.abs(a.ms.ppm) - Math.abs(b.ms.ppm)));
        return results;
      }
    }

    const GAUSSIAN_EXP_FACTOR = -4 * Math.LN2;
    const ROOT_PI_OVER_LN2 = Math.sqrt(Math.PI / Math.LN2);
    const ROOT_THREE = Math.sqrt(3);
    const ROOT_2LN2 = Math.sqrt(2 * Math.LN2);
    const ROOT_2LN2_MINUS_ONE = Math.sqrt(2 * Math.LN2) - 1;

    // https://en.wikipedia.org/wiki/Error_function#Inverse_functions
    // This code yields to a good approximation
    // If needed a better implementation using polynomial can be found on https://en.wikipedia.org/wiki/Error_function#Inverse_functions
    function erfinv(x) {
      let a = 0.147;
      if (x === 0) return 0;
      let ln1MinusXSqrd = Math.log(1 - x * x);
      let lnEtcBy2Plus2 = ln1MinusXSqrd / 2 + 2 / (Math.PI * a);
      let firstSqrt = Math.sqrt(lnEtcBy2Plus2 ** 2 - ln1MinusXSqrd / a);
      let secondSqrt = Math.sqrt(firstSqrt - lnEtcBy2Plus2);
      return secondSqrt * (x > 0 ? 1 : -1);
    }

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

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

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

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

    function addBaseline(data, baselineFct) {
      if (!baselineFct) return data;
      let xs = data.x;
      let ys = data.y;
      for (let i = 0; i < xs.length; i++) {
        ys[i] += baselineFct(xs[i]);
      }
      return data;
    }

    // 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$2(value) {
      const tag = toString$2.call(value);
      return tag.endsWith('Array]') && !tag.includes('Big');
    }

    /**
     * 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
     */
    function xAdd(array1, array2) {
      let isConstant = false;
      let constant = 0;
      if (isAnyArray$2(array2)) {
        if (array1.length !== array2.length) {
          throw new Error('xAdd: size of array1 and array2 must be identical');
        }
      } else {
        isConstant = true;
        constant = array2;
      }
      let 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;
    }

    /**
     * Checks if input is of type array
     *
     * @param input - input
     */
    function xCheck$2(input) {
      let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      const {
        minLength
      } = options;
      if (!isAnyArray$2(input)) {
        throw new TypeError('input must be an array');
      }
      if (input.length === 0) {
        throw new TypeError('input must not be empty');
      }
      if (minLength && input.length < minLength) {
        throw new Error(`input must have a length of at least ${minLength}`);
      }
    }

    /**
     * Returns the closest index of a `target`
     *
     * @param array - array of numbers
     * @param target - target
     * @returns - closest index
     */
    function xFindClosestIndex$2(array, target) {
      let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
      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$2(x) {
      let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      let {
        fromIndex,
        toIndex,
        from,
        to
      } = options;
      if (fromIndex === undefined) {
        if (from !== undefined) {
          fromIndex = xFindClosestIndex$2(x, from);
        } else {
          fromIndex = 0;
        }
      }
      if (toIndex === undefined) {
        if (to !== undefined) {
          toIndex = xFindClosestIndex$2(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
      };
    }

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

    const LOOP = 8;
    const FLOAT_MUL = 1 / 16777216;
    const sh1 = 15;
    const sh2 = 18;
    const sh3 = 11;
    function multiply_uint32(n, m) {
      n >>>= 0;
      m >>>= 0;
      const nlo = n & 0xffff;
      const nhi = n - nlo;
      return (nhi * m >>> 0) + nlo * m >>> 0;
    }
    class XSadd {
      constructor() {
        let seed = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 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 + multiply_uint32(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
     *
     * @return - array of random floats normally distributed
     */
    let spare;
    let hasSpare = false;
    function createRandomArray() {
      let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
      const {
        mean = 0,
        standardDeviation = 1,
        length = 1000,
        range = 1,
        seed,
        distribution = 'normal'
      } = options;
      const generator = new XSadd(seed);
      let returnArray = new Float64Array(length);
      switch (distribution) {
        case 'normal':
          for (let i = 0; i < length; i++) {
            returnArray[i] = generateGaussian(mean, standardDeviation, generator);
          }
          break;
        case 'uniform':
          for (let i = 0; i < length; i++) {
            returnArray[i] = (generator.random() - 0.5) * range + mean;
          }
          break;
        default:
          // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
          throw new Error(`unknown distribution: ${distribution}`);
      }
      return returnArray;
    }
    function generateGaussian(mean, standardDeviation, generator) {
      let val, u, v, s;
      if (hasSpare) {
        hasSpare = false;
        val = spare * standardDeviation + mean;
      } else {
        do {
          u = generator.random() * 2 - 1;
          v = generator.random() * 2 - 1;
          s = u * u + v * v;
        } while (s >= 1 || s === 0);
        s = Math.sqrt(-2 * Math.log(s) / s);
        spare = v * s;
        hasSpare = true;
        val = mean + standardDeviation * u * s;
      }
      return val;
    }

    function addNoise(data) {
      let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      const {
        seed = 0,
        distribution = 'normal',
        percent = 1
      } = options;
      const range = xMaxValue$2(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 {
      constructor() {
        let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
        const {
          from = 0,
          to = 1000,
          nbPoints = 10001,
          peakWidthFct,
          shape = {
            kind: 'gaussian',
            fwhm: 5
          }
        } = options;
        this.from = from;
        this.to = to;
        this.nbPoints = nbPoints;
        this.interval = (this.to - this.from) / (this.nbPoints - 1);
        this.peakWidthFct = peakWidthFct;
        this.maxPeakHeight = Number.MIN_SAFE_INTEGER;
        this.data = {
          x: new Float64Array(this.nbPoints),
          y: new Float64Array(this.nbPoints)
        };
        let shapeGenerator = getShape1D(shape);
        this.shape = shapeGenerator;
        assertNumber(this.from, 'from');
        assertNumber(this.to, 'to');
        assertInteger(this.nbPoints, 'nbPoints');
        if (this.to <= this.from) {
          throw new RangeError('to option must be larger than from');
        }
        if (this.peakWidthFct && typeof this.peakWidthFct !== 'function') {
          throw new TypeError('peakWidthFct option must be a function');
        }
        this.reset();
      }
      /**
       * Add a series of peaks to the spectrum.
       * @param peaks - Peaks to add.
       */
      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);
          }
        }
      }
      /**
       * Add a single peak to the spectrum.
       * A peak may be either defined as [x,y,fwhm,...] or as {x, y, shape}
       * @param peak
       * @param options
       */
      addPeak(peak) {
        let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
        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?}');
        }
        let xPosition;
        let intensity;
        let peakFWHM;
        let peakWidth;
        let peakShapeOptions;
        if (Array.isArray(peak)) {
          [xPosition, intensity, peakFWHM, peakShapeOptions] = peak;
        } else {
          xPosition = peak.x;
          intensity = peak.y;
          peakWidth = peak.width;
          peakShapeOptions = peak.shape;
        }
        if (intensity > this.maxPeakHeight) this.maxPeakHeight = intensity;
        let {
          shape: shapeOptions
        } = options;
        if (peakShapeOptions) {
          shapeOptions = shapeOptions ? {
            ...shapeOptions,
            ...peakShapeOptions
          } : peakShapeOptions;
        }
        const shape = shapeOptions ? getShape1D(shapeOptions) : Object.assign(Object.create(Object.getPrototypeOf(this.shape)), this.shape);
        let {
          width,
          widthLeft,
          widthRight
        } = options;
        /*
         if we don't force the fwhm we just take the one from the shape
         however we have many way to force it:
         - use [x,y,fwhm]
         - define `width` that will be converted to fwhm
         - define `widthLeft` and `widthRight` to define asymmetric peaks
         - have a callback `peakWidthFct`
         This should evolve in the future because we will not always have `fwhm`
         */
        const fwhm = peakFWHM !== undefined ? peakFWHM : peakWidth ? shape.widthToFWHM(peakWidth) : this.peakWidthFct ? this.peakWidthFct(xPosition) : 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');
        }
        let factor = options.factor === undefined ? shape.getFactor() : options.factor;
        const firstValue = xPosition - widthLeft / 2 * factor;
        const lastValue = xPosition + 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((xPosition - this.from) / this.interval);
        // PEAK SHAPE MAY BE ASYMMETRC (widthLeft and widthRight) !
        // we calculate the left part of the shape
        shape.fwhm = widthLeft;
        for (let index = firstPoint; index < Math.max(middlePoint, 0); index++) {
          this.data.y[index] += intensity * shape.fct(this.data.x[index] - xPosition);
        }
        // we calculate the right part of the gaussian
        shape.fwhm = widthRight;
        for (let index = Math.min(middlePoint, lastPoint); index <= lastPoint; index++) {
          this.data.y[index] += intensity * shape.fct(this.data.x[index] - xPosition);
        }
      }
      /**
       * Add a baseline to the spectrum.
       * @param baselineFct - Mathematical function producing the baseline you want.
       */
      addBaseline(baselineFct) {
        addBaseline(this.data, baselineFct);
        return this;
      }
      /**
       * Add noise to the spectrum.
       *
       * @param percent - Noise's amplitude in percents of the spectrum max value. Default: 1.
       */
      addNoise(options) {
        addNoise(this.data, options);
        return this;
      }
      /**
       * Get the generated spectrum.
       */
      getSpectrum() {
        let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
        if (typeof options === 'boolean') {
          options = {
            copy: options
          };
        }
        const {
          copy = true,
          threshold = 0
        } = options;
        if (threshold) {
          let minPeakHeight = this.maxPeakHeight * threshold;
          let x = [];
          let 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,
            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.
       */
      reset() {
        const spectrum = this.data;
        for (let i = 0; i < this.nbPoints; i++) {
          spectrum.x[i] = this.from + i * this.interval;
        }
        return this;
      }
    }
    function assertInteger(value, name) {
      if (!Number.isInteger(value)) {
        throw new TypeError(`${name} option must be an integer`);
      }
    }
    function assertNumber(value, name) {
      if (!Number.isFinite(value)) {
        throw new TypeError(`${name} option must be a number`);
      }
    }

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

    /**
     * Join x values if there are similar
     */

    function joinX(self) {
      let threshold = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 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 = {
            x: item.x,
            y: item.y
          };
          if (item.composition) current.composition = item.composition;
          result.push(current);
        }
      }
      self.array = result;
      self.ySorted = false;
      return self;
    }

    function multiply(a, b) {
      let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
      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;
    }

    // https://en.wikipedia.org/wiki/Exponentiation_by_squaring

    function power(a, p) {
      let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
      if (p <= 0) throw new Error('power must be larger than 0');
      if (p === 1) return a;
      if (p === 2) {
        return a.square();
      }
      p--;
      let base = a.copy(); // linear time
      while (p !== 0) {
        if ((p & 1) !== 0) {
          a.multiply(base, options); // executed <= log2(p) times
        }

        p >>= 1;
        if (p !== 0) base.square(options); // executed <= log2(p) times
      }

      return a;
    }

    /**
     * Internal class to deal with isotopic distribution calculations
     */
    class Distribution {
      constructor(array) {
        if (Array.isArray(array)) {
          this.array = array;
          this.xSorted = false;
          this.ySorted = false;
        } else {
          this.array = [];
          this.xSorted = true;
          this.ySorted = true;
        }
      }
      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 minX() {
        if (!this.xSorted) this.sortX();
        return this.array[0].x;
      }
      get maxX() {
        if (!this.xSorted) this.sortX();
        return this.array[this.array.length - 1].x;
      }
      get minY() {
        if (!this.ySorted) this.sortY();
        return this.array[0].y;
      }
      get maxY() {
        if (!this.ySorted) this.sortY();
        return this.array[this.array.length - 1];
      }
      multiplyY(value) {
        this.array.forEach(item => item.y *= value);
      }
      setArray(array) {
        this.array = array;
        this.xSorted = false;
        this.ySorted = false;
      }
      move(other) {
        this.xSorted = other.xSorted;
        this.ySorted = other.ySorted;
        this.array = other.array;
      }
      push(point) {
        this.array.push(point);
        this.xSorted = false;
        this.ySorted = false;
      }
      sortX() {
        this.ySorted = false;
        if (this.xSorted) return this;
        this.array.sort((a, b) => a.x - b.x);
        this.xSorted = true;
        return this;
      }
      sortY() {
        this.xSorted = false;
        if (this.ySorted) return this;
        this.array.sort((a, b) => b.y - a.y);
        this.ySorted = true;
        return this;
      }
      normalize() {
        let sum = 0;
        for (let item of this.array) {
          sum += item.y;
        }
        for (let item of this.array) {
          item.y /= sum;
        }
        return this;
      }
      topY(limit) {
        if (!limit) return this;
        if (this.array.length <= limit) return this;
        this.sortY();
        this.array.splice(limit);
        return this;
      }
      square() {
        let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
        return this.multiply(this, options);
      }
      multiply(b, options) {
        return multiply(this, b, options);
      }
      power(p, options) {
        return power(this, p, options);
      }
      copy() {
        let distCopy = new Distribution();
        distCopy.xSorted = this.xSorted;
        distCopy.ySorted = this.ySorted;
        distCopy.array = JSON.parse(JSON.stringify(this.array));
        return distCopy;
      }
      maxToOne() {
        if (this.array.length === 0) return this;
        let currentMax = this.array[0].y;
        for (let item of this.array) {
          if (item.y > currentMax) currentMax = item.y;
        }
        for (let item of this.array) {
          item.y /= currentMax;
        }
        return this;
      }
      joinX(threshold) {
        return joinX(this, threshold);
      }
      append(distribution) {
        for (let item of distribution.array) {
          this.array.push(item);
        }
        this.xSorted = false;
        this.ySorted = false;
      }
      closestPointX(target) {
        this.sortX();
        return closestPointX(this.array, target);
      }
    }

    function getDerivedCompositionInfo(composition) {
      const shortComposition = {};
      let label = '';
      let shortLabel = '';
      for (let key in composition) {
        let isotopeLabel = '';
        for (let i = 0; i < key.length; i++) {
          if (superscript[key[i]]) {
            isotopeLabel += 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 += subscript[number[i]];
          }
        }
        label += isotopeLabel;
        if (stableIsotopesObject[key].mostAbundant) continue;
        shortLabel += isotopeLabel;
        shortComposition[key] = composition[key];
      }
      return {
        label,
        shortComposition,
        shortLabel
      };
    }

    const MINIMAL_FWHM = 1e-8;

    /**
     * An object containing two arrays
     * @typedef {object} XY
     * @property {number[]} x - The x array
     * @property {number[]} y - The y array
     */

    /**
     * A class that allows to manage isotopic distribution
     */
    class IsotopicDistribution {
      /**
       * Class that manage isotopic distribution
       * @param {string|array} value - Molecular formula or an array of parts
       * @param {object} [options={}]
       * @param {string} [options.ionizations=''] - string containing a comma separated list of modifications
       * @param {number} [options.fwhm=0.01] - Amount of Dalton under which 2 peaks are joined
       * @param {number} [options.maxLines=5000] - Maximal number of lines during calculations
       * @param {number} [options.minY=1e-8] - Minimal signal height during calculations
       * @param {number} [options.ensureCase=false] - Ensure uppercase / lowercase
       * @param {number} [options.allowNeutral=true] - Should we keep the distribution if the molecule has no charge
       */

      constructor(value) {
        let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
        if (Array.isArray(value)) {
          this.parts = JSON.parse(JSON.stringify(value));
          for (let part of this.parts) {
            part.confidence = 0;
            part.isotopesInfo = new MF(`${part.mf}(${part.ionization.mf})`).getIsotopesInfo();
          }
        } else {
          let mf = new MF(value, {
            ensureCase: options.ensureCase
          });
          let mfInfo = mf.getInfo();
          let ionizations = preprocessIonizations(options.ionizations);
          let parts = mfInfo.parts || [mfInfo];
          this.parts = [];
          for (let partOriginal of parts) {
            // we calculate information for each part
            for (const ionization of ionizations) {
              let part = JSON.parse(JSON.stringify(partOriginal));
              part.em = part.monoisotopicMass; // TODO: To remove !!! we change the name !?
              part.isotopesInfo = new MF(`${part.mf}(${ionization.mf})`).getIsotopesInfo();
              part.confidence = 0;
              let msInfo = 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_FWHM : options.minY;
        this.maxLines = options.maxLines || 5000;
        this.allowNeutral = options.allowNeutral === undefined ? true : options.allowNeutral;
      }
      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();
        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([{
            x: 0,
            y: 1,
            composition: this.fwhm === MINIMAL_FWHM ? {} : undefined
          }]);
          let charge = part.ms.charge;
          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 = JSON.parse(JSON.stringify(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(newDistribution);
                distribution.power(isotope.number, options);
                totalDistribution.multiply(distribution, options);
              }
            }
            this.confidence += totalDistribution.array.reduce((sum, value) => sum + value.y, 0);

            // we finally deal with the charge

            if (charge) {
              totalDistribution.array.forEach(e => {
                e.x = (e.x - ELECTRON_MASS$1 * charge) / absoluteCharge;
              });
            }
            if (totalDistribution.array && totalDistribution.array.length > 0) {
              totalDistribution.sortX();
              part.fromX = totalDistribution.array[0].x;
              part.toX = totalDistribution.array[totalDistribution.array.length - 1].x;
            }
            if (part.ms.target && 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;
            if (finalDistribution.array.length === 0) {
              finalDistribution = totalDistribution;
            } else {
              finalDistribution.append(totalDistribution);
            }
          }
        }
        if (finalDistribution) finalDistribution.joinX(this.fwhm);
        for (let entry of finalDistribution.array) {
          if (!entry.composition) continue;
          Object.assign(entry, getDerivedCompositionInfo(entry.composition));
        }
        this.confidence /= this.parts.length;
        this.cachedDistribution = finalDistribution;
        return finalDistribution;
      }
      getCSV() {
        return this.getText({
          delimiter: ', '
        });
      }
      getTSV() {
        return this.getText({
          delimiter: '\t'
        });
      }
      getTable() {
        let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
        const {
          maxValue,
          xLabel = 'x',
          yLabel = 'y'
        } = options;
        let points = this.getDistribution().array;
        let factor = 1;
        if (maxValue) {
          let maxY = this.getMaxY(points);
          factor = maxValue / maxY;
        }
        return points.map(point => {
          let newPoint = {};
          newPoint[xLabel] = point.x;
          newPoint[yLabel] = point.y * factor;
          return newPoint;
        });
      }
      getText() {
        let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
        const {
          delimiter = '\t',
          numberDecimals = 3
        } = options;
        let points = this.getDistribution().array;
        let csv = [];
        for (let point of points) {
          csv.push(`${point.x.toFixed(5)}${delimiter}${(point.y * 100).toFixed(numberDecimals)}`);
        }
        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<any>} an object containing at least the 2 properties: x:[] and y:[]
       */
      getPeaks() {
        let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
        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;
        } else if (maxValue) {
          let maxY = this.getMaxY(peaks);
          factor = maxY / maxValue;
        }
        if (factor !== 1) {
          // we need to copy the array because we prefer no side effects
          peaks = JSON.parse(JSON.stringify(peaks));
          for (const peak of peaks) {
            peak.y = peak.y / factor;
          }
        }
        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 {XY} an object containing at least the 2 properties: x:[] and y:[]
       */
      getXY() {
        let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
        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() {
        let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
        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]
       * @param {number} [options.threshold=0.00001] // minimal height to return point
       * @param {number} [options.maxLength=1e6] // minimal height to return point
       * @param {number} [options.maxValue] // rescale Y to reach maxValue
       * @param {function} [options.peakWidthFct=(mz)=>(this.fwhm)]
       * @return {XY} isotopic distribution as an object containing 2 properties: x:[] and y:[]
       */

      getGaussian() {
        let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
        const {
          peakWidthFct = () => this.fwhm,
          threshold = 0.00001,
          gaussianWidth = 10,
          maxValue,
          maxLength = 1e6
        } = options;
        let points = this.getTable({
          maxValue
        });
        if (points.length === 0) return {
          x: [],
          y: []
        };
        const from = Math.floor(options.from || points[0].x - 2);
        const to = Math.ceil(options.to || points[points.length - 1].x + 2);
        const nbPoints = Math.round((to - from) * gaussianWidth / this.fwhm + 1);
        if (nbPoints > maxLength) {
          throw Error(`Number of points is over the maxLength: ${nbPoints}>${maxLength}`);
        }
        let gaussianOptions = {
          from,
          to,
          nbPoints,
          peakWidthFct
        };
        let spectrumGenerator = new SpectrumGenerator(gaussianOptions);
        for (let point of points) {
          spectrumGenerator.addPeak([point.x, point.y]);
        }
        let spectrum = spectrumGenerator.getSpectrum({
          threshold
        });
        return spectrum;
      }
    }

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

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

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

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

    var mlStat = {};

    var array$2 = {};

    (function (exports) {

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    var matrix = {};

    (function (exports) {

      var arrayStat = array$2;
      function compareNumbers(a, b) {
        return a - b;
      }
      exports.max = function max(matrix) {
        var max = -Infinity;
        for (var i = 0; i < matrix.length; i++) {
          for (var j = 0; j < matrix[i].length; j++) {
            if (matrix[i][j] > max) max = matrix[i][j];
          }
        }
        return max;
      };
      exports.min = function min(matrix) {
        var min = Infinity;
        for (var i = 0; i < matrix.length; i++) {
          for (var j = 0; j < matrix[i].length; j++) {
            if (matrix[i][j] < min) min = matrix[i][j];
          }
        }
        return min;
      };
      exports.minMax = function minMax(matrix) {
        var min = Infinity;
        var max = -Infinity;
        for (var i = 0; i < matrix.length; i++) {
          for (var j = 0; j < matrix[i].length; j++) {
            if (matrix[i][j] < min) min = matrix[i][j];
            if (matrix[i][j] > max) max = matrix[i][j];
          }
        }
        return {
          min: min,
          max: max
        };
      };
      exports.entropy = function entropy(matrix, eps) {
        if (typeof eps === 'undefined') {
          eps = 0;
        }
        var sum = 0,
          l1 = matrix.length,
          l2 = matrix[0].length;
        for (var i = 0; i < l1; i++) {
          for (var j = 0; j < l2; j++) {
            sum += matrix[i][j] * Math.log(matrix[i][j] + eps);
          }
        }
        return -sum;
      };
      exports.mean = function mean(matrix, dimension) {
        if (typeof dimension === 'undefined') {
          dimension = 0;
        }
        var rows = matrix.length,
          cols = matrix[0].length,
          theMean,
          N,
          i,
          j;
        if (dimension === -1) {
          theMean = [0];
          N = rows * cols;
          for (i = 0; i < rows; i++) {
            for (j = 0; j < cols; j++) {
              theMean[0] += matrix[i][j];
            }
          }
          theMean[0] /= N;
        } else if (dimension === 0) {
          theMean = new Array(cols);
          N = rows;
          for (j = 0; j < cols; j++) {
            theMean[j] = 0;
            for (i = 0; i < rows; i++) {
              theMean[j] += matrix[i][j];
            }
            theMean[j] /= N;
          }
        } else if (dimension === 1) {
          theMean = new Array(rows);
          N = cols;
          for (j = 0; j < rows; j++) {
            theMean[j] = 0;
            for (i = 0; i < cols; i++) {
              theMean[j] += matrix[j][i];
            }
            theMean[j] /= N;
          }
        } else {
          throw new Error('Invalid dimension');
        }
        return theMean;
      };
      exports.sum = function sum(matrix, dimension) {
        if (typeof dimension === 'undefined') {
          dimension = 0;
        }
        var rows = matrix.length,
          cols = matrix[0].length,
          theSum,
          i,
          j;
        if (dimension === -1) {
          theSum = [0];
          for (i = 0; i < rows; i++) {
            for (j = 0; j < cols; j++) {
              theSum[0] += matrix[i][j];
            }
          }
        } else if (dimension === 0) {
          theSum = new Array(cols);
          for (j = 0; j < cols; j++) {
            theSum[j] = 0;
            for (i = 0; i < rows; i++) {
              theSum[j] += matrix[i][j];
            }
          }
        } else if (dimension === 1) {
          theSum = new Array(rows);
          for (j = 0; j < rows; j++) {
            theSum[j] = 0;
            for (i = 0; i < cols; i++) {
              theSum[j] += matrix[j][i];
            }
          }
        } else {
          throw new Error('Invalid dimension');
        }
        return theSum;
      };
      exports.product = function product(matrix, dimension) {
        if (typeof dimension === 'undefined') {
          dimension = 0;
        }
        var rows = matrix.length,
          cols = matrix[0].length,
          theProduct,
          i,
          j;
        if (dimension === -1) {
          theProduct = [1];
          for (i = 0; i < rows; i++) {
            for (j = 0; j < cols; j++) {
              theProduct[0] *= matrix[i][j];
            }
          }
        } else if (dimension === 0) {
          theProduct = new Array(cols);
          for (j = 0; j < cols; j++) {
            theProduct[j] = 1;
            for (i = 0; i < rows; i++) {
              theProduct[j] *= matrix[i][j];
            }
          }
        } else if (dimension === 1) {
          theProduct = new Array(rows);
          for (j = 0; j < rows; j++) {
            theProduct[j] = 1;
            for (i = 0; i < cols; i++) {
              theProduct[j] *= matrix[j][i];
            }
          }
        } else {
          throw new Error('Invalid dimension');
        }
        return theProduct;
      };
      exports.standardDeviation = function standardDeviation(matrix, means, unbiased) {
        var vari = exports.variance(matrix, means, unbiased),
          l = vari.length;
        for (var i = 0; i < l; i++) {
          vari[i] = Math.sqrt(vari[i]);
        }
        return vari;
      };
      exports.variance = function variance(matrix, means, unbiased) {
        if (typeof unbiased === 'undefined') {
          unbiased = true;
        }
        means = means || exports.mean(matrix);
        var rows = matrix.length;
        if (rows === 0) return [];
        var cols = matrix[0].length;
        var vari = new Array(cols);
        for (var j = 0; j < cols; j++) {
          var sum1 = 0,
            sum2 = 0,
            x = 0;
          for (var i = 0; i < rows; i++) {
            x = matrix[i][j] - means[j];
            sum1 += x;
            sum2 += x * x;
          }
          if (unbiased) {
            vari[j] = (sum2 - sum1 * sum1 / rows) / (rows - 1);
          } else {
            vari[j] = (sum2 - sum1 * sum1 / rows) / rows;
          }
        }
        return vari;
      };
      exports.median = function median(matrix) {
        var rows = matrix.length,
          cols = matrix[0].length;
        var medians = new Array(cols);
        for (var i = 0; i < cols; i++) {
          var data = new Array(rows);
          for (var j = 0; j < rows; j++) {
            data[j] = matrix[j][i];
          }
          data.sort(compareNumbers);
          var N = data.length;
          if (N % 2 === 0) {
            medians[i] = (data[N / 2] + data[N / 2 - 1]) * 0.5;
          } else {
            medians[i] = data[Math.floor(N / 2)];
          }
        }
        return medians;
      };
      exports.mode = function mode(matrix) {
        var rows = matrix.length,
          cols = matrix[0].length,
          modes = new Array(cols),
          i,
          j;
        for (i = 0; i < cols; i++) {
          var itemCount = new Array(rows);
          for (var k = 0; k < rows; k++) {
            itemCount[k] = 0;
          }
          var itemArray = new Array(rows);
          var count = 0;
          for (j = 0; j < rows; j++) {
            var index = itemArray.indexOf(matrix[j][i]);
            if (index >= 0) {
              itemCount[index]++;
            } else {
              itemArray[count] = matrix[j][i];
              itemCount[count] = 1;
              count++;
            }
          }
          var maxValue = 0,
            maxIndex = 0;
          for (j = 0; j < count; j++) {
            if (itemCount[j] > maxValue) {
              maxValue = itemCount[j];
              maxIndex = j;
            }
          }
          modes[i] = itemArray[maxIndex];
        }
        return modes;
      };
      exports.skewness = function skewness(matrix, unbiased) {
        if (typeof unbiased === 'undefined') unbiased = true;
        var means = exports.mean(matrix);
        var n = matrix.length,
          l = means.length;
        var skew = new Array(l);
        for (var j = 0; j < l; j++) {
          var s2 = 0,
            s3 = 0;
          for (var i = 0; i < n; i++) {
            var dev = matrix[i][j] - means[j];
            s2 += dev * dev;
            s3 += dev * dev * dev;
          }
          var m2 = s2 / n;
          var m3 = s3 / n;
          var g = m3 / Math.pow(m2, 3 / 2);
          if (unbiased) {
            var a = Math.sqrt(n * (n - 1));
            var b = n - 2;
            skew[j] = a / b * g;
          } else {
            skew[j] = g;
          }
        }
        return skew;
      };
      exports.kurtosis = function kurtosis(matrix, unbiased) {
        if (typeof unbiased === 'undefined') unbiased = true;
        var means = exports.mean(matrix);
        var n = matrix.length,
          m = matrix[0].length;
        var kurt = new Array(m);
        for (var j = 0; j < m; j++) {
          var s2 = 0,
            s4 = 0;
          for (var i = 0; i < n; i++) {
            var dev = matrix[i][j] - means[j];
            s2 += dev * dev;
            s4 += dev * dev * dev * dev;
          }
          var m2 = s2 / n;
          var m4 = s4 / n;
          if (unbiased) {
            var v = s2 / (n - 1);
            var a = n * (n + 1) / ((n - 1) * (n - 2) * (n - 3));
            var b = s4 / (v * v);
            var c = (n - 1) * (n - 1) / ((n - 2) * (n - 3));
            kurt[j] = a * b - 3 * c;
          } else {
            kurt[j] = m4 / (m2 * m2) - 3;
          }
        }
        return kurt;
      };
      exports.standardError = function standardError(matrix) {
        var samples = matrix.length;
        var standardDeviations = exports.standardDeviation(matrix);
        var l = standardDeviations.length;
        var standardErrors = new Array(l);
        var sqrtN = Math.sqrt(samples);
        for (var i = 0; i < l; i++) {
          standardErrors[i] = standardDeviations[i] / sqrtN;
        }
        return standardErrors;
      };
      exports.covariance = function covariance(matrix, dimension) {
        return exports.scatter(matrix, undefined, dimension);
      };
      exports.scatter = function scatter(matrix, divisor, dimension) {
        if (typeof dimension === 'undefined') {
          dimension = 0;
        }
        if (typeof divisor === 'undefined') {
          if (dimension === 0) {
            divisor = matrix.length - 1;
          } else if (dimension === 1) {
            divisor = matrix[0].length - 1;
          }
        }
        var means = exports.mean(matrix, dimension);
        var rows = matrix.length;
        if (rows === 0) {
          return [[]];
        }
        var cols = matrix[0].length,
          cov,
          i,
          j,
          s,
          k;
        if (dimension === 0) {
          cov = new Array(cols);
          for (i = 0; i < cols; i++) {
            cov[i] = new Array(cols);
          }
          for (i = 0; i < cols; i++) {
            for (j = i; j < cols; j++) {
              s = 0;
              for (k = 0; k < rows; k++) {
                s += (matrix[k][j] - means[j]) * (matrix[k][i] - means[i]);
              }
              s /= divisor;
              cov[i][j] = s;
              cov[j][i] = s;
            }
          }
        } else if (dimension === 1) {
          cov = new Array(rows);
          for (i = 0; i < rows; i++) {
            cov[i] = new Array(rows);
          }
          for (i = 0; i < rows; i++) {
            for (j = i; j < rows; j++) {
              s = 0;
              for (k = 0; k < cols; k++) {
                s += (matrix[j][k] - means[j]) * (matrix[i][k] - means[i]);
              }
              s /= divisor;
              cov[i][j] = s;
              cov[j][i] = s;
            }
          }
        } else {
          throw new Error('Invalid dimension');
        }
        return cov;
      };
      exports.correlation = function correlation(matrix) {
        var means = exports.mean(matrix),
          standardDeviations = exports.standardDeviation(matrix, true, means),
          scores = exports.zScores(matrix, means, standardDeviations),
          rows = matrix.length,
          cols = matrix[0].length,
          i,
          j;
        var cor = new Array(cols);
        for (i = 0; i < cols; i++) {
          cor[i] = new Array(cols);
        }
        for (i = 0; i < cols; i++) {
          for (j = i; j < cols; j++) {
            var c = 0;
            for (var k = 0, l = scores.length; k < l; k++) {
              c += scores[k][j] * scores[k][i];
            }
            c /= rows - 1;
            cor[i][j] = c;
            cor[j][i] = c;
          }
        }
        return cor;
      };
      exports.zScores = function zScores(matrix, means, standardDeviations) {
        means = means || exports.mean(matrix);
        if (typeof standardDeviations === 'undefined') standardDeviations = exports.standardDeviation(matrix, true, means);
        return exports.standardize(exports.center(matrix, means, false), standardDeviations, true);
      };
      exports.center = function center(matrix, means, inPlace) {
        means = means || exports.mean(matrix);
        var result = matrix,
          l = matrix.length,
          i,
          j,
          jj;
        if (!inPlace) {
          result = new Array(l);
          for (i = 0; i < l; i++) {
            result[i] = new Array(matrix[i].length);
          }
        }
        for (i = 0; i < l; i++) {
          var row = result[i];
          for (j = 0, jj = row.length; j < jj; j++) {
            row[j] = matrix[i][j] - means[j];
          }
        }
        return result;
      };
      exports.standardize = function standardize(matrix, standardDeviations, inPlace) {
        if (typeof standardDeviations === 'undefined') standardDeviations = exports.standardDeviation(matrix);
        var result = matrix,
          l = matrix.length,
          i,
          j,
          jj;
        if (!inPlace) {
          result = new Array(l);
          for (i = 0; i < l; i++) {
            result[i] = new Array(matrix[i].length);
          }
        }
        for (i = 0; i < l; i++) {
          var resultRow = result[i];
          var sourceRow = matrix[i];
          for (j = 0, jj = resultRow.length; j < jj; j++) {
            if (standardDeviations[j] !== 0 && !isNaN(standardDeviations[j])) {
              resultRow[j] = sourceRow[j] / standardDeviations[j];
            }
          }
        }
        return result;
      };
      exports.weightedVariance = function weightedVariance(matrix, weights) {
        var means = exports.mean(matrix);
        var rows = matrix.length;
        if (rows === 0) return [];
        var cols = matrix[0].length;
        var vari = new Array(cols);
        for (var j = 0; j < cols; j++) {
          var sum = 0;
          var a = 0,
            b = 0;
          for (var i = 0; i < rows; i++) {
            var z = matrix[i][j] - means[j];
            var w = weights[i];
            sum += w * (z * z);
            b += w;
            a += w * w;
          }
          vari[j] = sum * (b / (b * b - a));
        }
        return vari;
      };
      exports.weightedMean = function weightedMean(matrix, weights, dimension) {
        if (typeof dimension === 'undefined') {
          dimension = 0;
        }
        var rows = matrix.length;
        if (rows === 0) return [];
        var cols = matrix[0].length,
          means,
          i,
          ii,
          j,
          w,
          row;
        if (dimension === 0) {
          means = new Array(cols);
          for (i = 0; i < cols; i++) {
            means[i] = 0;
          }
          for (i = 0; i < rows; i++) {
            row = matrix[i];
            w = weights[i];
            for (j = 0; j < cols; j++) {
              means[j] += row[j] * w;
            }
          }
        } else if (dimension === 1) {
          means = new Array(rows);
          for (i = 0; i < rows; i++) {
            means[i] = 0;
          }
          for (j = 0; j < rows; j++) {
            row = matrix[j];
            w = weights[j];
            for (i = 0; i < cols; i++) {
              means[j] += row[i] * w;
            }
          }
        } else {
          throw new Error('Invalid dimension');
        }
        var weightSum = arrayStat.sum(weights);
        if (weightSum !== 0) {
          for (i = 0, ii = means.length; i < ii; i++) {
            means[i] /= weightSum;
          }
        }
        return means;
      };
      exports.weightedCovariance = function weightedCovariance(matrix, weights, means, dimension) {
        dimension = dimension || 0;
        means = means || exports.weightedMean(matrix, weights, dimension);
        var s1 = 0,
          s2 = 0;
        for (var i = 0, ii = weights.length; i < ii; i++) {
          s1 += weights[i];
          s2 += weights[i] * weights[i];
        }
        var factor = s1 / (s1 * s1 - s2);
        return exports.weightedScatter(matrix, weights, means, factor, dimension);
      };
      exports.weightedScatter = function weightedScatter(matrix, weights, means, factor, dimension) {
        dimension = dimension || 0;
        means = means || exports.weightedMean(matrix, weights, dimension);
        if (typeof factor === 'undefined') {
          factor = 1;
        }
        var rows = matrix.length;
        if (rows === 0) {
          return [[]];
        }
        var cols = matrix[0].length,
          cov,
          i,
          j,
          k,
          s;
        if (dimension === 0) {
          cov = new Array(cols);
          for (i = 0; i < cols; i++) {
            cov[i] = new Array(cols);
          }
          for (i = 0; i < cols; i++) {
            for (j = i; j < cols; j++) {
              s = 0;
              for (k = 0; k < rows; k++) {
                s += weights[k] * (matrix[k][j] - means[j]) * (matrix[k][i] - means[i]);
              }
              cov[i][j] = s * factor;
              cov[j][i] = s * factor;
            }
          }
        } else if (dimension === 1) {
          cov = new Array(rows);
          for (i = 0; i < rows; i++) {
            cov[i] = new Array(rows);
          }
          for (i = 0; i < rows; i++) {
            for (j = i; j < rows; j++) {
              s = 0;
              for (k = 0; k < cols; k++) {
                s += weights[k] * (matrix[j][k] - means[j]) * (matrix[i][k] - means[i]);
              }
              cov[i][j] = s * factor;
              cov[j][i] = s * factor;
            }
          }
        } else {
          throw new Error('Invalid dimension');
        }
        return cov;
      };
    })(matrix);

    var array$1 = mlStat.array = array$2;
    mlStat.matrix = matrix;

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

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

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

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

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

    /**
    Search for an experimental monoisotopic mass and calculate the similarity
    * @param {object}   [options={}]
    * @param {array}    [options.databases] - an array containing the name of the databases so search, by default all
    * @param {boolean}  [options.flatten] - should we return the array as a flat result
    * @param {function} [options.onStep] - Callback to do after each step
    * @param {string}   [options.ionizations=''] - Comma separated list of ionizations (to charge the molecule)
    * @param {object}   [options.minSimilarity=0.5] - min similarity value

    * @param {object}   [options.filter={}]
    * @param {boolean}  [options.filter.forceIonization=false] - If true ignore existing ionizations
    * @param {number}   [options.filter.msem] - Observed monoisotopic mass in mass spectrometer
    * @param {number}   [options.filter.precision=1000] - The precision on the experimental mass
    * @param {number}   [options.filter.minCharge=-Infinity] - Minimal charge
    * @param {number}   [options.filter.maxCharge=+Infinity] - Maximal charge
    * @param {boolean}  [options.filter.absoluteCharge=false] - If true, the charge is absolute (so between 0 and +Infinity by default)
    * @param {object}   [options.filter.unsaturation={}]
    * @param {number}   [options.filter.unsaturation.min=-Infinity] - Minimal unsaturation
    * @param {number}   [options.filter.unsaturation.max=+Infinity] - Maximal unsaturation
    * @param {boolean}  [options.filter.unsaturation.onlyInteger=false] - Integer unsaturation
    * @param {boolean}  [options.filter.unsaturation.onlyNonInteger=false] - Non integer unsaturation
    * @param {object}   [options.filter.atoms] - object of atom:{min, max}
    * @param {object}   [options.filter.callback] - a function to filter the MF
    * @param {object}   [options.similarity={}]
    * @param {object}   [options.similarity.widthBottom]
    * @param {object}   [options.similarity.widthTop]
    * @param {object}   [options.similarity.widthFunction] - function called with mass that should return an object width containing top and bottom
    * @param {object}   [options.similarity.zone={}]
    * @param {object}   [options.similarity.zone.low=-0.5] - window shift based on observed monoisotopic mass
    * @param {object}   [options.similarity.zone.high=2.5] - to value for the comparison window
    * @param {object}   [options.similarity.common]
    * @returns {Promise}
    */

    async function searchSimilarity(emdb) {
      let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      const {
        similarity = {},
        minSimilarity = 0.5,
        filter = {},
        onStep
      } = options;
      let width = {
        bottom: similarity.widthBottom,
        top: similarity.widthTop
      };
      if (!emdb.experimentalSpectrum || !emdb.experimentalSpectrum.data.x.length > 0) {
        throw Error('You need to add an experimental spectrum first using setMassSpectrum');
      }
      let experimentalData = emdb.experimentalSpectrum.data;
      let sumY = emdb.experimentalSpectrum.sumY();

      // the result of emdb query will be stored in a property 'ms'

      let results = emdb.searchMSEM(filter.msem, options);
      let flatEntries = [];
      if (!options.flatten) {
        for (let database of Object.keys(results)) {
          for (let entry of results[database]) {
            flatEntries.push(entry);
          }
        }
      } else {
        flatEntries = results;
      }
      let {
        widthFunction,
        zone = {}
      } = similarity;
      if (widthFunction && typeof widthFunction === 'string') {
        // eslint-disable-next-line no-new-func
        widthFunction = new Function('mass', widthFunction);
        let checkTopBottom = widthFunction(123);
        if (!checkTopBottom.bottom || !checkTopBottom.top) {
          throw Error('widthFunction should return an object with bottom and top properties');
        }
      }
      const {
        low = -0.5,
        high = 2.5
      } = zone;

      // we need to calculate the similarity of the isotopic distribution
      let similarityProcessor = new Comparator(similarity);
      similarityProcessor.setPeaks1([experimentalData.x, experimentalData.y]);
      for (let i = 0; i < flatEntries.length; i++) {
        const entry = flatEntries[i];
        if (onStep) await onStep(i);
        let isotopicDistribution = new IsotopicDistribution(entry.mf, {
          allowNeutral: false,
          ionizations: [entry.ionization]
        });
        let distribution = isotopicDistribution.getDistribution();
        // we need to define the comparison zone that depends of the charge
        let from = entry.ms.em + low / Math.abs(entry.ms.charge);
        let to = entry.ms.em + high / Math.abs(entry.ms.charge);
        similarityProcessor.setFromTo(from, to);
        if (widthFunction) {
          width = widthFunction(entry.ms.em);
          similarityProcessor.setTrapezoid(width.bottom, width.top);
        }
        similarityProcessor.setPeaks2([distribution.xs, distribution.ys]);
        let result = similarityProcessor.getSimilarity();
        result.extractInfo1.from = from;
        result.extractInfo1.to = to;
        if (result.similarity > minSimilarity) {
          entry.ms.similarity = {
            value: result.similarity,
            experimental: result.extract1,
            theoretical: result.extract2,
            difference: result.diff,
            experimentalInfo: result.extractInfo1,
            thereoticalInfo: result.extractInfo2,
            quantity: result.extractInfo1.sum / sumY,
            width
          };
        }
      }
      if (!options.flatten) {
        for (let database of Object.keys(results)) {
          results[database] = results[database].filter(entry => entry.ms.similarity).sort((a, b) => b.ms.similarity.value - a.ms.similarity.value);
          for (let entry of results[database]) {
            flatEntries.push(entry);
          }
        }
      } else {
        results = results.filter(entry => entry.ms.similarity).sort((a, b) => b.ms.similarity.value - a.ms.similarity.value);
      }
      return results;
    }

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

    function maybeToPrecision(value, digits) {
      if (value < 0) {
        value = 0 - value;
        if (typeof digits === 'number') {
          return `- ${value.toPrecision(digits)}`;
        } else {
          return `- ${value.toString()}`;
        }
      } else {
        if (typeof digits === 'number') {
          return value.toPrecision(digits);
        } else {
          return value.toString();
        }
      }
    }

    function checkArraySize(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 (let i = 0; i < x.length; i++) {
            y.push(this._predict(x[i]));
          }
          return y;
        } else {
          throw new TypeError('x must be a number or array');
        }
      }
      _predict() {
        throw new Error('_predict must be implemented');
      }
      train() {
        // Do nothing for this package
      }
      toString() {
        return '';
      }
      toLaTeX() {
        return '';
      }

      /**
       * Return the correlation coefficient of determination (r) and chi-square.
       * @param {Array<number>} x
       * @param {Array<number>} y
       * @return {object}
       */
      score(x, y) {
        if (!isAnyArray$1(x) || !isAnyArray$1(y) || x.length !== y.length) {
          throw new Error('x and y must be arrays of the same length');
        }
        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: r,
          r2: r * r,
          chi2: chi2,
          rmsd: Math.sqrt(rmsd / n)
        };
      }
    }

    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); !0;) {
            if (f <= e) return d[j];
            if (f == e + 1) return d[e] > d[f] && b(d, e, f), d[j];
            for (g = c(e, f), d[g] > d[f] && b(d, g, f), d[e] > d[f] && b(d, e, f), d[g] > d[e] && b(d, g, e), b(d, g, e + 1), h = e + 1, i = f; !0;) {
              do h++; while (d[e] > d[h]);
              do i--; while (d[i] > d[e]);
              if (i < h) break;
              b(d, h, i);
            }
            b(d, e, i), i <= j && (e = h), i >= j && (f = i - 1);
          }
        }
        var b = function b(d, e, f) {
            var _ref;
            return _ref = [d[f], d[e]], d[e] = _ref[0], d[f] = _ref[1], _ref;
          },
          c = function c(d, e) {
            return ~~((d + e) / 2);
          };
        module.exports ? module.exports = a : window.median = a;
      })();
    })(medianQuickselect_min);
    var quickSelectMedian = medianQuickselect_min.exports;

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

    class TheilSenRegression extends BaseRegression {
      /**
       * Theil–Sen estimator
       * https://en.wikipedia.org/wiki/Theil%E2%80%93Sen_estimator
       * @param {Array<number>|boolean} x
       * @param {Array<number>|object} y
       * @constructor
       */
      constructor(x, y) {
        super();
        if (x === true) {
          // loads the model
          this.slope = y.slope;
          this.intercept = y.intercept;
          this.coefficients = y.coefficients;
        } else {
          // creates the model
          checkArraySize(x, y);
          theilSen(this, x, y);
        }
      }
      toJSON() {
        return {
          name: 'TheilSenRegression',
          slope: this.slope,
          intercept: this.intercept
        };
      }
      _predict(input) {
        return this.slope * input + this.intercept;
      }
      computeX(input) {
        return (input - this.intercept) / this.slope;
      }
      toString(precision) {
        var result = 'f(x) = ';
        if (this.slope) {
          var xFactor = maybeToPrecision(this.slope, precision);
          result += `${Math.abs(xFactor - 1) < 1e-5 ? '' : `${xFactor} * `}x`;
          if (this.intercept) {
            var absIntercept = Math.abs(this.intercept);
            var operator = absIntercept === this.intercept ? '+' : '-';
            result += ` ${operator} ${maybeToPrecision(absIntercept, precision)}`;
          }
        } else {
          result += maybeToPrecision(this.intercept, precision);
        }
        return result;
      }
      toLaTeX(precision) {
        return this.toString(precision);
      }
      static load(json) {
        if (json.name !== 'TheilSenRegression') {
          throw new TypeError('not a Theil-Sen model');
        }
        return new TheilSenRegression(true, json);
      }
    }
    function theilSen(regression, x, y) {
      let len = x.length;
      let slopes = new Array(len * len);
      let count = 0;
      for (let i = 0; i < len; ++i) {
        for (let j = i + 1; j < len; ++j) {
          if (x[i] !== x[j]) {
            slopes[count++] = (y[j] - y[i]) / (x[j] - x[i]);
          }
        }
      }
      slopes.length = count;
      let medianSlope = median(slopes);
      let cuts = new Array(len);
      for (let i = 0; i < len; ++i) {
        cuts[i] = y[i] - medianSlope * x[i];
      }
      regression.slope = medianSlope;
      regression.intercept = median(cuts);
      regression.coefficients = [regression.intercept, regression.slope];
    }

    /**
     * Checks if input is of type array
     *
     * @param input - input
     */
    function xCheck$1(input) {
      let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      const {
        minLength
      } = options;
      if (!isAnyArray$1(input)) {
        throw new TypeError('input must be an array');
      }
      if (input.length === 0) {
        throw new TypeError('input must not be empty');
      }
      if (minLength && input.length < minLength) {
        throw new Error(`input must have a length of at least ${minLength}`);
      }
    }

    /**
     * Returns the closest index of a `target`
     *
     * @param array - array of numbers
     * @param target - target
     * @returns - closest index
     */
    function xFindClosestIndex$1(array, target) {
      let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
      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$1(x) {
      let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      let {
        fromIndex,
        toIndex,
        from,
        to
      } = options;
      if (fromIndex === undefined) {
        if (from !== undefined) {
          fromIndex = xFindClosestIndex$1(x, from);
        } else {
          fromIndex = 0;
        }
      }
      if (toIndex === undefined) {
        if (to !== undefined) {
          toIndex = xFindClosestIndex$1(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
      };
    }

    /**
     * Computes the maximal value of an array of values
     *
     * @param array - array of numbers
     * @param options - options
     */
    function xMaxValue$1(array) {
      let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      xCheck$1(array);
      const {
        fromIndex,
        toIndex
      } = xGetFromToIndex$1(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) {
      let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      xCheck$1(array);
      const {
        fromIndex,
        toIndex
      } = xGetFromToIndex$1(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 function that allows post-calibration on mass spectra based on the error in assignment
     * @param {*} similarities
     * @param {object} [options={}]
     * @returns
     */
    function massShifts(similarities) {
      let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      const {
        minSimilarity = 0.95,
        minLength = 10
      } = options;
      let results = [];
      if (!Array.isArray(similarities)) {
        for (let key of results) {
          for (let entry of results[key]) {
            results.push(entry);
          }
        }
      } else {
        results = similarities;
      }
      results = results.filter(result => result.ms && result.ms.similarity && result.ms.similarity.value > minSimilarity);
      if (results.length < minLength) {
        throw new Error(`X rescale can not be applied. We need at least ${minLength} peaks with over ${Math.round(minSimilarity * 100)}% similarity`);
      }
      const data = results.map(result => {
        return {
          em: result.ms.em,
          delta: result.ms.delta
        };
      }).sort((a, b) => a.em - b.em);
      let shifts = {
        x: [],
        y: []
      };
      data.forEach(datum => {
        shifts.x.push(Number(datum.em));
        shifts.y.push(Number(datum.delta));
      });
      const regression = new TheilSenRegression(shifts.x, shifts.y);
      let minX = xMinValue(shifts.x);
      let maxX = xMaxValue$1(shifts.x);
      let shiftsPPM = {
        x: shifts.x,
        y: []
      };
      data.forEach(datum => {
        shiftsPPM.y.push(Number(datum.delta / datum.em * 1e6));
      });
      let regressionChart = {
        x: [],
        y: []
      };
      for (let i = minX; i < maxX; i += (maxX - minX) / 1000) {
        regressionChart.x.push(i);
        regressionChart.y.push(regression.predict(i));
      }
      return {
        shifts,
        shiftsPPM,
        fit: regressionChart,
        score: regression.score(shifts.x, shifts.y),
        predictFct: regression.predict.bind(regression),
        tex: regression.toLaTeX(3),
        slope: regression.slope,
        intercept: regression.intercept,
        predictFctString: `${regression.slope} * mass + ${regression.intercept}`
      };
    }

    /**
     * A class that deals with database of monoisotopic mass and molecular formula
     */
    class EMDB$1 {
      constructor() {
        this.databases = {};
        this.experimentalSpectrum = undefined;
      }

      /**
       *
       * @param {*} data
       * @param {object} [options={}]
       * @param {number} [options.normed=true] Should we normed (sum Y to 1) the experimental spectrum ?
       */
      setExperimentalSpectrum(data) {
        let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
        const {
          normed = true
        } = options;
        if (normed) {
          this.experimentalSpectrum = new Spectrum$1(data).normedY();
        } else {
          this.experimentalSpectrum = new Spectrum$1(data);
        }
        return this.experimentalSpectrum;
      }

      /**
       * Add a new database using the KnapSack content
       * @param {*} options
       */
      async loadKnapSack() {
        let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
        const {
          databaseName = 'knapSack',
          forceReload = false
        } = options;
        if (this.databases[databaseName] && !forceReload) return;
        this.databases[databaseName] = await loadKnapSack();
      }

      /**
       * Add a new database of 12000 commercial products
       * @param {*} options
       */
      async loadCommercials() {
        let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
        const {
          databaseName = 'commercials',
          forceReload = false
        } = options;
        if (this.databases[databaseName] && !forceReload) return;
        this.databases[databaseName] = await loadCommercials();
      }
      get(databaseName) {
        return this.databases[databaseName];
      }

      /**
       * Load the contaminants database from a google sheet document
       * @param {object} [options={}]
       * @param {string} [options.databaseName='contaminants']
       * @param {string} [options.forceReload=false]
       */
      async loadContaminants() {
        let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
        const {
          databaseName = 'contaminants',
          forceReload = false
        } = options;
        if (this.databases[databaseName] && !forceReload) return;
        this.databases[databaseName] = await loadGoogleSheet();
      }

      /**
       * Load a google sheet containing MF information
       * @param {object} [options={}]
       * @param {string} [options.databaseName='sheet']
       * @param {string} [options.forceReload=false]
       */

      async loadGoogleSheet() {
        let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
        const {
          databaseName = 'sheet',
          forceReload = false
        } = options;
        if (this.databases[databaseName] && !forceReload) return;
        this.databases[databaseName] = await loadGoogleSheet();
      }
      async loadTest() {
        await this.fromArray(['C1-100'], {
          databaseName: 'test',
          ionizations: '+'
        });
      }
      async loadNeutralTest() {
        let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
        const {
          maxC = 100
        } = options;
        await this.fromArray([`C1-${maxC}`], {
          databaseName: 'test'
        });
      }
      async fromMonoisotopicMass(mass) {
        let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
        const {
          databaseName = 'monoisotopic',
          append = false
        } = options;
        let result = await fromMonoisotopicMass(mass, options);
        replaceOrAppend(this, databaseName, result.mfs, append);
        return result;
      }
      async fromArray(sequence) {
        let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
        const {
          databaseName = 'generated',
          append = false,
          estimate
        } = options;
        const results = await fromArray(sequence, options);
        if (estimate) return results;
        replaceOrAppend(this, databaseName, results, append);
      }
      async fromMolecules(entries, ocl) {
        let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
        const {
          databaseName = 'molecules',
          append = false
        } = options;
        const results = await fromMolecules(entries, ocl, options);
        replaceOrAppend(this, databaseName, results, append);
      }
      async fromRange(sequence) {
        let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
        const {
          databaseName = 'generated',
          append = false,
          estimate
        } = options;
        const results = await fromRange(sequence, options);
        if (estimate) return results;
        replaceOrAppend(this, databaseName, results, append);
      }
      async fromPeptidicSequence(sequence) {
        let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
        const {
          databaseName = 'peptidic',
          append = false,
          estimate
        } = options;
        const results = await fromPeptidicSequence(sequence, options);
        if (estimate) return results;
        replaceOrAppend(this, databaseName, results, append);
      }

      /**
       *
       * @param {string} databaseName
       * @param {object} [options={}]
       * @param {number} [options.precision=100]
       * @param {string} [options.ionizations='']
       * @returns
       */
      async appendFragmentsInfo(databaseName) {
        let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
        const database = this.databases[databaseName];
        await appendFragmentsInfo(this.experimentalSpectrum, database, options);
        return database;
      }
      async fromNucleicSequence(sequence) {
        let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
        const {
          databaseName = 'nucleic',
          append = false,
          estimate
        } = options;
        const results = await fromNucleicSequence(sequence, options);
        if (estimate) return results;
        replaceOrAppend(this, databaseName, results, append);
      }
      listDatabases() {
        return Object.keys(this.databases).sort();
      }
      getInfo() {
        return {
          databases: Object.keys(this.databases).sort().map(key => {
            return {
              name: key,
              nbEntries: this.databases[key].length
            };
          })
        };
      }
      search(filter) {
        let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
        return search$2(this, filter, options);
      }
      searchMSEM(filter) {
        let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
        return searchMSEM(this, filter, options);
      }
      searchSimilarity() {
        let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
        return searchSimilarity(this, options);
      }
    }
    function replaceOrAppend(emdb, databaseName, results) {
      let append = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
      if (!emdb.databases[databaseName] || !append) {
        emdb.databases[databaseName] = results;
        return;
      }
      emdb.databases[databaseName] = emdb.databases[databaseName].concat(results);
    }

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

    function max(input) {
      var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      if (!isAnyArray(input)) {
        throw new TypeError('input must be an array');
      }
      if (input.length === 0) {
        throw new TypeError('input must not be empty');
      }
      var _options$fromIndex = options.fromIndex,
        fromIndex = _options$fromIndex === void 0 ? 0 : _options$fromIndex,
        _options$toIndex = options.toIndex,
        toIndex = _options$toIndex === void 0 ? input.length : _options$toIndex;
      if (fromIndex < 0 || fromIndex >= input.length || !Number.isInteger(fromIndex)) {
        throw new Error('fromIndex must be a positive integer smaller than length');
      }
      if (toIndex <= fromIndex || toIndex > input.length || !Number.isInteger(toIndex)) {
        throw new Error('toIndex must be an integer greater than fromIndex and at most equal to length');
      }
      var maxValue = input[fromIndex];
      for (var i = fromIndex + 1; i < toIndex; i++) {
        if (input[i] > maxValue) maxValue = input[i];
      }
      return maxValue;
    }

    function min(input) {
      var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      if (!isAnyArray(input)) {
        throw new TypeError('input must be an array');
      }
      if (input.length === 0) {
        throw new TypeError('input must not be empty');
      }
      var _options$fromIndex = options.fromIndex,
        fromIndex = _options$fromIndex === void 0 ? 0 : _options$fromIndex,
        _options$toIndex = options.toIndex,
        toIndex = _options$toIndex === void 0 ? input.length : _options$toIndex;
      if (fromIndex < 0 || fromIndex >= input.length || !Number.isInteger(fromIndex)) {
        throw new Error('fromIndex must be a positive integer smaller than length');
      }
      if (toIndex <= fromIndex || toIndex > input.length || !Number.isInteger(toIndex)) {
        throw new Error('toIndex must be an integer greater than fromIndex and at most equal to length');
      }
      var minValue = input[fromIndex];
      for (var i = fromIndex + 1; i < toIndex; i++) {
        if (input[i] < minValue) minValue = input[i];
      }
      return minValue;
    }

    function rescale(input) {
      var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      if (!isAnyArray(input)) {
        throw new TypeError('input must be an array');
      } else if (input.length === 0) {
        throw new TypeError('input must not be empty');
      }
      var output;
      if (options.output !== undefined) {
        if (!isAnyArray(options.output)) {
          throw new TypeError('output option must be an array if specified');
        }
        output = options.output;
      } else {
        output = new Array(input.length);
      }
      var currentMin = min(input);
      var currentMax = max(input);
      if (currentMin === currentMax) {
        throw new RangeError('minimum and maximum input values are equal. Cannot rescale a constant array');
      }
      var _options$min = options.min,
        minValue = _options$min === void 0 ? options.autoMinMax ? currentMin : 0 : _options$min,
        _options$max = options.max,
        maxValue = _options$max === void 0 ? options.autoMinMax ? currentMax : 1 : _options$max;
      if (minValue >= maxValue) {
        throw new RangeError('min option must be smaller than max option');
      }
      var factor = (maxValue - minValue) / (currentMax - currentMin);
      for (var i = 0; i < input.length; i++) {
        output[i] = (input[i] - currentMin) * factor + minValue;
      }
      return output;
    }

    const indent = ' '.repeat(2);
    const indentData = ' '.repeat(4);
    function inspectMatrix() {
      return inspectMatrixWithOptions(this);
    }
    function inspectMatrixWithOptions(matrix) {
      let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      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, Math.pow(this.get(i, j), value));
          }
        }
        return this;
      };
      AbstractMatrix.prototype.powM = function powM(matrix) {
        matrix = Matrix.checkMatrix(matrix);
        if (this.rows !== matrix.rows || this.columns !== matrix.columns) {
          throw new RangeError('Matrices dimensions must be equal');
        }
        for (let i = 0; i < this.rows; i++) {
          for (let j = 0; j < this.columns; j++) {
            this.set(i, j, Math.pow(this.get(i, j), matrix.get(i, j)));
          }
        }
        return this;
      };
    }

    /**
     * @private
     * Check that a row index is not out of bounds
     * @param {Matrix} matrix
     * @param {number} index
     * @param {boolean} [outer]
     */
    function checkRowIndex(matrix, index, outer) {
      let max = outer ? matrix.rows : matrix.rows - 1;
      if (index < 0 || index > max) {
        throw new RangeError('Row index out of range');
      }
    }

    /**
     * @private
     * Check that a column index is not out of bounds
     * @param {Matrix} matrix
     * @param {number} index
     * @param {boolean} [outer]
     */
    function checkColumnIndex(matrix, index, outer) {
      let max = outer ? matrix.columns : matrix.columns - 1;
      if (index < 0 || index > max) {
        throw new RangeError('Column index out of range');
      }
    }

    /**
     * @private
     * Check that the provided vector is an array with the right length
     * @param {Matrix} matrix
     * @param {Array|Matrix} vector
     * @return {Array}
     * @throws {RangeError}
     */
    function checkRowVector(matrix, vector) {
      if (vector.to1DArray) {
        vector = vector.to1DArray();
      }
      if (vector.length !== matrix.columns) {
        throw new RangeError('vector size must be the same as the number of columns');
      }
      return vector;
    }

    /**
     * @private
     * Check that the provided vector is an array with the right length
     * @param {Matrix} matrix
     * @param {Array|Matrix} vector
     * @return {Array}
     * @throws {RangeError}
     */
    function checkColumnVector(matrix, vector) {
      if (vector.to1DArray) {
        vector = vector.to1DArray();
      }
      if (vector.length !== matrix.rows) {
        throw new RangeError('vector size must be the same as the number of rows');
      }
      return vector;
    }
    function 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(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) {
      let value = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
      let array = [];
      for (let i = 0; i < length; i++) {
        array.push(value);
      }
      return array;
    }
    function checkNumber(name, value) {
      if (typeof value !== 'number') {
        throw new TypeError(`${name} must be a number`);
      }
    }
    function checkNonEmpty(matrix) {
      if (matrix.isEmpty()) {
        throw new Error('Empty matrix has no elements to index');
      }
    }

    function sumByRow(matrix) {
      let sum = newArray(matrix.rows);
      for (let i = 0; i < matrix.rows; ++i) {
        for (let j = 0; j < matrix.columns; ++j) {
          sum[i] += matrix.get(i, j);
        }
      }
      return sum;
    }
    function sumByColumn(matrix) {
      let sum = newArray(matrix.columns);
      for (let i = 0; i < matrix.rows; ++i) {
        for (let j = 0; j < matrix.columns; ++j) {
          sum[j] += matrix.get(i, j);
        }
      }
      return sum;
    }
    function sumAll(matrix) {
      let v = 0;
      for (let i = 0; i < matrix.rows; i++) {
        for (let j = 0; j < matrix.columns; j++) {
          v += matrix.get(i, j);
        }
      }
      return v;
    }
    function productByRow(matrix) {
      let sum = newArray(matrix.rows, 1);
      for (let i = 0; i < matrix.rows; ++i) {
        for (let j = 0; j < matrix.columns; ++j) {
          sum[i] *= matrix.get(i, j);
        }
      }
      return sum;
    }
    function productByColumn(matrix) {
      let sum = newArray(matrix.columns, 1);
      for (let i = 0; i < matrix.rows; ++i) {
        for (let j = 0; j < matrix.columns; ++j) {
          sum[j] *= matrix.get(i, j);
        }
      }
      return sum;
    }
    function productAll(matrix) {
      let v = 1;
      for (let i = 0; i < matrix.rows; i++) {
        for (let j = 0; j < matrix.columns; j++) {
          v *= matrix.get(i, j);
        }
      }
      return v;
    }
    function varianceByRow(matrix, unbiased, mean) {
      const rows = matrix.rows;
      const cols = matrix.columns;
      const variance = [];
      for (let i = 0; i < rows; i++) {
        let sum1 = 0;
        let sum2 = 0;
        let x = 0;
        for (let j = 0; j < cols; j++) {
          x = matrix.get(i, j) - mean[i];
          sum1 += x;
          sum2 += x * x;
        }
        if (unbiased) {
          variance.push((sum2 - sum1 * sum1 / cols) / (cols - 1));
        } else {
          variance.push((sum2 - sum1 * sum1 / cols) / cols);
        }
      }
      return variance;
    }
    function varianceByColumn(matrix, unbiased, mean) {
      const rows = matrix.rows;
      const cols = matrix.columns;
      const variance = [];
      for (let j = 0; j < cols; j++) {
        let sum1 = 0;
        let sum2 = 0;
        let x = 0;
        for (let i = 0; i < rows; i++) {
          x = matrix.get(i, j) - mean[j];
          sum1 += x;
          sum2 += x * x;
        }
        if (unbiased) {
          variance.push((sum2 - sum1 * sum1 / rows) / (rows - 1));
        } else {
          variance.push((sum2 - sum1 * sum1 / rows) / rows);
        }
      }
      return variance;
    }
    function varianceAll(matrix, unbiased, mean) {
      const rows = matrix.rows;
      const cols = matrix.columns;
      const size = rows * cols;
      let sum1 = 0;
      let sum2 = 0;
      let x = 0;
      for (let i = 0; i < rows; i++) {
        for (let j = 0; j < cols; j++) {
          x = matrix.get(i, j) - mean;
          sum1 += x;
          sum2 += x * x;
        }
      }
      if (unbiased) {
        return (sum2 - sum1 * sum1 / size) / (size - 1);
      } else {
        return (sum2 - sum1 * sum1 / size) / size;
      }
    }
    function centerByRow(matrix, mean) {
      for (let i = 0; i < matrix.rows; i++) {
        for (let j = 0; j < matrix.columns; j++) {
          matrix.set(i, j, matrix.get(i, j) - mean[i]);
        }
      }
    }
    function centerByColumn(matrix, mean) {
      for (let i = 0; i < matrix.rows; i++) {
        for (let j = 0; j < matrix.columns; j++) {
          matrix.set(i, j, matrix.get(i, j) - mean[j]);
        }
      }
    }
    function centerAll(matrix, mean) {
      for (let i = 0; i < matrix.rows; i++) {
        for (let j = 0; j < matrix.columns; j++) {
          matrix.set(i, j, matrix.get(i, j) - mean);
        }
      }
    }
    function getScaleByRow(matrix) {
      const scale = [];
      for (let i = 0; i < matrix.rows; i++) {
        let sum = 0;
        for (let j = 0; j < matrix.columns; j++) {
          sum += Math.pow(matrix.get(i, j), 2) / (matrix.columns - 1);
        }
        scale.push(Math.sqrt(sum));
      }
      return scale;
    }
    function scaleByRow(matrix, scale) {
      for (let i = 0; i < matrix.rows; i++) {
        for (let j = 0; j < matrix.columns; j++) {
          matrix.set(i, j, matrix.get(i, j) / scale[i]);
        }
      }
    }
    function getScaleByColumn(matrix) {
      const scale = [];
      for (let j = 0; j < matrix.columns; j++) {
        let sum = 0;
        for (let i = 0; i < matrix.rows; i++) {
          sum += Math.pow(matrix.get(i, j), 2) / (matrix.rows - 1);
        }
        scale.push(Math.sqrt(sum));
      }
      return scale;
    }
    function scaleByColumn(matrix, scale) {
      for (let i = 0; i < matrix.rows; i++) {
        for (let j = 0; j < matrix.columns; j++) {
          matrix.set(i, j, matrix.get(i, j) / scale[j]);
        }
      }
    }
    function getScaleAll(matrix) {
      const divider = matrix.size - 1;
      let sum = 0;
      for (let j = 0; j < matrix.columns; j++) {
        for (let i = 0; i < matrix.rows; i++) {
          sum += Math.pow(matrix.get(i, j), 2) / divider;
        }
      }
      return Math.sqrt(sum);
    }
    function scaleAll(matrix, scale) {
      for (let i = 0; i < matrix.rows; i++) {
        for (let j = 0; j < matrix.columns; j++) {
          matrix.set(i, j, matrix.get(i, j) / scale);
        }
      }
    }

    class AbstractMatrix {
      static from1DArray(newRows, newColumns, newData) {
        let length = newRows * newColumns;
        if (length !== newData.length) {
          throw new RangeError('data length does not match given dimensions');
        }
        let newMatrix = new Matrix$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) {
        let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
        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) {
        let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
        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;
      }
      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() {
        let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
        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() {
        let type = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'frobenius';
        let result = 0;
        if (type === 'max') {
          return this.max();
        } else if (type === 'frobenius') {
          for (let i = 0; i < this.rows; i++) {
            for (let j = 0; j < this.columns; j++) {
              result = result + this.get(i, j) * this.get(i, j);
            }
          }
          return Math.sqrt(result);
        } else {
          throw new RangeError(`unknown norm type: ${type}`);
        }
      }
      cumulativeSum() {
        let sum = 0;
        for (let i = 0; i < this.rows; i++) {
          for (let j = 0; j < this.columns; j++) {
            sum += this.get(i, j);
            this.set(i, j, sum);
          }
        }
        return this;
      }
      dot(vector2) {
        if (AbstractMatrix.isMatrix(vector2)) vector2 = vector2.to1DArray();
        let vector1 = this.to1DArray();
        if (vector1.length !== vector2.length) {
          throw new RangeError('vectors do not have the same size');
        }
        let dot = 0;
        for (let i = 0; i < vector1.length; i++) {
          dot += vector1[i] * vector2[i];
        }
        return dot;
      }
      mmul(other) {
        other = Matrix$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;
      }
      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 resultat = AbstractMatrix.zeros(2 * c11.rows, 2 * c11.columns);
          resultat = resultat.setSubMatrix(c11, 0, 0);
          resultat = resultat.setSubMatrix(c12, c11.rows, 0);
          resultat = resultat.setSubMatrix(c21, 0, c11.columns);
          resultat = resultat.setSubMatrix(c22, c11.rows, c11.columns);
          return resultat.subMatrix(0, rows - 1, 0, cols - 1);
        }
        return blockMult(x, y, r, c);
      }
      scaleRows() {
        let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
        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() {
        let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
        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: min,
              max: max,
              output: column
            });
          }
          newMatrix.setColumn(i, column);
        }
        return newMatrix;
      }
      flipRows() {
        const middle = Math.ceil(this.columns / 2);
        for (let i = 0; i < this.rows; i++) {
          for (let j = 0; j < middle; j++) {
            let first = this.get(i, j);
            let last = this.get(i, this.columns - 1 - j);
            this.set(i, j, last);
            this.set(i, this.columns - 1 - j, first);
          }
        }
        return this;
      }
      flipColumns() {
        const middle = Math.ceil(this.rows / 2);
        for (let j = 0; j < this.columns; j++) {
          for (let i = 0; i < middle; i++) {
            let first = this.get(i, j);
            let last = this.get(this.rows - 1 - i, j);
            this.set(i, j, last);
            this.set(this.rows - 1 - i, j, first);
          }
        }
        return this;
      }
      kroneckerProduct(other) {
        other = Matrix$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() {
        let compareFunction = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : compareNumbers;
        for (let i = 0; i < this.rows; i++) {
          this.setRow(i, this.getRow(i).sort(compareFunction));
        }
        return this;
      }
      sortColumns() {
        let compareFunction = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : compareNumbers;
        for (let i = 0; i < this.columns; i++) {
          this.setColumn(i, this.getColumn(i).sort(compareFunction));
        }
        return this;
      }
      subMatrix(startRow, endRow, startColumn, endColumn) {
        checkRange(this, startRow, endRow, startColumn, endColumn);
        let newMatrix = new Matrix$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(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() {
        let newMatrix = new Matrix$1(this.rows, this.columns);
        for (let row = 0; row < this.rows; row++) {
          for (let column = 0; column < this.columns; column++) {
            newMatrix.set(row, column, this.get(row, column));
          }
        }
        return newMatrix;
      }
      sum(by) {
        switch (by) {
          case 'row':
            return sumByRow(this);
          case 'column':
            return sumByColumn(this);
          case undefined:
            return sumAll(this);
          default:
            throw new Error(`invalid option: ${by}`);
        }
      }
      product(by) {
        switch (by) {
          case 'row':
            return productByRow(this);
          case 'column':
            return productByColumn(this);
          case undefined:
            return productAll(this);
          default:
            throw new Error(`invalid option: ${by}`);
        }
      }
      mean(by) {
        const sum = this.sum(by);
        switch (by) {
          case 'row':
            {
              for (let i = 0; i < this.rows; i++) {
                sum[i] /= this.columns;
              }
              return sum;
            }
          case 'column':
            {
              for (let i = 0; i < this.columns; i++) {
                sum[i] /= this.rows;
              }
              return sum;
            }
          case undefined:
            return sum / this.size;
          default:
            throw new Error(`invalid option: ${by}`);
        }
      }
      variance(by) {
        let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
        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) {
        let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
        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) {
        let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
        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);
      }
    }
    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;
    class Matrix$1 extends AbstractMatrix {
      constructor(nRows, nColumns) {
        super();
        if (Matrix$1.isMatrix(nRows)) {
          // eslint-disable-next-line no-constructor-return
          return nRows.clone();
        } else if (Number.isInteger(nRows) && nRows >= 0) {
          // Create an empty matrix
          this.data = [];
          if (Number.isInteger(nColumns) && nColumns >= 0) {
            for (let i = 0; i < nRows; i++) {
              this.data.push(new Float64Array(nColumns));
            }
          } else {
            throw new TypeError('nColumns must be a positive integer');
          }
        } else if (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]));
          }
        } else {
          throw new TypeError('First argument must be a positive number or an array');
        }
        this.rows = nRows;
        this.columns = nColumns;
      }
      set(rowIndex, columnIndex, value) {
        this.data[rowIndex][columnIndex] = value;
        return this;
      }
      get(rowIndex, columnIndex) {
        return this.data[rowIndex][columnIndex];
      }
      removeRow(index) {
        checkRowIndex(this, index);
        this.data.splice(index, 1);
        this.rows -= 1;
        return this;
      }
      addRow(index, array) {
        if (array === undefined) {
          array = index;
          index = this.rows;
        }
        checkRowIndex(this, index, true);
        array = Float64Array.from(checkRowVector(this, array));
        this.data.splice(index, 0, array);
        this.rows += 1;
        return this;
      }
      removeColumn(index) {
        checkColumnIndex(this, index);
        for (let i = 0; i < this.rows; i++) {
          const newRow = new Float64Array(this.columns - 1);
          for (let j = 0; j < index; j++) {
            newRow[j] = this.data[i][j];
          }
          for (let j = index + 1; j < this.columns; j++) {
            newRow[j - 1] = this.data[i][j];
          }
          this.data[i] = newRow;
        }
        this.columns -= 1;
        return this;
      }
      addColumn(index, array) {
        if (typeof array === 'undefined') {
          array = index;
          index = this.columns;
        }
        checkColumnIndex(this, index, true);
        array = checkColumnVector(this, array);
        for (let i = 0; i < this.rows; i++) {
          const newRow = new Float64Array(this.columns + 1);
          let j = 0;
          for (; j < index; j++) {
            newRow[j] = this.data[i][j];
          }
          newRow[j++] = array[i];
          for (; j < this.columns + 1; j++) {
            newRow[j] = this.data[i][j - 1];
          }
          this.data[i] = newRow;
        }
        this.columns += 1;
        return this;
      }
    }
    installMathOperations(AbstractMatrix, Matrix$1);

    class WrapperMatrix2D extends AbstractMatrix {
      constructor(data) {
        super();
        this.data = data;
        this.rows = data.length;
        this.columns = data[0].length;
      }
      set(rowIndex, columnIndex, value) {
        this.data[rowIndex][columnIndex] = value;
        return this;
      }
      get(rowIndex, columnIndex) {
        return this.data[rowIndex][columnIndex];
      }
    }

    class LuDecomposition {
      constructor(matrix) {
        matrix = WrapperMatrix2D.checkMatrix(matrix);
        let lu = matrix.clone();
        let rows = lu.rows;
        let columns = lu.columns;
        let pivotVector = new Float64Array(rows);
        let pivotSign = 1;
        let i, j, k, p, s, t, v;
        let LUcolj, kmax;
        for (i = 0; i < rows; i++) {
          pivotVector[i] = i;
        }
        LUcolj = new Float64Array(rows);
        for (j = 0; j < columns; j++) {
          for (i = 0; i < rows; i++) {
            LUcolj[i] = lu.get(i, j);
          }
          for (i = 0; i < rows; i++) {
            kmax = Math.min(i, j);
            s = 0;
            for (k = 0; k < kmax; k++) {
              s += lu.get(i, k) * LUcolj[k];
            }
            LUcolj[i] -= s;
            lu.set(i, j, LUcolj[i]);
          }
          p = j;
          for (i = j + 1; i < rows; i++) {
            if (Math.abs(LUcolj[i]) > Math.abs(LUcolj[p])) {
              p = i;
            }
          }
          if (p !== j) {
            for (k = 0; k < columns; k++) {
              t = lu.get(p, k);
              lu.set(p, k, lu.get(j, k));
              lu.set(j, k, t);
            }
            v = pivotVector[p];
            pivotVector[p] = pivotVector[j];
            pivotVector[j] = v;
            pivotSign = -pivotSign;
          }
          if (j < rows && lu.get(j, j) !== 0) {
            for (i = j + 1; i < rows; i++) {
              lu.set(i, j, lu.get(i, j) / lu.get(j, j));
            }
          }
        }
        this.LU = lu;
        this.pivotVector = pivotVector;
        this.pivotSign = pivotSign;
      }
      isSingular() {
        let data = this.LU;
        let col = data.columns;
        for (let j = 0; j < col; j++) {
          if (data.get(j, j) === 0) {
            return true;
          }
        }
        return false;
      }
      solve(value) {
        value = Matrix$1.checkMatrix(value);
        let lu = this.LU;
        let rows = lu.rows;
        if (rows !== value.rows) {
          throw new Error('Invalid matrix dimensions');
        }
        if (this.isSingular()) {
          throw new Error('LU matrix is singular');
        }
        let count = value.columns;
        let X = value.subMatrixRow(this.pivotVector, 0, count - 1);
        let columns = lu.columns;
        let i, j, k;
        for (k = 0; k < columns; k++) {
          for (i = k + 1; i < columns; i++) {
            for (j = 0; j < count; j++) {
              X.set(i, j, X.get(i, j) - X.get(k, j) * lu.get(i, k));
            }
          }
        }
        for (k = columns - 1; k >= 0; k--) {
          for (j = 0; j < count; j++) {
            X.set(k, j, X.get(k, j) / lu.get(k, k));
          }
          for (i = 0; i < k; i++) {
            for (j = 0; j < count; j++) {
              X.set(i, j, X.get(i, j) - X.get(k, j) * lu.get(i, k));
            }
          }
        }
        return X;
      }
      get determinant() {
        let data = this.LU;
        if (!data.isSquare()) {
          throw new Error('Matrix must be square');
        }
        let determinant = this.pivotSign;
        let col = data.columns;
        for (let j = 0; j < col; j++) {
          determinant *= data.get(j, j);
        }
        return determinant;
      }
      get lowerTriangularMatrix() {
        let data = this.LU;
        let rows = data.rows;
        let columns = data.columns;
        let X = new Matrix$1(rows, columns);
        for (let i = 0; i < rows; i++) {
          for (let j = 0; j < columns; j++) {
            if (i > j) {
              X.set(i, j, data.get(i, j));
            } else if (i === j) {
              X.set(i, j, 1);
            } else {
              X.set(i, j, 0);
            }
          }
        }
        return X;
      }
      get upperTriangularMatrix() {
        let data = this.LU;
        let rows = data.rows;
        let columns = data.columns;
        let X = new Matrix$1(rows, columns);
        for (let i = 0; i < rows; i++) {
          for (let j = 0; j < columns; j++) {
            if (i <= j) {
              X.set(i, j, data.get(i, j));
            } else {
              X.set(i, j, 0);
            }
          }
        }
        return X;
      }
      get pivotPermutationVector() {
        return Array.from(this.pivotVector);
      }
    }

    function hypotenuse(a, b) {
      let r = 0;
      if (Math.abs(a) > Math.abs(b)) {
        r = b / a;
        return Math.abs(a) * Math.sqrt(1 + r * r);
      }
      if (b !== 0) {
        r = a / b;
        return Math.abs(b) * Math.sqrt(1 + r * r);
      }
      return 0;
    }

    class QrDecomposition {
      constructor(value) {
        value = WrapperMatrix2D.checkMatrix(value);
        let qr = value.clone();
        let m = value.rows;
        let n = value.columns;
        let rdiag = new Float64Array(n);
        let i, j, k, s;
        for (k = 0; k < n; k++) {
          let nrm = 0;
          for (i = k; i < m; i++) {
            nrm = hypotenuse(nrm, qr.get(i, k));
          }
          if (nrm !== 0) {
            if (qr.get(k, k) < 0) {
              nrm = -nrm;
            }
            for (i = k; i < m; i++) {
              qr.set(i, k, qr.get(i, k) / nrm);
            }
            qr.set(k, k, qr.get(k, k) + 1);
            for (j = k + 1; j < n; j++) {
              s = 0;
              for (i = k; i < m; i++) {
                s += qr.get(i, k) * qr.get(i, j);
              }
              s = -s / qr.get(k, k);
              for (i = k; i < m; i++) {
                qr.set(i, j, qr.get(i, j) + s * qr.get(i, k));
              }
            }
          }
          rdiag[k] = -nrm;
        }
        this.QR = qr;
        this.Rdiag = rdiag;
      }
      solve(value) {
        value = Matrix$1.checkMatrix(value);
        let qr = this.QR;
        let m = qr.rows;
        if (value.rows !== m) {
          throw new Error('Matrix row dimensions must agree');
        }
        if (!this.isFullRank()) {
          throw new Error('Matrix is rank deficient');
        }
        let count = value.columns;
        let X = value.clone();
        let n = qr.columns;
        let i, j, k, s;
        for (k = 0; k < n; k++) {
          for (j = 0; j < count; j++) {
            s = 0;
            for (i = k; i < m; i++) {
              s += qr.get(i, k) * X.get(i, j);
            }
            s = -s / qr.get(k, k);
            for (i = k; i < m; i++) {
              X.set(i, j, X.get(i, j) + s * qr.get(i, k));
            }
          }
        }
        for (k = n - 1; k >= 0; k--) {
          for (j = 0; j < count; j++) {
            X.set(k, j, X.get(k, j) / this.Rdiag[k]);
          }
          for (i = 0; i < k; i++) {
            for (j = 0; j < count; j++) {
              X.set(i, j, X.get(i, j) - X.get(k, j) * qr.get(i, k));
            }
          }
        }
        return X.subMatrix(0, n - 1, 0, count - 1);
      }
      isFullRank() {
        let columns = this.QR.columns;
        for (let i = 0; i < columns; i++) {
          if (this.Rdiag[i] === 0) {
            return false;
          }
        }
        return true;
      }
      get upperTriangularMatrix() {
        let qr = this.QR;
        let n = qr.columns;
        let X = new Matrix$1(n, n);
        let i, j;
        for (i = 0; i < n; i++) {
          for (j = 0; j < n; j++) {
            if (i < j) {
              X.set(i, j, qr.get(i, j));
            } else if (i === j) {
              X.set(i, j, this.Rdiag[i]);
            } else {
              X.set(i, j, 0);
            }
          }
        }
        return X;
      }
      get orthogonalMatrix() {
        let qr = this.QR;
        let rows = qr.rows;
        let columns = qr.columns;
        let X = new Matrix$1(rows, columns);
        let i, j, k, s;
        for (k = columns - 1; k >= 0; k--) {
          for (i = 0; i < rows; i++) {
            X.set(i, k, 0);
          }
          X.set(k, k, 1);
          for (j = k; j < columns; j++) {
            if (qr.get(k, k) !== 0) {
              s = 0;
              for (i = k; i < rows; i++) {
                s += qr.get(i, k) * X.get(i, j);
              }
              s = -s / qr.get(k, k);
              for (i = k; i < rows; i++) {
                X.set(i, j, X.get(i, j) + s * qr.get(i, k));
              }
            }
          }
        }
        return X;
      }
    }

    class SingularValueDecomposition {
      constructor(value) {
        let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
        value = WrapperMatrix2D.checkMatrix(value);
        if (value.isEmpty()) {
          throw new Error('Matrix must be non-empty');
        }
        let m = value.rows;
        let n = value.columns;
        const {
          computeLeftSingularVectors = true,
          computeRightSingularVectors = true,
          autoTranspose = false
        } = options;
        let wantu = Boolean(computeLeftSingularVectors);
        let wantv = Boolean(computeRightSingularVectors);
        let swapped = false;
        let a;
        if (m < n) {
          if (!autoTranspose) {
            a = value.clone();
            // eslint-disable-next-line no-console
            console.warn('Computing SVD on a matrix with more columns than rows. Consider enabling autoTranspose');
          } else {
            a = value.transpose();
            m = a.rows;
            n = a.columns;
            swapped = true;
            let aux = wantu;
            wantu = wantv;
            wantv = aux;
          }
        } else {
          a = value.clone();
        }
        let nu = Math.min(m, n);
        let ni = Math.min(m + 1, n);
        let s = new Float64Array(ni);
        let U = new Matrix$1(m, nu);
        let V = new Matrix$1(n, n);
        let e = new Float64Array(n);
        let work = new Float64Array(m);
        let si = new Float64Array(ni);
        for (let i = 0; i < ni; i++) si[i] = i;
        let nct = Math.min(m - 1, n);
        let nrt = Math.max(0, Math.min(n - 2, m));
        let mrc = Math.max(nct, nrt);
        for (let k = 0; k < mrc; k++) {
          if (k < nct) {
            s[k] = 0;
            for (let i = k; i < m; i++) {
              s[k] = hypotenuse(s[k], a.get(i, k));
            }
            if (s[k] !== 0) {
              if (a.get(k, k) < 0) {
                s[k] = -s[k];
              }
              for (let i = k; i < m; i++) {
                a.set(i, k, a.get(i, k) / s[k]);
              }
              a.set(k, k, a.get(k, k) + 1);
            }
            s[k] = -s[k];
          }
          for (let j = k + 1; j < n; j++) {
            if (k < nct && s[k] !== 0) {
              let t = 0;
              for (let i = k; i < m; i++) {
                t += a.get(i, k) * a.get(i, j);
              }
              t = -t / a.get(k, k);
              for (let i = k; i < m; i++) {
                a.set(i, j, a.get(i, j) + t * a.get(i, k));
              }
            }
            e[j] = a.get(k, j);
          }
          if (wantu && k < nct) {
            for (let i = k; i < m; i++) {
              U.set(i, k, a.get(i, k));
            }
          }
          if (k < nrt) {
            e[k] = 0;
            for (let i = k + 1; i < n; i++) {
              e[k] = hypotenuse(e[k], e[i]);
            }
            if (e[k] !== 0) {
              if (e[k + 1] < 0) {
                e[k] = 0 - e[k];
              }
              for (let i = k + 1; i < n; i++) {
                e[i] /= e[k];
              }
              e[k + 1] += 1;
            }
            e[k] = -e[k];
            if (k + 1 < m && e[k] !== 0) {
              for (let i = k + 1; i < m; i++) {
                work[i] = 0;
              }
              for (let i = k + 1; i < m; i++) {
                for (let j = k + 1; j < n; j++) {
                  work[i] += e[j] * a.get(i, j);
                }
              }
              for (let j = k + 1; j < n; j++) {
                let t = -e[j] / e[k + 1];
                for (let i = k + 1; i < m; i++) {
                  a.set(i, j, a.get(i, j) + t * work[i]);
                }
              }
            }
            if (wantv) {
              for (let i = k + 1; i < n; i++) {
                V.set(i, k, e[i]);
              }
            }
          }
        }
        let p = Math.min(n, m + 1);
        if (nct < n) {
          s[nct] = a.get(nct, nct);
        }
        if (m < p) {
          s[p - 1] = 0;
        }
        if (nrt + 1 < p) {
          e[nrt] = a.get(nrt, p - 1);
        }
        e[p - 1] = 0;
        if (wantu) {
          for (let j = nct; j < nu; j++) {
            for (let i = 0; i < m; i++) {
              U.set(i, j, 0);
            }
            U.set(j, j, 1);
          }
          for (let k = nct - 1; k >= 0; k--) {
            if (s[k] !== 0) {
              for (let j = k + 1; j < nu; j++) {
                let t = 0;
                for (let i = k; i < m; i++) {
                  t += U.get(i, k) * U.get(i, j);
                }
                t = -t / U.get(k, k);
                for (let i = k; i < m; i++) {
                  U.set(i, j, U.get(i, j) + t * U.get(i, k));
                }
              }
              for (let i = k; i < m; i++) {
                U.set(i, k, -U.get(i, k));
              }
              U.set(k, k, 1 + U.get(k, k));
              for (let i = 0; i < k - 1; i++) {
                U.set(i, k, 0);
              }
            } else {
              for (let i = 0; i < m; i++) {
                U.set(i, k, 0);
              }
              U.set(k, k, 1);
            }
          }
        }
        if (wantv) {
          for (let k = n - 1; k >= 0; k--) {
            if (k < nrt && e[k] !== 0) {
              for (let j = k + 1; j < n; j++) {
                let t = 0;
                for (let i = k + 1; i < n; i++) {
                  t += V.get(i, k) * V.get(i, j);
                }
                t = -t / V.get(k + 1, k);
                for (let i = k + 1; i < n; i++) {
                  V.set(i, j, V.get(i, j) + t * V.get(i, k));
                }
              }
            }
            for (let i = 0; i < n; i++) {
              V.set(i, k, 0);
            }
            V.set(k, k, 1);
          }
        }
        let pp = p - 1;
        let eps = Number.EPSILON;
        while (p > 0) {
          let k, kase;
          for (k = p - 2; k >= -1; k--) {
            if (k === -1) {
              break;
            }
            const alpha = Number.MIN_VALUE + eps * Math.abs(s[k] + Math.abs(s[k + 1]));
            if (Math.abs(e[k]) <= alpha || Number.isNaN(e[k])) {
              e[k] = 0;
              break;
            }
          }
          if (k === p - 2) {
            kase = 4;
          } else {
            let ks;
            for (ks = p - 1; ks >= k; ks--) {
              if (ks === k) {
                break;
              }
              let t = (ks !== p ? Math.abs(e[ks]) : 0) + (ks !== k + 1 ? Math.abs(e[ks - 1]) : 0);
              if (Math.abs(s[ks]) <= eps * t) {
                s[ks] = 0;
                break;
              }
            }
            if (ks === k) {
              kase = 3;
            } else if (ks === p - 1) {
              kase = 1;
            } else {
              kase = 2;
              k = ks;
            }
          }
          k++;
          switch (kase) {
            case 1:
              {
                let f = e[p - 2];
                e[p - 2] = 0;
                for (let j = p - 2; j >= k; j--) {
                  let t = hypotenuse(s[j], f);
                  let cs = s[j] / t;
                  let sn = f / t;
                  s[j] = t;
                  if (j !== k) {
                    f = -sn * e[j - 1];
                    e[j - 1] = cs * e[j - 1];
                  }
                  if (wantv) {
                    for (let i = 0; i < n; i++) {
                      t = cs * V.get(i, j) + sn * V.get(i, p - 1);
                      V.set(i, p - 1, -sn * V.get(i, j) + cs * V.get(i, p - 1));
                      V.set(i, j, t);
                    }
                  }
                }
                break;
              }
            case 2:
              {
                let f = e[k - 1];
                e[k - 1] = 0;
                for (let j = k; j < p; j++) {
                  let t = hypotenuse(s[j], f);
                  let cs = s[j] / t;
                  let sn = f / t;
                  s[j] = t;
                  f = -sn * e[j];
                  e[j] = cs * e[j];
                  if (wantu) {
                    for (let i = 0; i < m; i++) {
                      t = cs * U.get(i, j) + sn * U.get(i, k - 1);
                      U.set(i, k - 1, -sn * U.get(i, j) + cs * U.get(i, k - 1));
                      U.set(i, j, t);
                    }
                  }
                }
                break;
              }
            case 3:
              {
                const scale = Math.max(Math.abs(s[p - 1]), Math.abs(s[p - 2]), Math.abs(e[p - 2]), Math.abs(s[k]), Math.abs(e[k]));
                const sp = s[p - 1] / scale;
                const spm1 = s[p - 2] / scale;
                const epm1 = e[p - 2] / scale;
                const sk = s[k] / scale;
                const ek = e[k] / scale;
                const b = ((spm1 + sp) * (spm1 - sp) + epm1 * epm1) / 2;
                const c = sp * epm1 * (sp * epm1);
                let shift = 0;
                if (b !== 0 || c !== 0) {
                  if (b < 0) {
                    shift = 0 - Math.sqrt(b * b + c);
                  } else {
                    shift = Math.sqrt(b * b + c);
                  }
                  shift = c / (b + shift);
                }
                let f = (sk + sp) * (sk - sp) + shift;
                let g = sk * ek;
                for (let j = k; j < p - 1; j++) {
                  let t = hypotenuse(f, g);
                  if (t === 0) t = Number.MIN_VALUE;
                  let cs = f / t;
                  let sn = g / t;
                  if (j !== k) {
                    e[j - 1] = t;
                  }
                  f = cs * s[j] + sn * e[j];
                  e[j] = cs * e[j] - sn * s[j];
                  g = sn * s[j + 1];
                  s[j + 1] = cs * s[j + 1];
                  if (wantv) {
                    for (let i = 0; i < n; i++) {
                      t = cs * V.get(i, j) + sn * V.get(i, j + 1);
                      V.set(i, j + 1, -sn * V.get(i, j) + cs * V.get(i, j + 1));
                      V.set(i, j, t);
                    }
                  }
                  t = hypotenuse(f, g);
                  if (t === 0) t = Number.MIN_VALUE;
                  cs = f / t;
                  sn = g / t;
                  s[j] = t;
                  f = cs * e[j] + sn * s[j + 1];
                  s[j + 1] = -sn * e[j] + cs * s[j + 1];
                  g = sn * e[j + 1];
                  e[j + 1] = cs * e[j + 1];
                  if (wantu && j < m - 1) {
                    for (let i = 0; i < m; i++) {
                      t = cs * U.get(i, j) + sn * U.get(i, j + 1);
                      U.set(i, j + 1, -sn * U.get(i, j) + cs * U.get(i, j + 1));
                      U.set(i, j, t);
                    }
                  }
                }
                e[p - 2] = f;
                break;
              }
            case 4:
              {
                if (s[k] <= 0) {
                  s[k] = s[k] < 0 ? -s[k] : 0;
                  if (wantv) {
                    for (let i = 0; i <= pp; i++) {
                      V.set(i, k, -V.get(i, k));
                    }
                  }
                }
                while (k < pp) {
                  if (s[k] >= s[k + 1]) {
                    break;
                  }
                  let t = s[k];
                  s[k] = s[k + 1];
                  s[k + 1] = t;
                  if (wantv && k < n - 1) {
                    for (let i = 0; i < n; i++) {
                      t = V.get(i, k + 1);
                      V.set(i, k + 1, V.get(i, k));
                      V.set(i, k, t);
                    }
                  }
                  if (wantu && k < m - 1) {
                    for (let i = 0; i < m; i++) {
                      t = U.get(i, k + 1);
                      U.set(i, k + 1, U.get(i, k));
                      U.set(i, k, t);
                    }
                  }
                  k++;
                }
                p--;
                break;
              }
            // no default
          }
        }

        if (swapped) {
          let tmp = V;
          V = U;
          U = tmp;
        }
        this.m = m;
        this.n = n;
        this.s = s;
        this.U = U;
        this.V = V;
      }
      solve(value) {
        let Y = value;
        let e = this.threshold;
        let scols = this.s.length;
        let Ls = Matrix$1.zeros(scols, scols);
        for (let i = 0; i < scols; i++) {
          if (Math.abs(this.s[i]) <= e) {
            Ls.set(i, i, 0);
          } else {
            Ls.set(i, i, 1 / this.s[i]);
          }
        }
        let U = this.U;
        let V = this.rightSingularVectors;
        let VL = V.mmul(Ls);
        let vrows = V.rows;
        let urows = U.rows;
        let VLU = Matrix$1.zeros(vrows, urows);
        for (let i = 0; i < vrows; i++) {
          for (let j = 0; j < urows; j++) {
            let sum = 0;
            for (let k = 0; k < scols; k++) {
              sum += VL.get(i, k) * U.get(j, k);
            }
            VLU.set(i, j, sum);
          }
        }
        return VLU.mmul(Y);
      }
      solveForDiagonal(value) {
        return this.solve(Matrix$1.diag(value));
      }
      inverse() {
        let V = this.V;
        let e = this.threshold;
        let vrows = V.rows;
        let vcols = V.columns;
        let X = new Matrix$1(vrows, this.s.length);
        for (let i = 0; i < vrows; i++) {
          for (let j = 0; j < vcols; j++) {
            if (Math.abs(this.s[j]) > e) {
              X.set(i, j, V.get(i, j) / this.s[j]);
            }
          }
        }
        let U = this.U;
        let urows = U.rows;
        let ucols = U.columns;
        let Y = new Matrix$1(vrows, urows);
        for (let i = 0; i < vrows; i++) {
          for (let j = 0; j < urows; j++) {
            let sum = 0;
            for (let k = 0; k < ucols; k++) {
              sum += X.get(i, k) * U.get(j, k);
            }
            Y.set(i, j, sum);
          }
        }
        return Y;
      }
      get condition() {
        return this.s[0] / this.s[Math.min(this.m, this.n) - 1];
      }
      get norm2() {
        return this.s[0];
      }
      get rank() {
        let tol = Math.max(this.m, this.n) * this.s[0] * Number.EPSILON;
        let r = 0;
        let s = this.s;
        for (let i = 0, ii = s.length; i < ii; i++) {
          if (s[i] > tol) {
            r++;
          }
        }
        return r;
      }
      get diagonal() {
        return Array.from(this.s);
      }
      get threshold() {
        return Number.EPSILON / 2 * Math.max(this.m, this.n) * this.s[0];
      }
      get leftSingularVectors() {
        return this.U;
      }
      get rightSingularVectors() {
        return this.V;
      }
      get diagonalMatrix() {
        return Matrix$1.diag(this.s);
      }
    }

    function solve(leftHandSide, rightHandSide) {
      let useSVD = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 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);
      }
    }

    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 = Boolean(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;
      }
    }

    /**
     *
     * @private
     * @param {Array of arrays} collection
     */
    function sortCollectionSet(collection) {
      let objectCollection = collection.map((value, index) => {
        let key = BigInt(0);
        value.forEach(item => key |= BigInt(1) << BigInt(item));
        return {
          value,
          index,
          key
        };
      }).sort((a, b) => {
        if (a.key - b.key < 0) return -1;
        return 1;
      });
      let sorted = [];
      let indices = [];
      let key;
      for (let set of objectCollection) {
        if (set.key !== key) {
          key = set.key;
          indices.push([]);
          sorted.push(set.value);
        }
        indices[indices.length - 1].push(set.index);
      }
      let result = {
        values: sorted,
        indices
      };
      return result;
    }

    /**
     * (Combinatorial Subspace Least Squares) - subfunction for the FC-NNLS
     * @private
     * @param {Matrix} XtX
     * @param {Matrix} XtY
     * @param {Array} Pset
     * @param {Numbers} l
     * @param {Numbers} p
     */
    function cssls(XtX, XtY, Pset, l, p) {
      // Solves the set of equation XtX*K = XtY for the variables in Pset
      // if XtX (or XtX(vars,vars)) is singular, performs the svd and find pseudoinverse, otherwise (even if ill-conditioned) finds inverse with LU decomposition and solves the set of equation
      // it is consistent with matlab results for ill-conditioned matrices (at least consistent with test 'ill-conditionned square X rank 2, Y 3x1' in cssls.test)

      let K = Matrix$1.zeros(l, p);
      if (Pset === null) {
        let choXtX = new CholeskyDecomposition(XtX);
        if (choXtX.isPositiveDefinite() === true) {
          K = choXtX.solve(XtY);
        } else {
          let luXtX = new LuDecomposition(XtX);
          if (luXtX.isSingular() === false) {
            K = luXtX.solve(Matrix$1.eye(l)).mmul(XtY);
          } else {
            K = solve(XtX, XtY, {
              useSVD: true
            });
          }
        }
      } else {
        let sortedPset = sortCollectionSet(Pset).values;
        let sortedEset = sortCollectionSet(Pset).indices;
        if (sortedPset.length === 1 && sortedPset[0].length === 0 && sortedEset[0].length === p) {
          return K;
        } else if (sortedPset.length === 1 && sortedPset[0].length === l && sortedEset[0].length === p) {
          let choXtX = new CholeskyDecomposition(XtX);
          if (choXtX.isPositiveDefinite() === true) {
            K = choXtX.solve(XtY);
          } else {
            let luXtX = new LuDecomposition(XtX);
            if (luXtX.isSingular() === false) {
              K = luXtX.solve(Matrix$1.eye(l)).mmul(XtY);
            } else {
              K = solve(XtX, XtY, {
                useSVD: true
              });
            }
          }
        } else {
          for (let k = 0; k < sortedPset.length; k++) {
            let cols2Solve = sortedEset[k];
            let vars = sortedPset[k];
            let L;
            let choXtX = new CholeskyDecomposition(XtX.selection(vars, vars));
            if (choXtX.isPositiveDefinite() === true) {
              L = choXtX.solve(XtY.selection(vars, cols2Solve));
            } else {
              let luXtX = new LuDecomposition(XtX.selection(vars, vars));
              if (luXtX.isSingular() === false) {
                L = luXtX.solve(Matrix$1.eye(vars.length)).mmul(XtY.selection(vars, cols2Solve));
              } else {
                L = solve(XtX.selection(vars, vars), XtY.selection(vars, cols2Solve), {
                  useSVD: true
                });
              }
            }
            for (let i = 0; i < L.rows; i++) {
              for (let j = 0; j < L.columns; j++) {
                K.set(vars[i], cols2Solve[j], L.get(i, j));
              }
            }
          }
        }
      }
      return K;
    }

    function initialisation(X, Y) {
      let n = X.rows;
      let l = X.columns;
      let p = Y.columns;
      let iter = 0;
      if (Y.rows !== n) throw new Error('ERROR: matrix size not compatible');
      let W = Matrix$1.zeros(l, p);

      // precomputes part of pseudoinverse
      let XtX = X.transpose().mmul(X);
      let XtY = X.transpose().mmul(Y);
      let K = cssls(XtX, XtY, null, l, p); // K is lxp
      let Pset = [];
      for (let j = 0; j < p; j++) {
        Pset[j] = [];
        for (let i = 0; i < l; i++) {
          if (K.get(i, j) > 0) {
            Pset[j].push(i);
          } else {
            K.set(i, j, 0);
          } //This is our initial solution, it's the solution found by overwriting the unconstrained least square solution
        }
      }

      let Fset = [];
      for (let j = 0; j < p; j++) {
        if (Pset[j].length !== l) {
          Fset.push(j);
        }
      }
      let D = K.clone();
      return {
        n,
        l,
        p,
        iter,
        W,
        XtX,
        XtY,
        K,
        Pset,
        Fset,
        D
      };
    }

    /**
     * Computes the set difference A\B
     * @private
     * @param {A} set A as an array
     * @param {B} set B as an array
     */
    function setDifference(A, B) {
      let C = [];
      for (let i of A) {
        if (!B.includes(i)) C.push(i);
      }
      return C;
    }

    // Makes sure the solution has converged
    function optimality(iter, maxIter, XtX, XtY, Fset, Pset, W, K, l, p, D) {
      if (iter === maxIter) {
        throw new Error('Maximum number of iterations exceeded');
      }

      // Check solution for optimality
      let V = XtY.subMatrixColumn(Fset).subtract(XtX.mmul(K.subMatrixColumn(Fset)));
      for (let j = 0; j < Fset.length; j++) {
        W.setColumn(Fset[j], V.subMatrixColumn([j]));
      }
      let Jset = [];
      let fullSet = [];
      for (let i = 0; i < l; i++) {
        fullSet.push(i);
      }
      for (let j = 0; j < Fset.length; j++) {
        let notPset = setDifference(fullSet, Pset[Fset[j]]);
        if (notPset.length === 0) {
          Jset.push(Fset[j]);
        } else if (W.selection(notPset, [Fset[j]]).max() <= 0) {
          Jset.push(Fset[j]);
        }
      }
      Fset = setDifference(Fset, Jset);

      // For non-optimal solutions, add the appropriate variables to Pset
      if (Fset.length !== 0) {
        for (let j = 0; j < Fset.length; j++) {
          for (let i = 0; i < l; i++) {
            if (Pset[Fset[j]].includes(i)) W.set(i, Fset[j], -Infinity);
          }
          Pset[Fset[j]].push(W.subMatrixColumn(Fset).maxColumnIndex(j)[0]);
        }
        for (let j = 0; j < Fset.length; j++) {
          D.setColumn(Fset[j], K.getColumn(Fset[j]));
        }
      }
      for (let j = 0; j < p; j++) {
        Pset[j].sort((a, b) => a - b);
      }
      return {
        Pset,
        Fset,
        W
      };
    }

    /**
     * Returns a new array based on extraction of specific indices of an array
     * @private
     * @param {Array} vector
     * @param {Array} indices
     */
    function selection(vector, indices) {
      let u = []; //new Float64Array(indices.length);
      for (let i = 0; i < indices.length; i++) {
        u[i] = vector[indices[i]];
      }
      return u;
    }

    /**
     * Fast Combinatorial Non-negative Least Squares with multiple Right Hand Side
     * @param {Matrix|number[][]} X
     * @param {Matrix|number[][]} Y
     * @param {object} [options={}]
     * @param {number} [options.maxIterations] if empty maxIterations is set at 3 times the number of columns of X
     * @returns {Matrix} K
     */
    function fcnnls(X, Y) {
      let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
      X = Matrix$1.checkMatrix(X);
      Y = Matrix$1.checkMatrix(Y);
      let {
        l,
        p,
        iter,
        W,
        XtX,
        XtY,
        K,
        Pset,
        Fset,
        D
      } = initialisation(X, Y);
      const {
        maxIterations = X.columns * 3
      } = options;

      // Active set algorithm for NNLS main loop
      while (Fset.length > 0) {
        // Solves for the passive variables (uses subroutine below)
        let L = cssls(XtX, XtY.subMatrixColumn(Fset), selection(Pset, Fset), l, Fset.length);
        for (let i = 0; i < l; i++) {
          for (let j = 0; j < Fset.length; j++) {
            K.set(i, Fset[j], L.get(i, j));
          }
        }

        // Finds any infeasible solutions
        let infeasIndex = [];
        for (let j = 0; j < Fset.length; j++) {
          for (let i = 0; i < l; i++) {
            if (L.get(i, j) < 0) {
              infeasIndex.push(j);
              break;
            }
          }
        }
        let Hset = selection(Fset, infeasIndex);

        // Makes infeasible solutions feasible (standard NNLS inner loop)
        if (Hset.length > 0) {
          let m = Hset.length;
          let alpha = Matrix$1.ones(l, m);
          while (m > 0 && iter < maxIterations) {
            iter++;
            alpha.mul(Infinity);

            // Finds indices of negative variables in passive set
            let hRowColIdx = [[], []]; // Indexes work in pairs, each pair reprensents a single element, first array is row index, second array is column index
            let negRowColIdx = [[], []]; // Same as before
            for (let j = 0; j < m; j++) {
              for (let i = 0; i < Pset[Hset[j]].length; i++) {
                if (K.get(Pset[Hset[j]][i], Hset[j]) < 0) {
                  hRowColIdx[0].push(Pset[Hset[j]][i]); // i
                  hRowColIdx[1].push(j);
                  negRowColIdx[0].push(Pset[Hset[j]][i]); // i
                  negRowColIdx[1].push(Hset[j]);
                } // Compared to matlab, here we keep the row/column indexing (we are not taking the linear indexing)
              }
            }

            for (let k = 0; k < hRowColIdx[0].length; k++) {
              // could be hRowColIdx[1].length as well
              alpha.set(hRowColIdx[0][k], hRowColIdx[1][k], D.get(negRowColIdx[0][k], negRowColIdx[1][k]) / (D.get(negRowColIdx[0][k], negRowColIdx[1][k]) - K.get(negRowColIdx[0][k], negRowColIdx[1][k])));
            }
            let alphaMin = [];
            let minIdx = [];
            for (let j = 0; j < m; j++) {
              alphaMin[j] = alpha.minColumn(j);
              minIdx[j] = alpha.minColumnIndex(j)[0];
            }
            alphaMin = Matrix$1.rowVector(alphaMin);
            for (let i = 0; i < l; i++) {
              alpha.setSubMatrix(alphaMin, i, 0);
            }
            let E = new Matrix$1(l, m);
            E = D.subMatrixColumn(Hset).subtract(alpha.subMatrix(0, l - 1, 0, m - 1).mul(D.subMatrixColumn(Hset).subtract(K.subMatrixColumn(Hset))));
            for (let j = 0; j < m; j++) {
              D.setColumn(Hset[j], E.subMatrixColumn([j]));
            }
            let idx2zero = [minIdx, Hset];
            for (let k = 0; k < m; k++) {
              D.set(idx2zero[0][k], idx2zero[1][k], 0);
            }
            for (let j = 0; j < m; j++) {
              Pset[Hset[j]].splice(Pset[Hset[j]].findIndex(item => item === minIdx[j]), 1);
            }
            L = cssls(XtX, XtY.subMatrixColumn(Hset), selection(Pset, Hset), l, m);
            for (let j = 0; j < m; j++) {
              K.setColumn(Hset[j], L.subMatrixColumn([j]));
            }
            Hset = [];
            for (let j = 0; j < K.columns; j++) {
              for (let i = 0; i < l; i++) {
                if (K.get(i, j) < 0) {
                  Hset.push(j);
                  break;
                }
              }
            }
            m = Hset.length;
          }
        }
        let newParam = optimality(iter, maxIterations, XtX, XtY, Fset, Pset, W, K, l, p, D);
        Pset = newParam.Pset;
        Fset = newParam.Fset;
        W = newParam.W;
      }
      return K;
    }

    /**
     * Fast Combinatorial Non-negative Least Squares with single Right Hand Side
     * @param {Matrix|number[][]} X
     * @param {number[]} y
     * @param {object} [options={}]
     * @param {boolean} [maxIterations] if true or empty maxIterations is set at 3 times the number of columns of X
     * @returns {Array} k
     */
    function fcnnlsVector(X, y) {
      let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
      if (Array.isArray(y) === false) {
        throw new TypeError('y must be a 1D Array');
      }
      let Y = Matrix$1.columnVector(y);
      let K = fcnnls(X, Y, options);
      let k = K.to1DArray();
      return k;
    }

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

    /**
     * Checks if input is of type array
     *
     * @param input - input
     */
    function xCheck(input) {
      let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      const {
        minLength
      } = options;
      if (!isAnyArray(input)) {
        throw new TypeError('input must be an array');
      }
      if (input.length === 0) {
        throw new TypeError('input must not be empty');
      }
      if (minLength && input.length < minLength) {
        throw new Error(`input must have a length of at least ${minLength}`);
      }
    }

    /**
     * Returns the closest index of a `target`
     *
     * @param array - array of numbers
     * @param target - target
     * @returns - closest index
     */
    function xFindClosestIndex(array, target) {
      let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
      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) {
      let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      let {
        fromIndex,
        toIndex,
        from,
        to
      } = options;
      if (fromIndex === undefined) {
        if (from !== undefined) {
          fromIndex = xFindClosestIndex(x, from);
        } else {
          fromIndex = 0;
        }
      }
      if (toIndex === undefined) {
        if (to !== undefined) {
          toIndex = xFindClosestIndex(x, to);
        } else {
          toIndex = x.length - 1;
        }
      }
      if (fromIndex < 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
      };
    }

    /**
     * Computes the maximal value of an array of values
     *
     * @param array - array of numbers
     * @param options - options
     */
    function xMaxValue(array) {
      let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      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;
    }

    /**
     * Calculate the sum of the values
     *
     * @param array - Object that contains property x (an ordered increasing array) and y (an array).
     * @param options - Options.
     * @returns XSum value on the specified range.
     */
    function xSum(array) {
      let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      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 array - Array containing values
     * @param options - options
     * @returns - normalized data
     */
    function xNormed(input) {
      let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      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.toLowerCase()) {
        case 'absolute':
          {
            let absoluteSumValue = absoluteSum(input) / value;
            if (absoluteSumValue === 0) {
              throw new Error('xNormed: trying to divide by 0');
            }
            for (let i = 0; i < input.length; i++) {
              output[i] = input[i] / absoluteSumValue;
            }
            return output;
          }
        case 'max':
          {
            let currentMaxValue = xMaxValue(input);
            if (currentMaxValue === 0) {
              throw new Error('xNormed: 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':
          {
            let sumFactor = xSum(input) / value;
            if (sumFactor === 0) {
              throw new Error('xNormed: trying to divide by 0');
            }
            for (let i = 0; i < input.length; i++) {
              output[i] = input[i] / sumFactor;
            }
            return output;
          }
        default:
          throw new Error(`norm: unknown algorithm: ${algorithm}`);
      }
    }
    function absoluteSum(input) {
      let sumValue = 0;
      for (let i = 0; i < input.length; i++) {
        sumValue += Math.abs(input[i]);
      }
      return sumValue;
    }

    /**
     * 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) {
      let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      let {
        delta = 1
      } = options;
      if (typeof delta === 'number') {
        let deltaNumber = delta;
        delta = () => deltaNumber;
      }
      data = data.filter(spectrum => spectrum.x.length > 0);
      if (data.length === 0) return {
        x: [],
        y: []
      };
      let x = [];
      let y = [];
      const positions = new Array(data.length).fill(0);
      const point = {
        x: 0,
        y: 0
      };
      nextValue(data, positions, point);
      let slot = {
        maxX: point.x + delta(point.x),
        sumY: point.y,
        sumXY: point.y * point.x
      };
      while (data.length > 0) {
        nextValue(data, positions, point);
        let sameSlot = point.x <= slot.maxX;
        if (!sameSlot) {
          if (slot.sumY > 0) {
            x.push(slot.sumXY / slot.sumY);
            y.push(slot.sumY);
          }
          slot.sumY = 0;
          slot.sumXY = 0;
        }
        slot.sumY += point.y;
        slot.sumXY += point.x * point.y;
        slot.maxX = point.x + delta(point.x);
        if (data.length === 0 && slot.sumY > 0) {
          x.push(slot.sumXY / slot.sumY);
          y.push(slot.sumY);
        }
      }
      return {
        x,
        y
      };
    }
    /**
     * NextValue.
     *
     * @param data - data.
     * @param positions - Positions array.
     * @param point - Point.
     */
    function nextValue(data, positions, point) {
      let minIndex = 0;
      let minX = data[0].x[positions[0]];
      for (let i = 1; i < data.length; i++) {
        let currentX = data[i].x[positions[i]];
        if (currentX < minX) {
          minX = currentX;
          minIndex = i;
        }
      }
      point.x = minX;
      point.y = data[minIndex].y[positions[minIndex]];
      positions[minIndex]++;
      if (positions[minIndex] === data[minIndex].x.length) {
        positions.splice(minIndex, 1);
        data.splice(minIndex, 1);
      }
    }

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

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

    /**
     *
     * @param {object} [options ={}]
     * @param {object}        [options.mass={}]
     * @param {number}        [options.mass.precision=0] -  Precision (accuracy) of the monoisotopic mass in ppm
     * @param {string|Function} [options.mass.peakWidthFct=()=>0.01]
     * @param {import('cheminfo-types').Logger} [options.logger]
     * @returns
     */
    function getPeakWidthFct() {
      let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
      const {
        logger,
        mass: massOptions = {}
      } = options;
      const {
        precision = 0,
        peakWidthFct
      } = massOptions;
      if (peakWidthFct instanceof Function) {
        return peakWidthFct;
      }
      if (!peakWidthFct) {
        return () => 0.01;
      }
      try {
        // eslint-disable-next-line no-new-func
        return new Function('mass', `return ${peakWidthFct} + ${precision} * mass / 1e6`);
      } catch (e) {
        logger?.warn(`error in peakWidthFct: ${e.toString()}`);
        return () => 0.01;
      }
    }

    /**
     *
     * @param {import('ms-spectrum').Spectrum} spectrum
     * @param {Array}         ranges
     * @param {object}        [options={}]
     * @param {string}        [options.ionizations=''] - Comma separated list of ionizations (to charge the molecule)
     * @param {object}        [options.mass={}]
     * @param {number}        [options.mass.threshold=0.001]
     * @param {number}        [options.mass.precision=0] -  Precision (accuracy) of the monoisotopic mass in ppm
     * @param {string|Function} [options.mass.peakWidthFct=()=>0.01]
     * @param {import('cheminfo-types').Logger} [options.logger]
     * @param {object}        [options.filter={}]
     * @param {number}        [options.filter.minMass=0] - Minimal monoisotopic mass
     * @param {number}        [options.filter.maxMass=+Infinity] - Maximal monoisotopic mass
     * @param {number}        [options.filter.minCharge=-Infinity] - Minimal charge
     * @param {number}        [options.filter.maxCharge=+Infinity] - Maximal charge
     * @param {boolean}       [options.filter.absoluteCharge=false] - If true, the charge is absolute (so between 0 and +Infinity by default)
     * @param {boolean}       [options.filter.allowNegativeAtoms=false] - Allow to have negative number of atoms
     * @param {object}        [options.filter.unsaturation={}]
     * @param {number}        [options.filter.unsaturation.min=-Infinity] - Minimal unsaturation
     * @param {number}        [options.filter.unsaturation.max=+Infinity] - Maximal unsaturation
     * @param {boolean}       [options.filter.unsaturation.onlyInteger=false] - Integer unsaturation
     * @param {boolean}       [options.filter.unsaturation.onlyNonInteger=false] - Non integer unsaturation
     * @param {object}        [options.filter.atoms] - object of atom:{min, max}
     * @param {function}      [options.filter.callback] - a function to filter the MF
     * @param {string}        [options.filterFct]
     */
    async function mfsDeconvolution(spectrum, ranges) {
      let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
      if (!spectrum || !spectrum.getPeaks || !spectrum.getPeaks()) {
        throw new Error('spectrum must be an instance of Spectrum');
      }
      const {
        mass: massOptions = {},
        filter,
        ionizations,
        logger
      } = options;
      const {
        threshold = 0.001
      } = massOptions;
      if (!ionizations) {
        logger?.warn('No ionizations provided this could be an error if the molecule is not naturally charged.');
      }
      const peakWidthFct = getPeakWidthFct(options);
      const centroids = getCentroids(spectrum, {
        threshold
      });
      let mfs = await generateMFs(ranges, {
        filter,
        ionizations
      });
      mfs = addIsotopicDistributionAndCheckMF(mfs, {
        logger,
        peakWidthFct
      });
      mfs.sort((mf1, mf2) => mf1.ms.em - mf2.ms.em);
      const combined = buildCombined(centroids, mfs, {
        peakWidthFct
      });
      if (!hasOverlap(combined.ys)) {
        throw new Error('Could not find any overlaping peaks between experimental and theoretical spectrum.');
      }

      // Time to make the NNMF
      const A = new Matrix$1(combined.ys.slice(1));
      const At = A.transpose();
      const b = Array.from(combined.ys[0]); // target

      const w = fcnnlsVector(At, b);
      const W = new Matrix$1([w]); // weights

      // now that we know all the weights we need to reconstruct the spectrum to check if it looks ok
      const reconstructed = W.mmul(A).getRow(0);
      const relativeIntensity = xNormed(w);
      for (let i = 0; i < mfs.length; i++) {
        mfs[i].absoluteQuantity = w[i];
        mfs[i].relativeQuantity = relativeIntensity[i];
        mfs[i].distribution.y = mfs[i].distribution.y.map(y => y * w[i]);
      }
      mfs.sort((mf1, mf2) => mf2.absoluteQuantity - mf1.absoluteQuantity);
      return {
        reconstructed: {
          x: combined.x,
          y: reconstructed
        },
        mfs
      };
    }

    /**
     *
     * @param {object} spectrum
     * @param {object} [options={}]
     * @param {number} [options.threshold=0.001]
     */
    function getCentroids(spectrum) {
      let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      const {
        threshold = 0.001
      } = options;
      const peaks = spectrum.getPeaks({
        threshold
      });
      return {
        x: peaks.map(peak => peak.x),
        y: peaks.map(peak => peak.y)
      };
    }
    function addIsotopicDistributionAndCheckMF(mfs, options) {
      const {
        logger,
        peakWidthFct
      } = options;
      for (let mf of mfs) {
        const isotopicDistribution = new IsotopicDistribution(mf.mf, {
          fwhm: peakWidthFct(mf.ms.em),
          // when should we join peaks
          ionizations: mf.ms.ionization
        });
        mf.distribution = isotopicDistribution.getXY({
          sumValue: 1
        });
        if (mf.distribution.y.length === 0) {
          logger?.warn(`Problem with isotopic distribution calculation. Negative number of atoms ? ${mf.mf} ${mf.ms.ionization}`);
        }
      }
      mfs = mfs.filter(mf => mf.distribution.x.length > 0);
      if (mfs.length === 0) {
        throw new Error('No MF found. Did you forget ionization ?');
      }
      return mfs;
    }
    function buildCombined(centroids, mfs) {
      let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
      const {
        peakWidthFct
      } = options;
      if (!peakWidthFct) {
        throw new Error('peakWidthFct is mandatory');
      }
      const data = [centroids];
      mfs.forEach(mf => data.push(mf.distribution));
      let combined = xyArrayAlignToFirst(data, {
        delta: peakWidthFct
      });
      return combined;
    }

    /**
     * We will check if there is any overlap between the theoretical and experimental spectra
     * @param {number[][]} ys
     */
    function hasOverlap(ys) {
      for (let i = 0; i < ys[0].length; i++) {
        for (let j = 1; j < ys.length; j++) {
          if (ys[0][i] > 0 && ys[j][i] > 0) {
            return true;
          }
        }
      }
      return false;
    }

    function reconstruct(mfs) {
      let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      const delta = getPeakWidthFct(options);
      const data = mfs.map(mf => mf.distribution);
      const reconstructed = xyArrayWeightedMerge(data, {
        delta
      });
      return reconstructed;
    }

    var MFSDeconvolutionPkg = /*#__PURE__*/Object.freeze({
        __proto__: null,
        mfsDeconvolution: mfsDeconvolution,
        reconstruct: reconstruct
    });

    function appendInternals(data) {
      // for each line (internal fragment) we calculate the vertical position
      // where it should be drawn as well and the maximal number of lines
      let maxNumberLines = 0;
      for (let result of data.results) {
        if (result.internal) {
          result.slot = assignSlot(result.from, result.to, data.residues.residues);
          if (result.slot > maxNumberLines) maxNumberLines = result.slot;
        }
      }
      for (let row of data.rows) {
        row.info.internals = maxNumberLines;
      }
    }

    // we need to define the height of the line.
    // we need to find a height that is not yet used.
    function assignSlot(from, to, residues) {
      let used = {};
      if (from > 0) from--; // we ensure that we don't put on the same line to sequences that are consecutive
      for (let i = from; i < to; i++) {
        let residue = residues[i];
        residue.paper.usedSlots.forEach((usedSlot, index) => {
          used[index] = true;
        });
      }
      let counter = 0;
      while (true) {
        if (!used[counter]) {
          break;
        }
        counter++;
      }
      for (let i = from; i < to; i++) {
        residues[i].paper.usedSlots[counter] = true;
      }
      return counter;
    }

    const ALTERNATIVES = ['', '¹', '²', '³', '⁴', '⁵', '⁶', '⁷', '⁸', '⁹'];
    const SYMBOLS = ['Θ', 'Δ', 'Λ', 'Φ', 'Ω', 'Γ', 'Χ'];
    let currentSymbol = 0;

    /**
     * Code that allows to split a sequence of amino acids or nucleotides natural or non natural
     * @param {string} [sequence]
     * @param {object} [options={}]
     * @param {string} [options.kind] - peptide, rna, ds-dna or dna. Default if contains U: rna, otherwise ds-dna
     * @param {string} [options.fivePrime=monophosphate] - alcohol, monophosphate, diphosphate, triphosphate
     * @param {string} [options.circular=false]
     */

    function appendResidues(data, sequence) {
      let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
      const {
        kind = 'peptide'
      } = options;
      currentSymbol = 0;
      // we normalize the sequence to 3 letter codes

      if (kind === 'peptide') {
        sequence = sequenceToMF(sequence);
      } else {
        sequence = sequenceToMF$1(sequence, options);
      }
      const result = {
        begin: '',
        end: '',
        residues: []
      };
      const STATE_BEGIN = 0;
      const STATE_MIDDLE = 1;
      const STATE_END = 2;
      let parenthesisLevel = 0;
      let state = STATE_BEGIN; // as long as we don't have an uppercase followed by 2 lowercases
      for (let i = 0; i < sequence.length; i++) {
        let currentChar = sequence.charAt(i);
        let nextChar = i < sequence.length - 1 ? sequence.charAt(i + 1) : '';
        let nextNextChar = i < sequence.length - 2 ? sequence.charAt(i + 2) : '';
        if (state === STATE_BEGIN && currentChar.match(/[A-Z]/) && nextChar.match(/[a-z]/) && nextNextChar.match(/[a-z]/) && parenthesisLevel === 0) {
          state = STATE_MIDDLE;
        }
        if (state === STATE_MIDDLE && !sequence.substring(i).match(/[A-Z][a-z][a-z]/) && !currentChar.match(/[a-z]/) && parenthesisLevel === 0) {
          state = STATE_END;
        } else if (currentChar.match(/[A-Z]/) && nextChar.match(/[a-z]/) && nextNextChar.match(/[a-z]/) && parenthesisLevel === 0) {
          result.residues.push('');
        }
        switch (state) {
          case STATE_BEGIN:
            result.begin = result.begin + currentChar;
            break;
          case STATE_MIDDLE:
            result.residues[result.residues.length - 1] = result.residues[result.residues.length - 1] + currentChar;
            break;
          case STATE_END:
            result.end = result.end + currentChar;
            break;
        }
        if (currentChar === '(') {
          parenthesisLevel++;
        } else if (currentChar === ')') {
          parenthesisLevel--;
        }
      }

      // we process all the residues
      let alternatives = {};
      let replacements = {};
      for (let i = 0; i < result.residues.length; i++) {
        let label = result.residues[i];
        let residue = {
          value: label,
          results: {
            begin: [],
            end: []
          }
        };
        residue.fromBegin = i + 1;
        residue.fromEnd = result.residues.length - i;
        residue.kind = 'residue';
        if (label.includes('(')) {
          getModifiedReplacement(label, residue, alternatives, replacements);
        } else if (groupsObject[label] && groupsObject[label].oneLetter) {
          residue.label = groupsObject[label].oneLetter;
        } else {
          getUnknownReplacement(label, residue, replacements);
        }
        result.residues[i] = residue;
      }
      result.begin = removeStartEndParenthesis(result.begin);
      result.end = removeStartEndParenthesis(result.end);
      if (result.begin.length > 2) {
        let label = options.kind === 'peptide' ? 'Nter' : "5'";
        replacements[result.begin] = {
          label
        };
        result.begin = label;
      }
      if (result.end.length > 2) {
        let label = options.kind === 'peptide' ? 'Cter' : "3'";
        replacements[result.end] = {
          label
        };
        result.end = label;
      }
      result.begin = {
        label: result.begin,
        kind: 'begin'
      };
      result.end = {
        label: result.end,
        kind: 'end'
      };
      result.alternatives = alternatives;
      result.replacements = replacements;
      result.all = [result.begin].concat(result.residues, [result.end]);
      result.all.forEach(entry => {
        entry.info = {
          nbOver: 0,
          nbUnder: 0
        };
      });
      data.residues = result;
    }
    function getUnknownReplacement(unknownResidue, residue, replacements) {
      if (!replacements[unknownResidue]) {
        replacements[unknownResidue] = {
          label: SYMBOLS[currentSymbol] || '?',
          id: unknownResidue
        };
      }
      currentSymbol++;
      residue.replaced = true;
      residue.label = replacements[unknownResidue].label;
    }
    function getModifiedReplacement(modifiedResidue, residue, alternatives, replacements) {
      if (!replacements[modifiedResidue]) {
        let position = modifiedResidue.indexOf('(');
        let residueCode = modifiedResidue.substring(0, position);
        let modification = removeStartEndParenthesis(modifiedResidue.substring(position));
        if (groupsObject[residueCode] && groupsObject[residueCode].alternativeOneLetter) {
          let alternativeOneLetter = groupsObject[residueCode].alternativeOneLetter;
          if (!alternatives[alternativeOneLetter]) {
            alternatives[alternativeOneLetter] = {
              count: 1
            };
          } else {
            alternatives[alternativeOneLetter].count++;
          }
          replacements[modifiedResidue] = {
            label: ALTERNATIVES[alternatives[alternativeOneLetter].count - 1] + alternativeOneLetter,
            residue: residueCode,
            modification
          };
        } else {
          getUnknownReplacement(modifiedResidue, residue, replacements);
        }
      }
      residue.replaced = true;
      residue.label = replacements[modifiedResidue].label;
    }
    function removeStartEndParenthesis(mf) {
      if (mf[0] === '(' && mf[mf.length - 1] === ')') {
        return mf.substring(1, mf.length - 1);
      }
      return mf;
    }

    const methods$1 = {};
    const names = [];
    function registerMethods(name, m) {
      if (Array.isArray(name)) {
        for (const _name of name) {
          registerMethods(_name, m);
        }
        return;
      }
      if (typeof name === 'object') {
        for (const _name in name) {
          registerMethods(_name, name[_name]);
        }
        return;
      }
      addMethodNames(Object.getOwnPropertyNames(m));
      methods$1[name] = Object.assign(methods$1[name] || {}, m);
    }
    function getMethodsFor(name) {
      return methods$1[name] || {};
    }
    function getMethodNames() {
      return [...new Set(names)];
    }
    function addMethodNames(_names) {
      names.push(..._names);
    }

    // Map function
    function map(array, block) {
      let i;
      const il = array.length;
      const result = [];
      for (i = 0; i < il; i++) {
        result.push(block(array[i]));
      }
      return result;
    } // Filter function

    function filter(array, block) {
      let i;
      const il = array.length;
      const result = [];
      for (i = 0; i < il; i++) {
        if (block(array[i])) {
          result.push(array[i]);
        }
      }
      return result;
    } // Degrees to radians

    function radians(d) {
      return d % 360 * Math.PI / 180;
    } // Radians to degrees

    function camelCase(s) {
      return s.toLowerCase().replace(/-(.)/g, function (m, g) {
        return g.toUpperCase();
      });
    } // Convert camel cased string to dash separated

    function unCamelCase(s) {
      return s.replace(/([A-Z])/g, function (m, g) {
        return '-' + g.toLowerCase();
      });
    } // Capitalize first letter of a string

    function capitalize(s) {
      return s.charAt(0).toUpperCase() + s.slice(1);
    } // Calculate proportional width and height values when necessary

    function proportionalSize(element, width, height, box) {
      if (width == null || height == null) {
        box = box || element.bbox();
        if (width == null) {
          width = box.width / box.height * height;
        } else if (height == null) {
          height = box.height / box.width * width;
        }
      }
      return {
        width: width,
        height: height
      };
    }
    /**
     * This function adds support for string origins.
     * It searches for an origin in o.origin o.ox and o.originX.
     * This way, origin: {x: 'center', y: 50} can be passed as well as ox: 'center', oy: 50
    **/

    function getOrigin(o, element) {
      const origin = o.origin; // First check if origin is in ox or originX

      let ox = o.ox != null ? o.ox : o.originX != null ? o.originX : 'center';
      let oy = o.oy != null ? o.oy : o.originY != null ? o.originY : 'center'; // Then check if origin was used and overwrite in that case

      if (origin != null) {
        [ox, oy] = Array.isArray(origin) ? origin : typeof origin === 'object' ? [origin.x, origin.y] : [origin, origin];
      } // Make sure to only call bbox when actually needed

      const condX = typeof ox === 'string';
      const condY = typeof oy === 'string';
      if (condX || condY) {
        const {
          height,
          width,
          x,
          y
        } = element.bbox(); // And only overwrite if string was passed for this specific axis

        if (condX) {
          ox = ox.includes('left') ? x : ox.includes('right') ? x + width : x + width / 2;
        }
        if (condY) {
          oy = oy.includes('top') ? y : oy.includes('bottom') ? y + height : y + height / 2;
        }
      } // Return the origin as it is if it wasn't a string

      return [ox, oy];
    }

    // Default namespaces
    const svg = 'http://www.w3.org/2000/svg';
    const html = 'http://www.w3.org/1999/xhtml';
    const xmlns = 'http://www.w3.org/2000/xmlns/';
    const xlink = 'http://www.w3.org/1999/xlink';
    const svgjs = 'http://svgjs.dev/svgjs';
    const globals = {
      window: typeof window === 'undefined' ? null : window,
      document: typeof document === 'undefined' ? null : document
    };
    class Base {// constructor (node/*, {extensions = []} */) {
      //   // this.tags = []
      //   //
      //   // for (let extension of extensions) {
      //   //   extension.setup.call(this, node)
      //   //   this.tags.push(extension.name)
      //   // }
      // }
    }
    const elements = {};
    const root = '___SYMBOL___ROOT___'; // Method for element creation

    function create$6(name) {
      let ns = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : svg;
      // create element
      return globals.document.createElementNS(ns, name);
    }
    function makeInstance(element) {
      let isHTML = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
      if (element instanceof Base) return element;
      if (typeof element === 'object') {
        return adopter(element);
      }
      if (element == null) {
        return new elements[root]();
      }
      if (typeof element === 'string' && element.charAt(0) !== '<') {
        return adopter(globals.document.querySelector(element));
      } // Make sure, that HTML elements are created with the correct namespace

      const wrapper = isHTML ? globals.document.createElement('div') : create$6('svg');
      wrapper.innerHTML = element; // We can use firstChild here because we know,
      // that the first char is < and thus an element

      element = adopter(wrapper.firstChild); // make sure, that element doesnt have its wrapper attached

      wrapper.removeChild(wrapper.firstChild);
      return element;
    }
    function nodeOrNew(name, node) {
      return node instanceof globals.window.Node ? node : create$6(name);
    } // Adopt existing svg elements

    function adopt(node) {
      // check for presence of node
      if (!node) return null; // make sure a node isn't already adopted

      if (node.instance instanceof Base) return node.instance;
      if (node.nodeName === '#document-fragment') {
        return new elements.Fragment(node);
      } // initialize variables

      let className = capitalize(node.nodeName || 'Dom'); // Make sure that gradients are adopted correctly

      if (className === 'LinearGradient' || className === 'RadialGradient') {
        className = 'Gradient'; // Fallback to Dom if element is not known
      } else if (!elements[className]) {
        className = 'Dom';
      }
      return new elements[className](node);
    }
    let adopter = adopt;
    function register(element) {
      let name = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : element.name;
      let asRoot = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
      elements[name] = element;
      if (asRoot) elements[root] = element;
      addMethodNames(Object.getOwnPropertyNames(element.prototype));
      return element;
    }
    function getClass(name) {
      return elements[name];
    } // Element id sequence

    let did = 1000; // Get next named element id

    function eid(name) {
      return 'Svgjs' + capitalize(name) + did++;
    } // Deep new id assignment

    function assignNewId(node) {
      // do the same for SVG child nodes as well
      for (let i = node.children.length - 1; i >= 0; i--) {
        assignNewId(node.children[i]);
      }
      if (node.id) {
        node.id = eid(node.nodeName);
        return node;
      }
      return node;
    } // Method for extending objects

    function extend(modules, methods) {
      let key, i;
      modules = Array.isArray(modules) ? modules : [modules];
      for (i = modules.length - 1; i >= 0; i--) {
        for (key in methods) {
          modules[i].prototype[key] = methods[key];
        }
      }
    }
    function wrapWithAttrCheck(fn) {
      return function () {
        for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
          args[_key] = arguments[_key];
        }
        const o = args[args.length - 1];
        if (o && o.constructor === Object && !(o instanceof Array)) {
          return fn.apply(this, args.slice(0, -1)).attr(o);
        } else {
          return fn.apply(this, args);
        }
      };
    }
    function siblings() {
      return this.parent().children();
    } // Get the current position siblings

    function position() {
      return this.parent().index(this);
    } // Get the next element (will return null if there is none)

    function next() {
      return this.siblings()[this.position() + 1];
    } // Get the next element (will return null if there is none)

    function prev() {
      return this.siblings()[this.position() - 1];
    } // Send given element one step forward

    function forward() {
      const i = this.position();
      const p = this.parent(); // move node one step forward

      p.add(this.remove(), i + 1);
      return this;
    } // Send given element one step backward

    function backward() {
      const i = this.position();
      const p = this.parent();
      p.add(this.remove(), i ? i - 1 : 0);
      return this;
    } // Send given element all the way to the front

    function front() {
      const p = this.parent(); // Move node forward

      p.add(this.remove());
      return this;
    } // Send given element all the way to the back

    function back() {
      const p = this.parent(); // Move node back

      p.add(this.remove(), 0);
      return this;
    } // Inserts a given element before the targeted element

    function before(element) {
      element = makeInstance(element);
      element.remove();
      const i = this.position();
      this.parent().add(element, i);
      return this;
    } // Inserts a given element after the targeted element

    function after(element) {
      element = makeInstance(element);
      element.remove();
      const i = this.position();
      this.parent().add(element, i + 1);
      return this;
    }
    function insertBefore(element) {
      element = makeInstance(element);
      element.before(this);
      return this;
    }
    function insertAfter(element) {
      element = makeInstance(element);
      element.after(this);
      return this;
    }
    registerMethods('Dom', {
      siblings,
      position,
      next,
      prev,
      forward,
      backward,
      front,
      back,
      before,
      after,
      insertBefore,
      insertAfter
    });

    // Parse unit value
    const numberAndUnit = /^([+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?)([a-z%]*)$/i; // Parse hex value

    const hex = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i; // Parse rgb value

    const rgb = /rgb\((\d+),(\d+),(\d+)\)/; // Parse reference id

    const reference = /(#[a-z_][a-z0-9\-_]*)/i; // splits a transformation chain

    const transforms = /\)\s*,?\s*/; // Whitespace

    const whitespace = /\s/g; // Test hex value

    const isHex = /^#[a-f0-9]{3}$|^#[a-f0-9]{6}$/i; // Test rgb value

    const isRgb = /^rgb\(/; // Test for blank string

    const isBlank = /^(\s+)?$/; // Test for numeric string

    const isNumber = /^[+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?$/i; // Test for image url

    const isImage = /\.(jpg|jpeg|png|gif|svg)(\?[^=]+.*)?/i; // split at whitespace and comma

    const delimiter = /[\s,]+/; // Test for path letter

    const isPathLetter = /[MLHVCSQTAZ]/i;
    function classes() {
      const attr = this.attr('class');
      return attr == null ? [] : attr.trim().split(delimiter);
    } // Return true if class exists on the node, false otherwise

    function hasClass(name) {
      return this.classes().indexOf(name) !== -1;
    } // Add class to the node

    function addClass(name) {
      if (!this.hasClass(name)) {
        const array = this.classes();
        array.push(name);
        this.attr('class', array.join(' '));
      }
      return this;
    } // Remove class from the node

    function removeClass(name) {
      if (this.hasClass(name)) {
        this.attr('class', this.classes().filter(function (c) {
          return c !== name;
        }).join(' '));
      }
      return this;
    } // Toggle the presence of a class on the node

    function toggleClass(name) {
      return this.hasClass(name) ? this.removeClass(name) : this.addClass(name);
    }
    registerMethods('Dom', {
      classes,
      hasClass,
      addClass,
      removeClass,
      toggleClass
    });
    function css(style, val) {
      const ret = {};
      if (arguments.length === 0) {
        // get full style as object
        this.node.style.cssText.split(/\s*;\s*/).filter(function (el) {
          return !!el.length;
        }).forEach(function (el) {
          const t = el.split(/\s*:\s*/);
          ret[t[0]] = t[1];
        });
        return ret;
      }
      if (arguments.length < 2) {
        // get style properties as array
        if (Array.isArray(style)) {
          for (const name of style) {
            const cased = camelCase(name);
            ret[cased] = this.node.style[cased];
          }
          return ret;
        } // get style for property

        if (typeof style === 'string') {
          return this.node.style[camelCase(style)];
        } // set styles in object

        if (typeof style === 'object') {
          for (const name in style) {
            // set empty string if null/undefined/'' was given
            this.node.style[camelCase(name)] = style[name] == null || isBlank.test(style[name]) ? '' : style[name];
          }
        }
      } // set style for property

      if (arguments.length === 2) {
        this.node.style[camelCase(style)] = val == null || isBlank.test(val) ? '' : val;
      }
      return this;
    } // Show element

    function show() {
      return this.css('display', '');
    } // Hide element

    function hide() {
      return this.css('display', 'none');
    } // Is element visible?

    function visible() {
      return this.css('display') !== 'none';
    }
    registerMethods('Dom', {
      css,
      show,
      hide,
      visible
    });
    function data(a, v, r) {
      if (a == null) {
        // get an object of attributes
        return this.data(map(filter(this.node.attributes, el => el.nodeName.indexOf('data-') === 0), el => el.nodeName.slice(5)));
      } else if (a instanceof Array) {
        const data = {};
        for (const key of a) {
          data[key] = this.data(key);
        }
        return data;
      } else if (typeof a === 'object') {
        for (v in a) {
          this.data(v, a[v]);
        }
      } else if (arguments.length < 2) {
        try {
          return JSON.parse(this.attr('data-' + a));
        } catch (e) {
          return this.attr('data-' + a);
        }
      } else {
        this.attr('data-' + a, v === null ? null : r === true || typeof v === 'string' || typeof v === 'number' ? v : JSON.stringify(v));
      }
      return this;
    }
    registerMethods('Dom', {
      data
    });
    function remember(k, v) {
      // remember every item in an object individually
      if (typeof arguments[0] === 'object') {
        for (const key in k) {
          this.remember(key, k[key]);
        }
      } else if (arguments.length === 1) {
        // retrieve memory
        return this.memory()[k];
      } else {
        // store memory
        this.memory()[k] = v;
      }
      return this;
    } // Erase a given memory

    function forget() {
      if (arguments.length === 0) {
        this._memory = {};
      } else {
        for (let i = arguments.length - 1; i >= 0; i--) {
          delete this.memory()[arguments[i]];
        }
      }
      return this;
    } // This triggers creation of a new hidden class which is not performant
    // However, this function is not rarely used so it will not happen frequently
    // Return local memory object

    function memory() {
      return this._memory = this._memory || {};
    }
    registerMethods('Dom', {
      remember,
      forget,
      memory
    });
    function sixDigitHex(hex) {
      return hex.length === 4 ? ['#', hex.substring(1, 2), hex.substring(1, 2), hex.substring(2, 3), hex.substring(2, 3), hex.substring(3, 4), hex.substring(3, 4)].join('') : hex;
    }
    function componentHex(component) {
      const integer = Math.round(component);
      const bounded = Math.max(0, Math.min(255, integer));
      const hex = bounded.toString(16);
      return hex.length === 1 ? '0' + hex : hex;
    }
    function is(object, space) {
      for (let i = space.length; i--;) {
        if (object[space[i]] == null) {
          return false;
        }
      }
      return true;
    }
    function getParameters(a, b) {
      const params = is(a, 'rgb') ? {
        _a: a.r,
        _b: a.g,
        _c: a.b,
        _d: 0,
        space: 'rgb'
      } : is(a, 'xyz') ? {
        _a: a.x,
        _b: a.y,
        _c: a.z,
        _d: 0,
        space: 'xyz'
      } : is(a, 'hsl') ? {
        _a: a.h,
        _b: a.s,
        _c: a.l,
        _d: 0,
        space: 'hsl'
      } : is(a, 'lab') ? {
        _a: a.l,
        _b: a.a,
        _c: a.b,
        _d: 0,
        space: 'lab'
      } : is(a, 'lch') ? {
        _a: a.l,
        _b: a.c,
        _c: a.h,
        _d: 0,
        space: 'lch'
      } : is(a, 'cmyk') ? {
        _a: a.c,
        _b: a.m,
        _c: a.y,
        _d: a.k,
        space: 'cmyk'
      } : {
        _a: 0,
        _b: 0,
        _c: 0,
        space: 'rgb'
      };
      params.space = b || params.space;
      return params;
    }
    function cieSpace(space) {
      if (space === 'lab' || space === 'xyz' || space === 'lch') {
        return true;
      } else {
        return false;
      }
    }
    function hueToRgb(p, q, t) {
      if (t < 0) t += 1;
      if (t > 1) t -= 1;
      if (t < 1 / 6) return p + (q - p) * 6 * t;
      if (t < 1 / 2) return q;
      if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
      return p;
    }
    class Color {
      constructor() {
        this.init(...arguments);
      } // Test if given value is a color

      static isColor(color) {
        return color && (color instanceof Color || this.isRgb(color) || this.test(color));
      } // Test if given value is an rgb object

      static isRgb(color) {
        return color && typeof color.r === 'number' && typeof color.g === 'number' && typeof color.b === 'number';
      }
      /*
      Generating random colors
      */

      static random() {
        let mode = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'vibrant';
        let t = arguments.length > 1 ? arguments[1] : undefined;
        // Get the math modules
        const {
          random,
          round,
          sin,
          PI: pi
        } = Math; // Run the correct generator

        if (mode === 'vibrant') {
          const l = (81 - 57) * random() + 57;
          const c = (83 - 45) * random() + 45;
          const h = 360 * random();
          const color = new Color(l, c, h, 'lch');
          return color;
        } else if (mode === 'sine') {
          t = t == null ? random() : t;
          const r = round(80 * sin(2 * pi * t / 0.5 + 0.01) + 150);
          const g = round(50 * sin(2 * pi * t / 0.5 + 4.6) + 200);
          const b = round(100 * sin(2 * pi * t / 0.5 + 2.3) + 150);
          const color = new Color(r, g, b);
          return color;
        } else if (mode === 'pastel') {
          const l = (94 - 86) * random() + 86;
          const c = (26 - 9) * random() + 9;
          const h = 360 * random();
          const color = new Color(l, c, h, 'lch');
          return color;
        } else if (mode === 'dark') {
          const l = 10 + 10 * random();
          const c = (125 - 75) * random() + 86;
          const h = 360 * random();
          const color = new Color(l, c, h, 'lch');
          return color;
        } else if (mode === 'rgb') {
          const r = 255 * random();
          const g = 255 * random();
          const b = 255 * random();
          const color = new Color(r, g, b);
          return color;
        } else if (mode === 'lab') {
          const l = 100 * random();
          const a = 256 * random() - 128;
          const b = 256 * random() - 128;
          const color = new Color(l, a, b, 'lab');
          return color;
        } else if (mode === 'grey') {
          const grey = 255 * random();
          const color = new Color(grey, grey, grey);
          return color;
        } else {
          throw new Error('Unsupported random color mode');
        }
      } // Test if given value is a color string

      static test(color) {
        return typeof color === 'string' && (isHex.test(color) || isRgb.test(color));
      }
      cmyk() {
        // Get the rgb values for the current color
        const {
          _a,
          _b,
          _c
        } = this.rgb();
        const [r, g, b] = [_a, _b, _c].map(v => v / 255); // Get the cmyk values in an unbounded format

        const k = Math.min(1 - r, 1 - g, 1 - b);
        if (k === 1) {
          // Catch the black case
          return new Color(0, 0, 0, 1, 'cmyk');
        }
        const c = (1 - r - k) / (1 - k);
        const m = (1 - g - k) / (1 - k);
        const y = (1 - b - k) / (1 - k); // Construct the new color

        const color = new Color(c, m, y, k, 'cmyk');
        return color;
      }
      hsl() {
        // Get the rgb values
        const {
          _a,
          _b,
          _c
        } = this.rgb();
        const [r, g, b] = [_a, _b, _c].map(v => v / 255); // Find the maximum and minimum values to get the lightness

        const max = Math.max(r, g, b);
        const min = Math.min(r, g, b);
        const l = (max + min) / 2; // If the r, g, v values are identical then we are grey

        const isGrey = max === min; // Calculate the hue and saturation

        const delta = max - min;
        const s = isGrey ? 0 : l > 0.5 ? delta / (2 - max - min) : delta / (max + min);
        const h = isGrey ? 0 : max === r ? ((g - b) / delta + (g < b ? 6 : 0)) / 6 : max === g ? ((b - r) / delta + 2) / 6 : max === b ? ((r - g) / delta + 4) / 6 : 0; // Construct and return the new color

        const color = new Color(360 * h, 100 * s, 100 * l, 'hsl');
        return color;
      }
      init() {
        let a = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
        let b = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
        let c = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
        let d = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0;
        let space = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 'rgb';
        // This catches the case when a falsy value is passed like ''
        a = !a ? 0 : a; // Reset all values in case the init function is rerun with new color space

        if (this.space) {
          for (const component in this.space) {
            delete this[this.space[component]];
          }
        }
        if (typeof a === 'number') {
          // Allow for the case that we don't need d...
          space = typeof d === 'string' ? d : space;
          d = typeof d === 'string' ? 0 : d; // Assign the values straight to the color

          Object.assign(this, {
            _a: a,
            _b: b,
            _c: c,
            _d: d,
            space
          }); // If the user gave us an array, make the color from it
        } else if (a instanceof Array) {
          this.space = b || (typeof a[3] === 'string' ? a[3] : a[4]) || 'rgb';
          Object.assign(this, {
            _a: a[0],
            _b: a[1],
            _c: a[2],
            _d: a[3] || 0
          });
        } else if (a instanceof Object) {
          // Set the object up and assign its values directly
          const values = getParameters(a, b);
          Object.assign(this, values);
        } else if (typeof a === 'string') {
          if (isRgb.test(a)) {
            const noWhitespace = a.replace(whitespace, '');
            const [_a, _b, _c] = rgb.exec(noWhitespace).slice(1, 4).map(v => parseInt(v));
            Object.assign(this, {
              _a,
              _b,
              _c,
              _d: 0,
              space: 'rgb'
            });
          } else if (isHex.test(a)) {
            const hexParse = v => parseInt(v, 16);
            const [, _a, _b, _c] = hex.exec(sixDigitHex(a)).map(hexParse);
            Object.assign(this, {
              _a,
              _b,
              _c,
              _d: 0,
              space: 'rgb'
            });
          } else throw Error('Unsupported string format, can\'t construct Color');
        } // Now add the components as a convenience

        const {
          _a,
          _b,
          _c,
          _d
        } = this;
        const components = this.space === 'rgb' ? {
          r: _a,
          g: _b,
          b: _c
        } : this.space === 'xyz' ? {
          x: _a,
          y: _b,
          z: _c
        } : this.space === 'hsl' ? {
          h: _a,
          s: _b,
          l: _c
        } : this.space === 'lab' ? {
          l: _a,
          a: _b,
          b: _c
        } : this.space === 'lch' ? {
          l: _a,
          c: _b,
          h: _c
        } : this.space === 'cmyk' ? {
          c: _a,
          m: _b,
          y: _c,
          k: _d
        } : {};
        Object.assign(this, components);
      }
      lab() {
        // Get the xyz color
        const {
          x,
          y,
          z
        } = this.xyz(); // Get the lab components

        const l = 116 * y - 16;
        const a = 500 * (x - y);
        const b = 200 * (y - z); // Construct and return a new color

        const color = new Color(l, a, b, 'lab');
        return color;
      }
      lch() {
        // Get the lab color directly
        const {
          l,
          a,
          b
        } = this.lab(); // Get the chromaticity and the hue using polar coordinates

        const c = Math.sqrt(a ** 2 + b ** 2);
        let h = 180 * Math.atan2(b, a) / Math.PI;
        if (h < 0) {
          h *= -1;
          h = 360 - h;
        } // Make a new color and return it

        const color = new Color(l, c, h, 'lch');
        return color;
      }
      /*
      Conversion Methods
      */

      rgb() {
        if (this.space === 'rgb') {
          return this;
        } else if (cieSpace(this.space)) {
          // Convert to the xyz color space
          let {
            x,
            y,
            z
          } = this;
          if (this.space === 'lab' || this.space === 'lch') {
            // Get the values in the lab space
            let {
              l,
              a,
              b
            } = this;
            if (this.space === 'lch') {
              const {
                c,
                h
              } = this;
              const dToR = Math.PI / 180;
              a = c * Math.cos(dToR * h);
              b = c * Math.sin(dToR * h);
            } // Undo the nonlinear function

            const yL = (l + 16) / 116;
            const xL = a / 500 + yL;
            const zL = yL - b / 200; // Get the xyz values

            const ct = 16 / 116;
            const mx = 0.008856;
            const nm = 7.787;
            x = 0.95047 * (xL ** 3 > mx ? xL ** 3 : (xL - ct) / nm);
            y = 1.00000 * (yL ** 3 > mx ? yL ** 3 : (yL - ct) / nm);
            z = 1.08883 * (zL ** 3 > mx ? zL ** 3 : (zL - ct) / nm);
          } // Convert xyz to unbounded rgb values

          const rU = x * 3.2406 + y * -1.5372 + z * -0.4986;
          const gU = x * -0.9689 + y * 1.8758 + z * 0.0415;
          const bU = x * 0.0557 + y * -0.2040 + z * 1.0570; // Convert the values to true rgb values

          const pow = Math.pow;
          const bd = 0.0031308;
          const r = rU > bd ? 1.055 * pow(rU, 1 / 2.4) - 0.055 : 12.92 * rU;
          const g = gU > bd ? 1.055 * pow(gU, 1 / 2.4) - 0.055 : 12.92 * gU;
          const b = bU > bd ? 1.055 * pow(bU, 1 / 2.4) - 0.055 : 12.92 * bU; // Make and return the color

          const color = new Color(255 * r, 255 * g, 255 * b);
          return color;
        } else if (this.space === 'hsl') {
          // https://bgrins.github.io/TinyColor/docs/tinycolor.html
          // Get the current hsl values
          let {
            h,
            s,
            l
          } = this;
          h /= 360;
          s /= 100;
          l /= 100; // If we are grey, then just make the color directly

          if (s === 0) {
            l *= 255;
            const color = new Color(l, l, l);
            return color;
          } // TODO I have no idea what this does :D If you figure it out, tell me!

          const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
          const p = 2 * l - q; // Get the rgb values

          const r = 255 * hueToRgb(p, q, h + 1 / 3);
          const g = 255 * hueToRgb(p, q, h);
          const b = 255 * hueToRgb(p, q, h - 1 / 3); // Make a new color

          const color = new Color(r, g, b);
          return color;
        } else if (this.space === 'cmyk') {
          // https://gist.github.com/felipesabino/5066336
          // Get the normalised cmyk values
          const {
            c,
            m,
            y,
            k
          } = this; // Get the rgb values

          const r = 255 * (1 - Math.min(1, c * (1 - k) + k));
          const g = 255 * (1 - Math.min(1, m * (1 - k) + k));
          const b = 255 * (1 - Math.min(1, y * (1 - k) + k)); // Form the color and return it

          const color = new Color(r, g, b);
          return color;
        } else {
          return this;
        }
      }
      toArray() {
        const {
          _a,
          _b,
          _c,
          _d,
          space
        } = this;
        return [_a, _b, _c, _d, space];
      }
      toHex() {
        const [r, g, b] = this._clamped().map(componentHex);
        return `#${r}${g}${b}`;
      }
      toRgb() {
        const [rV, gV, bV] = this._clamped();
        const string = `rgb(${rV},${gV},${bV})`;
        return string;
      }
      toString() {
        return this.toHex();
      }
      xyz() {
        // Normalise the red, green and blue values
        const {
          _a: r255,
          _b: g255,
          _c: b255
        } = this.rgb();
        const [r, g, b] = [r255, g255, b255].map(v => v / 255); // Convert to the lab rgb space

        const rL = r > 0.04045 ? Math.pow((r + 0.055) / 1.055, 2.4) : r / 12.92;
        const gL = g > 0.04045 ? Math.pow((g + 0.055) / 1.055, 2.4) : g / 12.92;
        const bL = b > 0.04045 ? Math.pow((b + 0.055) / 1.055, 2.4) : b / 12.92; // Convert to the xyz color space without bounding the values

        const xU = (rL * 0.4124 + gL * 0.3576 + bL * 0.1805) / 0.95047;
        const yU = (rL * 0.2126 + gL * 0.7152 + bL * 0.0722) / 1.00000;
        const zU = (rL * 0.0193 + gL * 0.1192 + bL * 0.9505) / 1.08883; // Get the proper xyz values by applying the bounding

        const x = xU > 0.008856 ? Math.pow(xU, 1 / 3) : 7.787 * xU + 16 / 116;
        const y = yU > 0.008856 ? Math.pow(yU, 1 / 3) : 7.787 * yU + 16 / 116;
        const z = zU > 0.008856 ? Math.pow(zU, 1 / 3) : 7.787 * zU + 16 / 116; // Make and return the color

        const color = new Color(x, y, z, 'xyz');
        return color;
      }
      /*
      Input and Output methods
      */

      _clamped() {
        const {
          _a,
          _b,
          _c
        } = this.rgb();
        const {
          max,
          min,
          round
        } = Math;
        const format = v => max(0, min(round(v), 255));
        return [_a, _b, _c].map(format);
      }
      /*
      Constructing colors
      */
    }

    class Point {
      // Initialize
      constructor() {
        this.init(...arguments);
      } // Clone point

      clone() {
        return new Point(this);
      }
      init(x, y) {
        const base = {
          x: 0,
          y: 0
        }; // ensure source as object

        const source = Array.isArray(x) ? {
          x: x[0],
          y: x[1]
        } : typeof x === 'object' ? {
          x: x.x,
          y: x.y
        } : {
          x: x,
          y: y
        }; // merge source

        this.x = source.x == null ? base.x : source.x;
        this.y = source.y == null ? base.y : source.y;
        return this;
      }
      toArray() {
        return [this.x, this.y];
      }
      transform(m) {
        return this.clone().transformO(m);
      } // Transform point with matrix

      transformO(m) {
        if (!Matrix.isMatrixLike(m)) {
          m = new Matrix(m);
        }
        const {
          x,
          y
        } = this; // Perform the matrix multiplication

        this.x = m.a * x + m.c * y + m.e;
        this.y = m.b * x + m.d * y + m.f;
        return this;
      }
    }
    function point(x, y) {
      return new Point(x, y).transform(this.screenCTM().inverse());
    }
    function closeEnough(a, b, threshold) {
      return Math.abs(b - a) < (threshold || 1e-6);
    }
    class Matrix {
      constructor() {
        this.init(...arguments);
      }
      static formatTransforms(o) {
        // Get all of the parameters required to form the matrix
        const flipBoth = o.flip === 'both' || o.flip === true;
        const flipX = o.flip && (flipBoth || o.flip === 'x') ? -1 : 1;
        const flipY = o.flip && (flipBoth || o.flip === 'y') ? -1 : 1;
        const skewX = o.skew && o.skew.length ? o.skew[0] : isFinite(o.skew) ? o.skew : isFinite(o.skewX) ? o.skewX : 0;
        const skewY = o.skew && o.skew.length ? o.skew[1] : isFinite(o.skew) ? o.skew : isFinite(o.skewY) ? o.skewY : 0;
        const scaleX = o.scale && o.scale.length ? o.scale[0] * flipX : isFinite(o.scale) ? o.scale * flipX : isFinite(o.scaleX) ? o.scaleX * flipX : flipX;
        const scaleY = o.scale && o.scale.length ? o.scale[1] * flipY : isFinite(o.scale) ? o.scale * flipY : isFinite(o.scaleY) ? o.scaleY * flipY : flipY;
        const shear = o.shear || 0;
        const theta = o.rotate || o.theta || 0;
        const origin = new Point(o.origin || o.around || o.ox || o.originX, o.oy || o.originY);
        const ox = origin.x;
        const oy = origin.y; // We need Point to be invalid if nothing was passed because we cannot default to 0 here. Thats why NaN

        const position = new Point(o.position || o.px || o.positionX || NaN, o.py || o.positionY || NaN);
        const px = position.x;
        const py = position.y;
        const translate = new Point(o.translate || o.tx || o.translateX, o.ty || o.translateY);
        const tx = translate.x;
        const ty = translate.y;
        const relative = new Point(o.relative || o.rx || o.relativeX, o.ry || o.relativeY);
        const rx = relative.x;
        const ry = relative.y; // Populate all of the values

        return {
          scaleX,
          scaleY,
          skewX,
          skewY,
          shear,
          theta,
          rx,
          ry,
          tx,
          ty,
          ox,
          oy,
          px,
          py
        };
      }
      static fromArray(a) {
        return {
          a: a[0],
          b: a[1],
          c: a[2],
          d: a[3],
          e: a[4],
          f: a[5]
        };
      }
      static isMatrixLike(o) {
        return o.a != null || o.b != null || o.c != null || o.d != null || o.e != null || o.f != null;
      } // left matrix, right matrix, target matrix which is overwritten

      static matrixMultiply(l, r, o) {
        // Work out the product directly
        const a = l.a * r.a + l.c * r.b;
        const b = l.b * r.a + l.d * r.b;
        const c = l.a * r.c + l.c * r.d;
        const d = l.b * r.c + l.d * r.d;
        const e = l.e + l.a * r.e + l.c * r.f;
        const f = l.f + l.b * r.e + l.d * r.f; // make sure to use local variables because l/r and o could be the same

        o.a = a;
        o.b = b;
        o.c = c;
        o.d = d;
        o.e = e;
        o.f = f;
        return o;
      }
      around(cx, cy, matrix) {
        return this.clone().aroundO(cx, cy, matrix);
      } // Transform around a center point

      aroundO(cx, cy, matrix) {
        const dx = cx || 0;
        const dy = cy || 0;
        return this.translateO(-dx, -dy).lmultiplyO(matrix).translateO(dx, dy);
      } // Clones this matrix

      clone() {
        return new Matrix(this);
      } // Decomposes this matrix into its affine parameters

      decompose() {
        let cx = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
        let cy = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
        // Get the parameters from the matrix
        const a = this.a;
        const b = this.b;
        const c = this.c;
        const d = this.d;
        const e = this.e;
        const f = this.f; // Figure out if the winding direction is clockwise or counterclockwise

        const determinant = a * d - b * c;
        const ccw = determinant > 0 ? 1 : -1; // Since we only shear in x, we can use the x basis to get the x scale
        // and the rotation of the resulting matrix

        const sx = ccw * Math.sqrt(a * a + b * b);
        const thetaRad = Math.atan2(ccw * b, ccw * a);
        const theta = 180 / Math.PI * thetaRad;
        const ct = Math.cos(thetaRad);
        const st = Math.sin(thetaRad); // We can then solve the y basis vector simultaneously to get the other
        // two affine parameters directly from these parameters

        const lam = (a * c + b * d) / determinant;
        const sy = c * sx / (lam * a - b) || d * sx / (lam * b + a); // Use the translations

        const tx = e - cx + cx * ct * sx + cy * (lam * ct * sx - st * sy);
        const ty = f - cy + cx * st * sx + cy * (lam * st * sx + ct * sy); // Construct the decomposition and return it

        return {
          // Return the affine parameters
          scaleX: sx,
          scaleY: sy,
          shear: lam,
          rotate: theta,
          translateX: tx,
          translateY: ty,
          originX: cx,
          originY: cy,
          // Return the matrix parameters
          a: this.a,
          b: this.b,
          c: this.c,
          d: this.d,
          e: this.e,
          f: this.f
        };
      } // Check if two matrices are equal

      equals(other) {
        if (other === this) return true;
        const comp = new Matrix(other);
        return closeEnough(this.a, comp.a) && closeEnough(this.b, comp.b) && closeEnough(this.c, comp.c) && closeEnough(this.d, comp.d) && closeEnough(this.e, comp.e) && closeEnough(this.f, comp.f);
      } // Flip matrix on x or y, at a given offset

      flip(axis, around) {
        return this.clone().flipO(axis, around);
      }
      flipO(axis, around) {
        return axis === 'x' ? this.scaleO(-1, 1, around, 0) : axis === 'y' ? this.scaleO(1, -1, 0, around) : this.scaleO(-1, -1, axis, around || axis); // Define an x, y flip point
      } // Initialize

      init(source) {
        const base = Matrix.fromArray([1, 0, 0, 1, 0, 0]); // ensure source as object

        source = source instanceof Element ? source.matrixify() : typeof source === 'string' ? Matrix.fromArray(source.split(delimiter).map(parseFloat)) : Array.isArray(source) ? Matrix.fromArray(source) : typeof source === 'object' && Matrix.isMatrixLike(source) ? source : typeof source === 'object' ? new Matrix().transform(source) : arguments.length === 6 ? Matrix.fromArray([].slice.call(arguments)) : base; // Merge the source matrix with the base matrix

        this.a = source.a != null ? source.a : base.a;
        this.b = source.b != null ? source.b : base.b;
        this.c = source.c != null ? source.c : base.c;
        this.d = source.d != null ? source.d : base.d;
        this.e = source.e != null ? source.e : base.e;
        this.f = source.f != null ? source.f : base.f;
        return this;
      }
      inverse() {
        return this.clone().inverseO();
      } // Inverses matrix

      inverseO() {
        // Get the current parameters out of the matrix
        const a = this.a;
        const b = this.b;
        const c = this.c;
        const d = this.d;
        const e = this.e;
        const f = this.f; // Invert the 2x2 matrix in the top left

        const det = a * d - b * c;
        if (!det) throw new Error('Cannot invert ' + this); // Calculate the top 2x2 matrix

        const na = d / det;
        const nb = -b / det;
        const nc = -c / det;
        const nd = a / det; // Apply the inverted matrix to the top right

        const ne = -(na * e + nc * f);
        const nf = -(nb * e + nd * f); // Construct the inverted matrix

        this.a = na;
        this.b = nb;
        this.c = nc;
        this.d = nd;
        this.e = ne;
        this.f = nf;
        return this;
      }
      lmultiply(matrix) {
        return this.clone().lmultiplyO(matrix);
      }
      lmultiplyO(matrix) {
        const r = this;
        const l = matrix instanceof Matrix ? matrix : new Matrix(matrix);
        return Matrix.matrixMultiply(l, r, this);
      } // Left multiplies by the given matrix

      multiply(matrix) {
        return this.clone().multiplyO(matrix);
      }
      multiplyO(matrix) {
        // Get the matrices
        const l = this;
        const r = matrix instanceof Matrix ? matrix : new Matrix(matrix);
        return Matrix.matrixMultiply(l, r, this);
      } // Rotate matrix

      rotate(r, cx, cy) {
        return this.clone().rotateO(r, cx, cy);
      }
      rotateO(r) {
        let cx = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
        let cy = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
        // Convert degrees to radians
        r = radians(r);
        const cos = Math.cos(r);
        const sin = Math.sin(r);
        const {
          a,
          b,
          c,
          d,
          e,
          f
        } = this;
        this.a = a * cos - b * sin;
        this.b = b * cos + a * sin;
        this.c = c * cos - d * sin;
        this.d = d * cos + c * sin;
        this.e = e * cos - f * sin + cy * sin - cx * cos + cx;
        this.f = f * cos + e * sin - cx * sin - cy * cos + cy;
        return this;
      } // Scale matrix

      scale(x, y, cx, cy) {
        return this.clone().scaleO(...arguments);
      }
      scaleO(x) {
        let y = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : x;
        let cx = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
        let cy = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0;
        // Support uniform scaling
        if (arguments.length === 3) {
          cy = cx;
          cx = y;
          y = x;
        }
        const {
          a,
          b,
          c,
          d,
          e,
          f
        } = this;
        this.a = a * x;
        this.b = b * y;
        this.c = c * x;
        this.d = d * y;
        this.e = e * x - cx * x + cx;
        this.f = f * y - cy * y + cy;
        return this;
      } // Shear matrix

      shear(a, cx, cy) {
        return this.clone().shearO(a, cx, cy);
      }
      shearO(lx) {
        let cy = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
        const {
          a,
          b,
          c,
          d,
          e,
          f
        } = this;
        this.a = a + b * lx;
        this.c = c + d * lx;
        this.e = e + f * lx - cy * lx;
        return this;
      } // Skew Matrix

      skew(x, y, cx, cy) {
        return this.clone().skewO(...arguments);
      }
      skewO(x) {
        let y = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : x;
        let cx = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
        let cy = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0;
        // support uniformal skew
        if (arguments.length === 3) {
          cy = cx;
          cx = y;
          y = x;
        } // Convert degrees to radians

        x = radians(x);
        y = radians(y);
        const lx = Math.tan(x);
        const ly = Math.tan(y);
        const {
          a,
          b,
          c,
          d,
          e,
          f
        } = this;
        this.a = a + b * lx;
        this.b = b + a * ly;
        this.c = c + d * lx;
        this.d = d + c * ly;
        this.e = e + f * lx - cy * lx;
        this.f = f + e * ly - cx * ly;
        return this;
      } // SkewX

      skewX(x, cx, cy) {
        return this.skew(x, 0, cx, cy);
      } // SkewY

      skewY(y, cx, cy) {
        return this.skew(0, y, cx, cy);
      }
      toArray() {
        return [this.a, this.b, this.c, this.d, this.e, this.f];
      } // Convert matrix to string

      toString() {
        return 'matrix(' + this.a + ',' + this.b + ',' + this.c + ',' + this.d + ',' + this.e + ',' + this.f + ')';
      } // Transform a matrix into another matrix by manipulating the space

      transform(o) {
        // Check if o is a matrix and then left multiply it directly
        if (Matrix.isMatrixLike(o)) {
          const matrix = new Matrix(o);
          return matrix.multiplyO(this);
        } // Get the proposed transformations and the current transformations

        const t = Matrix.formatTransforms(o);
        const current = this;
        const {
          x: ox,
          y: oy
        } = new Point(t.ox, t.oy).transform(current); // Construct the resulting matrix

        const transformer = new Matrix().translateO(t.rx, t.ry).lmultiplyO(current).translateO(-ox, -oy).scaleO(t.scaleX, t.scaleY).skewO(t.skewX, t.skewY).shearO(t.shear).rotateO(t.theta).translateO(ox, oy); // If we want the origin at a particular place, we force it there

        if (isFinite(t.px) || isFinite(t.py)) {
          const origin = new Point(ox, oy).transform(transformer); // TODO: Replace t.px with isFinite(t.px)
          // Doesnt work because t.px is also 0 if it wasnt passed

          const dx = isFinite(t.px) ? t.px - origin.x : 0;
          const dy = isFinite(t.py) ? t.py - origin.y : 0;
          transformer.translateO(dx, dy);
        } // Translate now after positioning

        transformer.translateO(t.tx, t.ty);
        return transformer;
      } // Translate matrix

      translate(x, y) {
        return this.clone().translateO(x, y);
      }
      translateO(x, y) {
        this.e += x || 0;
        this.f += y || 0;
        return this;
      }
      valueOf() {
        return {
          a: this.a,
          b: this.b,
          c: this.c,
          d: this.d,
          e: this.e,
          f: this.f
        };
      }
    }
    function ctm() {
      return new Matrix(this.node.getCTM());
    }
    function screenCTM() {
      /* https://bugzilla.mozilla.org/show_bug.cgi?id=1344537
         This is needed because FF does not return the transformation matrix
         for the inner coordinate system when getScreenCTM() is called on nested svgs.
         However all other Browsers do that */
      if (typeof this.isRoot === 'function' && !this.isRoot()) {
        const rect = this.rect(1, 1);
        const m = rect.node.getScreenCTM();
        rect.remove();
        return new Matrix(m);
      }
      return new Matrix(this.node.getScreenCTM());
    }
    register(Matrix, 'Matrix');
    function parser() {
      // Reuse cached element if possible
      if (!parser.nodes) {
        const svg = makeInstance().size(2, 0);
        svg.node.style.cssText = ['opacity: 0', 'position: absolute', 'left: -100%', 'top: -100%', 'overflow: hidden'].join(';');
        svg.attr('focusable', 'false');
        svg.attr('aria-hidden', 'true');
        const path = svg.path().node;
        parser.nodes = {
          svg,
          path
        };
      }
      if (!parser.nodes.svg.node.parentNode) {
        const b = globals.document.body || globals.document.documentElement;
        parser.nodes.svg.addTo(b);
      }
      return parser.nodes;
    }
    function isNulledBox(box) {
      return !box.width && !box.height && !box.x && !box.y;
    }
    function domContains(node) {
      return node === globals.document || (globals.document.documentElement.contains || function (node) {
        // This is IE - it does not support contains() for top-level SVGs
        while (node.parentNode) {
          node = node.parentNode;
        }
        return node === globals.document;
      }).call(globals.document.documentElement, node);
    }
    class Box {
      constructor() {
        this.init(...arguments);
      }
      addOffset() {
        // offset by window scroll position, because getBoundingClientRect changes when window is scrolled
        this.x += globals.window.pageXOffset;
        this.y += globals.window.pageYOffset;
        return new Box(this);
      }
      init(source) {
        const base = [0, 0, 0, 0];
        source = typeof source === 'string' ? source.split(delimiter).map(parseFloat) : Array.isArray(source) ? source : typeof source === 'object' ? [source.left != null ? source.left : source.x, source.top != null ? source.top : source.y, source.width, source.height] : arguments.length === 4 ? [].slice.call(arguments) : base;
        this.x = source[0] || 0;
        this.y = source[1] || 0;
        this.width = this.w = source[2] || 0;
        this.height = this.h = source[3] || 0; // Add more bounding box properties

        this.x2 = this.x + this.w;
        this.y2 = this.y + this.h;
        this.cx = this.x + this.w / 2;
        this.cy = this.y + this.h / 2;
        return this;
      }
      isNulled() {
        return isNulledBox(this);
      } // Merge rect box with another, return a new instance

      merge(box) {
        const x = Math.min(this.x, box.x);
        const y = Math.min(this.y, box.y);
        const width = Math.max(this.x + this.width, box.x + box.width) - x;
        const height = Math.max(this.y + this.height, box.y + box.height) - y;
        return new Box(x, y, width, height);
      }
      toArray() {
        return [this.x, this.y, this.width, this.height];
      }
      toString() {
        return this.x + ' ' + this.y + ' ' + this.width + ' ' + this.height;
      }
      transform(m) {
        if (!(m instanceof Matrix)) {
          m = new Matrix(m);
        }
        let xMin = Infinity;
        let xMax = -Infinity;
        let yMin = Infinity;
        let yMax = -Infinity;
        const pts = [new Point(this.x, this.y), new Point(this.x2, this.y), new Point(this.x, this.y2), new Point(this.x2, this.y2)];
        pts.forEach(function (p) {
          p = p.transform(m);
          xMin = Math.min(xMin, p.x);
          xMax = Math.max(xMax, p.x);
          yMin = Math.min(yMin, p.y);
          yMax = Math.max(yMax, p.y);
        });
        return new Box(xMin, yMin, xMax - xMin, yMax - yMin);
      }
    }
    function getBox(el, getBBoxFn, retry) {
      let box;
      try {
        // Try to get the box with the provided function
        box = getBBoxFn(el.node); // If the box is worthless and not even in the dom, retry
        // by throwing an error here...

        if (isNulledBox(box) && !domContains(el.node)) {
          throw new Error('Element not in the dom');
        }
      } catch (e) {
        // ... and calling the retry handler here
        box = retry(el);
      }
      return box;
    }
    function bbox() {
      // Function to get bbox is getBBox()
      const getBBox = node => node.getBBox(); // Take all measures so that a stupid browser renders the element
      // so we can get the bbox from it when we try again

      const retry = el => {
        try {
          const clone = el.clone().addTo(parser().svg).show();
          const box = clone.node.getBBox();
          clone.remove();
          return box;
        } catch (e) {
          // We give up...
          throw new Error(`Getting bbox of element "${el.node.nodeName}" is not possible: ${e.toString()}`);
        }
      };
      const box = getBox(this, getBBox, retry);
      const bbox = new Box(box);
      return bbox;
    }
    function rbox(el) {
      const getRBox = node => node.getBoundingClientRect();
      const retry = el => {
        // There is no point in trying tricks here because if we insert the element into the dom ourselves
        // it obviously will be at the wrong position
        throw new Error(`Getting rbox of element "${el.node.nodeName}" is not possible`);
      };
      const box = getBox(this, getRBox, retry);
      const rbox = new Box(box); // If an element was passed, we want the bbox in the coordinate system of that element

      if (el) {
        return rbox.transform(el.screenCTM().inverseO());
      } // Else we want it in absolute screen coordinates
      // Therefore we need to add the scrollOffset

      return rbox.addOffset();
    } // Checks whether the given point is inside the bounding box

    function inside(x, y) {
      const box = this.bbox();
      return x > box.x && y > box.y && x < box.x + box.width && y < box.y + box.height;
    }
    registerMethods({
      viewbox: {
        viewbox(x, y, width, height) {
          // act as getter
          if (x == null) return new Box(this.attr('viewBox')); // act as setter

          return this.attr('viewBox', new Box(x, y, width, height));
        },
        zoom(level, point) {
          // Its best to rely on the attributes here and here is why:
          // clientXYZ: Doesn't work on non-root svgs because they dont have a CSSBox (silly!)
          // getBoundingClientRect: Doesn't work because Chrome just ignores width and height of nested svgs completely
          //                        that means, their clientRect is always as big as the content.
          //                        Furthermore this size is incorrect if the element is further transformed by its parents
          // computedStyle: Only returns meaningful values if css was used with px. We dont go this route here!
          // getBBox: returns the bounding box of its content - that doesnt help!
          let {
            width,
            height
          } = this.attr(['width', 'height']); // Width and height is a string when a number with a unit is present which we can't use
          // So we try clientXYZ

          if (!width && !height || typeof width === 'string' || typeof height === 'string') {
            width = this.node.clientWidth;
            height = this.node.clientHeight;
          } // Giving up...

          if (!width || !height) {
            throw new Error('Impossible to get absolute width and height. Please provide an absolute width and height attribute on the zooming element');
          }
          const v = this.viewbox();
          const zoomX = width / v.width;
          const zoomY = height / v.height;
          const zoom = Math.min(zoomX, zoomY);
          if (level == null) {
            return zoom;
          }
          let zoomAmount = zoom / level; // Set the zoomAmount to the highest value which is safe to process and recover from
          // The * 100 is a bit of wiggle room for the matrix transformation

          if (zoomAmount === Infinity) zoomAmount = Number.MAX_SAFE_INTEGER / 100;
          point = point || new Point(width / 2 / zoomX + v.x, height / 2 / zoomY + v.y);
          const box = new Box(v).transform(new Matrix({
            scale: zoomAmount,
            origin: point
          }));
          return this.viewbox(box);
        }
      }
    });
    register(Box, 'Box');
    class List extends Array {
      constructor() {
        let arr = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
        for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
          args[_key2 - 1] = arguments[_key2];
        }
        super(arr, ...args);
        if (typeof arr === 'number') return this;
        this.length = 0;
        this.push(...arr);
      }
    }
    extend([List], {
      each(fnOrMethodName) {
        for (var _len3 = arguments.length, args = new Array(_len3 > 1 ? _len3 - 1 : 0), _key3 = 1; _key3 < _len3; _key3++) {
          args[_key3 - 1] = arguments[_key3];
        }
        if (typeof fnOrMethodName === 'function') {
          return this.map((el, i, arr) => {
            return fnOrMethodName.call(el, el, i, arr);
          });
        } else {
          return this.map(el => {
            return el[fnOrMethodName](...args);
          });
        }
      },
      toArray() {
        return Array.prototype.concat.apply([], this);
      }
    });
    const reserved = ['toArray', 'constructor', 'each'];
    List.extend = function (methods) {
      methods = methods.reduce((obj, name) => {
        // Don't overwrite own methods
        if (reserved.includes(name)) return obj; // Don't add private methods

        if (name[0] === '_') return obj; // Relay every call to each()

        obj[name] = function () {
          for (var _len4 = arguments.length, attrs = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
            attrs[_key4] = arguments[_key4];
          }
          return this.each(name, ...attrs);
        };
        return obj;
      }, {});
      extend([List], methods);
    };
    function baseFind(query, parent) {
      return new List(map((parent || globals.document).querySelectorAll(query), function (node) {
        return adopt(node);
      }));
    } // Scoped find method

    function find$2(query) {
      return baseFind(query, this.node);
    }
    function findOne(query) {
      return adopt(this.node.querySelector(query));
    }
    let listenerId = 0;
    const windowEvents = {};
    function getEvents(instance) {
      let n = instance.getEventHolder(); // We dont want to save events in global space

      if (n === globals.window) n = windowEvents;
      if (!n.events) n.events = {};
      return n.events;
    }
    function getEventTarget(instance) {
      return instance.getEventTarget();
    }
    function clearEvents(instance) {
      let n = instance.getEventHolder();
      if (n === globals.window) n = windowEvents;
      if (n.events) n.events = {};
    } // Add event binder in the SVG namespace

    function on(node, events, listener, binding, options) {
      const l = listener.bind(binding || node);
      const instance = makeInstance(node);
      const bag = getEvents(instance);
      const n = getEventTarget(instance); // events can be an array of events or a string of events

      events = Array.isArray(events) ? events : events.split(delimiter); // add id to listener

      if (!listener._svgjsListenerId) {
        listener._svgjsListenerId = ++listenerId;
      }
      events.forEach(function (event) {
        const ev = event.split('.')[0];
        const ns = event.split('.')[1] || '*'; // ensure valid object

        bag[ev] = bag[ev] || {};
        bag[ev][ns] = bag[ev][ns] || {}; // reference listener

        bag[ev][ns][listener._svgjsListenerId] = l; // add listener

        n.addEventListener(ev, l, options || false);
      });
    } // Add event unbinder in the SVG namespace

    function off(node, events, listener, options) {
      const instance = makeInstance(node);
      const bag = getEvents(instance);
      const n = getEventTarget(instance); // listener can be a function or a number

      if (typeof listener === 'function') {
        listener = listener._svgjsListenerId;
        if (!listener) return;
      } // events can be an array of events or a string or undefined

      events = Array.isArray(events) ? events : (events || '').split(delimiter);
      events.forEach(function (event) {
        const ev = event && event.split('.')[0];
        const ns = event && event.split('.')[1];
        let namespace, l;
        if (listener) {
          // remove listener reference
          if (bag[ev] && bag[ev][ns || '*']) {
            // removeListener
            n.removeEventListener(ev, bag[ev][ns || '*'][listener], options || false);
            delete bag[ev][ns || '*'][listener];
          }
        } else if (ev && ns) {
          // remove all listeners for a namespaced event
          if (bag[ev] && bag[ev][ns]) {
            for (l in bag[ev][ns]) {
              off(n, [ev, ns].join('.'), l);
            }
            delete bag[ev][ns];
          }
        } else if (ns) {
          // remove all listeners for a specific namespace
          for (event in bag) {
            for (namespace in bag[event]) {
              if (ns === namespace) {
                off(n, [event, ns].join('.'));
              }
            }
          }
        } else if (ev) {
          // remove all listeners for the event
          if (bag[ev]) {
            for (namespace in bag[ev]) {
              off(n, [ev, namespace].join('.'));
            }
            delete bag[ev];
          }
        } else {
          // remove all listeners on a given node
          for (event in bag) {
            off(n, event);
          }
          clearEvents(instance);
        }
      });
    }
    function dispatch(node, event, data, options) {
      const n = getEventTarget(node); // Dispatch event

      if (event instanceof globals.window.Event) {
        n.dispatchEvent(event);
      } else {
        event = new globals.window.CustomEvent(event, {
          detail: data,
          cancelable: true,
          ...options
        });
        n.dispatchEvent(event);
      }
      return event;
    }
    class EventTarget extends Base {
      addEventListener() {}
      dispatch(event, data, options) {
        return dispatch(this, event, data, options);
      }
      dispatchEvent(event) {
        const bag = this.getEventHolder().events;
        if (!bag) return true;
        const events = bag[event.type];
        for (const i in events) {
          for (const j in events[i]) {
            events[i][j](event);
          }
        }
        return !event.defaultPrevented;
      } // Fire given event

      fire(event, data, options) {
        this.dispatch(event, data, options);
        return this;
      }
      getEventHolder() {
        return this;
      }
      getEventTarget() {
        return this;
      } // Unbind event from listener

      off(event, listener) {
        off(this, event, listener);
        return this;
      } // Bind given event to listener

      on(event, listener, binding, options) {
        on(this, event, listener, binding, options);
        return this;
      }
      removeEventListener() {}
    }
    register(EventTarget, 'EventTarget');
    function noop() {} // Default animation values

    const timeline = {
      duration: 400,
      ease: '>',
      delay: 0
    }; // Default attribute values

    const attrs = {
      // fill and stroke
      'fill-opacity': 1,
      'stroke-opacity': 1,
      'stroke-width': 0,
      'stroke-linejoin': 'miter',
      'stroke-linecap': 'butt',
      fill: '#000000',
      stroke: '#000000',
      opacity: 1,
      // position
      x: 0,
      y: 0,
      cx: 0,
      cy: 0,
      // size
      width: 0,
      height: 0,
      // radius
      r: 0,
      rx: 0,
      ry: 0,
      // gradient
      offset: 0,
      'stop-opacity': 1,
      'stop-color': '#000000',
      // text
      'text-anchor': 'start'
    };
    class SVGArray extends Array {
      constructor() {
        super(...arguments);
        this.init(...arguments);
      }
      clone() {
        return new this.constructor(this);
      }
      init(arr) {
        // This catches the case, that native map tries to create an array with new Array(1)
        if (typeof arr === 'number') return this;
        this.length = 0;
        this.push(...this.parse(arr));
        return this;
      } // Parse whitespace separated string

      parse() {
        let array = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
        // If already is an array, no need to parse it
        if (array instanceof Array) return array;
        return array.trim().split(delimiter).map(parseFloat);
      }
      toArray() {
        return Array.prototype.concat.apply([], this);
      }
      toSet() {
        return new Set(this);
      }
      toString() {
        return this.join(' ');
      } // Flattens the array if needed

      valueOf() {
        const ret = [];
        ret.push(...this);
        return ret;
      }
    }
    class SVGNumber {
      // Initialize
      constructor() {
        this.init(...arguments);
      }
      convert(unit) {
        return new SVGNumber(this.value, unit);
      } // Divide number

      divide(number) {
        number = new SVGNumber(number);
        return new SVGNumber(this / number, this.unit || number.unit);
      }
      init(value, unit) {
        unit = Array.isArray(value) ? value[1] : unit;
        value = Array.isArray(value) ? value[0] : value; // initialize defaults

        this.value = 0;
        this.unit = unit || ''; // parse value

        if (typeof value === 'number') {
          // ensure a valid numeric value
          this.value = isNaN(value) ? 0 : !isFinite(value) ? value < 0 ? -3.4e+38 : +3.4e+38 : value;
        } else if (typeof value === 'string') {
          unit = value.match(numberAndUnit);
          if (unit) {
            // make value numeric
            this.value = parseFloat(unit[1]); // normalize

            if (unit[5] === '%') {
              this.value /= 100;
            } else if (unit[5] === 's') {
              this.value *= 1000;
            } // store unit

            this.unit = unit[5];
          }
        } else {
          if (value instanceof SVGNumber) {
            this.value = value.valueOf();
            this.unit = value.unit;
          }
        }
        return this;
      } // Subtract number

      minus(number) {
        number = new SVGNumber(number);
        return new SVGNumber(this - number, this.unit || number.unit);
      } // Add number

      plus(number) {
        number = new SVGNumber(number);
        return new SVGNumber(this + number, this.unit || number.unit);
      } // Multiply number

      times(number) {
        number = new SVGNumber(number);
        return new SVGNumber(this * number, this.unit || number.unit);
      }
      toArray() {
        return [this.value, this.unit];
      }
      toJSON() {
        return this.toString();
      }
      toString() {
        return (this.unit === '%' ? ~~(this.value * 1e8) / 1e6 : this.unit === 's' ? this.value / 1e3 : this.value) + this.unit;
      }
      valueOf() {
        return this.value;
      }
    }
    const hooks = [];
    function registerAttrHook(fn) {
      hooks.push(fn);
    } // Set svg element attribute

    function attr(attr, val, ns) {
      // act as full getter
      if (attr == null) {
        // get an object of attributes
        attr = {};
        val = this.node.attributes;
        for (const node of val) {
          attr[node.nodeName] = isNumber.test(node.nodeValue) ? parseFloat(node.nodeValue) : node.nodeValue;
        }
        return attr;
      } else if (attr instanceof Array) {
        // loop through array and get all values
        return attr.reduce((last, curr) => {
          last[curr] = this.attr(curr);
          return last;
        }, {});
      } else if (typeof attr === 'object' && attr.constructor === Object) {
        // apply every attribute individually if an object is passed
        for (val in attr) this.attr(val, attr[val]);
      } else if (val === null) {
        // remove value
        this.node.removeAttribute(attr);
      } else if (val == null) {
        // act as a getter if the first and only argument is not an object
        val = this.node.getAttribute(attr);
        return val == null ? attrs[attr] : isNumber.test(val) ? parseFloat(val) : val;
      } else {
        // Loop through hooks and execute them to convert value
        val = hooks.reduce((_val, hook) => {
          return hook(attr, _val, this);
        }, val); // ensure correct numeric values (also accepts NaN and Infinity)

        if (typeof val === 'number') {
          val = new SVGNumber(val);
        } else if (Color.isColor(val)) {
          // ensure full hex color
          val = new Color(val);
        } else if (val.constructor === Array) {
          // Check for plain arrays and parse array values
          val = new SVGArray(val);
        } // if the passed attribute is leading...

        if (attr === 'leading') {
          // ... call the leading method instead
          if (this.leading) {
            this.leading(val);
          }
        } else {
          // set given attribute on node
          typeof ns === 'string' ? this.node.setAttributeNS(ns, attr, val.toString()) : this.node.setAttribute(attr, val.toString());
        } // rebuild if required

        if (this.rebuild && (attr === 'font-size' || attr === 'x')) {
          this.rebuild();
        }
      }
      return this;
    }
    class Dom extends EventTarget {
      constructor(node, attrs) {
        super();
        this.node = node;
        this.type = node.nodeName;
        if (attrs && node !== attrs) {
          this.attr(attrs);
        }
      } // Add given element at a position

      add(element, i) {
        element = makeInstance(element); // If non-root svg nodes are added we have to remove their namespaces

        if (element.removeNamespace && this.node instanceof globals.window.SVGElement) {
          element.removeNamespace();
        }
        if (i == null) {
          this.node.appendChild(element.node);
        } else if (element.node !== this.node.childNodes[i]) {
          this.node.insertBefore(element.node, this.node.childNodes[i]);
        }
        return this;
      } // Add element to given container and return self

      addTo(parent, i) {
        return makeInstance(parent).put(this, i);
      } // Returns all child elements

      children() {
        return new List(map(this.node.children, function (node) {
          return adopt(node);
        }));
      } // Remove all elements in this container

      clear() {
        // remove children
        while (this.node.hasChildNodes()) {
          this.node.removeChild(this.node.lastChild);
        }
        return this;
      } // Clone element

      clone() {
        let deep = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
        // write dom data to the dom so the clone can pickup the data
        this.writeDataToDom(); // clone element and assign new id

        return new this.constructor(assignNewId(this.node.cloneNode(deep)));
      } // Iterates over all children and invokes a given block

      each(block, deep) {
        const children = this.children();
        let i, il;
        for (i = 0, il = children.length; i < il; i++) {
          block.apply(children[i], [i, children]);
          if (deep) {
            children[i].each(block, deep);
          }
        }
        return this;
      }
      element(nodeName, attrs) {
        return this.put(new Dom(create$6(nodeName), attrs));
      } // Get first child

      first() {
        return adopt(this.node.firstChild);
      } // Get a element at the given index

      get(i) {
        return adopt(this.node.childNodes[i]);
      }
      getEventHolder() {
        return this.node;
      }
      getEventTarget() {
        return this.node;
      } // Checks if the given element is a child

      has(element) {
        return this.index(element) >= 0;
      }
      html(htmlOrFn, outerHTML) {
        return this.xml(htmlOrFn, outerHTML, html);
      } // Get / set id

      id(id) {
        // generate new id if no id set
        if (typeof id === 'undefined' && !this.node.id) {
          this.node.id = eid(this.type);
        } // don't set directly with this.node.id to make `null` work correctly

        return this.attr('id', id);
      } // Gets index of given element

      index(element) {
        return [].slice.call(this.node.childNodes).indexOf(element.node);
      } // Get the last child

      last() {
        return adopt(this.node.lastChild);
      } // matches the element vs a css selector

      matches(selector) {
        const el = this.node;
        const matcher = el.matches || el.matchesSelector || el.msMatchesSelector || el.mozMatchesSelector || el.webkitMatchesSelector || el.oMatchesSelector || null;
        return matcher && matcher.call(el, selector);
      } // Returns the parent element instance

      parent(type) {
        let parent = this; // check for parent

        if (!parent.node.parentNode) return null; // get parent element

        parent = adopt(parent.node.parentNode);
        if (!type) return parent; // loop trough ancestors if type is given

        do {
          if (typeof type === 'string' ? parent.matches(type) : parent instanceof type) return parent;
        } while (parent = adopt(parent.node.parentNode));
        return parent;
      } // Basically does the same as `add()` but returns the added element instead

      put(element, i) {
        element = makeInstance(element);
        this.add(element, i);
        return element;
      } // Add element to given container and return container

      putIn(parent, i) {
        return makeInstance(parent).add(this, i);
      } // Remove element

      remove() {
        if (this.parent()) {
          this.parent().removeElement(this);
        }
        return this;
      } // Remove a given child

      removeElement(element) {
        this.node.removeChild(element.node);
        return this;
      } // Replace this with element

      replace(element) {
        element = makeInstance(element);
        if (this.node.parentNode) {
          this.node.parentNode.replaceChild(element.node, this.node);
        }
        return element;
      }
      round() {
        let precision = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 2;
        let map = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
        const factor = 10 ** precision;
        const attrs = this.attr(map);
        for (const i in attrs) {
          if (typeof attrs[i] === 'number') {
            attrs[i] = Math.round(attrs[i] * factor) / factor;
          }
        }
        this.attr(attrs);
        return this;
      } // Import / Export raw svg

      svg(svgOrFn, outerSVG) {
        return this.xml(svgOrFn, outerSVG, svg);
      } // Return id on string conversion

      toString() {
        return this.id();
      }
      words(text) {
        // This is faster than removing all children and adding a new one
        this.node.textContent = text;
        return this;
      }
      wrap(node) {
        const parent = this.parent();
        if (!parent) {
          return this.addTo(node);
        }
        const position = parent.index(this);
        return parent.put(node, position).put(this);
      } // write svgjs data to the dom

      writeDataToDom() {
        // dump variables recursively
        this.each(function () {
          this.writeDataToDom();
        });
        return this;
      } // Import / Export raw svg

      xml(xmlOrFn, outerXML, ns) {
        if (typeof xmlOrFn === 'boolean') {
          ns = outerXML;
          outerXML = xmlOrFn;
          xmlOrFn = null;
        } // act as getter if no svg string is given

        if (xmlOrFn == null || typeof xmlOrFn === 'function') {
          // The default for exports is, that the outerNode is included
          outerXML = outerXML == null ? true : outerXML; // write svgjs data to the dom

          this.writeDataToDom();
          let current = this; // An export modifier was passed

          if (xmlOrFn != null) {
            current = adopt(current.node.cloneNode(true)); // If the user wants outerHTML we need to process this node, too

            if (outerXML) {
              const result = xmlOrFn(current);
              current = result || current; // The user does not want this node? Well, then he gets nothing

              if (result === false) return '';
            } // Deep loop through all children and apply modifier

            current.each(function () {
              const result = xmlOrFn(this);
              const _this = result || this; // If modifier returns false, discard node

              if (result === false) {
                this.remove(); // If modifier returns new node, use it
              } else if (result && this !== _this) {
                this.replace(_this);
              }
            }, true);
          } // Return outer or inner content

          return outerXML ? current.node.outerHTML : current.node.innerHTML;
        } // Act as setter if we got a string
        // The default for import is, that the current node is not replaced

        outerXML = outerXML == null ? false : outerXML; // Create temporary holder

        const well = create$6('wrapper', ns);
        const fragment = globals.document.createDocumentFragment(); // Dump raw svg

        well.innerHTML = xmlOrFn; // Transplant nodes into the fragment

        for (let len = well.children.length; len--;) {
          fragment.appendChild(well.firstElementChild);
        }
        const parent = this.parent(); // Add the whole fragment at once

        return outerXML ? this.replace(fragment) && parent : this.add(fragment);
      }
    }
    extend(Dom, {
      attr,
      find: find$2,
      findOne
    });
    register(Dom, 'Dom');
    class Element extends Dom {
      constructor(node, attrs) {
        super(node, attrs); // initialize data object

        this.dom = {}; // create circular reference

        this.node.instance = this;
        if (node.hasAttribute('svgjs:data')) {
          // pull svgjs data from the dom (getAttributeNS doesn't work in html5)
          this.setData(JSON.parse(node.getAttribute('svgjs:data')) || {});
        }
      } // Move element by its center

      center(x, y) {
        return this.cx(x).cy(y);
      } // Move by center over x-axis

      cx(x) {
        return x == null ? this.x() + this.width() / 2 : this.x(x - this.width() / 2);
      } // Move by center over y-axis

      cy(y) {
        return y == null ? this.y() + this.height() / 2 : this.y(y - this.height() / 2);
      } // Get defs

      defs() {
        const root = this.root();
        return root && root.defs();
      } // Relative move over x and y axes

      dmove(x, y) {
        return this.dx(x).dy(y);
      } // Relative move over x axis

      dx() {
        let x = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
        return this.x(new SVGNumber(x).plus(this.x()));
      } // Relative move over y axis

      dy() {
        let y = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
        return this.y(new SVGNumber(y).plus(this.y()));
      }
      getEventHolder() {
        return this;
      } // Set height of element

      height(height) {
        return this.attr('height', height);
      } // Move element to given x and y values

      move(x, y) {
        return this.x(x).y(y);
      } // return array of all ancestors of given type up to the root svg

      parents() {
        let until = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.root();
        until = makeInstance(until);
        const parents = new List();
        let parent = this;
        while ((parent = parent.parent()) && parent.node !== globals.document && parent.nodeName !== '#document-fragment') {
          parents.push(parent);
          if (parent.node === until.node) {
            break;
          }
        }
        return parents;
      } // Get referenced element form attribute value

      reference(attr) {
        attr = this.attr(attr);
        if (!attr) return null;
        const m = (attr + '').match(reference);
        return m ? makeInstance(m[1]) : null;
      } // Get parent document

      root() {
        const p = this.parent(getClass(root));
        return p && p.root();
      } // set given data to the elements data property

      setData(o) {
        this.dom = o;
        return this;
      } // Set element size to given width and height

      size(width, height) {
        const p = proportionalSize(this, width, height);
        return this.width(new SVGNumber(p.width)).height(new SVGNumber(p.height));
      } // Set width of element

      width(width) {
        return this.attr('width', width);
      } // write svgjs data to the dom

      writeDataToDom() {
        // remove previously set data
        this.node.removeAttribute('svgjs:data');
        if (Object.keys(this.dom).length) {
          this.node.setAttribute('svgjs:data', JSON.stringify(this.dom)); // see #428
        }

        return super.writeDataToDom();
      } // Move over x-axis

      x(x) {
        return this.attr('x', x);
      } // Move over y-axis

      y(y) {
        return this.attr('y', y);
      }
    }
    extend(Element, {
      bbox,
      rbox,
      inside,
      point,
      ctm,
      screenCTM
    });
    register(Element, 'Element');
    const sugar = {
      stroke: ['color', 'width', 'opacity', 'linecap', 'linejoin', 'miterlimit', 'dasharray', 'dashoffset'],
      fill: ['color', 'opacity', 'rule'],
      prefix: function (t, a) {
        return a === 'color' ? t : t + '-' + a;
      }
    } // Add sugar for fill and stroke
    ;

    ['fill', 'stroke'].forEach(function (m) {
      const extension = {};
      let i;
      extension[m] = function (o) {
        if (typeof o === 'undefined') {
          return this.attr(m);
        }
        if (typeof o === 'string' || o instanceof Color || Color.isRgb(o) || o instanceof Element) {
          this.attr(m, o);
        } else {
          // set all attributes from sugar.fill and sugar.stroke list
          for (i = sugar[m].length - 1; i >= 0; i--) {
            if (o[sugar[m][i]] != null) {
              this.attr(sugar.prefix(m, sugar[m][i]), o[sugar[m][i]]);
            }
          }
        }
        return this;
      };
      registerMethods(['Element', 'Runner'], extension);
    });
    registerMethods(['Element', 'Runner'], {
      // Let the user set the matrix directly
      matrix: function (mat, b, c, d, e, f) {
        // Act as a getter
        if (mat == null) {
          return new Matrix(this);
        } // Act as a setter, the user can pass a matrix or a set of numbers

        return this.attr('transform', new Matrix(mat, b, c, d, e, f));
      },
      // Map rotation to transform
      rotate: function (angle, cx, cy) {
        return this.transform({
          rotate: angle,
          ox: cx,
          oy: cy
        }, true);
      },
      // Map skew to transform
      skew: function (x, y, cx, cy) {
        return arguments.length === 1 || arguments.length === 3 ? this.transform({
          skew: x,
          ox: y,
          oy: cx
        }, true) : this.transform({
          skew: [x, y],
          ox: cx,
          oy: cy
        }, true);
      },
      shear: function (lam, cx, cy) {
        return this.transform({
          shear: lam,
          ox: cx,
          oy: cy
        }, true);
      },
      // Map scale to transform
      scale: function (x, y, cx, cy) {
        return arguments.length === 1 || arguments.length === 3 ? this.transform({
          scale: x,
          ox: y,
          oy: cx
        }, true) : this.transform({
          scale: [x, y],
          ox: cx,
          oy: cy
        }, true);
      },
      // Map translate to transform
      translate: function (x, y) {
        return this.transform({
          translate: [x, y]
        }, true);
      },
      // Map relative translations to transform
      relative: function (x, y) {
        return this.transform({
          relative: [x, y]
        }, true);
      },
      // Map flip to transform
      flip: function () {
        let direction = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'both';
        let origin = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'center';
        if ('xybothtrue'.indexOf(direction) === -1) {
          origin = direction;
          direction = 'both';
        }
        return this.transform({
          flip: direction,
          origin: origin
        }, true);
      },
      // Opacity
      opacity: function (value) {
        return this.attr('opacity', value);
      }
    });
    registerMethods('radius', {
      // Add x and y radius
      radius: function (x) {
        let y = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : x;
        const type = (this._element || this).type;
        return type === 'radialGradient' ? this.attr('r', new SVGNumber(x)) : this.rx(x).ry(y);
      }
    });
    registerMethods('Path', {
      // Get path length
      length: function () {
        return this.node.getTotalLength();
      },
      // Get point at length
      pointAt: function (length) {
        return new Point(this.node.getPointAtLength(length));
      }
    });
    registerMethods(['Element', 'Runner'], {
      // Set font
      font: function (a, v) {
        if (typeof a === 'object') {
          for (v in a) this.font(v, a[v]);
          return this;
        }
        return a === 'leading' ? this.leading(v) : a === 'anchor' ? this.attr('text-anchor', v) : a === 'size' || a === 'family' || a === 'weight' || a === 'stretch' || a === 'variant' || a === 'style' ? this.attr('font-' + a, v) : this.attr(a, v);
      }
    }); // Add events to elements

    const methods = ['click', 'dblclick', 'mousedown', 'mouseup', 'mouseover', 'mouseout', 'mousemove', 'mouseenter', 'mouseleave', 'touchstart', 'touchmove', 'touchleave', 'touchend', 'touchcancel'].reduce(function (last, event) {
      // add event to Element
      const fn = function (f) {
        if (f === null) {
          this.off(event);
        } else {
          this.on(event, f);
        }
        return this;
      };
      last[event] = fn;
      return last;
    }, {});
    registerMethods('Element', methods);
    function untransform() {
      return this.attr('transform', null);
    } // merge the whole transformation chain into one matrix and returns it

    function matrixify() {
      const matrix = (this.attr('transform') || '').
      // split transformations
      split(transforms).slice(0, -1).map(function (str) {
        // generate key => value pairs
        const kv = str.trim().split('(');
        return [kv[0], kv[1].split(delimiter).map(function (str) {
          return parseFloat(str);
        })];
      }).reverse() // merge every transformation into one matrix
      .reduce(function (matrix, transform) {
        if (transform[0] === 'matrix') {
          return matrix.lmultiply(Matrix.fromArray(transform[1]));
        }
        return matrix[transform[0]].apply(matrix, transform[1]);
      }, new Matrix());
      return matrix;
    } // add an element to another parent without changing the visual representation on the screen

    function toParent(parent, i) {
      if (this === parent) return this;
      const ctm = this.screenCTM();
      const pCtm = parent.screenCTM().inverse();
      this.addTo(parent, i).untransform().transform(pCtm.multiply(ctm));
      return this;
    } // same as above with parent equals root-svg

    function toRoot(i) {
      return this.toParent(this.root(), i);
    } // Add transformations

    function transform(o, relative) {
      // Act as a getter if no object was passed
      if (o == null || typeof o === 'string') {
        const decomposed = new Matrix(this).decompose();
        return o == null ? decomposed : decomposed[o];
      }
      if (!Matrix.isMatrixLike(o)) {
        // Set the origin according to the defined transform
        o = {
          ...o,
          origin: getOrigin(o, this)
        };
      } // The user can pass a boolean, an Element or an Matrix or nothing

      const cleanRelative = relative === true ? this : relative || false;
      const result = new Matrix(cleanRelative).transform(o);
      return this.attr('transform', result);
    }
    registerMethods('Element', {
      untransform,
      matrixify,
      toParent,
      toRoot,
      transform
    });
    class Container extends Element {
      flatten() {
        this.each(function () {
          if (this instanceof Container) {
            return this.flatten().ungroup();
          }
        });
        return this;
      }
      ungroup() {
        let parent = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.parent();
        let index = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : parent.index(this);
        // when parent != this, we want append all elements to the end
        index = index === -1 ? parent.children().length : index;
        this.each(function (i, children) {
          // reverse each
          return children[children.length - i - 1].toParent(parent, index);
        });
        return this.remove();
      }
    }
    register(Container, 'Container');
    class Defs extends Container {
      constructor(node) {
        let attrs = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : node;
        super(nodeOrNew('defs', node), attrs);
      }
      flatten() {
        return this;
      }
      ungroup() {
        return this;
      }
    }
    register(Defs, 'Defs');
    class Shape extends Element {}
    register(Shape, 'Shape');
    function rx(rx) {
      return this.attr('rx', rx);
    } // Radius y value

    function ry(ry) {
      return this.attr('ry', ry);
    } // Move over x-axis

    function x$3(x) {
      return x == null ? this.cx() - this.rx() : this.cx(x + this.rx());
    } // Move over y-axis

    function y$3(y) {
      return y == null ? this.cy() - this.ry() : this.cy(y + this.ry());
    } // Move by center over x-axis

    function cx$1(x) {
      return this.attr('cx', x);
    } // Move by center over y-axis

    function cy$1(y) {
      return this.attr('cy', y);
    } // Set width of element

    function width$2(width) {
      return width == null ? this.rx() * 2 : this.rx(new SVGNumber(width).divide(2));
    } // Set height of element

    function height$2(height) {
      return height == null ? this.ry() * 2 : this.ry(new SVGNumber(height).divide(2));
    }
    var circled = {
      __proto__: null,
      rx: rx,
      ry: ry,
      x: x$3,
      y: y$3,
      cx: cx$1,
      cy: cy$1,
      width: width$2,
      height: height$2
    };
    class Ellipse extends Shape {
      constructor(node) {
        let attrs = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : node;
        super(nodeOrNew('ellipse', node), attrs);
      }
      size(width, height) {
        const p = proportionalSize(this, width, height);
        return this.rx(new SVGNumber(p.width).divide(2)).ry(new SVGNumber(p.height).divide(2));
      }
    }
    extend(Ellipse, circled);
    registerMethods('Container', {
      // Create an ellipse
      ellipse: wrapWithAttrCheck(function () {
        let width = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
        let height = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : width;
        return this.put(new Ellipse()).size(width, height).move(0, 0);
      })
    });
    register(Ellipse, 'Ellipse');
    class Fragment extends Dom {
      constructor() {
        let node = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : globals.document.createDocumentFragment();
        super(node);
      } // Import / Export raw xml

      xml(xmlOrFn, outerXML, ns) {
        if (typeof xmlOrFn === 'boolean') {
          ns = outerXML;
          outerXML = xmlOrFn;
          xmlOrFn = null;
        } // because this is a fragment we have to put all elements into a wrapper first
        // before we can get the innerXML from it

        if (xmlOrFn == null || typeof xmlOrFn === 'function') {
          const wrapper = new Dom(create$6('wrapper', ns));
          wrapper.add(this.node.cloneNode(true));
          return wrapper.xml(false, ns);
        } // Act as setter if we got a string

        return super.xml(xmlOrFn, false, ns);
      }
    }
    register(Fragment, 'Fragment');
    function from(x, y) {
      return (this._element || this).type === 'radialGradient' ? this.attr({
        fx: new SVGNumber(x),
        fy: new SVGNumber(y)
      }) : this.attr({
        x1: new SVGNumber(x),
        y1: new SVGNumber(y)
      });
    }
    function to(x, y) {
      return (this._element || this).type === 'radialGradient' ? this.attr({
        cx: new SVGNumber(x),
        cy: new SVGNumber(y)
      }) : this.attr({
        x2: new SVGNumber(x),
        y2: new SVGNumber(y)
      });
    }
    var gradiented = {
      __proto__: null,
      from: from,
      to: to
    };
    class Gradient extends Container {
      constructor(type, attrs) {
        super(nodeOrNew(type + 'Gradient', typeof type === 'string' ? null : type), attrs);
      } // custom attr to handle transform

      attr(a, b, c) {
        if (a === 'transform') a = 'gradientTransform';
        return super.attr(a, b, c);
      }
      bbox() {
        return new Box();
      }
      targets() {
        return baseFind('svg [fill*="' + this.id() + '"]');
      } // Alias string conversion to fill

      toString() {
        return this.url();
      } // Update gradient

      update(block) {
        // remove all stops
        this.clear(); // invoke passed block

        if (typeof block === 'function') {
          block.call(this, this);
        }
        return this;
      } // Return the fill id

      url() {
        return 'url("#' + this.id() + '")';
      }
    }
    extend(Gradient, gradiented);
    registerMethods({
      Container: {
        // Create gradient element in defs
        gradient() {
          return this.defs().gradient(...arguments);
        }
      },
      // define gradient
      Defs: {
        gradient: wrapWithAttrCheck(function (type, block) {
          return this.put(new Gradient(type)).update(block);
        })
      }
    });
    register(Gradient, 'Gradient');
    class Pattern extends Container {
      // Initialize node
      constructor(node) {
        let attrs = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : node;
        super(nodeOrNew('pattern', node), attrs);
      } // custom attr to handle transform

      attr(a, b, c) {
        if (a === 'transform') a = 'patternTransform';
        return super.attr(a, b, c);
      }
      bbox() {
        return new Box();
      }
      targets() {
        return baseFind('svg [fill*="' + this.id() + '"]');
      } // Alias string conversion to fill

      toString() {
        return this.url();
      } // Update pattern by rebuilding

      update(block) {
        // remove content
        this.clear(); // invoke passed block

        if (typeof block === 'function') {
          block.call(this, this);
        }
        return this;
      } // Return the fill id

      url() {
        return 'url("#' + this.id() + '")';
      }
    }
    registerMethods({
      Container: {
        // Create pattern element in defs
        pattern() {
          return this.defs().pattern(...arguments);
        }
      },
      Defs: {
        pattern: wrapWithAttrCheck(function (width, height, block) {
          return this.put(new Pattern()).update(block).attr({
            x: 0,
            y: 0,
            width: width,
            height: height,
            patternUnits: 'userSpaceOnUse'
          });
        })
      }
    });
    register(Pattern, 'Pattern');
    class Image extends Shape {
      constructor(node) {
        let attrs = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : node;
        super(nodeOrNew('image', node), attrs);
      } // (re)load image

      load(url, callback) {
        if (!url) return this;
        const img = new globals.window.Image();
        on(img, 'load', function (e) {
          const p = this.parent(Pattern); // ensure image size

          if (this.width() === 0 && this.height() === 0) {
            this.size(img.width, img.height);
          }
          if (p instanceof Pattern) {
            // ensure pattern size if not set
            if (p.width() === 0 && p.height() === 0) {
              p.size(this.width(), this.height());
            }
          }
          if (typeof callback === 'function') {
            callback.call(this, e);
          }
        }, this);
        on(img, 'load error', function () {
          // dont forget to unbind memory leaking events
          off(img);
        });
        return this.attr('href', img.src = url, xlink);
      }
    }
    registerAttrHook(function (attr, val, _this) {
      // convert image fill and stroke to patterns
      if (attr === 'fill' || attr === 'stroke') {
        if (isImage.test(val)) {
          val = _this.root().defs().image(val);
        }
      }
      if (val instanceof Image) {
        val = _this.root().defs().pattern(0, 0, pattern => {
          pattern.add(val);
        });
      }
      return val;
    });
    registerMethods({
      Container: {
        // create image element, load image and set its size
        image: wrapWithAttrCheck(function (source, callback) {
          return this.put(new Image()).size(0, 0).load(source, callback);
        })
      }
    });
    register(Image, 'Image');
    class PointArray extends SVGArray {
      // Get bounding box of points
      bbox() {
        let maxX = -Infinity;
        let maxY = -Infinity;
        let minX = Infinity;
        let minY = Infinity;
        this.forEach(function (el) {
          maxX = Math.max(el[0], maxX);
          maxY = Math.max(el[1], maxY);
          minX = Math.min(el[0], minX);
          minY = Math.min(el[1], minY);
        });
        return new Box(minX, minY, maxX - minX, maxY - minY);
      } // Move point string

      move(x, y) {
        const box = this.bbox(); // get relative offset

        x -= box.x;
        y -= box.y; // move every point

        if (!isNaN(x) && !isNaN(y)) {
          for (let i = this.length - 1; i >= 0; i--) {
            this[i] = [this[i][0] + x, this[i][1] + y];
          }
        }
        return this;
      } // Parse point string and flat array

      parse() {
        let array = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [0, 0];
        const points = []; // if it is an array, we flatten it and therefore clone it to 1 depths

        if (array instanceof Array) {
          array = Array.prototype.concat.apply([], array);
        } else {
          // Else, it is considered as a string
          // parse points
          array = array.trim().split(delimiter).map(parseFloat);
        } // validate points - https://svgwg.org/svg2-draft/shapes.html#DataTypePoints
        // Odd number of coordinates is an error. In such cases, drop the last odd coordinate.

        if (array.length % 2 !== 0) array.pop(); // wrap points in two-tuples

        for (let i = 0, len = array.length; i < len; i = i + 2) {
          points.push([array[i], array[i + 1]]);
        }
        return points;
      } // Resize poly string

      size(width, height) {
        let i;
        const box = this.bbox(); // recalculate position of all points according to new size

        for (i = this.length - 1; i >= 0; i--) {
          if (box.width) this[i][0] = (this[i][0] - box.x) * width / box.width + box.x;
          if (box.height) this[i][1] = (this[i][1] - box.y) * height / box.height + box.y;
        }
        return this;
      } // Convert array to line object

      toLine() {
        return {
          x1: this[0][0],
          y1: this[0][1],
          x2: this[1][0],
          y2: this[1][1]
        };
      } // Convert array to string

      toString() {
        const array = []; // convert to a poly point string

        for (let i = 0, il = this.length; i < il; i++) {
          array.push(this[i].join(','));
        }
        return array.join(' ');
      }
      transform(m) {
        return this.clone().transformO(m);
      } // transform points with matrix (similar to Point.transform)

      transformO(m) {
        if (!Matrix.isMatrixLike(m)) {
          m = new Matrix(m);
        }
        for (let i = this.length; i--;) {
          // Perform the matrix multiplication
          const [x, y] = this[i];
          this[i][0] = m.a * x + m.c * y + m.e;
          this[i][1] = m.b * x + m.d * y + m.f;
        }
        return this;
      }
    }
    const MorphArray = PointArray; // Move by left top corner over x-axis

    function x$2(x) {
      return x == null ? this.bbox().x : this.move(x, this.bbox().y);
    } // Move by left top corner over y-axis

    function y$2(y) {
      return y == null ? this.bbox().y : this.move(this.bbox().x, y);
    } // Set width of element

    function width$1(width) {
      const b = this.bbox();
      return width == null ? b.width : this.size(width, b.height);
    } // Set height of element

    function height$1(height) {
      const b = this.bbox();
      return height == null ? b.height : this.size(b.width, height);
    }
    var pointed = {
      __proto__: null,
      MorphArray: MorphArray,
      x: x$2,
      y: y$2,
      width: width$1,
      height: height$1
    };
    class Line extends Shape {
      // Initialize node
      constructor(node) {
        let attrs = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : node;
        super(nodeOrNew('line', node), attrs);
      } // Get array

      array() {
        return new PointArray([[this.attr('x1'), this.attr('y1')], [this.attr('x2'), this.attr('y2')]]);
      } // Move by left top corner

      move(x, y) {
        return this.attr(this.array().move(x, y).toLine());
      } // Overwrite native plot() method

      plot(x1, y1, x2, y2) {
        if (x1 == null) {
          return this.array();
        } else if (typeof y1 !== 'undefined') {
          x1 = {
            x1,
            y1,
            x2,
            y2
          };
        } else {
          x1 = new PointArray(x1).toLine();
        }
        return this.attr(x1);
      } // Set element size to given width and height

      size(width, height) {
        const p = proportionalSize(this, width, height);
        return this.attr(this.array().size(p.width, p.height).toLine());
      }
    }
    extend(Line, pointed);
    registerMethods({
      Container: {
        // Create a line element
        line: wrapWithAttrCheck(function () {
          for (var _len5 = arguments.length, args = new Array(_len5), _key5 = 0; _key5 < _len5; _key5++) {
            args[_key5] = arguments[_key5];
          }
          // make sure plot is called as a setter
          // x1 is not necessarily a number, it can also be an array, a string and a PointArray
          return Line.prototype.plot.apply(this.put(new Line()), args[0] != null ? args : [0, 0, 0, 0]);
        })
      }
    });
    register(Line, 'Line');
    class Marker extends Container {
      // Initialize node
      constructor(node) {
        let attrs = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : node;
        super(nodeOrNew('marker', node), attrs);
      } // Set height of element

      height(height) {
        return this.attr('markerHeight', height);
      }
      orient(orient) {
        return this.attr('orient', orient);
      } // Set marker refX and refY

      ref(x, y) {
        return this.attr('refX', x).attr('refY', y);
      } // Return the fill id

      toString() {
        return 'url(#' + this.id() + ')';
      } // Update marker

      update(block) {
        // remove all content
        this.clear(); // invoke passed block

        if (typeof block === 'function') {
          block.call(this, this);
        }
        return this;
      } // Set width of element

      width(width) {
        return this.attr('markerWidth', width);
      }
    }
    registerMethods({
      Container: {
        marker() {
          // Create marker element in defs
          return this.defs().marker(...arguments);
        }
      },
      Defs: {
        // Create marker
        marker: wrapWithAttrCheck(function (width, height, block) {
          // Set default viewbox to match the width and height, set ref to cx and cy and set orient to auto
          return this.put(new Marker()).size(width, height).ref(width / 2, height / 2).viewbox(0, 0, width, height).attr('orient', 'auto').update(block);
        })
      },
      marker: {
        // Create and attach markers
        marker(marker, width, height, block) {
          let attr = ['marker']; // Build attribute name

          if (marker !== 'all') attr.push(marker);
          attr = attr.join('-'); // Set marker attribute

          marker = arguments[1] instanceof Marker ? arguments[1] : this.defs().marker(width, height, block);
          return this.attr(attr, marker);
        }
      }
    });
    register(Marker, 'Marker');

    /***
    Base Class
    ==========
    The base stepper class that will be
    ***/

    function makeSetterGetter(k, f) {
      return function (v) {
        if (v == null) return this[k];
        this[k] = v;
        if (f) f.call(this);
        return this;
      };
    }
    const easing = {
      '-': function (pos) {
        return pos;
      },
      '<>': function (pos) {
        return -Math.cos(pos * Math.PI) / 2 + 0.5;
      },
      '>': function (pos) {
        return Math.sin(pos * Math.PI / 2);
      },
      '<': function (pos) {
        return -Math.cos(pos * Math.PI / 2) + 1;
      },
      bezier: function (x1, y1, x2, y2) {
        // see https://www.w3.org/TR/css-easing-1/#cubic-bezier-algo
        return function (t) {
          if (t < 0) {
            if (x1 > 0) {
              return y1 / x1 * t;
            } else if (x2 > 0) {
              return y2 / x2 * t;
            } else {
              return 0;
            }
          } else if (t > 1) {
            if (x2 < 1) {
              return (1 - y2) / (1 - x2) * t + (y2 - x2) / (1 - x2);
            } else if (x1 < 1) {
              return (1 - y1) / (1 - x1) * t + (y1 - x1) / (1 - x1);
            } else {
              return 1;
            }
          } else {
            return 3 * t * (1 - t) ** 2 * y1 + 3 * t ** 2 * (1 - t) * y2 + t ** 3;
          }
        };
      },
      // see https://www.w3.org/TR/css-easing-1/#step-timing-function-algo
      steps: function (steps) {
        let stepPosition = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'end';
        // deal with "jump-" prefix
        stepPosition = stepPosition.split('-').reverse()[0];
        let jumps = steps;
        if (stepPosition === 'none') {
          --jumps;
        } else if (stepPosition === 'both') {
          ++jumps;
        } // The beforeFlag is essentially useless

        return function (t) {
          let beforeFlag = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
          // Step is called currentStep in referenced url
          let step = Math.floor(t * steps);
          const jumping = t * step % 1 === 0;
          if (stepPosition === 'start' || stepPosition === 'both') {
            ++step;
          }
          if (beforeFlag && jumping) {
            --step;
          }
          if (t >= 0 && step < 0) {
            step = 0;
          }
          if (t <= 1 && step > jumps) {
            step = jumps;
          }
          return step / jumps;
        };
      }
    };
    class Stepper {
      done() {
        return false;
      }
    }
    /***
    Easing Functions
    ================
    ***/

    class Ease extends Stepper {
      constructor() {
        let fn = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : timeline.ease;
        super();
        this.ease = easing[fn] || fn;
      }
      step(from, to, pos) {
        if (typeof from !== 'number') {
          return pos < 1 ? from : to;
        }
        return from + (to - from) * this.ease(pos);
      }
    }
    /***
    Controller Types
    ================
    ***/

    class Controller extends Stepper {
      constructor(fn) {
        super();
        this.stepper = fn;
      }
      done(c) {
        return c.done;
      }
      step(current, target, dt, c) {
        return this.stepper(current, target, dt, c);
      }
    }
    function recalculate() {
      // Apply the default parameters
      const duration = (this._duration || 500) / 1000;
      const overshoot = this._overshoot || 0; // Calculate the PID natural response

      const eps = 1e-10;
      const pi = Math.PI;
      const os = Math.log(overshoot / 100 + eps);
      const zeta = -os / Math.sqrt(pi * pi + os * os);
      const wn = 3.9 / (zeta * duration); // Calculate the Spring values

      this.d = 2 * zeta * wn;
      this.k = wn * wn;
    }
    class Spring extends Controller {
      constructor() {
        let duration = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 500;
        let overshoot = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
        super();
        this.duration(duration).overshoot(overshoot);
      }
      step(current, target, dt, c) {
        if (typeof current === 'string') return current;
        c.done = dt === Infinity;
        if (dt === Infinity) return target;
        if (dt === 0) return current;
        if (dt > 100) dt = 16;
        dt /= 1000; // Get the previous velocity

        const velocity = c.velocity || 0; // Apply the control to get the new position and store it

        const acceleration = -this.d * velocity - this.k * (current - target);
        const newPosition = current + velocity * dt + acceleration * dt * dt / 2; // Store the velocity

        c.velocity = velocity + acceleration * dt; // Figure out if we have converged, and if so, pass the value

        c.done = Math.abs(target - newPosition) + Math.abs(velocity) < 0.002;
        return c.done ? target : newPosition;
      }
    }
    extend(Spring, {
      duration: makeSetterGetter('_duration', recalculate),
      overshoot: makeSetterGetter('_overshoot', recalculate)
    });
    class PID extends Controller {
      constructor() {
        let p = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0.1;
        let i = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0.01;
        let d = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
        let windup = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 1000;
        super();
        this.p(p).i(i).d(d).windup(windup);
      }
      step(current, target, dt, c) {
        if (typeof current === 'string') return current;
        c.done = dt === Infinity;
        if (dt === Infinity) return target;
        if (dt === 0) return current;
        const p = target - current;
        let i = (c.integral || 0) + p * dt;
        const d = (p - (c.error || 0)) / dt;
        const windup = this._windup; // antiwindup

        if (windup !== false) {
          i = Math.max(-windup, Math.min(i, windup));
        }
        c.error = p;
        c.integral = i;
        c.done = Math.abs(p) < 0.001;
        return c.done ? target : current + (this.P * p + this.I * i + this.D * d);
      }
    }
    extend(PID, {
      windup: makeSetterGetter('_windup'),
      p: makeSetterGetter('P'),
      i: makeSetterGetter('I'),
      d: makeSetterGetter('D')
    });
    const segmentParameters = {
      M: 2,
      L: 2,
      H: 1,
      V: 1,
      C: 6,
      S: 4,
      Q: 4,
      T: 2,
      A: 7,
      Z: 0
    };
    const pathHandlers = {
      M: function (c, p, p0) {
        p.x = p0.x = c[0];
        p.y = p0.y = c[1];
        return ['M', p.x, p.y];
      },
      L: function (c, p) {
        p.x = c[0];
        p.y = c[1];
        return ['L', c[0], c[1]];
      },
      H: function (c, p) {
        p.x = c[0];
        return ['H', c[0]];
      },
      V: function (c, p) {
        p.y = c[0];
        return ['V', c[0]];
      },
      C: function (c, p) {
        p.x = c[4];
        p.y = c[5];
        return ['C', c[0], c[1], c[2], c[3], c[4], c[5]];
      },
      S: function (c, p) {
        p.x = c[2];
        p.y = c[3];
        return ['S', c[0], c[1], c[2], c[3]];
      },
      Q: function (c, p) {
        p.x = c[2];
        p.y = c[3];
        return ['Q', c[0], c[1], c[2], c[3]];
      },
      T: function (c, p) {
        p.x = c[0];
        p.y = c[1];
        return ['T', c[0], c[1]];
      },
      Z: function (c, p, p0) {
        p.x = p0.x;
        p.y = p0.y;
        return ['Z'];
      },
      A: function (c, p) {
        p.x = c[5];
        p.y = c[6];
        return ['A', c[0], c[1], c[2], c[3], c[4], c[5], c[6]];
      }
    };
    const mlhvqtcsaz = 'mlhvqtcsaz'.split('');
    for (let i = 0, il = mlhvqtcsaz.length; i < il; ++i) {
      pathHandlers[mlhvqtcsaz[i]] = function (i) {
        return function (c, p, p0) {
          if (i === 'H') c[0] = c[0] + p.x;else if (i === 'V') c[0] = c[0] + p.y;else if (i === 'A') {
            c[5] = c[5] + p.x;
            c[6] = c[6] + p.y;
          } else {
            for (let j = 0, jl = c.length; j < jl; ++j) {
              c[j] = c[j] + (j % 2 ? p.y : p.x);
            }
          }
          return pathHandlers[i](c, p, p0);
        };
      }(mlhvqtcsaz[i].toUpperCase());
    }
    function makeAbsolut(parser) {
      const command = parser.segment[0];
      return pathHandlers[command](parser.segment.slice(1), parser.p, parser.p0);
    }
    function segmentComplete(parser) {
      return parser.segment.length && parser.segment.length - 1 === segmentParameters[parser.segment[0].toUpperCase()];
    }
    function startNewSegment(parser, token) {
      parser.inNumber && finalizeNumber(parser, false);
      const pathLetter = isPathLetter.test(token);
      if (pathLetter) {
        parser.segment = [token];
      } else {
        const lastCommand = parser.lastCommand;
        const small = lastCommand.toLowerCase();
        const isSmall = lastCommand === small;
        parser.segment = [small === 'm' ? isSmall ? 'l' : 'L' : lastCommand];
      }
      parser.inSegment = true;
      parser.lastCommand = parser.segment[0];
      return pathLetter;
    }
    function finalizeNumber(parser, inNumber) {
      if (!parser.inNumber) throw new Error('Parser Error');
      parser.number && parser.segment.push(parseFloat(parser.number));
      parser.inNumber = inNumber;
      parser.number = '';
      parser.pointSeen = false;
      parser.hasExponent = false;
      if (segmentComplete(parser)) {
        finalizeSegment(parser);
      }
    }
    function finalizeSegment(parser) {
      parser.inSegment = false;
      if (parser.absolute) {
        parser.segment = makeAbsolut(parser);
      }
      parser.segments.push(parser.segment);
    }
    function isArcFlag(parser) {
      if (!parser.segment.length) return false;
      const isArc = parser.segment[0].toUpperCase() === 'A';
      const length = parser.segment.length;
      return isArc && (length === 4 || length === 5);
    }
    function isExponential(parser) {
      return parser.lastToken.toUpperCase() === 'E';
    }
    function pathParser(d) {
      let toAbsolute = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
      let index = 0;
      let token = '';
      const parser = {
        segment: [],
        inNumber: false,
        number: '',
        lastToken: '',
        inSegment: false,
        segments: [],
        pointSeen: false,
        hasExponent: false,
        absolute: toAbsolute,
        p0: new Point(),
        p: new Point()
      };
      while (parser.lastToken = token, token = d.charAt(index++)) {
        if (!parser.inSegment) {
          if (startNewSegment(parser, token)) {
            continue;
          }
        }
        if (token === '.') {
          if (parser.pointSeen || parser.hasExponent) {
            finalizeNumber(parser, false);
            --index;
            continue;
          }
          parser.inNumber = true;
          parser.pointSeen = true;
          parser.number += token;
          continue;
        }
        if (!isNaN(parseInt(token))) {
          if (parser.number === '0' || isArcFlag(parser)) {
            parser.inNumber = true;
            parser.number = token;
            finalizeNumber(parser, true);
            continue;
          }
          parser.inNumber = true;
          parser.number += token;
          continue;
        }
        if (token === ' ' || token === ',') {
          if (parser.inNumber) {
            finalizeNumber(parser, false);
          }
          continue;
        }
        if (token === '-') {
          if (parser.inNumber && !isExponential(parser)) {
            finalizeNumber(parser, false);
            --index;
            continue;
          }
          parser.number += token;
          parser.inNumber = true;
          continue;
        }
        if (token.toUpperCase() === 'E') {
          parser.number += token;
          parser.hasExponent = true;
          continue;
        }
        if (isPathLetter.test(token)) {
          if (parser.inNumber) {
            finalizeNumber(parser, false);
          } else if (!segmentComplete(parser)) {
            throw new Error('parser Error');
          } else {
            finalizeSegment(parser);
          }
          --index;
        }
      }
      if (parser.inNumber) {
        finalizeNumber(parser, false);
      }
      if (parser.inSegment && segmentComplete(parser)) {
        finalizeSegment(parser);
      }
      return parser.segments;
    }
    function arrayToString(a) {
      let s = '';
      for (let i = 0, il = a.length; i < il; i++) {
        s += a[i][0];
        if (a[i][1] != null) {
          s += a[i][1];
          if (a[i][2] != null) {
            s += ' ';
            s += a[i][2];
            if (a[i][3] != null) {
              s += ' ';
              s += a[i][3];
              s += ' ';
              s += a[i][4];
              if (a[i][5] != null) {
                s += ' ';
                s += a[i][5];
                s += ' ';
                s += a[i][6];
                if (a[i][7] != null) {
                  s += ' ';
                  s += a[i][7];
                }
              }
            }
          }
        }
      }
      return s + ' ';
    }
    class PathArray extends SVGArray {
      // Get bounding box of path
      bbox() {
        parser().path.setAttribute('d', this.toString());
        return new Box(parser.nodes.path.getBBox());
      } // Move path string

      move(x, y) {
        // get bounding box of current situation
        const box = this.bbox(); // get relative offset

        x -= box.x;
        y -= box.y;
        if (!isNaN(x) && !isNaN(y)) {
          // move every point
          for (let l, i = this.length - 1; i >= 0; i--) {
            l = this[i][0];
            if (l === 'M' || l === 'L' || l === 'T') {
              this[i][1] += x;
              this[i][2] += y;
            } else if (l === 'H') {
              this[i][1] += x;
            } else if (l === 'V') {
              this[i][1] += y;
            } else if (l === 'C' || l === 'S' || l === 'Q') {
              this[i][1] += x;
              this[i][2] += y;
              this[i][3] += x;
              this[i][4] += y;
              if (l === 'C') {
                this[i][5] += x;
                this[i][6] += y;
              }
            } else if (l === 'A') {
              this[i][6] += x;
              this[i][7] += y;
            }
          }
        }
        return this;
      } // Absolutize and parse path to array

      parse() {
        let d = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'M0 0';
        if (Array.isArray(d)) {
          d = Array.prototype.concat.apply([], d).toString();
        }
        return pathParser(d);
      } // Resize path string

      size(width, height) {
        // get bounding box of current situation
        const box = this.bbox();
        let i, l; // If the box width or height is 0 then we ignore
        // transformations on the respective axis

        box.width = box.width === 0 ? 1 : box.width;
        box.height = box.height === 0 ? 1 : box.height; // recalculate position of all points according to new size

        for (i = this.length - 1; i >= 0; i--) {
          l = this[i][0];
          if (l === 'M' || l === 'L' || l === 'T') {
            this[i][1] = (this[i][1] - box.x) * width / box.width + box.x;
            this[i][2] = (this[i][2] - box.y) * height / box.height + box.y;
          } else if (l === 'H') {
            this[i][1] = (this[i][1] - box.x) * width / box.width + box.x;
          } else if (l === 'V') {
            this[i][1] = (this[i][1] - box.y) * height / box.height + box.y;
          } else if (l === 'C' || l === 'S' || l === 'Q') {
            this[i][1] = (this[i][1] - box.x) * width / box.width + box.x;
            this[i][2] = (this[i][2] - box.y) * height / box.height + box.y;
            this[i][3] = (this[i][3] - box.x) * width / box.width + box.x;
            this[i][4] = (this[i][4] - box.y) * height / box.height + box.y;
            if (l === 'C') {
              this[i][5] = (this[i][5] - box.x) * width / box.width + box.x;
              this[i][6] = (this[i][6] - box.y) * height / box.height + box.y;
            }
          } else if (l === 'A') {
            // resize radii
            this[i][1] = this[i][1] * width / box.width;
            this[i][2] = this[i][2] * height / box.height; // move position values

            this[i][6] = (this[i][6] - box.x) * width / box.width + box.x;
            this[i][7] = (this[i][7] - box.y) * height / box.height + box.y;
          }
        }
        return this;
      } // Convert array to string

      toString() {
        return arrayToString(this);
      }
    }
    const getClassForType = value => {
      const type = typeof value;
      if (type === 'number') {
        return SVGNumber;
      } else if (type === 'string') {
        if (Color.isColor(value)) {
          return Color;
        } else if (delimiter.test(value)) {
          return isPathLetter.test(value) ? PathArray : SVGArray;
        } else if (numberAndUnit.test(value)) {
          return SVGNumber;
        } else {
          return NonMorphable;
        }
      } else if (morphableTypes.indexOf(value.constructor) > -1) {
        return value.constructor;
      } else if (Array.isArray(value)) {
        return SVGArray;
      } else if (type === 'object') {
        return ObjectBag;
      } else {
        return NonMorphable;
      }
    };
    class Morphable {
      constructor(stepper) {
        this._stepper = stepper || new Ease('-');
        this._from = null;
        this._to = null;
        this._type = null;
        this._context = null;
        this._morphObj = null;
      }
      at(pos) {
        const _this = this;
        return this._morphObj.fromArray(this._from.map(function (i, index) {
          return _this._stepper.step(i, _this._to[index], pos, _this._context[index], _this._context);
        }));
      }
      done() {
        const complete = this._context.map(this._stepper.done).reduce(function (last, curr) {
          return last && curr;
        }, true);
        return complete;
      }
      from(val) {
        if (val == null) {
          return this._from;
        }
        this._from = this._set(val);
        return this;
      }
      stepper(stepper) {
        if (stepper == null) return this._stepper;
        this._stepper = stepper;
        return this;
      }
      to(val) {
        if (val == null) {
          return this._to;
        }
        this._to = this._set(val);
        return this;
      }
      type(type) {
        // getter
        if (type == null) {
          return this._type;
        } // setter

        this._type = type;
        return this;
      }
      _set(value) {
        if (!this._type) {
          this.type(getClassForType(value));
        }
        let result = new this._type(value);
        if (this._type === Color) {
          result = this._to ? result[this._to[4]]() : this._from ? result[this._from[4]]() : result;
        }
        if (this._type === ObjectBag) {
          result = this._to ? result.align(this._to) : this._from ? result.align(this._from) : result;
        }
        result = result.toArray();
        this._morphObj = this._morphObj || new this._type();
        this._context = this._context || Array.apply(null, Array(result.length)).map(Object).map(function (o) {
          o.done = true;
          return o;
        });
        return result;
      }
    }
    class NonMorphable {
      constructor() {
        this.init(...arguments);
      }
      init(val) {
        val = Array.isArray(val) ? val[0] : val;
        this.value = val;
        return this;
      }
      toArray() {
        return [this.value];
      }
      valueOf() {
        return this.value;
      }
    }
    class TransformBag {
      constructor() {
        this.init(...arguments);
      }
      init(obj) {
        if (Array.isArray(obj)) {
          obj = {
            scaleX: obj[0],
            scaleY: obj[1],
            shear: obj[2],
            rotate: obj[3],
            translateX: obj[4],
            translateY: obj[5],
            originX: obj[6],
            originY: obj[7]
          };
        }
        Object.assign(this, TransformBag.defaults, obj);
        return this;
      }
      toArray() {
        const v = this;
        return [v.scaleX, v.scaleY, v.shear, v.rotate, v.translateX, v.translateY, v.originX, v.originY];
      }
    }
    TransformBag.defaults = {
      scaleX: 1,
      scaleY: 1,
      shear: 0,
      rotate: 0,
      translateX: 0,
      translateY: 0,
      originX: 0,
      originY: 0
    };
    const sortByKey = (a, b) => {
      return a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0;
    };
    class ObjectBag {
      constructor() {
        this.init(...arguments);
      }
      align(other) {
        for (let i = 0, il = this.values.length; i < il; ++i) {
          if (this.values[i] === Color) {
            const space = other[i + 6];
            const color = new Color(this.values.splice(i + 2, 5))[space]().toArray();
            this.values.splice(i + 2, 0, ...color);
          }
        }
        return this;
      }
      init(objOrArr) {
        this.values = [];
        if (Array.isArray(objOrArr)) {
          this.values = objOrArr.slice();
          return;
        }
        objOrArr = objOrArr || {};
        const entries = [];
        for (const i in objOrArr) {
          const Type = getClassForType(objOrArr[i]);
          const val = new Type(objOrArr[i]).toArray();
          entries.push([i, Type, val.length, ...val]);
        }
        entries.sort(sortByKey);
        this.values = entries.reduce((last, curr) => last.concat(curr), []);
        return this;
      }
      toArray() {
        return this.values;
      }
      valueOf() {
        const obj = {};
        const arr = this.values; // for (var i = 0, len = arr.length; i < len; i += 2) {

        while (arr.length) {
          const key = arr.shift();
          const Type = arr.shift();
          const num = arr.shift();
          const values = arr.splice(0, num);
          obj[key] = new Type(values).valueOf();
        }
        return obj;
      }
    }
    const morphableTypes = [NonMorphable, TransformBag, ObjectBag];
    function registerMorphableType() {
      let type = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
      morphableTypes.push(...[].concat(type));
    }
    function makeMorphable() {
      extend(morphableTypes, {
        to(val) {
          return new Morphable().type(this.constructor).from(this.valueOf()).to(val);
        },
        fromArray(arr) {
          this.init(arr);
          return this;
        }
      });
    }
    class Path extends Shape {
      // Initialize node
      constructor(node) {
        let attrs = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : node;
        super(nodeOrNew('path', node), attrs);
      } // Get array

      array() {
        return this._array || (this._array = new PathArray(this.attr('d')));
      } // Clear array cache

      clear() {
        delete this._array;
        return this;
      } // Set height of element

      height(height) {
        return height == null ? this.bbox().height : this.size(this.bbox().width, height);
      } // Move by left top corner

      move(x, y) {
        return this.attr('d', this.array().move(x, y));
      } // Plot new path

      plot(d) {
        return d == null ? this.array() : this.clear().attr('d', typeof d === 'string' ? d : this._array = new PathArray(d));
      } // Set element size to given width and height

      size(width, height) {
        const p = proportionalSize(this, width, height);
        return this.attr('d', this.array().size(p.width, p.height));
      } // Set width of element

      width(width) {
        return width == null ? this.bbox().width : this.size(width, this.bbox().height);
      } // Move by left top corner over x-axis

      x(x) {
        return x == null ? this.bbox().x : this.move(x, this.bbox().y);
      } // Move by left top corner over y-axis

      y(y) {
        return y == null ? this.bbox().y : this.move(this.bbox().x, y);
      }
    } // Define morphable array

    Path.prototype.MorphArray = PathArray; // Add parent method

    registerMethods({
      Container: {
        // Create a wrapped path element
        path: wrapWithAttrCheck(function (d) {
          // make sure plot is called as a setter
          return this.put(new Path()).plot(d || new PathArray());
        })
      }
    });
    register(Path, 'Path');
    function array() {
      return this._array || (this._array = new PointArray(this.attr('points')));
    } // Clear array cache

    function clear() {
      delete this._array;
      return this;
    } // Move by left top corner

    function move$2(x, y) {
      return this.attr('points', this.array().move(x, y));
    } // Plot new path

    function plot(p) {
      return p == null ? this.array() : this.clear().attr('points', typeof p === 'string' ? p : this._array = new PointArray(p));
    } // Set element size to given width and height

    function size$1(width, height) {
      const p = proportionalSize(this, width, height);
      return this.attr('points', this.array().size(p.width, p.height));
    }
    var poly = {
      __proto__: null,
      array: array,
      clear: clear,
      move: move$2,
      plot: plot,
      size: size$1
    };
    class Polygon extends Shape {
      // Initialize node
      constructor(node) {
        let attrs = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : node;
        super(nodeOrNew('polygon', node), attrs);
      }
    }
    registerMethods({
      Container: {
        // Create a wrapped polygon element
        polygon: wrapWithAttrCheck(function (p) {
          // make sure plot is called as a setter
          return this.put(new Polygon()).plot(p || new PointArray());
        })
      }
    });
    extend(Polygon, pointed);
    extend(Polygon, poly);
    register(Polygon, 'Polygon');
    class Polyline extends Shape {
      // Initialize node
      constructor(node) {
        let attrs = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : node;
        super(nodeOrNew('polyline', node), attrs);
      }
    }
    registerMethods({
      Container: {
        // Create a wrapped polygon element
        polyline: wrapWithAttrCheck(function (p) {
          // make sure plot is called as a setter
          return this.put(new Polyline()).plot(p || new PointArray());
        })
      }
    });
    extend(Polyline, pointed);
    extend(Polyline, poly);
    register(Polyline, 'Polyline');
    class Rect extends Shape {
      // Initialize node
      constructor(node) {
        let attrs = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : node;
        super(nodeOrNew('rect', node), attrs);
      }
    }
    extend(Rect, {
      rx,
      ry
    });
    registerMethods({
      Container: {
        // Create a rect element
        rect: wrapWithAttrCheck(function (width, height) {
          return this.put(new Rect()).size(width, height);
        })
      }
    });
    register(Rect, 'Rect');
    class Queue {
      constructor() {
        this._first = null;
        this._last = null;
      } // Shows us the first item in the list

      first() {
        return this._first && this._first.value;
      } // Shows us the last item in the list

      last() {
        return this._last && this._last.value;
      }
      push(value) {
        // An item stores an id and the provided value
        const item = typeof value.next !== 'undefined' ? value : {
          value: value,
          next: null,
          prev: null
        }; // Deal with the queue being empty or populated

        if (this._last) {
          item.prev = this._last;
          this._last.next = item;
          this._last = item;
        } else {
          this._last = item;
          this._first = item;
        } // Return the current item

        return item;
      } // Removes the item that was returned from the push

      remove(item) {
        // Relink the previous item
        if (item.prev) item.prev.next = item.next;
        if (item.next) item.next.prev = item.prev;
        if (item === this._last) this._last = item.prev;
        if (item === this._first) this._first = item.next; // Invalidate item

        item.prev = null;
        item.next = null;
      }
      shift() {
        // Check if we have a value
        const remove = this._first;
        if (!remove) return null; // If we do, remove it and relink things

        this._first = remove.next;
        if (this._first) this._first.prev = null;
        this._last = this._first ? this._last : null;
        return remove.value;
      }
    }
    const Animator = {
      nextDraw: null,
      frames: new Queue(),
      timeouts: new Queue(),
      immediates: new Queue(),
      timer: () => globals.window.performance || globals.window.Date,
      transforms: [],
      frame(fn) {
        // Store the node
        const node = Animator.frames.push({
          run: fn
        }); // Request an animation frame if we don't have one

        if (Animator.nextDraw === null) {
          Animator.nextDraw = globals.window.requestAnimationFrame(Animator._draw);
        } // Return the node so we can remove it easily

        return node;
      },
      timeout(fn, delay) {
        delay = delay || 0; // Work out when the event should fire

        const time = Animator.timer().now() + delay; // Add the timeout to the end of the queue

        const node = Animator.timeouts.push({
          run: fn,
          time: time
        }); // Request another animation frame if we need one

        if (Animator.nextDraw === null) {
          Animator.nextDraw = globals.window.requestAnimationFrame(Animator._draw);
        }
        return node;
      },
      immediate(fn) {
        // Add the immediate fn to the end of the queue
        const node = Animator.immediates.push(fn); // Request another animation frame if we need one

        if (Animator.nextDraw === null) {
          Animator.nextDraw = globals.window.requestAnimationFrame(Animator._draw);
        }
        return node;
      },
      cancelFrame(node) {
        node != null && Animator.frames.remove(node);
      },
      clearTimeout(node) {
        node != null && Animator.timeouts.remove(node);
      },
      cancelImmediate(node) {
        node != null && Animator.immediates.remove(node);
      },
      _draw(now) {
        // Run all the timeouts we can run, if they are not ready yet, add them
        // to the end of the queue immediately! (bad timeouts!!! [sarcasm])
        let nextTimeout = null;
        const lastTimeout = Animator.timeouts.last();
        while (nextTimeout = Animator.timeouts.shift()) {
          // Run the timeout if its time, or push it to the end
          if (now >= nextTimeout.time) {
            nextTimeout.run();
          } else {
            Animator.timeouts.push(nextTimeout);
          } // If we hit the last item, we should stop shifting out more items

          if (nextTimeout === lastTimeout) break;
        } // Run all of the animation frames

        let nextFrame = null;
        const lastFrame = Animator.frames.last();
        while (nextFrame !== lastFrame && (nextFrame = Animator.frames.shift())) {
          nextFrame.run(now);
        }
        let nextImmediate = null;
        while (nextImmediate = Animator.immediates.shift()) {
          nextImmediate();
        } // If we have remaining timeouts or frames, draw until we don't anymore

        Animator.nextDraw = Animator.timeouts.first() || Animator.frames.first() ? globals.window.requestAnimationFrame(Animator._draw) : null;
      }
    };
    const makeSchedule = function (runnerInfo) {
      const start = runnerInfo.start;
      const duration = runnerInfo.runner.duration();
      const end = start + duration;
      return {
        start: start,
        duration: duration,
        end: end,
        runner: runnerInfo.runner
      };
    };
    const defaultSource = function () {
      const w = globals.window;
      return (w.performance || w.Date).now();
    };
    class Timeline extends EventTarget {
      // Construct a new timeline on the given element
      constructor() {
        let timeSource = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : defaultSource;
        super();
        this._timeSource = timeSource; // Store the timing variables

        this._startTime = 0;
        this._speed = 1.0; // Determines how long a runner is hold in memory. Can be a dt or true/false

        this._persist = 0; // Keep track of the running animations and their starting parameters

        this._nextFrame = null;
        this._paused = true;
        this._runners = [];
        this._runnerIds = [];
        this._lastRunnerId = -1;
        this._time = 0;
        this._lastSourceTime = 0;
        this._lastStepTime = 0; // Make sure that step is always called in class context

        this._step = this._stepFn.bind(this, false);
        this._stepImmediate = this._stepFn.bind(this, true);
      }
      active() {
        return !!this._nextFrame;
      }
      finish() {
        // Go to end and pause
        this.time(this.getEndTimeOfTimeline() + 1);
        return this.pause();
      } // Calculates the end of the timeline

      getEndTime() {
        const lastRunnerInfo = this.getLastRunnerInfo();
        const lastDuration = lastRunnerInfo ? lastRunnerInfo.runner.duration() : 0;
        const lastStartTime = lastRunnerInfo ? lastRunnerInfo.start : this._time;
        return lastStartTime + lastDuration;
      }
      getEndTimeOfTimeline() {
        const endTimes = this._runners.map(i => i.start + i.runner.duration());
        return Math.max(0, ...endTimes);
      }
      getLastRunnerInfo() {
        return this.getRunnerInfoById(this._lastRunnerId);
      }
      getRunnerInfoById(id) {
        return this._runners[this._runnerIds.indexOf(id)] || null;
      }
      pause() {
        this._paused = true;
        return this._continue();
      }
      persist(dtOrForever) {
        if (dtOrForever == null) return this._persist;
        this._persist = dtOrForever;
        return this;
      }
      play() {
        // Now make sure we are not paused and continue the animation
        this._paused = false;
        return this.updateTime()._continue();
      }
      reverse(yes) {
        const currentSpeed = this.speed();
        if (yes == null) return this.speed(-currentSpeed);
        const positive = Math.abs(currentSpeed);
        return this.speed(yes ? -positive : positive);
      } // schedules a runner on the timeline

      schedule(runner, delay, when) {
        if (runner == null) {
          return this._runners.map(makeSchedule);
        } // The start time for the next animation can either be given explicitly,
        // derived from the current timeline time or it can be relative to the
        // last start time to chain animations directly

        let absoluteStartTime = 0;
        const endTime = this.getEndTime();
        delay = delay || 0; // Work out when to start the animation

        if (when == null || when === 'last' || when === 'after') {
          // Take the last time and increment
          absoluteStartTime = endTime;
        } else if (when === 'absolute' || when === 'start') {
          absoluteStartTime = delay;
          delay = 0;
        } else if (when === 'now') {
          absoluteStartTime = this._time;
        } else if (when === 'relative') {
          const runnerInfo = this.getRunnerInfoById(runner.id);
          if (runnerInfo) {
            absoluteStartTime = runnerInfo.start + delay;
            delay = 0;
          }
        } else if (when === 'with-last') {
          const lastRunnerInfo = this.getLastRunnerInfo();
          const lastStartTime = lastRunnerInfo ? lastRunnerInfo.start : this._time;
          absoluteStartTime = lastStartTime;
        } else {
          throw new Error('Invalid value for the "when" parameter');
        } // Manage runner

        runner.unschedule();
        runner.timeline(this);
        const persist = runner.persist();
        const runnerInfo = {
          persist: persist === null ? this._persist : persist,
          start: absoluteStartTime + delay,
          runner
        };
        this._lastRunnerId = runner.id;
        this._runners.push(runnerInfo);
        this._runners.sort((a, b) => a.start - b.start);
        this._runnerIds = this._runners.map(info => info.runner.id);
        this.updateTime()._continue();
        return this;
      }
      seek(dt) {
        return this.time(this._time + dt);
      }
      source(fn) {
        if (fn == null) return this._timeSource;
        this._timeSource = fn;
        return this;
      }
      speed(speed) {
        if (speed == null) return this._speed;
        this._speed = speed;
        return this;
      }
      stop() {
        // Go to start and pause
        this.time(0);
        return this.pause();
      }
      time(time) {
        if (time == null) return this._time;
        this._time = time;
        return this._continue(true);
      } // Remove the runner from this timeline

      unschedule(runner) {
        const index = this._runnerIds.indexOf(runner.id);
        if (index < 0) return this;
        this._runners.splice(index, 1);
        this._runnerIds.splice(index, 1);
        runner.timeline(null);
        return this;
      } // Makes sure, that after pausing the time doesn't jump

      updateTime() {
        if (!this.active()) {
          this._lastSourceTime = this._timeSource();
        }
        return this;
      } // Checks if we are running and continues the animation

      _continue() {
        let immediateStep = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
        Animator.cancelFrame(this._nextFrame);
        this._nextFrame = null;
        if (immediateStep) return this._stepImmediate();
        if (this._paused) return this;
        this._nextFrame = Animator.frame(this._step);
        return this;
      }
      _stepFn() {
        let immediateStep = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
        // Get the time delta from the last time and update the time
        const time = this._timeSource();
        let dtSource = time - this._lastSourceTime;
        if (immediateStep) dtSource = 0;
        const dtTime = this._speed * dtSource + (this._time - this._lastStepTime);
        this._lastSourceTime = time; // Only update the time if we use the timeSource.
        // Otherwise use the current time

        if (!immediateStep) {
          // Update the time
          this._time += dtTime;
          this._time = this._time < 0 ? 0 : this._time;
        }
        this._lastStepTime = this._time;
        this.fire('time', this._time); // This is for the case that the timeline was seeked so that the time
        // is now before the startTime of the runner. Thats why we need to set
        // the runner to position 0
        // FIXME:
        // However, reseting in insertion order leads to bugs. Considering the case,
        // where 2 runners change the same attribute but in different times,
        // reseting both of them will lead to the case where the later defined
        // runner always wins the reset even if the other runner started earlier
        // and therefore should win the attribute battle
        // this can be solved by reseting them backwards

        for (let k = this._runners.length; k--;) {
          // Get and run the current runner and ignore it if its inactive
          const runnerInfo = this._runners[k];
          const runner = runnerInfo.runner; // Make sure that we give the actual difference
          // between runner start time and now

          const dtToStart = this._time - runnerInfo.start; // Dont run runner if not started yet
          // and try to reset it

          if (dtToStart <= 0) {
            runner.reset();
          }
        } // Run all of the runners directly

        let runnersLeft = false;
        for (let i = 0, len = this._runners.length; i < len; i++) {
          // Get and run the current runner and ignore it if its inactive
          const runnerInfo = this._runners[i];
          const runner = runnerInfo.runner;
          let dt = dtTime; // Make sure that we give the actual difference
          // between runner start time and now

          const dtToStart = this._time - runnerInfo.start; // Dont run runner if not started yet

          if (dtToStart <= 0) {
            runnersLeft = true;
            continue;
          } else if (dtToStart < dt) {
            // Adjust dt to make sure that animation is on point
            dt = dtToStart;
          }
          if (!runner.active()) continue; // If this runner is still going, signal that we need another animation
          // frame, otherwise, remove the completed runner

          const finished = runner.step(dt).done;
          if (!finished) {
            runnersLeft = true; // continue
          } else if (runnerInfo.persist !== true) {
            // runner is finished. And runner might get removed
            const endTime = runner.duration() - runner.time() + this._time;
            if (endTime + runnerInfo.persist < this._time) {
              // Delete runner and correct index
              runner.unschedule();
              --i;
              --len;
            }
          }
        } // Basically: we continue when there are runners right from us in time
        // when -->, and when runners are left from us when <--

        if (runnersLeft && !(this._speed < 0 && this._time === 0) || this._runnerIds.length && this._speed < 0 && this._time > 0) {
          this._continue();
        } else {
          this.pause();
          this.fire('finished');
        }
        return this;
      }
    }
    registerMethods({
      Element: {
        timeline: function (timeline) {
          if (timeline == null) {
            this._timeline = this._timeline || new Timeline();
            return this._timeline;
          } else {
            this._timeline = timeline;
            return this;
          }
        }
      }
    });
    class Runner extends EventTarget {
      constructor(options) {
        super(); // Store a unique id on the runner, so that we can identify it later

        this.id = Runner.id++; // Ensure a default value

        options = options == null ? timeline.duration : options; // Ensure that we get a controller

        options = typeof options === 'function' ? new Controller(options) : options; // Declare all of the variables

        this._element = null;
        this._timeline = null;
        this.done = false;
        this._queue = []; // Work out the stepper and the duration

        this._duration = typeof options === 'number' && options;
        this._isDeclarative = options instanceof Controller;
        this._stepper = this._isDeclarative ? options : new Ease(); // We copy the current values from the timeline because they can change

        this._history = {}; // Store the state of the runner

        this.enabled = true;
        this._time = 0;
        this._lastTime = 0; // At creation, the runner is in reseted state

        this._reseted = true; // Save transforms applied to this runner

        this.transforms = new Matrix();
        this.transformId = 1; // Looping variables

        this._haveReversed = false;
        this._reverse = false;
        this._loopsDone = 0;
        this._swing = false;
        this._wait = 0;
        this._times = 1;
        this._frameId = null; // Stores how long a runner is stored after beeing done

        this._persist = this._isDeclarative ? true : null;
      }
      static sanitise(duration, delay, when) {
        // Initialise the default parameters
        let times = 1;
        let swing = false;
        let wait = 0;
        duration = duration || timeline.duration;
        delay = delay || timeline.delay;
        when = when || 'last'; // If we have an object, unpack the values

        if (typeof duration === 'object' && !(duration instanceof Stepper)) {
          delay = duration.delay || delay;
          when = duration.when || when;
          swing = duration.swing || swing;
          times = duration.times || times;
          wait = duration.wait || wait;
          duration = duration.duration || timeline.duration;
        }
        return {
          duration: duration,
          delay: delay,
          swing: swing,
          times: times,
          wait: wait,
          when: when
        };
      }
      active(enabled) {
        if (enabled == null) return this.enabled;
        this.enabled = enabled;
        return this;
      }
      /*
      Private Methods
      ===============
      Methods that shouldn't be used externally
      */

      addTransform(transform, index) {
        this.transforms.lmultiplyO(transform);
        return this;
      }
      after(fn) {
        return this.on('finished', fn);
      }
      animate(duration, delay, when) {
        const o = Runner.sanitise(duration, delay, when);
        const runner = new Runner(o.duration);
        if (this._timeline) runner.timeline(this._timeline);
        if (this._element) runner.element(this._element);
        return runner.loop(o).schedule(o.delay, o.when);
      }
      clearTransform() {
        this.transforms = new Matrix();
        return this;
      } // TODO: Keep track of all transformations so that deletion is faster

      clearTransformsFromQueue() {
        if (!this.done || !this._timeline || !this._timeline._runnerIds.includes(this.id)) {
          this._queue = this._queue.filter(item => {
            return !item.isTransform;
          });
        }
      }
      delay(delay) {
        return this.animate(0, delay);
      }
      duration() {
        return this._times * (this._wait + this._duration) - this._wait;
      }
      during(fn) {
        return this.queue(null, fn);
      }
      ease(fn) {
        this._stepper = new Ease(fn);
        return this;
      }
      /*
      Runner Definitions
      ==================
      These methods help us define the runtime behaviour of the Runner or they
      help us make new runners from the current runner
      */

      element(element) {
        if (element == null) return this._element;
        this._element = element;
        element._prepareRunner();
        return this;
      }
      finish() {
        return this.step(Infinity);
      }
      loop(times, swing, wait) {
        // Deal with the user passing in an object
        if (typeof times === 'object') {
          swing = times.swing;
          wait = times.wait;
          times = times.times;
        } // Sanitise the values and store them

        this._times = times || Infinity;
        this._swing = swing || false;
        this._wait = wait || 0; // Allow true to be passed

        if (this._times === true) {
          this._times = Infinity;
        }
        return this;
      }
      loops(p) {
        const loopDuration = this._duration + this._wait;
        if (p == null) {
          const loopsDone = Math.floor(this._time / loopDuration);
          const relativeTime = this._time - loopsDone * loopDuration;
          const position = relativeTime / this._duration;
          return Math.min(loopsDone + position, this._times);
        }
        const whole = Math.floor(p);
        const partial = p % 1;
        const time = loopDuration * whole + this._duration * partial;
        return this.time(time);
      }
      persist(dtOrForever) {
        if (dtOrForever == null) return this._persist;
        this._persist = dtOrForever;
        return this;
      }
      position(p) {
        // Get all of the variables we need
        const x = this._time;
        const d = this._duration;
        const w = this._wait;
        const t = this._times;
        const s = this._swing;
        const r = this._reverse;
        let position;
        if (p == null) {
          /*
          This function converts a time to a position in the range [0, 1]
          The full explanation can be found in this desmos demonstration
            https://www.desmos.com/calculator/u4fbavgche
          The logic is slightly simplified here because we can use booleans
          */
          // Figure out the value without thinking about the start or end time
          const f = function (x) {
            const swinging = s * Math.floor(x % (2 * (w + d)) / (w + d));
            const backwards = swinging && !r || !swinging && r;
            const uncliped = Math.pow(-1, backwards) * (x % (w + d)) / d + backwards;
            const clipped = Math.max(Math.min(uncliped, 1), 0);
            return clipped;
          }; // Figure out the value by incorporating the start time

          const endTime = t * (w + d) - w;
          position = x <= 0 ? Math.round(f(1e-5)) : x < endTime ? f(x) : Math.round(f(endTime - 1e-5));
          return position;
        } // Work out the loops done and add the position to the loops done

        const loopsDone = Math.floor(this.loops());
        const swingForward = s && loopsDone % 2 === 0;
        const forwards = swingForward && !r || r && swingForward;
        position = loopsDone + (forwards ? p : 1 - p);
        return this.loops(position);
      }
      progress(p) {
        if (p == null) {
          return Math.min(1, this._time / this.duration());
        }
        return this.time(p * this.duration());
      }
      /*
      Basic Functionality
      ===================
      These methods allow us to attach basic functions to the runner directly
      */

      queue(initFn, runFn, retargetFn, isTransform) {
        this._queue.push({
          initialiser: initFn || noop,
          runner: runFn || noop,
          retarget: retargetFn,
          isTransform: isTransform,
          initialised: false,
          finished: false
        });
        const timeline = this.timeline();
        timeline && this.timeline()._continue();
        return this;
      }
      reset() {
        if (this._reseted) return this;
        this.time(0);
        this._reseted = true;
        return this;
      }
      reverse(reverse) {
        this._reverse = reverse == null ? !this._reverse : reverse;
        return this;
      }
      schedule(timeline, delay, when) {
        // The user doesn't need to pass a timeline if we already have one
        if (!(timeline instanceof Timeline)) {
          when = delay;
          delay = timeline;
          timeline = this.timeline();
        } // If there is no timeline, yell at the user...

        if (!timeline) {
          throw Error('Runner cannot be scheduled without timeline');
        } // Schedule the runner on the timeline provided

        timeline.schedule(this, delay, when);
        return this;
      }
      step(dt) {
        // If we are inactive, this stepper just gets skipped
        if (!this.enabled) return this; // Update the time and get the new position

        dt = dt == null ? 16 : dt;
        this._time += dt;
        const position = this.position(); // Figure out if we need to run the stepper in this frame

        const running = this._lastPosition !== position && this._time >= 0;
        this._lastPosition = position; // Figure out if we just started

        const duration = this.duration();
        const justStarted = this._lastTime <= 0 && this._time > 0;
        const justFinished = this._lastTime < duration && this._time >= duration;
        this._lastTime = this._time;
        if (justStarted) {
          this.fire('start', this);
        } // Work out if the runner is finished set the done flag here so animations
        // know, that they are running in the last step (this is good for
        // transformations which can be merged)

        const declarative = this._isDeclarative;
        this.done = !declarative && !justFinished && this._time >= duration; // Runner is running. So its not in reseted state anymore

        this._reseted = false;
        let converged = false; // Call initialise and the run function

        if (running || declarative) {
          this._initialise(running); // clear the transforms on this runner so they dont get added again and again

          this.transforms = new Matrix();
          converged = this._run(declarative ? dt : position);
          this.fire('step', this);
        } // correct the done flag here
        // declaritive animations itself know when they converged

        this.done = this.done || converged && declarative;
        if (justFinished) {
          this.fire('finished', this);
        }
        return this;
      }
      /*
      Runner animation methods
      ========================
      Control how the animation plays
      */

      time(time) {
        if (time == null) {
          return this._time;
        }
        const dt = time - this._time;
        this.step(dt);
        return this;
      }
      timeline(timeline) {
        // check explicitly for undefined so we can set the timeline to null
        if (typeof timeline === 'undefined') return this._timeline;
        this._timeline = timeline;
        return this;
      }
      unschedule() {
        const timeline = this.timeline();
        timeline && timeline.unschedule(this);
        return this;
      } // Run each initialise function in the runner if required

      _initialise(running) {
        // If we aren't running, we shouldn't initialise when not declarative
        if (!running && !this._isDeclarative) return; // Loop through all of the initialisers

        for (let i = 0, len = this._queue.length; i < len; ++i) {
          // Get the current initialiser
          const current = this._queue[i]; // Determine whether we need to initialise

          const needsIt = this._isDeclarative || !current.initialised && running;
          running = !current.finished; // Call the initialiser if we need to

          if (needsIt && running) {
            current.initialiser.call(this);
            current.initialised = true;
          }
        }
      } // Save a morpher to the morpher list so that we can retarget it later

      _rememberMorpher(method, morpher) {
        this._history[method] = {
          morpher: morpher,
          caller: this._queue[this._queue.length - 1]
        }; // We have to resume the timeline in case a controller
        // is already done without being ever run
        // This can happen when e.g. this is done:
        //    anim = el.animate(new SVG.Spring)
        // and later
        //    anim.move(...)

        if (this._isDeclarative) {
          const timeline = this.timeline();
          timeline && timeline.play();
        }
      } // Try to set the target for a morpher if the morpher exists, otherwise
      // Run each run function for the position or dt given

      _run(positionOrDt) {
        // Run all of the _queue directly
        let allfinished = true;
        for (let i = 0, len = this._queue.length; i < len; ++i) {
          // Get the current function to run
          const current = this._queue[i]; // Run the function if its not finished, we keep track of the finished
          // flag for the sake of declarative _queue

          const converged = current.runner.call(this, positionOrDt);
          current.finished = current.finished || converged === true;
          allfinished = allfinished && current.finished;
        } // We report when all of the constructors are finished

        return allfinished;
      } // do nothing and return false

      _tryRetarget(method, target, extra) {
        if (this._history[method]) {
          // if the last method wasnt even initialised, throw it away
          if (!this._history[method].caller.initialised) {
            const index = this._queue.indexOf(this._history[method].caller);
            this._queue.splice(index, 1);
            return false;
          } // for the case of transformations, we use the special retarget function
          // which has access to the outer scope

          if (this._history[method].caller.retarget) {
            this._history[method].caller.retarget.call(this, target, extra); // for everything else a simple morpher change is sufficient
          } else {
            this._history[method].morpher.to(target);
          }
          this._history[method].caller.finished = false;
          const timeline = this.timeline();
          timeline && timeline.play();
          return true;
        }
        return false;
      }
    }
    Runner.id = 0;
    class FakeRunner {
      constructor() {
        let transforms = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : new Matrix();
        let id = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : -1;
        let done = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
        this.transforms = transforms;
        this.id = id;
        this.done = done;
      }
      clearTransformsFromQueue() {}
    }
    extend([Runner, FakeRunner], {
      mergeWith(runner) {
        return new FakeRunner(runner.transforms.lmultiply(this.transforms), runner.id);
      }
    }); // FakeRunner.emptyRunner = new FakeRunner()

    const lmultiply = (last, curr) => last.lmultiplyO(curr);
    const getRunnerTransform = runner => runner.transforms;
    function mergeTransforms() {
      // Find the matrix to apply to the element and apply it
      const runners = this._transformationRunners.runners;
      const netTransform = runners.map(getRunnerTransform).reduce(lmultiply, new Matrix());
      this.transform(netTransform);
      this._transformationRunners.merge();
      if (this._transformationRunners.length() === 1) {
        this._frameId = null;
      }
    }
    class RunnerArray {
      constructor() {
        this.runners = [];
        this.ids = [];
      }
      add(runner) {
        if (this.runners.includes(runner)) return;
        const id = runner.id + 1;
        this.runners.push(runner);
        this.ids.push(id);
        return this;
      }
      clearBefore(id) {
        const deleteCnt = this.ids.indexOf(id + 1) || 1;
        this.ids.splice(0, deleteCnt, 0);
        this.runners.splice(0, deleteCnt, new FakeRunner()).forEach(r => r.clearTransformsFromQueue());
        return this;
      }
      edit(id, newRunner) {
        const index = this.ids.indexOf(id + 1);
        this.ids.splice(index, 1, id + 1);
        this.runners.splice(index, 1, newRunner);
        return this;
      }
      getByID(id) {
        return this.runners[this.ids.indexOf(id + 1)];
      }
      length() {
        return this.ids.length;
      }
      merge() {
        let lastRunner = null;
        for (let i = 0; i < this.runners.length; ++i) {
          const runner = this.runners[i];
          const condition = lastRunner && runner.done && lastRunner.done // don't merge runner when persisted on timeline
          && (!runner._timeline || !runner._timeline._runnerIds.includes(runner.id)) && (!lastRunner._timeline || !lastRunner._timeline._runnerIds.includes(lastRunner.id));
          if (condition) {
            // the +1 happens in the function
            this.remove(runner.id);
            const newRunner = runner.mergeWith(lastRunner);
            this.edit(lastRunner.id, newRunner);
            lastRunner = newRunner;
            --i;
          } else {
            lastRunner = runner;
          }
        }
        return this;
      }
      remove(id) {
        const index = this.ids.indexOf(id + 1);
        this.ids.splice(index, 1);
        this.runners.splice(index, 1);
        return this;
      }
    }
    registerMethods({
      Element: {
        animate(duration, delay, when) {
          const o = Runner.sanitise(duration, delay, when);
          const timeline = this.timeline();
          return new Runner(o.duration).loop(o).element(this).timeline(timeline.play()).schedule(o.delay, o.when);
        },
        delay(by, when) {
          return this.animate(0, by, when);
        },
        // this function searches for all runners on the element and deletes the ones
        // which run before the current one. This is because absolute transformations
        // overwfrite anything anyway so there is no need to waste time computing
        // other runners
        _clearTransformRunnersBefore(currentRunner) {
          this._transformationRunners.clearBefore(currentRunner.id);
        },
        _currentTransform(current) {
          return this._transformationRunners.runners // we need the equal sign here to make sure, that also transformations
          // on the same runner which execute before the current transformation are
          // taken into account
          .filter(runner => runner.id <= current.id).map(getRunnerTransform).reduce(lmultiply, new Matrix());
        },
        _addRunner(runner) {
          this._transformationRunners.add(runner); // Make sure that the runner merge is executed at the very end of
          // all Animator functions. Thats why we use immediate here to execute
          // the merge right after all frames are run

          Animator.cancelImmediate(this._frameId);
          this._frameId = Animator.immediate(mergeTransforms.bind(this));
        },
        _prepareRunner() {
          if (this._frameId == null) {
            this._transformationRunners = new RunnerArray().add(new FakeRunner(new Matrix(this)));
          }
        }
      }
    }); // Will output the elements from array A that are not in the array B

    const difference = (a, b) => a.filter(x => !b.includes(x));
    extend(Runner, {
      attr(a, v) {
        return this.styleAttr('attr', a, v);
      },
      // Add animatable styles
      css(s, v) {
        return this.styleAttr('css', s, v);
      },
      styleAttr(type, nameOrAttrs, val) {
        if (typeof nameOrAttrs === 'string') {
          return this.styleAttr(type, {
            [nameOrAttrs]: val
          });
        }
        let attrs = nameOrAttrs;
        if (this._tryRetarget(type, attrs)) return this;
        let morpher = new Morphable(this._stepper).to(attrs);
        let keys = Object.keys(attrs);
        this.queue(function () {
          morpher = morpher.from(this.element()[type](keys));
        }, function (pos) {
          this.element()[type](morpher.at(pos).valueOf());
          return morpher.done();
        }, function (newToAttrs) {
          // Check if any new keys were added
          const newKeys = Object.keys(newToAttrs);
          const differences = difference(newKeys, keys); // If their are new keys, initialize them and add them to morpher

          if (differences.length) {
            // Get the values
            const addedFromAttrs = this.element()[type](differences); // Get the already initialized values

            const oldFromAttrs = new ObjectBag(morpher.from()).valueOf(); // Merge old and new

            Object.assign(oldFromAttrs, addedFromAttrs);
            morpher.from(oldFromAttrs);
          } // Get the object from the morpher

          const oldToAttrs = new ObjectBag(morpher.to()).valueOf(); // Merge in new attributes

          Object.assign(oldToAttrs, newToAttrs); // Change morpher target

          morpher.to(oldToAttrs); // Make sure that we save the work we did so we don't need it to do again

          keys = newKeys;
          attrs = newToAttrs;
        });
        this._rememberMorpher(type, morpher);
        return this;
      },
      zoom(level, point) {
        if (this._tryRetarget('zoom', level, point)) return this;
        let morpher = new Morphable(this._stepper).to(new SVGNumber(level));
        this.queue(function () {
          morpher = morpher.from(this.element().zoom());
        }, function (pos) {
          this.element().zoom(morpher.at(pos), point);
          return morpher.done();
        }, function (newLevel, newPoint) {
          point = newPoint;
          morpher.to(newLevel);
        });
        this._rememberMorpher('zoom', morpher);
        return this;
      },
      /**
       ** absolute transformations
       **/
      //
      // M v -----|-----(D M v = F v)------|----->  T v
      //
      // 1. define the final state (T) and decompose it (once)
      //    t = [tx, ty, the, lam, sy, sx]
      // 2. on every frame: pull the current state of all previous transforms
      //    (M - m can change)
      //   and then write this as m = [tx0, ty0, the0, lam0, sy0, sx0]
      // 3. Find the interpolated matrix F(pos) = m + pos * (t - m)
      //   - Note F(0) = M
      //   - Note F(1) = T
      // 4. Now you get the delta matrix as a result: D = F * inv(M)
      transform(transforms, relative, affine) {
        // If we have a declarative function, we should retarget it if possible
        relative = transforms.relative || relative;
        if (this._isDeclarative && !relative && this._tryRetarget('transform', transforms)) {
          return this;
        } // Parse the parameters

        const isMatrix = Matrix.isMatrixLike(transforms);
        affine = transforms.affine != null ? transforms.affine : affine != null ? affine : !isMatrix; // Create a morepher and set its type

        const morpher = new Morphable(this._stepper).type(affine ? TransformBag : Matrix);
        let origin;
        let element;
        let current;
        let currentAngle;
        let startTransform;
        function setup() {
          // make sure element and origin is defined
          element = element || this.element();
          origin = origin || getOrigin(transforms, element);
          startTransform = new Matrix(relative ? undefined : element); // add the runner to the element so it can merge transformations

          element._addRunner(this); // Deactivate all transforms that have run so far if we are absolute

          if (!relative) {
            element._clearTransformRunnersBefore(this);
          }
        }
        function run(pos) {
          // clear all other transforms before this in case something is saved
          // on this runner. We are absolute. We dont need these!
          if (!relative) this.clearTransform();
          const {
            x,
            y
          } = new Point(origin).transform(element._currentTransform(this));
          let target = new Matrix({
            ...transforms,
            origin: [x, y]
          });
          let start = this._isDeclarative && current ? current : startTransform;
          if (affine) {
            target = target.decompose(x, y);
            start = start.decompose(x, y); // Get the current and target angle as it was set

            const rTarget = target.rotate;
            const rCurrent = start.rotate; // Figure out the shortest path to rotate directly

            const possibilities = [rTarget - 360, rTarget, rTarget + 360];
            const distances = possibilities.map(a => Math.abs(a - rCurrent));
            const shortest = Math.min(...distances);
            const index = distances.indexOf(shortest);
            target.rotate = possibilities[index];
          }
          if (relative) {
            // we have to be careful here not to overwrite the rotation
            // with the rotate method of Matrix
            if (!isMatrix) {
              target.rotate = transforms.rotate || 0;
            }
            if (this._isDeclarative && currentAngle) {
              start.rotate = currentAngle;
            }
          }
          morpher.from(start);
          morpher.to(target);
          const affineParameters = morpher.at(pos);
          currentAngle = affineParameters.rotate;
          current = new Matrix(affineParameters);
          this.addTransform(current);
          element._addRunner(this);
          return morpher.done();
        }
        function retarget(newTransforms) {
          // only get a new origin if it changed since the last call
          if ((newTransforms.origin || 'center').toString() !== (transforms.origin || 'center').toString()) {
            origin = getOrigin(newTransforms, element);
          } // overwrite the old transformations with the new ones

          transforms = {
            ...newTransforms,
            origin
          };
        }
        this.queue(setup, run, retarget, true);
        this._isDeclarative && this._rememberMorpher('transform', morpher);
        return this;
      },
      // Animatable x-axis
      x(x, relative) {
        return this._queueNumber('x', x);
      },
      // Animatable y-axis
      y(y) {
        return this._queueNumber('y', y);
      },
      dx() {
        let x = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
        return this._queueNumberDelta('x', x);
      },
      dy() {
        let y = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
        return this._queueNumberDelta('y', y);
      },
      dmove(x, y) {
        return this.dx(x).dy(y);
      },
      _queueNumberDelta(method, to) {
        to = new SVGNumber(to); // Try to change the target if we have this method already registerd

        if (this._tryRetarget(method, to)) return this; // Make a morpher and queue the animation

        const morpher = new Morphable(this._stepper).to(to);
        let from = null;
        this.queue(function () {
          from = this.element()[method]();
          morpher.from(from);
          morpher.to(from + to);
        }, function (pos) {
          this.element()[method](morpher.at(pos));
          return morpher.done();
        }, function (newTo) {
          morpher.to(from + new SVGNumber(newTo));
        }); // Register the morpher so that if it is changed again, we can retarget it

        this._rememberMorpher(method, morpher);
        return this;
      },
      _queueObject(method, to) {
        // Try to change the target if we have this method already registerd
        if (this._tryRetarget(method, to)) return this; // Make a morpher and queue the animation

        const morpher = new Morphable(this._stepper).to(to);
        this.queue(function () {
          morpher.from(this.element()[method]());
        }, function (pos) {
          this.element()[method](morpher.at(pos));
          return morpher.done();
        }); // Register the morpher so that if it is changed again, we can retarget it

        this._rememberMorpher(method, morpher);
        return this;
      },
      _queueNumber(method, value) {
        return this._queueObject(method, new SVGNumber(value));
      },
      // Animatable center x-axis
      cx(x) {
        return this._queueNumber('cx', x);
      },
      // Animatable center y-axis
      cy(y) {
        return this._queueNumber('cy', y);
      },
      // Add animatable move
      move(x, y) {
        return this.x(x).y(y);
      },
      // Add animatable center
      center(x, y) {
        return this.cx(x).cy(y);
      },
      // Add animatable size
      size(width, height) {
        // animate bbox based size for all other elements
        let box;
        if (!width || !height) {
          box = this._element.bbox();
        }
        if (!width) {
          width = box.width / box.height * height;
        }
        if (!height) {
          height = box.height / box.width * width;
        }
        return this.width(width).height(height);
      },
      // Add animatable width
      width(width) {
        return this._queueNumber('width', width);
      },
      // Add animatable height
      height(height) {
        return this._queueNumber('height', height);
      },
      // Add animatable plot
      plot(a, b, c, d) {
        // Lines can be plotted with 4 arguments
        if (arguments.length === 4) {
          return this.plot([a, b, c, d]);
        }
        if (this._tryRetarget('plot', a)) return this;
        const morpher = new Morphable(this._stepper).type(this._element.MorphArray).to(a);
        this.queue(function () {
          morpher.from(this._element.array());
        }, function (pos) {
          this._element.plot(morpher.at(pos));
          return morpher.done();
        });
        this._rememberMorpher('plot', morpher);
        return this;
      },
      // Add leading method
      leading(value) {
        return this._queueNumber('leading', value);
      },
      // Add animatable viewbox
      viewbox(x, y, width, height) {
        return this._queueObject('viewbox', new Box(x, y, width, height));
      },
      update(o) {
        if (typeof o !== 'object') {
          return this.update({
            offset: arguments[0],
            color: arguments[1],
            opacity: arguments[2]
          });
        }
        if (o.opacity != null) this.attr('stop-opacity', o.opacity);
        if (o.color != null) this.attr('stop-color', o.color);
        if (o.offset != null) this.attr('offset', o.offset);
        return this;
      }
    });
    extend(Runner, {
      rx,
      ry,
      from,
      to
    });
    register(Runner, 'Runner');
    class Svg extends Container {
      constructor(node) {
        let attrs = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : node;
        super(nodeOrNew('svg', node), attrs);
        this.namespace();
      } // Creates and returns defs element

      defs() {
        if (!this.isRoot()) return this.root().defs();
        return adopt(this.node.querySelector('defs')) || this.put(new Defs());
      }
      isRoot() {
        return !this.node.parentNode || !(this.node.parentNode instanceof globals.window.SVGElement) && this.node.parentNode.nodeName !== '#document-fragment';
      } // Add namespaces

      namespace() {
        if (!this.isRoot()) return this.root().namespace();
        return this.attr({
          xmlns: svg,
          version: '1.1'
        }).attr('xmlns:xlink', xlink, xmlns).attr('xmlns:svgjs', svgjs, xmlns);
      }
      removeNamespace() {
        return this.attr({
          xmlns: null,
          version: null
        }).attr('xmlns:xlink', null, xmlns).attr('xmlns:svgjs', null, xmlns);
      } // Check if this is a root svg
      // If not, call root() from this element

      root() {
        if (this.isRoot()) return this;
        return super.root();
      }
    }
    registerMethods({
      Container: {
        // Create nested svg document
        nested: wrapWithAttrCheck(function () {
          return this.put(new Svg());
        })
      }
    });
    register(Svg, 'Svg', true);
    class Symbol$1 extends Container {
      // Initialize node
      constructor(node) {
        let attrs = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : node;
        super(nodeOrNew('symbol', node), attrs);
      }
    }
    registerMethods({
      Container: {
        symbol: wrapWithAttrCheck(function () {
          return this.put(new Symbol$1());
        })
      }
    });
    register(Symbol$1, 'Symbol');
    function plain(text) {
      // clear if build mode is disabled
      if (this._build === false) {
        this.clear();
      } // create text node

      this.node.appendChild(globals.document.createTextNode(text));
      return this;
    } // Get length of text element

    function length() {
      return this.node.getComputedTextLength();
    } // Move over x-axis
    // Text is moved by its bounding box
    // text-anchor does NOT matter

    function x$1(x) {
      let box = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.bbox();
      if (x == null) {
        return box.x;
      }
      return this.attr('x', this.attr('x') + x - box.x);
    } // Move over y-axis

    function y$1(y) {
      let box = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.bbox();
      if (y == null) {
        return box.y;
      }
      return this.attr('y', this.attr('y') + y - box.y);
    }
    function move$1(x, y) {
      let box = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : this.bbox();
      return this.x(x, box).y(y, box);
    } // Move center over x-axis

    function cx(x) {
      let box = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.bbox();
      if (x == null) {
        return box.cx;
      }
      return this.attr('x', this.attr('x') + x - box.cx);
    } // Move center over y-axis

    function cy(y) {
      let box = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.bbox();
      if (y == null) {
        return box.cy;
      }
      return this.attr('y', this.attr('y') + y - box.cy);
    }
    function center(x, y) {
      let box = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : this.bbox();
      return this.cx(x, box).cy(y, box);
    }
    function ax(x) {
      return this.attr('x', x);
    }
    function ay(y) {
      return this.attr('y', y);
    }
    function amove(x, y) {
      return this.ax(x).ay(y);
    } // Enable / disable build mode

    function build(build) {
      this._build = !!build;
      return this;
    }
    var textable = {
      __proto__: null,
      plain: plain,
      length: length,
      x: x$1,
      y: y$1,
      move: move$1,
      cx: cx,
      cy: cy,
      center: center,
      ax: ax,
      ay: ay,
      amove: amove,
      build: build
    };
    class Text extends Shape {
      // Initialize node
      constructor(node) {
        let attrs = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : node;
        super(nodeOrNew('text', node), attrs);
        this.dom.leading = new SVGNumber(1.3); // store leading value for rebuilding

        this._rebuild = true; // enable automatic updating of dy values

        this._build = false; // disable build mode for adding multiple lines
      } // Set / get leading

      leading(value) {
        // act as getter
        if (value == null) {
          return this.dom.leading;
        } // act as setter

        this.dom.leading = new SVGNumber(value);
        return this.rebuild();
      } // Rebuild appearance type

      rebuild(rebuild) {
        // store new rebuild flag if given
        if (typeof rebuild === 'boolean') {
          this._rebuild = rebuild;
        } // define position of all lines

        if (this._rebuild) {
          const self = this;
          let blankLineOffset = 0;
          const leading = this.dom.leading;
          this.each(function (i) {
            const fontSize = globals.window.getComputedStyle(this.node).getPropertyValue('font-size');
            const dy = leading * new SVGNumber(fontSize);
            if (this.dom.newLined) {
              this.attr('x', self.attr('x'));
              if (this.text() === '\n') {
                blankLineOffset += dy;
              } else {
                this.attr('dy', i ? dy + blankLineOffset : 0);
                blankLineOffset = 0;
              }
            }
          });
          this.fire('rebuild');
        }
        return this;
      } // overwrite method from parent to set data properly

      setData(o) {
        this.dom = o;
        this.dom.leading = new SVGNumber(o.leading || 1.3);
        return this;
      } // Set the text content

      text(text) {
        // act as getter
        if (text === undefined) {
          const children = this.node.childNodes;
          let firstLine = 0;
          text = '';
          for (let i = 0, len = children.length; i < len; ++i) {
            // skip textPaths - they are no lines
            if (children[i].nodeName === 'textPath') {
              if (i === 0) firstLine = 1;
              continue;
            } // add newline if its not the first child and newLined is set to true

            if (i !== firstLine && children[i].nodeType !== 3 && adopt(children[i]).dom.newLined === true) {
              text += '\n';
            } // add content of this node

            text += children[i].textContent;
          }
          return text;
        } // remove existing content

        this.clear().build(true);
        if (typeof text === 'function') {
          // call block
          text.call(this, this);
        } else {
          // store text and make sure text is not blank
          text = (text + '').split('\n'); // build new lines

          for (let j = 0, jl = text.length; j < jl; j++) {
            this.newLine(text[j]);
          }
        } // disable build mode and rebuild lines

        return this.build(false).rebuild();
      }
    }
    extend(Text, textable);
    registerMethods({
      Container: {
        // Create text element
        text: wrapWithAttrCheck(function () {
          let text = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
          return this.put(new Text()).text(text);
        }),
        // Create plain text element
        plain: wrapWithAttrCheck(function () {
          let text = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
          return this.put(new Text()).plain(text);
        })
      }
    });
    register(Text, 'Text');
    class Tspan extends Shape {
      // Initialize node
      constructor(node) {
        let attrs = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : node;
        super(nodeOrNew('tspan', node), attrs);
        this._build = false; // disable build mode for adding multiple lines
      } // Shortcut dx

      dx(dx) {
        return this.attr('dx', dx);
      } // Shortcut dy

      dy(dy) {
        return this.attr('dy', dy);
      } // Create new line

      newLine() {
        // mark new line
        this.dom.newLined = true; // fetch parent

        const text = this.parent(); // early return in case we are not in a text element

        if (!(text instanceof Text)) {
          return this;
        }
        const i = text.index(this);
        const fontSize = globals.window.getComputedStyle(this.node).getPropertyValue('font-size');
        const dy = text.dom.leading * new SVGNumber(fontSize); // apply new position

        return this.dy(i ? dy : 0).attr('x', text.x());
      } // Set text content

      text(text) {
        if (text == null) return this.node.textContent + (this.dom.newLined ? '\n' : '');
        if (typeof text === 'function') {
          this.clear().build(true);
          text.call(this, this);
          this.build(false);
        } else {
          this.plain(text);
        }
        return this;
      }
    }
    extend(Tspan, textable);
    registerMethods({
      Tspan: {
        tspan: wrapWithAttrCheck(function () {
          let text = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
          const tspan = new Tspan(); // clear if build mode is disabled

          if (!this._build) {
            this.clear();
          } // add new tspan

          return this.put(tspan).text(text);
        })
      },
      Text: {
        newLine: function () {
          let text = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
          return this.tspan(text).newLine();
        }
      }
    });
    register(Tspan, 'Tspan');
    class Circle extends Shape {
      constructor(node) {
        let attrs = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : node;
        super(nodeOrNew('circle', node), attrs);
      }
      radius(r) {
        return this.attr('r', r);
      } // Radius x value

      rx(rx) {
        return this.attr('r', rx);
      } // Alias radius x value

      ry(ry) {
        return this.rx(ry);
      }
      size(size) {
        return this.radius(new SVGNumber(size).divide(2));
      }
    }
    extend(Circle, {
      x: x$3,
      y: y$3,
      cx: cx$1,
      cy: cy$1,
      width: width$2,
      height: height$2
    });
    registerMethods({
      Container: {
        // Create circle element
        circle: wrapWithAttrCheck(function () {
          let size = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
          return this.put(new Circle()).size(size).move(0, 0);
        })
      }
    });
    register(Circle, 'Circle');
    class ClipPath extends Container {
      constructor(node) {
        let attrs = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : node;
        super(nodeOrNew('clipPath', node), attrs);
      } // Unclip all clipped elements and remove itself

      remove() {
        // unclip all targets
        this.targets().forEach(function (el) {
          el.unclip();
        }); // remove clipPath from parent

        return super.remove();
      }
      targets() {
        return baseFind('svg [clip-path*="' + this.id() + '"]');
      }
    }
    registerMethods({
      Container: {
        // Create clipping element
        clip: wrapWithAttrCheck(function () {
          return this.defs().put(new ClipPath());
        })
      },
      Element: {
        // Distribute clipPath to svg element
        clipper() {
          return this.reference('clip-path');
        },
        clipWith(element) {
          // use given clip or create a new one
          const clipper = element instanceof ClipPath ? element : this.parent().clip().add(element); // apply mask

          return this.attr('clip-path', 'url("#' + clipper.id() + '")');
        },
        // Unclip element
        unclip() {
          return this.attr('clip-path', null);
        }
      }
    });
    register(ClipPath, 'ClipPath');
    class ForeignObject extends Element {
      constructor(node) {
        let attrs = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : node;
        super(nodeOrNew('foreignObject', node), attrs);
      }
    }
    registerMethods({
      Container: {
        foreignObject: wrapWithAttrCheck(function (width, height) {
          return this.put(new ForeignObject()).size(width, height);
        })
      }
    });
    register(ForeignObject, 'ForeignObject');
    function dmove(dx, dy) {
      this.children().forEach((child, i) => {
        let bbox; // We have to wrap this for elements that dont have a bbox
        // e.g. title and other descriptive elements

        try {
          // Get the childs bbox
          bbox = child.bbox();
        } catch (e) {
          return;
        } // Get childs matrix

        const m = new Matrix(child); // Translate childs matrix by amount and
        // transform it back into parents space

        const matrix = m.translate(dx, dy).transform(m.inverse()); // Calculate new x and y from old box

        const p = new Point(bbox.x, bbox.y).transform(matrix); // Move element

        child.move(p.x, p.y);
      });
      return this;
    }
    function dx(dx) {
      return this.dmove(dx, 0);
    }
    function dy(dy) {
      return this.dmove(0, dy);
    }
    function height(height) {
      let box = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.bbox();
      if (height == null) return box.height;
      return this.size(box.width, height, box);
    }
    function move() {
      let x = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
      let y = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
      let box = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : this.bbox();
      const dx = x - box.x;
      const dy = y - box.y;
      return this.dmove(dx, dy);
    }
    function size(width, height) {
      let box = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : this.bbox();
      const p = proportionalSize(this, width, height, box);
      const scaleX = p.width / box.width;
      const scaleY = p.height / box.height;
      this.children().forEach((child, i) => {
        const o = new Point(box).transform(new Matrix(child).inverse());
        child.scale(scaleX, scaleY, o.x, o.y);
      });
      return this;
    }
    function width(width) {
      let box = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.bbox();
      if (width == null) return box.width;
      return this.size(width, box.height, box);
    }
    function x(x) {
      let box = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.bbox();
      if (x == null) return box.x;
      return this.move(x, box.y, box);
    }
    function y(y) {
      let box = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.bbox();
      if (y == null) return box.y;
      return this.move(box.x, y, box);
    }
    var containerGeometry = {
      __proto__: null,
      dmove: dmove,
      dx: dx,
      dy: dy,
      height: height,
      move: move,
      size: size,
      width: width,
      x: x,
      y: y
    };
    class G extends Container {
      constructor(node) {
        let attrs = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : node;
        super(nodeOrNew('g', node), attrs);
      }
    }
    extend(G, containerGeometry);
    registerMethods({
      Container: {
        // Create a group element
        group: wrapWithAttrCheck(function () {
          return this.put(new G());
        })
      }
    });
    register(G, 'G');
    class A extends Container {
      constructor(node) {
        let attrs = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : node;
        super(nodeOrNew('a', node), attrs);
      } // Link target attribute

      target(target) {
        return this.attr('target', target);
      } // Link url

      to(url) {
        return this.attr('href', url, xlink);
      }
    }
    extend(A, containerGeometry);
    registerMethods({
      Container: {
        // Create a hyperlink element
        link: wrapWithAttrCheck(function (url) {
          return this.put(new A()).to(url);
        })
      },
      Element: {
        unlink() {
          const link = this.linker();
          if (!link) return this;
          const parent = link.parent();
          if (!parent) {
            return this.remove();
          }
          const index = parent.index(link);
          parent.add(this, index);
          link.remove();
          return this;
        },
        linkTo(url) {
          // reuse old link if possible
          let link = this.linker();
          if (!link) {
            link = new A();
            this.wrap(link);
          }
          if (typeof url === 'function') {
            url.call(link, link);
          } else {
            link.to(url);
          }
          return this;
        },
        linker() {
          const link = this.parent();
          if (link && link.node.nodeName.toLowerCase() === 'a') {
            return link;
          }
          return null;
        }
      }
    });
    register(A, 'A');
    class Mask extends Container {
      // Initialize node
      constructor(node) {
        let attrs = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : node;
        super(nodeOrNew('mask', node), attrs);
      } // Unmask all masked elements and remove itself

      remove() {
        // unmask all targets
        this.targets().forEach(function (el) {
          el.unmask();
        }); // remove mask from parent

        return super.remove();
      }
      targets() {
        return baseFind('svg [mask*="' + this.id() + '"]');
      }
    }
    registerMethods({
      Container: {
        mask: wrapWithAttrCheck(function () {
          return this.defs().put(new Mask());
        })
      },
      Element: {
        // Distribute mask to svg element
        masker() {
          return this.reference('mask');
        },
        maskWith(element) {
          // use given mask or create a new one
          const masker = element instanceof Mask ? element : this.parent().mask().add(element); // apply mask

          return this.attr('mask', 'url("#' + masker.id() + '")');
        },
        // Unmask element
        unmask() {
          return this.attr('mask', null);
        }
      }
    });
    register(Mask, 'Mask');
    class Stop extends Element {
      constructor(node) {
        let attrs = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : node;
        super(nodeOrNew('stop', node), attrs);
      } // add color stops

      update(o) {
        if (typeof o === 'number' || o instanceof SVGNumber) {
          o = {
            offset: arguments[0],
            color: arguments[1],
            opacity: arguments[2]
          };
        } // set attributes

        if (o.opacity != null) this.attr('stop-opacity', o.opacity);
        if (o.color != null) this.attr('stop-color', o.color);
        if (o.offset != null) this.attr('offset', new SVGNumber(o.offset));
        return this;
      }
    }
    registerMethods({
      Gradient: {
        // Add a color stop
        stop: function (offset, color, opacity) {
          return this.put(new Stop()).update(offset, color, opacity);
        }
      }
    });
    register(Stop, 'Stop');
    function cssRule(selector, rule) {
      if (!selector) return '';
      if (!rule) return selector;
      let ret = selector + '{';
      for (const i in rule) {
        ret += unCamelCase(i) + ':' + rule[i] + ';';
      }
      ret += '}';
      return ret;
    }
    class Style extends Element {
      constructor(node) {
        let attrs = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : node;
        super(nodeOrNew('style', node), attrs);
      }
      addText() {
        let w = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
        this.node.textContent += w;
        return this;
      }
      font(name, src) {
        let params = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
        return this.rule('@font-face', {
          fontFamily: name,
          src: src,
          ...params
        });
      }
      rule(selector, obj) {
        return this.addText(cssRule(selector, obj));
      }
    }
    registerMethods('Dom', {
      style(selector, obj) {
        return this.put(new Style()).rule(selector, obj);
      },
      fontface(name, src, params) {
        return this.put(new Style()).font(name, src, params);
      }
    });
    register(Style, 'Style');
    class TextPath extends Text {
      // Initialize node
      constructor(node) {
        let attrs = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : node;
        super(nodeOrNew('textPath', node), attrs);
      } // return the array of the path track element

      array() {
        const track = this.track();
        return track ? track.array() : null;
      } // Plot path if any

      plot(d) {
        const track = this.track();
        let pathArray = null;
        if (track) {
          pathArray = track.plot(d);
        }
        return d == null ? pathArray : this;
      } // Get the path element

      track() {
        return this.reference('href');
      }
    }
    registerMethods({
      Container: {
        textPath: wrapWithAttrCheck(function (text, path) {
          // Convert text to instance if needed
          if (!(text instanceof Text)) {
            text = this.text(text);
          }
          return text.path(path);
        })
      },
      Text: {
        // Create path for text to run on
        path: wrapWithAttrCheck(function (track) {
          let importNodes = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
          const textPath = new TextPath(); // if track is a path, reuse it

          if (!(track instanceof Path)) {
            // create path element
            track = this.defs().path(track);
          } // link textPath to path and add content

          textPath.attr('href', '#' + track, xlink); // Transplant all nodes from text to textPath

          let node;
          if (importNodes) {
            while (node = this.node.firstChild) {
              textPath.node.appendChild(node);
            }
          } // add textPath element as child node and return textPath

          return this.put(textPath);
        }),
        // Get the textPath children
        textPath() {
          return this.findOne('textPath');
        }
      },
      Path: {
        // creates a textPath from this path
        text: wrapWithAttrCheck(function (text) {
          // Convert text to instance if needed
          if (!(text instanceof Text)) {
            text = new Text().addTo(this.parent()).text(text);
          } // Create textPath from text and path and return

          return text.path(this);
        }),
        targets() {
          return baseFind('svg textPath').filter(node => {
            return (node.attr('href') || '').includes(this.id());
          }); // Does not work in IE11. Use when IE support is dropped
          // return baseFind('svg textPath[*|href*="' + this.id() + '"]')
        }
      }
    });

    TextPath.prototype.MorphArray = PathArray;
    register(TextPath, 'TextPath');
    class Use extends Shape {
      constructor(node) {
        let attrs = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : node;
        super(nodeOrNew('use', node), attrs);
      } // Use element as a reference

      use(element, file) {
        // Set lined element
        return this.attr('href', (file || '') + '#' + element, xlink);
      }
    }
    registerMethods({
      Container: {
        // Create a use element
        use: wrapWithAttrCheck(function (element, file) {
          return this.put(new Use()).use(element, file);
        })
      }
    });
    register(Use, 'Use');

    /* Optional Modules */
    const SVG = makeInstance;
    extend([Svg, Symbol$1, Image, Pattern, Marker], getMethodsFor('viewbox'));
    extend([Line, Polyline, Polygon, Path], getMethodsFor('marker'));
    extend(Text, getMethodsFor('Text'));
    extend(Path, getMethodsFor('Path'));
    extend(Defs, getMethodsFor('Defs'));
    extend([Text, Tspan], getMethodsFor('Tspan'));
    extend([Rect, Ellipse, Gradient, Runner], getMethodsFor('radius'));
    extend(EventTarget, getMethodsFor('EventTarget'));
    extend(Dom, getMethodsFor('Dom'));
    extend(Element, getMethodsFor('Element'));
    extend(Shape, getMethodsFor('Shape'));
    extend([Container, Fragment], getMethodsFor('Container'));
    extend(Gradient, getMethodsFor('Gradient'));
    extend(Runner, getMethodsFor('Runner'));
    List.extend(getMethodNames());
    registerMorphableType([SVGNumber, Color, Box, Matrix, SVGArray, PointArray, PathArray]);
    makeMorphable();

    function getPaper() {
      // eslint-disable-next-line new-cap
      return SVG();
    }

    function appendResiduesPosition(data) {
      let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      const residues = data.residues;
      const {
        leftRightBorders = 20,
        spaceBetweenResidues = 20,
        width = 600
      } = options;
      let xPos = leftRightBorders;
      let xOld = xPos;
      let line = 0;
      // we create a temporary paper in order to get the width of the text blocs

      const paper = getPaper();
      for (let i = 0; i < residues.all.length; i++) {
        let residue = residues.all[i];
        let textWidth = getTextWidth(paper, residue.label, options);
        xPos += textWidth;
        if (xPos > width - leftRightBorders) {
          xOld = leftRightBorders;
          xPos = leftRightBorders + textWidth;
          line++;
        }
        setPaper(residue, xOld, xPos, line);
        xPos += spaceBetweenResidues;
        xOld = xPos;
      }
      residues.nbLines = line;
    }
    function setPaper(residue, xFrom, xTo, line) {
      residue.paper = {
        xFrom,
        xTo,
        line,
        usedSlots: [],
        topPosition: 0,
        bottomPosition: 0
      };
    }
    function getTextWidth(paper, label) {
      let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
      const {
        labelFontFamily = 'Verdana',
        labelSize = 12
      } = options;
      let text = paper.text(label);
      text.font({
        family: labelFontFamily,
        size: labelSize,
        weight: 'bold',
        fill: '#888'
      });
      let textWidth = text.length() || text.bbox().width;
      text.remove();
      return textWidth;
    }

    function appendResults(data, analysisResult) {
      let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
      const numberResidues = data.residues.residues.length;
      const {
        merge = {},
        filter = {}
      } = options;
      let results = JSON.parse(JSON.stringify(analysisResult));
      results = results.filter(result => !result.type.match(/^-B[0-9]$/));
      // we calculate all the lines based on the results
      for (let result of results) {
        let parts = result.type.split(/:|(?=[a-z])/); // we may have ':' but not mandatory
        if (parts.length === 2) {
          result.internal = true;
          if (parts[1].match(/^[abcd][1-9]/)) {
            [parts[0], parts[1]] = [parts[1], parts[0]];
          }
          result.to = getNumber(parts[0]) - 1;
          result.from = numberResidues - getNumber(parts[1]);
        } else {
          if (parts[0].match(/^[abcd][1-9]/)) {
            result.fromBegin = true;
            result.position = getNumber(parts[0]) - 1;
          }
          if (parts[0].match(/^[wxyz][1-9]/)) {
            result.fromEnd = true;
            result.position = numberResidues - 1 - getNumber(parts[0]);
          }
        }
        if (result.fromEnd) result.color = 'red';
        if (result.fromBegin) result.color = 'blue';
        if (result.internal) {
          switch (result.type.substring(0, 1)) {
            case 'a':
              result.color = 'green';
              break;
            case 'b':
              result.color = 'orange';
              break;
            case 'c':
              result.color = 'cyan';
              break;
            default:
              result.color = 'green';
          }
        }
      }
      if (merge.charge) {
        const unique = {};
        for (let result of results) {
          if (!unique[result.type]) {
            unique[result.type] = [];
          }
          unique[result.type].push(result);
        }
        results = [];
        for (let key in unique) {
          let current = unique[key][0];
          current.similarity = unique[key].reduce((previous, item) => previous + item.similarity, 0);
          current.similarity = current.similarity / unique[key].length;
          results.push(current);
          current.charge = '';
        }
      }
      for (let result of results) {
        if (result.similarity > 0.95) {
          result.textColor = 'black';
        } else if (result.similarity > 0.9) {
          result.textColor = '#333';
        } else if (result.similariy > 0.8) {
          result.textColor = '#666';
        } else {
          result.textColor = '#999';
        }
      }
      results = filterResults(results, filter);

      // sort by residue length
      results.sort((a, b) => a.length - b.length);
      data.results = results;
    }
    function getNumber(text) {
      return Number(text.replace(/^.([0-9]+).*$/, '$1'));
    }
    function filterResults(results, filter) {
      if (!filter) return;
      let {
        minRelativeQuantity = 0,
        minSimilarity = 0,
        minQuantity = 0,
        showInternals = true
      } = filter;
      if (minRelativeQuantity) {
        minQuantity = Math.max(...results.map(entry => entry.quantity)) * minRelativeQuantity;
      }
      if (minSimilarity) {
        results = results.filter(result => !result.similarity || result.similarity >= minSimilarity);
      }
      if (minQuantity) {
        results = results.filter(result => !result.quantity || result.quantity >= minQuantity);
      }
      if (!showInternals) {
        results = results.filter(result => !result.internal);
      }
      return results;
    }

    function appendRows(data) {
      let allResidues = data.residues.all.sort((a, b) => a.line - b.line);
      data.rows = [];
      for (let residue of allResidues) {
        let line = residue.paper.line;
        if (!data.rows[line]) {
          data.rows[line] = {
            residues: []
          };
        }
        data.rows[line].residues.push(residue);
      }
      for (let row of data.rows) {
        row.info = {};
      }
    }

    /* eslint-disable no-loop-func */

    /**
     * For each row we calculate internals, label over and label under
     * @param {*} data
     */
    function appendRowsInformation(data) {
      for (let row of data.rows) {
        let filtered = row.residues.filter(entry => entry.fromBegin !== undefined);
        if (filtered.length > 0) {
          row.info.firstResidue = filtered[0].fromBegin;
          row.info.lastResidue = filtered[filtered.length - 1].fromBegin;
        }
        row.internals = [];
      }
      for (let result of data.results) {
        if (result.internal) {
          let fromResidue = data.residues.residues[result.from];
          let from = fromResidue.fromBegin;
          let toResidue = data.residues.residues[result.to];
          let to = toResidue.fromBegin;
          for (let row of data.rows) {
            if (from <= row.info.lastResidue && to >= row.info.firstResidue) {
              result = JSON.parse(JSON.stringify(result));
              result.fromResidue = fromResidue;
              if (from < row.info.firstResidue) {
                result.firstIndex = true;
              } else {
                row.residues.forEach((residue, index) => {
                  if (residue.fromBegin === from) {
                    result.firstIndex = index;
                  }
                });
              }
              result.toResidue = toResidue;
              if (to > row.info.lastResidue) {
                result.lastIndex = true;
              } else {
                row.residues.forEach((residue, index) => {
                  if (residue.fromBegin === to) {
                    result.lastIndex = index;
                  }
                });
              }
              row.internals.push(result);
            }
          }
        } else if (result.position !== undefined && data.residues.residues[result.position]) {
          let residue = data.residues.residues[result.position];
          if (result.fromEnd) {
            residue.info.nbOver++;
            residue.results.end.push(result);
          }
          if (result.fromBegin) {
            residue.info.nbUnder++;
            residue.results.begin.push(result);
          }
        }
      }
      for (let row of data.rows) {
        let maxNbOver = 0;
        let maxNbUnder = 0;
        for (let residue of row.residues) {
          if (residue.info.nbOver > maxNbOver) maxNbOver = residue.info.nbOver;
          if (residue.info.nbUnder > maxNbUnder) maxNbUnder = residue.info.nbUnder;
        }
        row.info.nbOver = maxNbOver;
        row.info.nbUnder = maxNbUnder;
      }
    }

    function addCSS(paper) {
      let cssCode = `
    .highlight {
        stroke: yellow;
        stroke-width: 5px;
        fill: red;
    }
    .highlightText {
        fill: red;
    }
    `;
      let style = paper.element('style');
      style.words(cssCode);
    }

    function addScript(paper) {
      let scriptCode = ` // <![CDATA[
        function mouseOver(evt) {
           
            let targetRange=evt.target.id.replace(/^line/,'');
            let from=targetRange.replace(/-.*/,'')*1;
            let to=targetRange.replace(/.*-/,'')*1;
            let children=evt.target.parentNode.children;
            for (let child of children) {
                if (child.id === evt.target.id) {
                    child.setAttribute('class','highlight');
                }
                if (child.nodeName === 'text' && child.id.startsWith("residue")) {
                    let residueNumber=child.id.replace(/residue-/,'')*1;
                    if (residueNumber>=from && residueNumber<=to) {
                        child.setAttribute('class','highlightText');
                    }
                }
            }
        }
        function mouseOut(evt) {
            let children=evt.target.parentNode.children;
            for (let child of children) {
                if (child.id === evt.target.id) {
                    child.setAttribute('class','');
                }
                if (child.nodeName === 'text' && child.id.startsWith("residue")) {
                    child.setAttribute('class','');
                }
            }
        }
     // ]]>
    `;
      let script = paper.element('script');
      script.attr({
        type: 'application/ecmascript'
      });
      script.words(scriptCode);
    }

    function drawLabel(paper, result, x, y, options) {
      let label = result.type;
      let similarity = String(Math.round(result.similarity * 100));
      let charge = result.charge > 0 ? `+${result.charge}` : result.charge;
      let text = paper.plain(label);
      text.font({
        fill: result.textColor,
        family: options.labelFontFamily,
        weight: 'bold',
        size: options.labelSize,
        anchor: 'end'
      });
      text.attr({
        x,
        y
      });
      let textWidth = 0;
      text = paper.plain(charge);
      text.font({
        fill: result.textColor,
        family: options.labelFontFamily,
        size: options.labelSize / 2
      });
      text.attr({
        x: x + textWidth,
        y: y - options.labelSize / 2
      });
      text = paper.plain(similarity);
      text.font({
        fill: result.textColor,
        family: options.labelFontFamily,
        size: options.labelSize / 2
      });
      text.attr({
        x: x + textWidth,
        y
      });
    }

    function drawInternals(paper, row, options) {
      let fromX = 0;
      let toX = 0;
      for (const internal of row.internals) {
        options.verticalPosition += options.spaceBetweenInternalLines;
        if (internal.firstIndex === true) {
          fromX = 0;
        } else {
          fromX = internal.fromResidue.paper.xFrom - options.spaceBetweenResidues / 2;
        }
        if (internal.lastIndex === true) {
          toX = options.width - 1;
        } else {
          toX = internal.toResidue.paper.xTo + options.spaceBetweenResidues / 2;
        }
        let y = options.verticalPosition;
        let drawLine = paper.line(fromX, y, toX, y);
        drawLine.attr({
          onmouseover: 'mouseOver(evt)',
          onmouseout: 'mouseOut(evt)',
          id: `line${internal.fromResidue.fromBegin}-${internal.toResidue.fromBegin}`
        });
        drawLine.stroke({
          color: internal.color,
          width: options.strokeWidth
        });
        let center = (fromX + toX + internal.type.length * options.labelSize * 2 / 3) / 2;
        drawLabel(paper, internal, center, y - 2, options);
      }
      options.verticalPosition += options.spaceBetweenInternalLines * 2;
    }

    function drawReplacements(paper, data, options) {
      let replacements = data.residues.replacements;
      replacements = Object.keys(replacements).map(key => {
        return {
          key,
          ...replacements[key]
        };
      });
      for (let replacement of replacements) {
        options.verticalPosition += options.spaceBetweenInternalLines;
        let text = paper.plain(`${replacement.label} = ${replacement.key}`);
        text.font({
          fill: 'darkviolet',
          family: options.labelFontFamily,
          weight: 'bold',
          size: 10
        });
        text.attr({
          x: options.leftRightBorders,
          y: options.verticalPosition
        });
      }
      options.verticalPosition += options.spaceBetweenInternalLines;
    }

    function drawTerminals(paper, row, options) {
      for (let residue of row.residues) {
        if (!residue.results) continue;
        for (let result of residue.results.begin) {
          let line = paper.line(residue.paper.xTo + options.spaceBetweenResidues / 2, residue.paper.y, residue.paper.xTo + options.spaceBetweenResidues / 2, residue.paper.y - 8);
          line.stroke({
            color: result.color,
            width: options.strokeWidth,
            linecap: 'round'
          });
          line = paper.line(residue.paper.xTo + options.spaceBetweenResidues / 2, residue.paper.y, residue.paper.xTo + options.spaceBetweenResidues / 2 - 5, residue.paper.y + 5);
          line.stroke({
            color: result.color,
            width: options.strokeWidth,
            linecap: 'round'
          });
          drawLabel(paper, result, residue.paper.xTo + options.spaceBetweenResidues / 2, residue.paper.y + options.labelSize + 6 + residue.paper.bottomPosition * options.labelSize, options);
          residue.paper.bottomPosition++;
        }
        for (let result of residue.results.end) {
          let line = paper.line(residue.paper.xTo + options.spaceBetweenResidues / 2 + options.strokeWidth, residue.paper.y, residue.paper.xTo + options.spaceBetweenResidues / 2 + options.strokeWidth, residue.paper.y - 8);
          line.stroke({
            color: result.color,
            width: options.strokeWidth,
            linecap: 'round'
          });
          line = paper.line(residue.paper.xTo + options.spaceBetweenResidues / 2 + options.strokeWidth, residue.paper.y - 8, residue.paper.xTo + options.spaceBetweenResidues / 2 + 5 + options.strokeWidth, residue.paper.y - 13);
          line.stroke({
            color: result.color,
            width: options.strokeWidth,
            linecap: 'round'
          });
          drawLabel(paper, result, residue.paper.xTo + options.spaceBetweenResidues, residue.paper.y - 17 - residue.paper.topPosition * options.labelSize, options);
          residue.paper.topPosition++;
        }
      }
    }

    function drawSequence(paper, row, options) {
      // need to plan some space for the OVER
      options.verticalPosition += row.info.nbOver * (options.labelSize + 1);
      for (const residue of row.residues) {
        residue.paper.y = options.verticalPosition;
        let text = paper.plain(residue.label);
        let textColor = residue.replaced ? 'darkviolet' : residue.kind === 'residue' ? '#555' : '#CCC';
        text.font({
          family: options.labelFontFamily,
          size: 12,
          weight: 'bold',
          fill: textColor
        });
        text.attr({
          x: residue.paper.xFrom,
          y: residue.paper.y
        });
        text.attr({
          id: `residue-${residue.fromBegin}`
        });
      }
      drawTerminals(paper, row, options);

      // need to plan some space for the UNDER
      options.verticalPosition += row.info.nbUnder * (options.labelSize + 1);
      options.verticalPosition += options.spaceBetweenInternalLines * 2;
    }

    /**
     *
     * @param {string} sequence
     * @param {array} analysisResults
     * @param {object} [options={}]
     * @param {number} [options.leftRightBorders=50]
     * @param {number} [options.width=600]
     * @param {number} [options.spaceBetweenResidues=30]
     * @param {number} [options.spaceBetweenInternalLines=12]
     * @param {number} [options.strokeWidth=2]
     * @param {string} [options.labelFontFamily='Verdana']
     * @param {number} [options.labelSize=8]
     * @param {number} [options.parsing] Sequence parsing options
     * @param {object} [options.merge={}]
     * @param {boolean} [options.merge.charge] Merge results if only differs by charge
     * @param {object} [options.filter={}] define some filters
     * @param {number} [options.filter.minSimilarity=0]  minimal similarity
     * @param {number} [options.filter.minQuantity=0]  minimal quantity
     * @param {number} [options.filter.minRelativeQuantity=0]  minimal relative quantity. This value should be between 0 and 1 and supersede minQuantity.
     * @param {boolean} [options.filter.showInternals=true] show the internal fragments
     *
     */
    function sequenceSVG(sequence, analysisResults) {
      let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
      const {
        width = 600,
        leftRightBorders = 50,
        spaceBetweenResidues = 30,
        spaceBetweenInternalLines = 12,
        strokeWidth = 2,
        labelFontFamily = 'Verdana',
        labelSize = 8,
        parsing,
        merge,
        filter
      } = options;
      const drawOptions = {
        spaceBetweenResidues,
        leftRightBorders,
        spaceBetweenInternalLines,
        strokeWidth,
        labelSize,
        labelFontFamily,
        verticalPosition: spaceBetweenInternalLines,
        width
      };
      let data = {};
      appendResidues(data, sequence, parsing);
      appendResults(data, analysisResults, {
        merge,
        filter
      });
      appendResiduesPosition(data, {
        leftRightBorders,
        spaceBetweenResidues,
        labelFontFamily,
        labelSize,
        width
      });
      appendRows(data);
      appendInternals(data);
      appendRowsInformation(data);

      // We start to create the SVG and create the paper
      const paper = getPaper();
      addCSS(paper);
      addScript(paper);
      for (let row of data.rows) {
        drawInternals(paper, row, drawOptions);
        drawSequence(paper, row, drawOptions);
      }
      drawReplacements(paper, data, drawOptions);
      paper.size(width, drawOptions.verticalPosition);
      let svg = paper.svg();
      paper.clear();
      return svg;
    }

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

    function preprocessEARanges(ranges, targetEA) {
      ranges = JSON.parse(JSON.stringify(ranges));
      if (typeof ranges === 'string') {
        // need to convert to ranges
        let parsed = parse(ranges.replace(/[\r\n\t ]/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
        // 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 Kind.ATOM:
              if (parenthesisLevel === 0 && currentMF) {
                current.mf += currentMF;
                currentMF = '';
              }
              currentMF += item.value;
              break;
            case Kind.ISOTOPE:
              if (parenthesisLevel === 0 && currentMF) {
                current.mf += currentMF;
                currentMF = '';
              }
              currentMF += `[${item.value.isotope}${item.value.atom}]`;
              break;
            case Kind.MULTIPLIER:
              if (parenthesisLevel === 0 && currentMF) {
                current.mf += currentMF + item.value;
                currentMF = '';
              } else {
                currentMF += item.value;
              }
              break;
            case 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 Kind.OPENING_PARENTHESIS:
              parenthesisLevel++;
              currentMF += '(';
              break;
            case Kind.CLOSING_PARENTHESIS:
              parenthesisLevel--;
              currentMF += ')';
              break;
            default:
              throw 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,
          minCount: min,
          // value defined by the user
          maxCount: max,
          // value defined by the user
          targetEA: targetEA[range.mf],
          mw: 0,
          // mw till this level
          aw: 0,
          // atomic weight (mass * currentCount)
          maxRatio: 0,
          // maximum ratio possible if all next elements are 0
          currentValue: 0,
          currentCount: min - 1,
          currentUnsaturation: 0,
          initialOrder: i
        };
        possibilities.push(possibility);
        let info = new MF(range.mf).getInfo();
        possibility.mass = info.mass;
        possibility.unsaturation = range.unsaturation === undefined ? (info.unsaturation - 1) * 2 : range.unsaturation;
        if (possibility.mf !== info.mf) possibility.isGroup = true;
      }
      possibilities = possibilities.filter(r => r.minCount !== 0 || r.maxCount !== 0);
      possibilities.sort((a, b) => {
        return b.targetEA - a.targetEA;
      });
      return possibilities;
    }

    /**
     * Returns possible combinations
     * @param {object} [targetEA]
     * @param {object} [options={}]
     * @param {string} [options.ranges='C0-100 H0-100 O0-100 N0-100'] - range of mfs to search
     * @param {number} [options.maxElementError=0.003]
     * @param {number} [options.maxTotalError=0.01]
     * @param {number} [options.minMW=0] minimal molecular weight
     * @param {number} [options.maxMW=+Infinity] maximal molecular weight
     * @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
     * @return {Array<object>}
     */

    function mfFromEA(targetEA) {
      let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      const {
        unsaturation = {},
        maxIterations = 1e8,
        minMW = 0,
        maxMW = +Infinity,
        ranges = [{
          mf: 'C',
          min: 0,
          max: 100
        }, {
          mf: 'H',
          min: 0,
          max: 100
        }, {
          mf: 'O',
          min: 0,
          max: 100
        }, {
          mf: 'N',
          min: 0,
          max: 100
        }],
        maxElementError = 0.003,
        maxTotalError = 0.01
      } = options;
      let filterUnsaturation = !!unsaturation;
      // we calculate not the real unsaturation but the one before dividing by 2 + 1
      let fakeMinUnsaturation = unsaturation.min === undefined ? Number.MIN_SAFE_INTEGER : (unsaturation.min - 1) * 2;
      let fakeMaxUnsaturation = unsaturation.max === undefined ? Number.MAX_SAFE_INTEGER : (unsaturation.max - 1) * 2;
      let results = {
        mfs: [],
        info: {
          numberMFEvaluated: 0,
          numberResults: 0
        }
      };
      let orderMapping = []; // used to sort the atoms

      let possibilities = preprocessEARanges(ranges, targetEA);
      orderMapping = getOrderMapping(possibilities);
      if (possibilities.length === 0) return {
        mfs: []
      };
      let currentPosition = 0;
      let currentAtom;

      //  if (DEBUG) console.log('possibilities', possibilities.map((a) => `${a.mf + a.originalMinCount}-${a.originalMaxCount}`));

      mfWhile: while (true) {
        while (currentPosition < possibilities.length && currentPosition >= 0) {
          let previousAtom = currentPosition === 0 ? {
            mw: 0
          } : possibilities[currentPosition - 1];
          currentAtom = possibilities[currentPosition];
          if (currentAtom.currentCount < currentAtom.maxCount) {
            currentAtom.currentCount++;
            currentAtom.aw = currentAtom.mass * currentAtom.currentCount;
            currentAtom.mw = currentAtom.aw + previousAtom.mw;
            currentAtom.maxRatio = currentAtom.aw / currentAtom.mw;
            // we should check if we can reach the target
            if (currentAtom.targetEA && currentAtom.aw / currentAtom.mw - currentAtom.targetEA < -maxElementError) {
              // we already don't have enough quantity of this element and it can only become worse
              continue;
            }
            if (currentPosition < possibilities.length - 1) {
              currentPosition++;
            } else {
              break;
            }
          } else {
            currentAtom.currentCount = currentAtom.minCount - 1;
            currentPosition--;
          }
        }
        if (currentPosition < 0) {
          break;
        }
        if (results.info.numberMFEvaluated++ > maxIterations) {
          throw Error(`Iteration number is over the current maximum of: ${maxIterations}`);
        }
        if (filterUnsaturation) {
          let unsaturationValue = 0;
          for (const possibility of possibilities) {
            unsaturationValue += possibility.unsaturation * possibility.currentCount;
          }
          let isOdd = Math.abs(unsaturationValue % 2);
          if (unsaturation.onlyInteger && isOdd === 1 || unsaturation.onlyNonInteger && isOdd === 0 || fakeMinUnsaturation > unsaturationValue || fakeMaxUnsaturation < unsaturationValue) {
            continue;
          }
        }
        let mw = currentAtom.mw;
        if (mw < minMW || mw > maxMW) continue;
        let totalError = 0;
        for (let i = 0; i < possibilities.length; i++) {
          const possibility = possibilities[i];
          let ratio = possibility.mass * possibility.currentCount / mw;
          if (possibility.targetEA !== undefined) {
            let error = Math.abs(possibility.targetEA - ratio);
            if (error > maxElementError) {
              continue mfWhile;
            }
            totalError += error;
          }
          possibility.currentValue = ratio;
        }
        if (isNaN(totalError) || totalError > maxTotalError) continue;
        results.mfs.push(getResult(possibilities, totalError, orderMapping));
        results.info.numberResults++;
      }
      results.mfs.sort((a, b) => Math.abs(a.totalError) - Math.abs(b.totalError));
      return results;
    }
    function getResult(possibilities, totalError, orderMapping) {
      const result = {
        mf: '',
        totalError
      };
      // we check that the first time we meet the ionization group it does not end
      // in the final result
      for (let i = 0; i < possibilities.length; i++) {
        let possibility = possibilities[orderMapping[i]];
        if (possibility.currentCount !== 0) {
          if (possibility.isGroup) {
            result.mf += `(${possibility.mf})`;
            if (possibility.currentCount !== 1) {
              result.mf += possibility.currentCount;
            }
          } else {
            result.mf += possibility.mf;
            if (possibility.currentCount !== 1) {
              result.mf += possibility.currentCount;
            }
          }
        }
        result.ea = possibilities.map(current => ({
          mf: current.mf,
          value: current.currentValue,
          expected: current.targetEA,
          error: current.targetEA === undefined ? undefined : Math.abs(current.targetEA - current.currentValue)
        }));
      }
      return result;
    }
    function getOrderMapping(possibilities) {
      let mapping = possibilities.map((p, i) => ({
        atom: p.mf,
        index: i
      }));
      mapping.sort((a, b) => {
        return atomSorter$1(a.atom, b.atom);
      });
      return mapping.map(a => a.index);
    }

    function appendAllDBRefs(object, allDBRefs) {
      if (Array.isArray(object)) {
        for (let item of object) {
          appendAllDBRefs(item, allDBRefs);
        }
      } else if (typeof object === 'object' && object !== null) {
        if (object.$ref && object.$id) {
          allDBRefs.push(object);
        }
        for (let key of Object.keys(object)) {
          appendAllDBRefs(object[key], allDBRefs);
        }
      }
    }

    async function appendURLs(object) {
      let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      const {
        collections,
        force = false
      } = options;
      let allDBRefs = [];
      appendAllDBRefs(object, allDBRefs);
      if (collections) {
        allDBRefs = allDBRefs.filter(dbRef => collections.includes(dbRef.$ref));
      }
      if (!force) {
        allDBRefs = allDBRefs.filter(dbRef => !dbRef.url);
      }
      const unknowns = {};
      for (const entry of allDBRefs) {
        switch (entry.$ref) {
          case 'compounds':
            entry.url = `https://pubchem.ncbi.nlm.nih.gov/compound/${entry.$id}`;
            break;
          case 'pubmeds':
            entry.url = `https://pubmed.ncbi.nlm.nih.gov/${entry.$id}`;
            break;
          case 'gnps':
            entry.url = `https://gnps.ucsd.edu/ProteoSAFe/gnpslibraryspectrum.jsp?SpectrumID=${entry.$id}`;
            break;
          case 'patents':
            entry.url = `https://pubchem.ncbi.nlm.nih.gov/patent/${entry.$id}`;
            break;
          case 'bioassays':
            entry.url = `https://pubchem.ncbi.nlm.nih.gov/bioassay/${entry.$id.replace(/.*_/, '')}`;
            break;
          case 'npasses':
            entry.url = `http://bidd.group/NPASS/compound.php?compoundID=${entry.$id}`;
            break;
          case 'cmaups':
            entry.url = `http://bidd.group/CMAUP/ingredient.php?ingredient=${entry.$id}`;
            break;
          case 'coconuts':
            entry.url = `https://coconut.naturalproducts.net/compound/coconut_id/${entry.$id}`;
            break;
          case 'lotuses':
            entry.url = `https://lotus.naturalproducts.net/compound/lotus_id/${entry.$id}`;
            break;
          case 'npAtlases':
            entry.url = `https://www.npatlas.org/explore/compounds/${entry.$id}`;
            break;
          case 'massBank':
            entry.url = `https://massbank.eu/MassBank/RecordDisplay?id=${entry.$id}`;
            break;
          default:
            unknowns[entry.$ref] = true;
            break;
        }
      }
      if (Object.keys(unknowns).length) {
        // eslint-disable-next-line no-console
        console.error('Unknown url to original data for the following collections: ', unknowns);
      }
    }

    async function fetchJSON(url, data) {
      if (data) {
        const searchParams = new URLSearchParams(data);
        const result = await fetch(`${url}?${searchParams.toString()}`);
        return result.json();
      } else {
        const result = await fetch(url);
        return result.json();
      }
    }

    async function postFetchJSON(url) {
      let data = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      const formData = new FormData();
      for (const name in data) {
        formData.append(name, data[name]);
      }
      const result = await fetch(url, {
        method: 'POST',
        body: formData
      });
      return result.json();
    }

    /**
     * Load the DBrefs and create a new property `data` for each DBRef
     * @param {any} object
     * @param {object} [options={}]
     * @param {string[]|undefined} [options.collections] - List of collections to include
     * @param {boolean} [options.force=false] - Force the inclusion of the data even if it is already present
     * @param {string} [options.baseURL='https://octochemdb.cheminfo.org/'] - URL of the webservice
     */
    async function includeDBRefs(object) {
      let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      const {
        collections,
        force = false,
        baseURL = 'https://octochemdb.cheminfo.org/'
      } = options;
      let allDBRefs = [];
      appendAllDBRefs(object, allDBRefs);
      if (collections) {
        allDBRefs = allDBRefs.filter(dbRef => collections.includes(dbRef.$ref));
      }
      if (!force) {
        allDBRefs = allDBRefs.filter(dbRef => !dbRef.data);
      }
      const groups = groupsByCollection(allDBRefs);
      const promises = [];
      for (const [collection, entries] of Object.entries(groups)) {
        promises.push(addDataForOneCollection(collection, baseURL, entries));
      }
      await Promise.allSettled(promises);
    }
    async function addDataForOneCollection(collection, baseURL, entries) {
      const url = new URL(`${collection}/v1/ids`, baseURL).toString();
      const searchParams = {};
      searchParams.ids = entries.map(entry => entry.$id).join(',');
      const data = {};
      const result = await postFetchJSON(url, searchParams);
      if (!result.data) {
        throw new Error(`No data for ${url}`);
      }
      result.data.forEach(entry => {
        data[entry._id] = entry.data;
      });
      for (let datum of entries) {
        datum.data = data[datum.$id];
      }
    }
    function groupsByCollection(allDBRefs) {
      const groups = {};
      for (let dbRef of allDBRefs) {
        if (!groups[dbRef.$ref]) groups[dbRef.$ref] = [];
        groups[dbRef.$ref].push(dbRef);
      }
      return groups;
    }

    /**
     * @description This function normalizes the activities of an entry of ActivesOrNaturals collection.
     * @param {Object} activeOrNatural - an entry of ActivesOrNaturals collection
     * @returns - ActiveOrNatural entry with normalized activities
     */
    function normalizeActivities(activeOrNatural) {
      activeOrNatural = {
        ...activeOrNatural
      };
      let activities = [];
      for (let activity of activeOrNatural.data.activities) {
        if (activity?.data?.activities !== undefined) {
          for (let activityEntry of activity.data.activities) {
            let normalizedActivity = {
              $ref: activity.$ref,
              $id: activity.$id,
              data: {
                assay: activityEntry.assay,
                ocl: activity.data.ocl
              },
              url: activity.url
            };
            if (activityEntry?.targetTaxonomies !== undefined) {
              normalizedActivity.data.targetTaxonomies = [activityEntry.targetTaxonomies];
            }
            activities.push(normalizedActivity);
          }
        } else {
          activities.push(activity);
        }
      }
      activeOrNatural.data.activities = activities;
      return activeOrNatural;
    }

    /**
     * Search for a specific natural or active compound using its ID
     * @param {object} [options={}]
     * @param {string} [options.fields='_id,data'] - List of fields to retrieve
     * @param {string} [options.url='activesOrNaturals/v1/id'] - URL of the webservice
     * @param {string} [options.baseURL='https://octochemdb.cheminfo.org/'] - URL of the webservice
     */

    async function activeOrNaturalDetails(id) {
      let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      const {
        url = 'activesOrNaturals/v1/id',
        baseURL = 'https://octochemdb.cheminfo.org/',
        fields = '_id,data'
      } = options;
      const activeOrNatural = await fetchActiveOrNatural(id, {
        url: new URL(url, baseURL).toString(),
        fields
      });
      await includeDBRefs(activeOrNatural, {
        baseURL
      });
      ensureArray(activeOrNatural);
      appendURLs(activeOrNatural);
      normalizeActivities(activeOrNatural);
      return activeOrNatural;
    }
    function ensureArray(activeOrNatural) {
      if (!activeOrNatural.data.activities) {
        activeOrNatural.data.activities = [];
      }
      if (!activeOrNatural.data.pubmeds) {
        activeOrNatural.data.pubmeds = [];
      }
      if (!activeOrNatural.data.patents) {
        activeOrNatural.data.patents = [];
      }
      if (!activeOrNatural.data.taxonomies) {
        activeOrNatural.data.taxonomies = [];
      }
    }
    async function fetchActiveOrNatural(id, options) {
      const {
        fields,
        url
      } = options;
      const searchParams = {};
      searchParams.id = id;
      if (fields) {
        searchParams.fields = fields;
      }
      return (await fetchJSON(url, searchParams)).data;
    }

    function parseMasses(masses) {
      if (!masses) return [];
      if (Array.isArray(masses)) return masses;
      if (typeof masses === 'number') {
        return [masses];
      }
      if (typeof masses === 'string') {
        return masses.split(/[\r\n\t,; ]+/).filter(value => value).map(Number);
      }
      throw new Error('Cannot parse masses');
    }

    const ELECTRON_MASS = 5.4857990907e-4;

    async function getAllowedEMs(options) {
      const {
        ranges,
        masses,
        precision,
        ionizations
      } = options;
      if (!ranges) return;
      const allowedEMs = [];
      for (let mass of masses) {
        (await findMFs(mass, {
          ionizations,
          precision,
          ranges,
          limit: 100000
        })).mfs.forEach(mf => allowedEMs.push(mf.em));
      }
      return Float64Array.from(allowedEMs).sort();
    }

    async function searchWithIonizations(options) {
      const {
        realURL,
        fields,
        precision = 100,
        limit = 1000,
        searchParams,
        ranges
      } = options;
      searchParams.precision = String(precision);
      searchParams.limit = String(limit);
      if (fields) searchParams.fields = fields;
      let ionizations = preprocessIonizations(options.ionizations);
      const masses = parseMasses(options.masses || [0]);

      // if we have ranges we need to filter the allowed EMs
      const allowedEMs = await getAllowedEMs({
        ranges,
        masses,
        precision,
        ionizations
      });
      const promises = [];
      for (let ionization of ionizations) {
        for (let mass of masses) {
          if (mass !== 0) {
            const realMass = mass * Math.abs(ionization.charge || 1) - ionization.em + ELECTRON_MASS * ionization.charge;
            searchParams.em = String(realMass);
          }
          promises.push(fetchJSON(realURL, searchParams));
        }
      }
      const results = await Promise.all(promises);
      const entries = [];
      let counter = 0;
      for (const result of results) {
        const ionization = ionizations[Math.floor(counter / masses.length)];
        const targetMass = masses[counter % masses.length];
        counter++;
        for (const entry of result.data) {
          if (allowedEMs && !allowedEMs.find(em => Math.abs(em - entry.data.em) < 0.0000001)) {
            continue;
          }
          try {
            entry.mfInfo = new MF(entry.data?.mf || entry._id).getInfo({
              emFieldName: 'em',
              msemFieldName: 'msem'
            });
            entry.ionization = ionization;
            entry.ms = getMsInfo(entry.mfInfo, {
              targetMass,
              ionization
            }).ms;
            entries.push(entry);
          } catch (e) {
            // eslint-disable-next-line no-console
            console.warn(`${e}`);
          }
        }
      }
      if (entries.length > limit) {
        entries.length = limit;
      }
      return entries;
    }

    /**
     * Search for natural or active compounds using various criteria
     * @param {object} [options={}]
     * @param {number|string|number[]} [options.masses] - Observed monoisotopic mass
     * @param {string} [options.ionizations=''] - string containing a comma separated list of modifications
     * @param {string} [options.ranges=''] - Allows to filter by a range of molecular formula
     * @param {string} [options.mf=''] - Search by molecular formula
     * @param {number} [options.precision=1000] - Precision of the monoisotopic mass in ppm
     * @param {string} [options.fields] - List of fields to retrieve
     * @param {string} [options.kwTaxonomies=''] - Comma separated list of taxonomies family, genus or species of the product source
     * @param {string} [options.kwActiveAgainst=''] - Comma separated list of taxonomies family, genus or species of the bioactivity target
     * @param {string} [options.kwBioassays=''] - Comma separated list of keyword from the description of the bioassay
     * @param {string} [options.kwMeshTerms=''] - Comma separated list of keyword from the medline MeshTerms
     * @param {string} [options.minNbMassSpectra] - Minimal number of mass spectra
     * @param {number} [options.limit=1000] - Maximal number of entries to return
     * @param {string} [options.url='activesOrNaturals/v1/search'] - URL of the webservice
     * @param {string[]|undefined} [options.includes] - Array of DBref collections to include
     * @param {string} [options.baseURL='https://octochemdb.cheminfo.org/'] - URL of the webservice
     */

    async function activesOrNaturals() {
      let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
      const {
        url = 'activesOrNaturals/v1/search',
        baseURL = 'https://octochemdb.cheminfo.org/',
        includes
      } = options;
      const realURL = new URL(url, baseURL).toString();
      const searchParams = prepareSearchParams(options);
      const masses = parseMasses(options.masses || [0]);
      const entries = await searchWithIonizations({
        ...options,
        masses,
        realURL,
        searchParams
      });
      if (options.includes) {
        await includeDBRefs(entries, {
          baseURL,
          collections: includes
        });
      }
      appendURLs(entries);
      entries.sort((a, b) => {
        if (a.data.nbMassSpectra > b.data.nbMassSpectra) return -1;
        if (a.data.nbMassSpectra < b.data.nbMassSpectra) return 1;
        if (a.data.nbPubmeds > b.data.nbPubmeds) return -1;
        if (a.data.nbPubmeds < b.data.nbPubmeds) return 1;
        if (a.data.nbPatents > b.data.nbPatents) return -1;
        if (a.data.nbPatents < b.data.nbPatents) return 1;
        return 0;
      });
      return entries;
    }
    function prepareSearchParams(options) {
      const {
        kwTaxonomies,
        kwActiveAgainst,
        kwBioassays,
        kwMeshTerms,
        mf,
        minNbMassSpectra
      } = options;
      const searchParams = {};
      if (kwTaxonomies) {
        searchParams.kwTaxonomies = kwTaxonomies;
      }
      if (kwActiveAgainst) {
        searchParams.kwActiveAgainst = kwActiveAgainst;
      }
      if (kwBioassays) {
        searchParams.kwBioassays = kwBioassays;
      }
      if (kwMeshTerms) {
        searchParams.kwMeshTerms = kwMeshTerms;
      }
      if (mf) {
        searchParams.mf = mf;
      }
      if (minNbMassSpectra) {
        searchParams.minNbMassSpectra = minNbMassSpectra;
      }
      return searchParams;
    }

    /**
     * Search for natural or active compounds and group them by molecular formula
     * @param {object} [options={}]
     * @param {number|string|number[]} [options.masses] - Observed monoisotopic mass
     * @param {string} [options.ionizations=''] - string containing a comma separated list of modifications
     * @param {string} [options.ranges=''] - Allows to filter by a range of molecular formula
     * @param {number} [options.precision=1000] - Precision (accuracy) of the monoisotopic mass in ppm
     * @param {string} [options.fields='data.noStereoOcl,data.em,data.mf'] - List of fields to retrieve
     * @param {string} [options.kwTaxonomies=''] - Comma separated list of taxonomies family, genus or species of the product source
     * @param {string} [options.kwActiveAgainst=''] - Comma separated list of taxonomies family, genus or species of the bioactivity target
     * @param {string} [options.kwBioassays=''] - Comma separated list of keyword from the description of the bioassay
     * @param {string} [options.kwMeshTerms=''] - Comma separated list of keyword from the medline MeshTerms
     * @param {number} [options.limit=1000] - Maximal number of entries to return
     * @param {string} [options.url='activesOrNaturals/v1/search'] - URL of the webservice
     * @param {string} [options.baseURL='https://octochemdb.cheminfo.org/'] - URL of the webservice
     */

    async function activesOrNaturalsByMF() {
      let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
      const fields = 'data.noStereoOcl,data.mf,data.em,data.nbPatents,data.nbPubmeds,data.nbActivities,data.bioactive,data.nbMassSpectra,data.naturalProduct'.split(',');
      if (options.fields) fields.push(...options.fields.split(/[ ,]+/));
      const entries = await activesOrNaturals({
        ...options,
        fields: fields.join(',')
      });

      // we will now group the data per mf
      const grouped = {};
      for (const entry of entries) {
        if (!grouped[entry.data.mf]) {
          grouped[entry.data.mf] = {
            mfInfo: entry.mfInfo,
            ms: entry.ms,
            ionization: entry.ionization,
            nbNaturals: 0,
            nbBioactives: 0,
            nbPatents: 0,
            nbPubmeds: 0,
            nbMassSpectra: 0
          };
        }
        const currentGroup = grouped[entry.data.mf];
        if (entry.data.naturalProduct) currentGroup.nbNaturals++;
        if (entry.data.bioactive) currentGroup.nbBioactives++;
        if (entry.data.nbMassSpectra) {
          currentGroup.nbMassSpectra += entry.data.nbMassSpectra;
        }
        if (entry.data.nbPubmeds) currentGroup.nbPubmeds += entry.data.nbPubmeds;
        if (entry.data.nbPatents) currentGroup.nbPatents += entry.data.nbPatents;
      }
      const groupedArray = Object.keys(grouped).map(key => grouped[key]);
      // because we can combine many ionizations we should resort the data
      groupedArray.sort((a, b) => Math.abs(a.ms.ppm) - Math.abs(b.ms.ppm));
      return groupedArray;
    }

    /**
     * @param {object} [options={}]
     * @param {string} [options.baseURL='https://octochemdb.cheminfo.org/'] - URL of the webservice
     * @param {number|string|number[]} [options.masses] - Observed fragment masses
     * @param {string} [options.mf] - Molecular formula of the non ionized molecule
     * @param {number} [options.precision=100] - Precision (accuracy) of the monoisotopic mass in ppm
     * @param {number} [options.limit=1000] - Maximal number of entries to return
     * @param {string} [options.modifications=''] - Comma
     * @param {string} [options.url='gnps/v1/fromMasses']
     *
     */

    async function gnps() {
      let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
      const {
        url = 'gnps/v1/search',
        baseURL = 'https://octochemdb.cheminfo.org/',
        precision = 100,
        limit = 1000,
        mf = ''
      } = options;
      const realURL = new URL(url, baseURL).toString();
      const masses = parseMasses(options.masses);
      const modifications = preprocessIonizations(options.modifications);
      const allResults = [];
      for (let modification of modifications) {
        const massShift = modification.em;
        const searchParams = {
          masses: masses && masses.map(mass => mass + massShift).join(','),
          precision,
          limit,
          mf
        };
        const results = (await fetchJSON(realURL, searchParams)).data;
        results.forEach(result => {
          allResults.push({
            ...result,
            modification,
            url: `https://gnps.ucsd.edu/ProteoSAFe/gnpslibraryspectrum.jsp?SpectrumID=${result._id}`
          });
        });
      }
      return allResults;
    }

    /**
     * @param {object} [options={}]
     * @param {string} [options.baseURL='https://octochemdb.cheminfo.org/'] - URL of the webservice
     * @param {number|string|number[]} [options.masses] - Observed fragment masses
     * @param {string} [options.mf] - Molecular formula of the non ionized molecule
     * @param {number} [options.precision=100] - Precision (accuracy) of the monoisotopic mass in ppm
     * @param {number} [options.limit=1000] - Maximal number of entries to return
     * @param {string} [options.modifications=''] - Comma
     * @param {string} [options.url='massBank/v1/fromMasses']
     */

    async function massBank() {
      let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
      const {
        url = 'massBank/v1/search',
        baseURL = 'https://octochemdb.cheminfo.org/',
        precision = 100,
        limit = 1000,
        mf = ''
      } = options;
      const masses = parseMasses(options.masses);
      const realURL = new URL(url, baseURL).toString();
      const modifications = preprocessIonizations(options.modifications);
      const allResults = [];
      for (let modification of modifications) {
        const massShift = modification.em;
        const searchParams = {
          masses: masses.map(mass => mass + massShift).join(','),
          precision,
          limit,
          mf
        };
        const results = (await fetchJSON(realURL, searchParams)).data;
        results.forEach(result => {
          allResults.push({
            ...result,
            modification,
            url: `https://massbank.eu/MassBank/RecordDisplay?id=${result._id}`
          });
        });
      }
      return allResults;
    }

    /**
     * This method will retrieve similar mass spectra
     * @param {object} [options={}]
     * @param {string} [options.baseURL='https://octochemdb.cheminfo.org/'] - URL of the webservice
     * @param {number|string|number[]} [options.masses] - Observed monoisotopic mass
     * @param {number} [options.precision=1000] - Precision (accuracy) of the monoisotopic mass in ppm
     * @param {number} [options.limit=1000] - Maximal number of entries to return
     * @param {string} [options.modifications=''] - Comma
     * @param {boolean} [options.uniqueMolecules=true] - If true, only one molecule per entry is returned
     * @param {object} [options.similarity={}]
     * @param {number} [options.similarity.nbPeaks=5] - Number of peaks to use for the similarity
     * @param {number} [options.similarity.massPower=1] - Power to use for the mass
     * @param {number} [options.similarity.minSimilarity=0.2] - Minimal similarity to return
     * @param {number} [options.similarity.intensityPower=0.6] - Power to use for the intensity
     * @param {number} [options.similarity.minNbCommonPeaks=0] - Minimal number of common peaks
     * @param {{x:number[],y:number[]}} [options.similarity.experimental=[]] - Experimental mass spectra to compare to
     *
     */
    async function massSpectra() {
      let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
      const {
        uniqueMolecules = true
      } = options;
      let results = (await Promise.all([gnps(options), massBank(options)])).flat();
      results = appendAndFilterSimilarity(results, options);
      if (uniqueMolecules) {
        results = uniqueMol(results);
      }
      return results;
    }
    function uniqueMol(results) {
      const unique = {};
      results.forEach(result => {
        if (!unique[result.data.ocl.idCode]) {
          unique[result.data.ocl.idCode] = result;
        }
      });
      return Object.values(unique);
    }
    function appendAndFilterSimilarity(results) {
      let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      const {
        similarity
      } = options;
      if (!similarity || !similarity.experimental) return results;
      const {
        experimental,
        nbPeaks = 5,
        massPower = 1,
        minSimilarity = 0.2,
        intensityPower = 0.6,
        minNbCommonPeaks = 0
      } = similarity;
      const precision = Number(options.precision) / 1e6;
      const comparator = new MSComparator$1({
        nbPeaks,
        massPower,
        intensityPower,
        delta: mass => mass * precision,
        minNbCommonPeaks
      });
      for (const result of results) {
        result.similarity = comparator.getSimilarity(experimental, JSON.parse(JSON.stringify(result.data.spectrum.data)));
      }
      results = results.filter(a => a.similarity >= minSimilarity);
      results.sort((a, b) => b.similarity - a.similarity);
      return results;
    }

    /**
     * Retrieve a list of molecular formulas from one or many monoisotopic mass that are present in pubchem compound.
     * in the database and a monoisotopic mass.
     * @param {number|string|number[]} masses - Observed monoisotopic mass
     * @param {object} [options={}]
     * @param {string} [options.ionizations=''] - string containing a comma separated list of modifications
     * @param {number} [options.precision=1000] - Precision (accuracy) of the monoisotopic mass in ppm
     * @param {string} [options.ranges=''] - Range of allowed molecular formula
     * @param {number} [options.limit=1000] - Maximal number of entries to return
     * @param {number} [options.minCount=5] - Minimal number of molecules having a specific MF
     * @param {number} [options.fields] - Fields to retrieve from the database
     * @param {string} [options.baseURL='https://octochemdb.cheminfo.org/'] - URL of the webservice
     * @param {string} [options.url='mfs/v1/fromEM'] - URL of the webservice
     * @param {string} [options.compoundsURL='mfs/v1/fromMF'] - URL to retrieve corresponding compounds
     */

    async function mfsFromEM(masses) {
      let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      const {
        url = 'mfs/v1/fromEM',
        compoundsURL = 'compounds/v1/fromMF',
        baseURL = 'https://octochemdb.cheminfo.org/',
        minCount = 5
      } = options;
      const realURL = new URL(url, baseURL).toString();
      const searchParams = {};
      searchParams.minCount = String(minCount);
      const entries = await searchWithIonizations({
        ...options,
        masses,
        realURL,
        searchParams
      });
      const realCompoundsURL = new URL(compoundsURL, baseURL).toString();
      const searchParamsCompounds = new URLSearchParams();
      for (const entry of entries) {
        searchParamsCompounds.set('mf', String(entry._id));
        entry.compoundsURL = `${realCompoundsURL}?${searchParamsCompounds.toString()}`;
      }

      // because we can combine many ionizations we should resort the data
      entries.sort((a, b) => Math.abs(a.ms.ppm) - Math.abs(b.ms.ppm));
      return entries;
    }

    /**
     * Retrieve the compounds from a pubmedID
     * @param {string} pubmedID
     * @param {object} [options={}]
     * @param {string} [options.url='pubmeds/v1/id'] - relative URL of the webservice
     * @param {string} [options.baseURL='https://octochemdb.cheminfo.org/'] - URL of the webservice
     * @returns
     */
    async function pubmedCompounds(pubmedID) {
      let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      const {
        url = 'pubmeds/v1/id',
        baseURL = 'https://octochemdb.cheminfo.org/'
      } = options;
      const realURL = new URL(url, baseURL).toString();
      const searchParams = {
        id: pubmedID,
        fields: 'data.compounds'
      };
      const result = (await fetchJSON(realURL, searchParams)).data;
      await includeDBRefs(result, {
        baseURL
      });
      return result.data.compounds;
    }

    class OctoChemDB {
      /**
       *
       * @param {*} options
       */
      constructor() {
        let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
        this.baseURL = options.baseURL ?? 'https://octochemdb.cheminfo.org/';
      }
      async mfsFromEMs(masses) {
        let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
        return mfsFromEM(masses, {
          baseURL: this.baseURL,
          ...options
        });
      }
      async activesOrNaturals() {
        let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
        return activesOrNaturals({
          baseURL: this.baseURL,
          ...options
        });
      }
      async activesOrNaturalsByMF() {
        let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
        return activesOrNaturalsByMF({
          baseURL: this.baseURL,
          ...options
        });
      }
      async activeOrNaturalDetails(id) {
        let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
        return activeOrNaturalDetails(id, {
          baseURL: this.baseURL,
          ...options
        });
      }
      async gnps() {
        let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
        return gnps({
          baseURL: this.baseURL,
          ...options
        });
      }
      async massBank() {
        let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
        return massBank({
          baseURL: this.baseURL,
          ...options
        });
      }
      async massSpectra() {
        let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
        return massSpectra({
          baseURL: this.baseURL,
          ...options
        });
      }
      async pubmedCompounds(pubmedID) {
        let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
        return pubmedCompounds(pubmedID, {
          baseURL: this.baseURL,
          ...options
        });
      }
    }

    const STEMMERS = {
      arabic: 'ar',
      armenian: 'am',
      bulgarian: 'bg',
      danish: 'dk',
      dutch: 'nl',
      english: 'en',
      finnish: 'fi',
      french: 'fr',
      german: 'de',
      greek: 'gr',
      hungarian: 'hu',
      indian: 'in',
      indonesian: 'id',
      irish: 'ie',
      italian: 'it',
      lithuanian: 'lt',
      nepali: 'np',
      norwegian: 'no',
      portuguese: 'pt',
      romanian: 'ro',
      russian: 'ru',
      serbian: 'rs',
      slovenian: 'ru',
      spanish: 'es',
      swedish: 'se',
      turkish: 'tr',
      ukrainian: 'uk'
    };
    const SPLITTERS = {
      dutch: /[^A-Za-zàèéìòóù0-9_'-]+/gim,
      english: /[^A-Za-zàèéìòóù0-9_'-]+/gim,
      french: /[^a-z0-9äâàéèëêïîöôùüûœç-]+/gim,
      italian: /[^A-Za-zàèéìòóù0-9_'-]+/gim,
      norwegian: /[^a-z0-9_æøåÆØÅäÄöÖüÜ]+/gim,
      portuguese: /[^a-z0-9à-úÀ-Ú]/gim,
      russian: /[^a-z0-9а-яА-ЯёЁ]+/gim,
      spanish: /[^a-z0-9A-Zá-úÁ-ÚñÑüÜ]+/gim,
      swedish: /[^a-z0-9_åÅäÄöÖüÜ-]+/gim,
      german: /[^a-z0-9A-ZäöüÄÖÜß]+/gim,
      finnish: /[^a-z0-9äöÄÖ]+/gim,
      danish: /[^a-z0-9æøåÆØÅ]+/gim,
      hungarian: /[^a-z0-9áéíóöőúüűÁÉÍÓÖŐÚÜŰ]+/gim,
      romanian: /[^a-z0-9ăâîșțĂÂÎȘȚ]+/gim,
      serbian: /[^a-z0-9čćžšđČĆŽŠĐ]+/gim,
      turkish: /[^a-z0-9çÇğĞıİöÖşŞüÜ]+/gim,
      lithuanian: /[^a-z0-9ąčęėįšųūžĄČĘĖĮŠŲŪŽ]+/gim,
      arabic: /[^a-z0-9أ-ي]+/gim,
      nepali: /[^a-z0-9अ-ह]+/gim,
      irish: /[^a-z0-9áéíóúÁÉÍÓÚ]+/gim,
      indian: /[^a-z0-9अ-ह]+/gim,
      armenian: /[^a-z0-9ա-ֆ]+/gim,
      greek: /[^a-z0-9α-ωά-ώ]+/gim,
      indonesian: /[^a-z0-9]+/gim,
      ukrainian: /[^a-z0-9а-яА-ЯіїєІЇЄ]+/gim,
      slovenian: /[^a-z0-9čžšČŽŠ]+/gim,
      bulgarian: /[^a-z0-9а-яА-Я]+/gim
    };
    const SUPPORTED_LANGUAGES = Object.keys(STEMMERS);

    const baseId = Date.now().toString().slice(5);
    let lastId = 0;
    const nano = BigInt(1e3);
    const milli = BigInt(1e6);
    const second = BigInt(1e9);
    function sprintf(template) {
      for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
        args[_key - 1] = arguments[_key];
      }
      return template.replace(/%(?:(?<position>\d+)\$)?(?<width>-?\d*\.?\d*)(?<type>[dfs])/g, function () {
        var _ref;
        const groups = (_ref = arguments.length - 1, _ref < 0 || arguments.length <= _ref ? undefined : arguments[_ref]);
        const {
          width: rawWidth,
          type,
          position
        } = groups;
        const replacement = position ? args[Number.parseInt(position) - 1] : args.shift();
        const width = rawWidth === '' ? 0 : Number.parseInt(rawWidth);
        switch (type) {
          case 'd':
            return replacement.toString().padStart(width, '0');
          case 'f':
            {
              let value = replacement;
              const [padding, precision] = rawWidth.split('.').map(w => Number.parseFloat(w));
              if (typeof precision === 'number' && precision >= 0) {
                value = value.toFixed(precision);
              }
              return typeof padding === 'number' && padding >= 0 ? value.toString().padStart(width, '0') : value.toString();
            }
          case 's':
            return width < 0 ? replacement.toString().padEnd(-width, ' ') : replacement.toString().padStart(width, ' ');
          default:
            return replacement;
        }
      });
    }
    async function formatNanoseconds(value) {
      if (typeof value === 'number') {
        value = BigInt(value);
      }
      if (value < nano) {
        return `${value}ns`;
      } else if (value < milli) {
        return `${value / nano}μs`;
      } else if (value < second) {
        return `${value / milli}ms`;
      }
      return `${value / second}s`;
    }
    async function getNanosecondsTime() {
      if (typeof process !== 'undefined' && process.hrtime !== undefined) {
        return process.hrtime.bigint();
      }
      if (typeof performance !== 'undefined') {
        return BigInt(Math.floor(performance.now() * 1e6));
      }
      // @todo: fallback to V8 native method to get microtime
      return BigInt(0);
    }
    async function uniqueId() {
      return `${baseId}-${lastId++}`;
    }
    // This is only used internally, keep in sync with the previous one
    function syncUniqueId() {
      return `${baseId}-${lastId++}`;
    }
    function getOwnProperty(object, property) {
      // Checks if `hasOwn` method is defined avoiding errors with older Node.js versions
      if (Object.hasOwn === undefined) {
        return Object.prototype.hasOwnProperty.call(object, property) ? object[property] : undefined;
      }
      return Object.hasOwn(object, property) ? object[property] : undefined;
    }
    function sortTokenScorePredicate(a, b) {
      if (b[1] === a[1]) {
        return a[0].localeCompare(b[0]);
      }
      return b[1] - a[1];
    }
    // Intersection function taken from https://github.com/lovasoa/fast_array_intersect.
    // MIT Licensed at the time of writing.
    function intersect(arrays) {
      if (arrays.length === 0) {
        return [];
      } else if (arrays.length === 1) {
        return arrays[0];
      }
      for (let i = 1; i < arrays.length; i++) {
        if (arrays[i].length < arrays[0].length) {
          const tmp = arrays[0];
          arrays[0] = arrays[i];
          arrays[i] = tmp;
        }
      }
      const set = new Map();
      for (const elem of arrays[0]) {
        set.set(elem, 1);
      }
      for (let i = 1; i < arrays.length; i++) {
        let found = 0;
        for (const elem of arrays[i]) {
          const count = set.get(elem);
          if (count === i) {
            set.set(elem, count + 1);
            found++;
          }
        }
        if (found === 0) return [];
      }
      return arrays[0].filter(e => {
        const count = set.get(e);
        if (count !== undefined) set.set(e, 0);
        return count === arrays.length;
      });
    }
    async function getDocumentProperties(doc, paths) {
      const properties = {};
      const pathsLength = paths.length;
      for (let i = 0; i < pathsLength; i++) {
        const path = paths[i];
        const pathTokens = path.split('.');
        let current = doc;
        const pathTokensLength = pathTokens.length;
        for (let j = 0; j < pathTokensLength; j++) {
          current = current[pathTokens[j]];
          // We found an object but we were supposed to be done
          if (typeof current === 'object' && !Array.isArray(current) && current !== null && j === pathTokensLength - 1) {
            current = undefined;
            break;
          } else if ((current === null || typeof current !== 'object') && j < pathTokensLength - 1) {
            // We can't recurse anymore but we were supposed to
            current = undefined;
            break;
          }
        }
        if (typeof current !== 'undefined') {
          properties[path] = current;
        }
      }
      return properties;
    }
    async function getNested(obj, path) {
      const props = await getDocumentProperties(obj, [path]);
      return props[path];
    }

    const allLanguages = SUPPORTED_LANGUAGES.join('\n - ');
    const errors = {
      NO_LANGUAGE_WITH_CUSTOM_TOKENIZER: 'Do not pass the language option to create when using a custom tokenizer.',
      LANGUAGE_NOT_SUPPORTED: `Language "%s" is not supported.\nSupported languages are:\n - ${allLanguages}`,
      INVALID_STEMMER_FUNCTION_TYPE: `config.stemmer property must be a function.`,
      MISSING_STEMMER: `As of version 1.0.0 @orama/orama does not ship non English stemmers by default. To solve this, please explicitly import and specify the "%s" stemmer from the package @orama/stemmers. See https://docs.oramasearch.com/text-analysis/stemming for more information.`,
      CUSTOM_STOP_WORDS_MUST_BE_FUNCTION_OR_ARRAY: 'Custom stop words array must only contain strings.',
      UNSUPPORTED_COMPONENT: `Unsupported component "%s".`,
      COMPONENT_MUST_BE_FUNCTION: `The component "%s" must be a function.`,
      COMPONENT_MUST_BE_FUNCTION_OR_ARRAY_FUNCTIONS: `The component "%s" must be a function or an array of functions.`,
      INVALID_SCHEMA_TYPE: `Unsupported schema type "%s" at "%s". Expected "string", "boolean" or "number" or array of them.`,
      DOCUMENT_ID_MUST_BE_STRING: `Document id must be of type "string". Got "%s" instead.`,
      DOCUMENT_ALREADY_EXISTS: `A document with id "%s" already exists.`,
      DOCUMENT_DOES_NOT_EXIST: `A document with id "%s" does not exists.`,
      MISSING_DOCUMENT_PROPERTY: `Missing searchable property "%s".`,
      INVALID_DOCUMENT_PROPERTY: `Invalid document property "%s": expected "%s", got "%s"`,
      UNKNOWN_INDEX: `Invalid property name "%s". Expected a wildcard string ("*") or array containing one of the following properties: %s`,
      INVALID_BOOST_VALUE: `Boost value must be a number greater than, or less than 0.`,
      INVALID_FILTER_OPERATION: `You can only use one operation per filter, you requested %d.`,
      SCHEMA_VALIDATION_FAILURE: `Cannot insert document due schema validation failure on "%s" property.`,
      INVALID_SORT_SCHEMA_TYPE: `Unsupported sort schema type "%s" at "%s". Expected "string" or "number".`,
      CANNOT_SORT_BY_ARRAY: `Cannot configure sort for "%s" because it is an array (%s).`,
      UNABLE_TO_SORT_ON_UNKNOWN_FIELD: `Unable to sort on unknown field "%s". Allowed fields: %s`,
      SORT_DISABLED: `Sort is disabled. Please read the documentation at https://docs.oramasearch for more information.`,
      UNKNOWN_GROUP_BY_PROPERTY: `Unknown groupBy property "%s".`,
      INVALID_GROUP_BY_PROPERTY: `Invalid groupBy property "%s". Allowed types: "%s", but given "%s".`,
      UNKNOWN_FILTER_PROPERTY: `Unknown filter property "%s".`
    };
    function createError(code) {
      for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
        args[_key - 1] = arguments[_key];
      }
      const error = new Error(sprintf(errors[code] ?? `Unsupported Orama Error code: ${code}`, ...args));
      error.code = code;
      if ('captureStackTrace' in Error.prototype) {
        Error.captureStackTrace(error);
      }
      return error;
    }

    async function formatElapsedTime(n) {
      return {
        raw: Number(n),
        formatted: await formatNanoseconds(n)
      };
    }
    async function getDocumentIndexId(doc) {
      if (doc.id) {
        if (typeof doc.id !== 'string') {
          throw createError('DOCUMENT_ID_MUST_BE_STRING', typeof doc.id);
        }
        return doc.id;
      }
      return await uniqueId();
    }
    async function validateSchema(doc, schema) {
      for (const [prop, type] of Object.entries(schema)) {
        const value = doc[prop];
        const typeOfValue = typeof value;
        if (typeOfValue === 'undefined') {
          continue;
        }
        const typeOfType = typeof type;
        if (typeOfType === 'string' && isArrayType(type)) {
          if (!Array.isArray(value)) {
            return prop;
          }
          const expectedType = getInnerType(type);
          const valueLength = value.length;
          for (let i = 0; i < valueLength; i++) {
            if (typeof value[i] !== expectedType) {
              return prop + '.' + i;
            }
          }
          continue;
        }
        if (typeOfType === 'object') {
          if (!value || typeOfValue !== 'object') {
            return prop;
          }
          const subProp = await validateSchema(value, type);
          if (subProp) {
            return prop + '.' + subProp;
          }
          continue;
        }
        if (typeOfValue !== type) {
          return prop;
        }
      }
      return undefined;
    }
    const IS_ARRAY_TYPE = {
      string: false,
      number: false,
      boolean: false,
      'string[]': true,
      'number[]': true,
      'boolean[]': true
    };
    const INNER_TYPE = {
      'string[]': 'string',
      'number[]': 'number',
      'boolean[]': 'boolean'
    };
    function isArrayType(type) {
      return IS_ARRAY_TYPE[type];
    }
    function getInnerType(type) {
      return INNER_TYPE[type];
    }

    async function create$5() {
      return {
        docs: {},
        count: 0
      };
    }
    async function get(store, id) {
      return store.docs[id];
    }
    async function getMultiple(store, ids) {
      const found = Array.from({
        length: ids.length
      });
      for (let i = 0; i < ids.length; i++) {
        found[i] = store.docs[ids[i]];
      }
      return found;
    }
    async function getAll(store) {
      return store.docs;
    }
    async function store(store, id, doc) {
      if (typeof store.docs[id] !== 'undefined') {
        return false;
      }
      store.docs[id] = doc;
      store.count++;
      return true;
    }
    async function remove$3(store, id) {
      if (typeof store.docs[id] === 'undefined') {
        return false;
      }
      delete store.docs[id];
      store.count--;
      return true;
    }
    async function count(store) {
      return store.count;
    }
    async function load$2(raw) {
      const rawDocument = raw;
      return {
        docs: rawDocument.docs,
        count: rawDocument.count
      };
    }
    async function save$2(store) {
      return {
        docs: store.docs,
        count: store.count
      };
    }
    async function createDocumentsStore() {
      return {
        create: create$5,
        get,
        getMultiple,
        getAll,
        store,
        remove: remove$3,
        count,
        load: load$2,
        save: save$2
      };
    }

    const OBJECT_COMPONENTS = ['tokenizer', 'index', 'documentsStore', 'sorter'];
    const FUNCTION_COMPONENTS = ['validateSchema', 'getDocumentIndexId', 'getDocumentProperties', 'formatElapsedTime'];
    const SINGLE_OR_ARRAY_COMPONENTS = ['beforeInsert', 'afterInsert', 'beforeRemove', 'afterRemove', 'beforeUpdate', 'afterUpdate', 'afterSearch', 'beforeMultipleInsert', 'afterMultipleInsert', 'beforeMultipleRemove', 'afterMultipleRemove', 'beforeMultipleUpdate', 'afterMultipleUpdate'];
    async function runSingleHook(hooks, orama, id, doc) {
      const hooksLength = hooks.length;
      for (let i = 0; i < hooksLength; i++) {
        await hooks[i](orama, id, doc);
      }
    }
    async function runAfterSearch(hooks, db, params, language, results) {
      const hooksLength = hooks.length;
      for (let i = 0; i < hooksLength; i++) {
        await hooks[i](db, params, language, results);
      }
    }

    const BALANCE_STATE = {
      UNBALANCED_RIGHT: -2,
      SLIGHTLY_UNBALANCED_RIGHT: -1,
      BALANCED: 0,
      SLIGHTLY_UNBALANCED_LEFT: 1,
      UNBALANCED_LEFT: 2
    };
    function getHeight(node) {
      return node ? node.height : -1;
    }
    function rotateLeft(node) {
      const right = node.right;
      node.right = right.left;
      right.left = node;
      node.height = Math.max(getHeight(node.left), getHeight(node.right)) + 1;
      right.height = Math.max(getHeight(right.left), getHeight(right.right)) + 1;
      return right;
    }
    function rotateRight(node) {
      const left = node.left;
      node.left = left.right;
      left.right = node;
      node.height = Math.max(getHeight(node.left), getHeight(node.right)) + 1;
      left.height = Math.max(getHeight(left.left), getHeight(left.right)) + 1;
      return left;
    }
    function rangeSearch(node, min, max) {
      if (!node) {
        return [];
      }
      // eslint-disable-next-line @typescript-eslint/ban-ts-comment
      // @ts-ignore
      const result = [];
      function traverse(node) {
        if (!node) {
          return;
        }
        if (node.key > min) {
          traverse(node.left);
        }
        if (node.key >= min && node.key <= max) {
          result.push(...node.value);
        }
        if (node.key < max) {
          traverse(node.right);
        }
      }
      traverse(node);
      return result;
    }
    function greaterThan(node, key) {
      let inclusive = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
      if (!node) {
        return [];
      }
      // eslint-disable-next-line @typescript-eslint/ban-ts-comment
      // @ts-ignore
      const result = [];
      function traverse(node) {
        if (!node) {
          return;
        }
        if (inclusive && node.key >= key) {
          result.push(...node.value);
        }
        if (!inclusive && node.key > key) {
          result.push(...node.value);
        }
        traverse(node.left);
        traverse(node.right);
      }
      traverse(node);
      return result;
    }
    function lessThan(node, key) {
      let inclusive = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
      if (!node) {
        return [];
      }
      // eslint-disable-next-line @typescript-eslint/ban-ts-comment
      // @ts-ignore
      const result = [];
      function traverse(node) {
        if (!node) {
          return;
        }
        if (inclusive && node.key <= key) {
          result.push(...node.value);
        }
        if (!inclusive && node.key < key) {
          result.push(...node.value);
        }
        traverse(node.left);
        traverse(node.right);
      }
      traverse(node);
      return result;
    }
    function getNodeByKey(node, key) {
      while (node !== null) {
        if (key < node.key) {
          node = node.left;
        } else if (key > node.key) {
          node = node.right;
        } else {
          return node;
        }
      }
      return null;
    }
    function create$4(key, value) {
      return {
        key,
        value,
        left: null,
        right: null,
        height: 0
      };
    }
    function insert$4(root, key, value) {
      let parent = null;
      let current = root;
      while (current !== null) {
        parent = current;
        if (key < current.key) {
          current = current.left;
        } else if (key > current.key) {
          current = current.right;
        } else {
          current.value = current.value.concat(value);
          return root;
        }
      }
      const newNode = create$4(key, value);
      if (!parent) {
        root = newNode // tree was empty
        ;
      } else if (key < parent.key) {
        parent.left = newNode;
      } else {
        parent.right = newNode;
      }
      current = newNode;
      while (parent) {
        const balanceFactor = getHeight(parent.left) - getHeight(parent.right);
        if (balanceFactor === BALANCE_STATE.UNBALANCED_LEFT) {
          if (key > parent.left.key) {
            parent.left = rotateLeft(parent.left);
          }
          parent = rotateRight(parent);
        }
        if (balanceFactor === BALANCE_STATE.UNBALANCED_RIGHT) {
          if (key < parent.right.key) {
            parent.right = rotateRight(parent.right);
          }
          parent = rotateLeft(parent);
        }
        if (parent === root) {
          break;
        }
        current = parent;
        parent = getNodeParent(root, current.key);
      }
      return root;
    }
    function getNodeParent(root, key) {
      let current = root;
      let parent = null;
      while (current !== null) {
        if (key < current.key) {
          parent = current;
          current = current.left;
        } else if (key > current.key) {
          parent = current;
          current = current.right;
        } else {
          break;
        }
      }
      return parent;
    }
    function find$1(root, key) {
      const node = getNodeByKey(root, key);
      if (!node) {
        return null;
      }
      return node.value;
    }
    function remove$2(root, key) {
      let node = root;
      let parentNode = null;
      while (node && node.key !== key) {
        parentNode = node;
        if (key < node.key) {
          node = node.left;
        } else {
          node = node.right;
        }
      }
      if (!node) {
        return null;
      }
      if (!node.left && !node.right) {
        if (!parentNode) {
          // Node to be deleted is root
          root = null;
        } else {
          if (parentNode.left === node) {
            parentNode.left = null;
          } else {
            parentNode.right = null;
          }
        }
      } else if (node.left && node.right) {
        let minValueNode = node.right;
        let minValueParent = node;
        while (minValueNode.left) {
          minValueParent = minValueNode;
          minValueNode = minValueNode.left;
        }
        node.key = minValueNode.key;
        if (minValueParent === node) {
          minValueParent.right = minValueNode.right;
        } else {
          minValueParent.left = minValueNode.right;
        }
      } else {
        const childNode = node.left ? node.left : node.right;
        if (!parentNode) {
          root = childNode;
        } else {
          if (parentNode.left === node) {
            parentNode.left = childNode;
          } else {
            parentNode.right = childNode;
          }
        }
      }
      return root;
    }
    function removeDocument$1(root, id, key) {
      const node = getNodeByKey(root, key);
      if (node.value.length === 1) {
        remove$2(root, key);
        return;
      }
      node.value.splice(node.value.indexOf(id), 1);
    }

    /**
     * Inspired by:
     * https://github.com/Yomguithereal/talisman/blob/86ae55cbd040ff021d05e282e0e6c71f2dde21f8/src/metrics/levenshtein.js#L218-L340
     */function _boundedLevenshtein(a, b, tolerance) {
      // the strings are the same
      if (a === b) {
        return 0;
      }
      // a should be the shortest string
      const swap = a;
      if (a.length > b.length) {
        a = b;
        b = swap;
      }
      let lenA = a.length;
      let lenB = b.length;
      // ignore common suffix
      // note: `~-` decreases by a unit in a bitwise fashion
      while (lenA > 0 && a.charCodeAt(~-lenA) === b.charCodeAt(~-lenB)) {
        lenA--;
        lenB--;
      }
      // early return when the smallest string is empty
      if (!lenA) {
        return lenB > tolerance ? -1 : lenB;
      }
      // ignore common prefix
      let startIdx = 0;
      while (startIdx < lenA && a.charCodeAt(startIdx) === b.charCodeAt(startIdx)) {
        startIdx++;
      }
      lenA -= startIdx;
      lenB -= startIdx;
      // early return when the smallest string is empty
      if (lenA === 0) {
        return lenB > tolerance ? -1 : lenB;
      }
      const delta = lenB - lenA;
      if (tolerance > lenB) {
        tolerance = lenB;
      } else if (delta > tolerance) {
        return -1;
      }
      let i = 0;
      const row = [];
      const characterCodeCache = [];
      while (i < tolerance) {
        characterCodeCache[i] = b.charCodeAt(startIdx + i);
        row[i] = ++i;
      }
      while (i < lenB) {
        characterCodeCache[i] = b.charCodeAt(startIdx + i);
        row[i++] = tolerance + 1;
      }
      const offset = tolerance - delta;
      const haveMax = tolerance < lenB;
      let jStart = 0;
      let jEnd = tolerance;
      let current = 0;
      let left = 0;
      let above = 0;
      let charA = 0;
      let j = 0;
      // Starting the nested loops
      for (i = 0; i < lenA; i++) {
        left = i;
        current = i + 1;
        charA = a.charCodeAt(startIdx + i);
        jStart += i > offset ? 1 : 0;
        jEnd += jEnd < lenB ? 1 : 0;
        for (j = jStart; j < jEnd; j++) {
          above = current;
          current = left;
          left = row[j];
          if (charA !== characterCodeCache[j]) {
            // insert current
            if (left < current) {
              current = left;
            }
            // delete current
            if (above < current) {
              current = above;
            }
            current++;
          }
          row[j] = current;
        }
        if (haveMax && row[i + delta] > tolerance) {
          return -1;
        }
      }
      return current <= tolerance ? current : -1;
    }
    // This is only used internally, keep in sync with the previous one
    function syncBoundedLevenshtein(a, b, tolerance) {
      const distance = _boundedLevenshtein(a, b, tolerance);
      return {
        distance,
        isBounded: distance >= 0
      };
    }

    /* c8 ignore next 5 */
    function serialize() {
      const {
        word,
        subWord,
        children,
        docs,
        end
      } = this;
      return {
        word,
        subWord,
        children,
        docs,
        end
      };
    }
    function updateParent(node, parent) {
      node.parent = parent.id;
      node.word = parent.word + node.subWord;
    }
    function addDocument(node, docID) {
      node.docs.push(docID);
    }
    function removeDocument(node, docID) {
      const index = node.docs.indexOf(docID);
      /* c8 ignore next 3 */
      if (index === -1) {
        return false;
      }
      node.docs.splice(index, 1);
      return true;
    }
    function findAllWords(node, output, term, exact, tolerance) {
      if (node.end) {
        const {
          word,
          docs: docIDs
        } = node;
        if (exact && word !== term) {
          return {};
        }
        // always check in own property to prevent access to inherited properties
        // fix https://github.com/OramaSearch/orama/issues/137
        if (!getOwnProperty(output, word)) {
          if (tolerance) {
            // computing the absolute difference of letters between the term and the word
            const difference = Math.abs(term.length - word.length);
            // if the tolerance is set, check whether the edit distance is within tolerance.
            // In that case, we don't need to add the word to the output
            if (difference <= tolerance && syncBoundedLevenshtein(term, word, tolerance).isBounded) {
              output[word] = [];
            }
          } else {
            // prevent default tolerance not set
            output[word] = [];
          }
        }
        // check if _output[word] exists and then add the doc to it
        // always check in own property to prevent access to inherited properties
        // fix https://github.com/OramaSearch/orama/issues/137
        if (getOwnProperty(output, word) && docIDs.length) {
          const docs = new Set(output[word]);
          const docIDsLength = docIDs.length;
          for (let i = 0; i < docIDsLength; i++) {
            docs.add(docIDs[i]);
          }
          output[word] = Array.from(docs);
        }
      }
      // recursively search the children
      for (const character of Object.keys(node.children)) {
        findAllWords(node.children[character], output, term, exact, tolerance);
      }
      return output;
    }
    function getCommonPrefix(a, b) {
      let commonPrefix = '';
      const len = Math.min(a.length, b.length);
      for (let i = 0; i < len; i++) {
        if (a[i] !== b[i]) {
          return commonPrefix;
        }
        commonPrefix += a[i];
      }
      return commonPrefix;
    }
    function create$3() {
      let end = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
      let subWord = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
      let key = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : '';
      const node = {
        id: syncUniqueId(),
        key,
        subWord,
        parent: null,
        children: {},
        docs: [],
        end,
        word: ''
      };
      Object.defineProperty(node, 'toJSON', {
        value: serialize
      });
      return node;
    }
    function insert$3(root, word, docId) {
      for (let i = 0; i < word.length; i++) {
        const currentCharacter = word[i];
        const wordAtIndex = word.substring(i);
        const rootChildCurrentChar = root.children[currentCharacter];
        if (rootChildCurrentChar) {
          const edgeLabel = rootChildCurrentChar.subWord;
          const edgeLabelLength = edgeLabel.length;
          const commonPrefix = getCommonPrefix(edgeLabel, wordAtIndex);
          const commonPrefixLength = commonPrefix.length;
          // the wordAtIndex matches exactly with an existing child node
          if (edgeLabel === wordAtIndex) {
            addDocument(rootChildCurrentChar, docId);
            rootChildCurrentChar.end = true;
            return;
          }
          const edgeLabelAtCommonPrefix = edgeLabel[commonPrefixLength];
          // the wordAtIndex is completely contained in the child node subword
          if (commonPrefixLength < edgeLabelLength && commonPrefixLength === wordAtIndex.length) {
            const newNode = create$3(true, wordAtIndex, currentCharacter) // Create a new node with end set to true
            ;

            newNode.children[edgeLabelAtCommonPrefix] = rootChildCurrentChar;
            const newNodeChild = newNode.children[edgeLabelAtCommonPrefix];
            newNodeChild.subWord = edgeLabel.substring(commonPrefixLength);
            newNodeChild.key = edgeLabelAtCommonPrefix;
            root.children[currentCharacter] = newNode;
            updateParent(newNode, root);
            updateParent(newNodeChild, newNode);
            addDocument(newNode, docId);
            return;
          }
          // the wordAtIndex is partially contained in the child node subword
          if (commonPrefixLength < edgeLabelLength && commonPrefixLength < wordAtIndex.length) {
            const inbetweenNode = create$3(false, commonPrefix, currentCharacter);
            inbetweenNode.children[edgeLabelAtCommonPrefix] = rootChildCurrentChar;
            root.children[currentCharacter] = inbetweenNode;
            const inbetweenNodeChild = inbetweenNode.children[edgeLabelAtCommonPrefix];
            inbetweenNodeChild.subWord = edgeLabel.substring(commonPrefixLength);
            inbetweenNodeChild.key = edgeLabelAtCommonPrefix;
            const wordAtCommonPrefix = wordAtIndex[commonPrefixLength];
            const newNode = create$3(true, word.substring(i + commonPrefixLength), wordAtCommonPrefix);
            addDocument(newNode, docId);
            inbetweenNode.children[wordAtCommonPrefix] = newNode;
            updateParent(inbetweenNode, root);
            updateParent(newNode, inbetweenNode);
            updateParent(inbetweenNodeChild, inbetweenNode);
            return;
          }
          // skip to the next divergent character
          i += edgeLabelLength - 1;
          // navigate in the child node
          root = rootChildCurrentChar;
        } else {
          // if the node for the current character doesn't exist create new node
          const newNode = create$3(true, wordAtIndex, currentCharacter);
          addDocument(newNode, docId);
          root.children[currentCharacter] = newNode;
          updateParent(newNode, root);
          return;
        }
      }
    }
    function find(root, _ref) {
      let {
        term,
        exact,
        tolerance
      } = _ref;
      // find the closest node to the term
      for (let i = 0; i < term.length; i++) {
        const character = term[i];
        if (character in root.children) {
          const rootChildCurrentChar = root.children[character];
          const edgeLabel = rootChildCurrentChar.subWord;
          const termSubstring = term.substring(i);
          // find the common prefix between two words ex: prime and primate = prim
          const commonPrefix = getCommonPrefix(edgeLabel, termSubstring);
          const commonPrefixLength = commonPrefix.length;
          // if the common prefix lenght is equal to edgeLabel lenght (the node subword) it means they are a match
          // if the common prefix is equal to the term means it is contained in the node
          if (commonPrefixLength !== edgeLabel.length && commonPrefixLength !== termSubstring.length) {
            // if tolerance is set we take the current node as the closest
            if (tolerance) break;
            return {};
          }
          // skip the subword lenght and check the next divergent character
          i += rootChildCurrentChar.subWord.length - 1;
          // navigate into the child node
          root = rootChildCurrentChar;
        } else {
          return {};
        }
      }
      const output = {};
      // found the closest node we recursively search through children
      findAllWords(root, output, term, exact, tolerance);
      return output;
    }
    function removeDocumentByWord(root, term, docID) {
      let exact = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true;
      if (!term) {
        return true;
      }
      for (let i = 0; i < term.length; i++) {
        const character = term[i];
        if (character in root.children) {
          const rootChildCurrentChar = root.children[character];
          i += rootChildCurrentChar.subWord.length - 1;
          root = rootChildCurrentChar;
          if (exact && root.word !== term) ; else {
            removeDocument(root, docID);
          }
        } else {
          return false;
        }
      }
      return true;
    }

    function prioritizeTokenScores(arrays, boost) {
      let threshold = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 1;
      if (boost === 0) {
        throw createError('INVALID_BOOST_VALUE');
      }
      const tokenMap = {};
      const mapsLength = arrays.length;
      for (let i = 0; i < mapsLength; i++) {
        const arr = arrays[i];
        const entriesLength = arr.length;
        for (let j = 0; j < entriesLength; j++) {
          const [token, score] = arr[j];
          const boostScore = score * boost;
          if (token in tokenMap) {
            tokenMap[token] *= 1.5 + boostScore;
          } else {
            tokenMap[token] = boostScore;
          }
        }
      }
      const results = Object.entries(tokenMap).sort((a, b) => b[1] - a[1]);
      // If threshold is 1, it means we will return all the results with at least one search term,
      // prioritizig the ones that contains more search terms (fuzzy match)
      if (threshold === 1) {
        return results;
      }
      // If threshold is 0, it means we will only return all the results that contains ALL the search terms (exact match)
      if (threshold === 0) {
        const shortestArrayLength = Math.min(...arrays.map(arr => arr.length));
        return results.slice(0, shortestArrayLength);
      }
      // If the theshold is between 0 and 1, we will return all the results that contains at least the threshold of search terms
      // For example, if threshold is 0.5, we will return all the results that contains at least 50% of the search terms
      // (fuzzy match with a minimum threshold)
      const thresholdLength = Math.ceil(threshold * 100 * results.length / 100);
      return results.slice(0, results.length + thresholdLength);
    }
    function BM25(tf, matchingCount, docsCount, fieldLength, averageFieldLength, BM25Params) {
      const {
        k,
        b,
        d
      } = BM25Params;
      const idf = Math.log(1 + (docsCount - matchingCount + 0.5) / (matchingCount + 0.5));
      return idf * (d + tf * (k + 1)) / (tf + k * (1 - b + b * fieldLength / averageFieldLength));
    }

    async function insertDocumentScoreParameters(index, prop, id, tokens, docsCount) {
      index.avgFieldLength[prop] = ((index.avgFieldLength[prop] ?? 0) * (docsCount - 1) + tokens.length) / docsCount;
      index.fieldLengths[prop][id] = tokens.length;
      index.frequencies[prop][id] = {};
    }
    async function insertTokenScoreParameters(index, prop, id, tokens, token) {
      let tokenFrequency = 0;
      for (const t of tokens) {
        if (t === token) {
          tokenFrequency++;
        }
      }
      const tf = tokenFrequency / tokens.length;
      index.frequencies[prop][id][token] = tf;
      if (!(token in index.tokenOccurrencies[prop])) {
        index.tokenOccurrencies[prop][token] = 0;
      }
      // increase a token counter that may not yet exist
      index.tokenOccurrencies[prop][token] = (index.tokenOccurrencies[prop][token] ?? 0) + 1;
    }
    async function removeDocumentScoreParameters(index, prop, id, docsCount) {
      index.avgFieldLength[prop] = (index.avgFieldLength[prop] * docsCount - index.fieldLengths[prop][id]) / (docsCount - 1);
      index.fieldLengths[prop][id] = undefined;
      index.frequencies[prop][id] = undefined;
    }
    async function removeTokenScoreParameters(index, prop, token) {
      index.tokenOccurrencies[prop][token]--;
    }
    async function calculateResultScores(context, index, prop, term, ids) {
      const documentIDs = Array.from(ids);
      // Exact fields for TF-IDF
      const avgFieldLength = index.avgFieldLength[prop];
      const fieldLengths = index.fieldLengths[prop];
      const oramaOccurrencies = index.tokenOccurrencies[prop];
      const oramaFrequencies = index.frequencies[prop];
      // oramaOccurrencies[term] can be undefined, 0, string, or { [k: string]: number }
      const termOccurrencies = typeof oramaOccurrencies[term] === 'number' ? oramaOccurrencies[term] ?? 0 : 0;
      const scoreList = [];
      // Calculate TF-IDF value for each term, in each document, for each index.
      const documentIDsLength = documentIDs.length;
      for (let k = 0; k < documentIDsLength; k++) {
        var _oramaFrequencies_id;
        const id = documentIDs[k];
        const tf = (oramaFrequencies === null || oramaFrequencies === void 0 ? void 0 : (_oramaFrequencies_id = oramaFrequencies[id]) === null || _oramaFrequencies_id === void 0 ? void 0 : _oramaFrequencies_id[term]) ?? 0;
        const bm25 = BM25(tf, termOccurrencies, context.docsCount, fieldLengths[id], avgFieldLength, context.params.relevance);
        scoreList.push([id, bm25]);
      }
      return scoreList;
    }
    async function create$2(orama, schema, index) {
      let prefix = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : '';
      if (!index) {
        index = {
          indexes: {},
          searchableProperties: [],
          searchablePropertiesWithTypes: {},
          frequencies: {},
          tokenOccurrencies: {},
          avgFieldLength: {},
          fieldLengths: {}
        };
      }
      for (const [prop, type] of Object.entries(schema)) {
        const typeActualType = typeof type;
        const path = `${prefix}${prefix ? '.' : ''}${prop}`;
        if (typeActualType === 'object' && !Array.isArray(type)) {
          // Nested
          create$2(orama, type, index, path);
          continue;
        }
        switch (type) {
          case 'boolean':
          case 'boolean[]':
            index.indexes[path] = {
              true: [],
              false: []
            };
            break;
          case 'number':
          case 'number[]':
            index.indexes[path] = create$4(0, []);
            break;
          case 'string':
          case 'string[]':
            index.indexes[path] = create$3();
            index.avgFieldLength[path] = 0;
            index.frequencies[path] = {};
            index.tokenOccurrencies[path] = {};
            index.fieldLengths[path] = {};
            break;
          default:
            throw createError('INVALID_SCHEMA_TYPE', Array.isArray(type) ? 'array' : type, path);
        }
        index.searchableProperties.push(path);
        index.searchablePropertiesWithTypes[path] = type;
      }
      return index;
    }
    async function insertScalar(implementation, index, prop, id, value, schemaType, language, tokenizer, docsCount) {
      switch (schemaType) {
        case 'boolean':
          {
            const booleanIndex = index.indexes[prop];
            booleanIndex[value ? 'true' : 'false'].push(id);
            break;
          }
        case 'number':
          insert$4(index.indexes[prop], value, [id]);
          break;
        case 'string':
          {
            const tokens = await tokenizer.tokenize(value, language, prop);
            await implementation.insertDocumentScoreParameters(index, prop, id, tokens, docsCount);
            for (const token of tokens) {
              await implementation.insertTokenScoreParameters(index, prop, id, tokens, token);
              insert$3(index.indexes[prop], token, id);
            }
            break;
          }
      }
    }
    async function insert$2(implementation, index, prop, id, value, schemaType, language, tokenizer, docsCount) {
      if (!isArrayType(schemaType)) {
        return insertScalar(implementation, index, prop, id, value, schemaType, language, tokenizer, docsCount);
      }
      const innerSchemaType = getInnerType(schemaType);
      const elements = value;
      const elementsLength = elements.length;
      for (let i = 0; i < elementsLength; i++) {
        await insertScalar(implementation, index, prop, id, elements[i], innerSchemaType, language, tokenizer, docsCount);
      }
    }
    async function removeScalar(implementation, index, prop, id, value, schemaType, language, tokenizer, docsCount) {
      switch (schemaType) {
        case 'number':
          {
            removeDocument$1(index.indexes[prop], id, value);
            return true;
          }
        case 'boolean':
          {
            const booleanKey = value ? 'true' : 'false';
            const position = index.indexes[prop][booleanKey].indexOf(id);
            index.indexes[prop][value ? 'true' : 'false'].splice(position, 1);
            return true;
          }
        case 'string':
          {
            const tokens = await tokenizer.tokenize(value, language, prop);
            await implementation.removeDocumentScoreParameters(index, prop, id, docsCount);
            for (const token of tokens) {
              await implementation.removeTokenScoreParameters(index, prop, token);
              removeDocumentByWord(index.indexes[prop], token, id);
            }
            return true;
          }
      }
    }
    async function remove$1(implementation, index, prop, id, value, schemaType, language, tokenizer, docsCount) {
      if (!isArrayType(schemaType)) {
        return removeScalar(implementation, index, prop, id, value, schemaType, language, tokenizer, docsCount);
      }
      const innerSchemaType = getInnerType(schemaType);
      const elements = value;
      const elementsLength = elements.length;
      for (let i = 0; i < elementsLength; i++) {
        await removeScalar(implementation, index, prop, id, elements[i], innerSchemaType, language, tokenizer, docsCount);
      }
      return true;
    }
    async function search$1(context, index, prop, term) {
      if (!(prop in index.tokenOccurrencies)) {
        return [];
      }
      // Performa the search
      const rootNode = index.indexes[prop];
      const {
        exact,
        tolerance
      } = context.params;
      const searchResult = find(rootNode, {
        term,
        exact,
        tolerance
      });
      const ids = new Set();
      for (const key in searchResult) {
        for (const id of searchResult[key]) {
          ids.add(id);
        }
      }
      return context.index.calculateResultScores(context, index, prop, term, Array.from(ids));
    }
    async function searchByWhereClause(context, index, filters) {
      const filterKeys = Object.keys(filters);
      const filtersMap = filterKeys.reduce((acc, key) => ({
        [key]: [],
        ...acc
      }), {});
      for (const param of filterKeys) {
        const operation = filters[param];
        if (typeof operation === 'boolean') {
          const idx = index.indexes[param];
          if (typeof idx === 'undefined') {
            throw createError('UNKNOWN_FILTER_PROPERTY', param);
          }
          const filteredIDs = idx[operation.toString()];
          filtersMap[param].push(...filteredIDs);
          continue;
        }
        if (typeof operation === 'string' || Array.isArray(operation)) {
          const idx = index.indexes[param];
          if (typeof idx === 'undefined') {
            throw createError('UNKNOWN_FILTER_PROPERTY', param);
          }
          for (const raw of [operation].flat()) {
            const term = await context.tokenizer.tokenize(raw, context.language, param);
            const filteredIDsResults = find(idx, {
              term: term[0],
              exact: true
            });
            filtersMap[param].push(...Object.values(filteredIDsResults).flat());
          }
          continue;
        }
        const operationKeys = Object.keys(operation);
        if (operationKeys.length > 1) {
          throw createError('INVALID_FILTER_OPERATION', operationKeys.length);
        }
        const operationOpt = operationKeys[0];
        const operationValue = operation[operationOpt];
        const AVLNode = index.indexes[param];
        if (typeof AVLNode === 'undefined') {
          throw createError('UNKNOWN_FILTER_PROPERTY', param);
        }
        switch (operationOpt) {
          case 'gt':
            {
              const filteredIDs = greaterThan(AVLNode, operationValue, false);
              filtersMap[param].push(...filteredIDs);
              break;
            }
          case 'gte':
            {
              const filteredIDs = greaterThan(AVLNode, operationValue, true);
              filtersMap[param].push(...filteredIDs);
              break;
            }
          case 'lt':
            {
              const filteredIDs = lessThan(AVLNode, operationValue, false);
              filtersMap[param].push(...filteredIDs);
              break;
            }
          case 'lte':
            {
              const filteredIDs = lessThan(AVLNode, operationValue, true);
              filtersMap[param].push(...filteredIDs);
              break;
            }
          case 'eq':
            {
              const filteredIDs = find$1(AVLNode, operationValue) ?? [];
              filtersMap[param].push(...filteredIDs);
              break;
            }
          case 'between':
            {
              const [min, max] = operationValue;
              const filteredIDs = rangeSearch(AVLNode, min, max);
              filtersMap[param].push(...filteredIDs);
            }
        }
      }
      // AND operation: calculate the intersection between all the IDs in filterMap
      const result = intersect(Object.values(filtersMap));
      return result;
    }
    async function getSearchableProperties(index) {
      return index.searchableProperties;
    }
    async function getSearchablePropertiesWithTypes(index) {
      return index.searchablePropertiesWithTypes;
    }
    async function load$1(raw) {
      const {
        indexes,
        searchableProperties,
        searchablePropertiesWithTypes,
        frequencies,
        tokenOccurrencies,
        avgFieldLength,
        fieldLengths
      } = raw;
      return {
        indexes,
        searchableProperties,
        searchablePropertiesWithTypes,
        frequencies,
        tokenOccurrencies,
        avgFieldLength,
        fieldLengths
      };
    }
    async function save$1(index) {
      const {
        indexes,
        searchableProperties,
        searchablePropertiesWithTypes,
        frequencies,
        tokenOccurrencies,
        avgFieldLength,
        fieldLengths
      } = index;
      return {
        indexes,
        searchableProperties,
        searchablePropertiesWithTypes,
        frequencies,
        tokenOccurrencies,
        avgFieldLength,
        fieldLengths
      };
    }
    async function createIndex() {
      return {
        create: create$2,
        insert: insert$2,
        remove: remove$1,
        insertDocumentScoreParameters,
        insertTokenScoreParameters,
        removeDocumentScoreParameters,
        removeTokenScoreParameters,
        calculateResultScores,
        search: search$1,
        searchByWhereClause,
        getSearchableProperties,
        getSearchablePropertiesWithTypes,
        load: load$1,
        save: save$1
      };
    }

    const DIACRITICS_CHARCODE_START = 192;
    const DIACRITICS_CHARCODE_END = 383;
    const CHARCODE_REPLACE_MAPPING = [65, 65, 65, 65, 65, 65, 65, 67, 69, 69, 69, 69, 73, 73, 73, 73, 69, 78, 79, 79, 79, 79, 79, null, 79, 85, 85, 85, 85, 89, 80, 115, 97, 97, 97, 97, 97, 97, 97, 99, 101, 101, 101, 101, 105, 105, 105, 105, 101, 110, 111, 111, 111, 111, 111, null, 111, 117, 117, 117, 117, 121, 112, 121, 65, 97, 65, 97, 65, 97, 67, 99, 67, 99, 67, 99, 67, 99, 68, 100, 68, 100, 69, 101, 69, 101, 69, 101, 69, 101, 69, 101, 71, 103, 71, 103, 71, 103, 71, 103, 72, 104, 72, 104, 73, 105, 73, 105, 73, 105, 73, 105, 73, 105, 73, 105, 74, 106, 75, 107, 107, 76, 108, 76, 108, 76, 108, 76, 108, 76, 108, 78, 110, 78, 110, 78, 110, 110, 78, 110, 79, 111, 79, 111, 79, 111, 79, 111, 82, 114, 82, 114, 82, 114, 83, 115, 83, 115, 83, 115, 83, 115, 84, 116, 84, 116, 84, 116, 85, 117, 85, 117, 85, 117, 85, 117, 85, 117, 85, 117, 87, 119, 89, 121, 89, 90, 122, 90, 122, 90, 122, 115];
    function replaceChar(charCode) {
      if (charCode < DIACRITICS_CHARCODE_START || charCode > DIACRITICS_CHARCODE_END) return charCode;
      /* c8 ignore next  */
      return CHARCODE_REPLACE_MAPPING[charCode - DIACRITICS_CHARCODE_START] || charCode;
    }
    function replaceDiacritics(str) {
      const stringCharCode = [];
      for (let idx = 0; idx < str.length; idx++) {
        stringCharCode[idx] = replaceChar(str.charCodeAt(idx));
      }
      return String.fromCharCode(...stringCharCode);
    }

    const en = ['i', 'me', 'my', 'myself', 'we', 'us', 'our', 'ours', 'ourselves', 'you', 'your', 'yours', 'yourself', 'yourselves', 'he', 'him', 'his', 'himself', 'she', 'her', 'hers', 'herself', 'it', 'its', 'itself', 'they', 'them', 'their', 'theirs', 'themselves', 'what', 'which', 'who', 'whom', 'this', 'that', 'these', 'those', 'am', 'is', 'are', 'was', 'were', 'be', 'been', 'being', 'have', 'has', 'had', 'having', 'do', 'does', 'did', 'doing', 'will', 'would', 'shall', 'should', 'can', 'could', 'may', 'might', 'must', 'ought', "i'm", "you're", "he's", "she's", "it's", "we're", "they're", "i've", "you've", "we've", "they've", "i'd", "you'd", "he'd", "she'd", "we'd", "they'd", "i'll", "you'll", "he'll", "she'll", "we'll", "they'll", "isn't", "aren't", "wasn't", "weren't", "hasn't", "haven't", "hadn't", "doesn't", "don't", "didn't", "won't", "wouldn't", "shan't", "shouldn't", "can't", 'cannot', "couldn't", "mustn't", "let's", "that's", "who's", "what's", "here's", "there's", "when's", "where's", "why's", "how's", 'an', 'the', 'and', 'but', 'if', 'or', 'because', 'as', 'until', 'while', 'of', 'at', 'by', 'for', 'with', 'about', 'against', 'between', 'into', 'through', 'during', 'before', 'after', 'above', 'below', 'to', 'from', 'up', 'down', 'in', 'out', 'on', 'off', 'over', 'under', 'again', 'further', 'then', 'once', 'here', 'there', 'when', 'where', 'why', 'how', 'all', 'any', 'both', 'each', 'few', 'more', 'most', 'other', 'some', 'such', 'no', 'nor', 'not', 'only', 'own', 'same', 'so', 'than', 'too', 'very'];

    const it = ['ad', 'al', 'allo', 'ai', 'agli', 'all', 'agl', 'alla', 'alle', 'con', 'col', 'coi', 'da', 'dal', 'dallo', 'dai', 'dagli', 'dall', 'dagl', 'dalla', 'dalle', 'di', 'del', 'dello', 'dei', 'degli', 'dell', 'degl', 'della', 'delle', 'in', 'nel', 'nello', 'nei', 'negli', 'nell', 'negl', 'nella', 'nelle', 'su', 'sul', 'sullo', 'sui', 'sugli', 'sull', 'sugl', 'sulla', 'sulle', 'per', 'tra', 'contro', 'io', 'tu', 'lui', 'lei', 'noi', 'voi', 'loro', 'mio', 'mia', 'miei', 'mie', 'tuo', 'tua', 'tuoi', 'tue', 'suo', 'sua', 'suoi', 'sue', 'nostro', 'nostra', 'nostri', 'nostre', 'vostro', 'vostra', 'vostri', 'vostre', 'mi', 'ti', 'ci', 'vi', 'lo', 'la', 'li', 'le', 'gli', 'ne', 'il', 'un', 'uno', 'una', 'ma', 'ed', 'se', 'perché', 'anche', 'come', 'dov', 'dove', 'che', 'chi', 'cui', 'non', 'più', 'quale', 'quanto', 'quanti', 'quanta', 'quante', 'quello', 'quelli', 'quella', 'quelle', 'questo', 'questi', 'questa', 'queste', 'si', 'tutto', 'tutti', 'a', 'c', 'e', 'i', 'l', 'o', 'ho', 'hai', 'ha', 'abbiamo', 'avete', 'hanno', 'abbia', 'abbiate', 'abbiano', 'avrò', 'avrai', 'avrà', 'avremo', 'avrete', 'avranno', 'avrei', 'avresti', 'avrebbe', 'avremmo', 'avreste', 'avrebbero', 'avevo', 'avevi', 'aveva', 'avevamo', 'avevate', 'avevano', 'ebbi', 'avesti', 'ebbe', 'avemmo', 'aveste', 'ebbero', 'avessi', 'avesse', 'avessimo', 'avessero', 'avendo', 'avuto', 'avuta', 'avuti', 'avute', 'sono', 'sei', 'è', 'siamo', 'siete', 'sia', 'siate', 'siano', 'sarò', 'sarai', 'sarà', 'saremo', 'sarete', 'saranno', 'sarei', 'saresti', 'sarebbe', 'saremmo', 'sareste', 'sarebbero', 'ero', 'eri', 'era', 'eravamo', 'eravate', 'erano', 'fui', 'fosti', 'fu', 'fummo', 'foste', 'furono', 'fossi', 'fosse', 'fossimo', 'fossero', 'essendo', 'faccio', 'fai', 'facciamo', 'fanno', 'faccia', 'facciate', 'facciano', 'farò', 'farai', 'farà', 'faremo', 'farete', 'faranno', 'farei', 'faresti', 'farebbe', 'faremmo', 'fareste', 'farebbero', 'facevo', 'facevi', 'faceva', 'facevamo', 'facevate', 'facevano', 'feci', 'facesti', 'fece', 'facemmo', 'faceste', 'fecero', 'facessi', 'facesse', 'facessimo', 'facessero', 'facendo', 'sto', 'stai', 'sta', 'stiamo', 'stanno', 'stia', 'stiate', 'stiano', 'starò', 'starai', 'starà', 'staremo', 'starete', 'staranno', 'starei', 'staresti', 'starebbe', 'staremmo', 'stareste', 'starebbero', 'stavo', 'stavi', 'stava', 'stavamo', 'stavate', 'stavano', 'stetti', 'stesti', 'stette', 'stemmo', 'steste', 'stettero', 'stessi', 'stesse', 'stessimo', 'stessero', 'stando'];

    const fr = ['au', 'aux', 'avec', 'ce', 'ces', 'dans', 'de', 'des', 'du', 'elle', 'en', 'et', 'eux', 'il', 'je', 'la', 'le', 'leur', 'lui', 'ma', 'mais', 'me', 'même', 'mes', 'moi', 'mon', 'ne', 'nos', 'notre', 'nous', 'on', 'ou', 'par', 'pas', 'pour', 'qu', 'que', 'qui', 'sa', 'se', 'ses', 'son', 'sur', 'ta', 'te', 'tes', 'toi', 'ton', 'tu', 'un', 'une', 'vos', 'votre', 'vous', 'c', 'd', 'j', 'l', 'à', 'm', 'n', 's', 't', 'y', '', 'été', 'étée', 'étées', 'étés', 'étant', 'suis', 'es', 'est', 'sommes', 'êtes', 'sont', 'serai', 'seras', 'sera', 'serons', 'serez', 'seront', 'serais', 'serait', 'serions', 'seriez', 'seraient', 'étais', 'était', 'étions', 'étiez', 'étaient', 'fus', 'fut', 'fûmes', 'fûtes', 'furent', 'sois', 'soit', 'soyons', 'soyez', 'soient', 'fusse', 'fusses', 'fût', 'fussions', 'fussiez', 'fussent', 'ayant', 'eu', 'eue', 'eues', 'eus', 'ai', 'as', 'avons', 'avez', 'ont', 'aurai', 'auras', 'aura', 'aurons', 'aurez', 'auront', 'aurais', 'aurait', 'aurions', 'auriez', 'auraient', 'avais', 'avait', 'avions', 'aviez', 'avaient', 'eut', 'eûmes', 'eûtes', 'eurent', 'aie', 'aies', 'ait', 'ayons', 'ayez', 'aient', 'eusse', 'eusses', 'eût', 'eussions', 'eussiez', 'eussent', 'ceci', 'cela', 'celà', 'cet', 'cette', 'ici', 'ils', 'les', 'leurs', 'quel', 'quels', 'quelle', 'quelles', 'sans', 'soi'];

    const es = ['de', 'la', 'que', 'el', 'en', 'y', 'a', 'los', 'del', 'se', 'las', 'por', 'un', 'para', 'con', 'no', 'una', 'su', 'al', 'lo', 'como', 'más', 'pero', 'sus', 'le', 'ya', 'o', 'este', 'sí', 'porque', 'esta', 'entre', 'cuando', 'muy', 'sin', 'sobre', 'también', 'me', 'hasta', 'hay', 'donde', 'quien', 'desde', 'todo', 'nos', 'durante', 'todos', 'uno', 'les', 'ni', 'contra', 'otros', 'ese', 'eso', 'ante', 'ellos', 'e', 'esto', 'mí', 'antes', 'algunos', 'qué', 'unos', 'yo', 'otro', 'otras', 'otra', 'él', 'tanto', 'esa', 'estos', 'mucho', 'quienes', 'nada', 'muchos', 'cual', 'poco', 'ella', 'estar', 'estas', 'algunas', 'algo', 'nosotros', 'mi', 'mis', 'tú', 'te', 'ti', 'tu', 'tus', 'ellas', 'nosotras', 'vosotros', 'vosotras', 'os', 'mío', 'mía', 'míos', 'mías', 'tuyo', 'tuya', 'tuyos', 'tuyas', 'suyo', 'suya', 'suyos', 'suyas', 'nuestro', 'nuestra', 'nuestros', 'nuestras', 'vuestro', 'vuestra', 'vuestros', 'vuestras', 'esos', 'esas', 'estoy', 'estás', 'está', 'estamos', 'estáis', 'están', 'esté', 'estés', 'estemos', 'estéis', 'estén', 'estaré', 'estarás', 'estará', 'estaremos', 'estaréis', 'estarán', 'estaría', 'estarías', 'estaríamos', 'estaríais', 'estarían', 'estaba', 'estabas', 'estábamos', 'estabais', 'estaban', 'estuve', 'estuviste', 'estuvo', 'estuvimos', 'estuvisteis', 'estuvieron', 'estuviera', 'estuvieras', 'estuviéramos', 'estuvierais', 'estuvieran', 'estuviese', 'estuvieses', 'estuviésemos', 'estuvieseis', 'estuviesen', 'estando', 'estado', 'estada', 'estados', 'estadas', 'estad', 'he', 'has', 'ha', 'hemos', 'habéis', 'han', 'haya', 'hayas', 'hayamos', 'hayáis', 'hayan', 'habré', 'habrás', 'habrá', 'habremos', 'habréis', 'habrán', 'habría', 'habrías', 'habríamos', 'habríais', 'habrían', 'había', 'habías', 'habíamos', 'habíais', 'habían', 'hube', 'hubiste', 'hubo', 'hubimos', 'hubisteis', 'hubieron', 'hubiera', 'hubieras', 'hubiéramos', 'hubierais', 'hubieran', 'hubiese', 'hubieses', 'hubiésemos', 'hubieseis', 'hubiesen', 'habiendo', 'habido', 'habida', 'habidos', 'habidas', 'soy', 'eres', 'es', 'somos', 'sois', 'son', 'sea', 'seas', 'seamos', 'seáis', 'sean', 'seré', 'serás', 'será', 'seremos', 'seréis', 'serán', 'sería', 'serías', 'seríamos', 'seríais', 'serían', 'era', 'eras', 'éramos', 'erais', 'eran', 'fui', 'fuiste', 'fue', 'fuimos', 'fuisteis', 'fueron', 'fuera', 'fueras', 'fuéramos', 'fuerais', 'fueran', 'fuese', 'fueses', 'fuésemos', 'fueseis', 'fuesen', 'siendo', 'sido', 'tengo', 'tienes', 'tiene', 'tenemos', 'tenéis', 'tienen', 'tenga', 'tengas', 'tengamos', 'tengáis', 'tengan', 'tendré', 'tendrás', 'tendrá', 'tendremos', 'tendréis', 'tendrán', 'tendría', 'tendrías', 'tendríamos', 'tendríais', 'tendrían', 'tenía', 'tenías', 'teníamos', 'teníais', 'tenían', 'tuve', 'tuviste', 'tuvo', 'tuvimos', 'tuvisteis', 'tuvieron', 'tuviera', 'tuvieras', 'tuviéramos', 'tuvierais', 'tuvieran', 'tuviese', 'tuvieses', 'tuviésemos', 'tuvieseis', 'tuviesen', 'teniendo', 'tenido', 'tenida', 'tenidos', 'tenidas', 'tened'];

    const pt = ['de', 'a', 'o', 'que', 'e', 'do', 'da', 'em', 'um', 'para', 'com', 'não', 'uma', 'os', 'no', 'se', 'na', 'por', 'mais', 'as', 'dos', 'como', 'mas', 'ao', 'ele', 'das', 'à', 'seu', 'sua', 'ou', 'quando', 'muito', 'nos', 'já', 'eu', 'também', 'só', 'pelo', 'pela', 'até', 'isso', 'ela', 'entre', 'depois', 'sem', 'mesmo', 'aos', 'seus', 'quem', 'nas', 'me', 'esse', 'eles', 'você', 'essa', 'num', 'nem', 'suas', 'meu', 'às', 'minha', 'numa', 'pelos', 'elas', 'qual', 'nós', 'lhe', 'deles', 'essas', 'esses', 'pelas', 'este', 'dele', 'tu', 'te', 'vocês', 'vos', 'lhes', 'meus', 'minhas', 'teu', 'tua', 'teus', 'tuas', 'nosso', 'nossa', 'nossos', 'nossas', 'dela', 'delas', 'esta', 'estes', 'estas', 'aquele', 'aquela', 'aqueles', 'aquelas', 'isto', 'aquilo', 'estou', 'está', 'estamos', 'estão', 'estive', 'esteve', 'estivemos', 'estiveram', 'estava', 'estávamos', 'estavam', 'estivera', 'estivéramos', 'esteja', 'estejamos', 'estejam', 'estivesse', 'estivéssemos', 'estivessem', 'estiver', 'estivermos', 'estiverem', 'hei', 'há', 'havemos', 'hão', 'houve', 'houvemos', 'houveram', 'houvera', 'houvéramos', 'haja', 'hajamos', 'hajam', 'houvesse', 'houvéssemos', 'houvessem', 'houver', 'houvermos', 'houverem', 'houverei', 'houverá', 'houveremos', 'houverão', 'houveria', 'houveríamos', 'houveriam', 'sou', 'somos', 'são', 'era', 'éramos', 'eram', 'fui', 'foi', 'fomos', 'foram', 'fora', 'fôramos', 'seja', 'sejamos', 'sejam', 'fosse', 'fôssemos', 'fossem', 'for', 'formos', 'forem', 'serei', 'será', 'seremos', 'serão', 'seria', 'seríamos', 'seriam', 'tenho', 'tem', 'temos', 'tém', 'tinha', 'tínhamos', 'tinham', 'tive', 'teve', 'tivemos', 'tiveram', 'tivera', 'tivéramos', 'tenha', 'tenhamos', 'tenham', 'tivesse', 'tivéssemos', 'tivessem', 'tiver', 'tivermos', 'tiverem', 'terei', 'terá', 'teremos', 'terão', 'teria', 'teríamos', 'teriam'];

    const nl = ['de', 'en', 'van', 'ik', 'te', 'dat', 'die', 'in', 'een', 'hij', 'het', 'niet', 'zijn', 'is', 'was', 'op', 'aan', 'met', 'als', 'voor', 'had', 'er', 'maar', 'om', 'hem', 'dan', 'zou', 'of', 'wat', 'mijn', 'men', 'dit', 'zo', 'door', 'over', 'ze', 'zich', 'bij', 'ook', 'tot', 'je', 'mij', 'uit', 'der', 'daar', 'haar', 'naar', 'heb', 'hoe', 'heeft', 'hebben', 'deze', 'u', 'want', 'nog', 'zal', 'me', 'zij', 'nu', 'ge', 'geen', 'omdat', 'iets', 'worden', 'toch', 'al', 'waren', 'veel', 'meer', 'doen', 'toen', 'moet', 'ben', 'zonder', 'kan', 'hun', 'dus', 'alles', 'onder', 'ja', 'eens', 'hier', 'wie', 'werd', 'altijd', 'doch', 'wordt', 'wezen', 'kunnen', 'ons', 'zelf', 'tegen', 'na', 'reeds', 'wil', 'kon', 'niets', 'uw', 'iemand', 'geweest', 'andere'];

    const se = ['och', 'det', 'att', 'i', 'en', 'jag', 'hon', 'som', 'han', 'på', 'den', 'med', 'var', 'sig', 'för', 'så', 'till', 'är', 'men', 'ett', 'om', 'hade', 'de', 'av', 'icke', 'mig', 'du', 'henne', 'då', 'sin', 'nu', 'har', 'inte', 'hans', 'honom', 'skulle', 'hennes', 'där', 'min', 'man', 'ej', 'vid', 'kunde', 'något', 'från', 'ut', 'när', 'efter', 'upp', 'vi', 'dem', 'vara', 'vad', 'över', 'än', 'dig', 'kan', 'sina', 'här', 'ha', 'mot', 'alla', 'under', 'någon', 'eller', 'allt', 'mycket', 'sedan', 'ju', 'denna', 'själv', 'detta', 'åt', 'utan', 'varit', 'hur', 'ingen', 'mitt', 'ni', 'bli', 'blev', 'oss', 'din', 'dessa', 'några', 'deras', 'blir', 'mina', 'samma', 'vilken', 'er', 'sådan', 'vår', 'blivit', 'dess', 'inom', 'mellan', 'sådant', 'varför', 'varje', 'vilka', 'ditt', 'vem', 'vilket', 'sitta', 'sådana', 'vart', 'dina', 'vars', 'vårt', 'våra', 'ert', 'era', 'vilkas'];

    const ru = ['и', 'в', 'во', 'не', 'что', 'он', 'на', 'я', 'с', 'со', 'как', 'а', 'то', 'все', 'она', 'так', 'его', 'но', 'да', 'ты', 'к', 'у', 'же', 'вы', 'за', 'бы', 'по', 'только', 'ее', 'мне', 'было', 'вот', 'от', 'меня', 'еще', 'нет', 'о', 'из', 'ему', 'теперь', 'когда', 'даже', 'ну', 'вдруг', 'ли', 'если', 'уже', 'или', 'ни', 'быть', 'был', 'него', 'до', 'вас', 'нибудь', 'опять', 'уж', 'вам', 'сказал', 'ведь', 'там', 'потом', 'себя', 'ничего', 'ей', 'может', 'они', 'тут', 'где', 'есть', 'надо', 'ней', 'для', 'мы', 'тебя', 'их', 'чем', 'была', 'сам', 'чтоб', 'без', 'будто', 'человек', 'чего', 'раз', 'тоже', 'себе', 'под', 'жизнь', 'будет', 'ж', 'тогда', 'кто', 'этот', 'говорил', 'того', 'потому', 'этого', 'какой', 'совсем', 'ним', 'здесь', 'этом', 'один', 'почти', 'мой', 'тем', 'чтобы', 'нее', 'кажется', 'сейчас', 'были', 'куда', 'зачем', 'сказать', 'всех', 'никогда', 'сегодня', 'можно', 'при', 'наконец', 'два', 'об', 'другой', 'хоть', 'после', 'над', 'больше', 'тот', 'через', 'эти', 'нас', 'про', 'всего', 'них', 'какая', 'много', 'разве', 'сказала', 'три', 'эту', 'моя', 'впрочем', 'хорошо', 'свою', 'этой', 'перед', 'иногда', 'лучше', 'чуть', 'том', 'нельзя', 'такой', 'им', 'более', 'всегда', 'конечно', 'всю', 'между'];

    const no = ['og', 'i', 'jeg', 'det', 'at', 'en', 'et', 'den', 'til', 'er', 'som', 'på', 'de', 'med', 'han', 'av', 'ikke', 'ikkje', 'der', 'så', 'var', 'meg', 'seg', 'men', 'ett', 'har', 'om', 'vi', 'min', 'mitt', 'ha', 'hadde', 'hun', 'nå', 'over', 'da', 'ved', 'fra', 'du', 'ut', 'sin', 'dem', 'oss', 'opp', 'man', 'kan', 'hans', 'hvor', 'eller', 'hva', 'skal', 'selv', 'sjøl', 'her', 'alle', 'vil', 'bli', 'ble', 'blei', 'blitt', 'kunne', 'inn', 'når', 'være', 'kom', 'noen', 'noe', 'ville', 'dere', 'som', 'deres', 'kun', 'ja', 'etter', 'ned', 'skulle', 'denne', 'for', 'deg', 'si', 'sine', 'sitt', 'mot', 'å', 'meget', 'hvorfor', 'dette', 'disse', 'uten', 'hvordan', 'ingen', 'din', 'ditt', 'blir', 'samme', 'hvilken', 'hvilke', 'sånn', 'inni', 'mellom', 'vår', 'hver', 'hvem', 'vors', 'hvis', 'både', 'bare', 'enn', 'fordi', 'før', 'mange', 'også', 'slik', 'vært', 'være', 'båe', 'begge', 'siden', 'dykk', 'dykkar', 'dei', 'deira', 'deires', 'deim', 'di', 'då', 'eg', 'ein', 'eit', 'eitt', 'elles', 'honom', 'hjå', 'ho', 'hoe', 'henne', 'hennar', 'hennes', 'hoss', 'hossen', 'ikkje', 'ingi', 'inkje', 'korleis', 'korso', 'kva', 'kvar', 'kvarhelst', 'kven', 'kvi', 'kvifor', 'me', 'medan', 'mi', 'mine', 'mykje', 'no', 'nokon', 'noka', 'nokor', 'noko', 'nokre', 'si', 'sia', 'sidan', 'so', 'somt', 'somme', 'um', 'upp', 'vere', 'vore', 'verte', 'vort', 'varte', 'vart'];

    const de = ['aber', 'alle', 'allem', 'allen', 'aller', 'alles', 'als', 'also', 'am', 'an', 'ander', 'andere', 'anderem', 'anderen', 'anderer', 'anderes', 'anderm', 'andern', 'anderr', 'anders', 'auch', 'auf', 'aus', 'bei', 'bin', 'bis', 'bist', 'da', 'damit', 'dann', 'der', 'den', 'des', 'dem', 'die', 'das', 'daß', 'derselbe', 'derselben', 'denselben', 'desselben', 'demselben', 'dieselbe', 'dieselben', 'dasselbe', 'dazu', 'dein', 'deine', 'deinem', 'deinen', 'deiner', 'deines', 'denn', 'derer', 'dessen', 'dich', 'dir', 'du', 'dies', 'diese', 'diesem', 'diesen', 'dieser', 'dieses', 'doch', 'dort', 'durch', 'ein', 'eine', 'einem', 'einen', 'einer', 'eines', 'einig', 'einige', 'einigem', 'einigen', 'einiger', 'einiges', 'einmal', 'er', 'ihn', 'ihm', 'es', 'etwas', 'euer', 'eure', 'eurem', 'euren', 'eurer', 'eures', 'für', 'gegen', 'gewesen', 'hab', 'habe', 'haben', 'hat', 'hatte', 'hatten', 'hier', 'hin', 'hinter', 'ich', 'mich', 'mir', 'ihr', 'ihre', 'ihrem', 'ihren', 'ihrer', 'ihres', 'euch', 'im', 'in', 'indem', 'ins', 'ist', 'jede', 'jedem', 'jeden', 'jeder', 'jedes', 'jene', 'jenem', 'jenen', 'jener', 'jenes', 'jetzt', 'kann', 'kein', 'keine', 'keinem', 'keinen', 'keiner', 'keines', 'können', 'könnte', 'machen', 'man', 'manche', 'manchem', 'manchen', 'mancher', 'manches', 'mein', 'meine', 'meinem', 'meinen', 'meiner', 'meines', 'mit', 'muss', 'musste', 'nach', 'nicht', 'nichts', 'noch', 'nun', 'nur', 'ob', 'oder', 'ohne', 'sehr', 'sein', 'seine', 'seinem', 'seinen', 'seiner', 'seines', 'selbst', 'sich', 'sie', 'ihnen', 'sind', 'so', 'solche', 'solchem', 'solchen', 'solcher', 'solches', 'soll', 'sollte', 'sondern', 'sonst', 'über', 'um', 'und', 'uns', 'unse', 'unsem', 'unsen', 'unser', 'unses', 'unter', 'viel', 'vom', 'von', 'vor', 'während', 'war', 'waren', 'warst', 'was', 'weg', 'weil', 'weiter', 'welche', 'welchem', 'welchen', 'welcher', 'welches', 'wenn', 'werde', 'werden', 'wie', 'wieder', 'will', 'wir', 'wird', 'wirst', 'wo', 'wollen', 'wollte', 'würde', 'würden', 'zu', 'zum', 'zur', 'zwar', 'zwischen'];

    const dk = ['og', 'i', 'jeg', 'det', 'at', 'en', 'den', 'til', 'er', 'som', 'på', 'de', 'med', 'han', 'af', 'for', 'ikke', 'der', 'var', 'mig', 'sig', 'men', 'et', 'har', 'om', 'vi', 'min', 'havde', 'ham', 'hun', 'nu', 'over', 'da', 'fra', 'du', 'ud', 'sin', 'dem', 'os', 'op', 'man', 'hans', 'hvor', 'eller', 'hvad', 'skal', 'selv', 'her', 'alle', 'vil', 'blev', 'kunne', 'ind', 'når', 'være', 'dog', 'noget', 'ville', 'jo', 'deres', 'efter', 'ned', 'skulle', 'denne', 'end', 'dette', 'mit', 'også', 'under', 'have', 'dig', 'anden', 'hende', 'mine', 'alt', 'meget', 'sit', 'sine', 'vor', 'mod', 'disse', 'hvis', 'din', 'nogle', 'hos', 'blive', 'mange', 'ad', 'bliver', 'hendes', 'været', 'thi', 'jer', 'sådan'];

    const fi = ['olla', 'olen', 'olet', 'on', 'olemme', 'olette', 'ovat', 'ole', 'oli', 'olisi', 'olisit', 'olisin', 'olisimme', 'olisitte', 'olisivat', 'olit', 'olin', 'olimme', 'olitte', 'olivat', 'ollut', 'olleet', 'en', 'et', 'ei', 'emme', 'ette', 'eivät', 'minä', 'minun', 'minut', 'minua', 'minussa', 'minusta', 'minuun', 'minulla', 'minulta', 'minulle', 'sinä', 'sinun', 'sinut', 'sinua', 'sinussa', 'sinusta', 'sinuun', 'sinulla', 'sinulta', 'sinulle', 'hän', 'hänen', 'hänet', 'häntä', 'hänessä', 'hänestä', 'häneen', 'hänellä', 'häneltä', 'hänelle', 'me', 'meidän', 'meidät', 'meitä', 'meissä', 'meistä', 'meihin', 'meillä', 'meiltä', 'meille', 'te', 'teidän', 'teidät', 'teitä', 'teissä', 'teistä', 'teihin', 'teillä', 'teiltä', 'teille', 'he', 'heidän', 'heidät', 'heitä', 'heissä', 'heistä', 'heihin', 'heillä', 'heiltä', 'heille', 'tämä', 'tämän', 'tätä', 'tässä', 'tästä', 'tähän', 'tällä', 'tältä', 'tälle', 'tänä', 'täksi', 'tuo', 'tuon', 'tuota', 'tuossa', 'tuosta', 'tuohon', 'tuolla', 'tuolta', 'tuolle', 'tuona', 'tuoksi', 'se', 'sen', 'sitä', 'siinä', 'siitä', 'siihen', 'sillä', 'siltä', 'sille', 'sinä', 'siksi', 'nämä', 'näiden', 'näitä', 'näissä', 'näistä', 'näihin', 'näillä', 'näiltä', 'näille', 'näinä', 'näiksi', 'nuo', 'noiden', 'noita', 'noissa', 'noista', 'noihin', 'noilla', 'noilta', 'noille', 'noina', 'noiksi', 'ne', 'niiden', 'niitä', 'niissä', 'niistä', 'niihin', 'niillä', 'niiltä', 'niille', 'niinä', 'niiksi', 'kuka', 'kenen', 'kenet', 'ketä', 'kenessä', 'kenestä', 'keneen', 'kenellä', 'keneltä', 'kenelle', 'kenenä', 'keneksi', 'ketkä', 'keiden', 'ketkä', 'keitä', 'keissä', 'keistä', 'keihin', 'keillä', 'keiltä', 'keille', 'keinä', 'keiksi', 'mikä', 'minkä', 'minkä', 'mitä', 'missä', 'mistä', 'mihin', 'millä', 'miltä', 'mille', 'minä', 'miksi', 'mitkä', 'joka', 'jonka', 'jota', 'jossa', 'josta', 'johon', 'jolla', 'jolta', 'jolle', 'jona', 'joksi', 'jotka', 'joiden', 'joita', 'joissa', 'joista', 'joihin', 'joilla', 'joilta', 'joille', 'joina', 'joiksi', 'että', 'ja', 'jos', 'koska', 'kuin', 'mutta', 'niin', 'sekä', 'sillä', 'tai', 'vaan', 'vai', 'vaikka', 'kanssa', 'mukaan', 'noin', 'poikki', 'yli', 'kun', 'niin', 'nyt', 'itse'];

    const stopWords = {
      english: en,
      italian: it,
      french: fr,
      spanish: es,
      portuguese: pt,
      dutch: nl,
      swedish: se,
      russian: ru,
      norwegian: no,
      german: de,
      danish: dk,
      finnish: fi
    };

    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
    // @ts-nocheck
    const step2List = {
      ational: 'ate',
      tional: 'tion',
      enci: 'ence',
      anci: 'ance',
      izer: 'ize',
      bli: 'ble',
      alli: 'al',
      entli: 'ent',
      eli: 'e',
      ousli: 'ous',
      ization: 'ize',
      ation: 'ate',
      ator: 'ate',
      alism: 'al',
      iveness: 'ive',
      fulness: 'ful',
      ousness: 'ous',
      aliti: 'al',
      iviti: 'ive',
      biliti: 'ble',
      logi: 'log'
    };
    const step3List = {
      icate: 'ic',
      ative: '',
      alize: 'al',
      iciti: 'ic',
      ical: 'ic',
      ful: '',
      ness: ''
    };
    // Consonant
    const c = '[^aeiou]';
    // Vowel
    const v = '[aeiouy]';
    // Consonant sequence
    const C = c + '[^aeiouy]*';
    // Vowel sequence
    const V = v + '[aeiou]*';
    // [C]VC... is m>0
    const mgr0 = '^(' + C + ')?' + V + C;
    // [C]VC[V] is m=1
    const meq1 = '^(' + C + ')?' + V + C + '(' + V + ')?$';
    // [C]VCVC... is m>1
    const mgr1 = '^(' + C + ')?' + V + C + V + C;
    // vowel in stem
    const s_v = '^(' + C + ')?' + v;
    function stemmer(w) {
      let stem;
      let suffix;
      let re;
      let re2;
      let re3;
      let re4;
      if (w.length < 3) {
        return w;
      }
      const firstch = w.substring(0, 1);
      if (firstch == 'y') {
        w = firstch.toUpperCase() + w.substring(1);
      }
      re = /^(.+?)(ss|i)es$/;
      re2 = /^(.+?)([^s])s$/;
      if (re.test(w)) {
        w = w.replace(re, '$1$2');
      } else if (re2.test(w)) {
        w = w.replace(re2, '$1$2');
      }
      re = /^(.+?)eed$/;
      re2 = /^(.+?)(ed|ing)$/;
      if (re.test(w)) {
        const fp = re.exec(w);
        re = new RegExp(mgr0);
        if (re.test(fp[1])) {
          re = /.$/;
          w = w.replace(re, '');
        }
      } else if (re2.test(w)) {
        const fp = re2.exec(w);
        stem = fp[1];
        re2 = new RegExp(s_v);
        if (re2.test(stem)) {
          w = stem;
          re2 = /(at|bl|iz)$/;
          re3 = new RegExp('([^aeiouylsz])\\1$');
          re4 = new RegExp('^' + C + v + '[^aeiouwxy]$');
          if (re2.test(w)) {
            w = w + 'e';
          } else if (re3.test(w)) {
            re = /.$/;
            w = w.replace(re, '');
          } else if (re4.test(w)) {
            w = w + 'e';
          }
        }
      }
      re = /^(.+?)y$/;
      if (re.test(w)) {
        const fp = re.exec(w);
        stem = fp === null || fp === void 0 ? void 0 : fp[1];
        re = new RegExp(s_v);
        if (stem && re.test(stem)) {
          w = stem + 'i';
        }
      }
      re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/;
      if (re.test(w)) {
        const fp = re.exec(w);
        stem = fp === null || fp === void 0 ? void 0 : fp[1];
        suffix = fp === null || fp === void 0 ? void 0 : fp[2];
        re = new RegExp(mgr0);
        if (stem && re.test(stem)) {
          // eslint-disable-next-line @typescript-eslint/ban-ts-comment
          // @ts-ignore
          w = stem + step2List[suffix];
        }
      }
      re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/;
      if (re.test(w)) {
        const fp = re.exec(w);
        stem = fp === null || fp === void 0 ? void 0 : fp[1];
        suffix = fp === null || fp === void 0 ? void 0 : fp[2];
        re = new RegExp(mgr0);
        // eslint-disable-next-line @typescript-eslint/ban-ts-comment
        // @ts-ignore
        if (stem && re.test(stem)) {
          // eslint-disable-next-line @typescript-eslint/ban-ts-comment
          // @ts-ignore
          w = stem + step3List[suffix];
        }
      }
      re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/;
      re2 = /^(.+?)(s|t)(ion)$/;
      if (re.test(w)) {
        const fp = re.exec(w);
        stem = fp === null || fp === void 0 ? void 0 : fp[1];
        re = new RegExp(mgr1);
        if (stem && re.test(stem)) {
          w = stem;
        }
      } else if (re2.test(w)) {
        const fp = re2.exec(w);
        stem = (fp === null || fp === void 0 ? void 0 : fp[1]) ?? '' + (fp === null || fp === void 0 ? void 0 : fp[2]) ?? '';
        re2 = new RegExp(mgr1);
        if (re2.test(stem)) {
          w = stem;
        }
      }
      re = /^(.+?)e$/;
      if (re.test(w)) {
        const fp = re.exec(w);
        stem = fp === null || fp === void 0 ? void 0 : fp[1];
        re = new RegExp(mgr1);
        re2 = new RegExp(meq1);
        re3 = new RegExp('^' + C + v + '[^aeiouwxy]$');
        if (stem && (re.test(stem) || re2.test(stem) && !re3.test(stem))) {
          w = stem;
        }
      }
      re = /ll$/;
      re2 = new RegExp(mgr1);
      if (re.test(w) && re2.test(w)) {
        re = /.$/;
        w = w.replace(re, '');
      }
      if (firstch == 'y') {
        w = firstch.toLowerCase() + w.substring(1);
      }
      return w;
    }

    function normalizeToken(prop, token) {
      var _this_stopWords;
      const key = `${this.language}:${prop}:${token}`;
      if (this.normalizationCache.has(key)) {
        return this.normalizationCache.get(key);
      }
      // Remove stopwords if enabled
      if ((_this_stopWords = this.stopWords) === null || _this_stopWords === void 0 ? void 0 : _this_stopWords.includes(token)) {
        this.normalizationCache.set(key, '');
        return '';
      }
      // Apply stemming if enabled
      if (this.stemmer && !this.stemmerSkipProperties.has(prop)) {
        token = this.stemmer(token);
      }
      token = replaceDiacritics(token);
      this.normalizationCache.set(key, token);
      return token;
    }
    /* c8 ignore next 10 */
    function trim(text) {
      while (text[text.length - 1] === '') {
        text.pop();
      }
      while (text[0] === '') {
        text.shift();
      }
      return text;
    }
    function tokenize(input, language, prop) {
      if (language && language !== this.language) {
        throw createError('LANGUAGE_NOT_SUPPORTED', language);
      }
      /* c8 ignore next 3 */
      if (typeof input !== 'string') {
        return [input];
      }
      const splitRule = SPLITTERS[this.language];
      const tokens = input.toLowerCase().split(splitRule).map(this.normalizeToken.bind(this, prop ?? '')).filter(Boolean);
      const trimTokens = trim(tokens);
      if (!this.allowDuplicates) {
        return Array.from(new Set(trimTokens));
      }
      return trimTokens;
    }
    async function createTokenizer() {
      let config = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
      if (!config.language) {
        config.language = 'english';
      } else if (!SUPPORTED_LANGUAGES.includes(config.language)) {
        throw createError('LANGUAGE_NOT_SUPPORTED', config.language);
      }
      // Handle stemming - It is disabled by default
      let stemmer$1;
      if (config.stemming || config.stemmer && !('stemming' in config)) {
        if (config.stemmer) {
          if (typeof config.stemmer !== 'function') {
            throw createError('INVALID_STEMMER_FUNCTION_TYPE');
          }
          stemmer$1 = config.stemmer;
        } else {
          if (config.language === 'english') {
            stemmer$1 = stemmer;
          } else {
            throw createError('MISSING_STEMMER', config.language);
          }
        }
      }
      // Handle stopwords
      let stopWords$1;
      if (config.stopWords !== false) {
        stopWords$1 = stopWords[config.language] ?? [];
        if (Array.isArray(config.stopWords)) {
          stopWords$1 = config.stopWords;
        } else if (typeof config.stopWords === 'function') {
          stopWords$1 = await config.stopWords(stopWords$1);
        } else if (config.stopWords) {
          throw createError('CUSTOM_STOP_WORDS_MUST_BE_FUNCTION_OR_ARRAY');
        }
        // Make sure stopWords is just an array of strings
        if (!Array.isArray(stopWords$1)) {
          throw createError('CUSTOM_STOP_WORDS_MUST_BE_FUNCTION_OR_ARRAY');
        }
        for (const s of stopWords$1) {
          if (typeof s !== 'string') {
            throw createError('CUSTOM_STOP_WORDS_MUST_BE_FUNCTION_OR_ARRAY');
          }
        }
      }
      // Create the tokenizer
      const tokenizer = {
        tokenize,
        language: config.language,
        stemmer: stemmer$1,
        stemmerSkipProperties: new Set(config.stemmerSkipProperties ? [config.stemmerSkipProperties].flat() : []),
        stopWords: stopWords$1,
        allowDuplicates: Boolean(config.allowDuplicates),
        normalizeToken,
        normalizationCache: new Map()
      };
      tokenizer.tokenize = tokenize.bind(tokenizer);
      tokenizer.normalizeToken = normalizeToken;
      return tokenizer;
    }

    function innerCreate(schema, sortableDeniedProperties, prefix) {
      const sorter = {
        enabled: true,
        sortableProperties: [],
        sortablePropertiesWithTypes: {},
        sorts: {}
      };
      for (const [prop, type] of Object.entries(schema)) {
        const typeActualType = typeof type;
        const path = `${prefix}${prefix ? '.' : ''}${prop}`;
        if (sortableDeniedProperties.includes(path)) {
          continue;
        }
        if (typeActualType === 'object' && !Array.isArray(type)) {
          // Nested
          const ret = innerCreate(type, sortableDeniedProperties, path);
          sorter.sortableProperties.push(...ret.sortableProperties);
          sorter.sorts = {
            ...sorter.sorts,
            ...ret.sorts
          };
          sorter.sortablePropertiesWithTypes = {
            ...sorter.sortablePropertiesWithTypes,
            ...ret.sortablePropertiesWithTypes
          };
          continue;
        }
        switch (type) {
          case 'boolean':
          case 'number':
          case 'string':
            sorter.sortableProperties.push(path);
            sorter.sortablePropertiesWithTypes[path] = type;
            sorter.sorts[path] = {
              docs: {},
              orderedDocs: [],
              type: type
            };
            break;
          case 'boolean[]':
          case 'number[]':
          case 'string[]':
            continue;
          default:
            throw createError('INVALID_SORT_SCHEMA_TYPE', Array.isArray(type) ? 'array' : type, path);
        }
      }
      return sorter;
    }
    async function create$1(_, schema, config) {
      const isSortEnabled = (config === null || config === void 0 ? void 0 : config.enabled) !== false;
      if (!isSortEnabled) {
        return {
          disabled: true
        };
      }
      return innerCreate(schema, (config || {}).unsortableProperties || [], '');
    }
    function stringSort(value, language, d) {
      return d[1].localeCompare(value, language) > 0;
    }
    function numerSort(value, d) {
      return d[1] > value;
    }
    function booleanSort(value, d) {
      return d[1];
    }
    async function insert$1(sorter, prop, id, value, schemaType, language) {
      if (!sorter.enabled) {
        return;
      }
      const s = sorter.sorts[prop];
      let predicate;
      switch (schemaType) {
        case 'string':
          predicate = stringSort.bind(null, value, language);
          break;
        case 'number':
          predicate = numerSort.bind(null, value);
          break;
        case 'boolean':
          predicate = booleanSort.bind(null, value);
          break;
      }
      // Find the right position to insert the element
      let index = s.orderedDocs.findIndex(predicate);
      if (index === -1) {
        index = s.orderedDocs.length;
        s.orderedDocs.push([id, value]);
      } else {
        s.orderedDocs.splice(index, 0, [id, value]);
      }
      s.docs[id] = index;
      // Increment position for the greather documents
      const orderedDocsLength = s.orderedDocs.length;
      for (let i = index + 1; i < orderedDocsLength; i++) {
        const docId = s.orderedDocs[i][0];
        s.docs[docId]++;
      }
    }
    async function remove(sorter, prop, id) {
      if (!sorter.enabled) {
        return;
      }
      const s = sorter.sorts[prop];
      const index = s.docs[id];
      delete s.docs[id];
      // Decrement position for the greather documents
      const orderedDocsLength = s.orderedDocs.length;
      for (let i = index + 1; i < orderedDocsLength; i++) {
        const docId = s.orderedDocs[i][0];
        s.docs[docId]--;
      }
      s.orderedDocs.splice(index, 1);
    }
    async function sortBy(sorter, docIds, by) {
      if (!sorter.enabled) {
        throw createError('SORT_DISABLED');
      }
      const property = by.property;
      const isDesc = by.order === 'DESC';
      const s = sorter.sorts[property];
      if (!s) {
        throw createError('UNABLE_TO_SORT_ON_UNKNOWN_FIELD', property, sorter.sortableProperties.join(', '));
      }
      docIds.sort((a, b) => {
        // This sort algorithm works leveraging on
        // that s.docs is a map of docId -> position
        // If a document is not indexed, it will be not present in the map
        const indexOfA = s.docs[a[0]];
        const indexOfB = s.docs[b[0]];
        const isAIndexed = typeof indexOfA !== 'undefined';
        const isBIndexed = typeof indexOfB !== 'undefined';
        if (!isAIndexed && !isBIndexed) {
          return 0;
        }
        // unindexed documents are always at the end
        if (!isAIndexed) {
          return 1;
        }
        if (!isBIndexed) {
          return -1;
        }
        return isDesc ? indexOfB - indexOfA : indexOfA - indexOfB;
      });
      return docIds;
    }
    async function getSortableProperties(sorter) {
      if (!sorter.enabled) {
        return [];
      }
      return sorter.sortableProperties;
    }
    async function getSortablePropertiesWithTypes(sorter) {
      if (!sorter.enabled) {
        return {};
      }
      return sorter.sortablePropertiesWithTypes;
    }
    async function load(raw) {
      const rawDocument = raw;
      if (!rawDocument.enabled) {
        return {
          enabled: false
        };
      }
      return {
        sortableProperties: rawDocument.sortableProperties,
        sortablePropertiesWithTypes: rawDocument.sortablePropertiesWithTypes,
        sorts: rawDocument.sorts,
        enabled: true
      };
    }
    async function save(sorter) {
      if (!sorter.enabled) {
        return {
          enabled: false
        };
      }
      return {
        sortableProperties: sorter.sortableProperties,
        sortablePropertiesWithTypes: sorter.sortablePropertiesWithTypes,
        sorts: sorter.sorts,
        enabled: sorter.enabled
      };
    }
    async function createSorter() {
      return {
        create: create$1,
        insert: insert$1,
        remove,
        save,
        load,
        sortBy,
        getSortableProperties,
        getSortablePropertiesWithTypes
      };
    }

    function validateComponents(components) {
      const defaultComponents = {
        formatElapsedTime,
        getDocumentIndexId,
        getDocumentProperties,
        validateSchema
      };
      for (const rawKey of FUNCTION_COMPONENTS) {
        const key = rawKey;
        if (components[key]) {
          if (typeof components[key] !== 'function') {
            throw createError('COMPONENT_MUST_BE_FUNCTION', key);
          }
        } else {
          // @ts-expect-error TSC is unable to resolve this
          components[key] = defaultComponents[key];
        }
      }
      for (const rawKey of SINGLE_OR_ARRAY_COMPONENTS) {
        const key = rawKey;
        const component = components[key];
        if (!component) {
          components[key] = [];
        } else if (!Array.isArray(components[key])) {
          // @ts-expect-error TSC is unable to resolve this
          components[key] = [components[key]];
        }
        for (const fn of components[key]) {
          if (typeof fn !== 'function') {
            throw createError('COMPONENT_MUST_BE_FUNCTION_OR_ARRAY_FUNCTIONS', key);
          }
        }
      }
      for (const rawKey of Object.keys(components)) {
        if (!OBJECT_COMPONENTS.includes(rawKey) && !FUNCTION_COMPONENTS.includes(rawKey) && !SINGLE_OR_ARRAY_COMPONENTS.includes(rawKey)) {
          throw createError('UNSUPPORTED_COMPONENT', rawKey);
        }
      }
    }
    async function create(_ref) {
      let {
        schema,
        sort,
        language,
        components,
        id
      } = _ref;
      if (!components) {
        components = {};
      }
      if (!id) {
        id = await uniqueId();
      }
      let tokenizer = components.tokenizer;
      let index = components.index;
      let documentsStore = components.documentsStore;
      let sorter = components.sorter;
      if (!tokenizer) {
        // Use the default tokenizer
        tokenizer = await createTokenizer({
          language: language ?? 'english'
        });
      } else if (!tokenizer.tokenize) {
        // If there is no tokenizer function, we assume this is a TokenizerConfig
        tokenizer = await createTokenizer(tokenizer);
      }
      if (components.tokenizer && language) {
        // Accept language only if a tokenizer is not provided
        throw createError('NO_LANGUAGE_WITH_CUSTOM_TOKENIZER');
      }
      index ||= await createIndex();
      sorter ||= await createSorter();
      documentsStore ||= await createDocumentsStore();
      // Validate all other components
      validateComponents(components);
      // Assign only recognized components and hooks
      const {
        getDocumentProperties,
        getDocumentIndexId,
        validateSchema,
        beforeInsert,
        afterInsert,
        beforeRemove,
        afterRemove,
        beforeUpdate,
        afterUpdate,
        afterSearch,
        beforeMultipleInsert,
        afterMultipleInsert,
        beforeMultipleRemove,
        afterMultipleRemove,
        beforeMultipleUpdate,
        afterMultipleUpdate,
        formatElapsedTime
      } = components;
      const orama = {
        data: {},
        caches: {},
        schema,
        tokenizer,
        index,
        sorter,
        documentsStore,
        getDocumentProperties,
        getDocumentIndexId,
        validateSchema,
        beforeInsert,
        afterInsert,
        beforeRemove,
        afterRemove,
        beforeUpdate,
        afterUpdate,
        afterSearch,
        beforeMultipleInsert,
        afterMultipleInsert,
        beforeMultipleRemove,
        afterMultipleRemove,
        beforeMultipleUpdate,
        afterMultipleUpdate,
        formatElapsedTime,
        id
      };
      orama.data = {
        index: await orama.index.create(orama, schema),
        docs: await orama.documentsStore.create(orama),
        sorting: await orama.sorter.create(orama, schema, sort)
      };
      return orama;
    }

    const kInsertions = Symbol('orama.insertions');

    var _globalThis_process;
    // Web platforms don't have process. React-Native doesn't have process.emitWarning.
    const warn = ((_globalThis_process = globalThis.process) === null || _globalThis_process === void 0 ? void 0 : _globalThis_process.emitWarning) ?? function emitWarning(message, options) {
      console.warn(`[WARNING] [${options.code}] ${message}`);
    };
    function trackInsertion(orama) {
      if (typeof orama[kInsertions] !== 'number') {
        queueMicrotask(() => {
          orama[kInsertions] = undefined;
        });
        orama[kInsertions] = 0;
      }
      if (orama[kInsertions] > 1000) {
        warn("Orama's insert operation is synchronous. Please avoid inserting a large number of document in a single operation in order not to block the main thread or, in alternative, please use insertMultiple.", {
          code: 'ORAMA0001'
        });
        orama[kInsertions] = -1;
      } else if (orama[kInsertions] >= 0) {
        orama[kInsertions]++;
      }
    }

    async function insert(orama, doc, language, skipHooks) {
      const errorProperty = await orama.validateSchema(doc, orama.schema);
      if (errorProperty) {
        throw createError('SCHEMA_VALIDATION_FAILURE', errorProperty);
      }
      return innerInsert(orama, doc, language, skipHooks);
    }
    async function innerInsert(orama, doc, language, skipHooks) {
      const {
        index,
        docs
      } = orama.data;
      const id = await orama.getDocumentIndexId(doc);
      if (typeof id !== 'string') {
        throw createError('DOCUMENT_ID_MUST_BE_STRING', typeof id);
      }
      if (!(await orama.documentsStore.store(docs, id, doc))) {
        throw createError('DOCUMENT_ALREADY_EXISTS', id);
      }
      const docsCount = await orama.documentsStore.count(docs);
      if (!skipHooks) {
        await runSingleHook(orama.beforeInsert, orama, id, doc);
      }
      const indexableProperties = await orama.index.getSearchableProperties(index);
      const indexablePropertiesWithTypes = await orama.index.getSearchablePropertiesWithTypes(index);
      const indexableValues = await orama.getDocumentProperties(doc, indexableProperties);
      for (const [key, value] of Object.entries(indexableValues)) {
        if (typeof value === 'undefined') {
          continue;
        }
        const actualType = typeof value;
        const expectedType = indexablePropertiesWithTypes[key];
        if (isArrayType(expectedType) && Array.isArray(value)) {
          continue;
        }
        if (actualType !== expectedType) {
          throw createError('INVALID_DOCUMENT_PROPERTY', key, expectedType, actualType);
        }
      }
      for (const prop of indexableProperties) {
        var _orama_index, _orama_index_beforeInsert, _orama_index1, _orama_index_afterInsert;
        const value = indexableValues[prop];
        if (typeof value === 'undefined') {
          continue;
        }
        const expectedType = indexablePropertiesWithTypes[prop];
        await ((_orama_index_beforeInsert = (_orama_index = orama.index).beforeInsert) === null || _orama_index_beforeInsert === void 0 ? void 0 : _orama_index_beforeInsert.call(_orama_index, orama.data.index, prop, id, value, expectedType, language, orama.tokenizer, docsCount));
        await orama.index.insert(orama.index, orama.data.index, prop, id, value, expectedType, language, orama.tokenizer, docsCount);
        await ((_orama_index_afterInsert = (_orama_index1 = orama.index).afterInsert) === null || _orama_index_afterInsert === void 0 ? void 0 : _orama_index_afterInsert.call(_orama_index1, orama.data.index, prop, id, value, expectedType, language, orama.tokenizer, docsCount));
      }
      const sortableProperties = await orama.sorter.getSortableProperties(orama.data.sorting);
      const sortablePropertiesWithTypes = await orama.sorter.getSortablePropertiesWithTypes(orama.data.sorting);
      const sortableValues = await orama.getDocumentProperties(doc, sortableProperties);
      for (const prop of sortableProperties) {
        const value = sortableValues[prop];
        if (typeof value === 'undefined') {
          continue;
        }
        const expectedType = sortablePropertiesWithTypes[prop];
        await orama.sorter.insert(orama.data.sorting, prop, id, value, expectedType, language);
      }
      if (!skipHooks) {
        await runSingleHook(orama.afterInsert, orama, id, doc);
      }
      trackInsertion(orama);
      return id;
    }

    function sortingPredicate() {
      let order = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'desc';
      let a = arguments.length > 1 ? arguments[1] : undefined;
      let b = arguments.length > 2 ? arguments[2] : undefined;
      if (order.toLowerCase() === 'asc') {
        return a[1] - b[1];
      } else {
        return b[1] - a[1];
      }
    }
    async function getFacets(orama, results, facetsConfig) {
      const facets = {};
      const allIDs = results.map(_ref => {
        let [id] = _ref;
        return id;
      });
      const allDocs = await orama.documentsStore.getMultiple(orama.data.docs, allIDs);
      const facetKeys = Object.keys(facetsConfig);
      const properties = await orama.index.getSearchablePropertiesWithTypes(orama.data.index);
      for (const facet of facetKeys) {
        let values = {};
        // Hack to guarantee the same order of ranges as specified by the user
        // TODO: Revisit this once components land
        if (properties[facet] === 'number') {
          const {
            ranges
          } = facetsConfig[facet];
          const tmp = [];
          for (const range of ranges) {
            tmp.push([`${range.from}-${range.to}`, 0]);
          }
          values = Object.fromEntries(tmp);
        }
        facets[facet] = {
          count: 0,
          values
        };
      }
      const allDocsLength = allDocs.length;
      for (let i = 0; i < allDocsLength; i++) {
        const doc = allDocs[i];
        for (const facet of facetKeys) {
          const facetValue = facet.includes('.') ? await getNested(doc, facet) : doc[facet];
          const propertyType = properties[facet];
          switch (propertyType) {
            case 'number':
              {
                const ranges = facetsConfig[facet].ranges;
                calculateNumberFacet(ranges, facets[facet].values, facetValue);
                break;
              }
            case 'number[]':
              {
                const alreadyInsertedValues = new Set();
                const ranges = facetsConfig[facet].ranges;
                for (const v of facetValue) {
                  calculateNumberFacet(ranges, facets[facet].values, v, alreadyInsertedValues);
                }
                break;
              }
            case 'boolean':
            case 'string':
              {
                calculateBooleanOrStringFacet(facets[facet].values, facetValue, propertyType);
                break;
              }
            case 'boolean[]':
            case 'string[]':
              {
                const alreadyInsertedValues = new Set();
                const innerType = propertyType === 'boolean[]' ? 'boolean' : 'string';
                for (const v of facetValue) {
                  calculateBooleanOrStringFacet(facets[facet].values, v, innerType, alreadyInsertedValues);
                }
                break;
              }
          }
        }
      }
      for (const facet of facetKeys) {
        // Count the number of values for each facet
        facets[facet].count = Object.keys(facets[facet].values).length;
        // Sort only string-based facets
        if (properties[facet] === 'string') {
          const stringFacetDefinition = facetsConfig;
          facets[facet].values = Object.fromEntries(Object.entries(facets[facet].values).sort((a, b) => sortingPredicate(stringFacetDefinition.sort, a, b)).slice(stringFacetDefinition.offset ?? 0, stringFacetDefinition.limit ?? 10));
        }
      }
      return facets;
    }
    function calculateNumberFacet(ranges, values, facetValue, alreadyInsertedValues) {
      for (const range of ranges) {
        const value = `${range.from}-${range.to}`;
        if (alreadyInsertedValues && alreadyInsertedValues.has(value)) {
          continue;
        }
        if (facetValue >= range.from && facetValue <= range.to) {
          if (values[value] === undefined) {
            values[value] = 1;
          } else {
            values[value]++;
            if (alreadyInsertedValues) {
              alreadyInsertedValues.add(value);
            }
          }
        }
      }
    }
    function calculateBooleanOrStringFacet(values, facetValue, propertyType, alreadyInsertedValues) {
      // String or boolean based facets
      const value = (facetValue === null || facetValue === void 0 ? void 0 : facetValue.toString()) ?? (propertyType === 'boolean' ? 'false' : '');
      if (alreadyInsertedValues && alreadyInsertedValues.has(value)) {
        return;
      }
      values[value] = (values[value] ?? 0) + 1;
      if (alreadyInsertedValues) {
        alreadyInsertedValues.add(value);
      }
    }

    function intersectFilteredIDs(filtered, lookedUp) {
      const map = new Map();
      const result = [];
      for (const id of filtered) {
        map.set(id, true);
      }
      for (const [id, score] of lookedUp) {
        if (map.has(id)) {
          result.push([id, score]);
          map.delete(id);
        }
      }
      return result;
    }

    const DEFAULT_REDUCE = {
      reducer: (_, acc, res, index) => {
        acc[index] = res;
        return acc;
      },
      getInitialValue: length => Array.from({
        length
      })
    };
    const ALLOWED_TYPES = ['string', 'number', 'boolean'];
    async function getGroups(orama, results, groupBy) {
      const properties = groupBy.properties;
      const propertiesLength = properties.length;
      const schemaProperties = await orama.index.getSearchablePropertiesWithTypes(orama.data.index);
      for (let i = 0; i < propertiesLength; i++) {
        const property = properties[i];
        if (typeof schemaProperties[property] === 'undefined') {
          throw createError('UNKNOWN_GROUP_BY_PROPERTY', property);
        }
        if (!ALLOWED_TYPES.includes(schemaProperties[property])) {
          throw createError('INVALID_GROUP_BY_PROPERTY', property, ALLOWED_TYPES.join(', '), schemaProperties[property]);
        }
      }
      const allIDs = results.map(_ref => {
        let [id] = _ref;
        return id;
      });
      // allDocs is already sorted by the sortBy algorithm
      // We leverage on that to limit the number of documents returned
      const allDocs = await orama.documentsStore.getMultiple(orama.data.docs, allIDs);
      const allDocsLength = allDocs.length;
      const returnedCount = groupBy.maxResult || Number.MAX_SAFE_INTEGER;
      const listOfValues = [];
      // We want to understand which documents have which values
      // and group them by the property and values
      const g = {};
      for (let i = 0; i < propertiesLength; i++) {
        const groupByKey = properties[i];
        const group = {
          property: groupByKey,
          perValue: {}
        };
        const values = new Set();
        for (let j = 0; j < allDocsLength; j++) {
          const doc = allDocs[j];
          const value = await getNested(doc, groupByKey);
          // we don't want to consider undefined values
          if (typeof value === 'undefined') {
            continue;
          }
          const keyValue = typeof value !== 'boolean' ? value : '' + value;
          if (typeof group.perValue[keyValue] === 'undefined') {
            group.perValue[keyValue] = {
              indexes: [],
              count: 0
            };
          }
          if (group.perValue[keyValue].count >= returnedCount) {
            continue;
          }
          // We use the index to keep track of the original order
          group.perValue[keyValue].indexes.push(j);
          group.perValue[keyValue].count++;
          values.add(value);
        }
        listOfValues.push(Array.from(values));
        g[groupByKey] = group;
      }
      const combinations = calculateCombination(listOfValues);
      const combinationsLength = combinations.length;
      const groups = [];
      for (let i = 0; i < combinationsLength; i++) {
        const combination = combinations[i];
        const combinationLength = combination.length;
        const group = {
          values: [],
          indexes: []
        };
        const indexes = [];
        for (let j = 0; j < combinationLength; j++) {
          const value = combination[j];
          const property = properties[j];
          indexes.push(g[property].perValue[typeof value !== 'boolean' ? value : '' + value].indexes);
          group.values.push(value);
        }
        // We leverage on the index to sort the results by the original order
        group.indexes = intersect(indexes).sort((a, b) => a - b);
        // don't generate empty groups
        if (group.indexes.length === 0) {
          continue;
        }
        groups.push(group);
      }
      const groupsLength = groups.length;
      const res = Array.from({
        length: groupsLength
      });
      for (let i = 0; i < groupsLength; i++) {
        const group = groups[i];
        const reduce = groupBy.reduce || DEFAULT_REDUCE;
        const docs = group.indexes.map(index => {
          return {
            id: allIDs[index],
            score: results[index][1],
            document: allDocs[index]
          };
        });
        const func = reduce.reducer.bind(null, group.values);
        const initialValue = reduce.getInitialValue(group.indexes.length);
        const aggregationValue = docs.reduce(func, initialValue);
        res[i] = {
          values: group.values,
          result: aggregationValue
        };
      }
      return res;
    }
    function calculateCombination(arrs) {
      let index = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
      if (index + 1 === arrs.length) return arrs[index].map(item => [item]);
      const head = arrs[index];
      const c = calculateCombination(arrs, index + 1);
      const combinations = [];
      for (const value of head) {
        for (const combination of c) {
          combinations.push([value, ...combination]);
        }
      }
      return combinations;
    }

    const defaultBM25Params = {
      k: 1.2,
      b: 0.75,
      d: 0.5
    };
    async function createSearchContext(tokenizer, index, documentsStore, language, params, properties, tokens, docsCount, timeStart) {
      // If filters are enabled, we need to get the IDs of the documents that match the filters.
      // const hasFilters = Object.keys(params.where ?? {}).length > 0;
      // let whereFiltersIDs: string[] = [];
      // if (hasFilters) {
      //   whereFiltersIDs = getWhereFiltersIDs(params.where!, orama);
      // }
      // indexMap is an object containing all the indexes considered for the current search,
      // and an array of doc IDs for each token in all the indices.
      //
      // Given the search term "quick brown fox" on the "description" index,
      // indexMap will look like this:
      //
      // {
      //   description: {
      //     quick: [doc1, doc2, doc3],
      //     brown: [doc2, doc4],
      //     fox:   [doc2]
      //   }
      // }
      const indexMap = {};
      // After we create the indexMap, we need to calculate the intersection
      // between all the postings lists for each token.
      // Given the example above, docsIntersection will look like this:
      //
      // {
      //   description: [doc2]
      // }
      //
      // as doc2 is the only document present in all the postings lists for the "description" index.
      const docsIntersection = {};
      for (const prop of properties) {
        const tokensMap = {};
        for (const token of tokens) {
          tokensMap[token] = [];
        }
        indexMap[prop] = tokensMap;
        docsIntersection[prop] = [];
      }
      return {
        timeStart,
        tokenizer,
        index,
        documentsStore,
        language,
        params,
        docsCount,
        uniqueDocsIDs: {},
        indexMap,
        docsIntersection
      };
    }
    async function search(orama, params, language) {
      const timeStart = await getNanosecondsTime();
      params.relevance = Object.assign(params.relevance ?? {}, defaultBM25Params);
      const shouldCalculateFacets = params.facets && Object.keys(params.facets).length > 0;
      const {
        limit = 10,
        offset = 0,
        term,
        properties,
        threshold = 1,
        distinctOn
      } = params;
      const isPreflight = params.preflight === true;
      const {
        index,
        docs
      } = orama.data;
      const tokens = await orama.tokenizer.tokenize(term ?? '', language);
      // Get searchable string properties
      let propertiesToSearch = orama.caches['propertiesToSearch'];
      if (!propertiesToSearch) {
        const propertiesToSearchWithTypes = await orama.index.getSearchablePropertiesWithTypes(index);
        propertiesToSearch = await orama.index.getSearchableProperties(index);
        propertiesToSearch = propertiesToSearch.filter(prop => propertiesToSearchWithTypes[prop].startsWith('string'));
        orama.caches['propertiesToSearch'] = propertiesToSearch;
      }
      if (properties && properties !== '*') {
        for (const prop of properties) {
          if (!propertiesToSearch.includes(prop)) {
            throw createError('UNKNOWN_INDEX', prop, propertiesToSearch.join(', '));
          }
        }
        propertiesToSearch = propertiesToSearch.filter(prop => properties.includes(prop));
      }
      // Create the search context and the results
      const context = await createSearchContext(orama.tokenizer, orama.index, orama.documentsStore, language, params, propertiesToSearch, tokens, await orama.documentsStore.count(docs), timeStart);
      // If filters are enabled, we need to get the IDs of the documents that match the filters.
      const hasFilters = Object.keys(params.where ?? {}).length > 0;
      let whereFiltersIDs = [];
      if (hasFilters) {
        whereFiltersIDs = await orama.index.searchByWhereClause(context, index, params.where);
      }
      if (tokens.length) {
        // Now it's time to loop over all the indices and get the documents IDs for every single term
        const indexesLength = propertiesToSearch.length;
        for (let i = 0; i < indexesLength; i++) {
          var _params_boost;
          const prop = propertiesToSearch[i];
          const tokensLength = tokens.length;
          for (let j = 0; j < tokensLength; j++) {
            const term = tokens[j];
            // Lookup
            const scoreList = await orama.index.search(context, index, prop, term);
            context.indexMap[prop][term].push(...scoreList);
          }
          const docIds = context.indexMap[prop];
          const vals = Object.values(docIds);
          context.docsIntersection[prop] = prioritizeTokenScores(vals, (params === null || params === void 0 ? void 0 : (_params_boost = params.boost) === null || _params_boost === void 0 ? void 0 : _params_boost[prop]) ?? 1, threshold);
          const uniqueDocs = context.docsIntersection[prop];
          const uniqueDocsLength = uniqueDocs.length;
          for (let i = 0; i < uniqueDocsLength; i++) {
            const [id, score] = uniqueDocs[i];
            const prevScore = context.uniqueDocsIDs[id];
            if (prevScore) {
              context.uniqueDocsIDs[id] = prevScore + score + 0.5;
            } else {
              context.uniqueDocsIDs[id] = score;
            }
          }
        }
      } else if (tokens.length === 0 && term) {
        // This case is hard to handle correctly.
        // For the time being, if tokenizer returns empty array but the term is not empty,
        // we returns an empty result set
        context.uniqueDocsIDs = {};
      } else {
        context.uniqueDocsIDs = Object.fromEntries(Object.keys(await orama.documentsStore.getAll(orama.data.docs)).map(k => [k, 0]));
      }
      // Get unique doc IDs from uniqueDocsIDs map
      let uniqueDocsArray = Object.entries(context.uniqueDocsIDs);
      // If filters are enabled, we need to remove the IDs of the documents that don't match the filters.
      if (hasFilters) {
        uniqueDocsArray = intersectFilteredIDs(whereFiltersIDs, uniqueDocsArray);
      }
      if (params.sortBy) {
        if (typeof params.sortBy === 'function') {
          const ids = uniqueDocsArray.map(_ref => {
            let [id] = _ref;
            return id;
          });
          const docs = await orama.documentsStore.getMultiple(orama.data.docs, ids);
          const docsWithIdAndScore = docs.map((d, i) => [uniqueDocsArray[i][0], uniqueDocsArray[i][1], d]);
          docsWithIdAndScore.sort(params.sortBy);
          uniqueDocsArray = docsWithIdAndScore.map(_ref2 => {
            let [id, score] = _ref2;
            return [id, score];
          });
        } else {
          uniqueDocsArray = await orama.sorter.sortBy(orama.data.sorting, uniqueDocsArray, params.sortBy);
        }
      } else {
        uniqueDocsArray = uniqueDocsArray.sort(sortTokenScorePredicate);
      }
      let results;
      if (!isPreflight && distinctOn) {
        results = await fetchDocumentsWithDistinct(orama, uniqueDocsArray, offset, limit, distinctOn);
      } else if (!isPreflight) {
        results = await fetchDocuments(orama, uniqueDocsArray, offset, limit);
      }
      const searchResult = {
        elapsed: {
          formatted: '',
          raw: 0
        },
        // We keep the hits array empty if it's a preflight request.
        hits: [],
        count: uniqueDocsArray.length
      };
      if (typeof results !== 'undefined') {
        searchResult.hits = results.filter(Boolean);
      }
      if (shouldCalculateFacets) {
        // Populate facets if needed
        const facets = await getFacets(orama, uniqueDocsArray, params.facets);
        searchResult.facets = facets;
      }
      if (params.groupBy) {
        searchResult.groups = await getGroups(orama, uniqueDocsArray, params.groupBy);
      }
      if (orama.afterSearch) {
        await runAfterSearch(orama.afterSearch, orama, params, language, searchResult);
      }
      // Calculate elapsed time only at the end of the function
      searchResult.elapsed = await orama.formatElapsedTime((await getNanosecondsTime()) - context.timeStart);
      return searchResult;
    }
    async function fetchDocumentsWithDistinct(orama, uniqueDocsArray, offset, limit, distinctOn) {
      const docs = orama.data.docs;
      // Keep track which values we already seen
      const values = new Map();
      // We cannot know how many results we will have in the end,
      // so we need cannot pre-allocate the array.
      const results = [];
      const resultIDs = new Set();
      const uniqueDocsArrayLength = uniqueDocsArray.length;
      let count = 0;
      for (let i = 0; i < uniqueDocsArrayLength; i++) {
        const idAndScore = uniqueDocsArray[i];
        // If there are no more results, just break the loop
        if (typeof idAndScore === 'undefined') {
          continue;
        }
        const [id, score] = idAndScore;
        if (resultIDs.has(id)) {
          continue;
        }
        const doc = await orama.documentsStore.get(docs, id);
        const value = await getNested(doc, distinctOn);
        if (typeof value === 'undefined' || values.has(value)) {
          continue;
        }
        values.set(value, true);
        count++;
        // We shouldn't consider the document if it's not in the offset range
        if (count <= offset) {
          continue;
        }
        results.push({
          id,
          score,
          document: doc
        });
        resultIDs.add(id);
        // reached the limit, break the loop
        if (count >= offset + limit) {
          break;
        }
      }
      return results;
    }
    async function fetchDocuments(orama, uniqueDocsArray, offset, limit) {
      const docs = orama.data.docs;
      const results = Array.from({
        length: limit
      });
      const resultIDs = new Set();
      // We already have the list of ALL the document IDs containing the search terms.
      // We loop over them starting from a positional value "offset" and ending at "offset + limit"
      // to provide pagination capabilities to the search.
      for (let i = offset; i < limit + offset; i++) {
        const idAndScore = uniqueDocsArray[i];
        // If there are no more results, just break the loop
        if (typeof idAndScore === 'undefined') {
          break;
        }
        const [id, score] = idAndScore;
        if (!resultIDs.has(id)) {
          // We retrieve the full document only AFTER making sure that we really want it.
          // We never retrieve the full document preventively.
          const fullDoc = await orama.documentsStore.get(docs, id);
          results[i] = {
            id,
            score,
            document: fullDoc
          };
          resultIDs.add(id);
        }
      }
      return results;
    }

    async function getActivitiesDB(activities) {
      let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      const {
        queryFields = ['assay']
      } = options;
      const activitiesDB = await create({
        schema: {
          $id: 'string',
          ...(queryFields.includes('assay') ? {
            assay: 'string'
          } : null)
        }
      });
      for (const activity of activities) {
        let assay = {
          $id: activity.$id
        };
        assay.assay = activity.data.assay;
        await insert(activitiesDB, assay);
      }
      return activitiesDB;
    }

    async function getPatentsDB(patents) {
      let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      const {
        queryFields = ['title', 'abstract'],
        abstractsLimit = 1000
      } = options;
      if (patents.length > abstractsLimit) {
        queryFields.splice(queryFields.indexOf('abstract'), 1);
      }
      const patentsDB = await create({
        schema: {
          $id: 'string',
          ...(queryFields.includes('title') ? {
            title: 'string'
          } : null),
          ...(queryFields.includes('abstract') ? {
            abstract: 'string'
          } : null),
          nbCompounds: 'number'
        }
      });
      for (const patent of patents) {
        let article = {
          $id: patent.$id,
          ...(queryFields.includes('title') ? {
            title: patent?.data?.title
          } : null),
          ...(queryFields.includes('abstract') ? {
            abstract: patent?.data?.abstract
          } : null),
          nbCompounds: patent.data.nbCompounds + 2 || +2
        };
        await insert(patentsDB, article);
      }
      return patentsDB;
    }

    async function getPubmedsDB(pubmeds) {
      let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      const {
        queryFields = ['title', 'abstract', 'meshHeadings'],
        abstractsLimit = 1000
      } = options;
      if (pubmeds.length > abstractsLimit) {
        queryFields.splice(queryFields.indexOf('abstract'), 1);
      }
      const pubmedsDB = await create({
        schema: {
          $ref: 'string',
          $id: 'string',
          ...(queryFields.includes('title') ? {
            title: 'string'
          } : null),
          ...(queryFields.includes('abstract') ? {
            abstract: 'string'
          } : null),
          ...(queryFields.includes('meshHeadings') ? {
            meshHeadings: 'string[]'
          } : null),
          nbCompounds: 'number'
        }
      });
      for (const pubmed of pubmeds) {
        const meshHeadings = [];
        for (const meshHeading of pubmed.data.meshHeadings) {
          if (meshHeading.descriptorName !== undefined) {
            meshHeadings.push(meshHeading.descriptorName);
          }
        }
        let article = {
          $id: pubmed.$id,
          ...(queryFields.includes('title') ? {
            title: pubmed?.data?.article?.title || ''
          } : null),
          ...(queryFields.includes('abstract') ? {
            abstract: pubmed?.data?.article?.abstract || ''
          } : null),
          ...(queryFields.includes('meshHeadings') ? {
            meshHeadings: meshHeadings || ''
          } : null),
          nbCompounds: pubmed.data.compounds.length + 2 || +2
        };
        await insert(pubmedsDB, article);
      }
      return pubmedsDB;
    }

    async function getTaxonomiesDB(taxonomies) {
      let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      const {
        queryFields = ['superKingdom', 'kingdom', 'phylum', 'class', 'order', 'family', 'genus', 'species']
      } = options;
      const taxonomiesDB = await create({
        schema: {
          $id: 'string',
          ...(queryFields.includes('superKingdom') ? {
            superKingdom: 'string'
          } : null),
          ...(queryFields.includes('kingdom') ? {
            kingdom: 'string'
          } : null),
          ...(queryFields.includes('phylum') ? {
            phylum: 'string'
          } : null),
          ...(queryFields.includes('class') ? {
            class: 'string'
          } : null),
          ...(queryFields.includes('order') ? {
            order: 'string'
          } : null),
          ...(queryFields.includes('family') ? {
            family: 'string'
          } : null),
          ...(queryFields.includes('genus') ? {
            genus: 'string'
          } : null),
          ...(queryFields.includes('species') ? {
            species: 'string'
          } : null)
        }
      });
      for (const taxonomy of taxonomies) {
        let level = Object.keys(taxonomy);
        let taxonomyEntry = {
          $id: taxonomy.dbRef.$id,
          ...(level.includes('superKingdom') ? {
            superKingdom: taxonomy.superKingdom
          } : null),
          ...(level.includes('kingdom') ? {
            kingdom: taxonomy.kingdom
          } : null),
          ...(level.includes('phylum') ? {
            phylum: taxonomy.phylum
          } : null),
          ...(level.includes('class') ? {
            class: taxonomy.class
          } : null),
          ...(level.includes('order') ? {
            order: taxonomy.order
          } : null),
          ...(level.includes('family') ? {
            family: taxonomy.family
          } : null),
          ...(level.includes('genus') ? {
            genus: taxonomy.genus
          } : null),
          ...(level.includes('species') ? {
            species: taxonomy.species
          } : null)
        };
        await insert(taxonomiesDB, taxonomyEntry);
      }
      return taxonomiesDB;
    }

    async function searchActivitiesDB(activitiesDB, activities) {
      let terms = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : '';
      let options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
      const {
        maxNbEntries = 100,
        minScore = 0.5,
        relevance = {
          k: 1.2,
          b: 0.75,
          d: 0.5
        },
        tolerance = 1,
        queryFields = ['assay']
      } = options;
      if (terms === '') {
        throw new Error('terms is empty');
      }
      let queryResult = await search(activitiesDB, {
        term: terms,
        relevance,
        properties: queryFields,
        tolerance,
        limit: maxNbEntries
      });
      let results = [];
      for (let result of queryResult.hits) {
        let id = result.document.$id;
        let assay = result.document.assay;
        let activityDocument = activities.find(activity => {
          return activity.$id === id && activity.data.assay === assay;
        });
        results.push({
          ...activityDocument,
          score: result.score
        });
      }
      results = results.filter(result => result.score >= minScore);
      if (results.length > maxNbEntries) {
        results.length = maxNbEntries;
      }
      results.sort((a, b) => b.score - a.score);
      return results;
    }

    async function searchPatentsDB(patentsDB, patents) {
      let terms = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : '';
      let options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
      const {
        maxNbEntries = 100,
        minScore = 0.5,
        relevance = {
          k: 1.2,
          b: 0.75,
          d: 0.5
        },
        tolerance = 1,
        queryFields = ['title', 'abstract'],
        boostFields = {
          title: 2,
          abstract: 1
        },
        abstractsLimit = 1000
      } = options;
      if (patents.length > abstractsLimit) {
        queryFields.splice(queryFields.indexOf('abstract'), 1);
      }
      if (terms === '') {
        throw new Error('terms is empty');
      }
      let queryResult = await search(patentsDB, {
        term: terms,
        properties: queryFields,
        boost: boostFields,
        relevance,
        tolerance,
        limit: maxNbEntries
      });
      queryResult.hits.map(item => {
        let nbCompounds = 2;
        if (item.document.nbCompounds) {
          nbCompounds = +Number(item.document.nbCompounds);
        }
        item.score = item.score / Math.log2(nbCompounds);
        return item;
      });
      let results = [];
      for (let result of queryResult.hits) {
        let id = result.document.$id;
        // to do this could be maybe improved using Map
        let patentsDocument = patents.find(patent => patent.$id === id);
        results.push({
          ...patentsDocument,
          score: result.score
        });
      }
      results = results.filter(result => result.score >= minScore);
      if (results.length > maxNbEntries) {
        results.length = maxNbEntries;
      }
      results.sort((a, b) => b.score - a.score);
      return results;
    }

    async function searchPubmedsDB(pubmedsDB, pubmeds) {
      let terms = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : '';
      let options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
      const {
        maxNbEntries = 100,
        minScore = 0.5,
        relevance = {
          k: 1.2,
          b: 0.75,
          d: 0.5
        },
        tolerance = 1,
        queryFields = ['title', 'abstract', 'meshHeadings'],
        boostFields = {
          title: 2,
          abstract: 1,
          meshHeadings: 1
        },
        abstractsLimit = 1000
      } = options;
      if (terms === '') {
        throw new Error('terms is empty');
      }
      if (pubmeds.length > abstractsLimit) {
        queryFields.splice(queryFields.indexOf('abstract'), 1);
      }
      let queryResult = await search(pubmedsDB, {
        term: terms,
        properties: queryFields,
        boost: boostFields,
        relevance,
        limit: maxNbEntries,
        tolerance
      });
      queryResult.hits.map(item => {
        let nbCompounds = 2;
        if (item.document.nbCompounds) {
          nbCompounds = +Number(item.document.nbCompounds);
        }
        item.score = item.score / Math.log2(nbCompounds);
        return item;
      });
      let results = [];
      for (let result of queryResult.hits) {
        let id = result.document.$id;
        let pubmedDocument = pubmeds.find(pubmed => pubmed.$id === id);
        results.push({
          ...pubmedDocument,
          score: result.score
        });
      }
      results = results.filter(result => result.score >= minScore);
      if (results.length > maxNbEntries) {
        results.length = maxNbEntries;
      }
      results.sort((a, b) => b.score - a.score);
      return results;
    }

    async function searchTaxonomiesDB(taxonomiesDB, taxonomies) {
      let terms = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : '';
      let options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
      const {
        queryFields = ['superKingdom', 'kingdom', 'phylum', 'class', 'order', 'family', 'genus', 'species'],
        maxNbEntries = 50,
        minScore = 0.5,
        tolerance = 1
      } = options;
      if (terms === '') {
        throw new Error('terms is empty');
      }
      let queryResult = await search(taxonomiesDB, {
        term: terms,
        properties: queryFields,
        tolerance,
        limit: maxNbEntries
      });
      let results = [];
      for (let result of queryResult.hits) {
        let id = result.document.$id;
        let species = result.document?.species;
        let genus = result.document?.genus;
        let family = result.document?.family;
        let order = result.document?.order;
        let phylum = result.document?.phylum;
        let kingdom = result.document?.kingdom;
        let superKingdom = result.document?.superKingdom;
        let taxonomiesDocument = taxonomies.find(taxonomy => {
          return taxonomy.dbRef.$id === id && (taxonomy?.species === species || taxonomy?.genus === genus || taxonomy?.family === family || taxonomy?.order === order || taxonomy?.phylum === phylum || taxonomy?.kingdom === kingdom || taxonomy?.superKingdom === superKingdom);
        });
        results.push({
          ...taxonomiesDocument,
          score: result.score
        });
      }
      results = results.filter(result => result.score >= minScore);
      if (results.length > maxNbEntries) {
        results.length = maxNbEntries;
      }
      results.sort((a, b) => b.score - a.score);
      return results;
    }

    function summarizeEmptyTerms(activities, patents, pubmeds, taxonomies, options) {
      activities = activities.slice();
      patents = patents.slice();
      pubmeds = pubmeds.slice();
      taxonomies = taxonomies.slice();
      // PATENTS
      const patentsMaxNbEntries = options?.patents?.maxNbEntries || 100;
      patents.sort((a, b) => {
        const nbCompoundsEntryA = a.data.nbCompounds + 2 || 2;
        const nbCompoundsEntryB = b.data.nbCompounds + 2 || 2;
        return 1 / Math.log2(nbCompoundsEntryB) - 1 / Math.log2(nbCompoundsEntryA);
      });
      if (patents.length > patentsMaxNbEntries) {
        patents.length = patentsMaxNbEntries;
      }
      // PUBMEDS
      const pubmedsMaxNbEntries = options?.pubmeds?.maxNbEntries || 100;
      pubmeds.sort((a, b) => {
        const nbCompoundsEntryA = a.data.compounds.length + 2 || 2;
        const nbCompoundsEntryB = b.data.compounds.length + 2 || 2;
        return 1 / Math.log2(nbCompoundsEntryB) - 1 / Math.log2(nbCompoundsEntryA);
      });
      if (pubmeds.length > pubmedsMaxNbEntries) {
        pubmeds.length = pubmedsMaxNbEntries;
      }
      // ACTIVITIES
      const activitiesMaxNbEntries = options?.activities?.maxNbEntries || 100;
      if (activities.length > activitiesMaxNbEntries) {
        activities.length = activitiesMaxNbEntries;
      }
      // TAXONOMIES
      const taxonomiesMaxNbEntries = options?.taxonomies?.maxNbEntries || 50;
      if (taxonomies.length > taxonomiesMaxNbEntries) {
        taxonomies.length = activitiesMaxNbEntries;
      }
      return {
        activities,
        patents,
        pubmeds,
        taxonomies
      };
    }

    class ActiveOrNaturalSummarizer {
      /**
       * @description Summarize the active or natural details with the given terms
       * @param {Object} activeOrNaturalDetails The active or natural details to summarize
       * @param {Object} [options={}] The options to use for the search
       * @param {Object} [options.activities={}] The options to use for the activities search
       * @param {number} [options.activities.minScore=0.5] - Minimum score for an entry to be returned
       * @param {number} [options.activities.maxNbEntries=100] - Maximum number of entries to return
       * @param {object} [options.activities.relevance={ k: 1.2, b: 0.75, d: 0.5 }] - BM25 algorithm {k: Term frequency saturation parameter, b: Length normalization parameter, d:Frequency normalization lower bound}
       * @param {number} [options.activities.tolerance=1] -Typo Tolerance following the Levenshtein algorithm
       * @param {string[]} [options.activities.queryFields=['assay']] - Fields to query
       * @param {Object} [options.taxonomies={}] The options to use for the activities search
       * @param {number} [options.taxonomies.minScore=0.5] - Minimum score for an entry to be returned
       * @param {number} [options.taxonomies.maxNbEntries=50] - Maximum number of entries to return
       * @param {number} [options.taxonomies.tolerance=1] -Typo Tolerance following the Levenshtein algorithm
       * @param {object} [options.patents={}] - Options for patents
       * @param {number} [options.patents.abstractsLimit=1000] - If more than this number of abstracts, the search will be done on the without abstracts
       * @param {number} [options.patents.maxNbEntries=100] - Maximum number of entries to return
       * @param {number} [options.patents.inScore=0.5] - Minimum score for an entry to be returned
       * @param {object} [options.patents.relevance={ k: 1.2, b: 0.75, d: 0.5 }] - BM25 algorithm {k: Term frequency saturation parameter, b: Length normalization parameter, d:Frequency normalization lower bound}
       * @param {number} [options.patents.tolerance=1] -Typo Tolerance following the Levenshtein algorithm
       * @param {string[]} [options.patents.queryFields=['title', 'abstract']] - Fields to query
       * @param {object} [options.patents.boostFields={ title: 2, abstract: 1 }] - Fields weights, higher weight means higher importance
       * @param {object} [options.pubmeds={}] - Options for pubmeds
       * @param {number} [options.pubmeds.abstractsLimit=1000] - If more than this number of abstracts, the search will be done on the without abstracts
       * @param {number} [options.pubmeds.minScore=0.5] - Minimum score for an entry to be returned
       * @param {number} [options.pubmeds.maxNbEntries=100] - Maximum number of entries to return
       * @param {object} [options.pubmeds.relevance={ k: 1.2, b: 0.75, d: 0.5 }] - BM25 algorithm {k: Term frequency saturation parameter, b: Length normalization parameter, d:Frequency normalization lower bound}
       * @param {number} [options.pubmeds.tolerance=1] -Typo Tolerance following the Levenshtein algorithm
       * @param {object} [options.pubmeds.boostFields={ title: 2, abstract: 1, meshHeadings: 1 }]  - Fields weights, higher weight means higher importance
       * @param {string[]} [options.pubmeds.queryFields=['title', 'abstract', 'meshHeadings']] - Fields to query
       * @returns
       */
      constructor(activeOrNaturalDetails) {
        let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
        this.options = options;
        this.activeOrNaturalDetails = activeOrNaturalDetails;
        this.activities = activeOrNaturalDetails.data.activities;
        this.patents = activeOrNaturalDetails.data.patents;
        this.pubmeds = activeOrNaturalDetails.data.pubmeds;
        this.taxonomies = activeOrNaturalDetails.data.taxonomies;
        this.isInitialized = false;
      }
      /**
       * @description Summarize the active or natural details with the given terms
       * @param {string} terms The terms to search
       * @returns {Promise<Object>} The summarized active or natural details
       */
      async summarize(terms) {
        if (terms === '') {
          const {
            patents,
            pubmeds,
            activities
          } = summarizeEmptyTerms(this.activities, this.patents, this.pubmeds, this.taxonomies, this.options);
          return {
            ...this.activeOrNaturalDetails,
            data: {
              ...this.activeOrNaturalDetails.data,
              activities,
              patents,
              pubmeds
            }
          };
        }
        if (!this.isInitialized) {
          await this.createDB();
          this.isInitialized = true;
        }
        return {
          ...this.activeOrNaturalDetails,
          data: {
            ...this.activeOrNaturalDetails.data,
            activities: await searchActivitiesDB(this.activitiesDB, this.activities, terms, this.options?.activities),
            patents: await searchPatentsDB(this.patentsDB, this.patents, terms, this.options?.patents),
            pubmeds: await searchPubmedsDB(this.pubmedsDB, this.pubmeds, terms, this.options?.pubmeds),
            taxonomies: await searchTaxonomiesDB(this.taxonomiesDB, this.taxonomies, terms, this.options?.taxonomies)
          }
        };
      }
      async createDB() {
        this.pubmedsDB = await getPubmedsDB(this.pubmeds, this.options.pubmeds);
        this.activitiesDB = await getActivitiesDB(this.activities, this.options.activities);
        this.patentsDB = await getPatentsDB(this.patents, this.options.patents);
        this.taxonomiesDB = await getTaxonomiesDB(this.taxonomies, this.options.taxonomies);
      }
    }

    const Nucleotide = NucleotidePkg;
    const Report = ReportPkg;
    const Peptide = PeptidePkg;
    const MFSDeconvolution = MFSDeconvolutionPkg;
    EMDB$1.massShifts = massShifts;
    EMDB$1.prototype.massShifts = massShifts;
    const EMDB = EMDB$1;
    Spectrum$1.JsGraph = JsGraph;
    Spectrum$1.prototype.JsGraph = JsGraph;
    const Spectrum = Spectrum$1;
    const MSComparator = MSComparator$1;
    const getBestPeaks = getBestPeaks$1;

    exports.ActiveOrNaturalSummarizer = ActiveOrNaturalSummarizer;
    exports.EMDB = EMDB;
    exports.IsotopicDistribution = IsotopicDistribution;
    exports.MF = MF;
    exports.MFSDeconvolution = MFSDeconvolution;
    exports.MSComparator = MSComparator;
    exports.Nucleotide = Nucleotide;
    exports.OctoChemDB = OctoChemDB;
    exports.Peptide = Peptide;
    exports.Report = Report;
    exports.Spectrum = Spectrum;
    exports.atomSorter = atomSorter$1;
    exports.elements = elements$2;
    exports.elementsObject = elementsObject;
    exports.ensureCase = ensureCase;
    exports.generateMFs = generateMFs;
    exports.getBestPeaks = getBestPeaks;
    exports.getRangesForFragment = getRangesForFragment;
    exports.groups = groups;
    exports.groupsObject = groupsObject;
    exports.mfFromEA = mfFromEA;
    exports.preprocessRanges = preprocessRanges;

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

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