/**
 * chromatography - Tools for storing, searching and analyzing GC/MS data
 * @version v5.2.0
 * @link https://github.com/cheminfo/chromatography#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.Chromatography = {}));
}(this, (function (exports) { 'use strict';

  const toString$4 = Object.prototype.toString;
  function isAnyArray(object) {
    return toString$4.call(object).endsWith('Array]');
  }

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

  function getAugmentedNamespace(n) {
  	if (n.__esModule) return n;
  	var a = Object.defineProperty({}, '__esModule', {value: true});
  	Object.keys(n).forEach(function (k) {
  		var d = Object.getOwnPropertyDescriptor(n, k);
  		Object.defineProperty(a, k, d.get ? d : {
  			enumerable: true,
  			get: function () {
  				return n[k];
  			}
  		});
  	});
  	return a;
  }

  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 median$1 = medianQuickselect_min.exports;

  function median(input) {
    if (!isAnyArray(input)) {
      throw new TypeError('input must be an array');
    }

    if (input.length === 0) {
      throw new TypeError('input must not be empty');
    }

    return median$1(input.slice());
  }

  /**
   * Returns the closest index of a `target` in an ordered array
   * @param {array<Number>} array
   * @param {number} target
   */
  function xFindClosestIndex$2(array, target) {
    let low = 0;
    let high = array.length - 1;
    let middle = 0;

    while (high - low > 1) {
      middle = low + (high - low >> 1);

      if (array[middle] < target) {
        low = middle;
      } else if (array[middle] > target) {
        high = middle;
      } else {
        return middle;
      }
    }

    if (low < array.length - 1) {
      if (Math.abs(target - array[low]) < Math.abs(array[low + 1] - target)) {
        return low;
      } else {
        return low + 1;
      }
    } else {
      return low;
    }
  }

  /**
   * Returns an object with {fromIndex, toIndex} for a specific from / to
   * @param {array} x
   * @param {object} [options={}]
   * @param {number} [options.from] - First value for xyIntegration in the X scale
   * @param {number} [options.fromIndex=0] - First point for xyIntegration
   * @param {number} [options.to] - Last value for xyIntegration in the X scale
   * @param {number} [options.toIndex=x.length-1] - Last point for xyIntegration
   */

  function xGetFromToIndex$1(x, options = {}) {
    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 > toIndex) [fromIndex, toIndex] = [toIndex, fromIndex];
    return {
      fromIndex,
      toIndex
    };
  }

  function _typeof(obj) {
    "@babel/helpers - typeof";

    if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
      _typeof = function (obj) {
        return typeof obj;
      };
    } else {
      _typeof = function (obj) {
        return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
      };
    }

    return _typeof(obj);
  }
  /**
   * Fill an array with sequential numbers
   * @param {Array<number>} [input] - optional destination array (if not provided a new array will be created)
   * @param {object} [options={}]
   * @param {number} [options.from=0] - first value in the array
   * @param {number} [options.to=10] - last value in the array
   * @param {number} [options.size=input.length] - size of the array (if not provided calculated from step)
   * @param {number} [options.step] - if not provided calculated from size
   * @return {Array<number>}
   */


  function sequentialFill() {
    var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
    var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};

    if (_typeof(input) === 'object' && !isAnyArray(input)) {
      options = input;
      input = [];
    }

    if (!isAnyArray(input)) {
      throw new TypeError('input must be an array');
    }

    var _options = options,
        _options$from = _options.from,
        from = _options$from === void 0 ? 0 : _options$from,
        _options$to = _options.to,
        to = _options$to === void 0 ? 10 : _options$to,
        _options$size = _options.size,
        size = _options$size === void 0 ? input.length : _options$size,
        step = _options.step;

    if (size !== 0 && step) {
      throw new Error('step is defined by the array size');
    }

    if (!size) {
      if (step) {
        size = Math.floor((to - from) / step) + 1;
      } else {
        size = to - from + 1;
      }
    }

    if (!step && size) {
      step = (to - from) / (size - 1);
    }

    if (Array.isArray(input)) {
      // only works with normal array
      input.length = 0;

      for (var i = 0; i < size; i++) {
        input.push(from);
        from += step;
      }
    } else {
      if (input.length !== size) {
        throw new Error('sequentialFill typed array must have the correct length');
      }

      for (var _i = 0; _i < size; _i++) {
        input[_i] = from;
        from += step;
      }
    }

    return input;
  }

  /**
   * Returns true if x is monotone
   * @param {Array} array
   * @return {boolean}
   */
  function xIsMonotone$1(array) {
    if (array.length <= 2) {
      return true;
    }

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

      return true;
    }

    if (array[0] < array[array.length - 1]) {
      for (let i = 0; i < array.length - 1; i++) {
        if (array[i] >= array[i + 1]) return false;
      }
    } else {
      for (let i = 0; i < array.length - 1; i++) {
        if (array[i] <= array[i + 1]) return false;
      }
    }

    return true;
  }

  var d3Array = {exports: {}};

  (function (module, exports) {
    (function (global, factory) {
      factory(exports) ;
    })(commonjsGlobal, function (exports) {

      function ascending(a, b) {
        return a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN;
      }

      function bisector(compare) {
        if (compare.length === 1) compare = ascendingComparator(compare);
        return {
          left: function (a, x, lo, hi) {
            if (lo == null) lo = 0;
            if (hi == null) hi = a.length;

            while (lo < hi) {
              var mid = lo + hi >>> 1;
              if (compare(a[mid], x) < 0) lo = mid + 1;else hi = mid;
            }

            return lo;
          },
          right: function (a, x, lo, hi) {
            if (lo == null) lo = 0;
            if (hi == null) hi = a.length;

            while (lo < hi) {
              var mid = lo + hi >>> 1;
              if (compare(a[mid], x) > 0) hi = mid;else lo = mid + 1;
            }

            return lo;
          }
        };
      }

      function ascendingComparator(f) {
        return function (d, x) {
          return ascending(f(d), x);
        };
      }

      var ascendingBisect = bisector(ascending);
      var bisectRight = ascendingBisect.right;
      var bisectLeft = ascendingBisect.left;

      function descending(a, b) {
        return b < a ? -1 : b > a ? 1 : b >= a ? 0 : NaN;
      }

      function number$1(x) {
        return x === null ? NaN : +x;
      }

      function variance(array, f) {
        var n = array.length,
            m = 0,
            a,
            d,
            s = 0,
            i = -1,
            j = 0;

        if (f == null) {
          while (++i < n) {
            if (!isNaN(a = number$1(array[i]))) {
              d = a - m;
              m += d / ++j;
              s += d * (a - m);
            }
          }
        } else {
          while (++i < n) {
            if (!isNaN(a = number$1(f(array[i], i, array)))) {
              d = a - m;
              m += d / ++j;
              s += d * (a - m);
            }
          }
        }

        if (j > 1) return s / (j - 1);
      }

      function deviation(array, f) {
        var v = variance(array, f);
        return v ? Math.sqrt(v) : v;
      }

      function extent(array, f) {
        var i = -1,
            n = array.length,
            a,
            b,
            c;

        if (f == null) {
          while (++i < n) if ((b = array[i]) != null && b >= b) {
            a = c = b;
            break;
          }

          while (++i < n) if ((b = array[i]) != null) {
            if (a > b) a = b;
            if (c < b) c = b;
          }
        } else {
          while (++i < n) if ((b = f(array[i], i, array)) != null && b >= b) {
            a = c = b;
            break;
          }

          while (++i < n) if ((b = f(array[i], i, array)) != null) {
            if (a > b) a = b;
            if (c < b) c = b;
          }
        }

        return [a, c];
      }

      function constant(x) {
        return function () {
          return x;
        };
      }

      function identity(x) {
        return x;
      }

      function range(start, stop, step) {
        start = +start, stop = +stop, step = (n = arguments.length) < 2 ? (stop = start, start = 0, 1) : n < 3 ? 1 : +step;
        var i = -1,
            n = Math.max(0, Math.ceil((stop - start) / step)) | 0,
            range = new Array(n);

        while (++i < n) {
          range[i] = start + i * step;
        }

        return range;
      }

      var e10 = Math.sqrt(50);
      var e5 = Math.sqrt(10);
      var e2 = Math.sqrt(2);

      function ticks(start, stop, count) {
        var step = tickStep(start, stop, count);
        return range(Math.ceil(start / step) * step, Math.floor(stop / step) * step + step / 2, // inclusive
        step);
      }

      function tickStep(start, stop, count) {
        var step0 = Math.abs(stop - start) / Math.max(0, count),
            step1 = Math.pow(10, Math.floor(Math.log(step0) / Math.LN10)),
            error = step0 / step1;
        if (error >= e10) step1 *= 10;else if (error >= e5) step1 *= 5;else if (error >= e2) step1 *= 2;
        return stop < start ? -step1 : step1;
      }

      function sturges(values) {
        return Math.ceil(Math.log(values.length) / Math.LN2) + 1;
      }

      function number(x) {
        return +x;
      }

      function histogram() {
        var value = identity,
            domain = extent,
            threshold = sturges;

        function histogram(data) {
          var i,
              n = data.length,
              x,
              values = new Array(n); // Coerce values to numbers.

          for (i = 0; i < n; ++i) {
            values[i] = +value(data[i], i, data);
          }

          var xz = domain(values),
              x0 = +xz[0],
              x1 = +xz[1],
              tz = threshold(values, x0, x1); // Convert number of thresholds into uniform thresholds.

          if (!Array.isArray(tz)) tz = ticks(x0, x1, +tz); // Coerce thresholds to numbers, ignoring any outside the domain.

          var m = tz.length;

          for (i = 0; i < m; ++i) tz[i] = +tz[i];

          while (tz[0] <= x0) tz.shift(), --m;

          while (tz[m - 1] >= x1) tz.pop(), --m;

          var bins = new Array(m + 1),
              bin; // Initialize bins.

          for (i = 0; i <= m; ++i) {
            bin = bins[i] = [];
            bin.x0 = i > 0 ? tz[i - 1] : x0;
            bin.x1 = i < m ? tz[i] : x1;
          } // Assign data to bins by value, ignoring any outside the domain.


          for (i = 0; i < n; ++i) {
            x = values[i];

            if (x0 <= x && x <= x1) {
              bins[bisectRight(tz, x, 0, m)].push(data[i]);
            }
          }

          return bins;
        }

        histogram.value = function (_) {
          return arguments.length ? (value = typeof _ === "function" ? _ : constant(+_), histogram) : value;
        };

        histogram.domain = function (_) {
          return arguments.length ? (domain = typeof _ === "function" ? _ : constant([+_[0], +_[1]]), histogram) : domain;
        };

        histogram.thresholds = function (_) {
          if (!arguments.length) return threshold;
          threshold = typeof _ === "function" ? _ : Array.isArray(_) ? constant(Array.prototype.map.call(_, number)) : constant(+_);
          return histogram;
        };

        return histogram;
      }

      function quantile(array, p, f) {
        if (f == null) f = number$1;
        if (!(n = array.length)) return;
        if ((p = +p) <= 0 || n < 2) return +f(array[0], 0, array);
        if (p >= 1) return +f(array[n - 1], n - 1, array);
        var n,
            h = (n - 1) * p,
            i = Math.floor(h),
            a = +f(array[i], i, array),
            b = +f(array[i + 1], i + 1, array);
        return a + (b - a) * (h - i);
      }

      function freedmanDiaconis(values, min, max) {
        values.sort(ascending);
        return Math.ceil((max - min) / (2 * (quantile(values, 0.75) - quantile(values, 0.25)) * Math.pow(values.length, -1 / 3)));
      }

      function scott(values, min, max) {
        return Math.ceil((max - min) / (3.5 * deviation(values) * Math.pow(values.length, -1 / 3)));
      }

      function max(array, f) {
        var i = -1,
            n = array.length,
            a,
            b;

        if (f == null) {
          while (++i < n) if ((b = array[i]) != null && b >= b) {
            a = b;
            break;
          }

          while (++i < n) if ((b = array[i]) != null && b > a) a = b;
        } else {
          while (++i < n) if ((b = f(array[i], i, array)) != null && b >= b) {
            a = b;
            break;
          }

          while (++i < n) if ((b = f(array[i], i, array)) != null && b > a) a = b;
        }

        return a;
      }

      function mean(array, f) {
        var s = 0,
            n = array.length,
            a,
            i = -1,
            j = n;

        if (f == null) {
          while (++i < n) if (!isNaN(a = number$1(array[i]))) s += a;else --j;
        } else {
          while (++i < n) if (!isNaN(a = number$1(f(array[i], i, array)))) s += a;else --j;
        }

        if (j) return s / j;
      }

      function median(array, f) {
        var numbers = [],
            n = array.length,
            a,
            i = -1;

        if (f == null) {
          while (++i < n) if (!isNaN(a = number$1(array[i]))) numbers.push(a);
        } else {
          while (++i < n) if (!isNaN(a = number$1(f(array[i], i, array)))) numbers.push(a);
        }

        return quantile(numbers.sort(ascending), 0.5);
      }

      function merge(arrays) {
        var n = arrays.length,
            m,
            i = -1,
            j = 0,
            merged,
            array;

        while (++i < n) j += arrays[i].length;

        merged = new Array(j);

        while (--n >= 0) {
          array = arrays[n];
          m = array.length;

          while (--m >= 0) {
            merged[--j] = array[m];
          }
        }

        return merged;
      }

      function min(array, f) {
        var i = -1,
            n = array.length,
            a,
            b;

        if (f == null) {
          while (++i < n) if ((b = array[i]) != null && b >= b) {
            a = b;
            break;
          }

          while (++i < n) if ((b = array[i]) != null && a > b) a = b;
        } else {
          while (++i < n) if ((b = f(array[i], i, array)) != null && b >= b) {
            a = b;
            break;
          }

          while (++i < n) if ((b = f(array[i], i, array)) != null && a > b) a = b;
        }

        return a;
      }

      function pairs(array) {
        var i = 0,
            n = array.length - 1,
            p = array[0],
            pairs = new Array(n < 0 ? 0 : n);

        while (i < n) pairs[i] = [p, p = array[++i]];

        return pairs;
      }

      function permute(array, indexes) {
        var i = indexes.length,
            permutes = new Array(i);

        while (i--) permutes[i] = array[indexes[i]];

        return permutes;
      }

      function scan(array, compare) {
        if (!(n = array.length)) return;
        var i = 0,
            n,
            j = 0,
            xi,
            xj = array[j];
        if (!compare) compare = ascending;

        while (++i < n) if (compare(xi = array[i], xj) < 0 || compare(xj, xj) !== 0) xj = xi, j = i;

        if (compare(xj, xj) === 0) return j;
      }

      function shuffle(array, i0, i1) {
        var m = (i1 == null ? array.length : i1) - (i0 = i0 == null ? 0 : +i0),
            t,
            i;

        while (m) {
          i = Math.random() * m-- | 0;
          t = array[m + i0];
          array[m + i0] = array[i + i0];
          array[i + i0] = t;
        }

        return array;
      }

      function sum(array, f) {
        var s = 0,
            n = array.length,
            a,
            i = -1;

        if (f == null) {
          while (++i < n) if (a = +array[i]) s += a; // Note: zero and null are equivalent.

        } else {
          while (++i < n) if (a = +f(array[i], i, array)) s += a;
        }

        return s;
      }

      function transpose(matrix) {
        if (!(n = matrix.length)) return [];

        for (var i = -1, m = min(matrix, length), transpose = new Array(m); ++i < m;) {
          for (var j = -1, n, row = transpose[i] = new Array(n); ++j < n;) {
            row[j] = matrix[j][i];
          }
        }

        return transpose;
      }

      function length(d) {
        return d.length;
      }

      function zip() {
        return transpose(arguments);
      }

      var version = "0.7.1";
      exports.version = version;
      exports.bisect = bisectRight;
      exports.bisectRight = bisectRight;
      exports.bisectLeft = bisectLeft;
      exports.ascending = ascending;
      exports.bisector = bisector;
      exports.descending = descending;
      exports.deviation = deviation;
      exports.extent = extent;
      exports.histogram = histogram;
      exports.thresholdFreedmanDiaconis = freedmanDiaconis;
      exports.thresholdScott = scott;
      exports.thresholdSturges = sturges;
      exports.max = max;
      exports.mean = mean;
      exports.median = median;
      exports.merge = merge;
      exports.min = min;
      exports.pairs = pairs;
      exports.permute = permute;
      exports.quantile = quantile;
      exports.range = range;
      exports.scan = scan;
      exports.shuffle = shuffle;
      exports.sum = sum;
      exports.ticks = ticks;
      exports.tickStep = tickStep;
      exports.transpose = transpose;
      exports.variance = variance;
      exports.zip = zip;
    });
  })(d3Array, d3Array.exports);

  const {
    bisectRight
  } = d3Array.exports;

  const quincunx = (u, v, w, q) => {
    const n = u.length - 1;
    u[0] = 0;
    v[0] = 0;
    w[0] = 0;
    v[1] = v[1] / u[1];
    w[1] = w[1] / u[1];

    for (let i = 2; i < n; ++i) {
      u[i] = u[i] - u[i - 2] * w[i - 2] * w[i - 2] - u[i - 1] * v[i - 1] * v[i - 1];
      v[i] = (v[i] - u[i - 1] * v[i - 1] * w[i - 1]) / u[i];
      w[i] = w[i] / u[i];
    }

    for (let i = 2; i < n; ++i) {
      q[i] = q[i] - v[i - 1] * q[i - 1] - w[i - 2] * q[i - 2];
    }

    for (let i = 1; i < n; ++i) {
      q[i] = q[i] / u[i];
    }

    q[n - 2] = q[n - 2] - v[n - 2] * q[n - 1];

    for (let i = n - 3; i > 0; --i) {
      q[i] = q[i] - v[i] * q[i + 1] - w[i] * q[i + 2];
    }
  };

  const smoothingSpline = (x, y, sigma, lambda) => {
    const n = x.length - 1;
    const h = new Array(n + 1);
    const r = new Array(n + 1);
    const f = new Array(n + 1);
    const p = new Array(n + 1);
    const q = new Array(n + 1);
    const u = new Array(n + 1);
    const v = new Array(n + 1);
    const w = new Array(n + 1);
    const params = x.map(() => [0, 0, 0, 0]);
    params.pop();
    const mu = 2 * (1 - lambda) / (3 * lambda);

    for (let i = 0; i < n; ++i) {
      h[i] = x[i + 1] - x[i];
      r[i] = 3 / h[i];
    }

    q[0] = 0;

    for (let i = 1; i < n; ++i) {
      f[i] = -(r[i - 1] + r[i]);
      p[i] = 2 * (x[i + 1] - x[i - 1]);
      q[i] = 3 * (y[i + 1] - y[i]) / h[i] - 3 * (y[i] - y[i - 1]) / h[i - 1];
    }

    q[n] = 0;

    for (let i = 1; i < n; ++i) {
      u[i] = r[i - 1] * r[i - 1] * sigma[i - 1] + f[i] * f[i] * sigma[i] + r[i] * r[i] * sigma[i + 1];
      u[i] = mu * u[i] + p[i];
    }

    for (let i = 1; i < n - 1; ++i) {
      v[i] = f[i] * r[i] * sigma[i] + r[i] * f[i + 1] * sigma[i + 1];
      v[i] = mu * v[i] + h[i];
    }

    for (let i = 1; i < n - 2; ++i) {
      w[i] = mu * r[i] * r[i + 1] * sigma[i + 1];
    }

    quincunx(u, v, w, q);
    params[0][3] = y[0] - mu * r[0] * q[1] * sigma[0];
    params[1][3] = y[1] - mu * (f[1] * q[1] + r[1] * q[2]) * sigma[0];
    params[0][0] = q[1] / (3 * h[0]);
    params[0][1] = 0;
    params[0][2] = (params[1][3] - params[0][3]) / h[0] - q[1] * h[0] / 3;
    r[0] = 0;

    for (let i = 1; i < n; ++i) {
      params[i][0] = (q[i + 1] - q[i]) / (3 * h[i]);
      params[i][1] = q[i];
      params[i][2] = (q[i] + q[i - 1]) * h[i - 1] + params[i - 1][2];
      params[i][3] = r[i - 1] * q[i - 1] + f[i] * q[i] + r[i] * q[i + 1];
      params[i][3] = y[i] - mu * params[i][3] * sigma[i];
    }

    return params;
  };

  class SplineInterpolator {
    constructor(xIn, yIn, lambda = 1) {
      const indices = xIn.map((_, i) => i);
      indices.sort((i, j) => xIn[i] - xIn[j]);
      const x = indices.map(i => xIn[i]);
      const y = indices.map(i => yIn[i]);
      const n = indices.length;
      const sigma = indices.map(() => 1);
      this.n = n;
      this.x = x;
      this.y = y;
      this.params = smoothingSpline(x, y, sigma, lambda);
    }

    interpolate(v) {
      if (v === this.x[this.n - 1]) {
        return this.y[this.n - 1];
      }

      const i = Math.min(Math.max(0, bisectRight(this.x, v) - 1), this.n - 2);
      const [a, b, c, d] = this.params[i];
      v = v - this.x[i];
      return a * v * v * v + b * v * v + c * v + d;
    }

    max(step = 100) {
      const xStart = this.x[0];
      const xStop = this.x[this.n - 1];
      const delta = (xStop - xStart) / step;
      let maxValue = -Infinity;

      for (let i = 0, x = xStart; i < step; ++i, x += delta) {
        const y = this.interpolate(x);

        if (y > maxValue) {
          maxValue = y;
        }
      }

      return maxValue;
    }

    min(step = 100) {
      const xStart = this.x[0];
      const xStop = this.x[this.n - 1];
      const delta = (xStop - xStart) / step;
      let minValue = Infinity;

      for (let i = 0, x = xStart; i < step; ++i, x += delta) {
        const y = this.interpolate(x);

        if (y < minValue) {
          minValue = y;
        }
      }

      return minValue;
    }

    domain() {
      return [this.x[0], this.x[this.x.length - 1]];
    }

    range() {
      return [this.min(), this.max()];
    }

    curve(nInterval, domain = null) {
      domain = domain || this.domain();
      const delta = (domain[1] - domain[0]) / (nInterval - 1);
      const vals = new Array(nInterval);

      for (let i = 0; i < nInterval; ++i) {
        const x = delta * i + domain[0];
        vals[i] = [x, this.interpolate(x)];
      }

      return vals;
    }

  }

  var splineInterpolator = SplineInterpolator;
  var SplineInterpolator$1 = splineInterpolator;

  function sum$3(input) {
    if (!isAnyArray(input)) {
      throw new TypeError('input must be an array');
    }

    if (input.length === 0) {
      throw new TypeError('input must not be empty');
    }

    var sumValue = 0;

    for (var i = 0; i < input.length; i++) {
      sumValue += input[i];
    }

    return sumValue;
  }

  function mean(input) {
    return sum$3(input) / input.length;
  }

  function variance(values) {
    var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};

    if (!isAnyArray(values)) {
      throw new TypeError('input must be an array');
    }

    var _options$unbiased = options.unbiased,
        unbiased = _options$unbiased === void 0 ? true : _options$unbiased,
        _options$mean = options.mean,
        mean$1 = _options$mean === void 0 ? mean(values) : _options$mean;
    var sqrError = 0;

    for (var i = 0; i < values.length; i++) {
      var x = values[i] - mean$1;
      sqrError += x * x;
    }

    if (unbiased) {
      return sqrError / (values.length - 1);
    } else {
      return sqrError / values.length;
    }
  }

  function standardDeviation(values) {
    var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
    return Math.sqrt(variance(values, options));
  }

  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 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 FFT(size) {
    this.size = size | 0;
    if (this.size <= 1 || (this.size & this.size - 1) !== 0) throw new Error('FFT size must be a power of two and bigger than 1');
    this._csize = size << 1; // NOTE: Use of `var` is intentional for old V8 versions

    var table = new Array(this.size * 2);

    for (var i = 0; i < table.length; i += 2) {
      const angle = Math.PI * i / this.size;
      table[i] = Math.cos(angle);
      table[i + 1] = -Math.sin(angle);
    }

    this.table = table; // Find size's power of two

    var power = 0;

    for (var t = 1; this.size > t; t <<= 1) power++; // Calculate initial step's width:
    //   * If we are full radix-4 - it is 2x smaller to give inital len=8
    //   * Otherwise it is the same as `power` to give len=4


    this._width = power % 2 === 0 ? power - 1 : power; // Pre-compute bit-reversal patterns

    this._bitrev = new Array(1 << this._width);

    for (var j = 0; j < this._bitrev.length; j++) {
      this._bitrev[j] = 0;

      for (var shift = 0; shift < this._width; shift += 2) {
        var revShift = this._width - shift - 2;
        this._bitrev[j] |= (j >>> shift & 3) << revShift;
      }
    }

    this._out = null;
    this._data = null;
    this._inv = 0;
  }

  var fft = FFT;

  FFT.prototype.fromComplexArray = function fromComplexArray(complex, storage) {
    var res = storage || new Array(complex.length >>> 1);

    for (var i = 0; i < complex.length; i += 2) res[i >>> 1] = complex[i];

    return res;
  };

  FFT.prototype.createComplexArray = function createComplexArray() {
    const res = new Array(this._csize);

    for (var i = 0; i < res.length; i++) res[i] = 0;

    return res;
  };

  FFT.prototype.toComplexArray = function toComplexArray(input, storage) {
    var res = storage || this.createComplexArray();

    for (var i = 0; i < res.length; i += 2) {
      res[i] = input[i >>> 1];
      res[i + 1] = 0;
    }

    return res;
  };

  FFT.prototype.completeSpectrum = function completeSpectrum(spectrum) {
    var size = this._csize;
    var half = size >>> 1;

    for (var i = 2; i < half; i += 2) {
      spectrum[size - i] = spectrum[i];
      spectrum[size - i + 1] = -spectrum[i + 1];
    }
  };

  FFT.prototype.transform = function transform(out, data) {
    if (out === data) throw new Error('Input and output buffers must be different');
    this._out = out;
    this._data = data;
    this._inv = 0;

    this._transform4();

    this._out = null;
    this._data = null;
  };

  FFT.prototype.realTransform = function realTransform(out, data) {
    if (out === data) throw new Error('Input and output buffers must be different');
    this._out = out;
    this._data = data;
    this._inv = 0;

    this._realTransform4();

    this._out = null;
    this._data = null;
  };

  FFT.prototype.inverseTransform = function inverseTransform(out, data) {
    if (out === data) throw new Error('Input and output buffers must be different');
    this._out = out;
    this._data = data;
    this._inv = 1;

    this._transform4();

    for (var i = 0; i < out.length; i++) out[i] /= this.size;

    this._out = null;
    this._data = null;
  }; // radix-4 implementation
  //
  // NOTE: Uses of `var` are intentional for older V8 version that do not
  // support both `let compound assignments` and `const phi`


  FFT.prototype._transform4 = function _transform4() {
    var out = this._out;
    var size = this._csize; // Initial step (permute and transform)

    var width = this._width;
    var step = 1 << width;
    var len = size / step << 1;
    var outOff;
    var t;
    var bitrev = this._bitrev;

    if (len === 4) {
      for (outOff = 0, t = 0; outOff < size; outOff += len, t++) {
        const off = bitrev[t];

        this._singleTransform2(outOff, off, step);
      }
    } else {
      // len === 8
      for (outOff = 0, t = 0; outOff < size; outOff += len, t++) {
        const off = bitrev[t];

        this._singleTransform4(outOff, off, step);
      }
    } // Loop through steps in decreasing order


    var inv = this._inv ? -1 : 1;
    var table = this.table;

    for (step >>= 2; step >= 2; step >>= 2) {
      len = size / step << 1;
      var quarterLen = len >>> 2; // Loop through offsets in the data

      for (outOff = 0; outOff < size; outOff += len) {
        // Full case
        var limit = outOff + quarterLen;

        for (var i = outOff, k = 0; i < limit; i += 2, k += step) {
          const A = i;
          const B = A + quarterLen;
          const C = B + quarterLen;
          const D = C + quarterLen; // Original values

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

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

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

          const FAr = T0r + T2r;
          const FAi = T0i + T2i;
          const FCr = T0r - T2r;
          const FCi = T0i - T2i;
          const FBr = T1r + T3i;
          const FBi = T1i - T3r;
          const FDr = T1r - T3i;
          const FDi = T1i + T3r;
          out[A] = FAr;
          out[A + 1] = FAi;
          out[B] = FBr;
          out[B + 1] = FBi;
          out[C] = FCr;
          out[C + 1] = FCi;
          out[D] = FDr;
          out[D + 1] = FDi;
        }
      }
    }
  }; // radix-2 implementation
  //
  // NOTE: Only called for len=4


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


  FFT.prototype._singleTransform4 = function _singleTransform4(outOff, off, step) {
    const out = this._out;
    const data = this._data;
    const inv = this._inv ? -1 : 1;
    const step2 = step * 2;
    const step3 = step * 3; // Original values

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

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

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


  FFT.prototype._realTransform4 = function _realTransform4() {
    var out = this._out;
    var size = this._csize; // Initial step (permute and transform)

    var width = this._width;
    var step = 1 << width;
    var len = size / step << 1;
    var outOff;
    var t;
    var bitrev = this._bitrev;

    if (len === 4) {
      for (outOff = 0, t = 0; outOff < size; outOff += len, t++) {
        const off = bitrev[t];

        this._singleRealTransform2(outOff, off >>> 1, step >>> 1);
      }
    } else {
      // len === 8
      for (outOff = 0, t = 0; outOff < size; outOff += len, t++) {
        const off = bitrev[t];

        this._singleRealTransform4(outOff, off >>> 1, step >>> 1);
      }
    } // Loop through steps in decreasing order


    var inv = this._inv ? -1 : 1;
    var table = this.table;

    for (step >>= 2; step >= 2; step >>= 2) {
      len = size / step << 1;
      var halfLen = len >>> 1;
      var quarterLen = halfLen >>> 1;
      var hquarterLen = quarterLen >>> 1; // Loop through offsets in the data

      for (outOff = 0; outOff < size; outOff += len) {
        for (var i = 0, k = 0; i <= hquarterLen; i += 2, k += step) {
          var A = outOff + i;
          var B = A + quarterLen;
          var C = B + quarterLen;
          var D = C + quarterLen; // Original values

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

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

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

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

          if (i === 0) {
            var FCr = T0r - T2r;
            var FCi = T0i - T2i;
            out[C] = FCr;
            out[C + 1] = FCi;
            continue;
          } // Do not overwrite ourselves


          if (i === hquarterLen) continue; // In the flipped case:
          // MAi = -MAi
          // MBr=-MBi, MBi=-MBr
          // MCr=-MCr
          // MDr=MDi, MDi=MDr

          var ST0r = T1r;
          var ST0i = -T1i;
          var ST1r = T0r;
          var ST1i = -T0i;
          var ST2r = -inv * T3i;
          var ST2i = -inv * T3r;
          var ST3r = -inv * T2i;
          var ST3i = -inv * T2r;
          var SFAr = ST0r + ST2r;
          var SFAi = ST0i + ST2i;
          var SFBr = ST1r + ST3i;
          var SFBi = ST1i - ST3r;
          var SA = outOff + quarterLen - i;
          var SB = outOff + halfLen - i;
          out[SA] = SFAr;
          out[SA + 1] = SFAi;
          out[SB] = SFBr;
          out[SB + 1] = SFBi;
        }
      }
    }
  }; // radix-2 implementation
  //
  // NOTE: Only called for len=4


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


  FFT.prototype._singleRealTransform4 = function _singleRealTransform4(outOff, off, step) {
    const out = this._out;
    const data = this._data;
    const inv = this._inv ? -1 : 1;
    const step2 = step * 2;
    const step3 = step * 3; // Original values

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

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

    const FAr = T0r + T2r;
    const FBr = T1r;
    const FBi = -T3r;
    const FCr = T0r - T2r;
    const FDr = T1r;
    const FDi = T3r;
    out[outOff] = FAr;
    out[outOff + 1] = 0;
    out[outOff + 2] = FBr;
    out[outOff + 3] = FBi;
    out[outOff + 4] = FCr;
    out[outOff + 5] = 0;
    out[outOff + 6] = FDr;
    out[outOff + 7] = FDi;
  };

  var FFT$1 = fft;

  /**
   * Throw an error in no an object of x,y arrays
   * @param {DataXY} [data={}]
   */

  function xyCheck$1(data = {}) {
    if (!isAnyArray(data.x) || !isAnyArray(data.y)) {
      throw new Error('Data must be an object of x and y arrays');
    }

    if (data.x.length !== data.y.length) {
      throw new Error('The x and y arrays mush have the same length');
    }
  }

  /**
   * 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 {Array<DataXY>} spectra
   * @param {object} [options={}]
   * @param {number|function} [options.delta=1] The range in which the two x values of the spectra must be to be placed on the same line. It may also be a function that allows to change `delta` depending on the X values of the spectrum
   * @returns {DataXY}
   */
  function xyArrayWeightedMerge$1(spectra, options = {}) {
    let {
      delta = 1
    } = options;

    if (typeof delta === 'number') {
      let deltaNumber = delta;

      delta = () => deltaNumber;
    }

    spectra = spectra.filter(spectrum => spectrum.x.length > 0);
    if (spectra.length === 0) return {
      x: [],
      y: []
    };
    let x = [];
    let y = [];
    const positions = new Array(spectra.length).fill(0);
    const point = {
      x: 0,
      y: 0
    };
    nextValue$1(spectra, positions, point);
    let slot = {
      maxX: point.x + delta(point.x),
      sumY: point.y,
      sumXY: point.y * point.x
    };

    while (spectra.length !== 0) {
      nextValue$1(spectra, 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 (spectra.length === 0) {
        if (slot.sumY > 0) {
          x.push(slot.sumXY / slot.sumY);
          y.push(slot.sumY);
        }
      }
    }

    return {
      x,
      y
    };
  }

  function nextValue$1(spectra, positions, point) {
    let minIndex = 0;
    let minX = spectra[0].x[positions[0]];

    for (let i = 1; i < spectra.length; i++) {
      let currentX = spectra[i].x[positions[i]];

      if (currentX < minX) {
        minX = currentX;
        minIndex = i;
      }
    }

    point.x = minX;
    point.y = spectra[minIndex].y[positions[minIndex]];
    positions[minIndex]++;

    if (positions[minIndex] === spectra[minIndex].x.length) {
      positions.splice(minIndex, 1);
      spectra.splice(minIndex, 1);
    }
  }

  /**
   * Calculate integration
   * @param {DataXY} [data={}] - Object that contains property x (an ordered increasing array) and y (an array)
   * @param {object} [options={}]
   * @param {number} [options.from] - First value for xyIntegration in the X scale
   * @param {number} [options.fromIndex=0] - First point for xyIntegration
   * @param {number} [options.to] - Last value for xyIntegration in the X scale
   * @param {number} [options.toIndex=x.length-1] - Last point for xyIntegration
   * @return {number} xyIntegration value on the specified range
   */

  function xyIntegration$1(data = {}, options = {}) {
    xyCheck$1(data);
    const {
      x,
      y
    } = data;
    if (x.length < 2) return 0;
    const {
      fromIndex,
      toIndex
    } = xGetFromToIndex$1(x, options);
    let currentxyIntegration = 0;

    for (let i = fromIndex; i < toIndex; i++) {
      currentxyIntegration += (x[i + 1] - x[i]) * (y[i + 1] + y[i]) / 2;
    }

    return currentxyIntegration;
  }

  const GAUSSIAN_EXP_FACTOR$1 = -4 * Math.LN2;
  const ROOT_PI_OVER_LN2$1 = Math.sqrt(Math.PI / Math.LN2);
  const ROOT_THREE$1 = Math.sqrt(3);
  const ROOT_2LN2$1 = Math.sqrt(2 * Math.LN2);
  const ROOT_2LN2_MINUS_ONE$1 = 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$1(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 Shape1D {}

  class Gaussian$1 extends Shape1D {
    constructor(options = {}) {
      super();
      const {
        fwhm = 500,
        sd,
        height
      } = options;
      this.fwhm = sd ? widthToFWHM$2(2 * sd) : fwhm;
      this.height = height === undefined ? Math.sqrt(-GAUSSIAN_EXP_FACTOR$1 / Math.PI) / this.fwhm : height;
    }

    fwhmToWidth(fwhm = this.fwhm) {
      return fwhmToWidth$2(fwhm);
    }

    widthToFWHM(width) {
      return widthToFWHM$2(width);
    }

    fct(x) {
      return fct$2(x, this.fwhm);
    }

    getArea() {
      return getArea$2({
        fwhm: this.fwhm,
        height: this.height
      });
    }

    getFactor(area) {
      return getFactor$2(area);
    }

    getData(options = {}) {
      const {
        length,
        factor
      } = options;
      return getData$2({
        fwhm: this.fwhm,
        height: this.height,
        factor,
        length
      });
    }

  }
  /**
   * Return a parameterized function of a gaussian shape (see README for equation).
   * @returns - the y value of gaussian with the current parameters.
   */

  function fct$2(x, fwhm) {
    return Math.exp(GAUSSIAN_EXP_FACTOR$1 * Math.pow(x / fwhm, 2));
  }
  /**
   * Compute the value of Full Width at Half Maximum (FWHM) from the width between the inflection points.
   * for more information check the [mathworld page](https://mathworld.wolfram.com/GaussianFunction.html)
   * @returns fwhm
   */

  function widthToFWHM$2(width) {
    return width * ROOT_2LN2$1;
  }
  /**
   * Compute the value of width between the inflection points from Full Width at Half Maximum (FWHM).
   * for more information check the [mathworld page](https://mathworld.wolfram.com/GaussianFunction.html)
   * @param fwhm - Full Width at Half Maximum.
   * @returns width
   */

  function fwhmToWidth$2(fwhm) {
    return fwhm / ROOT_2LN2$1;
  }
  /**
   * Calculate the area of a specific shape.
   * @returns returns the area of the specific shape and parameters.
   */

  function getArea$2(options) {
    let {
      fwhm,
      sd,
      height = 1
    } = options;
    if (sd) fwhm = widthToFWHM$2(2 * sd);

    if (fwhm === undefined) {
      throw new Error('should pass fwhm or sd parameters');
    }

    return height * ROOT_PI_OVER_LN2$1 * fwhm / 2;
  }
  /**
   * Calculate the number of times FWHM allows to reach a specific area coverage.
   * @param [area=0.9999] Expected area to be covered.
   * @returns
   */

  function getFactor$2(area = 0.9999) {
    return Math.sqrt(2) * erfinv$1(area);
  }
  /**
   * Calculate intensity array of a gaussian shape.
   * @returns {Float64Array} Intensity values.
   */

  function getData$2(options = {}) {
    let {
      length,
      factor = getFactor$2(),
      fwhm = 500,
      sd,
      height
    } = options;
    if (sd) fwhm = widthToFWHM$2(2 * sd);

    if (!height) {
      height = Math.sqrt(-GAUSSIAN_EXP_FACTOR$1 / Math.PI) / fwhm;
    }

    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] = fct$2(i - center, fwhm) * height;
      data[length - 1 - i] = data[i];
    }

    return data;
  }

  class Lorentzian$1 extends Shape1D {
    constructor(options = {}) {
      super();
      const {
        fwhm = 500,
        height
      } = options;
      this.fwhm = fwhm;
      this.height = height === undefined ? 2 / Math.PI / fwhm : height;
    }

    fwhmToWidth(fwhm = this.fwhm) {
      return fwhmToWidth$1(fwhm);
    }

    widthToFWHM(width) {
      return widthToFWHM$1(width);
    }

    fct(x) {
      return fct$1(x, this.fwhm);
    }

    getArea() {
      return getArea$1({
        fwhm: this.fwhm,
        height: this.height
      });
    }

    getFactor(area) {
      return getFactor$1(area);
    }

    getData(options = {}) {
      const {
        length,
        factor
      } = options;
      return getData$1({
        fwhm: this.fwhm,
        height: this.height,
        factor,
        length
      });
    }

  }
  /**
   * Return a parameterized function of a lorentzian shape (see README for equation).
   * @param x - x value to calculate.
   * @param fwhm - full width half maximum
   * @returns - the y value of lorentzian with the current parameters.
   */

  function fct$1(x, fwhm) {
    return Math.pow(fwhm, 2) / (4 * Math.pow(x, 2) + Math.pow(fwhm, 2));
  }
  /**
   * Compute the value of Full Width at Half Maximum (FWHM) from the width between the inflection points.
   * for more information check the [mathworld page](https://mathworld.wolfram.com/LorentzianFunction.html)
   * @param width - Width between the inflection points
   * @returns fwhm
   */

  function widthToFWHM$1(width) {
    return width * ROOT_THREE$1;
  }
  /**
   * Compute the value of width between the inflection points from Full Width at Half Maximum (FWHM).
   * for more information check the [mathworld page](https://mathworld.wolfram.com/LorentzianFunction.html)
   * @param fwhm - Full Width at Half Maximum.
   * @returns width
   */

  function fwhmToWidth$1(fwhm) {
    return fwhm / ROOT_THREE$1;
  }
  /**
   * Calculate the area of a specific shape.
   * @returns returns the area of the specific shape and parameters.
   */

  function getArea$1(options) {
    const {
      fwhm,
      height = 1
    } = options;

    if (fwhm === undefined) {
      throw new Error('should pass fwhm or sd parameters');
    }

    return height * Math.PI * fwhm / 2;
  }
  /**
   * Calculate the number of times FWHM allows to reach a specific area coverage.
   * @param [area=0.9999] Expected area to be covered.
   * @returns
   */

  function getFactor$1(area = 0.9999) {
    return 2 * Math.tan(Math.PI * (area - 0.5));
  }
  /**
   * Calculate intensity array of a lorentzian shape.
   * @returns {Float64Array} y values
   */

  function getData$1(options = {}) {
    let {
      length,
      factor = getFactor$1(),
      fwhm = 500,
      height
    } = options;

    if (!height) {
      height = 2 / Math.PI / fwhm;
    }

    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] = fct$1(i - center, fwhm) * height;
      data[length - 1 - i] = data[i];
    }

    return data;
  }

  class PseudoVoigt$1 extends Shape1D {
    constructor(options = {}) {
      super();
      const {
        fwhm = 500,
        height,
        mu = 0.5
      } = options;
      this.mu = mu;
      this.fwhm = fwhm;
      this.height = height === undefined ? 1 / (mu / Math.sqrt(-GAUSSIAN_EXP_FACTOR$1 / Math.PI) * fwhm + (1 - mu) * fwhm * Math.PI / 2) : height;
    }

    fwhmToWidth(fwhm = this.fwhm, mu = this.mu) {
      return fwhmToWidth(fwhm, mu);
    }

    widthToFWHM(width, mu = this.mu) {
      return widthToFWHM(width, mu);
    }

    fct(x) {
      return fct(x, this.fwhm, this.mu);
    }

    getArea() {
      return getArea({
        fwhm: this.fwhm,
        height: this.height,
        mu: this.mu
      });
    }

    getFactor(area) {
      return getFactor(area);
    }

    getData(options = {}) {
      const {
        length,
        factor
      } = options;
      return getData({
        fwhm: this.fwhm,
        height: this.height,
        mu: this.mu,
        factor,
        length
      });
    }

  }
  /**
   * Return a parameterized function of a pseudo voigt shape (see README for equation).
   * @param x - x value to calculate.
   * @param fwhm - full width half maximum
   * @returns - the y value of pseudo voigt with the current parameters.
   */

  function fct(x, fwhm, mu) {
    return (1 - mu) * fct$1(x, fwhm) + mu * fct$2(x, fwhm);
  }
  /**
   * Compute the value of Full Width at Half Maximum (FWHM) from the width between the inflection points.
   * @param width - Width between the inflection points
   * @param [mu=0.5] Ratio of gaussian contribution in the shape
   * @returns fwhm
   */

  function widthToFWHM(width, mu = 0.5) {
    return width * (mu * ROOT_2LN2_MINUS_ONE$1 + 1);
  }
  /**
   * Compute the value of width between the inflection points from Full Width at Half Maximum (FWHM).
   * @param fwhm - Full Width at Half Maximum.
   * @param [mu=0.5] Ratio of gaussian contribution in the shape
   * @returns width
   */

  function fwhmToWidth(fwhm, mu = 0.5) {
    return fwhm / (mu * ROOT_2LN2_MINUS_ONE$1 + 1);
  }
  /**
   * Calculate the area of a specific shape.
   * @returns returns the area of the specific shape and parameters.
   */

  function getArea(options) {
    const {
      fwhm,
      height = 1,
      mu = 0.5
    } = options;

    if (fwhm === undefined) {
      throw new Error('should pass fwhm or sd parameters');
    }

    return fwhm * height * (mu * ROOT_PI_OVER_LN2$1 + (1 - mu) * Math.PI) / 2;
  }
  /**
   * Calculate the number of times FWHM allows to reach a specific area coverage.
   * @param [area=0.9999] Expected area to be covered.
   * @returns
   */

  function getFactor(area = 0.9999, mu = 0.5) {
    return mu < 1 ? getFactor$1(area) : getFactor$2(area);
  }
  /**
   * Calculate intensity array of a pseudo voigt shape.
   * @returns {Float64Array} y values
   */

  function getData(options = {}) {
    let {
      length,
      factor = getFactor(),
      fwhm = 500,
      height,
      mu = 0.5
    } = options;

    if (!height) {
      height = 1 / (mu / Math.sqrt(-GAUSSIAN_EXP_FACTOR$1 / 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] = fct(i - center, fwhm, mu) * height;
      data[length - 1 - i] = data[i];
    }

    return data;
  }

  /**
   * Generate a instance of a specific kind of shape.
   */

  function getShape1D(kind, shapeOptions = {}) {
    switch (kind) {
      case 'gaussian':
        return new Gaussian$1(shapeOptions);

      case 'lorentzian':
        return new Lorentzian$1(shapeOptions);

      case 'pseudoVoigt':
        return new PseudoVoigt$1(shapeOptions);

      default:
        {
          const unHandled = kind; // eslint-disable-next-line @typescript-eslint/restrict-template-expressions

          throw Error(`Unknown distribution ${unHandled}`);
        }
    }
  }

  /**
   * Apply Savitzky Golay algorithm
   * @param {array} [ys] Array of y values
   * @param {array|number} [xs] Array of X or deltaX
   * @param {object} [options={}]
   * @param {number} [options.windowSize=9]
   * @param {number} [options.derivative=0]
   * @param {number} [options.polynomial=3]
   * @return {array} Array containing the new ys (same length)
   */
  function SavitzkyGolay(ys, xs, options = {}) {
    let {
      windowSize = 9,
      derivative = 0,
      polynomial = 3
    } = options;

    if (windowSize % 2 === 0 || windowSize < 5 || !Number.isInteger(windowSize)) {
      throw new RangeError('Invalid window size (should be odd and at least 5 integer number)');
    }

    if (windowSize > ys.length) {
      throw new RangeError(`Window size is higher than the data length ${windowSize}>${ys.length}`);
    }

    if (derivative < 0 || !Number.isInteger(derivative)) {
      throw new RangeError('Derivative should be a positive integer');
    }

    if (polynomial < 1 || !Number.isInteger(polynomial)) {
      throw new RangeError('Polynomial should be a positive integer');
    }

    if (polynomial >= 6) {
      // eslint-disable-next-line no-console
      console.warn('You should not use polynomial grade higher than 5 if you are' + ' not sure that your data arises from such a model. Possible polynomial oscillation problems');
    }

    let half = Math.floor(windowSize / 2);
    let np = ys.length;
    let ans = new Array(np);
    let weights = fullWeights(windowSize, polynomial, derivative);
    let hs = 0;
    let constantH = true;

    if (Array.isArray(xs)) {
      constantH = false;
    } else {
      hs = Math.pow(xs, derivative);
    } //For the borders


    for (let i = 0; i < half; i++) {
      let wg1 = weights[half - i - 1];
      let wg2 = weights[half + i + 1];
      let d1 = 0;
      let d2 = 0;

      for (let l = 0; l < windowSize; l++) {
        d1 += wg1[l] * ys[l];
        d2 += wg2[l] * ys[np - windowSize + l];
      }

      if (constantH) {
        ans[half - i - 1] = d1 / hs;
        ans[np - half + i] = d2 / hs;
      } else {
        hs = getHs(xs, half - i - 1, half, derivative);
        ans[half - i - 1] = d1 / hs;
        hs = getHs(xs, np - half + i, half, derivative);
        ans[np - half + i] = d2 / hs;
      }
    } //For the internal points


    let wg = weights[half];

    for (let i = windowSize; i <= np; i++) {
      let d = 0;

      for (let l = 0; l < windowSize; l++) d += wg[l] * ys[l + i - windowSize];

      if (!constantH) hs = getHs(xs, i - half - 1, half, derivative);
      ans[i - half - 1] = d / hs;
    }

    return ans;
  }

  function getHs(h, center, half, derivative) {
    let hs = 0;
    let count = 0;

    for (let i = center - half; i < center + half; i++) {
      if (i >= 0 && i < h.length - 1) {
        hs += h[i + 1] - h[i];
        count++;
      }
    }

    return Math.pow(hs / count, derivative);
  }

  function GramPoly(i, m, k, s) {
    let Grampoly = 0;

    if (k > 0) {
      Grampoly = (4 * k - 2) / (k * (2 * m - k + 1)) * (i * GramPoly(i, m, k - 1, s) + s * GramPoly(i, m, k - 1, s - 1)) - (k - 1) * (2 * m + k) / (k * (2 * m - k + 1)) * GramPoly(i, m, k - 2, s);
    } else {
      if (k === 0 && s === 0) {
        Grampoly = 1;
      } else {
        Grampoly = 0;
      }
    }

    return Grampoly;
  }

  function GenFact(a, b) {
    let gf = 1;

    if (a >= b) {
      for (let j = a - b + 1; j <= a; j++) {
        gf *= j;
      }
    }

    return gf;
  }

  function Weight(i, t, m, n, s) {
    let sum = 0;

    for (let k = 0; k <= n; k++) {
      //console.log(k);
      sum += (2 * k + 1) * (GenFact(2 * m, k) / GenFact(2 * m + k + 1, k + 1)) * GramPoly(i, m, k, 0) * GramPoly(t, m, k, s);
    }

    return sum;
  }
  /**
   *
   * @param m  Number of points
   * @param n  Polynomial grade
   * @param s  Derivative
   */


  function fullWeights(m, n, s) {
    let weights = new Array(m);
    let np = Math.floor(m / 2);

    for (let t = -np; t <= np; t++) {
      weights[t + np] = new Array(m);

      for (let j = -np; j <= np; j++) {
        weights[t + np][j + np] = Weight(j, t, np, n, s);
      }
    }

    return weights;
  }
  /*function entropy(data,h,options){
      var trend = SavitzkyGolay(data,h,trendOptions);
      var copy = new Array(data.length);
      var sum = 0;
      var max = 0;
      for(var i=0;i<data.length;i++){
          copy[i] = data[i]-trend[i];
      }

      sum/=data.length;
      console.log(sum+" "+max);
      console.log(stat.array.standardDeviation(copy));
      console.log(Math.abs(stat.array.mean(copy))/stat.array.standardDeviation(copy));
      return sum;

  }



  function guessWindowSize(data, h){
      console.log("entropy "+entropy(data,h,trendOptions));
      return 5;
  }
  */

  /**
   * Global spectra deconvolution
   * @param {object} data - Object data with x and y arrays
   * @param {Array<number>} [data.x] - Independent variable
   * @param {Array<number>} [data.y] - Dependent variable
   * @param {object} [options={}] - Options object
   * @param {object} [options.shape={}] - Object that specified the kind of shape to calculate the FWHM instead of width between inflection points. see https://mljs.github.io/peak-shape-generator/#inflectionpointswidthtofwhm
   * @param {object} [options.shape.kind='gaussian']
   * @param {object} [options.shape.options={}]
   * @param {object} [options.sgOptions] - Options object for Savitzky-Golay filter. See https://github.com/mljs/savitzky-golay-generalized#options
   * @param {number} [options.sgOptions.windowSize = 9] - points to use in the approximations
   * @param {number} [options.sgOptions.polynomial = 3] - degree of the polynomial to use in the approximations
   * @param {number} [options.minMaxRatio = 0.00025] - Threshold to determine if a given peak should be considered as a noise
   * @param {number} [options.broadRatio = 0.00] - If `broadRatio` is higher than 0, then all the peaks which second derivative
   * smaller than `broadRatio * maxAbsSecondDerivative` will be marked with the soft mask equal to true.
   * @param {number} [options.noiseLevel = 0] - Noise threshold in spectrum units
   * @param {boolean} [options.maxCriteria = true] - Peaks are local maximum(true) or minimum(false)
   * @param {boolean} [options.smoothY = true] - Select the peak intensities from a smoothed version of the independent variables
   * @param {boolean} [options.realTopDetection = false] - Use a quadratic optimizations with the peak and its 3 closest neighbors
   * to determine the true x,y values of the peak?
   * @param {number} [options.heightFactor = 0] - Factor to multiply the calculated height (usually 2)
   * @param {number} [options.derivativeThreshold = -1] - Filters based on the amplitude of the first derivative
   * @return {Array<object>}
   */

  function gsd(data, options = {}) {
    let {
      noiseLevel,
      sgOptions = {
        windowSize: 9,
        polynomial: 3
      },
      shape = {},
      smoothY = true,
      heightFactor = 0,
      broadRatio = 0.0,
      maxCriteria = true,
      minMaxRatio = 0.00025,
      derivativeThreshold = -1,
      realTopDetection = false
    } = options;
    let {
      y: yIn,
      x
    } = data;
    const y = yIn.slice();
    let equalSpaced = isEqualSpaced(x);

    if (maxCriteria === false) {
      for (let i = 0; i < y.length; i++) {
        y[i] *= -1;
      }
    }

    if (noiseLevel === undefined) {
      noiseLevel = equalSpaced ? getNoiseLevel(y) : 0;
    }

    for (let i = 0; i < y.length; i++) {
      y[i] -= noiseLevel;
    }

    for (let i = 0; i < y.length; i++) {
      if (y[i] < 0) {
        y[i] = 0;
      }
    } // If the max difference between delta x is less than 5%, then,
    // we can assume it to be equally spaced variable


    let yData = y;
    let dY, ddY;
    const {
      windowSize,
      polynomial
    } = sgOptions;

    if (equalSpaced) {
      if (smoothY) {
        yData = SavitzkyGolay(y, x[1] - x[0], {
          windowSize,
          polynomial,
          derivative: 0
        });
      }

      dY = SavitzkyGolay(y, x[1] - x[0], {
        windowSize,
        polynomial,
        derivative: 1
      });
      ddY = SavitzkyGolay(y, x[1] - x[0], {
        windowSize,
        polynomial,
        derivative: 2
      });
    } else {
      if (smoothY) {
        yData = SavitzkyGolay(y, x, {
          windowSize,
          polynomial,
          derivative: 0
        });
      }

      dY = SavitzkyGolay(y, x, {
        windowSize,
        polynomial,
        derivative: 1
      });
      ddY = SavitzkyGolay(y, x, {
        windowSize,
        polynomial,
        derivative: 2
      });
    }

    const xData = x;
    const dX = x[1] - x[0];
    let maxDdy = 0;
    let maxY = 0;

    for (let i = 0; i < yData.length; i++) {
      if (Math.abs(ddY[i]) > maxDdy) {
        maxDdy = Math.abs(ddY[i]);
      }

      if (Math.abs(yData[i]) > maxY) {
        maxY = Math.abs(yData[i]);
      }
    }

    let lastMax = null;
    let lastMin = null;
    let minddY = [];
    let intervalL = [];
    let intervalR = [];
    let broadMask = []; // By the intermediate value theorem We cannot find 2 consecutive maximum or minimum

    for (let i = 1; i < yData.length - 1; ++i) {
      // filter based on derivativeThreshold
      // console.log('pasa', y[i], dY[i], ddY[i]);
      if (Math.abs(dY[i]) > derivativeThreshold) {
        // Minimum in first derivative
        if (dY[i] < dY[i - 1] && dY[i] <= dY[i + 1] || dY[i] <= dY[i - 1] && dY[i] < dY[i + 1]) {
          lastMin = {
            x: xData[i],
            index: i
          };

          if (dX > 0 && lastMax !== null) {
            intervalL.push(lastMax);
            intervalR.push(lastMin);
          }
        } // Maximum in first derivative


        if (dY[i] >= dY[i - 1] && dY[i] > dY[i + 1] || dY[i] > dY[i - 1] && dY[i] >= dY[i + 1]) {
          lastMax = {
            x: xData[i],
            index: i
          };

          if (dX < 0 && lastMin !== null) {
            intervalL.push(lastMax);
            intervalR.push(lastMin);
          }
        }
      } // Minimum in second derivative


      if (ddY[i] < ddY[i - 1] && ddY[i] < ddY[i + 1]) {
        minddY.push(i);
        broadMask.push(Math.abs(ddY[i]) <= broadRatio * maxDdy);
      }
    }

    let widthProcessor = shape.kind ? getShape1D(shape.kind, shape.options).widthToFWHM : x => x;
    let signals = [];
    let lastK = -1;
    let possible, frequency, distanceJ, minDistance, gettingCloser;

    for (let j = 0; j < minddY.length; ++j) {
      frequency = xData[minddY[j]];
      possible = -1;
      let k = lastK + 1;
      minDistance = Number.MAX_VALUE;
      distanceJ = 0;
      gettingCloser = true;

      while (possible === -1 && k < intervalL.length && gettingCloser) {
        distanceJ = Math.abs(frequency - (intervalL[k].x + intervalR[k].x) / 2); // Still getting closer?

        if (distanceJ < minDistance) {
          minDistance = distanceJ;
        } else {
          gettingCloser = false;
        }

        if (distanceJ < Math.abs(intervalL[k].x - intervalR[k].x) / 2) {
          possible = k;
          lastK = k;
        }

        ++k;
      }

      if (possible !== -1) {
        if (Math.abs(yData[minddY[j]]) > minMaxRatio * maxY) {
          let width = Math.abs(intervalR[possible].x - intervalL[possible].x);
          signals.push({
            index: minddY[j],
            x: frequency,
            y: maxCriteria ? yData[minddY[j]] + noiseLevel : -yData[minddY[j]] - noiseLevel,
            width: widthProcessor(width),
            soft: broadMask[j]
          });
          signals[signals.length - 1].left = intervalL[possible];
          signals[signals.length - 1].right = intervalR[possible];

          if (heightFactor) {
            let yLeft = yData[intervalL[possible].index];
            let yRight = yData[intervalR[possible].index];
            signals[signals.length - 1].height = heightFactor * (signals[signals.length - 1].y - (yLeft + yRight) / 2);
          }
        }
      }
    }

    if (realTopDetection) {
      determineRealTop(signals, xData, yData);
    } // Correct the values to fit the original spectra data


    for (let j = 0; j < signals.length; j++) {
      signals[j].base = noiseLevel;
    }

    signals.sort(function (a, b) {
      return a.x - b.x;
    });
    return signals;
  }

  const isEqualSpaced = x => {
    let tmp;
    let maxDx = 0;
    let minDx = Number.MAX_SAFE_INTEGER;

    for (let i = 0; i < x.length - 1; ++i) {
      tmp = Math.abs(x[i + 1] - x[i]);

      if (tmp < minDx) {
        minDx = tmp;
      }

      if (tmp > maxDx) {
        maxDx = tmp;
      }
    }

    return (maxDx - minDx) / maxDx < 0.05;
  };

  const getNoiseLevel = y => {
    let mean = 0;
    let stddev = 0;
    let length = y.length;

    for (let i = 0; i < length; ++i) {
      mean += y[i];
    }

    mean /= length;
    let averageDeviations = new Array(length);

    for (let i = 0; i < length; ++i) {
      averageDeviations[i] = Math.abs(y[i] - mean);
    }

    averageDeviations.sort((a, b) => a - b);

    if (length % 2 === 1) {
      stddev = averageDeviations[(length - 1) / 2] / 0.6745;
    } else {
      stddev = 0.5 * (averageDeviations[length / 2] + averageDeviations[length / 2 - 1]) / 0.6745;
    }

    return stddev;
  };

  const determineRealTop = (peakList, x, y) => {
    let alpha, beta, gamma, p, currentPoint;

    for (let j = 0; j < peakList.length; j++) {
      currentPoint = peakList[j].index; // peakList[j][2];
      // The detected peak could be moved 1 or 2 units to left or right.

      if (y[currentPoint - 1] >= y[currentPoint - 2] && y[currentPoint - 1] >= y[currentPoint]) {
        currentPoint--;
      } else {
        if (y[currentPoint + 1] >= y[currentPoint] && y[currentPoint + 1] >= y[currentPoint + 2]) {
          currentPoint++;
        } else {
          if (y[currentPoint - 2] >= y[currentPoint - 3] && y[currentPoint - 2] >= y[currentPoint - 1]) {
            currentPoint -= 2;
          } else {
            if (y[currentPoint + 2] >= y[currentPoint + 1] && y[currentPoint + 2] >= y[currentPoint + 3]) {
              currentPoint += 2;
            }
          }
        }
      } // interpolation to a sin() function


      if (y[currentPoint - 1] > 0 && y[currentPoint + 1] > 0 && y[currentPoint] >= y[currentPoint - 1] && y[currentPoint] >= y[currentPoint + 1] && (y[currentPoint] !== y[currentPoint - 1] || y[currentPoint] !== y[currentPoint + 1])) {
        alpha = 20 * Math.log10(y[currentPoint - 1]);
        beta = 20 * Math.log10(y[currentPoint]);
        gamma = 20 * Math.log10(y[currentPoint + 1]);
        p = 0.5 * (alpha - gamma) / (alpha - 2 * beta + gamma); // console.log(alpha, beta, gamma, `p: ${p}`);
        // console.log(x[currentPoint]+" "+tmp+" "+currentPoint);

        peakList[j].x = x[currentPoint] + (x[currentPoint] - x[currentPoint - 1]) * p;
        peakList[j].y = y[currentPoint] - 0.25 * (y[currentPoint - 1] - y[currentPoint + 1]) * p;
      }
    }
  };

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

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

  class Gaussian {
    /**
     * @param {object} [options = {}]
     * @param {number} [options.height=4*LN2/(PI*FWHM)] Define the height of the peak, by default area=1 (normalized)
     * @param {number} [options.fwhm = 500] - Full Width at Half Maximum in the number of points in FWHM.
     * @param {number} [options.sd] - Standard deviation, if it's defined options.fwhm will be ignored and the value will be computed sd * Math.sqrt(8 * Math.LN2);
     */
    constructor(options = {}) {
      this.fwhm = options.sd ? Gaussian.widthToFWHM(2 * options.sd) : options.fwhm ? options.fwhm : 500;
      this.height = options.height === undefined ? Math.sqrt(-GAUSSIAN_EXP_FACTOR / Math.PI) / this.fwhm : options.height;
    }
    /**
     * Calculate a gaussian shape
     * @param {object} [options = {}]
     * @param {number} [options.factor = 6] - Number of time to take fwhm to calculate length. Default covers 99.99 % of area.
     * @param {number} [options.length = fwhm * factor + 1] - total number of points to calculate
     * @return {Float64Array} y values
     */


    getData(options = {}) {
      let {
        length,
        factor = this.getFactor()
      } = options;

      if (!length) {
        length = Math.min(Math.ceil(this.fwhm * factor), Math.pow(2, 25) - 1);
        if (length % 2 === 0) length++;
      }

      const center = (length - 1) / 2;
      const data = new Float64Array(length);

      for (let i = 0; i <= center; i++) {
        data[i] = this.fct(i - center) * this.height;
        data[length - 1 - i] = data[i];
      }

      return data;
    }
    /**
     * Return a parameterized function of a gaussian shape (see README for equation).
     * @param {number} x - x value to calculate.
     * @returns {number} - the y value of gaussian with the current parameters.
     */


    fct(x) {
      return Gaussian.fct(x, this.fwhm);
    }
    /**
     * Calculate the number of times FWHM allows to reach a specific area coverage
     * @param {number} [area=0.9999]
     * @returns {number}
     */


    getFactor(area = 0.9999) {
      return Gaussian.getFactor(area);
    }
    /**
     * Calculate the area of the shape.
     * @returns {number} - returns the area.
     */


    getArea() {
      return Gaussian.getArea(this.fwhm, {
        height: this.height
      });
    }
    /**
     * Compute the value of Full Width at Half Maximum (FWHM) from the width between the inflection points.
     * //https://mathworld.wolfram.com/GaussianFunction.html
     * @param {number} width - Width between the inflection points
     * @returns {number} fwhm
     */


    widthToFWHM(width) {
      //https://mathworld.wolfram.com/GaussianFunction.html
      return Gaussian.widthToFWHM(width);
    }
    /**
     * Compute the value of width between the inflection points from Full Width at Half Maximum (FWHM).
     * //https://mathworld.wolfram.com/GaussianFunction.html
     * @param {number} fwhm - Full Width at Half Maximum.
     * @returns {number} width
     */


    fwhmToWidth(fwhm = this.fwhm) {
      return Gaussian.fwhmToWidth(fwhm);
    }
    /**
     * set a new full width at half maximum
     * @param {number} fwhm - full width at half maximum
     */


    setFWHM(fwhm) {
      this.fwhm = fwhm;
    }
    /**
     * set a new height
     * @param {number} height - The maximal intensity of the shape.
     */


    setHeight(height) {
      this.height = height;
    }

  }
  /**
   * Return a parameterized function of a gaussian shape (see README for equation).
   * @param {number} x - x value to calculate.
   * @param {number} fwhm - full width half maximum
   * @returns {number} - the y value of gaussian with the current parameters.
   */

  Gaussian.fct = function fct(x, fwhm = 500) {
    return Math.exp(GAUSSIAN_EXP_FACTOR * Math.pow(x / fwhm, 2));
  };
  /**
   * Compute the value of Full Width at Half Maximum (FWHM) from the width between the inflection points.
   * //https://mathworld.wolfram.com/GaussianFunction.html
   * @param {number} width - Width between the inflection points
   * @returns {number} fwhm
   */


  Gaussian.widthToFWHM = function widthToFWHM(width) {
    return width * ROOT_2LN2;
  };
  /**
   * Compute the value of width between the inflection points from Full Width at Half Maximum (FWHM).
   * //https://mathworld.wolfram.com/GaussianFunction.html
   * @param {number} fwhm - Full Width at Half Maximum.
   * @returns {number} width
   */


  Gaussian.fwhmToWidth = function fwhmToWidth(fwhm) {
    return fwhm / ROOT_2LN2;
  };
  /**
   * Calculate the area of a specific shape.
   * @param {number} fwhm - Full width at half maximum.
   * @param {object} [options = {}] - options.
   * @param {number} [options.height = 1] - Maximum y value of the shape.
   * @returns {number} - returns the area of the specific shape and parameters.
   */


  Gaussian.getArea = function getArea(fwhm, options = {}) {
    let {
      height = 1
    } = options;
    return height * ROOT_PI_OVER_LN2 * fwhm / 2;
  };
  /**
   * Calculate the number of times FWHM allows to reach a specific area coverage.
   * @param {number} [area=0.9999]
   * @returns {number}
   */


  Gaussian.getFactor = function getFactor(area = 0.9999) {
    return Math.sqrt(2) * erfinv(area);
  };

  class Lorentzian {
    /**
     * @param {object} [options = {}]
     * @param {number} [options.height=2/(PI*FWHM)] Define the height of the peak, by default area=1 (normalized)
     * @param {number} [options.fwhm = 500] - Full Width at Half Maximum in the number of points in FWHM.
     * @param {number} [options.sd] - Standard deviation, if it's defined options.fwhm will be ignored and the value will be computed sd * Math.sqrt(8 * Math.LN2);
     */
    constructor(options = {}) {
      this.fwhm = options.fwhm === undefined ? 500 : options.fwhm;
      this.height = options.height === undefined ? 2 / Math.PI / this.fwhm : options.height;
    }
    /**
     * Calculate a lorentzian shape
     * @param {object} [options = {}]
     * @param {number} [options.factor = Math.tan(Math.PI * (0.9999 - 0.5))] - Number of time to take fwhm to calculate length. Default covers 99.99 % of area.
     * @param {number} [options.length = fwhm * factor + 1] - total number of points to calculate
     * @return {Float64Array} y values
     */


    getData(options = {}) {
      let {
        length,
        factor = this.getFactor()
      } = options;

      if (!length) {
        length = Math.min(Math.ceil(this.fwhm * factor), Math.pow(2, 25) - 1);
        if (length % 2 === 0) length++;
      }

      const center = (length - 1) / 2;
      const data = new Float64Array(length);

      for (let i = 0; i <= center; i++) {
        data[i] = this.fct(i - center) * this.height;
        data[length - 1 - i] = data[i];
      }

      return data;
    }
    /**
     * Return a parameterized function of a lorentzian shape (see README for equation).
     * @param {number} x - x value to calculate.
     * @returns {number} - the y value of lorentzian with the current parameters.
     */


    fct(x) {
      return Lorentzian.fct(x, this.fwhm);
    }
    /**
     * Calculate the number of times FWHM allows to reach a specific area coverage
     * @param {number} [area=0.9999]
     * @returns {number}
     */


    getFactor(area = 0.9999) {
      return Lorentzian.getFactor(area);
    }
    /**
     * Calculate the area of the shape.
     * @returns {number} - returns the area.
     */


    getArea() {
      return Lorentzian.getArea(this.fwhm, {
        height: this.height
      });
    }
    /**
     * Compute the value of width between the inflection points of a specific shape from Full Width at Half Maximum (FWHM).
     * //https://mathworld.wolfram.com/LorentzianFunction.html
     * @param {number} [fwhm] - Full Width at Half Maximum.
     * @returns {number} width between the inflection points
     */


    fwhmToWidth(fwhm = this.fwhm) {
      return Lorentzian.fwhmToWidth(fwhm);
    }
    /**
     * Compute the value of Full Width at Half Maximum (FWHM) of a specific shape from the width between the inflection points.
     * //https://mathworld.wolfram.com/LorentzianFunction.html
     * @param {number} [width] Width between the inflection points
     * @returns {number} fwhm
     */


    widthToFWHM(width) {
      return Lorentzian.widthToFWHM(width);
    }
    /**
     * set a new full width at half maximum
     * @param {number} fwhm - full width at half maximum
     */


    setFWHM(fwhm) {
      this.fwhm = fwhm;
    }
    /**
     * set a new height
     * @param {number} height - The maximal intensity of the shape.
     */


    setHeight(height) {
      this.height = height;
    }

  }
  /**
   * Return a parameterized function of a gaussian shape (see README for equation).
   * @param {number} x - x value to calculate.
   * @param {number} fwhm - full width half maximum
   * @returns {number} - the y value of gaussian with the current parameters.
   */

  Lorentzian.fct = function fct(x, fwhm) {
    const squareFWHM = fwhm * fwhm;
    return squareFWHM / (4 * Math.pow(x, 2) + squareFWHM);
  };
  /**
   * Compute the value of width between the inflection points of a specific shape from Full Width at Half Maximum (FWHM).
   * //https://mathworld.wolfram.com/LorentzianFunction.html
   * @param {number} [fwhm] - Full Width at Half Maximum.
   * @returns {number} width between the inflection points
   */


  Lorentzian.fwhmToWidth = function fwhmToWidth(fwhm) {
    return fwhm / ROOT_THREE;
  };
  /**
   * Compute the value of Full Width at Half Maximum (FWHM) of a specific shape from the width between the inflection points.
   * //https://mathworld.wolfram.com/LorentzianFunction.html
   * @param {number} [width] Width between the inflection points
   * @returns {number} fwhm
   */


  Lorentzian.widthToFWHM = function widthToFWHM(width) {
    return width * ROOT_THREE;
  };
  /**
   * Calculate the area of a specific shape.
   * @param {number} fwhm - Full width at half maximum.
   * @param {*} [options = {}] - options.
   * @param {number} [options.height = 1] - Maximum y value of the shape.
   * @returns {number} - returns the area of the specific shape and parameters.
   */


  Lorentzian.getArea = function getArea(fwhm, options = {}) {
    let {
      height = 1
    } = options;
    return height * Math.PI * fwhm / 2;
  };
  /**
   * Calculate the number of times FWHM allows to reach a specific area coverage
   * @param {number} [area=0.9999]
   * @returns {number}
   */


  Lorentzian.getFactor = function getFactor(area = 0.9999) {
    return 2 * Math.tan(Math.PI * (area - 0.5));
  };

  class PseudoVoigt {
    /**
     * @param {object} [options={}]
     * @param {number} [options.height=1/(mu*FWHM/sqrt(4*LN2/PI)+(1-mu)*fwhm*PI*0.5)] Define the height of the peak, by default area=1 (normalized)
     * @param {number} [options.fwhm=500] - Full Width at Half Maximum in the number of points in FWHM.
     * @param {number} [options.mu=0.5] - ratio of gaussian contribution.
     */
    constructor(options = {}) {
      this.mu = options.mu === undefined ? 0.5 : options.mu;
      this.fwhm = options.fwhm === undefined ? 500 : options.fwhm;
      this.height = options.height === undefined ? 1 / (this.mu / Math.sqrt(-GAUSSIAN_EXP_FACTOR / Math.PI) * this.fwhm + (1 - this.mu) * this.fwhm * Math.PI / 2) : options.height;
    }
    /**
     * Calculate a linear combination of gaussian and lorentzian function width an same full width at half maximum
     * @param { object } [options = {}]
     * @param { number } [options.factor = 2 * Math.tan(Math.PI * (0.9999 - 0.5))] - Number of time to take fwhm in the calculation of the length.Default covers 99.99 % of area.
     * @param { number } [options.length = fwhm * factor + 1] - total number of points to calculate
     * @return { object } - { fwhm, data<Float64Array>} - An with the number of points at half maximum and the array of y values covering the 99.99 % of the area.
     */


    getData(options = {}) {
      let {
        length,
        factor = this.getFactor()
      } = options;

      if (!length) {
        length = Math.ceil(this.fwhm * factor);
        if (length % 2 === 0) length++;
      }

      const center = (length - 1) / 2;
      let data = new Float64Array(length);

      for (let i = 0; i <= center; i++) {
        data[i] = this.fct(i - center) * this.height;
        data[length - 1 - i] = data[i];
      }

      return data;
    }
    /**
     * Return a parameterized function of a linear combination of Gaussian and Lorentzian shapes where the full width at half maximum are the same for both kind of shapes (see README for equation).
     * @param {number} [x] x value to calculate.
     * @returns {number} - the y value of a pseudo voigt with the current parameters.
     */


    fct(x) {
      return PseudoVoigt.fct(x, this.fwhm, this.mu);
    }
    /**
     * Calculate the number of times FWHM allows to reach a specific area coverage
     * @param {number} [area=0.9999] - required area to be coverage
     * @param {number} [mu=this.mu] - ratio of gaussian contribution.
     * @returns {number}
     */


    getFactor(area = 0.9999, mu = this.mu) {
      return PseudoVoigt.getFactor(area, mu);
    }
    /**
     * Calculate the area of the shape.
     * @returns {number} - returns the area.
     */


    getArea() {
      return PseudoVoigt.getArea(this.fwhm, {
        height: this.height,
        mu: this.mu
      });
    }
    /**
     * Compute the value of Full Width at Half Maximum (FMHM) from width between the inflection points.
     * @param {number} width - width between the inflection points
     * @param {number} [mu = 0.5] - ratio of gaussian contribution.
     * @returns {number} Full Width at Half Maximum (FMHM).
     */


    widthToFWHM(width, mu) {
      return PseudoVoigt.widthToFWHM(width, mu);
    }
    /**
     * Compute the value of width between the inflection points from Full Width at Half Maximum (FWHM).
     * @param {number} fwhm - Full Width at Half Maximum.
     * @param {number} [mu] - ratio of gaussian contribution.
     * @returns {number} width between the inflection points.
     */


    fwhmToWidth(fwhm = this.fwhm, mu = this.mu) {
      return PseudoVoigt.fwhmToWidth(fwhm, mu);
    }
    /**
     * set a new full width at half maximum
     * @param {number} fwhm - full width at half maximum
     */


    setFWHM(fwhm) {
      this.fwhm = fwhm;
    }
    /**
     * set a new height
     * @param {number} height - The maximal intensity of the shape.
     */


    setHeight(height) {
      this.height = height;
    }
    /**
     * set a new mu
     * @param {number} mu - ratio of gaussian contribution.
     */


    setMu(mu) {
      this.mu = mu;
    }

  }
  /**
   * Return a parameterized function of a gaussian shape (see README for equation).
   * @param {number} x - x value to calculate.
   * @param {number} fwhm - full width half maximum
   * @param {number} [mu=0.5] - ratio of gaussian contribution.
   * @returns {number} - the y value of gaussian with the current parameters.
   */

  PseudoVoigt.fct = function fct(x, fwhm, mu = 0.5) {
    return (1 - mu) * Lorentzian.fct(x, fwhm) + mu * Gaussian.fct(x, fwhm);
  };
  /**
   * Compute the value of Full Width at Half Maximum (FMHM) from width between the inflection points.
   * @param {number} width - width between the inflection points
   * @param {number} [mu = 0.5] - ratio of gaussian contribution.
   * @returns {number} Full Width at Half Maximum (FMHM).
   */


  PseudoVoigt.widthToFWHM = function widthToFWHM(width, mu = 0.5) {
    return width * (mu * ROOT_2LN2_MINUS_ONE + 1);
  };
  /**
   * Compute the value of width between the inflection points from Full Width at Half Maximum (FWHM).
   * @param {number} fwhm - Full Width at Half Maximum.
   * @param {number} [mu = 0.5] - ratio of gaussian contribution.
   * @returns {number} width between the inflection points.
   */


  PseudoVoigt.fwhmToWidth = function fwhmToWidth(fwhm, mu = 0.5) {
    return fwhm / (mu * ROOT_2LN2_MINUS_ONE + 1);
  };
  /**
   * Calculate the area of a specific shape.
   * @param {number} fwhm - Full width at half maximum.
   * @param {*} [options = {}] - options.
   * @param {number} [options.height = 1] - Maximum y value of the shape.
   * @param {number} [options.mu = 0.5] - ratio of gaussian contribution.
   * @returns {number} - returns the area of the specific shape and parameters.
   */


  PseudoVoigt.getArea = function getArea(fwhm, options = {}) {
    let {
      height = 1,
      mu = 0.5
    } = options;
    return fwhm * height * (mu * ROOT_PI_OVER_LN2 + (1 - mu) * Math.PI) / 2;
  };
  /**
   * Calculate the number of times FWHM allows to reach a specific area coverage
   * @param {number} [area=0.9999] - required area to be coverage
   * @param {number} [mu=this.mu] - ratio of gaussian contribution.
   * @returns {number}
   */


  PseudoVoigt.getFactor = function getFactor(area = 0.9999, mu = 0.5) {
    return mu < 1 ? Lorentzian.getFactor(area) : Gaussian.getFactor(area);
  };

  let axis = ['x', 'y'];
  class Gaussian2D {
    /**
     * @param {object} [options = {}]
     * @param {number} [options.height=4*LN2/(PI*xFWHM*yFWHM)] Define the height of the peak, by default area=1 (normalized).
     * @param {number} [options.fwhm = 500] - Full Width at Half Maximum in the number of points in FWHM used if x or y has not the fwhm property.
     * @param {object} [options.x] - Options for x axis.
     * @param {number} [options.x.fwhm = fwhm] - Full Width at Half Maximum in the number of points in FWHM for x axis.
     * @param {number} [options.x.sd] - Standard deviation for x axis, if it's defined options.x.fwhm will be ignored and the value will be computed sd * Math.sqrt(8 * Math.LN2);
     * @param {object} [options.y] - Options for y axis.
     * @param {number} [options.y.fwhm = fwhm] - Full Width at Half Maximum in the number of points in FWHM for y axis.
     * @param {number} [options.y.sd] - Standard deviation for y axis, if it's defined options.y.fwhm will be ignored and the value will be computed sd * Math.sqrt(8 * Math.LN2);
     */
    constructor(options = {}) {
      let {
        fwhm: globalFWHM = 500
      } = options;

      for (let i of axis) {
        let fwhm;

        if (!options[i]) {
          fwhm = globalFWHM;
        } else {
          fwhm = options[i].sd ? Gaussian2D.widthToFWHM(2 * options[i].sd) : options[i].fwhm || globalFWHM;
        }

        this[i] = {
          fwhm
        };
      }

      this.height = options.height === undefined ? -GAUSSIAN_EXP_FACTOR / Math.PI / this.x.fwhm / this.y.fwhm : options.height;
    }
    /**
     * Calculate a Gaussian2D shape
     * @param {object} [options = {}]
     * @param {number} [options.factor] - Number of time to take fwhm to calculate length. Default covers 99.99 % of area.
     * @param {object} [options.x] - parameter for x axis.
     * @param {number} [options.x.length=fwhm*factor+1] - length on x axis.
     * @param {number} [options.x.factor=factor] - Number of time to take fwhm to calculate length. Default covers 99.99 % of area.
     * @param {object} [options.y] - parameter for y axis.
     * @param {number} [options.y.length=fwhm*factor+1] - length on y axis.
     * @param {number} [options.y.factor=factor] - Number of time to take fwhm to calculate length. Default covers 99.99 % of area.
     * @return {Array<Float64Array>} - z values.
     */


    getData(options = {}) {
      let {
        x = {},
        y = {},
        factor = this.getFactor(),
        length
      } = options;
      let xLength = x.length || length;

      if (!xLength) {
        let {
          factor: xFactor = factor
        } = x;
        xLength = Math.min(Math.ceil(this.x.fwhm * xFactor), Math.pow(2, 25) - 1);
        if (xLength % 2 === 0) xLength++;
      }

      let yLength = y.length || length;

      if (!yLength) {
        let {
          factor: yFactor = factor
        } = y;
        yLength = Math.min(Math.ceil(this.y.fwhm * yFactor), Math.pow(2, 25) - 1);
        if (yLength % 2 === 0) yLength++;
      }

      const xCenter = (xLength - 1) / 2;
      const yCenter = (yLength - 1) / 2;
      const data = new Array(xLength);

      for (let i = 0; i < xLength; i++) {
        data[i] = new Array(yLength);
      }

      for (let i = 0; i < xLength; i++) {
        for (let j = 0; j < yLength; j++) {
          data[i][j] = this.fct(i - xCenter, j - yCenter) * this.height;
        }
      }

      return data;
    }
    /**
     * Return the intensity value of a 2D gaussian shape (see README for equation).
     * @param {number} x - x value to calculate.
     * @param {number} y - y value to calculate.
     * @returns {number} - the z value of bi-dimensional gaussian with the current parameters.
     */


    fct(x, y) {
      return Gaussian2D.fct(x, y, this.x.fwhm, this.y.fwhm);
    }
    /**
     * Calculate the number of times FWHM allows to reach a specific volume coverage.
     * @param {number} [volume=0.9999]
     * @returns {number}
     */


    getFactor(volume = 0.9999) {
      return Gaussian2D.getFactor(volume);
    }
    /**
     * Calculate the volume of the shape.
     * @returns {number} - returns the volume.
     */


    getVolume() {
      return Gaussian2D.getVolume(this.x.fwhm, this.y.fwhm, {
        height: this.height
      });
    }
    /**
     * Compute the value of Full Width at Half Maximum (FWHM) from the width between the inflection points.
     * //https://mathworld.wolfram.com/Gaussian2DFunction.html
     * @param {number} width - Width between the inflection points
     * @returns {number} fwhm
     */


    widthToFWHM(width) {
      //https://mathworld.wolfram.com/Gaussian2DFunction.html
      return Gaussian2D.widthToFWHM(width);
    }
    /**
     * Compute the value of width between the inflection points from Full Width at Half Maximum (FWHM).
     * //https://mathworld.wolfram.com/Gaussian2DFunction.html
     * @param {number} fwhm - Full Width at Half Maximum.
     * @returns {number} width
     */


    fwhmToWidth(fwhm = this.x.fwhm) {
      return Gaussian2D.fwhmToWidth(fwhm);
    }
    /**
     * set a new full width at half maximum
     * @param {number} fwhm - full width at half maximum
     * @param {string|Array<string>} axisLabel - label of axis, if it is undefined fwhm is set to both axis.
     */


    setFWHM(fwhm, axisLabel) {
      if (!axisLabel) axisLabel = axis;
      if (!Array.isArray(axisLabel)) axisLabel = [axisLabel];

      for (let i of axisLabel) {
        let axisName = i.toLowerCase();

        if (axisName !== 'y' && axisName !== 'x') {
          throw new Error('axis label should be x or y');
        }

        this[axisName].fwhm = fwhm;
      }
    }
    /**
     * set a new height
     * @param {number} height - The maximal intensity of the shape.
     */


    setHeight(height) {
      this.height = height;
    }

  }
  /**
   * Return a parameterized function of a Gaussian2D shape (see README for equation).
   * @param {number} x - x value to calculate.
   * @param {number} y - y value to calculate.
   * @param {number} fwhmX - full width half maximum in the x axis.
   * @param {number} fwhmY - full width half maximum in the y axis.
   * @returns {number} - the z value of bi-dimensional gaussian with the current parameters.
   */

  Gaussian2D.fct = function fct(x, y, xFWHM = 500, yFWHM = 500) {
    return Math.exp(GAUSSIAN_EXP_FACTOR * (Math.pow(x / xFWHM, 2) + Math.pow(y / yFWHM, 2)));
  };
  /**
   * Compute the value of Full Width at Half Maximum (FWHM) from the width between the inflection points.
   * //https://mathworld.wolfram.com/Gaussian2DFunction.html
   * @param {number} width - Width between the inflection points
   * @returns {number} fwhm
   */


  Gaussian2D.widthToFWHM = function widthToFWHM(width) {
    return width * ROOT_2LN2;
  };
  /**
   * Compute the value of width between the inflection points from Full Width at Half Maximum (FWHM).
   * //https://mathworld.wolfram.com/Gaussian2DFunction.html
   * @param {number} fwhm - Full Width at Half Maximum.
   * @returns {number} width
   */


  Gaussian2D.fwhmToWidth = function fwhmToWidth(fwhm) {
    return fwhm / ROOT_2LN2;
  };
  /**
   * Calculate the volume of a specific shape.
   * @param {number} xFWHM - Full width at half maximum for x axis.
   * @param {number} yFWHM - Full width at half maximum for y axis.
   * @param {object} [options = {}] - options.
   * @param {number} [options.height = 1] - Maximum z value of the shape.
   * @returns {number} - returns the area of the specific shape and parameters.
   */


  Gaussian2D.getVolume = function getVolume(xFWHM, yFWHM, options = {}) {
    let {
      height = 1
    } = options;
    return height * Math.PI * xFWHM * yFWHM / Math.LN2 / 4;
  };
  /**@TODO look for a better factor
   * Calculate the number of times FWHM allows to reach a specific volume coverage.
   * @param {number} [volume=0.9999]
   * @returns {number}
   */


  Gaussian2D.getFactor = function getFactor(volume = 0.9999) {
    return Math.sqrt(2) * erfinv(volume);
  };

  function getShapeGenerator(options) {
    let {
      kind = 'Gaussian',
      options: shapeOptions
    } = options;

    switch (kind.toLowerCase().replace(/[^a-z^0-9]/g, '')) {
      case 'gaussian':
        return new Gaussian(shapeOptions);

      case 'lorentzian':
        return new Lorentzian(shapeOptions);

      case 'pseudovoigt':
        return new PseudoVoigt(shapeOptions);

      case 'gaussian2d':
        return new Gaussian2D(shapeOptions);

      default:
        throw new Error(`Unknown kind: ${kind}`);
    }
  }

  function rescale(input) {
    var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};

    if (!isAnyArray(input)) {
      throw new TypeError('input must be an array');
    } else if (input.length === 0) {
      throw new TypeError('input must not be empty');
    }

    var output;

    if (options.output !== undefined) {
      if (!isAnyArray(options.output)) {
        throw new TypeError('output option must be an array if specified');
      }

      output = options.output;
    } else {
      output = new Array(input.length);
    }

    var currentMin = min(input);
    var currentMax = max(input);

    if (currentMin === currentMax) {
      throw new RangeError('minimum and maximum input values are equal. Cannot rescale a constant array');
    }

    var _options$min = options.min,
        minValue = _options$min === void 0 ? options.autoMinMax ? currentMin : 0 : _options$min,
        _options$max = options.max,
        maxValue = _options$max === void 0 ? options.autoMinMax ? currentMax : 1 : _options$max;

    if (minValue >= maxValue) {
      throw new RangeError('min option must be smaller than max option');
    }

    var factor = (maxValue - minValue) / (currentMax - currentMin);

    for (var i = 0; i < input.length; i++) {
      output[i] = (input[i] - currentMin) * factor + minValue;
    }

    return output;
  }

  const indent = ' '.repeat(2);
  const indentData = ' '.repeat(4);
  function inspectMatrix() {
    return inspectMatrixWithOptions(this);
  }
  function inspectMatrixWithOptions(matrix, options = {}) {
    const {
      maxRows = 15,
      maxColumns = 10,
      maxNumSize = 8
    } = options;
    return `${matrix.constructor.name} {
${indent}[
${indentData}${inspectData(matrix, maxRows, maxColumns, maxNumSize)}
${indent}]
${indent}rows: ${matrix.rows}
${indent}columns: ${matrix.columns}
}`;
  }

  function inspectData(matrix, maxRows, maxColumns, maxNumSize) {
    const {
      rows,
      columns
    } = matrix;
    const maxI = Math.min(rows, maxRows);
    const maxJ = Math.min(columns, maxColumns);
    const result = [];

    for (let i = 0; i < maxI; i++) {
      let line = [];

      for (let j = 0; j < maxJ; j++) {
        line.push(formatNumber(matrix.get(i, j), maxNumSize));
      }

      result.push(`${line.join(' ')}`);
    }

    if (maxJ !== columns) {
      result[result.length - 1] += ` ... ${columns - maxColumns} more columns`;
    }

    if (maxI !== rows) {
      result.push(`... ${rows - maxRows} more rows`);
    }

    return result.join(`\n${indentData}`);
  }

  function formatNumber(num, maxNumSize) {
    const numStr = String(num);

    if (numStr.length <= maxNumSize) {
      return numStr.padEnd(maxNumSize, ' ');
    }

    const precise = num.toPrecision(maxNumSize - 2);

    if (precise.length <= maxNumSize) {
      return precise;
    }

    const exponential = num.toExponential(maxNumSize - 2);
    const eIndex = exponential.indexOf('e');
    const e = exponential.slice(eIndex);
    return exponential.slice(0, maxNumSize - e.length) + e;
  }

  function installMathOperations(AbstractMatrix, Matrix) {
    AbstractMatrix.prototype.add = function add(value) {
      if (typeof value === 'number') return this.addS(value);
      return this.addM(value);
    };

    AbstractMatrix.prototype.addS = function addS(value) {
      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.columns; j++) {
          this.set(i, j, this.get(i, j) + value);
        }
      }

      return this;
    };

    AbstractMatrix.prototype.addM = function addM(matrix) {
      matrix = Matrix.checkMatrix(matrix);

      if (this.rows !== matrix.rows || this.columns !== matrix.columns) {
        throw new RangeError('Matrices dimensions must be equal');
      }

      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.columns; j++) {
          this.set(i, j, this.get(i, j) + matrix.get(i, j));
        }
      }

      return this;
    };

    AbstractMatrix.add = function add(matrix, value) {
      const newMatrix = new Matrix(matrix);
      return newMatrix.add(value);
    };

    AbstractMatrix.prototype.sub = function sub(value) {
      if (typeof value === 'number') return this.subS(value);
      return this.subM(value);
    };

    AbstractMatrix.prototype.subS = function subS(value) {
      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.columns; j++) {
          this.set(i, j, this.get(i, j) - value);
        }
      }

      return this;
    };

    AbstractMatrix.prototype.subM = function subM(matrix) {
      matrix = Matrix.checkMatrix(matrix);

      if (this.rows !== matrix.rows || this.columns !== matrix.columns) {
        throw new RangeError('Matrices dimensions must be equal');
      }

      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.columns; j++) {
          this.set(i, j, this.get(i, j) - matrix.get(i, j));
        }
      }

      return this;
    };

    AbstractMatrix.sub = function sub(matrix, value) {
      const newMatrix = new Matrix(matrix);
      return newMatrix.sub(value);
    };

    AbstractMatrix.prototype.subtract = AbstractMatrix.prototype.sub;
    AbstractMatrix.prototype.subtractS = AbstractMatrix.prototype.subS;
    AbstractMatrix.prototype.subtractM = AbstractMatrix.prototype.subM;
    AbstractMatrix.subtract = AbstractMatrix.sub;

    AbstractMatrix.prototype.mul = function mul(value) {
      if (typeof value === 'number') return this.mulS(value);
      return this.mulM(value);
    };

    AbstractMatrix.prototype.mulS = function mulS(value) {
      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.columns; j++) {
          this.set(i, j, this.get(i, j) * value);
        }
      }

      return this;
    };

    AbstractMatrix.prototype.mulM = function mulM(matrix) {
      matrix = Matrix.checkMatrix(matrix);

      if (this.rows !== matrix.rows || this.columns !== matrix.columns) {
        throw new RangeError('Matrices dimensions must be equal');
      }

      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.columns; j++) {
          this.set(i, j, this.get(i, j) * matrix.get(i, j));
        }
      }

      return this;
    };

    AbstractMatrix.mul = function mul(matrix, value) {
      const newMatrix = new Matrix(matrix);
      return newMatrix.mul(value);
    };

    AbstractMatrix.prototype.multiply = AbstractMatrix.prototype.mul;
    AbstractMatrix.prototype.multiplyS = AbstractMatrix.prototype.mulS;
    AbstractMatrix.prototype.multiplyM = AbstractMatrix.prototype.mulM;
    AbstractMatrix.multiply = AbstractMatrix.mul;

    AbstractMatrix.prototype.div = function div(value) {
      if (typeof value === 'number') return this.divS(value);
      return this.divM(value);
    };

    AbstractMatrix.prototype.divS = function divS(value) {
      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.columns; j++) {
          this.set(i, j, this.get(i, j) / value);
        }
      }

      return this;
    };

    AbstractMatrix.prototype.divM = function divM(matrix) {
      matrix = Matrix.checkMatrix(matrix);

      if (this.rows !== matrix.rows || this.columns !== matrix.columns) {
        throw new RangeError('Matrices dimensions must be equal');
      }

      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.columns; j++) {
          this.set(i, j, this.get(i, j) / matrix.get(i, j));
        }
      }

      return this;
    };

    AbstractMatrix.div = function div(matrix, value) {
      const newMatrix = new Matrix(matrix);
      return newMatrix.div(value);
    };

    AbstractMatrix.prototype.divide = AbstractMatrix.prototype.div;
    AbstractMatrix.prototype.divideS = AbstractMatrix.prototype.divS;
    AbstractMatrix.prototype.divideM = AbstractMatrix.prototype.divM;
    AbstractMatrix.divide = AbstractMatrix.div;

    AbstractMatrix.prototype.mod = function mod(value) {
      if (typeof value === 'number') return this.modS(value);
      return this.modM(value);
    };

    AbstractMatrix.prototype.modS = function modS(value) {
      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.columns; j++) {
          this.set(i, j, this.get(i, j) % value);
        }
      }

      return this;
    };

    AbstractMatrix.prototype.modM = function modM(matrix) {
      matrix = Matrix.checkMatrix(matrix);

      if (this.rows !== matrix.rows || this.columns !== matrix.columns) {
        throw new RangeError('Matrices dimensions must be equal');
      }

      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.columns; j++) {
          this.set(i, j, this.get(i, j) % matrix.get(i, j));
        }
      }

      return this;
    };

    AbstractMatrix.mod = function mod(matrix, value) {
      const newMatrix = new Matrix(matrix);
      return newMatrix.mod(value);
    };

    AbstractMatrix.prototype.modulus = AbstractMatrix.prototype.mod;
    AbstractMatrix.prototype.modulusS = AbstractMatrix.prototype.modS;
    AbstractMatrix.prototype.modulusM = AbstractMatrix.prototype.modM;
    AbstractMatrix.modulus = AbstractMatrix.mod;

    AbstractMatrix.prototype.and = function and(value) {
      if (typeof value === 'number') return this.andS(value);
      return this.andM(value);
    };

    AbstractMatrix.prototype.andS = function andS(value) {
      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.columns; j++) {
          this.set(i, j, this.get(i, j) & value);
        }
      }

      return this;
    };

    AbstractMatrix.prototype.andM = function andM(matrix) {
      matrix = Matrix.checkMatrix(matrix);

      if (this.rows !== matrix.rows || this.columns !== matrix.columns) {
        throw new RangeError('Matrices dimensions must be equal');
      }

      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.columns; j++) {
          this.set(i, j, this.get(i, j) & matrix.get(i, j));
        }
      }

      return this;
    };

    AbstractMatrix.and = function and(matrix, value) {
      const newMatrix = new Matrix(matrix);
      return newMatrix.and(value);
    };

    AbstractMatrix.prototype.or = function or(value) {
      if (typeof value === 'number') return this.orS(value);
      return this.orM(value);
    };

    AbstractMatrix.prototype.orS = function orS(value) {
      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.columns; j++) {
          this.set(i, j, this.get(i, j) | value);
        }
      }

      return this;
    };

    AbstractMatrix.prototype.orM = function orM(matrix) {
      matrix = Matrix.checkMatrix(matrix);

      if (this.rows !== matrix.rows || this.columns !== matrix.columns) {
        throw new RangeError('Matrices dimensions must be equal');
      }

      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.columns; j++) {
          this.set(i, j, this.get(i, j) | matrix.get(i, j));
        }
      }

      return this;
    };

    AbstractMatrix.or = function or(matrix, value) {
      const newMatrix = new Matrix(matrix);
      return newMatrix.or(value);
    };

    AbstractMatrix.prototype.xor = function xor(value) {
      if (typeof value === 'number') return this.xorS(value);
      return this.xorM(value);
    };

    AbstractMatrix.prototype.xorS = function xorS(value) {
      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.columns; j++) {
          this.set(i, j, this.get(i, j) ^ value);
        }
      }

      return this;
    };

    AbstractMatrix.prototype.xorM = function xorM(matrix) {
      matrix = Matrix.checkMatrix(matrix);

      if (this.rows !== matrix.rows || this.columns !== matrix.columns) {
        throw new RangeError('Matrices dimensions must be equal');
      }

      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.columns; j++) {
          this.set(i, j, this.get(i, j) ^ matrix.get(i, j));
        }
      }

      return this;
    };

    AbstractMatrix.xor = function xor(matrix, value) {
      const newMatrix = new Matrix(matrix);
      return newMatrix.xor(value);
    };

    AbstractMatrix.prototype.leftShift = function leftShift(value) {
      if (typeof value === 'number') return this.leftShiftS(value);
      return this.leftShiftM(value);
    };

    AbstractMatrix.prototype.leftShiftS = function leftShiftS(value) {
      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.columns; j++) {
          this.set(i, j, this.get(i, j) << value);
        }
      }

      return this;
    };

    AbstractMatrix.prototype.leftShiftM = function leftShiftM(matrix) {
      matrix = Matrix.checkMatrix(matrix);

      if (this.rows !== matrix.rows || this.columns !== matrix.columns) {
        throw new RangeError('Matrices dimensions must be equal');
      }

      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.columns; j++) {
          this.set(i, j, this.get(i, j) << matrix.get(i, j));
        }
      }

      return this;
    };

    AbstractMatrix.leftShift = function leftShift(matrix, value) {
      const newMatrix = new Matrix(matrix);
      return newMatrix.leftShift(value);
    };

    AbstractMatrix.prototype.signPropagatingRightShift = function signPropagatingRightShift(value) {
      if (typeof value === 'number') return this.signPropagatingRightShiftS(value);
      return this.signPropagatingRightShiftM(value);
    };

    AbstractMatrix.prototype.signPropagatingRightShiftS = function signPropagatingRightShiftS(value) {
      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.columns; j++) {
          this.set(i, j, this.get(i, j) >> value);
        }
      }

      return this;
    };

    AbstractMatrix.prototype.signPropagatingRightShiftM = function signPropagatingRightShiftM(matrix) {
      matrix = Matrix.checkMatrix(matrix);

      if (this.rows !== matrix.rows || this.columns !== matrix.columns) {
        throw new RangeError('Matrices dimensions must be equal');
      }

      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.columns; j++) {
          this.set(i, j, this.get(i, j) >> matrix.get(i, j));
        }
      }

      return this;
    };

    AbstractMatrix.signPropagatingRightShift = function signPropagatingRightShift(matrix, value) {
      const newMatrix = new Matrix(matrix);
      return newMatrix.signPropagatingRightShift(value);
    };

    AbstractMatrix.prototype.rightShift = function rightShift(value) {
      if (typeof value === 'number') return this.rightShiftS(value);
      return this.rightShiftM(value);
    };

    AbstractMatrix.prototype.rightShiftS = function rightShiftS(value) {
      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.columns; j++) {
          this.set(i, j, this.get(i, j) >>> value);
        }
      }

      return this;
    };

    AbstractMatrix.prototype.rightShiftM = function rightShiftM(matrix) {
      matrix = Matrix.checkMatrix(matrix);

      if (this.rows !== matrix.rows || this.columns !== matrix.columns) {
        throw new RangeError('Matrices dimensions must be equal');
      }

      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.columns; j++) {
          this.set(i, j, this.get(i, j) >>> matrix.get(i, j));
        }
      }

      return this;
    };

    AbstractMatrix.rightShift = function rightShift(matrix, value) {
      const newMatrix = new Matrix(matrix);
      return newMatrix.rightShift(value);
    };

    AbstractMatrix.prototype.zeroFillRightShift = AbstractMatrix.prototype.rightShift;
    AbstractMatrix.prototype.zeroFillRightShiftS = AbstractMatrix.prototype.rightShiftS;
    AbstractMatrix.prototype.zeroFillRightShiftM = AbstractMatrix.prototype.rightShiftM;
    AbstractMatrix.zeroFillRightShift = AbstractMatrix.rightShift;

    AbstractMatrix.prototype.not = function not() {
      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.columns; j++) {
          this.set(i, j, ~this.get(i, j));
        }
      }

      return this;
    };

    AbstractMatrix.not = function not(matrix) {
      const newMatrix = new Matrix(matrix);
      return newMatrix.not();
    };

    AbstractMatrix.prototype.abs = function abs() {
      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.columns; j++) {
          this.set(i, j, Math.abs(this.get(i, j)));
        }
      }

      return this;
    };

    AbstractMatrix.abs = function abs(matrix) {
      const newMatrix = new Matrix(matrix);
      return newMatrix.abs();
    };

    AbstractMatrix.prototype.acos = function acos() {
      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.columns; j++) {
          this.set(i, j, Math.acos(this.get(i, j)));
        }
      }

      return this;
    };

    AbstractMatrix.acos = function acos(matrix) {
      const newMatrix = new Matrix(matrix);
      return newMatrix.acos();
    };

    AbstractMatrix.prototype.acosh = function acosh() {
      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.columns; j++) {
          this.set(i, j, Math.acosh(this.get(i, j)));
        }
      }

      return this;
    };

    AbstractMatrix.acosh = function acosh(matrix) {
      const newMatrix = new Matrix(matrix);
      return newMatrix.acosh();
    };

    AbstractMatrix.prototype.asin = function asin() {
      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.columns; j++) {
          this.set(i, j, Math.asin(this.get(i, j)));
        }
      }

      return this;
    };

    AbstractMatrix.asin = function asin(matrix) {
      const newMatrix = new Matrix(matrix);
      return newMatrix.asin();
    };

    AbstractMatrix.prototype.asinh = function asinh() {
      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.columns; j++) {
          this.set(i, j, Math.asinh(this.get(i, j)));
        }
      }

      return this;
    };

    AbstractMatrix.asinh = function asinh(matrix) {
      const newMatrix = new Matrix(matrix);
      return newMatrix.asinh();
    };

    AbstractMatrix.prototype.atan = function atan() {
      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.columns; j++) {
          this.set(i, j, Math.atan(this.get(i, j)));
        }
      }

      return this;
    };

    AbstractMatrix.atan = function atan(matrix) {
      const newMatrix = new Matrix(matrix);
      return newMatrix.atan();
    };

    AbstractMatrix.prototype.atanh = function atanh() {
      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.columns; j++) {
          this.set(i, j, Math.atanh(this.get(i, j)));
        }
      }

      return this;
    };

    AbstractMatrix.atanh = function atanh(matrix) {
      const newMatrix = new Matrix(matrix);
      return newMatrix.atanh();
    };

    AbstractMatrix.prototype.cbrt = function cbrt() {
      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.columns; j++) {
          this.set(i, j, Math.cbrt(this.get(i, j)));
        }
      }

      return this;
    };

    AbstractMatrix.cbrt = function cbrt(matrix) {
      const newMatrix = new Matrix(matrix);
      return newMatrix.cbrt();
    };

    AbstractMatrix.prototype.ceil = function ceil() {
      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.columns; j++) {
          this.set(i, j, Math.ceil(this.get(i, j)));
        }
      }

      return this;
    };

    AbstractMatrix.ceil = function ceil(matrix) {
      const newMatrix = new Matrix(matrix);
      return newMatrix.ceil();
    };

    AbstractMatrix.prototype.clz32 = function clz32() {
      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.columns; j++) {
          this.set(i, j, Math.clz32(this.get(i, j)));
        }
      }

      return this;
    };

    AbstractMatrix.clz32 = function clz32(matrix) {
      const newMatrix = new Matrix(matrix);
      return newMatrix.clz32();
    };

    AbstractMatrix.prototype.cos = function cos() {
      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.columns; j++) {
          this.set(i, j, Math.cos(this.get(i, j)));
        }
      }

      return this;
    };

    AbstractMatrix.cos = function cos(matrix) {
      const newMatrix = new Matrix(matrix);
      return newMatrix.cos();
    };

    AbstractMatrix.prototype.cosh = function cosh() {
      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.columns; j++) {
          this.set(i, j, Math.cosh(this.get(i, j)));
        }
      }

      return this;
    };

    AbstractMatrix.cosh = function cosh(matrix) {
      const newMatrix = new Matrix(matrix);
      return newMatrix.cosh();
    };

    AbstractMatrix.prototype.exp = function exp() {
      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.columns; j++) {
          this.set(i, j, Math.exp(this.get(i, j)));
        }
      }

      return this;
    };

    AbstractMatrix.exp = function exp(matrix) {
      const newMatrix = new Matrix(matrix);
      return newMatrix.exp();
    };

    AbstractMatrix.prototype.expm1 = function expm1() {
      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.columns; j++) {
          this.set(i, j, Math.expm1(this.get(i, j)));
        }
      }

      return this;
    };

    AbstractMatrix.expm1 = function expm1(matrix) {
      const newMatrix = new Matrix(matrix);
      return newMatrix.expm1();
    };

    AbstractMatrix.prototype.floor = function floor() {
      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.columns; j++) {
          this.set(i, j, Math.floor(this.get(i, j)));
        }
      }

      return this;
    };

    AbstractMatrix.floor = function floor(matrix) {
      const newMatrix = new Matrix(matrix);
      return newMatrix.floor();
    };

    AbstractMatrix.prototype.fround = function fround() {
      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.columns; j++) {
          this.set(i, j, Math.fround(this.get(i, j)));
        }
      }

      return this;
    };

    AbstractMatrix.fround = function fround(matrix) {
      const newMatrix = new Matrix(matrix);
      return newMatrix.fround();
    };

    AbstractMatrix.prototype.log = function log() {
      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.columns; j++) {
          this.set(i, j, Math.log(this.get(i, j)));
        }
      }

      return this;
    };

    AbstractMatrix.log = function log(matrix) {
      const newMatrix = new Matrix(matrix);
      return newMatrix.log();
    };

    AbstractMatrix.prototype.log1p = function log1p() {
      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.columns; j++) {
          this.set(i, j, Math.log1p(this.get(i, j)));
        }
      }

      return this;
    };

    AbstractMatrix.log1p = function log1p(matrix) {
      const newMatrix = new Matrix(matrix);
      return newMatrix.log1p();
    };

    AbstractMatrix.prototype.log10 = function log10() {
      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.columns; j++) {
          this.set(i, j, Math.log10(this.get(i, j)));
        }
      }

      return this;
    };

    AbstractMatrix.log10 = function log10(matrix) {
      const newMatrix = new Matrix(matrix);
      return newMatrix.log10();
    };

    AbstractMatrix.prototype.log2 = function log2() {
      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.columns; j++) {
          this.set(i, j, Math.log2(this.get(i, j)));
        }
      }

      return this;
    };

    AbstractMatrix.log2 = function log2(matrix) {
      const newMatrix = new Matrix(matrix);
      return newMatrix.log2();
    };

    AbstractMatrix.prototype.round = function round() {
      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.columns; j++) {
          this.set(i, j, Math.round(this.get(i, j)));
        }
      }

      return this;
    };

    AbstractMatrix.round = function round(matrix) {
      const newMatrix = new Matrix(matrix);
      return newMatrix.round();
    };

    AbstractMatrix.prototype.sign = function sign() {
      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.columns; j++) {
          this.set(i, j, Math.sign(this.get(i, j)));
        }
      }

      return this;
    };

    AbstractMatrix.sign = function sign(matrix) {
      const newMatrix = new Matrix(matrix);
      return newMatrix.sign();
    };

    AbstractMatrix.prototype.sin = function sin() {
      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.columns; j++) {
          this.set(i, j, Math.sin(this.get(i, j)));
        }
      }

      return this;
    };

    AbstractMatrix.sin = function sin(matrix) {
      const newMatrix = new Matrix(matrix);
      return newMatrix.sin();
    };

    AbstractMatrix.prototype.sinh = function sinh() {
      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.columns; j++) {
          this.set(i, j, Math.sinh(this.get(i, j)));
        }
      }

      return this;
    };

    AbstractMatrix.sinh = function sinh(matrix) {
      const newMatrix = new Matrix(matrix);
      return newMatrix.sinh();
    };

    AbstractMatrix.prototype.sqrt = function sqrt() {
      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.columns; j++) {
          this.set(i, j, Math.sqrt(this.get(i, j)));
        }
      }

      return this;
    };

    AbstractMatrix.sqrt = function sqrt(matrix) {
      const newMatrix = new Matrix(matrix);
      return newMatrix.sqrt();
    };

    AbstractMatrix.prototype.tan = function tan() {
      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.columns; j++) {
          this.set(i, j, Math.tan(this.get(i, j)));
        }
      }

      return this;
    };

    AbstractMatrix.tan = function tan(matrix) {
      const newMatrix = new Matrix(matrix);
      return newMatrix.tan();
    };

    AbstractMatrix.prototype.tanh = function tanh() {
      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.columns; j++) {
          this.set(i, j, Math.tanh(this.get(i, j)));
        }
      }

      return this;
    };

    AbstractMatrix.tanh = function tanh(matrix) {
      const newMatrix = new Matrix(matrix);
      return newMatrix.tanh();
    };

    AbstractMatrix.prototype.trunc = function trunc() {
      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.columns; j++) {
          this.set(i, j, Math.trunc(this.get(i, j)));
        }
      }

      return this;
    };

    AbstractMatrix.trunc = function trunc(matrix) {
      const newMatrix = new Matrix(matrix);
      return newMatrix.trunc();
    };

    AbstractMatrix.pow = function pow(matrix, arg0) {
      const newMatrix = new Matrix(matrix);
      return newMatrix.pow(arg0);
    };

    AbstractMatrix.prototype.pow = function pow(value) {
      if (typeof value === 'number') return this.powS(value);
      return this.powM(value);
    };

    AbstractMatrix.prototype.powS = function powS(value) {
      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.columns; j++) {
          this.set(i, j, Math.pow(this.get(i, j), value));
        }
      }

      return this;
    };

    AbstractMatrix.prototype.powM = function powM(matrix) {
      matrix = Matrix.checkMatrix(matrix);

      if (this.rows !== matrix.rows || this.columns !== matrix.columns) {
        throw new RangeError('Matrices dimensions must be equal');
      }

      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.columns; j++) {
          this.set(i, j, Math.pow(this.get(i, j), matrix.get(i, j)));
        }
      }

      return this;
    };
  }

  /**
   * @private
   * Check that a row index is not out of bounds
   * @param {Matrix} matrix
   * @param {number} index
   * @param {boolean} [outer]
   */
  function checkRowIndex(matrix, index, outer) {
    let max = outer ? matrix.rows : matrix.rows - 1;

    if (index < 0 || index > max) {
      throw new RangeError('Row index out of range');
    }
  }
  /**
   * @private
   * Check that a column index is not out of bounds
   * @param {Matrix} matrix
   * @param {number} index
   * @param {boolean} [outer]
   */

  function checkColumnIndex(matrix, index, outer) {
    let max = outer ? matrix.columns : matrix.columns - 1;

    if (index < 0 || index > max) {
      throw new RangeError('Column index out of range');
    }
  }
  /**
   * @private
   * Check that the provided vector is an array with the right length
   * @param {Matrix} matrix
   * @param {Array|Matrix} vector
   * @return {Array}
   * @throws {RangeError}
   */

  function checkRowVector(matrix, vector) {
    if (vector.to1DArray) {
      vector = vector.to1DArray();
    }

    if (vector.length !== matrix.columns) {
      throw new RangeError('vector size must be the same as the number of columns');
    }

    return vector;
  }
  /**
   * @private
   * Check that the provided vector is an array with the right length
   * @param {Matrix} matrix
   * @param {Array|Matrix} vector
   * @return {Array}
   * @throws {RangeError}
   */

  function checkColumnVector(matrix, vector) {
    if (vector.to1DArray) {
      vector = vector.to1DArray();
    }

    if (vector.length !== matrix.rows) {
      throw new RangeError('vector size must be the same as the number of rows');
    }

    return vector;
  }
  function checkIndices(matrix, rowIndices, columnIndices) {
    return {
      row: checkRowIndices(matrix, rowIndices),
      column: checkColumnIndices(matrix, columnIndices)
    };
  }
  function checkRowIndices(matrix, rowIndices) {
    if (typeof rowIndices !== 'object') {
      throw new TypeError('unexpected type for row indices');
    }

    let rowOut = rowIndices.some(r => {
      return r < 0 || r >= matrix.rows;
    });

    if (rowOut) {
      throw new RangeError('row indices are out of range');
    }

    if (!Array.isArray(rowIndices)) rowIndices = Array.from(rowIndices);
    return rowIndices;
  }
  function checkColumnIndices(matrix, columnIndices) {
    if (typeof columnIndices !== 'object') {
      throw new TypeError('unexpected type for column indices');
    }

    let columnOut = columnIndices.some(c => {
      return c < 0 || c >= matrix.columns;
    });

    if (columnOut) {
      throw new RangeError('column indices are out of range');
    }

    if (!Array.isArray(columnIndices)) columnIndices = Array.from(columnIndices);
    return columnIndices;
  }
  function checkRange(matrix, startRow, endRow, startColumn, endColumn) {
    if (arguments.length !== 5) {
      throw new RangeError('expected 4 arguments');
    }

    checkNumber('startRow', startRow);
    checkNumber('endRow', endRow);
    checkNumber('startColumn', startColumn);
    checkNumber('endColumn', endColumn);

    if (startRow > endRow || startColumn > endColumn || startRow < 0 || startRow >= matrix.rows || endRow < 0 || endRow >= matrix.rows || startColumn < 0 || startColumn >= matrix.columns || endColumn < 0 || endColumn >= matrix.columns) {
      throw new RangeError('Submatrix indices are out of range');
    }
  }
  function newArray(length, value = 0) {
    let array = [];

    for (let i = 0; i < length; i++) {
      array.push(value);
    }

    return array;
  }

  function checkNumber(name, value) {
    if (typeof value !== 'number') {
      throw new TypeError(`${name} must be a number`);
    }
  }

  function checkNonEmpty(matrix) {
    if (matrix.isEmpty()) {
      throw new Error('Empty matrix has no elements to index');
    }
  }

  function sumByRow(matrix) {
    let sum = newArray(matrix.rows);

    for (let i = 0; i < matrix.rows; ++i) {
      for (let j = 0; j < matrix.columns; ++j) {
        sum[i] += matrix.get(i, j);
      }
    }

    return sum;
  }
  function sumByColumn(matrix) {
    let sum = newArray(matrix.columns);

    for (let i = 0; i < matrix.rows; ++i) {
      for (let j = 0; j < matrix.columns; ++j) {
        sum[j] += matrix.get(i, j);
      }
    }

    return sum;
  }
  function sumAll(matrix) {
    let v = 0;

    for (let i = 0; i < matrix.rows; i++) {
      for (let j = 0; j < matrix.columns; j++) {
        v += matrix.get(i, j);
      }
    }

    return v;
  }
  function productByRow(matrix) {
    let sum = newArray(matrix.rows, 1);

    for (let i = 0; i < matrix.rows; ++i) {
      for (let j = 0; j < matrix.columns; ++j) {
        sum[i] *= matrix.get(i, j);
      }
    }

    return sum;
  }
  function productByColumn(matrix) {
    let sum = newArray(matrix.columns, 1);

    for (let i = 0; i < matrix.rows; ++i) {
      for (let j = 0; j < matrix.columns; ++j) {
        sum[j] *= matrix.get(i, j);
      }
    }

    return sum;
  }
  function productAll(matrix) {
    let v = 1;

    for (let i = 0; i < matrix.rows; i++) {
      for (let j = 0; j < matrix.columns; j++) {
        v *= matrix.get(i, j);
      }
    }

    return v;
  }
  function varianceByRow(matrix, unbiased, mean) {
    const rows = matrix.rows;
    const cols = matrix.columns;
    const variance = [];

    for (let i = 0; i < rows; i++) {
      let sum1 = 0;
      let sum2 = 0;
      let x = 0;

      for (let j = 0; j < cols; j++) {
        x = matrix.get(i, j) - mean[i];
        sum1 += x;
        sum2 += x * x;
      }

      if (unbiased) {
        variance.push((sum2 - sum1 * sum1 / cols) / (cols - 1));
      } else {
        variance.push((sum2 - sum1 * sum1 / cols) / cols);
      }
    }

    return variance;
  }
  function varianceByColumn(matrix, unbiased, mean) {
    const rows = matrix.rows;
    const cols = matrix.columns;
    const variance = [];

    for (let j = 0; j < cols; j++) {
      let sum1 = 0;
      let sum2 = 0;
      let x = 0;

      for (let i = 0; i < rows; i++) {
        x = matrix.get(i, j) - mean[j];
        sum1 += x;
        sum2 += x * x;
      }

      if (unbiased) {
        variance.push((sum2 - sum1 * sum1 / rows) / (rows - 1));
      } else {
        variance.push((sum2 - sum1 * sum1 / rows) / rows);
      }
    }

    return variance;
  }
  function varianceAll(matrix, unbiased, mean) {
    const rows = matrix.rows;
    const cols = matrix.columns;
    const size = rows * cols;
    let sum1 = 0;
    let sum2 = 0;
    let x = 0;

    for (let i = 0; i < rows; i++) {
      for (let j = 0; j < cols; j++) {
        x = matrix.get(i, j) - mean;
        sum1 += x;
        sum2 += x * x;
      }
    }

    if (unbiased) {
      return (sum2 - sum1 * sum1 / size) / (size - 1);
    } else {
      return (sum2 - sum1 * sum1 / size) / size;
    }
  }
  function centerByRow(matrix, mean) {
    for (let i = 0; i < matrix.rows; i++) {
      for (let j = 0; j < matrix.columns; j++) {
        matrix.set(i, j, matrix.get(i, j) - mean[i]);
      }
    }
  }
  function centerByColumn(matrix, mean) {
    for (let i = 0; i < matrix.rows; i++) {
      for (let j = 0; j < matrix.columns; j++) {
        matrix.set(i, j, matrix.get(i, j) - mean[j]);
      }
    }
  }
  function centerAll(matrix, mean) {
    for (let i = 0; i < matrix.rows; i++) {
      for (let j = 0; j < matrix.columns; j++) {
        matrix.set(i, j, matrix.get(i, j) - mean);
      }
    }
  }
  function getScaleByRow(matrix) {
    const scale = [];

    for (let i = 0; i < matrix.rows; i++) {
      let sum = 0;

      for (let j = 0; j < matrix.columns; j++) {
        sum += Math.pow(matrix.get(i, j), 2) / (matrix.columns - 1);
      }

      scale.push(Math.sqrt(sum));
    }

    return scale;
  }
  function scaleByRow(matrix, scale) {
    for (let i = 0; i < matrix.rows; i++) {
      for (let j = 0; j < matrix.columns; j++) {
        matrix.set(i, j, matrix.get(i, j) / scale[i]);
      }
    }
  }
  function getScaleByColumn(matrix) {
    const scale = [];

    for (let j = 0; j < matrix.columns; j++) {
      let sum = 0;

      for (let i = 0; i < matrix.rows; i++) {
        sum += Math.pow(matrix.get(i, j), 2) / (matrix.rows - 1);
      }

      scale.push(Math.sqrt(sum));
    }

    return scale;
  }
  function scaleByColumn(matrix, scale) {
    for (let i = 0; i < matrix.rows; i++) {
      for (let j = 0; j < matrix.columns; j++) {
        matrix.set(i, j, matrix.get(i, j) / scale[j]);
      }
    }
  }
  function getScaleAll(matrix) {
    const divider = matrix.size - 1;
    let sum = 0;

    for (let j = 0; j < matrix.columns; j++) {
      for (let i = 0; i < matrix.rows; i++) {
        sum += Math.pow(matrix.get(i, j), 2) / divider;
      }
    }

    return Math.sqrt(sum);
  }
  function scaleAll(matrix, scale) {
    for (let i = 0; i < matrix.rows; i++) {
      for (let j = 0; j < matrix.columns; j++) {
        matrix.set(i, j, matrix.get(i, j) / scale);
      }
    }
  }

  class AbstractMatrix {
    static from1DArray(newRows, newColumns, newData) {
      let length = newRows * newColumns;

      if (length !== newData.length) {
        throw new RangeError('data length does not match given dimensions');
      }

      let newMatrix = new Matrix(newRows, newColumns);

      for (let row = 0; row < newRows; row++) {
        for (let column = 0; column < newColumns; column++) {
          newMatrix.set(row, column, newData[row * newColumns + column]);
        }
      }

      return newMatrix;
    }

    static rowVector(newData) {
      let vector = new Matrix(1, newData.length);

      for (let i = 0; i < newData.length; i++) {
        vector.set(0, i, newData[i]);
      }

      return vector;
    }

    static columnVector(newData) {
      let vector = new Matrix(newData.length, 1);

      for (let i = 0; i < newData.length; i++) {
        vector.set(i, 0, newData[i]);
      }

      return vector;
    }

    static zeros(rows, columns) {
      return new Matrix(rows, columns);
    }

    static ones(rows, columns) {
      return new Matrix(rows, columns).fill(1);
    }

    static rand(rows, columns, options = {}) {
      if (typeof options !== 'object') {
        throw new TypeError('options must be an object');
      }

      const {
        random = Math.random
      } = options;
      let matrix = new Matrix(rows, columns);

      for (let i = 0; i < rows; i++) {
        for (let j = 0; j < columns; j++) {
          matrix.set(i, j, random());
        }
      }

      return matrix;
    }

    static randInt(rows, columns, options = {}) {
      if (typeof options !== 'object') {
        throw new TypeError('options must be an object');
      }

      const {
        min = 0,
        max = 1000,
        random = Math.random
      } = options;
      if (!Number.isInteger(min)) throw new TypeError('min must be an integer');
      if (!Number.isInteger(max)) throw new TypeError('max must be an integer');
      if (min >= max) throw new RangeError('min must be smaller than max');
      let interval = max - min;
      let matrix = new Matrix(rows, columns);

      for (let i = 0; i < rows; i++) {
        for (let j = 0; j < columns; j++) {
          let value = min + Math.round(random() * interval);
          matrix.set(i, j, value);
        }
      }

      return matrix;
    }

    static eye(rows, columns, value) {
      if (columns === undefined) columns = rows;
      if (value === undefined) value = 1;
      let min = Math.min(rows, columns);
      let matrix = this.zeros(rows, columns);

      for (let i = 0; i < min; i++) {
        matrix.set(i, i, value);
      }

      return matrix;
    }

    static diag(data, rows, columns) {
      let l = data.length;
      if (rows === undefined) rows = l;
      if (columns === undefined) columns = rows;
      let min = Math.min(l, rows, columns);
      let matrix = this.zeros(rows, columns);

      for (let i = 0; i < min; i++) {
        matrix.set(i, i, data[i]);
      }

      return matrix;
    }

    static min(matrix1, matrix2) {
      matrix1 = this.checkMatrix(matrix1);
      matrix2 = this.checkMatrix(matrix2);
      let rows = matrix1.rows;
      let columns = matrix1.columns;
      let result = new Matrix(rows, columns);

      for (let i = 0; i < rows; i++) {
        for (let j = 0; j < columns; j++) {
          result.set(i, j, Math.min(matrix1.get(i, j), matrix2.get(i, j)));
        }
      }

      return result;
    }

    static max(matrix1, matrix2) {
      matrix1 = this.checkMatrix(matrix1);
      matrix2 = this.checkMatrix(matrix2);
      let rows = matrix1.rows;
      let columns = matrix1.columns;
      let result = new this(rows, columns);

      for (let i = 0; i < rows; i++) {
        for (let j = 0; j < columns; j++) {
          result.set(i, j, Math.max(matrix1.get(i, j), matrix2.get(i, j)));
        }
      }

      return result;
    }

    static checkMatrix(value) {
      return AbstractMatrix.isMatrix(value) ? value : new Matrix(value);
    }

    static isMatrix(value) {
      return value != null && value.klass === 'Matrix';
    }

    get size() {
      return this.rows * this.columns;
    }

    apply(callback) {
      if (typeof callback !== 'function') {
        throw new TypeError('callback must be a function');
      }

      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.columns; j++) {
          callback.call(this, i, j);
        }
      }

      return this;
    }

    to1DArray() {
      let array = [];

      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.columns; j++) {
          array.push(this.get(i, j));
        }
      }

      return array;
    }

    to2DArray() {
      let copy = [];

      for (let i = 0; i < this.rows; i++) {
        copy.push([]);

        for (let j = 0; j < this.columns; j++) {
          copy[i].push(this.get(i, j));
        }
      }

      return copy;
    }

    toJSON() {
      return this.to2DArray();
    }

    isRowVector() {
      return this.rows === 1;
    }

    isColumnVector() {
      return this.columns === 1;
    }

    isVector() {
      return this.rows === 1 || this.columns === 1;
    }

    isSquare() {
      return this.rows === this.columns;
    }

    isEmpty() {
      return this.rows === 0 || this.columns === 0;
    }

    isSymmetric() {
      if (this.isSquare()) {
        for (let i = 0; i < this.rows; i++) {
          for (let j = 0; j <= i; j++) {
            if (this.get(i, j) !== this.get(j, i)) {
              return false;
            }
          }
        }

        return true;
      }

      return false;
    }

    isEchelonForm() {
      let i = 0;
      let j = 0;
      let previousColumn = -1;
      let isEchelonForm = true;
      let checked = false;

      while (i < this.rows && isEchelonForm) {
        j = 0;
        checked = false;

        while (j < this.columns && checked === false) {
          if (this.get(i, j) === 0) {
            j++;
          } else if (this.get(i, j) === 1 && j > previousColumn) {
            checked = true;
            previousColumn = j;
          } else {
            isEchelonForm = false;
            checked = true;
          }
        }

        i++;
      }

      return isEchelonForm;
    }

    isReducedEchelonForm() {
      let i = 0;
      let j = 0;
      let previousColumn = -1;
      let isReducedEchelonForm = true;
      let checked = false;

      while (i < this.rows && isReducedEchelonForm) {
        j = 0;
        checked = false;

        while (j < this.columns && checked === false) {
          if (this.get(i, j) === 0) {
            j++;
          } else if (this.get(i, j) === 1 && j > previousColumn) {
            checked = true;
            previousColumn = j;
          } else {
            isReducedEchelonForm = false;
            checked = true;
          }
        }

        for (let k = j + 1; k < this.rows; k++) {
          if (this.get(i, k) !== 0) {
            isReducedEchelonForm = false;
          }
        }

        i++;
      }

      return isReducedEchelonForm;
    }

    echelonForm() {
      let result = this.clone();
      let h = 0;
      let k = 0;

      while (h < result.rows && k < result.columns) {
        let iMax = h;

        for (let i = h; i < result.rows; i++) {
          if (result.get(i, k) > result.get(iMax, k)) {
            iMax = i;
          }
        }

        if (result.get(iMax, k) === 0) {
          k++;
        } else {
          result.swapRows(h, iMax);
          let tmp = result.get(h, k);

          for (let j = k; j < result.columns; j++) {
            result.set(h, j, result.get(h, j) / tmp);
          }

          for (let i = h + 1; i < result.rows; i++) {
            let factor = result.get(i, k) / result.get(h, k);
            result.set(i, k, 0);

            for (let j = k + 1; j < result.columns; j++) {
              result.set(i, j, result.get(i, j) - result.get(h, j) * factor);
            }
          }

          h++;
          k++;
        }
      }

      return result;
    }

    reducedEchelonForm() {
      let result = this.echelonForm();
      let m = result.columns;
      let n = result.rows;
      let h = n - 1;

      while (h >= 0) {
        if (result.maxRow(h) === 0) {
          h--;
        } else {
          let p = 0;
          let pivot = false;

          while (p < n && pivot === false) {
            if (result.get(h, p) === 1) {
              pivot = true;
            } else {
              p++;
            }
          }

          for (let i = 0; i < h; i++) {
            let factor = result.get(i, p);

            for (let j = p; j < m; j++) {
              let tmp = result.get(i, j) - factor * result.get(h, j);
              result.set(i, j, tmp);
            }
          }

          h--;
        }
      }

      return result;
    }

    set() {
      throw new Error('set method is unimplemented');
    }

    get() {
      throw new Error('get method is unimplemented');
    }

    repeat(options = {}) {
      if (typeof options !== 'object') {
        throw new TypeError('options must be an object');
      }

      const {
        rows = 1,
        columns = 1
      } = options;

      if (!Number.isInteger(rows) || rows <= 0) {
        throw new TypeError('rows must be a positive integer');
      }

      if (!Number.isInteger(columns) || columns <= 0) {
        throw new TypeError('columns must be a positive integer');
      }

      let matrix = new Matrix(this.rows * rows, this.columns * columns);

      for (let i = 0; i < rows; i++) {
        for (let j = 0; j < columns; j++) {
          matrix.setSubMatrix(this, this.rows * i, this.columns * j);
        }
      }

      return matrix;
    }

    fill(value) {
      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.columns; j++) {
          this.set(i, j, value);
        }
      }

      return this;
    }

    neg() {
      return this.mulS(-1);
    }

    getRow(index) {
      checkRowIndex(this, index);
      let row = [];

      for (let i = 0; i < this.columns; i++) {
        row.push(this.get(index, i));
      }

      return row;
    }

    getRowVector(index) {
      return Matrix.rowVector(this.getRow(index));
    }

    setRow(index, array) {
      checkRowIndex(this, index);
      array = checkRowVector(this, array);

      for (let i = 0; i < this.columns; i++) {
        this.set(index, i, array[i]);
      }

      return this;
    }

    swapRows(row1, row2) {
      checkRowIndex(this, row1);
      checkRowIndex(this, row2);

      for (let i = 0; i < this.columns; i++) {
        let temp = this.get(row1, i);
        this.set(row1, i, this.get(row2, i));
        this.set(row2, i, temp);
      }

      return this;
    }

    getColumn(index) {
      checkColumnIndex(this, index);
      let column = [];

      for (let i = 0; i < this.rows; i++) {
        column.push(this.get(i, index));
      }

      return column;
    }

    getColumnVector(index) {
      return Matrix.columnVector(this.getColumn(index));
    }

    setColumn(index, array) {
      checkColumnIndex(this, index);
      array = checkColumnVector(this, array);

      for (let i = 0; i < this.rows; i++) {
        this.set(i, index, array[i]);
      }

      return this;
    }

    swapColumns(column1, column2) {
      checkColumnIndex(this, column1);
      checkColumnIndex(this, column2);

      for (let i = 0; i < this.rows; i++) {
        let temp = this.get(i, column1);
        this.set(i, column1, this.get(i, column2));
        this.set(i, column2, temp);
      }

      return this;
    }

    addRowVector(vector) {
      vector = checkRowVector(this, vector);

      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.columns; j++) {
          this.set(i, j, this.get(i, j) + vector[j]);
        }
      }

      return this;
    }

    subRowVector(vector) {
      vector = checkRowVector(this, vector);

      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.columns; j++) {
          this.set(i, j, this.get(i, j) - vector[j]);
        }
      }

      return this;
    }

    mulRowVector(vector) {
      vector = checkRowVector(this, vector);

      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.columns; j++) {
          this.set(i, j, this.get(i, j) * vector[j]);
        }
      }

      return this;
    }

    divRowVector(vector) {
      vector = checkRowVector(this, vector);

      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.columns; j++) {
          this.set(i, j, this.get(i, j) / vector[j]);
        }
      }

      return this;
    }

    addColumnVector(vector) {
      vector = checkColumnVector(this, vector);

      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.columns; j++) {
          this.set(i, j, this.get(i, j) + vector[i]);
        }
      }

      return this;
    }

    subColumnVector(vector) {
      vector = checkColumnVector(this, vector);

      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.columns; j++) {
          this.set(i, j, this.get(i, j) - vector[i]);
        }
      }

      return this;
    }

    mulColumnVector(vector) {
      vector = checkColumnVector(this, vector);

      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.columns; j++) {
          this.set(i, j, this.get(i, j) * vector[i]);
        }
      }

      return this;
    }

    divColumnVector(vector) {
      vector = checkColumnVector(this, vector);

      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.columns; j++) {
          this.set(i, j, this.get(i, j) / vector[i]);
        }
      }

      return this;
    }

    mulRow(index, value) {
      checkRowIndex(this, index);

      for (let i = 0; i < this.columns; i++) {
        this.set(index, i, this.get(index, i) * value);
      }

      return this;
    }

    mulColumn(index, value) {
      checkColumnIndex(this, index);

      for (let i = 0; i < this.rows; i++) {
        this.set(i, index, this.get(i, index) * value);
      }

      return this;
    }

    max() {
      if (this.isEmpty()) {
        return NaN;
      }

      let v = this.get(0, 0);

      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.columns; j++) {
          if (this.get(i, j) > v) {
            v = this.get(i, j);
          }
        }
      }

      return v;
    }

    maxIndex() {
      checkNonEmpty(this);
      let v = this.get(0, 0);
      let idx = [0, 0];

      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.columns; j++) {
          if (this.get(i, j) > v) {
            v = this.get(i, j);
            idx[0] = i;
            idx[1] = j;
          }
        }
      }

      return idx;
    }

    min() {
      if (this.isEmpty()) {
        return NaN;
      }

      let v = this.get(0, 0);

      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.columns; j++) {
          if (this.get(i, j) < v) {
            v = this.get(i, j);
          }
        }
      }

      return v;
    }

    minIndex() {
      checkNonEmpty(this);
      let v = this.get(0, 0);
      let idx = [0, 0];

      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.columns; j++) {
          if (this.get(i, j) < v) {
            v = this.get(i, j);
            idx[0] = i;
            idx[1] = j;
          }
        }
      }

      return idx;
    }

    maxRow(row) {
      checkRowIndex(this, row);

      if (this.isEmpty()) {
        return NaN;
      }

      let v = this.get(row, 0);

      for (let i = 1; i < this.columns; i++) {
        if (this.get(row, i) > v) {
          v = this.get(row, i);
        }
      }

      return v;
    }

    maxRowIndex(row) {
      checkRowIndex(this, row);
      checkNonEmpty(this);
      let v = this.get(row, 0);
      let idx = [row, 0];

      for (let i = 1; i < this.columns; i++) {
        if (this.get(row, i) > v) {
          v = this.get(row, i);
          idx[1] = i;
        }
      }

      return idx;
    }

    minRow(row) {
      checkRowIndex(this, row);

      if (this.isEmpty()) {
        return NaN;
      }

      let v = this.get(row, 0);

      for (let i = 1; i < this.columns; i++) {
        if (this.get(row, i) < v) {
          v = this.get(row, i);
        }
      }

      return v;
    }

    minRowIndex(row) {
      checkRowIndex(this, row);
      checkNonEmpty(this);
      let v = this.get(row, 0);
      let idx = [row, 0];

      for (let i = 1; i < this.columns; i++) {
        if (this.get(row, i) < v) {
          v = this.get(row, i);
          idx[1] = i;
        }
      }

      return idx;
    }

    maxColumn(column) {
      checkColumnIndex(this, column);

      if (this.isEmpty()) {
        return NaN;
      }

      let v = this.get(0, column);

      for (let i = 1; i < this.rows; i++) {
        if (this.get(i, column) > v) {
          v = this.get(i, column);
        }
      }

      return v;
    }

    maxColumnIndex(column) {
      checkColumnIndex(this, column);
      checkNonEmpty(this);
      let v = this.get(0, column);
      let idx = [0, column];

      for (let i = 1; i < this.rows; i++) {
        if (this.get(i, column) > v) {
          v = this.get(i, column);
          idx[0] = i;
        }
      }

      return idx;
    }

    minColumn(column) {
      checkColumnIndex(this, column);

      if (this.isEmpty()) {
        return NaN;
      }

      let v = this.get(0, column);

      for (let i = 1; i < this.rows; i++) {
        if (this.get(i, column) < v) {
          v = this.get(i, column);
        }
      }

      return v;
    }

    minColumnIndex(column) {
      checkColumnIndex(this, column);
      checkNonEmpty(this);
      let v = this.get(0, column);
      let idx = [0, column];

      for (let i = 1; i < this.rows; i++) {
        if (this.get(i, column) < v) {
          v = this.get(i, column);
          idx[0] = i;
        }
      }

      return idx;
    }

    diag() {
      let min = Math.min(this.rows, this.columns);
      let diag = [];

      for (let i = 0; i < min; i++) {
        diag.push(this.get(i, i));
      }

      return diag;
    }

    norm(type = 'frobenius') {
      let result = 0;

      if (type === 'max') {
        return this.max();
      } else if (type === 'frobenius') {
        for (let i = 0; i < this.rows; i++) {
          for (let j = 0; j < this.columns; j++) {
            result = result + this.get(i, j) * this.get(i, j);
          }
        }

        return Math.sqrt(result);
      } else {
        throw new RangeError(`unknown norm type: ${type}`);
      }
    }

    cumulativeSum() {
      let sum = 0;

      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.columns; j++) {
          sum += this.get(i, j);
          this.set(i, j, sum);
        }
      }

      return this;
    }

    dot(vector2) {
      if (AbstractMatrix.isMatrix(vector2)) vector2 = vector2.to1DArray();
      let vector1 = this.to1DArray();

      if (vector1.length !== vector2.length) {
        throw new RangeError('vectors do not have the same size');
      }

      let dot = 0;

      for (let i = 0; i < vector1.length; i++) {
        dot += vector1[i] * vector2[i];
      }

      return dot;
    }

    mmul(other) {
      other = Matrix.checkMatrix(other);
      let m = this.rows;
      let n = this.columns;
      let p = other.columns;
      let result = new Matrix(m, p);
      let Bcolj = new Float64Array(n);

      for (let j = 0; j < p; j++) {
        for (let k = 0; k < n; k++) {
          Bcolj[k] = other.get(k, j);
        }

        for (let i = 0; i < m; i++) {
          let s = 0;

          for (let k = 0; k < n; k++) {
            s += this.get(i, k) * Bcolj[k];
          }

          result.set(i, j, s);
        }
      }

      return result;
    }

    strassen2x2(other) {
      other = Matrix.checkMatrix(other);
      let result = new Matrix(2, 2);
      const a11 = this.get(0, 0);
      const b11 = other.get(0, 0);
      const a12 = this.get(0, 1);
      const b12 = other.get(0, 1);
      const a21 = this.get(1, 0);
      const b21 = other.get(1, 0);
      const a22 = this.get(1, 1);
      const b22 = other.get(1, 1); // Compute intermediate values.

      const m1 = (a11 + a22) * (b11 + b22);
      const m2 = (a21 + a22) * b11;
      const m3 = a11 * (b12 - b22);
      const m4 = a22 * (b21 - b11);
      const m5 = (a11 + a12) * b22;
      const m6 = (a21 - a11) * (b11 + b12);
      const m7 = (a12 - a22) * (b21 + b22); // Combine intermediate values into the output.

      const c00 = m1 + m4 - m5 + m7;
      const c01 = m3 + m5;
      const c10 = m2 + m4;
      const c11 = m1 - m2 + m3 + m6;
      result.set(0, 0, c00);
      result.set(0, 1, c01);
      result.set(1, 0, c10);
      result.set(1, 1, c11);
      return result;
    }

    strassen3x3(other) {
      other = Matrix.checkMatrix(other);
      let result = new Matrix(3, 3);
      const a00 = this.get(0, 0);
      const a01 = this.get(0, 1);
      const a02 = this.get(0, 2);
      const a10 = this.get(1, 0);
      const a11 = this.get(1, 1);
      const a12 = this.get(1, 2);
      const a20 = this.get(2, 0);
      const a21 = this.get(2, 1);
      const a22 = this.get(2, 2);
      const b00 = other.get(0, 0);
      const b01 = other.get(0, 1);
      const b02 = other.get(0, 2);
      const b10 = other.get(1, 0);
      const b11 = other.get(1, 1);
      const b12 = other.get(1, 2);
      const b20 = other.get(2, 0);
      const b21 = other.get(2, 1);
      const b22 = other.get(2, 2);
      const m1 = (a00 + a01 + a02 - a10 - a11 - a21 - a22) * b11;
      const m2 = (a00 - a10) * (-b01 + b11);
      const m3 = a11 * (-b00 + b01 + b10 - b11 - b12 - b20 + b22);
      const m4 = (-a00 + a10 + a11) * (b00 - b01 + b11);
      const m5 = (a10 + a11) * (-b00 + b01);
      const m6 = a00 * b00;
      const m7 = (-a00 + a20 + a21) * (b00 - b02 + b12);
      const m8 = (-a00 + a20) * (b02 - b12);
      const m9 = (a20 + a21) * (-b00 + b02);
      const m10 = (a00 + a01 + a02 - a11 - a12 - a20 - a21) * b12;
      const m11 = a21 * (-b00 + b02 + b10 - b11 - b12 - b20 + b21);
      const m12 = (-a02 + a21 + a22) * (b11 + b20 - b21);
      const m13 = (a02 - a22) * (b11 - b21);
      const m14 = a02 * b20;
      const m15 = (a21 + a22) * (-b20 + b21);
      const m16 = (-a02 + a11 + a12) * (b12 + b20 - b22);
      const m17 = (a02 - a12) * (b12 - b22);
      const m18 = (a11 + a12) * (-b20 + b22);
      const m19 = a01 * b10;
      const m20 = a12 * b21;
      const m21 = a10 * b02;
      const m22 = a20 * b01;
      const m23 = a22 * b22;
      const c00 = m6 + m14 + m19;
      const c01 = m1 + m4 + m5 + m6 + m12 + m14 + m15;
      const c02 = m6 + m7 + m9 + m10 + m14 + m16 + m18;
      const c10 = m2 + m3 + m4 + m6 + m14 + m16 + m17;
      const c11 = m2 + m4 + m5 + m6 + m20;
      const c12 = m14 + m16 + m17 + m18 + m21;
      const c20 = m6 + m7 + m8 + m11 + m12 + m13 + m14;
      const c21 = m12 + m13 + m14 + m15 + m22;
      const c22 = m6 + m7 + m8 + m9 + m23;
      result.set(0, 0, c00);
      result.set(0, 1, c01);
      result.set(0, 2, c02);
      result.set(1, 0, c10);
      result.set(1, 1, c11);
      result.set(1, 2, c12);
      result.set(2, 0, c20);
      result.set(2, 1, c21);
      result.set(2, 2, c22);
      return result;
    }

    mmulStrassen(y) {
      y = Matrix.checkMatrix(y);
      let x = this.clone();
      let r1 = x.rows;
      let c1 = x.columns;
      let r2 = y.rows;
      let c2 = y.columns;

      if (c1 !== r2) {
        // eslint-disable-next-line no-console
        console.warn(`Multiplying ${r1} x ${c1} and ${r2} x ${c2} matrix: dimensions do not match.`);
      } // Put a matrix into the top left of a matrix of zeros.
      // `rows` and `cols` are the dimensions of the output matrix.


      function embed(mat, rows, cols) {
        let r = mat.rows;
        let c = mat.columns;

        if (r === rows && c === cols) {
          return mat;
        } else {
          let resultat = AbstractMatrix.zeros(rows, cols);
          resultat = resultat.setSubMatrix(mat, 0, 0);
          return resultat;
        }
      } // Make sure both matrices are the same size.
      // This is exclusively for simplicity:
      // this algorithm can be implemented with matrices of different sizes.


      let r = Math.max(r1, r2);
      let c = Math.max(c1, c2);
      x = embed(x, r, c);
      y = embed(y, r, c); // Our recursive multiplication function.

      function blockMult(a, b, rows, cols) {
        // For small matrices, resort to naive multiplication.
        if (rows <= 512 || cols <= 512) {
          return a.mmul(b); // a is equivalent to this
        } // Apply dynamic padding.


        if (rows % 2 === 1 && cols % 2 === 1) {
          a = embed(a, rows + 1, cols + 1);
          b = embed(b, rows + 1, cols + 1);
        } else if (rows % 2 === 1) {
          a = embed(a, rows + 1, cols);
          b = embed(b, rows + 1, cols);
        } else if (cols % 2 === 1) {
          a = embed(a, rows, cols + 1);
          b = embed(b, rows, cols + 1);
        }

        let halfRows = parseInt(a.rows / 2, 10);
        let halfCols = parseInt(a.columns / 2, 10); // Subdivide input matrices.

        let a11 = a.subMatrix(0, halfRows - 1, 0, halfCols - 1);
        let b11 = b.subMatrix(0, halfRows - 1, 0, halfCols - 1);
        let a12 = a.subMatrix(0, halfRows - 1, halfCols, a.columns - 1);
        let b12 = b.subMatrix(0, halfRows - 1, halfCols, b.columns - 1);
        let a21 = a.subMatrix(halfRows, a.rows - 1, 0, halfCols - 1);
        let b21 = b.subMatrix(halfRows, b.rows - 1, 0, halfCols - 1);
        let a22 = a.subMatrix(halfRows, a.rows - 1, halfCols, a.columns - 1);
        let b22 = b.subMatrix(halfRows, b.rows - 1, halfCols, b.columns - 1); // Compute intermediate values.

        let m1 = blockMult(AbstractMatrix.add(a11, a22), AbstractMatrix.add(b11, b22), halfRows, halfCols);
        let m2 = blockMult(AbstractMatrix.add(a21, a22), b11, halfRows, halfCols);
        let m3 = blockMult(a11, AbstractMatrix.sub(b12, b22), halfRows, halfCols);
        let m4 = blockMult(a22, AbstractMatrix.sub(b21, b11), halfRows, halfCols);
        let m5 = blockMult(AbstractMatrix.add(a11, a12), b22, halfRows, halfCols);
        let m6 = blockMult(AbstractMatrix.sub(a21, a11), AbstractMatrix.add(b11, b12), halfRows, halfCols);
        let m7 = blockMult(AbstractMatrix.sub(a12, a22), AbstractMatrix.add(b21, b22), halfRows, halfCols); // Combine intermediate values into the output.

        let c11 = AbstractMatrix.add(m1, m4);
        c11.sub(m5);
        c11.add(m7);
        let c12 = AbstractMatrix.add(m3, m5);
        let c21 = AbstractMatrix.add(m2, m4);
        let c22 = AbstractMatrix.sub(m1, m2);
        c22.add(m3);
        c22.add(m6); // Crop output to the desired size (undo dynamic padding).

        let resultat = AbstractMatrix.zeros(2 * c11.rows, 2 * c11.columns);
        resultat = resultat.setSubMatrix(c11, 0, 0);
        resultat = resultat.setSubMatrix(c12, c11.rows, 0);
        resultat = resultat.setSubMatrix(c21, 0, c11.columns);
        resultat = resultat.setSubMatrix(c22, c11.rows, c11.columns);
        return resultat.subMatrix(0, rows - 1, 0, cols - 1);
      }

      return blockMult(x, y, r, c);
    }

    scaleRows(options = {}) {
      if (typeof options !== 'object') {
        throw new TypeError('options must be an object');
      }

      const {
        min = 0,
        max = 1
      } = options;
      if (!Number.isFinite(min)) throw new TypeError('min must be a number');
      if (!Number.isFinite(max)) throw new TypeError('max must be a number');
      if (min >= max) throw new RangeError('min must be smaller than max');
      let newMatrix = new Matrix(this.rows, this.columns);

      for (let i = 0; i < this.rows; i++) {
        const row = this.getRow(i);

        if (row.length > 0) {
          rescale(row, {
            min,
            max,
            output: row
          });
        }

        newMatrix.setRow(i, row);
      }

      return newMatrix;
    }

    scaleColumns(options = {}) {
      if (typeof options !== 'object') {
        throw new TypeError('options must be an object');
      }

      const {
        min = 0,
        max = 1
      } = options;
      if (!Number.isFinite(min)) throw new TypeError('min must be a number');
      if (!Number.isFinite(max)) throw new TypeError('max must be a number');
      if (min >= max) throw new RangeError('min must be smaller than max');
      let newMatrix = new Matrix(this.rows, this.columns);

      for (let i = 0; i < this.columns; i++) {
        const column = this.getColumn(i);

        if (column.length) {
          rescale(column, {
            min: min,
            max: max,
            output: column
          });
        }

        newMatrix.setColumn(i, column);
      }

      return newMatrix;
    }

    flipRows() {
      const middle = Math.ceil(this.columns / 2);

      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < middle; j++) {
          let first = this.get(i, j);
          let last = this.get(i, this.columns - 1 - j);
          this.set(i, j, last);
          this.set(i, this.columns - 1 - j, first);
        }
      }

      return this;
    }

    flipColumns() {
      const middle = Math.ceil(this.rows / 2);

      for (let j = 0; j < this.columns; j++) {
        for (let i = 0; i < middle; i++) {
          let first = this.get(i, j);
          let last = this.get(this.rows - 1 - i, j);
          this.set(i, j, last);
          this.set(this.rows - 1 - i, j, first);
        }
      }

      return this;
    }

    kroneckerProduct(other) {
      other = Matrix.checkMatrix(other);
      let m = this.rows;
      let n = this.columns;
      let p = other.rows;
      let q = other.columns;
      let result = new Matrix(m * p, n * q);

      for (let i = 0; i < m; i++) {
        for (let j = 0; j < n; j++) {
          for (let k = 0; k < p; k++) {
            for (let l = 0; l < q; l++) {
              result.set(p * i + k, q * j + l, this.get(i, j) * other.get(k, l));
            }
          }
        }
      }

      return result;
    }

    kroneckerSum(other) {
      other = Matrix.checkMatrix(other);

      if (!this.isSquare() || !other.isSquare()) {
        throw new Error('Kronecker Sum needs two Square Matrices');
      }

      let m = this.rows;
      let n = other.rows;
      let AxI = this.kroneckerProduct(Matrix.eye(n, n));
      let IxB = Matrix.eye(m, m).kroneckerProduct(other);
      return AxI.add(IxB);
    }

    transpose() {
      let result = new Matrix(this.columns, this.rows);

      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j < this.columns; j++) {
          result.set(j, i, this.get(i, j));
        }
      }

      return result;
    }

    sortRows(compareFunction = compareNumbers) {
      for (let i = 0; i < this.rows; i++) {
        this.setRow(i, this.getRow(i).sort(compareFunction));
      }

      return this;
    }

    sortColumns(compareFunction = compareNumbers) {
      for (let i = 0; i < this.columns; i++) {
        this.setColumn(i, this.getColumn(i).sort(compareFunction));
      }

      return this;
    }

    subMatrix(startRow, endRow, startColumn, endColumn) {
      checkRange(this, startRow, endRow, startColumn, endColumn);
      let newMatrix = new Matrix(endRow - startRow + 1, endColumn - startColumn + 1);

      for (let i = startRow; i <= endRow; i++) {
        for (let j = startColumn; j <= endColumn; j++) {
          newMatrix.set(i - startRow, j - startColumn, this.get(i, j));
        }
      }

      return newMatrix;
    }

    subMatrixRow(indices, startColumn, endColumn) {
      if (startColumn === undefined) startColumn = 0;
      if (endColumn === undefined) endColumn = this.columns - 1;

      if (startColumn > endColumn || startColumn < 0 || startColumn >= this.columns || endColumn < 0 || endColumn >= this.columns) {
        throw new RangeError('Argument out of range');
      }

      let newMatrix = new Matrix(indices.length, endColumn - startColumn + 1);

      for (let i = 0; i < indices.length; i++) {
        for (let j = startColumn; j <= endColumn; j++) {
          if (indices[i] < 0 || indices[i] >= this.rows) {
            throw new RangeError(`Row index out of range: ${indices[i]}`);
          }

          newMatrix.set(i, j - startColumn, this.get(indices[i], j));
        }
      }

      return newMatrix;
    }

    subMatrixColumn(indices, startRow, endRow) {
      if (startRow === undefined) startRow = 0;
      if (endRow === undefined) endRow = this.rows - 1;

      if (startRow > endRow || startRow < 0 || startRow >= this.rows || endRow < 0 || endRow >= this.rows) {
        throw new RangeError('Argument out of range');
      }

      let newMatrix = new Matrix(endRow - startRow + 1, indices.length);

      for (let i = 0; i < indices.length; i++) {
        for (let j = startRow; j <= endRow; j++) {
          if (indices[i] < 0 || indices[i] >= this.columns) {
            throw new RangeError(`Column index out of range: ${indices[i]}`);
          }

          newMatrix.set(j - startRow, i, this.get(j, indices[i]));
        }
      }

      return newMatrix;
    }

    setSubMatrix(matrix, startRow, startColumn) {
      matrix = Matrix.checkMatrix(matrix);

      if (matrix.isEmpty()) {
        return this;
      }

      let endRow = startRow + matrix.rows - 1;
      let endColumn = startColumn + matrix.columns - 1;
      checkRange(this, startRow, endRow, startColumn, endColumn);

      for (let i = 0; i < matrix.rows; i++) {
        for (let j = 0; j < matrix.columns; j++) {
          this.set(startRow + i, startColumn + j, matrix.get(i, j));
        }
      }

      return this;
    }

    selection(rowIndices, columnIndices) {
      let indices = checkIndices(this, rowIndices, columnIndices);
      let newMatrix = new Matrix(rowIndices.length, columnIndices.length);

      for (let i = 0; i < indices.row.length; i++) {
        let rowIndex = indices.row[i];

        for (let j = 0; j < indices.column.length; j++) {
          let columnIndex = indices.column[j];
          newMatrix.set(i, j, this.get(rowIndex, columnIndex));
        }
      }

      return newMatrix;
    }

    trace() {
      let min = Math.min(this.rows, this.columns);
      let trace = 0;

      for (let i = 0; i < min; i++) {
        trace += this.get(i, i);
      }

      return trace;
    }

    clone() {
      let newMatrix = new Matrix(this.rows, this.columns);

      for (let row = 0; row < this.rows; row++) {
        for (let column = 0; column < this.columns; column++) {
          newMatrix.set(row, column, this.get(row, column));
        }
      }

      return newMatrix;
    }

    sum(by) {
      switch (by) {
        case 'row':
          return sumByRow(this);

        case 'column':
          return sumByColumn(this);

        case undefined:
          return sumAll(this);

        default:
          throw new Error(`invalid option: ${by}`);
      }
    }

    product(by) {
      switch (by) {
        case 'row':
          return productByRow(this);

        case 'column':
          return productByColumn(this);

        case undefined:
          return productAll(this);

        default:
          throw new Error(`invalid option: ${by}`);
      }
    }

    mean(by) {
      const sum = this.sum(by);

      switch (by) {
        case 'row':
          {
            for (let i = 0; i < this.rows; i++) {
              sum[i] /= this.columns;
            }

            return sum;
          }

        case 'column':
          {
            for (let i = 0; i < this.columns; i++) {
              sum[i] /= this.rows;
            }

            return sum;
          }

        case undefined:
          return sum / this.size;

        default:
          throw new Error(`invalid option: ${by}`);
      }
    }

    variance(by, options = {}) {
      if (typeof by === 'object') {
        options = by;
        by = undefined;
      }

      if (typeof options !== 'object') {
        throw new TypeError('options must be an object');
      }

      const {
        unbiased = true,
        mean = this.mean(by)
      } = options;

      if (typeof unbiased !== 'boolean') {
        throw new TypeError('unbiased must be a boolean');
      }

      switch (by) {
        case 'row':
          {
            if (!Array.isArray(mean)) {
              throw new TypeError('mean must be an array');
            }

            return varianceByRow(this, unbiased, mean);
          }

        case 'column':
          {
            if (!Array.isArray(mean)) {
              throw new TypeError('mean must be an array');
            }

            return varianceByColumn(this, unbiased, mean);
          }

        case undefined:
          {
            if (typeof mean !== 'number') {
              throw new TypeError('mean must be a number');
            }

            return varianceAll(this, unbiased, mean);
          }

        default:
          throw new Error(`invalid option: ${by}`);
      }
    }

    standardDeviation(by, options) {
      if (typeof by === 'object') {
        options = by;
        by = undefined;
      }

      const variance = this.variance(by, options);

      if (by === undefined) {
        return Math.sqrt(variance);
      } else {
        for (let i = 0; i < variance.length; i++) {
          variance[i] = Math.sqrt(variance[i]);
        }

        return variance;
      }
    }

    center(by, options = {}) {
      if (typeof by === 'object') {
        options = by;
        by = undefined;
      }

      if (typeof options !== 'object') {
        throw new TypeError('options must be an object');
      }

      const {
        center = this.mean(by)
      } = options;

      switch (by) {
        case 'row':
          {
            if (!Array.isArray(center)) {
              throw new TypeError('center must be an array');
            }

            centerByRow(this, center);
            return this;
          }

        case 'column':
          {
            if (!Array.isArray(center)) {
              throw new TypeError('center must be an array');
            }

            centerByColumn(this, center);
            return this;
          }

        case undefined:
          {
            if (typeof center !== 'number') {
              throw new TypeError('center must be a number');
            }

            centerAll(this, center);
            return this;
          }

        default:
          throw new Error(`invalid option: ${by}`);
      }
    }

    scale(by, options = {}) {
      if (typeof by === 'object') {
        options = by;
        by = undefined;
      }

      if (typeof options !== 'object') {
        throw new TypeError('options must be an object');
      }

      let scale = options.scale;

      switch (by) {
        case 'row':
          {
            if (scale === undefined) {
              scale = getScaleByRow(this);
            } else if (!Array.isArray(scale)) {
              throw new TypeError('scale must be an array');
            }

            scaleByRow(this, scale);
            return this;
          }

        case 'column':
          {
            if (scale === undefined) {
              scale = getScaleByColumn(this);
            } else if (!Array.isArray(scale)) {
              throw new TypeError('scale must be an array');
            }

            scaleByColumn(this, scale);
            return this;
          }

        case undefined:
          {
            if (scale === undefined) {
              scale = getScaleAll(this);
            } else if (typeof scale !== 'number') {
              throw new TypeError('scale must be a number');
            }

            scaleAll(this, scale);
            return this;
          }

        default:
          throw new Error(`invalid option: ${by}`);
      }
    }

    toString(options) {
      return inspectMatrixWithOptions(this, options);
    }

  }
  AbstractMatrix.prototype.klass = 'Matrix';

  if (typeof Symbol !== 'undefined') {
    AbstractMatrix.prototype[Symbol.for('nodejs.util.inspect.custom')] = inspectMatrix;
  }

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


  AbstractMatrix.random = AbstractMatrix.rand;
  AbstractMatrix.randomInt = AbstractMatrix.randInt;
  AbstractMatrix.diagonal = AbstractMatrix.diag;
  AbstractMatrix.prototype.diagonal = AbstractMatrix.prototype.diag;
  AbstractMatrix.identity = AbstractMatrix.eye;
  AbstractMatrix.prototype.negate = AbstractMatrix.prototype.neg;
  AbstractMatrix.prototype.tensorProduct = AbstractMatrix.prototype.kroneckerProduct;
  class Matrix extends AbstractMatrix {
    constructor(nRows, nColumns) {
      super();

      if (Matrix.isMatrix(nRows)) {
        // eslint-disable-next-line no-constructor-return
        return nRows.clone();
      } else if (Number.isInteger(nRows) && nRows >= 0) {
        // Create an empty matrix
        this.data = [];

        if (Number.isInteger(nColumns) && nColumns >= 0) {
          for (let i = 0; i < nRows; i++) {
            this.data.push(new Float64Array(nColumns));
          }
        } else {
          throw new TypeError('nColumns must be a positive integer');
        }
      } else if (Array.isArray(nRows)) {
        // Copy the values from the 2D array
        const arrayData = nRows;
        nRows = arrayData.length;
        nColumns = nRows ? arrayData[0].length : 0;

        if (typeof nColumns !== 'number') {
          throw new TypeError('Data must be a 2D array with at least one element');
        }

        this.data = [];

        for (let i = 0; i < nRows; i++) {
          if (arrayData[i].length !== nColumns) {
            throw new RangeError('Inconsistent array dimensions');
          }

          this.data.push(Float64Array.from(arrayData[i]));
        }
      } else {
        throw new TypeError('First argument must be a positive number or an array');
      }

      this.rows = nRows;
      this.columns = nColumns;
    }

    set(rowIndex, columnIndex, value) {
      this.data[rowIndex][columnIndex] = value;
      return this;
    }

    get(rowIndex, columnIndex) {
      return this.data[rowIndex][columnIndex];
    }

    removeRow(index) {
      checkRowIndex(this, index);
      this.data.splice(index, 1);
      this.rows -= 1;
      return this;
    }

    addRow(index, array) {
      if (array === undefined) {
        array = index;
        index = this.rows;
      }

      checkRowIndex(this, index, true);
      array = Float64Array.from(checkRowVector(this, array));
      this.data.splice(index, 0, array);
      this.rows += 1;
      return this;
    }

    removeColumn(index) {
      checkColumnIndex(this, index);

      for (let i = 0; i < this.rows; i++) {
        const newRow = new Float64Array(this.columns - 1);

        for (let j = 0; j < index; j++) {
          newRow[j] = this.data[i][j];
        }

        for (let j = index + 1; j < this.columns; j++) {
          newRow[j - 1] = this.data[i][j];
        }

        this.data[i] = newRow;
      }

      this.columns -= 1;
      return this;
    }

    addColumn(index, array) {
      if (typeof array === 'undefined') {
        array = index;
        index = this.columns;
      }

      checkColumnIndex(this, index, true);
      array = checkColumnVector(this, array);

      for (let i = 0; i < this.rows; i++) {
        const newRow = new Float64Array(this.columns + 1);
        let j = 0;

        for (; j < index; j++) {
          newRow[j] = this.data[i][j];
        }

        newRow[j++] = array[i];

        for (; j < this.columns + 1; j++) {
          newRow[j] = this.data[i][j - 1];
        }

        this.data[i] = newRow;
      }

      this.columns += 1;
      return this;
    }

  }
  installMathOperations(AbstractMatrix, Matrix);

  class BaseView extends AbstractMatrix {
    constructor(matrix, rows, columns) {
      super();
      this.matrix = matrix;
      this.rows = rows;
      this.columns = columns;
    }

  }

  class MatrixTransposeView extends BaseView {
    constructor(matrix) {
      super(matrix, matrix.columns, matrix.rows);
    }

    set(rowIndex, columnIndex, value) {
      this.matrix.set(columnIndex, rowIndex, value);
      return this;
    }

    get(rowIndex, columnIndex) {
      return this.matrix.get(columnIndex, rowIndex);
    }

  }

  class WrapperMatrix2D extends AbstractMatrix {
    constructor(data) {
      super();
      this.data = data;
      this.rows = data.length;
      this.columns = data[0].length;
    }

    set(rowIndex, columnIndex, value) {
      this.data[rowIndex][columnIndex] = value;
      return this;
    }

    get(rowIndex, columnIndex) {
      return this.data[rowIndex][columnIndex];
    }

  }

  class LuDecomposition {
    constructor(matrix) {
      matrix = WrapperMatrix2D.checkMatrix(matrix);
      let lu = matrix.clone();
      let rows = lu.rows;
      let columns = lu.columns;
      let pivotVector = new Float64Array(rows);
      let pivotSign = 1;
      let i, j, k, p, s, t, v;
      let LUcolj, kmax;

      for (i = 0; i < rows; i++) {
        pivotVector[i] = i;
      }

      LUcolj = new Float64Array(rows);

      for (j = 0; j < columns; j++) {
        for (i = 0; i < rows; i++) {
          LUcolj[i] = lu.get(i, j);
        }

        for (i = 0; i < rows; i++) {
          kmax = Math.min(i, j);
          s = 0;

          for (k = 0; k < kmax; k++) {
            s += lu.get(i, k) * LUcolj[k];
          }

          LUcolj[i] -= s;
          lu.set(i, j, LUcolj[i]);
        }

        p = j;

        for (i = j + 1; i < rows; i++) {
          if (Math.abs(LUcolj[i]) > Math.abs(LUcolj[p])) {
            p = i;
          }
        }

        if (p !== j) {
          for (k = 0; k < columns; k++) {
            t = lu.get(p, k);
            lu.set(p, k, lu.get(j, k));
            lu.set(j, k, t);
          }

          v = pivotVector[p];
          pivotVector[p] = pivotVector[j];
          pivotVector[j] = v;
          pivotSign = -pivotSign;
        }

        if (j < rows && lu.get(j, j) !== 0) {
          for (i = j + 1; i < rows; i++) {
            lu.set(i, j, lu.get(i, j) / lu.get(j, j));
          }
        }
      }

      this.LU = lu;
      this.pivotVector = pivotVector;
      this.pivotSign = pivotSign;
    }

    isSingular() {
      let data = this.LU;
      let col = data.columns;

      for (let j = 0; j < col; j++) {
        if (data.get(j, j) === 0) {
          return true;
        }
      }

      return false;
    }

    solve(value) {
      value = Matrix.checkMatrix(value);
      let lu = this.LU;
      let rows = lu.rows;

      if (rows !== value.rows) {
        throw new Error('Invalid matrix dimensions');
      }

      if (this.isSingular()) {
        throw new Error('LU matrix is singular');
      }

      let count = value.columns;
      let X = value.subMatrixRow(this.pivotVector, 0, count - 1);
      let columns = lu.columns;
      let i, j, k;

      for (k = 0; k < columns; k++) {
        for (i = k + 1; i < columns; i++) {
          for (j = 0; j < count; j++) {
            X.set(i, j, X.get(i, j) - X.get(k, j) * lu.get(i, k));
          }
        }
      }

      for (k = columns - 1; k >= 0; k--) {
        for (j = 0; j < count; j++) {
          X.set(k, j, X.get(k, j) / lu.get(k, k));
        }

        for (i = 0; i < k; i++) {
          for (j = 0; j < count; j++) {
            X.set(i, j, X.get(i, j) - X.get(k, j) * lu.get(i, k));
          }
        }
      }

      return X;
    }

    get determinant() {
      let data = this.LU;

      if (!data.isSquare()) {
        throw new Error('Matrix must be square');
      }

      let determinant = this.pivotSign;
      let col = data.columns;

      for (let j = 0; j < col; j++) {
        determinant *= data.get(j, j);
      }

      return determinant;
    }

    get lowerTriangularMatrix() {
      let data = this.LU;
      let rows = data.rows;
      let columns = data.columns;
      let X = new Matrix(rows, columns);

      for (let i = 0; i < rows; i++) {
        for (let j = 0; j < columns; j++) {
          if (i > j) {
            X.set(i, j, data.get(i, j));
          } else if (i === j) {
            X.set(i, j, 1);
          } else {
            X.set(i, j, 0);
          }
        }
      }

      return X;
    }

    get upperTriangularMatrix() {
      let data = this.LU;
      let rows = data.rows;
      let columns = data.columns;
      let X = new Matrix(rows, columns);

      for (let i = 0; i < rows; i++) {
        for (let j = 0; j < columns; j++) {
          if (i <= j) {
            X.set(i, j, data.get(i, j));
          } else {
            X.set(i, j, 0);
          }
        }
      }

      return X;
    }

    get pivotPermutationVector() {
      return Array.from(this.pivotVector);
    }

  }

  function hypotenuse(a, b) {
    let r = 0;

    if (Math.abs(a) > Math.abs(b)) {
      r = b / a;
      return Math.abs(a) * Math.sqrt(1 + r * r);
    }

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

    return 0;
  }

  class QrDecomposition {
    constructor(value) {
      value = WrapperMatrix2D.checkMatrix(value);
      let qr = value.clone();
      let m = value.rows;
      let n = value.columns;
      let rdiag = new Float64Array(n);
      let i, j, k, s;

      for (k = 0; k < n; k++) {
        let nrm = 0;

        for (i = k; i < m; i++) {
          nrm = hypotenuse(nrm, qr.get(i, k));
        }

        if (nrm !== 0) {
          if (qr.get(k, k) < 0) {
            nrm = -nrm;
          }

          for (i = k; i < m; i++) {
            qr.set(i, k, qr.get(i, k) / nrm);
          }

          qr.set(k, k, qr.get(k, k) + 1);

          for (j = k + 1; j < n; j++) {
            s = 0;

            for (i = k; i < m; i++) {
              s += qr.get(i, k) * qr.get(i, j);
            }

            s = -s / qr.get(k, k);

            for (i = k; i < m; i++) {
              qr.set(i, j, qr.get(i, j) + s * qr.get(i, k));
            }
          }
        }

        rdiag[k] = -nrm;
      }

      this.QR = qr;
      this.Rdiag = rdiag;
    }

    solve(value) {
      value = Matrix.checkMatrix(value);
      let qr = this.QR;
      let m = qr.rows;

      if (value.rows !== m) {
        throw new Error('Matrix row dimensions must agree');
      }

      if (!this.isFullRank()) {
        throw new Error('Matrix is rank deficient');
      }

      let count = value.columns;
      let X = value.clone();
      let n = qr.columns;
      let i, j, k, s;

      for (k = 0; k < n; k++) {
        for (j = 0; j < count; j++) {
          s = 0;

          for (i = k; i < m; i++) {
            s += qr.get(i, k) * X.get(i, j);
          }

          s = -s / qr.get(k, k);

          for (i = k; i < m; i++) {
            X.set(i, j, X.get(i, j) + s * qr.get(i, k));
          }
        }
      }

      for (k = n - 1; k >= 0; k--) {
        for (j = 0; j < count; j++) {
          X.set(k, j, X.get(k, j) / this.Rdiag[k]);
        }

        for (i = 0; i < k; i++) {
          for (j = 0; j < count; j++) {
            X.set(i, j, X.get(i, j) - X.get(k, j) * qr.get(i, k));
          }
        }
      }

      return X.subMatrix(0, n - 1, 0, count - 1);
    }

    isFullRank() {
      let columns = this.QR.columns;

      for (let i = 0; i < columns; i++) {
        if (this.Rdiag[i] === 0) {
          return false;
        }
      }

      return true;
    }

    get upperTriangularMatrix() {
      let qr = this.QR;
      let n = qr.columns;
      let X = new Matrix(n, n);
      let i, j;

      for (i = 0; i < n; i++) {
        for (j = 0; j < n; j++) {
          if (i < j) {
            X.set(i, j, qr.get(i, j));
          } else if (i === j) {
            X.set(i, j, this.Rdiag[i]);
          } else {
            X.set(i, j, 0);
          }
        }
      }

      return X;
    }

    get orthogonalMatrix() {
      let qr = this.QR;
      let rows = qr.rows;
      let columns = qr.columns;
      let X = new Matrix(rows, columns);
      let i, j, k, s;

      for (k = columns - 1; k >= 0; k--) {
        for (i = 0; i < rows; i++) {
          X.set(i, k, 0);
        }

        X.set(k, k, 1);

        for (j = k; j < columns; j++) {
          if (qr.get(k, k) !== 0) {
            s = 0;

            for (i = k; i < rows; i++) {
              s += qr.get(i, k) * X.get(i, j);
            }

            s = -s / qr.get(k, k);

            for (i = k; i < rows; i++) {
              X.set(i, j, X.get(i, j) + s * qr.get(i, k));
            }
          }
        }
      }

      return X;
    }

  }

  class SingularValueDecomposition {
    constructor(value, options = {}) {
      value = WrapperMatrix2D.checkMatrix(value);

      if (value.isEmpty()) {
        throw new Error('Matrix must be non-empty');
      }

      let m = value.rows;
      let n = value.columns;
      const {
        computeLeftSingularVectors = true,
        computeRightSingularVectors = true,
        autoTranspose = false
      } = options;
      let wantu = Boolean(computeLeftSingularVectors);
      let wantv = Boolean(computeRightSingularVectors);
      let swapped = false;
      let a;

      if (m < n) {
        if (!autoTranspose) {
          a = value.clone(); // eslint-disable-next-line no-console

          console.warn('Computing SVD on a matrix with more columns than rows. Consider enabling autoTranspose');
        } else {
          a = value.transpose();
          m = a.rows;
          n = a.columns;
          swapped = true;
          let aux = wantu;
          wantu = wantv;
          wantv = aux;
        }
      } else {
        a = value.clone();
      }

      let nu = Math.min(m, n);
      let ni = Math.min(m + 1, n);
      let s = new Float64Array(ni);
      let U = new Matrix(m, nu);
      let V = new Matrix(n, n);
      let e = new Float64Array(n);
      let work = new Float64Array(m);
      let si = new Float64Array(ni);

      for (let i = 0; i < ni; i++) si[i] = i;

      let nct = Math.min(m - 1, n);
      let nrt = Math.max(0, Math.min(n - 2, m));
      let mrc = Math.max(nct, nrt);

      for (let k = 0; k < mrc; k++) {
        if (k < nct) {
          s[k] = 0;

          for (let i = k; i < m; i++) {
            s[k] = hypotenuse(s[k], a.get(i, k));
          }

          if (s[k] !== 0) {
            if (a.get(k, k) < 0) {
              s[k] = -s[k];
            }

            for (let i = k; i < m; i++) {
              a.set(i, k, a.get(i, k) / s[k]);
            }

            a.set(k, k, a.get(k, k) + 1);
          }

          s[k] = -s[k];
        }

        for (let j = k + 1; j < n; j++) {
          if (k < nct && s[k] !== 0) {
            let t = 0;

            for (let i = k; i < m; i++) {
              t += a.get(i, k) * a.get(i, j);
            }

            t = -t / a.get(k, k);

            for (let i = k; i < m; i++) {
              a.set(i, j, a.get(i, j) + t * a.get(i, k));
            }
          }

          e[j] = a.get(k, j);
        }

        if (wantu && k < nct) {
          for (let i = k; i < m; i++) {
            U.set(i, k, a.get(i, k));
          }
        }

        if (k < nrt) {
          e[k] = 0;

          for (let i = k + 1; i < n; i++) {
            e[k] = hypotenuse(e[k], e[i]);
          }

          if (e[k] !== 0) {
            if (e[k + 1] < 0) {
              e[k] = 0 - e[k];
            }

            for (let i = k + 1; i < n; i++) {
              e[i] /= e[k];
            }

            e[k + 1] += 1;
          }

          e[k] = -e[k];

          if (k + 1 < m && e[k] !== 0) {
            for (let i = k + 1; i < m; i++) {
              work[i] = 0;
            }

            for (let i = k + 1; i < m; i++) {
              for (let j = k + 1; j < n; j++) {
                work[i] += e[j] * a.get(i, j);
              }
            }

            for (let j = k + 1; j < n; j++) {
              let t = -e[j] / e[k + 1];

              for (let i = k + 1; i < m; i++) {
                a.set(i, j, a.get(i, j) + t * work[i]);
              }
            }
          }

          if (wantv) {
            for (let i = k + 1; i < n; i++) {
              V.set(i, k, e[i]);
            }
          }
        }
      }

      let p = Math.min(n, m + 1);

      if (nct < n) {
        s[nct] = a.get(nct, nct);
      }

      if (m < p) {
        s[p - 1] = 0;
      }

      if (nrt + 1 < p) {
        e[nrt] = a.get(nrt, p - 1);
      }

      e[p - 1] = 0;

      if (wantu) {
        for (let j = nct; j < nu; j++) {
          for (let i = 0; i < m; i++) {
            U.set(i, j, 0);
          }

          U.set(j, j, 1);
        }

        for (let k = nct - 1; k >= 0; k--) {
          if (s[k] !== 0) {
            for (let j = k + 1; j < nu; j++) {
              let t = 0;

              for (let i = k; i < m; i++) {
                t += U.get(i, k) * U.get(i, j);
              }

              t = -t / U.get(k, k);

              for (let i = k; i < m; i++) {
                U.set(i, j, U.get(i, j) + t * U.get(i, k));
              }
            }

            for (let i = k; i < m; i++) {
              U.set(i, k, -U.get(i, k));
            }

            U.set(k, k, 1 + U.get(k, k));

            for (let i = 0; i < k - 1; i++) {
              U.set(i, k, 0);
            }
          } else {
            for (let i = 0; i < m; i++) {
              U.set(i, k, 0);
            }

            U.set(k, k, 1);
          }
        }
      }

      if (wantv) {
        for (let k = n - 1; k >= 0; k--) {
          if (k < nrt && e[k] !== 0) {
            for (let j = k + 1; j < n; j++) {
              let t = 0;

              for (let i = k + 1; i < n; i++) {
                t += V.get(i, k) * V.get(i, j);
              }

              t = -t / V.get(k + 1, k);

              for (let i = k + 1; i < n; i++) {
                V.set(i, j, V.get(i, j) + t * V.get(i, k));
              }
            }
          }

          for (let i = 0; i < n; i++) {
            V.set(i, k, 0);
          }

          V.set(k, k, 1);
        }
      }

      let pp = p - 1;
      let eps = Number.EPSILON;

      while (p > 0) {
        let k, kase;

        for (k = p - 2; k >= -1; k--) {
          if (k === -1) {
            break;
          }

          const alpha = Number.MIN_VALUE + eps * Math.abs(s[k] + Math.abs(s[k + 1]));

          if (Math.abs(e[k]) <= alpha || Number.isNaN(e[k])) {
            e[k] = 0;
            break;
          }
        }

        if (k === p - 2) {
          kase = 4;
        } else {
          let ks;

          for (ks = p - 1; ks >= k; ks--) {
            if (ks === k) {
              break;
            }

            let t = (ks !== p ? Math.abs(e[ks]) : 0) + (ks !== k + 1 ? Math.abs(e[ks - 1]) : 0);

            if (Math.abs(s[ks]) <= eps * t) {
              s[ks] = 0;
              break;
            }
          }

          if (ks === k) {
            kase = 3;
          } else if (ks === p - 1) {
            kase = 1;
          } else {
            kase = 2;
            k = ks;
          }
        }

        k++;

        switch (kase) {
          case 1:
            {
              let f = e[p - 2];
              e[p - 2] = 0;

              for (let j = p - 2; j >= k; j--) {
                let t = hypotenuse(s[j], f);
                let cs = s[j] / t;
                let sn = f / t;
                s[j] = t;

                if (j !== k) {
                  f = -sn * e[j - 1];
                  e[j - 1] = cs * e[j - 1];
                }

                if (wantv) {
                  for (let i = 0; i < n; i++) {
                    t = cs * V.get(i, j) + sn * V.get(i, p - 1);
                    V.set(i, p - 1, -sn * V.get(i, j) + cs * V.get(i, p - 1));
                    V.set(i, j, t);
                  }
                }
              }

              break;
            }

          case 2:
            {
              let f = e[k - 1];
              e[k - 1] = 0;

              for (let j = k; j < p; j++) {
                let t = hypotenuse(s[j], f);
                let cs = s[j] / t;
                let sn = f / t;
                s[j] = t;
                f = -sn * e[j];
                e[j] = cs * e[j];

                if (wantu) {
                  for (let i = 0; i < m; i++) {
                    t = cs * U.get(i, j) + sn * U.get(i, k - 1);
                    U.set(i, k - 1, -sn * U.get(i, j) + cs * U.get(i, k - 1));
                    U.set(i, j, t);
                  }
                }
              }

              break;
            }

          case 3:
            {
              const scale = Math.max(Math.abs(s[p - 1]), Math.abs(s[p - 2]), Math.abs(e[p - 2]), Math.abs(s[k]), Math.abs(e[k]));
              const sp = s[p - 1] / scale;
              const spm1 = s[p - 2] / scale;
              const epm1 = e[p - 2] / scale;
              const sk = s[k] / scale;
              const ek = e[k] / scale;
              const b = ((spm1 + sp) * (spm1 - sp) + epm1 * epm1) / 2;
              const c = sp * epm1 * (sp * epm1);
              let shift = 0;

              if (b !== 0 || c !== 0) {
                if (b < 0) {
                  shift = 0 - Math.sqrt(b * b + c);
                } else {
                  shift = Math.sqrt(b * b + c);
                }

                shift = c / (b + shift);
              }

              let f = (sk + sp) * (sk - sp) + shift;
              let g = sk * ek;

              for (let j = k; j < p - 1; j++) {
                let t = hypotenuse(f, g);
                if (t === 0) t = Number.MIN_VALUE;
                let cs = f / t;
                let sn = g / t;

                if (j !== k) {
                  e[j - 1] = t;
                }

                f = cs * s[j] + sn * e[j];
                e[j] = cs * e[j] - sn * s[j];
                g = sn * s[j + 1];
                s[j + 1] = cs * s[j + 1];

                if (wantv) {
                  for (let i = 0; i < n; i++) {
                    t = cs * V.get(i, j) + sn * V.get(i, j + 1);
                    V.set(i, j + 1, -sn * V.get(i, j) + cs * V.get(i, j + 1));
                    V.set(i, j, t);
                  }
                }

                t = hypotenuse(f, g);
                if (t === 0) t = Number.MIN_VALUE;
                cs = f / t;
                sn = g / t;
                s[j] = t;
                f = cs * e[j] + sn * s[j + 1];
                s[j + 1] = -sn * e[j] + cs * s[j + 1];
                g = sn * e[j + 1];
                e[j + 1] = cs * e[j + 1];

                if (wantu && j < m - 1) {
                  for (let i = 0; i < m; i++) {
                    t = cs * U.get(i, j) + sn * U.get(i, j + 1);
                    U.set(i, j + 1, -sn * U.get(i, j) + cs * U.get(i, j + 1));
                    U.set(i, j, t);
                  }
                }
              }

              e[p - 2] = f;
              break;
            }

          case 4:
            {
              if (s[k] <= 0) {
                s[k] = s[k] < 0 ? -s[k] : 0;

                if (wantv) {
                  for (let i = 0; i <= pp; i++) {
                    V.set(i, k, -V.get(i, k));
                  }
                }
              }

              while (k < pp) {
                if (s[k] >= s[k + 1]) {
                  break;
                }

                let t = s[k];
                s[k] = s[k + 1];
                s[k + 1] = t;

                if (wantv && k < n - 1) {
                  for (let i = 0; i < n; i++) {
                    t = V.get(i, k + 1);
                    V.set(i, k + 1, V.get(i, k));
                    V.set(i, k, t);
                  }
                }

                if (wantu && k < m - 1) {
                  for (let i = 0; i < m; i++) {
                    t = U.get(i, k + 1);
                    U.set(i, k + 1, U.get(i, k));
                    U.set(i, k, t);
                  }
                }

                k++;
              }
              p--;
              break;
            }
          // no default
        }
      }

      if (swapped) {
        let tmp = V;
        V = U;
        U = tmp;
      }

      this.m = m;
      this.n = n;
      this.s = s;
      this.U = U;
      this.V = V;
    }

    solve(value) {
      let Y = value;
      let e = this.threshold;
      let scols = this.s.length;
      let Ls = Matrix.zeros(scols, scols);

      for (let i = 0; i < scols; i++) {
        if (Math.abs(this.s[i]) <= e) {
          Ls.set(i, i, 0);
        } else {
          Ls.set(i, i, 1 / this.s[i]);
        }
      }

      let U = this.U;
      let V = this.rightSingularVectors;
      let VL = V.mmul(Ls);
      let vrows = V.rows;
      let urows = U.rows;
      let VLU = Matrix.zeros(vrows, urows);

      for (let i = 0; i < vrows; i++) {
        for (let j = 0; j < urows; j++) {
          let sum = 0;

          for (let k = 0; k < scols; k++) {
            sum += VL.get(i, k) * U.get(j, k);
          }

          VLU.set(i, j, sum);
        }
      }

      return VLU.mmul(Y);
    }

    solveForDiagonal(value) {
      return this.solve(Matrix.diag(value));
    }

    inverse() {
      let V = this.V;
      let e = this.threshold;
      let vrows = V.rows;
      let vcols = V.columns;
      let X = new Matrix(vrows, this.s.length);

      for (let i = 0; i < vrows; i++) {
        for (let j = 0; j < vcols; j++) {
          if (Math.abs(this.s[j]) > e) {
            X.set(i, j, V.get(i, j) / this.s[j]);
          }
        }
      }

      let U = this.U;
      let urows = U.rows;
      let ucols = U.columns;
      let Y = new Matrix(vrows, urows);

      for (let i = 0; i < vrows; i++) {
        for (let j = 0; j < urows; j++) {
          let sum = 0;

          for (let k = 0; k < ucols; k++) {
            sum += X.get(i, k) * U.get(j, k);
          }

          Y.set(i, j, sum);
        }
      }

      return Y;
    }

    get condition() {
      return this.s[0] / this.s[Math.min(this.m, this.n) - 1];
    }

    get norm2() {
      return this.s[0];
    }

    get rank() {
      let tol = Math.max(this.m, this.n) * this.s[0] * Number.EPSILON;
      let r = 0;
      let s = this.s;

      for (let i = 0, ii = s.length; i < ii; i++) {
        if (s[i] > tol) {
          r++;
        }
      }

      return r;
    }

    get diagonal() {
      return Array.from(this.s);
    }

    get threshold() {
      return Number.EPSILON / 2 * Math.max(this.m, this.n) * this.s[0];
    }

    get leftSingularVectors() {
      return this.U;
    }

    get rightSingularVectors() {
      return this.V;
    }

    get diagonalMatrix() {
      return Matrix.diag(this.s);
    }

  }

  function solve(leftHandSide, rightHandSide, useSVD = false) {
    leftHandSide = WrapperMatrix2D.checkMatrix(leftHandSide);
    rightHandSide = WrapperMatrix2D.checkMatrix(rightHandSide);

    if (useSVD) {
      return new SingularValueDecomposition(leftHandSide).solve(rightHandSide);
    } else {
      return leftHandSide.isSquare() ? new LuDecomposition(leftHandSide).solve(rightHandSide) : new QrDecomposition(leftHandSide).solve(rightHandSide);
    }
  }

  class EigenvalueDecomposition {
    constructor(matrix, options = {}) {
      const {
        assumeSymmetric = false
      } = options;
      matrix = WrapperMatrix2D.checkMatrix(matrix);

      if (!matrix.isSquare()) {
        throw new Error('Matrix is not a square matrix');
      }

      if (matrix.isEmpty()) {
        throw new Error('Matrix must be non-empty');
      }

      let n = matrix.columns;
      let V = new Matrix(n, n);
      let d = new Float64Array(n);
      let e = new Float64Array(n);
      let value = matrix;
      let i, j;
      let isSymmetric = false;

      if (assumeSymmetric) {
        isSymmetric = true;
      } else {
        isSymmetric = matrix.isSymmetric();
      }

      if (isSymmetric) {
        for (i = 0; i < n; i++) {
          for (j = 0; j < n; j++) {
            V.set(i, j, value.get(i, j));
          }
        }

        tred2(n, e, d, V);
        tql2(n, e, d, V);
      } else {
        let H = new Matrix(n, n);
        let ort = new Float64Array(n);

        for (j = 0; j < n; j++) {
          for (i = 0; i < n; i++) {
            H.set(i, j, value.get(i, j));
          }
        }

        orthes(n, H, ort, V);
        hqr2(n, e, d, V, H);
      }

      this.n = n;
      this.e = e;
      this.d = d;
      this.V = V;
    }

    get realEigenvalues() {
      return Array.from(this.d);
    }

    get imaginaryEigenvalues() {
      return Array.from(this.e);
    }

    get eigenvectorMatrix() {
      return this.V;
    }

    get diagonalMatrix() {
      let n = this.n;
      let e = this.e;
      let d = this.d;
      let X = new Matrix(n, n);
      let i, j;

      for (i = 0; i < n; i++) {
        for (j = 0; j < n; j++) {
          X.set(i, j, 0);
        }

        X.set(i, i, d[i]);

        if (e[i] > 0) {
          X.set(i, i + 1, e[i]);
        } else if (e[i] < 0) {
          X.set(i, i - 1, e[i]);
        }
      }

      return X;
    }

  }

  function tred2(n, e, d, V) {
    let f, g, h, i, j, k, hh, scale;

    for (j = 0; j < n; j++) {
      d[j] = V.get(n - 1, j);
    }

    for (i = n - 1; i > 0; i--) {
      scale = 0;
      h = 0;

      for (k = 0; k < i; k++) {
        scale = scale + Math.abs(d[k]);
      }

      if (scale === 0) {
        e[i] = d[i - 1];

        for (j = 0; j < i; j++) {
          d[j] = V.get(i - 1, j);
          V.set(i, j, 0);
          V.set(j, i, 0);
        }
      } else {
        for (k = 0; k < i; k++) {
          d[k] /= scale;
          h += d[k] * d[k];
        }

        f = d[i - 1];
        g = Math.sqrt(h);

        if (f > 0) {
          g = -g;
        }

        e[i] = scale * g;
        h = h - f * g;
        d[i - 1] = f - g;

        for (j = 0; j < i; j++) {
          e[j] = 0;
        }

        for (j = 0; j < i; j++) {
          f = d[j];
          V.set(j, i, f);
          g = e[j] + V.get(j, j) * f;

          for (k = j + 1; k <= i - 1; k++) {
            g += V.get(k, j) * d[k];
            e[k] += V.get(k, j) * f;
          }

          e[j] = g;
        }

        f = 0;

        for (j = 0; j < i; j++) {
          e[j] /= h;
          f += e[j] * d[j];
        }

        hh = f / (h + h);

        for (j = 0; j < i; j++) {
          e[j] -= hh * d[j];
        }

        for (j = 0; j < i; j++) {
          f = d[j];
          g = e[j];

          for (k = j; k <= i - 1; k++) {
            V.set(k, j, V.get(k, j) - (f * e[k] + g * d[k]));
          }

          d[j] = V.get(i - 1, j);
          V.set(i, j, 0);
        }
      }

      d[i] = h;
    }

    for (i = 0; i < n - 1; i++) {
      V.set(n - 1, i, V.get(i, i));
      V.set(i, i, 1);
      h = d[i + 1];

      if (h !== 0) {
        for (k = 0; k <= i; k++) {
          d[k] = V.get(k, i + 1) / h;
        }

        for (j = 0; j <= i; j++) {
          g = 0;

          for (k = 0; k <= i; k++) {
            g += V.get(k, i + 1) * V.get(k, j);
          }

          for (k = 0; k <= i; k++) {
            V.set(k, j, V.get(k, j) - g * d[k]);
          }
        }
      }

      for (k = 0; k <= i; k++) {
        V.set(k, i + 1, 0);
      }
    }

    for (j = 0; j < n; j++) {
      d[j] = V.get(n - 1, j);
      V.set(n - 1, j, 0);
    }

    V.set(n - 1, n - 1, 1);
    e[0] = 0;
  }

  function tql2(n, e, d, V) {
    let g, h, i, j, k, l, m, p, r, dl1, c, c2, c3, el1, s, s2;

    for (i = 1; i < n; i++) {
      e[i - 1] = e[i];
    }

    e[n - 1] = 0;
    let f = 0;
    let tst1 = 0;
    let eps = Number.EPSILON;

    for (l = 0; l < n; l++) {
      tst1 = Math.max(tst1, Math.abs(d[l]) + Math.abs(e[l]));
      m = l;

      while (m < n) {
        if (Math.abs(e[m]) <= eps * tst1) {
          break;
        }

        m++;
      }

      if (m > l) {

        do {
          g = d[l];
          p = (d[l + 1] - g) / (2 * e[l]);
          r = hypotenuse(p, 1);

          if (p < 0) {
            r = -r;
          }

          d[l] = e[l] / (p + r);
          d[l + 1] = e[l] * (p + r);
          dl1 = d[l + 1];
          h = g - d[l];

          for (i = l + 2; i < n; i++) {
            d[i] -= h;
          }

          f = f + h;
          p = d[m];
          c = 1;
          c2 = c;
          c3 = c;
          el1 = e[l + 1];
          s = 0;
          s2 = 0;

          for (i = m - 1; i >= l; i--) {
            c3 = c2;
            c2 = c;
            s2 = s;
            g = c * e[i];
            h = c * p;
            r = hypotenuse(p, e[i]);
            e[i + 1] = s * r;
            s = e[i] / r;
            c = p / r;
            p = c * d[i] - s * g;
            d[i + 1] = h + s * (c * g + s * d[i]);

            for (k = 0; k < n; k++) {
              h = V.get(k, i + 1);
              V.set(k, i + 1, s * V.get(k, i) + c * h);
              V.set(k, i, c * V.get(k, i) - s * h);
            }
          }

          p = -s * s2 * c3 * el1 * e[l] / dl1;
          e[l] = s * p;
          d[l] = c * p;
        } while (Math.abs(e[l]) > eps * tst1);
      }

      d[l] = d[l] + f;
      e[l] = 0;
    }

    for (i = 0; i < n - 1; i++) {
      k = i;
      p = d[i];

      for (j = i + 1; j < n; j++) {
        if (d[j] < p) {
          k = j;
          p = d[j];
        }
      }

      if (k !== i) {
        d[k] = d[i];
        d[i] = p;

        for (j = 0; j < n; j++) {
          p = V.get(j, i);
          V.set(j, i, V.get(j, k));
          V.set(j, k, p);
        }
      }
    }
  }

  function orthes(n, H, ort, V) {
    let low = 0;
    let high = n - 1;
    let f, g, h, i, j, m;
    let scale;

    for (m = low + 1; m <= high - 1; m++) {
      scale = 0;

      for (i = m; i <= high; i++) {
        scale = scale + Math.abs(H.get(i, m - 1));
      }

      if (scale !== 0) {
        h = 0;

        for (i = high; i >= m; i--) {
          ort[i] = H.get(i, m - 1) / scale;
          h += ort[i] * ort[i];
        }

        g = Math.sqrt(h);

        if (ort[m] > 0) {
          g = -g;
        }

        h = h - ort[m] * g;
        ort[m] = ort[m] - g;

        for (j = m; j < n; j++) {
          f = 0;

          for (i = high; i >= m; i--) {
            f += ort[i] * H.get(i, j);
          }

          f = f / h;

          for (i = m; i <= high; i++) {
            H.set(i, j, H.get(i, j) - f * ort[i]);
          }
        }

        for (i = 0; i <= high; i++) {
          f = 0;

          for (j = high; j >= m; j--) {
            f += ort[j] * H.get(i, j);
          }

          f = f / h;

          for (j = m; j <= high; j++) {
            H.set(i, j, H.get(i, j) - f * ort[j]);
          }
        }

        ort[m] = scale * ort[m];
        H.set(m, m - 1, scale * g);
      }
    }

    for (i = 0; i < n; i++) {
      for (j = 0; j < n; j++) {
        V.set(i, j, i === j ? 1 : 0);
      }
    }

    for (m = high - 1; m >= low + 1; m--) {
      if (H.get(m, m - 1) !== 0) {
        for (i = m + 1; i <= high; i++) {
          ort[i] = H.get(i, m - 1);
        }

        for (j = m; j <= high; j++) {
          g = 0;

          for (i = m; i <= high; i++) {
            g += ort[i] * V.get(i, j);
          }

          g = g / ort[m] / H.get(m, m - 1);

          for (i = m; i <= high; i++) {
            V.set(i, j, V.get(i, j) + g * ort[i]);
          }
        }
      }
    }
  }

  function hqr2(nn, e, d, V, H) {
    let n = nn - 1;
    let low = 0;
    let high = nn - 1;
    let eps = Number.EPSILON;
    let exshift = 0;
    let norm = 0;
    let p = 0;
    let q = 0;
    let r = 0;
    let s = 0;
    let z = 0;
    let iter = 0;
    let i, j, k, l, m, t, w, x, y;
    let ra, sa, vr, vi;
    let notlast, cdivres;

    for (i = 0; i < nn; i++) {
      if (i < low || i > high) {
        d[i] = H.get(i, i);
        e[i] = 0;
      }

      for (j = Math.max(i - 1, 0); j < nn; j++) {
        norm = norm + Math.abs(H.get(i, j));
      }
    }

    while (n >= low) {
      l = n;

      while (l > low) {
        s = Math.abs(H.get(l - 1, l - 1)) + Math.abs(H.get(l, l));

        if (s === 0) {
          s = norm;
        }

        if (Math.abs(H.get(l, l - 1)) < eps * s) {
          break;
        }

        l--;
      }

      if (l === n) {
        H.set(n, n, H.get(n, n) + exshift);
        d[n] = H.get(n, n);
        e[n] = 0;
        n--;
        iter = 0;
      } else if (l === n - 1) {
        w = H.get(n, n - 1) * H.get(n - 1, n);
        p = (H.get(n - 1, n - 1) - H.get(n, n)) / 2;
        q = p * p + w;
        z = Math.sqrt(Math.abs(q));
        H.set(n, n, H.get(n, n) + exshift);
        H.set(n - 1, n - 1, H.get(n - 1, n - 1) + exshift);
        x = H.get(n, n);

        if (q >= 0) {
          z = p >= 0 ? p + z : p - z;
          d[n - 1] = x + z;
          d[n] = d[n - 1];

          if (z !== 0) {
            d[n] = x - w / z;
          }

          e[n - 1] = 0;
          e[n] = 0;
          x = H.get(n, n - 1);
          s = Math.abs(x) + Math.abs(z);
          p = x / s;
          q = z / s;
          r = Math.sqrt(p * p + q * q);
          p = p / r;
          q = q / r;

          for (j = n - 1; j < nn; j++) {
            z = H.get(n - 1, j);
            H.set(n - 1, j, q * z + p * H.get(n, j));
            H.set(n, j, q * H.get(n, j) - p * z);
          }

          for (i = 0; i <= n; i++) {
            z = H.get(i, n - 1);
            H.set(i, n - 1, q * z + p * H.get(i, n));
            H.set(i, n, q * H.get(i, n) - p * z);
          }

          for (i = low; i <= high; i++) {
            z = V.get(i, n - 1);
            V.set(i, n - 1, q * z + p * V.get(i, n));
            V.set(i, n, q * V.get(i, n) - p * z);
          }
        } else {
          d[n - 1] = x + p;
          d[n] = x + p;
          e[n - 1] = z;
          e[n] = -z;
        }

        n = n - 2;
        iter = 0;
      } else {
        x = H.get(n, n);
        y = 0;
        w = 0;

        if (l < n) {
          y = H.get(n - 1, n - 1);
          w = H.get(n, n - 1) * H.get(n - 1, n);
        }

        if (iter === 10) {
          exshift += x;

          for (i = low; i <= n; i++) {
            H.set(i, i, H.get(i, i) - x);
          }

          s = Math.abs(H.get(n, n - 1)) + Math.abs(H.get(n - 1, n - 2));
          x = y = 0.75 * s;
          w = -0.4375 * s * s;
        }

        if (iter === 30) {
          s = (y - x) / 2;
          s = s * s + w;

          if (s > 0) {
            s = Math.sqrt(s);

            if (y < x) {
              s = -s;
            }

            s = x - w / ((y - x) / 2 + s);

            for (i = low; i <= n; i++) {
              H.set(i, i, H.get(i, i) - s);
            }

            exshift += s;
            x = y = w = 0.964;
          }
        }

        iter = iter + 1;
        m = n - 2;

        while (m >= l) {
          z = H.get(m, m);
          r = x - z;
          s = y - z;
          p = (r * s - w) / H.get(m + 1, m) + H.get(m, m + 1);
          q = H.get(m + 1, m + 1) - z - r - s;
          r = H.get(m + 2, m + 1);
          s = Math.abs(p) + Math.abs(q) + Math.abs(r);
          p = p / s;
          q = q / s;
          r = r / s;

          if (m === l) {
            break;
          }

          if (Math.abs(H.get(m, m - 1)) * (Math.abs(q) + Math.abs(r)) < eps * (Math.abs(p) * (Math.abs(H.get(m - 1, m - 1)) + Math.abs(z) + Math.abs(H.get(m + 1, m + 1))))) {
            break;
          }

          m--;
        }

        for (i = m + 2; i <= n; i++) {
          H.set(i, i - 2, 0);

          if (i > m + 2) {
            H.set(i, i - 3, 0);
          }
        }

        for (k = m; k <= n - 1; k++) {
          notlast = k !== n - 1;

          if (k !== m) {
            p = H.get(k, k - 1);
            q = H.get(k + 1, k - 1);
            r = notlast ? H.get(k + 2, k - 1) : 0;
            x = Math.abs(p) + Math.abs(q) + Math.abs(r);

            if (x !== 0) {
              p = p / x;
              q = q / x;
              r = r / x;
            }
          }

          if (x === 0) {
            break;
          }

          s = Math.sqrt(p * p + q * q + r * r);

          if (p < 0) {
            s = -s;
          }

          if (s !== 0) {
            if (k !== m) {
              H.set(k, k - 1, -s * x);
            } else if (l !== m) {
              H.set(k, k - 1, -H.get(k, k - 1));
            }

            p = p + s;
            x = p / s;
            y = q / s;
            z = r / s;
            q = q / p;
            r = r / p;

            for (j = k; j < nn; j++) {
              p = H.get(k, j) + q * H.get(k + 1, j);

              if (notlast) {
                p = p + r * H.get(k + 2, j);
                H.set(k + 2, j, H.get(k + 2, j) - p * z);
              }

              H.set(k, j, H.get(k, j) - p * x);
              H.set(k + 1, j, H.get(k + 1, j) - p * y);
            }

            for (i = 0; i <= Math.min(n, k + 3); i++) {
              p = x * H.get(i, k) + y * H.get(i, k + 1);

              if (notlast) {
                p = p + z * H.get(i, k + 2);
                H.set(i, k + 2, H.get(i, k + 2) - p * r);
              }

              H.set(i, k, H.get(i, k) - p);
              H.set(i, k + 1, H.get(i, k + 1) - p * q);
            }

            for (i = low; i <= high; i++) {
              p = x * V.get(i, k) + y * V.get(i, k + 1);

              if (notlast) {
                p = p + z * V.get(i, k + 2);
                V.set(i, k + 2, V.get(i, k + 2) - p * r);
              }

              V.set(i, k, V.get(i, k) - p);
              V.set(i, k + 1, V.get(i, k + 1) - p * q);
            }
          }
        }
      }
    }

    if (norm === 0) {
      return;
    }

    for (n = nn - 1; n >= 0; n--) {
      p = d[n];
      q = e[n];

      if (q === 0) {
        l = n;
        H.set(n, n, 1);

        for (i = n - 1; i >= 0; i--) {
          w = H.get(i, i) - p;
          r = 0;

          for (j = l; j <= n; j++) {
            r = r + H.get(i, j) * H.get(j, n);
          }

          if (e[i] < 0) {
            z = w;
            s = r;
          } else {
            l = i;

            if (e[i] === 0) {
              H.set(i, n, w !== 0 ? -r / w : -r / (eps * norm));
            } else {
              x = H.get(i, i + 1);
              y = H.get(i + 1, i);
              q = (d[i] - p) * (d[i] - p) + e[i] * e[i];
              t = (x * s - z * r) / q;
              H.set(i, n, t);
              H.set(i + 1, n, Math.abs(x) > Math.abs(z) ? (-r - w * t) / x : (-s - y * t) / z);
            }

            t = Math.abs(H.get(i, n));

            if (eps * t * t > 1) {
              for (j = i; j <= n; j++) {
                H.set(j, n, H.get(j, n) / t);
              }
            }
          }
        }
      } else if (q < 0) {
        l = n - 1;

        if (Math.abs(H.get(n, n - 1)) > Math.abs(H.get(n - 1, n))) {
          H.set(n - 1, n - 1, q / H.get(n, n - 1));
          H.set(n - 1, n, -(H.get(n, n) - p) / H.get(n, n - 1));
        } else {
          cdivres = cdiv(0, -H.get(n - 1, n), H.get(n - 1, n - 1) - p, q);
          H.set(n - 1, n - 1, cdivres[0]);
          H.set(n - 1, n, cdivres[1]);
        }

        H.set(n, n - 1, 0);
        H.set(n, n, 1);

        for (i = n - 2; i >= 0; i--) {
          ra = 0;
          sa = 0;

          for (j = l; j <= n; j++) {
            ra = ra + H.get(i, j) * H.get(j, n - 1);
            sa = sa + H.get(i, j) * H.get(j, n);
          }

          w = H.get(i, i) - p;

          if (e[i] < 0) {
            z = w;
            r = ra;
            s = sa;
          } else {
            l = i;

            if (e[i] === 0) {
              cdivres = cdiv(-ra, -sa, w, q);
              H.set(i, n - 1, cdivres[0]);
              H.set(i, n, cdivres[1]);
            } else {
              x = H.get(i, i + 1);
              y = H.get(i + 1, i);
              vr = (d[i] - p) * (d[i] - p) + e[i] * e[i] - q * q;
              vi = (d[i] - p) * 2 * q;

              if (vr === 0 && vi === 0) {
                vr = eps * norm * (Math.abs(w) + Math.abs(q) + Math.abs(x) + Math.abs(y) + Math.abs(z));
              }

              cdivres = cdiv(x * r - z * ra + q * sa, x * s - z * sa - q * ra, vr, vi);
              H.set(i, n - 1, cdivres[0]);
              H.set(i, n, cdivres[1]);

              if (Math.abs(x) > Math.abs(z) + Math.abs(q)) {
                H.set(i + 1, n - 1, (-ra - w * H.get(i, n - 1) + q * H.get(i, n)) / x);
                H.set(i + 1, n, (-sa - w * H.get(i, n) - q * H.get(i, n - 1)) / x);
              } else {
                cdivres = cdiv(-r - y * H.get(i, n - 1), -s - y * H.get(i, n), z, q);
                H.set(i + 1, n - 1, cdivres[0]);
                H.set(i + 1, n, cdivres[1]);
              }
            }

            t = Math.max(Math.abs(H.get(i, n - 1)), Math.abs(H.get(i, n)));

            if (eps * t * t > 1) {
              for (j = i; j <= n; j++) {
                H.set(j, n - 1, H.get(j, n - 1) / t);
                H.set(j, n, H.get(j, n) / t);
              }
            }
          }
        }
      }
    }

    for (i = 0; i < nn; i++) {
      if (i < low || i > high) {
        for (j = i; j < nn; j++) {
          V.set(i, j, H.get(i, j));
        }
      }
    }

    for (j = nn - 1; j >= low; j--) {
      for (i = low; i <= high; i++) {
        z = 0;

        for (k = low; k <= Math.min(j, high); k++) {
          z = z + V.get(i, k) * H.get(k, j);
        }

        V.set(i, j, z);
      }
    }
  }

  function cdiv(xr, xi, yr, yi) {
    let r, d;

    if (Math.abs(yr) > Math.abs(yi)) {
      r = yi / yr;
      d = yr + r * yi;
      return [(xr + r * xi) / d, (xi - r * xr) / d];
    } else {
      r = yr / yi;
      d = yi + r * yr;
      return [(r * xr + xi) / d, (r * xi - xr) / d];
    }
  }

  class nipals {
    constructor(X, options = {}) {
      X = WrapperMatrix2D.checkMatrix(X);
      let {
        Y
      } = options;
      const {
        scaleScores = false,
        maxIterations = 1000,
        terminationCriteria = 1e-10
      } = options;
      let u;

      if (Y) {
        if (Array.isArray(Y) && typeof Y[0] === 'number') {
          Y = Matrix.columnVector(Y);
        } else {
          Y = WrapperMatrix2D.checkMatrix(Y);
        }

        if (Y.rows !== X.rows) {
          throw new Error('Y should have the same number of rows as X');
        }

        u = Y.getColumnVector(0);
      } else {
        u = X.getColumnVector(0);
      }

      let diff = 1;
      let t, q, w, tOld;

      for (let counter = 0; counter < maxIterations && diff > terminationCriteria; counter++) {
        w = X.transpose().mmul(u).div(u.transpose().mmul(u).get(0, 0));
        w = w.div(w.norm());
        t = X.mmul(w).div(w.transpose().mmul(w).get(0, 0));

        if (counter > 0) {
          diff = t.clone().sub(tOld).pow(2).sum();
        }

        tOld = t.clone();

        if (Y) {
          q = Y.transpose().mmul(t).div(t.transpose().mmul(t).get(0, 0));
          q = q.div(q.norm());
          u = Y.mmul(q).div(q.transpose().mmul(q).get(0, 0));
        } else {
          u = t;
        }
      }

      if (Y) {
        let p = X.transpose().mmul(t).div(t.transpose().mmul(t).get(0, 0));
        p = p.div(p.norm());
        let xResidual = X.clone().sub(t.clone().mmul(p.transpose()));
        let residual = u.transpose().mmul(t).div(t.transpose().mmul(t).get(0, 0));
        let yResidual = Y.clone().sub(t.clone().mulS(residual.get(0, 0)).mmul(q.transpose()));
        this.t = t;
        this.p = p.transpose();
        this.w = w.transpose();
        this.q = q;
        this.u = u;
        this.s = t.transpose().mmul(t);
        this.xResidual = xResidual;
        this.yResidual = yResidual;
        this.betas = residual;
      } else {
        this.w = w.transpose();
        this.s = t.transpose().mmul(t).sqrt();

        if (scaleScores) {
          this.t = t.clone().div(this.s.get(0, 0));
        } else {
          this.t = t;
        }

        this.xResidual = X.sub(t.mmul(w.transpose()));
      }
    }

  }

  /**
   * This method will allow to enlarge peaks and prevent overlap between peaks
   * Because peaks may not be symmetric after we add 2 properties, from and to.
   * @param {Array} peakList
   * @param {object} [options={}]
   * @param {number} [options.factor=2]
   * @param {boolean} [options.overlap=false] by default we don't allow overlap
   * @return {Array} peakList
   */
  function broadenPeaks(peakList, options = {}) {
    const {
      factor = 2,
      overlap = false
    } = options;

    for (let peak of peakList) {
      if (!peak.right || !peak.left) {
        peak.from = peak.x - peak.width / 2 * factor;
        peak.to = peak.x + peak.width / 2 * factor;
      } else {
        peak.from = peak.x - (peak.x - peak.left.x) * factor;
        peak.to = peak.x + (peak.right.x - peak.x) * factor;
      }
    }

    if (!overlap) {
      for (let i = 0; i < peakList.length - 1; i++) {
        let peak = peakList[i];
        let nextPeak = peakList[i + 1];

        if (peak.to > nextPeak.from) {
          peak.to = nextPeak.from = (peak.to + nextPeak.from) / 2;
        }
      }
    }

    for (let peak of peakList) {
      peak.width = peak.to - peak.from;
    }

    return peakList;
  }

  /**
  import { xIsMonotone } from '../x/xIsMonotone';
   * This function performs a quick sort of the x array while transforming the y array to preserve the coordinates.
   * @param {DataXY} [data] Object that contains property x (Array) and y (Array)
   */

  function xySortX$1(data) {
    const {
      x,
      y
    } = data; // no need to sort if it is already sorted

    if (xIsMonotone$1(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;
  }

  /**
   *
   * @param {ArrayPoints} [points] array of growing points {x,y}
   * @param {object} [options={}]
   * @param {object} [slotWidth=1] limit to join the data
   */
  function xyObjectSlotX$1(points, options = {}) {
    const {
      slotWidth = 1
    } = options;
    const halfSlot = slotWidth / 2; // when we join we will use the center of mass

    let result = [];
    let current = {
      x: Number.MIN_VALUE,
      y: 0
    };

    for (let point of points) {
      let slot = point.x - (point.x + halfSlot) % slotWidth + halfSlot;

      if (Math.abs(current.x - slot) > Number.EPSILON) {
        current = {
          x: slot,
          y: 0
        };
        result.push(current);
      }

      current.y += point.y;
    }

    return result;
  }

  /**
   * Class allowing to manage a Serie
   */
  class Series {
    constructor(array, dimension, options = {}) {
      let {
        meta = {}
      } = options;

      if (new.target === Series) {
        throw new Error('You need to create either a 1D or 2D series');
      }

      this.data = array;
      this.dimension = dimension;
      this.meta = meta;
      this.name = '';
    }

    getData() {
      return this.data;
    }

    is1D() {
      return this.dimension === 1;
    }

    is2D() {
      return this.dimension === 2;
    }

    toJSON() {
      return {
        data: this.data,
        meta: this.meta
      };
    }
    /**
     * Specify an array of index to keep
     * @param {Array} array
     */


    keep(array) {
      const newData = [];

      for (let i of array) {
        newData.push(this.data[i]);
      }

      this.data = newData;
      return this;
    }

  }

  /**
   * Class allowing to manage a Serie
   */

  class Series1D extends Series {
    constructor(array) {
      super(array, 1);
    }

  }

  /**
   * Class allowing to manage a 2D Serie
   */

  class Series2D extends Series {
    constructor(array) {
      super(array, 2);
    }

  }

  function seriesFromArray(array) {
    // need to check if it is a 1D or 2D array (or 3D ?)
    if (!isAnyArray(array)) {
      throw new TypeError('seriesFromArray requires as parameter an array of numbers or array');
    }

    if (array.length === 0 || typeof array[0] === 'number') {
      return new Series1D(array);
    }

    if (!isAnyArray(array[0])) {
      throw new TypeError('seriesFromArray requires as parameter an array of numbers or array');
    }

    return new Series2D(array);
  }

  function meanFilter(chromatogram, seriesName, options = {}) {
    const {
      factor = 2
    } = options;
    let series = chromatogram.getSeries(seriesName);
    let filtered = [];

    for (let i = 0; i < series.data.length; i++) {
      filtered.push(applyFilter$1(series.data[i], factor));
    }

    return seriesFromArray(filtered);
  }

  function applyFilter$1(series, factor) {
    let filtered = [[], []];
    if (series[1].length === 0) return filtered;
    const meanIntensity = factor * mean(series[1]);

    for (let i = 0; i < series[0].length; i++) {
      if (series[1][i] > meanIntensity) {
        filtered[0].push(series[0][i]);
        filtered[1].push(series[1][i]);
      }
    }

    return filtered;
  }

  function percentageFilter(chromatogram, seriesName, options = {}) {
    const {
      percentage = 0.1
    } = options;
    let series = chromatogram.getSeries(seriesName);
    let filtered = [];

    for (let i = 0; i < series.data.length; i++) {
      filtered.push(applyFilter(series.data[i], percentage));
    }

    return seriesFromArray(filtered);
  }

  function applyFilter(series, percentage) {
    let basePeak;

    try {
      basePeak = max(series[1]);
    } catch (e) {
      basePeak = 0;
    }

    let filtered = [[], []];

    for (let i = 0; i < series[0].length; i++) {
      if (series[1][i] > percentage * basePeak) {
        filtered[0].push(series[0][i]);
        filtered[1].push(series[1][i]);
      }
    }

    return filtered;
  }

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


  var Kind$a = {
    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
   */


  var parseCharge$1 = 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;
  };

  const Kind$9 = Kind$a;
  const parseCharge = parseCharge$1;
  /**
   * Parse a mf to an array of kind / value
   * @param {String} mf
   */

  var parse$3 = function parse(mf) {
    return new MFParser().parse(mf);
  };

  class MFParser {
    parse(mf = '') {
      this.mf = mf;
      this.i = 0;
      this.result = [];
      let lastKind = Kind$9.BEGIN;

      while (this.i < mf.length) {
        if (this.result.length > 0 && this.result[this.result.length - 1].kind !== Kind$9.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$9.SALT || lastKind === Kind$9.BEGIN || lastKind === Kind$9.OPENING_PARENTHESIS) {
            if (value.to) {
              throw new MFError(this.mf, this.i, 'Premultiplier may not contain a -');
            }

            this.result.push({
              kind: Kind$9.PRE_MULTIPLIER,
              value: value.from
            });
          } else if (lastKind === Kind$9.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$9.MULTIPLIER_RANGE,
                value: {
                  from: Math.min(value.from, value.to),
                  to: Math.max(value.from, value.to)
                }
              });
            } else {
              this.result.push({
                kind: Kind$9.MULTIPLIER,
                value: value.from
              });
            }
          }

          continue;
        } else if (char === '.') {
          // a point
          this.result.push({
            kind: Kind$9.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$9.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$9.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$9.CHARGE,
              value: charge
            });
          } else {
            this.result.push({
              kind: Kind$9.OPENING_PARENTHESIS,
              value: '('
            });
          }
        } else if (char === ')') {
          this.result.push({
            kind: Kind$9.CLOSING_PARENTHESIS,
            value: ')'
          });
        } else if (char === '[') {
          // defines an isotope
          let isotope = this.getIsotope(ascii);
          this.result.push({
            kind: Kind$9.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$9.ATOM) {
            let lastResult = this.result[this.result.length - 1];
            lastResult.kind = Kind$9.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$9.CHARGE,
            value: charge
          });
        } else if (char === '-') {
          // charge not in parenthesis
          let charge = this.getNonParenthesisCharge(ascii);
          this.result.push({
            kind: Kind$9.CHARGE,
            value: charge
          });
        } else if (char === '$') {
          // it is a comment after
          this.result.push({
            kind: Kind$9.COMMENT,
            value: this.mf.substring(this.i + 1)
          });
          break;
        } else {
          this.result.push({
            kind: Kind$9.TEXT,
            value: char
          });
        }

        this.i++;
      }

      this.checkParenthesis();
      return this.result;
    }

    checkParenthesis() {
      let counter = 0;

      for (let line of this.result) {
        if (line.kind === Kind$9.OPENING_PARENTHESIS) counter++;
        if (line.kind === Kind$9.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);
    }
  }

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


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

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

  const Format$1 = Format$2;
  const Kind$8 = Kind$a;
  const formatCharge = formatCharge$1;
  /**
   * Converts an array of mf elements to an array of formatting information
   * @param {Array<Object>} result of the parse method
   */

  var toDisplay$3 = function convertForDisplay(lines) {
    let results = [];
    let result = {};

    for (let line of lines) {
      switch (line.kind) {
        case Kind$8.MULTIPLIER:
          if (line.value !== 1) {
            result = {
              kind: Format$1.SUBSCRIPT,
              value: String(line.value)
            };
            results.push(result);
          }

          break;

        case Kind$8.MULTIPLIER_RANGE:
          result = {
            kind: Format$1.SUBSCRIPT,
            value: `${String(line.value.from)}-${line.value.to}`
          };
          results.push(result);
          break;

        case Kind$8.CHARGE:
          if (result.kind === Format$1.SUBSCRIPT) {
            result.kind = Format$1.SUPERIMPOSE;
            result.over = formatCharge(line.value);
            result.under = result.value;
            result.value = undefined;
          } else {
            result = {
              kind: Format$1.SUPERSCRIPT,
              value: formatCharge(line.value)
            };
            results.push(result);
          }

          break;

        case Kind$8.ISOTOPE:
          result = {
            kind: Format$1.SUPERSCRIPT,
            value: line.value.isotope
          };
          results.push(result);
          result = {
            kind: Format$1.TEXT,
            value: line.value.atom
          };
          results.push(result);
          break;

        case Kind$8.ISOTOPE_RATIO:
          if (result.kind === Format$1.TEXT) {
            result.value += line.value.atom;
          } else {
            result = {
              kind: Format$1.TEXT,
              value: line.value.atom
            };
            results.push(result);
          }

          result = {
            kind: Format$1.SUPERSCRIPT,
            value: `{${line.value.ratio.join(',')}}`
          };
          results.push(result);
          break;

        case Kind$8.SALT:
          if (result.kind === Format$1.TEXT) {
            result.value += ' • ';
          } else {
            result = {
              kind: Format$1.TEXT,
              value: ' • '
            };
            results.push(result);
          }

          break;

        default:
          if (result.kind === Format$1.TEXT) {
            result.value += line.value;
          } else {
            result = {
              kind: Format$1.TEXT,
              value: line.value
            };
            results.push(result);
          }

      }
    }

    return results;
  };

  var 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%'
  };

  const Format = Format$2;
  const Style = Style$1;

  var toHtml$2 = function getHtml(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.SUPERIMPOSE}">`);
          html.push(`<sup style="${Style.SUPERIMPOSE_SUP_SUB}">${line.over}</sup>`);
          html.push(`<sub style="${Style.SUPERIMPOSE_SUP_SUB}">${line.under}</sub>`);
          html.push('</span>');
          break;

        default:
          html.push(line.value);
      }
    }

    return html.join('');
  };

  var require$$0$1 = [
  	{
  		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$a = JSON.parse(JSON.stringify(require$$0$1));
  elements$a.forEach(element => {
    element.isotopes = element.isotopes.filter(i => i.abundance > 0);
  });
  var elementsAndStableIsotopes$2 = elements$a;

  const elementsAndStableIsotopes$1 = elementsAndStableIsotopes$2;
  let elementsAndStableIsotopesObject$1 = {};
  elementsAndStableIsotopes$1.forEach(element => {
    elementsAndStableIsotopesObject$1[element.symbol] = element;
  });
  var elementsAndStableIsotopesObject_1 = elementsAndStableIsotopesObject$1;

  const elements$9 = Object.keys(elementsAndStableIsotopesObject_1).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 capitalize(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$9.includes(two)) {
              newPart += two;
              j++;
            } else {
              if (elements$9.includes(one)) {
                newPart += one;
              } else {
                return mf;
              }
            }
          }
        }

        parts[i] = newPart;
      }
    }

    return parts.join('');
  }

  var ensureCase$1 = capitalize;

  var constants$3 = {
    ELECTRON_MASS: 5.4857990907e-4
  };

  const elements$8 = require$$0$1;
  const data$2 = elements$8.map(element => ({
    number: element.number,
    symbol: element.symbol,
    mass: element.mass,
    name: element.name,
    monoisotopicMass: element.monoisotopicMass
  }));
  var elements_1 = data$2;

  var elementsAndIsotopes$1 = require$$0$1;

  const elements$7 = require$$0$1;
  let elementsAndIsotopesObject$2 = {};
  elements$7.forEach(element => {
    elementsAndIsotopesObject$2[element.symbol] = element;
  });
  var elementsAndIsotopesObject_1 = elementsAndIsotopesObject$2;

  const elements$6 = elements_1;
  let elementsObject$2 = {};
  elements$6.forEach(element => {
    elementsObject$2[element.symbol] = element;
  });
  var elementsObject_1 = elementsObject$2;

  const {
    ELECTRON_MASS: ELECTRON_MASS$4
  } = constants$3;
  const elements$5 = elements_1;
  const elementsAndIsotopes = elementsAndIsotopes$1;
  const elementsAndIsotopesObject$1 = elementsAndIsotopesObject_1;
  const elementsAndStableIsotopes = elementsAndStableIsotopes$2;
  const elementsAndStableIsotopesObject = elementsAndStableIsotopesObject_1;
  const elementsObject$1 = elementsObject_1;
  var src$9 = {
    elements: elements$5,
    elementsObject: elementsObject$1,
    elementsAndIsotopes,
    elementsAndIsotopesObject: elementsAndIsotopesObject$1,
    elementsAndStableIsotopes,
    elementsAndStableIsotopesObject,
    ELECTRON_MASS: ELECTRON_MASS$4
  };

  var groups$4 = [{
    "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@FL"
    },
    "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@",
      "coordinates": ""
    },
    "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@",
      "coordinates": ""
    },
    "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@",
      "coordinates": ""
    },
    "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@",
      "coordinates": ""
    },
    "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@",
      "coordinates": ""
    },
    "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@",
      "coordinates": ""
    },
    "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@",
      "coordinates": ""
    },
    "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@",
      "coordinates": ""
    },
    "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@",
      "coordinates": ""
    },
    "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@",
      "coordinates": ""
    },
    "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@",
      "coordinates": ""
    },
    "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@",
      "coordinates": ""
    },
    "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@",
      "coordinates": ""
    },
    "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
    }]
  }];

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

    groupsObject[e.symbol] = e;
  });
  var groupsObject_1 = groupsObject;

  const elements$4 = src$9.elementsAndStableIsotopesObject;

  function getIsotopeRatioInfo$2(value) {
    let result = {
      mass: 0,
      monoisotopicMass: 0
    };
    let element = elements$4[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);
  }

  var getIsotopeRatioInfo_1 = getIsotopeRatioInfo$2;

  const elements$3 = elementsAndIsotopesObject_1;
  const isotopes$3 = {};
  Object.keys(elements$3).forEach(key => {
    let e = elements$3[key];
    e.isotopes.forEach(i => {
      isotopes$3[i.nominal + key] = {
        abundance: i.abundance,
        mass: i.mass
      };
    });
  });
  var getIsotopesObject = isotopes$3;

  const elements$2 = src$9.elementsObject;
  const groups$2 = groupsObject_1;
  const Kind$7 = Kind$a;
  const getIsotopeRatioInfo$1 = getIsotopeRatioInfo_1;
  const isotopes$2 = getIsotopesObject;
  /**
   *
   * @param {*} parts
   * @param {*} [options={}]
   */

  var getEA$1 = function getEA(parts) {
    let results = {};

    for (let part of parts) {
      for (let line of part) {
        switch (line.kind) {
          case Kind$7.ISOTOPE:
            {
              let isotope = isotopes$2[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$7.ISOTOPE_RATIO:
            {
              let isotopeRatioInfo = getIsotopeRatioInfo$1(line.value);
              addMass(results, line.value.atom, isotopeRatioInfo.mass * line.multiplier);
              break;
            }

          case Kind$7.ATOM:
            {
              let element = elements$2[line.value];

              if (!element) {
                element = groups$2[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$7.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;
  }

  const {
    elementsObject,
    elementsAndIsotopesObject
  } = src$9;
  const Kind$6 = Kind$a;
  /**
   *
   * @param {*} parts
   * @param {*} [options={}]
   */

  var getElements$1 = function getElements(parts) {
    const elements = [];

    for (const part of parts) {
      for (const line of part) {
        let number = line.multiplier;

        switch (line.kind) {
          case Kind$6.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$6.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);
  }

  var 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 Kind$5 = Kind$a;
  /**
   * Convert a MF part to an array of atoms
   * This procedure will suppress the isotopes !
   * This is mainly used to make queries
   */

  var partToAtoms$1 = function partToAtoms(part) {
    let atoms = {};

    for (let line of part) {
      switch (line.kind) {
        case Kind$5.ISOTOPE:
          if (!atoms[line.value.atom]) atoms[line.value.atom] = 0;
          atoms[line.value.atom] += line.multiplier;
          break;

        case Kind$5.ISOTOPE_RATIO:
          if (!atoms[line.value.atom]) atoms[line.value.atom] = 0;
          atoms[line.value.atom] += line.multiplier;
          break;

        case Kind$5.ATOM:
          if (!atoms[line.value]) atoms[line.value] = 0;
          atoms[line.value] += line.multiplier;
          break;

        case Kind$5.CHARGE:
          break;

        case Kind$5.ANCHOR:
          break;

        default:
          throw new Error('partToMF unhandled Kind: ', line.kind);
      }
    }

    return atoms;
  };

  const Kind$4 = Kind$a;

  var partToMF$2 = function partToMF(part, options = {}) {
    let mf = [];

    for (let line of part) {
      switch (line.kind) {
        case Kind$4.ISOTOPE:
          if (line.multiplier !== 0) {
            mf.push(`[${line.value.isotope}${line.value.atom}]${line.multiplier !== 1 ? line.multiplier : ''}`);
          }

          break;

        case Kind$4.ISOTOPE_RATIO:
          if (line.multiplier !== 0) {
            mf.push(`${line.value.atom}{${line.value.ratio.join(',')}}${line.multiplier !== 1 ? line.multiplier : ''}`);
          }

          break;

        case Kind$4.ATOM:
          if (line.multiplier !== 0) {
            mf.push(line.value + (line.multiplier !== 1 ? line.multiplier : ''));
          }

          break;

        case Kind$4.CHARGE:
          if (line.value === 0 || options.neutral) break;
          mf.push(`(${line.value > 0 ? `+${line.value}` : line.value})`);
          break;
      }
    }

    return mf.join('');
  };

  const {
    ELECTRON_MASS: ELECTRON_MASS$3
  } = constants$3;
  const elements$1 = elementsAndIsotopesObject_1;
  const unsaturations = unsaturationsObject;
  const groups$1 = groupsObject_1;
  const Kind$3 = Kind$a;
  const getIsotopeRatioInfo = getIsotopeRatioInfo_1;
  const isotopes$1 = getIsotopesObject;
  const partToAtoms = partToAtoms$1;
  const partToMF$1 = partToMF$2;
  /**
   *
   * @param {*} parts
   * @param {*} [options={}]
   */

  var getInfo$1 = function getInfo(parts, options = {}) {
    let {
      customUnsaturations = {}
    } = options;
    if (parts.length === 0) return {};

    if (parts.length === 1) {
      return getProcessedPart$1(parts[0], customUnsaturations);
    }

    let result = {
      parts: []
    };

    for (let part of parts) {
      result.parts.push(getProcessedPart$1(part, customUnsaturations));
    }

    result.monoisotopicMass = 0;
    result.mass = 0;
    result.charge = 0;
    result.mf = result.parts.map(a => a.mf).join('.');
    result.parts.forEach(a => {
      result.mass += a.mass;
      result.monoisotopicMass += a.monoisotopicMass;
      result.charge += a.charge;
    });
    return result;
  };

  function getProcessedPart$1(part, customUnsaturations) {
    let currentPart = {
      mass: 0,
      monoisotopicMass: 0,
      charge: 0,
      mf: '',
      atoms: partToAtoms(part)
    };
    let unsaturation = 0;
    let validUnsaturation = true;
    currentPart.mf = partToMF$1(part);

    for (let line of part) {
      let currentElement = '';

      switch (line.kind) {
        case Kind$3.ATOM:
          {
            currentElement = line.value;
            let element = elements$1[line.value]; // todo should we have a kind GROUP ?

            if (!element) {
              element = groups$1[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.monoisotopicMass += element.monoisotopicMass * line.multiplier;
            currentPart.mass += element.mass * line.multiplier;
            break;
          }

        case Kind$3.ISOTOPE:
          {
            currentElement = line.value.atom;
            let isotope = isotopes$1[line.value.isotope + line.value.atom];

            if (!isotope) {
              throw new Error(`Unknown isotope: ${line.value.isotope}${line.value.atom}`);
            }

            currentPart.monoisotopicMass += isotope.mass * line.multiplier;
            currentPart.mass += isotope.mass * line.multiplier;
            break;
          }

        case Kind$3.ISOTOPE_RATIO:
          {
            currentElement = line.value.atom;
            let isotopeRatioInfo = getIsotopeRatioInfo(line.value);
            currentPart.monoisotopicMass += isotopeRatioInfo.monoisotopicMass * line.multiplier;
            currentPart.mass += isotopeRatioInfo.mass * line.multiplier;
            break;
          }

        case Kind$3.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 (unsaturations[currentElement] !== undefined) {
          unsaturation += unsaturations[currentElement] * line.multiplier;
        } else {
          validUnsaturation = false;
        }
      }
    } // need to calculate the observedMonoisotopicMass


    if (currentPart.charge) {
      currentPart.observedMonoisotopicMass = (currentPart.monoisotopicMass - currentPart.charge * ELECTRON_MASS$3) / Math.abs(currentPart.charge);
    }

    if (validUnsaturation) {
      currentPart.unsaturation = unsaturation / 2 + 1;
    }

    return currentPart;
  }

  const elements = elementsAndStableIsotopesObject_1;
  const Kind$2 = Kind$a;
  const isotopes = getIsotopesObject;
  /**
   *
   * @param {*} parts
   * @param {*} options
   */

  var getIsotopesInfo$1 = 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$2.ISOTOPE:
          {
            let isotope = isotopes[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$2.ISOTOPE_RATIO:
          {
            let element = elements[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$2.ATOM:
          {
            let element = elements[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$2.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: ${isotopes}`);
    }

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

  const Kind$1 = Kind$a;
  const toDisplay$2 = toDisplay$3;
  /**
   * Converts an array of mf elements to an array of formatting information
   * @param {Array<Object>} result of the parse method
   */

  var partsToDisplay$1 = function partsToDisplay(parts) {
    let lines = [];

    for (let part of parts) {
      if (lines.length > 0) lines.push({
        kind: Kind$1.SALT,
        value: '•'
      });

      for (let partLine of part) {
        lines.push(partLine);

        if (partLine.multiplier) {
          lines.push({
            kind: Kind$1.MULTIPLIER,
            value: partLine.multiplier
          });
        }
      }
    }

    return toDisplay$2(lines);
  };

  const partToMF = partToMF$2;

  var partsToMF$1 = function partsToMF(parts, options) {
    let mf = [];

    for (let part of parts) {
      mf.push(partToMF(part, options));
    }

    return mf.join(' . ');
  };

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

  var src$8 = atomSorter$1;

  const atomSorter = src$8;
  const groups = groupsObject_1;
  const Kind = Kind$a;
  /**
   *
   * @param {*} lines
   * @param {object} [options={}]
   * @param {boolean} [options.expand=true] - Should we expand the groups
   */

  var toParts$1 = function toParts(lines, options = {}) {
    const {
      expand: shouldExpandGroups = 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(Object.assign({}, 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 (shouldExpandGroups) expandGroups(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 expandGroups(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 = groups[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(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;
  }

  const ensureCase = ensureCase$1;
  const parse$2 = parse$3;
  const getEA = getEA$1;
  const getElements = getElements$1;
  const getInfo = getInfo$1;
  const getIsotopesInfo = getIsotopesInfo$1;
  const partsToDisplay = partsToDisplay$1;
  const partsToMF = partsToMF$1;
  const toDisplay$1 = toDisplay$3;
  const toHtml$1 = toHtml$2;
  const toParts = toParts$1;

  class MF$3 {
    constructor(mf, options = {}) {
      if (options.ensureCase) {
        mf = ensureCase(mf);
      }

      this.parsed = parse$2(mf);
      this.cache = {};
    }

    toDisplay() {
      if (!this.cache.displayed) this.cache.displayed = toDisplay$1(this.parsed);
      return this.cache.displayed;
    }

    toHtml() {
      if (!this.cache.html) {
        this.toDisplay();
        this.cache.html = toHtml$1(this.cache.displayed);
      }

      return this.cache.html;
    }

    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 informations for all the parts
     * @param {object} [options={}] options
     */


    getInfo(options = {}) {
      if (!this.cache.info) {
        this.toParts();
        this.cache.info = getInfo(this.cache.parts, options);
      }

      return this.cache.info;
    }
    /**
     * Returns an object with the elemental analysis
     */


    getEA(options = {}) {
      if (!this.cache.ea) {
        this.toParts();
        this.cache.ea = getEA(this.cache.parts, options);
      }

      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(options = {}) {
      if (!this.cache.isotopesInfo) {
        this.toParts();
        this.cache.isotopesInfo = getIsotopesInfo(this.cache.parts, options);
      }

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

  }

  var MF_1 = MF$3;

  const parse$1 = parse$3;
  const toDisplay = toDisplay$3;
  const toHtml = toHtml$2;
  /**
   * Parse a molecular formula and converts it to an HTML code
   * @param {String} mf String containing the molecular formula
   */

  function parseToHtml(mf) {
    let parsed = parse$1(mf);
    let display = toDisplay(parsed);
    return toHtml(display);
  }

  var src$7 = {
    Kind: Kind$a,
    Format: Format$2,
    Style: Style$1,
    parse: parse$3,
    ensureCase: ensureCase$1,
    toDisplay,
    toHtml,
    parseToHtml,
    MF: MF_1
  };

  function applyLockMass(chromatogram, mfs, options = {}) {
    const {
      oddReference = true,
      maxShift = 0.1
    } = options; // allows mf as string or array

    if (typeof mfs === 'string') {
      mfs = [mfs];
    } // calculate the mass reference values


    const referenceMass = mfs.map(mf => {
      let info = new src$7.MF(mf).getInfo();
      return info.observedMonoisotopicMass || info.monoisotopicMass;
    });
    const ms = chromatogram.getSeries('ms').data; // check where is the reference values

    let referenceIndexShift = Number(oddReference);
    let msIndexShift = Number(!oddReference);
    const newSize = ms.length >> 1;
    let referencesCount = new Array(referenceMass.length).fill(0); // applying the changes for all the spectra

    let previousValidDifference = Number.MAX_VALUE;
    let usingPreviousValidDifference = false;

    for (let i = 0; i < newSize; i++) {
      let massIndex = 2 * i + msIndexShift;
      let referenceIndex = 2 * i + referenceIndexShift; // calculate the difference between theory and experimental (the smallest)

      let difference = Number.MAX_VALUE;
      let closestIndex = -1;

      for (let j = 0; j < referenceMass.length; j++) {
        for (let k = 0; k < ms[referenceIndex][0].length; k++) {
          if (Math.abs(difference) > Math.abs(referenceMass[j] - ms[referenceIndex][0][k])) {
            difference = referenceMass[j] - ms[referenceIndex][0][k];
            closestIndex = j;
          }
        }
      }

      if (Math.abs(difference) > maxShift && Math.abs(previousValidDifference) < maxShift) {
        difference = previousValidDifference;
        usingPreviousValidDifference = true;
      } else {
        usingPreviousValidDifference = false;
      } // apply identified lock mass


      if (Math.abs(difference) < maxShift) {
        previousValidDifference = difference;

        if (!usingPreviousValidDifference) {
          if (closestIndex !== -1) {
            referencesCount[closestIndex] += 1;
          }
        }

        for (let m = 0; m < ms[massIndex][0].length; m++) {
          ms[massIndex][0][m] += difference;
        }
      }
    }

    const referenceUsed = {
      total: newSize,
      totalFound: referencesCount.reduce((prev, current) => current + prev, 0),
      mfs: {},
      percent: 0
    };

    for (let r = 0; r < referenceMass.length; r++) {
      referenceUsed.mfs[mfs[r]] = referencesCount[r];
    }

    referenceUsed.percent = referenceUsed.totalFound / referenceUsed.total * 100; // remove the time and the mass spectra that contains the reference

    chromatogram.filter(index => index % 2 !== referenceIndexShift);
    return referenceUsed;
  }

  /**
   * Calculate bpc
   * @param {Chromatogram} chromatogram - GC/MS chromatogram where make the peak picking
   * @return {Array} - Calculated bpc
   */

  function calculateBpc(chromatogram) {
    const ms = chromatogram.getSeries('ms');
    const massSpectra = ms.data;
    const bpc = [];

    for (const massSpectrum of massSpectra) {
      if (massSpectrum[1].length > 0) {
        bpc.push(max(massSpectrum[1]));
      } else {
        bpc.push(0);
      }
    }

    return bpc;
  }

  /**
   * Calculate the Extracted Ion Chromatogram
   * @param {object} chromatogram
   * @param {string|number} targetMass
   * @param {object} [options={}]
   * @param {number} [options.slotWidth=1]
   * @returns
   */
  function calculateEic(chromatogram, targetMass, options = {}) {
    const {
      slotWidth = 1
    } = options;

    if (!targetMass) {
      throw new Error('targetMass must be defined and a number or string of comma separated numbers');
    }

    if (!isNaN(targetMass)) {
      targetMass = [targetMass];
    } else if (typeof targetMass === 'string') {
      targetMass = targetMass.split(/[ ,;\r\n\t]+/).map(value => Number(value));
    }

    for (let mass of targetMass) {
      if (isNaN(mass)) {
        throw new Error('targetMass must be defined and a number or string of comma separated numbers');
      }
    }

    const halfWidth = slotWidth / 2;
    const ms = chromatogram.getSeries('ms');
    const massSpectra = ms.data;
    const result = new Array(massSpectra.length).fill(0);

    for (let mass of targetMass) {
      for (let i = 0; i < massSpectra.length; i++) {
        let massSpectrum = massSpectra[i];

        for (let j = 0; j < massSpectrum[0].length; j++) {
          if (Math.abs(massSpectrum[0][j] - mass) <= halfWidth) {
            result[i] += massSpectrum[1][j];
          }
        }
      }
    }

    return result;
  }

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

  var defaultSource = Math.random;

  var randomUniform = (function sourceRandomUniform(source) {
    function randomUniform(min, max) {
      min = min == null ? 0 : +min;
      max = max == null ? 1 : +max;
      if (arguments.length === 1) max = min, min = 0;else max -= min;
      return function () {
        return source() * max + min;
      };
    }

    randomUniform.source = sourceRandomUniform;
    return randomUniform;
  })(defaultSource);

  var randomNormal = (function sourceRandomNormal(source) {
    function randomNormal(mu, sigma) {
      var x, r;
      mu = mu == null ? 0 : +mu;
      sigma = sigma == null ? 1 : +sigma;
      return function () {
        var y; // If available, use the second previously-generated uniform random.

        if (x != null) y = x, x = null; // Otherwise, generate a new x and y.
        else do {
          x = source() * 2 - 1;
          y = source() * 2 - 1;
          r = x * x + y * y;
        } while (!r || r > 1);
        return mu + sigma * y * Math.sqrt(-2 * Math.log(r) / r);
      };
    }

    randomNormal.source = sourceRandomNormal;
    return randomNormal;
  })(defaultSource);

  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(seed = Date.now()) {
      this.state = new Uint32Array(4);
      this.init(seed);
      this.random = this.getFloat.bind(this);
    }
    /**
     * Returns a 32-bit integer r (0 <= r < 2^32)
     */


    getUint32() {
      this.nextState();
      return this.state[3] + this.state[2] >>> 0;
    }
    /**
     * Returns a floating point number r (0.0 <= r < 1.0)
     */


    getFloat() {
      return (this.getUint32() >>> 8) * FLOAT_MUL;
    }

    init(seed) {
      if (!Number.isInteger(seed)) {
        throw new TypeError('seed must be an integer');
      }

      this.state[0] = seed;
      this.state[1] = 0;
      this.state[2] = 0;
      this.state[3] = 0;

      for (let i = 1; i < LOOP; i++) {
        this.state[i & 3] ^= i + 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;
    }

  }

  function addNoise(data, percent = 0, options = {}) {
    const {
      distribution = 'uniform',
      seed
    } = options;
    let generateRandomNumber;

    switch (distribution) {
      case 'uniform':
        {
          generateRandomNumber = getRandom(randomUniform, seed, -0.5, 0.5);
          break;
        }

      case 'normal':
        {
          generateRandomNumber = getRandom(randomNormal, seed);
          break;
        }

      default:
        throw new Error(`Unknown distribution ${options.distribution}`);
    }

    if (!percent) return data;
    let ys = data.y;
    let factor = percent * findMax(ys) / 100;

    for (let i = 0; i < ys.length; i++) {
      ys[i] += generateRandomNumber() * factor;
    }

    return data;
  }

  function getRandom(func, seed, ...args) {
    return typeof seed === 'number' ? func.source(new XSadd(seed).random)(...args) : func(...args);
  }

  function findMax(array) {
    let max = Number.MIN_VALUE;

    for (let item of array) {
      if (item > max) max = item;
    }

    return max;
  }

  class SpectrumGenerator$1 {
    /**
     *
     * @param {object} [options={}]
     * @param {number} [options.from=0]
     * @param {number} [options.to=0]
     * @param {function} [options.nbPoints=10001]
     * @param {number} [options.factor] default value depends of the shape in order to cover 99.99% of the surface
     * @param {object} [options.shape={kind:'gaussian'}]
     * @param {string} [options.shape.kind] kind of shape, gaussian, lorentzian or pseudovoigt
     * @param {object} [options.shape.options] options for the shape (like `mu` for pseudovoigt)
     */
    constructor(options = {}) {
      options = Object.assign({}, {
        from: 0,
        to: 1000,
        nbPoints: 10001,
        peakWidthFct: () => 5,
        shape: {
          kind: 'gaussian'
        }
      }, options);
      this.from = options.from;
      this.to = options.to;
      this.nbPoints = options.nbPoints;
      this.interval = (this.to - this.from) / (this.nbPoints - 1);
      this.peakWidthFct = options.peakWidthFct;
      this.maxPeakHeight = Number.MIN_SAFE_INTEGER;
      let shapeGenerator = getShapeGenerator(options.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 (typeof this.peakWidthFct !== 'function') {
        throw new TypeError('peakWidthFct option must be a function');
      }

      this.reset();
    }

    addPeaks(peaks, options) {
      if (!Array.isArray(peaks) && (typeof peaks !== 'object' || peaks.x === undefined || peaks.y === undefined || !Array.isArray(peaks.x) || !Array.isArray(peaks.y) || peaks.x.length !== peaks.y.length)) {
        throw new TypeError('peaks must be an array or an object containing x[] and y[]');
      }

      if (Array.isArray(peaks)) {
        for (const peak of peaks) {
          this.addPeak(peak, options);
        }
      } else {
        for (let i = 0; i < peaks.x.length; i++) {
          this.addPeak([peaks.x[i], peaks.y[i]], options);
        }
      }

      return this;
    }
    /**
     *
     * @param {[x,y]|[x,y,w]|{x,y,width}} [peak]
     * @param {*} options
     */


    addPeak(peak, options = {}) {
      if (typeof peak !== 'object' || peak.length !== 2 && peak.length !== 3 && (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 peakWidth;
      let peakShapeOptions;

      if (Array.isArray(peak)) {
        [xPosition, intensity, peakWidth, peakShapeOptions] = peak;
      } else {
        xPosition = peak.x;
        intensity = peak.y;
        peakWidth = peak.width;
        peakShapeOptions = peak.shape;
      }

      if (intensity > this.maxPeakHeight) this.maxPeakHeight = intensity;
      let {
        width = peakWidth === undefined ? this.peakWidthFct(xPosition) : peakWidth,
        widthLeft,
        widthRight,
        shape: shapeOptions = {}
      } = options;

      if (peakShapeOptions) {
        shapeOptions = { ...shapeOptions,
          ...peakShapeOptions
        };
      }

      let shapeGenerator = shapeOptions ? getShapeGenerator(shapeOptions) : this.shape;
      if (!widthLeft) widthLeft = width;
      if (!widthRight) widthRight = width;
      let factor = options.factor === undefined ? shapeGenerator.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

      shapeGenerator.setFWHM(widthLeft);

      for (let index = firstPoint; index < Math.max(middlePoint, 0); index++) {
        this.data.y[index] += intensity * shapeGenerator.fct(this.data.x[index] - xPosition);
      } // we calculate the right part of the gaussian


      shapeGenerator.setFWHM(widthRight);

      for (let index = Math.min(middlePoint, lastPoint); index <= lastPoint; index++) {
        this.data.y[index] += intensity * shapeGenerator.fct(this.data.x[index] - xPosition);
      }

      return this;
    }

    addBaseline(baselineFct) {
      addBaseline(this.data, baselineFct);
      return this;
    }

    addNoise(percent, options) {
      addNoise(this.data, percent, options);
      return this;
    }

    getSpectrum(options = {}) {
      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;
      }
    }

    reset() {
      const spectrum = this.data = {
        x: new Float64Array(this.nbPoints),
        y: new Float64Array(this.nbPoints)
      };

      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 generateSpectrum(peaks, options = {}) {
    const generator = new SpectrumGenerator$1(options);
    generator.addPeaks(peaks, options);
    if (options.baseline) generator.addBaseline(options.baseline);
    if (options.noise) generator.addNoise(options.noise.percent, options.noise);
    return generator.getSpectrum({
      threshold: options.threshold
    });
  }

  var src$6 = /*#__PURE__*/Object.freeze({
    __proto__: null,
    SpectrumGenerator: SpectrumGenerator$1,
    generateSpectrum: generateSpectrum
  });

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

  const {
    ELECTRON_MASS: ELECTRON_MASS$2
  } = constants$3;

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

  const getMsem = getMsem$1;
  /**
   * Returns an object containing:
   * {ms: {em, charge, ionization}, ionization: {}}
   * We return the ionization in order to know which one has been selected
   */

  var getMsInfo$2 = function getMsInfo(entry, options = {}) {
    const {
      allowNeutralMolecules,
      ionization = {
        mf: '',
        em: 0,
        charge: 0
      },
      forceIonization = false,
      targetMass
    } = options;
    let realIonization = ionization;

    if (!forceIonization && entry.ionization && entry.ionization.mf !== '') {
      realIonization = entry.ionization;
    }

    let ms = {
      ionization: realIonization.mf,
      em: 0,
      charge: entry.charge + realIonization.charge
    };

    if (ms.charge !== 0) {
      ms.em = 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 processRange$2(string, comment, options = {}) {
    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(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(mfs, currents, comment));
    return results;
  }

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

  var processRange_1 = processRange$2;

  const MF$2 = MF_1;
  const processRange$1 = processRange_1;

  var preprocessIonizations$2 = function preprocessIonizations(ionizationsString = '') {
    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$1(ionization);

      for (let part of parts) {
        let info = new MF$2(part).getInfo();
        results.push({
          mf: part,
          em: info.monoisotopicMass,
          charge: info.charge
        });
      }
    }

    return results;
  };

  var sortX = function sortX() {
    this.ySorted = false;
    if (this.xSorted) return this;
    this.array.sort((a, b) => a.x - b.x);
    this.xSorted = true;
    return this;
  };

  var sortY = function sortY() {
    this.xSorted = false;
    if (this.ySorted) return this;
    this.array.sort((a, b) => b.y - a.y);
    this.ySorted = true;
    return this;
  };

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


  var join = function joinX(threshold = Number.EPSILON) {
    // when we join we will use the center of mass
    let result = [];
    this.sortX();
    let current = {
      x: Number.MIN_SAFE_INTEGER,
      y: 0
    };

    for (let item of this.array) {
      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
        };
        result.push(current);
      }
    }

    this.array = result;
    this.ySorted = false;
    return this;
  };

  var topY = function topY(limit) {
    if (!limit) return this;
    if (this.array.length <= limit) return this;
    this.sortY();
    this.array.splice(limit);
    return this;
  };

  /**
   * Sum of Y to 1
   */


  var maxToOne = function 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;
  };

  var multiply = function multiply(b, options = {}) {
    const {
      minY = 1e-8,
      maxLines = 5000,
      deltaX = 1e-2
    } = options;
    const result = new this.constructor();
    this.sortY();
    b.sortY();

    for (let entryA of this.array) {
      for (let entryB of b.array) {
        let y = entryA.y * entryB.y;
        if (y > minY) result.push(entryA.x + entryB.x, y);

        if (result.length > maxLines) {
          result.join(deltaX);
          result.topY(maxLines / 2);
        }
      }
    }

    result.join(deltaX);
    result.topY(maxLines / 2);
    this.move(result);
    return this;
  };

  var square = function square(options = {}) {
    return this.multiply(this, options);
  };

  var power = function power(p, options = {}) {
    if (p <= 0) throw new Error('power must be larger than 0');
    if (p === 1) return this;

    if (p === 2) {
      return this.square();
    }

    p--;
    let base = this.copy(); // linear time

    while (p !== 0) {
      if ((p & 1) !== 0) {
        this.multiply(base, options); // executed <= log2(p) times
      }

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

    return this;
  };

  /**
   * Sum of Y to 1
   */


  var normalize$1 = function normalize() {
    let sum = 0;

    for (let item of this.array) {
      sum += item.y;
    }

    for (let item of this.array) {
      item.y /= sum;
    }

    return this;
  };

  var closestPointX = function closestPointX(target) {
    this.sortX();
    let low = 0;
    let high = this.array.length - 1;
    let middle = 0;

    while (high - low > 1) {
      middle = low + (high - low >> 1);

      if (this.array[middle].x < target) {
        low = middle;
      } else if (this.array[middle].x > target) {
        high = middle;
      } else {
        return this.array[middle];
      }
    }

    if (low < this.array.length - 1) {
      if (Math.abs(target - this.array[low].x) < Math.abs(this.array[low + 1].x - target)) {
        return this.array[low];
      } else {
        return this.array[low + 1];
      }
    } else {
      return this.array[low];
    }
  };

  class Distribution$1 {
    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];
    }

  }

  Distribution$1.prototype.multiplyY = function multiplyY(value) {
    this.array.forEach(item => item.y *= value);
  };

  Distribution$1.prototype.setArray = function setArray(array) {
    this.array = array;
    this.xSorted = false;
    this.ySorted = false;
  };

  Distribution$1.prototype.move = function move(other) {
    this.xSorted = other.xSorted;
    this.ySorted = other.ySorted;
    this.array = other.array;
  };

  Distribution$1.prototype.copy = function copy() {
    let distCopy = new this.constructor();
    distCopy.xSorted = this.xSorted;
    distCopy.ySorted = this.ySorted;
    distCopy.array = JSON.parse(JSON.stringify(this.array));
    return distCopy;
  };

  Distribution$1.prototype.push = function push(x, y) {
    this.array.push({
      x,
      y
    });
    this.xSorted = false;
    this.ySorted = false;
  };
  /**
   * Appen another distribution to the current distribution
   * @param {*} distribution
   */


  Distribution$1.prototype.append = function append(distribution) {
    for (let item of distribution.array) {
      this.array.push(item);
    }

    this.xSorted = false;
    this.ySorted = false;
  };

  Distribution$1.prototype.sortX = sortX;
  Distribution$1.prototype.sortY = sortY;
  Distribution$1.prototype.join = join;
  Distribution$1.prototype.topY = topY;
  Distribution$1.prototype.maxToOne = maxToOne;
  Distribution$1.prototype.multiply = multiply;
  Distribution$1.prototype.square = square;
  Distribution$1.prototype.power = power;
  Distribution$1.prototype.normalize = normalize$1;
  Distribution$1.prototype.closestPointX = closestPointX;
  var Distribution_1 = Distribution$1;

  const ELECTRON_MASS$1 = src$9.ELECTRON_MASS;
  const SpectrumGenerator = require$$1$1.SpectrumGenerator;
  const MF$1 = src$7.MF;
  const getMsInfo$1 = getMsInfo$2;
  const preprocessIonizations$1 = preprocessIonizations$2;
  const Distribution = Distribution_1;
  /**
   * An object containing two arrays
   * @typedef {object} XY
   * @property {Array<number>} x - The x array
   * @property {Array<number>} y - The y array
   */

  class IsotopicDistribution$2 {
    /**
     * 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, options = {}) {
      if (Array.isArray(value)) {
        this.parts = JSON.parse(JSON.stringify(value));

        for (let part of this.parts) {
          part.confidence = 0;
          part.isotopesInfo = new MF$1(`${part.mf}(${part.ionization.mf})`).getIsotopesInfo();
        }
      } else {
        let mf = new MF$1(value, {
          ensureCase: options.ensureCase
        });
        let mfInfo = mf.getInfo();
        let ionizations = preprocessIonizations$1(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$1(`${part.mf}(${ionization.mf})`).getIsotopesInfo();
            part.confidence = 0;
            let msInfo = getMsInfo$1(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 < 1e-8) this.fwhm = 1e-8;
      this.minY = options.minY === undefined ? 1e-8 : 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
        }]);
        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 [];

            if (isotope.number > 0) {
              let isotopeDistribution = new Distribution(isotope.distribution);
              isotopeDistribution.power(isotope.number, options);
              totalDistribution.multiply(isotopeDistribution, 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.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.join(this.fwhm);
      this.confidence /= this.parts.length;
      this.cachedDistribution = finalDistribution;
      return finalDistribution;
    }

    getCSV() {
      return this.getText({
        delimiter: ', '
      });
    }

    getTSV() {
      return this.getText({
        delimiter: '\t'
      });
    }

    getTable(options = {}) {
      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(options = {}) {
      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 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 2 properties: x:[] and y:[]
     */


    getXY(options = {}) {
      const {
        maxValue = 100,
        sumValue
      } = options;
      let points = this.getDistribution().array;
      if (points.length === 0) return [];
      let factor = 1;

      if (sumValue) {
        let sumY = this.getSumY(points);
        factor = sumY / sumValue;
      } else if (maxValue) {
        let maxY = this.getMaxY(points);
        factor = maxY / maxValue;
      }

      return {
        x: points.map(a => a.x),
        y: points.map(a => a.y / factor)
      };
    }
    /**
     * 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(options = {}) {
      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;
    }

  }

  var IsotopicDistribution_1 = IsotopicDistribution$2;

  const IsotopicDistribution = IsotopicDistribution_1;
  var src$5 = IsotopicDistribution;
  var IsotopicDistribution$1 = src$5;

  /**
   * @param {object}   [entry={}}]
   * @param {object}   [options={}}]
   * @param {number}   [options.min=-Infinity] - Minimal unsaturation
   * @param {number}   [options.max=+Infinity] - Maximal unsaturation
   * @param {number}   [options.onlyInteger=false] - Integer unsaturation
   * @param {number}   [options.onlyNonInteger=false] - Non integer unsaturation
   * @return {boolean}
   */


  var unsaturationMatcher = function unsaturationMatcher(entry, options = {}) {
    const {
      min = Number.MIN_SAFE_INTEGER,
      max = Number.MAX_SAFE_INTEGER,
      onlyInteger,
      onlyNonInteger
    } = options;

    if (entry.unsaturation !== undefined) {
      if (entry.unsaturation < min || entry.unsaturation > max) return false;
      if (onlyInteger && !Number.isInteger(entry.unsaturation)) return false;
      if (onlyNonInteger && Number.isInteger(entry.unsaturation)) return false;
    }

    return true;
  };

  /**
   * 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 {object}   [options.unsaturation={}}]
   * @param {number}   [options.unsaturation.min=-Infinity] - Minimal unsaturation
   * @param {number}   [options.unsaturation.max=+Infinity] - Maximal unsaturation
   * @param {number}   [options.unsaturation.onlyInteger=false] - Integer unsaturation
   * @param {number}   [options.unsaturation.onlyNonInteger=false] - Non integer unsaturation
   * @param {object}   [options.atoms] - object of atom:{min, max}
   * @return {boolean}
   */


  var generalMatcher = function generalMatcher(entry, options = {}) {
    const {
      minMW = 0,
      maxMW = +Infinity,
      minEM = 0,
      maxEM = +Infinity,
      minCharge = Number.MIN_SAFE_INTEGER,
      maxCharge = Number.MAX_SAFE_INTEGER,
      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) {
      if (entry.charge < minCharge || entry.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 of Object.keys(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;
  };

  /**
   * This function returns an array with absolute values
   * @param {Array<Number>} array
   * @return {Number}
   */
  function xAbsolute(array) {
    let tmpArray = array.slice();

    for (let i = 0; i < tmpArray.length; i++) {
      if (tmpArray[i] < 0) tmpArray[i] *= -1;
    }

    return tmpArray;
  }

  /**
   * This function calculates the median after taking the reimAbsolute values of the points
   * @param {Array<Number>} array - the array that will be rotated
   * @return {Number}
   */

  function xAbsoluteMedian(array) {
    return median(xAbsolute(array));
  }

  /**
   * This function xAdd the first array by the second array or a constant value to each element of the first array
   * @param {Array<Number>} array1 - the array that will be rotated
   * @param {Array|Number} array2
   * @return {Array}
   */

  function xAdd(array1, array2) {
    let isConstant = false;
    let constant;

    if (isAnyArray(array2)) {
      if (array1.length !== array2.length) {
        throw new Error('sub: size of array1 and array2 must be identical');
      }
    } else {
      isConstant = true;
      constant = Number(array2);
    }

    let array3 = new Array(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;
  }

  /**
   * This function xMultiply the first array by the second array or a constant value to each element of the first array
   * @param {Array} array1 - the array that will be rotated
   * @param {Array|Number} array2
   * @return {Float64Array}
   */

  function xMultiply(array1, array2) {
    let isConstant = false;
    let constant;

    if (isAnyArray(array2)) {
      if (array1.length !== array2.length) {
        throw new Error('sub: size of array1 and array2 must be identical');
      }
    } else {
      isConstant = true;
      constant = Number(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;
  }

  function xDotProduct(A, B) {
    let g = xMultiply(A, B);
    let result = 0;

    for (let i = 0; i < A.length; i++) {
      result += g[i];
    }

    return result;
  }

  /**
   * Calculates the cross-correlation between 2 vectors
   * @param {Array<Number>} [A] - fixed array
   * @param {Array<Number>} [B] - sweeping array
   * @param {object} [options={}]
   * @param {number} [options.tau=1] - sweep increment size (in number of points, min=1, max=A.length)
   * @param {number} [options.lag=A.length - 1] - scalar lag parameter
   */

  function xCrossCorrelation(A, B, options = {}) {
    let {
      tau = 1,
      lag = A.length - 1
    } = options;
    let result = new Float64Array(1 + 2 * lag / tau);

    if (A.length === B.length) {
      let n = B.length;
      let g = new Float64Array(2 * n);
      let q = new Float64Array(2 * n);

      for (let i = 0; i < n; i++) {
        q[n + i] = B[i];
      }

      for (let i = n * 2 - (tau - 1); i > 0; i -= tau) {
        let k = 0;

        for (let j = i; j < n * 2; j++) {
          g[k] = q[j];
          k++;
        }

        let w = [];

        for (let l = 0; l < n; l++) {
          w[l] = g[l];
        }

        result[(k - (n - lag)) / tau] = xDotProduct(A, w);
      }
    }

    return result;
  }

  /**
   * Calculates the auto-correlation of a vector
   * @param {Array<Number>} [A] - the array that will be fixed
   * @param {object} [options={}]
   * @param {number} [options.tau=1] - sweep increment size (in number of points, min=1, max=A.length)
   * @param {number} [options.lag=A.length - 1] - scalar lag parameter
   */

  function xAutoCorrelation(A, options = {}) {
    return xCrossCorrelation(A, A, options);
  }

  /**
   * This function xSubtract the first array by the second array or a constant value from each element of the first array
   * @param {Array<Number>} array1 - the array that will be rotated
   * @return {object}
   */
  function xBoxPlot(array) {
    array = array.slice(0).sort((a, b) => a - b);

    if (array.length < 5) {
      throw Error('xBoxPlot: can not calculate info if array contains less than 3 elements');
    }

    let info = {
      Q1: 0.0,
      Q2: 0.0,
      Q3: 0.0,
      min: array[0],
      max: array[array.length - 1]
    };
    let q1max, q3min;

    if (array.length % 2 === 1) {
      // odd
      let middle = (array.length - 1) / 2;
      info.Q2 = array[middle];
      q1max = middle - 1;
      q3min = middle + 1;
    } else {
      // even
      q3min = array.length / 2;
      q1max = q3min - 1;
      info.Q2 = (array[q1max] + array[q3min]) / 2;
    }

    if (q1max % 2 === 0) {
      info.Q1 = array[q1max / 2];
      info.Q3 = array[(array.length + q3min - 1) / 2];
    } else {
      info.Q1 = (array[(q1max + 1) / 2] + array[(q1max - 1) / 2]) / 2;
      let middleOver = (array.length + q3min) / 2;
      info.Q3 = (array[middleOver] + array[middleOver - 1]) / 2;
    }

    return info;
  }

  /**

  /**
   * Calculates the correlation between 2 vectors
   * https://en.wikipedia.org/wiki/Correlation_and_dependence
   *
   * @param {Array<Number>} [A] - the array that will be rotated
   * @param {Array<Number>} [B]
   * @return {Array}
   */
  function xCorrelation(A, B) {
    let n = A.length;
    let sumA = 0;
    let sumA2 = 0;
    let sumB = 0;
    let sumB2 = 0;
    let sumAB = 0;

    for (let i = 0; i < n; i++) {
      let a = A[i];
      let b = B[i];
      sumA += a;
      sumA2 += a ** 2;
      sumB += b;
      sumB2 += b ** 2;
      sumAB += a * b;
    }

    return (n * sumAB - sumA * sumB) / (Math.sqrt(n * sumA2 - sumA ** 2) * Math.sqrt(n * sumB2 - sumB ** 2));
  }

  /**
   * Calculate a new array of the same size that is the cumulative values
   * @param {Array<number>} isArray
   * @returns {Array<number}
   */

  function xCumulative(array) {
    if (!isAnyArray(array)) {
      throw new TypeError('input must be an array');
    }

    let newArray = new Float64Array(array.length);
    if (array.length < 1) return newArray;
    newArray[0] = array[0];

    for (let i = 1; i < array.length; i++) {
      newArray[i] = newArray[i - 1] + array[i];
    }

    return newArray;
  }

  /**
   * This function divide the first array by the second array or a constant value to each element of the first array
   * @param {Array<Number>} array1 - the array that will be rotated
   * @param {Array<Number>|Number} array2
   * @return {Array}
   */

  function xDivide(array1, array2) {
    let isConstant = false;
    let constant;

    if (isAnyArray(array2)) {
      if (array1.length !== array2.length) {
        throw new Error('sub: size of array1 and array2 must be identical');
      }
    } else {
      isConstant = true;
      constant = Number(array2);
    }

    let array3 = new Array(array1.length);

    if (isConstant) {
      for (let i = 0; i < array1.length; i++) {
        array3[i] = array1[i] / constant;
      }
    } else {
      for (let i = 0; i < array1.length; i++) {
        array3[i] = array1[i] / array2[i];
      }
    }

    return array3;
  }

  /**
   * Returns the closest index of a `target` in an ordered array
   * @param {array<Number>} array
   * @param {number} target
   */
  function xFindClosestIndex$1(array, target) {
    let low = 0;
    let high = array.length - 1;
    let middle = 0;

    while (high - low > 1) {
      middle = low + (high - low >> 1);

      if (array[middle] < target) {
        low = middle;
      } else if (array[middle] > target) {
        high = middle;
      } else {
        return middle;
      }
    }

    if (low < array.length - 1) {
      if (Math.abs(target - array[low]) < Math.abs(array[low + 1] - target)) {
        return low;
      } else {
        return low + 1;
      }
    } else {
      return low;
    }
  }

  /**
   * Returns an object with {fromIndex, toIndex} for a specific from / to
   * @param {array} x
   * @param {object} [options={}]
   * @param {number} [options.from] - First value for xyIntegration in the X scale
   * @param {number} [options.fromIndex=0] - First point for xyIntegration
   * @param {number} [options.to] - Last value for xyIntegration in the X scale
   * @param {number} [options.toIndex=x.length-1] - Last point for xyIntegration
   */

  function xGetFromToIndex(x, options = {}) {
    let {
      fromIndex,
      toIndex,
      from,
      to
    } = options;

    if (fromIndex === undefined) {
      if (from !== undefined) {
        fromIndex = xFindClosestIndex$1(x, from);
      } else {
        fromIndex = 0;
      }
    }

    if (toIndex === undefined) {
      if (to !== undefined) {
        toIndex = xFindClosestIndex$1(x, to);
      } else {
        toIndex = x.length - 1;
      }
    }

    if (fromIndex > toIndex) [fromIndex, toIndex] = [toIndex, fromIndex];
    return {
      fromIndex,
      toIndex
    };
  }

  /**
   *  Returns the targetIndex
   * @param {array} [x]
   * @param {object} [options={}]
   * @param {number} [options.target]
   * @param {number} [options.targetIndex=0]
   * @param {number}
   */

  function xGetTargetIndex(x, options = {}) {
    let {
      target,
      targetIndex
    } = options;

    if (targetIndex === undefined) {
      if (target !== undefined) {
        return xFindClosestIndex$1(x, target);
      } else {
        return 0;
      }
    }

    return targetIndex;
  }

  /**
   * Checks if input is valdi
   * @param {Array<number>} input

   */

  function xCheck(input) {
    if (!isAnyArray(input)) {
      throw new TypeError('input must be an array');
    }

    if (input.length === 0) {
      throw new TypeError('input must not be empty');
    }
  }
  function xCheckLengths(array1, array2) {
    if (array1.length !== array2.length) {
      throw new TypeError('Length of array1 and array2 must be identical');
    }
  }

  /**
   * Computes the maximal value of an array of values
   * @param {Array<number>} array
   * @param {object} [options={}]
   * @param {number} [options.fromIndex=0] - First point for xyIntegration
   * @param {number} [options.toIndex=x.length-1] - Last point for xyIntegration
   * @return {number}
   */

  function xMaxValue(array, options = {}) {
    xCheck(array);
    const {
      fromIndex = 0,
      toIndex = array.length - 1
    } = 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<number>} array
   * @param {object} [options={}]
   * @param {number} [options.fromIndex=0] - First point for xyIntegration
   * @param {number} [options.toIndex=x.length-1] - Last point for xyIntegration
   * @return {number}
   */

  function xMinValue(array, options = {}) {
    xCheck(array);
    const {
      fromIndex = 0,
      toIndex = array.length - 1
    } = options;
    let minValue = array[fromIndex];

    for (let i = fromIndex + 1; i <= toIndex; i++) {
      if (array[i] < minValue) {
        minValue = array[i];
      }
    }

    return minValue;
  }

  /**
   * Calculates an histogram of defined number of slots
   * @param {array} [array] Array containing values
   * @param {number} [options.nbSlots=256] Number of slots
   * @param {number} [options.min=minValue] Minimum value to calculate used to calculate slot size
   * @param {number} [options.max=maxValue] Maximal value to calculate used to calculate slot size
   * @param {number} [options.logBaseX] We can first apply a log on x axis
   * @param {number} [options.logBaseY] We can apply a log on the resulting histogram
   * @param {boolean} [options.absolute] Take the absolute value
   * @param {number} [options.centerX=true] Center the X value. We will enlarge the first and last values.
   * @param {DataXY} [options.histogram={x:[], y:[]}] Previously existing histogram to continue to fill
   * @return {DataXY} {x,y} of the histogram
   */

  function xHistogram(array, options = {}) {
    xCheck(array);
    let histogram = options.histogram;
    const {
      centerX = true,
      nbSlots = histogram === undefined ? 256 : histogram.x.length,
      logBaseX,
      logBaseY,
      absolute = false
    } = options;

    if (absolute) {
      array = xAbsolute(array);
    }

    if (logBaseX) {
      array = array.slice();
      const logOfBase = Math.log10(logBaseX);

      for (let i = 0; i < array.length; i++) {
        array[i] = Math.log10(array[i]) / logOfBase;
      }
    }

    const {
      min = xMinValue(array),
      max = xMaxValue(array)
    } = options;
    const slotSize = (max - min) / (nbSlots + Number.EPSILON);
    const y = histogram === undefined ? new Float64Array(nbSlots) : histogram.y;
    const x = histogram === undefined ? sequentialFill({
      from: min + (centerX ? slotSize / 2 : 0),
      to: max - (centerX ? slotSize / 2 : 0),
      size: nbSlots
    }) : histogram.x;

    for (let i = 0; i < array.length; i++) {
      const index = Math.max(Math.min((array[i] - min - Number.EPSILON) / slotSize >> 0, nbSlots - 1), 0);
      y[index]++;
    }

    if (logBaseY) {
      const logOfBase = Math.log10(logBaseY);

      for (let i = 0; i < y.length; i++) {
        y[i] = Math.log10(y[i] + 1) / logOfBase;
      }
    }

    return {
      x,
      y
    };
  }

  /**
   * Returns true if x is monotone
   * @param {Array} array
   * @return {boolean}
   */
  function xIsMonotone(array) {
    if (array.length <= 2) {
      return true;
    }

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

      return true;
    }

    if (array[0] < array[array.length - 1]) {
      for (let i = 0; i < array.length - 1; i++) {
        if (array[i] >= array[i + 1]) return false;
      }
    } else {
      for (let i = 0; i < array.length - 1; i++) {
        if (array[i] <= array[i + 1]) return false;
      }
    }

    return true;
  }

  /**
   * Computes the index of the maximum of the given values
   * @param {Array<number>} array
   * @return {number}
   */

  function xMaxIndex(array) {
    xCheck(array);
    let maxIndex = 0;

    for (let i = 1; i < array.length; i++) {
      if (array[i] > array[maxIndex]) {
        maxIndex = i;
      }
    }

    return maxIndex;
  }

  /**
   * Computes the maximal value of an array of values
   * @param {Array<number>} array
   * @param {object} [options={}]
   * @param {number} [options.fromIndex=0] - First point for xyIntegration
   * @param {number} [options.toIndex=x.length-1] - Last point for xyIntegration
   * @return {number}
   */

  function xMean(array, options = {}) {
    xCheck(array);
    const {
      fromIndex = 0,
      toIndex = array.length - 1
    } = options;
    let sumValue = array[fromIndex];

    for (let i = fromIndex + 1; i <= toIndex; i++) {
      sumValue += array[i];
    }

    return sumValue / (toIndex - fromIndex + 1);
  }

  /**
   * Computes the index of the minimum of the given values
   * @param {Array<number>} array
   * @return {number}
   */

  function xMinIndex(array) {
    xCheck(array);
    let minIndex = 0;

    for (let i = 1; i < array.length; i++) {
      if (array[i] < array[minIndex]) {
        minIndex = i;
      }
    }

    return minIndex;
  }

  /**
  import { xCheck } from './xCheck';
   * return min and max values of an array
   * @param {Array<number>} array
   * @returns {object} Object with 2 properties, min and max
   */

  function xMinMaxValues(array) {
    xCheck(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
    };
  }

  /* eslint-disable no-loss-of-precision */

  /*
  Adapted from: https://github.com/compute-io/erfcinv/blob/aa116e23883839359e310ad41a7c42f72815fc1e/lib/number.js

  The MIT License (MIT)

  Copyright (c) 2014-2015 The Compute.io Authors. All rights reserved.

  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files (the "Software"), to deal
  in the Software without restriction, including without limitation the rights
  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  copies of the Software, and to permit persons to whom the Software is
  furnished to do so, subject to the following conditions:

  The above copyright notice and this permission notice shall be included in all
  copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  SOFTWARE.


  Boost Software License - Version 1.0 - August 17th, 2003

  Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following:

  The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  */
  // Coefficients for erfcinv on [0, 0.5]:
  const Y1 = 8.91314744949340820313e-2;
  const P1 = [-5.38772965071242932965e-3, 8.22687874676915743155e-3, 2.19878681111168899165e-2, -3.65637971411762664006e-2, -1.26926147662974029034e-2, 3.34806625409744615033e-2, -8.36874819741736770379e-3, -5.08781949658280665617e-4];
  const Q1 = [8.86216390456424707504e-4, -2.33393759374190016776e-3, 7.95283687341571680018e-2, -5.27396382340099713954e-2, -7.1228902341542847553e-1, 6.62328840472002992063e-1, 1.56221558398423026363, -1.56574558234175846809, -9.70005043303290640362e-1, 1]; // Coefficients for erfcinv for 0.5 > 1-x >= 0:

  const Y2 = 2.249481201171875;
  const P2 = [-3.67192254707729348546, 2.11294655448340526258e1, 1.7445385985570866523e1, -4.46382324441786960818e1, -1.88510648058714251895e1, 1.76447298408374015486e1, 8.37050328343119927838, 1.05264680699391713268e-1, -2.02433508355938759655e-1];
  const Q2 = [1.72114765761200282724, -2.26436933413139721736e1, 1.08268667355460159008e1, 4.85609213108739935468e1, -2.01432634680485188801e1, -2.86608180499800029974e1, 3.9713437953343869095, 6.24264124854247537712, 1]; // Coefficients for erfcinv for sqrt( -log(1-x)):

  const Y3 = 8.07220458984375e-1;
  const P3 = [-6.81149956853776992068e-10, 2.85225331782217055858e-8, -6.79465575181126350155e-7, 2.14558995388805277169e-3, 2.90157910005329060432e-2, 1.42869534408157156766e-1, 3.37785538912035898924e-1, 3.87079738972604337464e-1, 1.17030156341995252019e-1, -1.63794047193317060787e-1, -1.31102781679951906451e-1];
  const Q3 = [1.105924229346489121e-2, 1.52264338295331783612e-1, 8.48854343457902036425e-1, 2.59301921623620271374, 4.77846592945843778382, 5.38168345707006855425, 3.46625407242567245975, 1];
  const Y4 = 9.3995571136474609375e-1;
  const P4 = [2.66339227425782031962e-12, -2.30404776911882601748e-10, 4.60469890584317994083e-6, 1.57544617424960554631e-4, 1.87123492819559223345e-3, 9.50804701325919603619e-3, 1.85573306514231072324e-2, -2.22426529213447927281e-3, -3.50353787183177984712e-2];
  const Q4 = [7.64675292302794483503e-5, 2.63861676657015992959e-3, 3.41589143670947727934e-2, 2.20091105764131249824e-1, 7.62059164553623404043e-1, 1.3653349817554063097, 1];
  const Y5 = 9.8362827301025390625e-1;
  const P5 = [9.9055709973310326855e-17, -2.81128735628831791805e-14, 4.62596163522878599135e-9, 4.49696789927706453732e-7, 1.49624783758342370182e-5, 2.09386317487588078668e-4, 1.05628862152492910091e-3, -1.12951438745580278863e-3, -1.67431005076633737133e-2];
  const Q5 = [2.82243172016108031869e-7, 2.75335474764726041141e-5, 9.64011807005165528527e-4, 1.60746087093676504695e-2, 1.38151865749083321638e-1, 5.91429344886417493481e-1, 1];

  function polyval(c, x) {
    let p = 0;

    for (const coef of c) {
      p = p * x + coef;
    }

    return p;
  }
  /**
   * Calculates a rational approximation.
   *
   * @private
   * @param {Number} x
   * @param {Number} v
   * @param {Array} P - array of polynomial coefficients
   * @param {Array} Q - array of polynomial coefficients
   * @param {Number} Y
   * @returns {Number} rational approximation
   */


  function calc(x, v, P, Q, Y) {
    const s = x - v;
    const r = polyval(P, s) / polyval(Q, s);
    return Y * x + r * x;
  }
  /**
   * Evaluates the complementary inverse error function for an input value.
   *
   * @private
   * @param {Number} x - input value
   * @returns {Number} evaluated complementary inverse error function
   */


  function erfcinv(x) {
    let sign = false;
    let val;
    let q;
    let g;
    let r; // [1] Special cases...
    // NaN:

    if (Number.isNaN(x)) {
      return NaN;
    } // x not on the interval: [0,2]


    if (x < 0 || x > 2) {
      throw new RangeError(`erfcinv()::invalid input argument. Value must be on the interval [0,2]. Value: \`${x}\`.`);
    }

    if (x === 0) {
      return Number.POSITIVE_INFINITY;
    }

    if (x === 2) {
      return Number.NEGATIVE_INFINITY;
    }

    if (x === 1) {
      return 0;
    } // [2] Get the sign and make use of `erfc` reflection formula: `erfc(-z)=2 - erfc(z)`...


    if (x > 1) {
      q = 2 - x;
      x = 1 - q;
      sign = true;
    } else {
      q = x;
      x = 1 - x;
    } // [3] |x| <= 0.5


    if (x <= 0.5) {
      g = x * (x + 10);
      r = polyval(P1, x) / polyval(Q1, x);
      val = g * Y1 + g * r;
      return sign ? -val : val;
    } // [4] 1-|x| >= 0.25


    if (q >= 0.25) {
      g = Math.sqrt(-2 * Math.log(q));
      q = q - 0.25;
      r = polyval(P2, q) / polyval(Q2, q);
      val = g / (Y2 + r);
      return sign ? -val : val;
    }

    q = Math.sqrt(-Math.log(q)); // [5] q < 3

    if (q < 3) {
      return calc(q, 1.125, P3, Q3, Y3);
    } // [6] q < 6


    if (q < 6) {
      return calc(q, 3, P4, Q4, Y4);
    } // Note that the smallest number in JavaScript is 5e-324. Math.sqrt( -Math.log( 5e-324 ) ) ~27.2844


    return calc(q, 6, P5, Q5, Y5); // Note that in the boost library, they are able to go to much smaller values, as 128 bit long doubles support ~1e-5000; something which JavaScript does not natively support.
  }

  function rayleighCdf(x, sigma = 1) {
    if (x < 0) {
      return 0;
    }

    return -Math.expm1(-Math.pow(x, 2) / (2 * Math.pow(sigma, 2)));
  }

  /**
   * Determine noise level by san plot methodology (https://doi.org/10.1002/mrc.4882)
   * @param {Array} data - real or magnitude spectra data.
   * @param {object} [options = {}]
   * @param {array} [options.mask] - boolean array to filter data, if the i-th element is true then the i-th element of the distribution will be ignored.
   * @param {number} [options.scaleFactor=1] - factor to scale the data input[i]*=scaleFactor.
   * @param {number} [options.cutOff] - percent of positive signal distribution where the noise level will be determined, if it is not defined the program calculate it.
   * @param {number} [options.factorStd=5] - factor times std to determine what will be marked as signals.
   * @param {boolean} [options.refine=true] - if true the noise level will be recalculated get out the signals using factorStd.
   * @param {boolean} [options.fixOffset=true] - If the baseline is correct, the midpoint of distribution should be zero. if true, the distribution will be centered.
   * @param {number} [options.logBaseY=2] - log scale to apply in the intensity axis in order to avoid big numbers.
   */

  function xNoiseSanPlot(data, options = {}) {
    const {
      mask,
      cutOff,
      refine = true,
      magnitudeMode = false,
      scaleFactor = 1,
      factorStd = 5,
      fixOffset = true
    } = options;
    let input;

    if (Array.isArray(mask) && mask.length === data.length) {
      input = new Float64Array(data.filter((_e, i) => !mask[i]));
    } else {
      input = new Float64Array(data);
    }

    if (scaleFactor > 1) {
      for (let i = 0; i < input.length; i++) {
        input[i] *= scaleFactor;
      }
    }

    input = input.sort().reverse();

    if (fixOffset && !magnitudeMode) {
      let medianIndex = Math.floor(input.length / 2);
      let median = 0.5 * (input[medianIndex] + input[medianIndex + 1]);

      for (let i = 0; i < input.length; i++) {
        input[i] -= median;
      }
    }

    let firstNegativeValueIndex = input[input.length - 1] >= 0 ? input.length : input.findIndex(e => e < 0);
    let lastPositiveValueIndex = firstNegativeValueIndex - 1;

    for (let i = lastPositiveValueIndex; i >= 0; i--) {
      if (input[i] > 0) {
        lastPositiveValueIndex = i;
        break;
      }
    }

    let signPositive = input.slice(0, lastPositiveValueIndex + 1);
    let signNegative = input.slice(firstNegativeValueIndex);
    let cutOffDist = cutOff || determineCutOff(signPositive, {
      magnitudeMode
    });
    let pIndex = Math.floor(signPositive.length * cutOffDist);
    let initialNoiseLevelPositive = signPositive[pIndex];
    let skyPoint = signPositive[0];
    let initialNoiseLevelNegative;

    if (signNegative.length > 0) {
      let nIndex = Math.floor(signNegative.length * (1 - cutOffDist));
      initialNoiseLevelNegative = -1 * signNegative[nIndex];
    } else {
      initialNoiseLevelNegative = 0;
    }

    let noiseLevelPositive = initialNoiseLevelPositive;
    let noiseLevelNegative = initialNoiseLevelNegative;
    let cloneSignPositive = signPositive.slice();
    let cloneSignNegative = signNegative.slice();
    let cutOffSignalsIndexPlus = 0;
    let cutOffSignalsIndexNeg = 2;

    if (refine) {
      let cutOffSignals = noiseLevelPositive * factorStd;
      cutOffSignalsIndexPlus = signPositive.findIndex(e => e < cutOffSignals);

      if (cutOffSignalsIndexPlus > -1) {
        cloneSignPositive = signPositive.slice(cutOffSignalsIndexPlus);
        noiseLevelPositive = cloneSignPositive[Math.floor(cloneSignPositive.length * cutOffDist)];
      }

      cutOffSignals = noiseLevelNegative * factorStd;
      cutOffSignalsIndexNeg = signNegative.findIndex(e => e < cutOffSignals);

      if (cutOffSignalsIndexNeg > -1) {
        cloneSignNegative = signNegative.slice(cutOffSignalsIndexNeg);
        noiseLevelNegative = cloneSignPositive[Math.floor(cloneSignNegative.length * (1 - cutOffDist))];
      }
    }

    let correctionFactor = -simpleNormInv(cutOffDist / 2, {
      magnitudeMode
    });
    initialNoiseLevelPositive = initialNoiseLevelPositive / correctionFactor;
    initialNoiseLevelNegative = initialNoiseLevelNegative / correctionFactor;
    let effectiveCutOffDist, refinedCorrectionFactor;

    if (refine && cutOffSignalsIndexPlus > -1) {
      effectiveCutOffDist = (cutOffDist * cloneSignPositive.length + cutOffSignalsIndexPlus) / (cloneSignPositive.length + cutOffSignalsIndexPlus);
      refinedCorrectionFactor = -1 * simpleNormInv(effectiveCutOffDist / 2, {
        magnitudeMode
      });
      noiseLevelPositive /= refinedCorrectionFactor;

      if (cutOffSignalsIndexNeg > -1) {
        effectiveCutOffDist = (cutOffDist * cloneSignNegative.length + cutOffSignalsIndexNeg) / (cloneSignNegative.length + cutOffSignalsIndexNeg);
        refinedCorrectionFactor = -1 * simpleNormInv(effectiveCutOffDist / 2, {
          magnitudeMode
        });

        if (noiseLevelNegative !== 0) {
          noiseLevelNegative /= refinedCorrectionFactor;
        }
      }
    } else {
      noiseLevelPositive /= correctionFactor;
      noiseLevelNegative /= correctionFactor;
    }

    return {
      positive: noiseLevelPositive,
      negative: noiseLevelNegative,
      snr: skyPoint / noiseLevelPositive,
      sanplot: generateSanPlot(input, {
        fromTo: {
          positive: {
            from: 0,
            to: lastPositiveValueIndex
          },
          negative: {
            from: firstNegativeValueIndex,
            to: input.length
          }
        }
      })
    };
  }

  function determineCutOff(signPositive, options = {}) {
    let {
      magnitudeMode = false,
      considerList = {
        from: 0.5,
        step: 0.1,
        to: 0.9
      }
    } = options; //generate a list of values for

    let cutOff = [];
    let indexMax = signPositive.length - 1;

    for (let i = 0.01; i <= 0.99; i += 0.01) {
      let index = Math.round(indexMax * i);
      let value = -signPositive[index] / simpleNormInv([i / 2], {
        magnitudeMode
      });
      cutOff.push([i, value]);
    }

    let minKi = Number.MAX_SAFE_INTEGER;
    let {
      from,
      to,
      step
    } = considerList;
    let delta = step / 2;
    let whereToCutStat = 0.5;

    for (let i = from; i <= to; i += step) {
      let floor = i - delta;
      let top = i + delta;
      let elementsOfCutOff = cutOff.filter(e => e[0] < top && e[0] > floor);
      let averageValue = elementsOfCutOff.reduce((a, b) => a + Math.abs(b[1]), 0);
      let kiSqrt = 0;

      for (let j = 0; j < elementsOfCutOff.length; j++) {
        kiSqrt += Math.pow(elementsOfCutOff[j][1] - averageValue, 2);
      }

      if (kiSqrt < minKi) {
        minKi = kiSqrt;
        whereToCutStat = i;
      }
    }

    return whereToCutStat;
  }

  function simpleNormInv(data, options = {}) {
    const {
      magnitudeMode = false
    } = options;
    if (!Array.isArray(data)) data = [data];
    let from = 0;
    let to = 2;
    let step = 0.01;
    let xTraining = createArray(from, to, step);
    let result = new Float64Array(data.length);
    let yTraining = new Float64Array(xTraining.length);

    if (magnitudeMode) {
      let factor = 1;

      for (let i = 0; i < yTraining.length; i++) {
        let finalInput = xTraining[i] * factor;
        yTraining[i] = 1 - rayleighCdf(finalInput);
      }

      let interp = new SplineInterpolator$1(xTraining, yTraining);

      for (let i = 0; i < result.length; i++) {
        let yValue = 2 * data[i];
        result[i] = -1 * interp.interpolate(yValue);
      }
    } else {
      for (let i = 0; i < result.length; i++) {
        result[i] = -1 * Math.SQRT2 * erfcinv(2 * data[i]);
      }
    }

    return result.length === 1 ? result[0] : result;
  }

  function createArray(from, to, step) {
    let result = new Array(Math.abs((from - to) / step + 1));

    for (let i = 0; i < result.length; i++) {
      result[i] = from + i * step;
    }

    return result;
  }

  function generateSanPlot(array, options = {}) {
    const {
      fromTo,
      logBaseY = 2
    } = options;
    let sanplot = {};

    for (let key in fromTo) {
      let {
        from,
        to
      } = fromTo[key];
      sanplot[key] = from !== to ? scale(array.slice(from, to), {
        logBaseY
      }) : {
        x: [],
        y: []
      };

      if (key === 'negative') {
        sanplot[key].y.reverse();
      }
    }

    return sanplot;
  }

  function scale(array, options = {}) {
    const {
      log10,
      abs
    } = Math;
    const {
      logBaseY
    } = options;

    if (logBaseY) {
      array = array.slice();
      const logOfBase = log10(logBaseY);

      for (let i = 0; i < array.length; i++) {
        array[i] = log10(abs(array[i])) / logOfBase;
      }
    }

    const xAxis = sequentialFill({
      from: 0,
      to: array.length - 1,
      size: array.length
    });
    return {
      x: xAxis,
      y: array
    };
  }

  /**
   * This function calculate the norm of a vector
   * @example xNorm([3, 4]) -> 5
   * @param {Array} array - the array that will be rotated
   * @return {number} calculated norm
   */
  function xNorm(array) {
    let result = 0;

    for (let i = 0; i < array.length; i++) {
      result += array[i] ** 2;
    }

    return Math.sqrt(result);
  }

  /**
   * Pareto scaling, which uses the square root of standard deviation as the scaling factor, circumvents the amplification of noise by retaining a small portion of magnitude information.
   * Noda, I. (2008). Scaling techniques to enhance two-dimensional correlation spectra. Journal of Molecular Structure, 883, 216-227.
   * DOI: 10.1016/j.molstruc.2007.12.026
   * @param {Array<Number>} array
   * @return {Array}
   */

  function xParetoNormalization(array) {
    xCheck(array);
    let result = [];
    const sqrtSD = Math.sqrt(standardDeviation(array));

    for (let item of array) {
      result.push(item / sqrtSD);
    }

    return result;
  }

  /**
   * This function pads an array
   * @param {Array} array - the array that will be padded
   * @param {object} [options={}]
   * @param {string} [options.algorithm=''] '', value, circular, duplicate
   * @param {number} [options.size=0] padding size before first element and after last element
   * @param {number} [options.value=0] value to use for padding (if algorithm='value')
   * @return {Array}
   */

  function xPadding(array, options = {}) {
    const {
      size = 0,
      value = 0,
      algorithm = ''
    } = options;
    xCheck(array);

    if (!algorithm) {
      if (array instanceof Float64Array) {
        return array.slice();
      } else {
        return Float64Array.from(array);
      }
    }

    let result = new Float64Array(array.length + size * 2);

    for (let i = 0; i < array.length; i++) {
      result[i + size] = array[i];
    }

    let fromEnd = size + array.length;
    let toEnd = 2 * size + array.length;

    switch (algorithm.toLowerCase()) {
      case 'value':
        for (let i = 0; i < size; i++) {
          result[i] = value;
        }

        for (let i = fromEnd; i < toEnd; i++) {
          result[i] = value;
        }

        break;

      case 'duplicate':
        for (let i = 0; i < size; i++) {
          result[i] = array[0];
        }

        for (let i = fromEnd; i < toEnd; i++) {
          result[i] = array[array.length - 1];
        }

        break;

      case 'circular':
        for (let i = 0; i < size; i++) {
          result[i] = array[(array.length - size % array.length + i) % array.length];
        }

        for (let i = 0; i < size; i++) {
          result[i + fromEnd] = array[i % array.length];
        }

        break;

      default:
        throw Error('xPadding: unknown algorithm');
    }

    return result;
  }

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

  /**
   * This function calculates a rolling average
   * @param {Array<Number>} array - the array that will be rotated
   * @param {function} fct callback function that from an array returns a value.
   * @param {object} [options={}]
   * @param {number} [options.window=5] rolling window
   * @param {string} [options.padding.size=0] none, value, circular, duplicate
   * @param {string} [options.padding.algorithm='value'] none, value, circular, duplicate
   * @param {number} [options.padding.value=0] value to use for padding (if algorithm='value')
   * @return {Array<Number>}
   */

  function xRolling(array, fct, options = {}) {
    xCheck(array);
    if (typeof fct !== 'function') throw Error('fct has to be a function');
    const {
      window = 5,
      padding = {}
    } = options;
    const {
      size = window - 1,
      algorithm,
      value
    } = padding;
    array = xPadding(array, {
      size,
      algorithm,
      value
    }); // ensure we get a copy and it is float64

    const newArray = [];

    for (let i = 0; i < array.length - window + 1; i++) {
      let subArray = new Float64Array(array.buffer, i * 8, window); // we will send a view to the original buffer

      newArray.push(fct(subArray));
    }

    return newArray;
  }

  /**
   * This function calculates a rolling average
   * @param {Array<Number>} array - the array that will be rotated
   * @param {object} [options={}]
   * @param {number} [options.window=5] rolling window
   * @param {string} [options.padding.size=window-1] none, value, circular, duplicate
   * @param {string} [options.padding.algorithm=''] none, value, circular, duplicate
   * @param {number} [options.padding.value=0] value to use for padding (if algorithm='value')
   * @return {Array<Number>}
   */

  function xRollingAverage(array, options = {}) {
    return xRolling(array, mean, options);
  }

  /**
   * This function calculates a rolling average
   * @param {Array<Number>} array - the array that will be rotated
   * @param {object} [options={}]
   * @param {number} [options.window=5] rolling window
   * @param {string} [options.padding.size=window-1] none, value, circular, duplicate
   * @param {string} [options.padding.algorithm=''] none, value, circular, duplicate
   * @param {number} [options.padding.value=0] value to use for padding (if algorithm='value')
   * @return {Array<Number>}
   */

  function xRollingMedian(array, options = {}) {
    return xRolling(array, median, options);
  }

  /**
   * This function calculates a minimum within a rolling window
   * @param {Array<Number>} array - the array that will be rotated
   * @param {object} [options={}]
   * @param {number} [options.window=5] rolling window
   * @param {string} [options.padding.size=window-1] none, value, circular, duplicate
   * @param {string} [options.padding.algorithm=''] none, value, circular, duplicate
   * @param {number} [options.padding.value=0] value to use for padding (if algorithm='value')
   * @return {Array<Number>}
   */

  function xRollingMin(array, options = {}) {
    return xRolling(array, min, options);
  }

  /**
   * This function calculates a maximum within a rolling window
   * @param {Array<Number>} array - the array that will be rotated
   * @param {object} [options={}]
   * @param {number} [options.window=5] rolling window
   * @param {string} [options.padding.size=window-1] none, value, circular, duplicate
   * @param {string} [options.padding.algorithm=''] none, value, circular, duplicate
   * @param {number} [options.padding.value=0] value to use for padding (if algorithm='value')
   * @return {Array<Number>}
   */

  function xRollingMax(array, options = {}) {
    return xRolling(array, max, options);
  }

  /**
   * This function xSubtract the first array by the second array or a constant value from each element of the first array
   * @param {Array} array1 - the array that will be rotated
   * @param {Array|Number} array2
   * @return {Array}
   */

  function xSubtract(array1, array2) {
    let isConstant = false;
    let constant;

    if (isAnyArray(array2)) {
      if (array1.length !== array2.length) {
        throw new Error('sub: size of array1 and array2 must be identical');
      }
    } else {
      isConstant = true;
      constant = Number(array2);
    }

    let array3 = new Array(array1.length);

    if (isConstant) {
      for (let i = 0; i < array1.length; i++) {
        array3[i] = array1[i] - constant;
      }
    } else {
      for (let i = 0; i < array1.length; i++) {
        array3[i] = array1[i] - array2[i];
      }
    }

    return array3;
  }

  /**
   * Calculate the sum of the values
   * @param {DataXY} [array={}] - Object that contains property x (an ordered increasing array) and y (an array)
   * @param {object} [options={}]
   * @param {number} [options.fromIndex=0] - First point for xSum
   * @param {number} [options.toIndex=x.length-1] - Last point for xSum
   * @return {number} xSum value on the specified range
   */

  function xSum(array, options = {}) {
    const {
      fromIndex = 0,
      toIndex = array.length - 1
    } = options;
    xCheck(array);
    let sumValue = array[fromIndex];

    for (let i = fromIndex + 1; i <= toIndex; i++) {
      sumValue += array[i];
    }

    return sumValue;
  }

  /**
   * This function calculates the mean absolute error
   * @param {Array<number>} array1 -
   * @param {Array<number>} array2
   * @return {Number}
   */

  function xMeanAbsoluteError(array1, array2) {
    xCheckLengths(array1, array2);
    let sum = 0;

    for (let i = 0; i < array1.length; i++) {
      sum += Math.abs(array1[i] - array2[i]);
    }

    return sum / array1.length;
  }

  /**
   * This function calculates the mean squared error
   * @param {Array<number>} array1 -
   * @param {Array<number>} array2
   * @return {Number}
   */

  function xMeanSquaredError(array1, array2) {
    xCheckLengths(array1, array2);
    let sum = 0;

    for (let i = 0; i < array1.length; i++) {
      sum += Math.pow(array1[i] - array2[i], 2);
    }

    return sum / array1.length;
  }

  /**
   * Calculates reimAbsolute value of a complex spectrum
   * @param {object} [reim] - An object of kind {re:[], im:[]}.
   * @return {Float64Array}
   */
  function reimAbsolute(data) {
    const length = data.re.length;
    const re = data.re;
    const im = data.im;
    const newArray = new Float64Array(length);

    for (let i = 0; i < length; i++) {
      newArray[i] = Math.sqrt(re[i] ** 2 + im[i] ** 2);
    }

    return newArray;
  }

  /**
   * Phase correction filter
   * @param {object} reim - An object of kind {re:[], im:[]}
   * @param {number} [phi0=0] - Angle in radians for zero order phase correction
   * @param {number} [phi1=0] - Angle in radians for first order phase correction
   * @return {object} returns a new object {re:[], im:[]}
   */
  function reimPhaseCorrection(data, phi0, phi1) {
    phi0 = Number.isFinite(phi0) ? phi0 : 0;
    phi1 = Number.isFinite(phi1) ? phi1 : 0;
    const re = data.re;
    const im = data.im;
    const length = data.re.length;
    const delta = phi1 / length;
    const alpha = 2 * Math.pow(Math.sin(delta / 2), 2);
    const beta = Math.sin(delta);
    let cosTheta = Math.cos(phi0);
    let sinTheta = Math.sin(phi0);
    const newRe = new Float64Array(length);
    const newIm = new Float64Array(length);

    for (let i = 0; i < length; i++) {
      newRe[i] = re[i] * cosTheta - im[i] * sinTheta;
      newIm[i] = re[i] * sinTheta + im[i] * cosTheta; // calculate angles i+1 from i

      let newCosTheta = cosTheta - (alpha * cosTheta + beta * sinTheta);
      let newSinTheta = sinTheta - (alpha * sinTheta - beta * cosTheta);
      cosTheta = newCosTheta;
      sinTheta = newSinTheta;
    }

    return {
      re: newRe,
      im: newIm
    };
  }

  /**
   * Implementation of the algorithm for automatic phase correction: A robust, general automatic phase
   * correction algorithm for high-resolution NMR data. 10.1002/mrc.4586
   * @param {object} data - { re, im } real and imaginary data.
   * @param {object} options -
   * @param {Number} options.minRegSize - min number of points to auto phase a region.
   * @param {Number} options.maxDistanceToJoin - max distance between regions (in number of points) to join two regions
   * @param {boolean} options.magnitudeMode - if true it uses magnitude spectrum.boolean
   * @param {Number} options.factorNoise - scale the cutoff like factorStd * noiseLevel.
   */

  const defaultOptions$3 = {
    minRegSize: 30,
    maxDistanceToJoin: 256,
    magnitudeMode: true,
    factorNoise: 3
  };
  function reimAutoPhaseCorrection(data, options = {}) {
    const {
      re,
      im
    } = data;
    const length = re.length;
    options = Object.assign(defaultOptions$3, options);
    const {
      magnitudeMode,
      minRegSize
    } = options;
    let magnitudeData = magnitudeMode ? reimAbsolute(data) : re;
    let ds = holoborodko(magnitudeData);
    let peaksDs = robustBaseLineRegionsDetection(ds, options);
    let peaksSp = robustBaseLineRegionsDetection(magnitudeData, options);
    let finalPeaks = new Array(length);

    for (let i = 0; i < length; i++) {
      finalPeaks[i] = peaksSp[i] & peaksDs[i];
    } // Once the regions are detected, we auto phase each of them separately.
    // TODO: This part can be put inside a function


    let i = -1;
    let x0 = 0;
    let res = [];

    while (i < length) {
      //phase first region
      let reTmp = [];
      let imTmp = []; //Look for the first 1 in the array

      while (!finalPeaks[++i] && i < length) {
        //TODO: Add some extra points(0.1 ppm) at rigth and left sides of the region.
        x0 = i;
      }

      for (; finalPeaks[i] && i < length; i++) {
        reTmp.push(re[i]);
        imTmp.push(im[i]);
        i++;
      }

      if (reTmp.length > minRegSize) {
        res.push(autoPhaseRegion(reTmp, imTmp, x0));
      }
    } // TODO: Still some corrections needed. In the paper they remove the outlayers interatively
    // until they can perform a regression witout bad points. Can someone help here?


    let [ph1, ph0] = weightedLinearRegression(res.map(r => r.x0 / length), res.map(r => r.ph0), res.map(r => r.area / 1e11));
    let phased = reimPhaseCorrection({
      re,
      im
    }, ph0 * Math.PI / 180, ph1 * Math.PI / 180);
    return {
      data: phased,
      ph0,
      ph1
    };
  }

  function autoPhaseRegion(re, im, x0) {
    let start = -180;
    let stop = 180;
    let nSteps = 6;
    let maxSteps = 5;
    let bestAng = 0;
    let minArea = Number.MAX_SAFE_INTEGER;

    while (maxSteps > 0) {
      let dAng = (stop - start) / (nSteps + 1);

      for (let i = start; i <= stop; i += dAng) {
        let phased = reimPhaseCorrection({
          re,
          im
        }, toRadians(i), 0);
        let negArea = getNegArea(phased.re);

        if (negArea < minArea) {
          [minArea, bestAng] = [negArea, i];
        }
      }

      start = bestAng - dAng;
      stop = bestAng + dAng;
      maxSteps--;
    } // Calculate the area for the best angle


    let phased = reimPhaseCorrection({
      re,
      im
    }, toRadians(bestAng), 0);
    let area = 0;
    let sumX = 0;

    for (let j = 0; j < re.length; j++) {
      area += phased.re[j];
      sumX += phased.re[j] * (j + x0);
    }

    return {
      ph0: bestAng,
      area,
      x0: sumX / area
    };
  }

  function holoborodko(s) {
    let dk = new Float64Array(s.length);

    for (let i = 5; i < s.length - 5; i++) {
      dk[i] = (42 * (s[i + 1] - s[i - 1]) + 48 * (s[i + 2] - s[i - 2]) + 27 * (s[i + 3] + s[i - 3]) + 8 * (s[i + 4] - s[i - 4]) + s[i + 5] - s[i - 5]) / 512;
    } //Fill the borders


    for (let i = 0; i < 5; i++) {
      dk[i] = dk[5];
      dk[s.length - i - 1] = dk[s.length - 6];
    }

    return dk;
  }

  function robustBaseLineRegionsDetection(s, options) {
    const {
      maxDistanceToJoin,
      magnitudeMode,
      factorNoise
    } = options;
    let mask = new Array(s.length);

    for (let i = 0; i < s.length; i++) {
      mask[i] = false;
    }

    let change = true;

    while (change) {
      let noiseLevel = xNoiseSanPlot(s, {
        magnitudeMode
      });
      let cutOff = factorNoise * noiseLevel.positive;
      change = false;

      for (let i = 0; i < s.length; i++) {
        if (Math.abs(s[i]) > cutOff && !mask[i]) {
          change = true;
          mask[i] = true;
        }
      }
    } // Clean up mask by merging peaks blocks, separated by just a few points(4??).


    let count = 0;
    let prev = 0;

    for (let i = 0; i < s.length; i++) {
      if (!mask[i]) {
        count++;
      } else {
        if (count < maxDistanceToJoin) {
          for (let j = 0; j <= count; j++) {
            mask[prev + j] = true;
          }
        }

        while (mask[++i] && i < s.length);

        prev = i;
        count = 0;
      }
    }

    return mask;
  }

  function weightedLinearRegression(x, y, w) {
    let sxtw = 0;
    let swx = 0;
    let sw = 0;
    let sxtwy = 0;
    let swy = 0;

    for (let i = 0; i < x.length; i++) {
      sxtw += x[i] * x[i] * w[i];
      swx += x[i] * w[i];
      sw += w[i];
      sxtwy += x[i] * w[i] * y[i];
      swy += w[i] * y[i];
    }
    /* Just to know what is the matrix system that we solve
     let Mx=[[sxtw, swx], [swx, sw]];
     let My=[[sxtwy], [swy]];
    */
    //Mx inverse


    let detMx = sxtw * sw - swx * swx;
    let inMx = [[sw / detMx, -swx / detMx], [-swx / detMx, sxtw / detMx]];
    return [inMx[0][0] * sxtwy + inMx[0][1] * swy, inMx[1][0] * sxtwy + inMx[1][1] * swy];
  }

  const toRadians = degree => degree * Math.PI / 180;

  const getNegArea = data => {
    let area = 0;

    for (let i = 0; i < data.length; i++) {
      if (data[i] < 0) area -= data[i];
    }

    return area;
  };

  function reimFFT(data, options = {}) {
    const {
      inverse = false,
      applyZeroShift = false
    } = options;
    let {
      re,
      im
    } = data;
    const size = re.length;
    const csize = size << 1;
    let complexArray = new Float64Array(csize);

    for (let i = 0; i < csize; i += 2) {
      complexArray[i] = re[i >>> 1];
      complexArray[i + 1] = im[i >>> 1];
    }

    let fft = new FFT$1(size);
    let output = new Float64Array(csize);

    if (inverse) {
      if (applyZeroShift) complexArray = zeroShift(complexArray, true);
      fft.inverseTransform(output, complexArray);
    } else {
      fft.transform(output, complexArray);
      if (applyZeroShift) output = zeroShift(output);
    }

    let newRe = new Float64Array(size);
    let newIm = new Float64Array(size);

    for (let i = 0; i < csize; i += 2) {
      newRe[i >>> 1] = output[i];
      newIm[i >>> 1] = output[i + 1];
    }

    return {
      re: newRe,
      im: newIm
    };
  }

  const zeroShift = (data, inverse) => {
    let middle = inverse ? Math.ceil(data.length / 2) : Math.floor(data.length / 2);
    return xRotate(data, middle);
  };

  /**
   * This function make a zero filling to re and im part.
   * @param {object} data Object of kind {x:[], re:[], im:[]}.
   * @param {number} totalLength - final number of points
   * @return {SD}
   */
  function xreimZeroFilling(data, totalLength) {
    let length = data.x.length;
    if (totalLength === 0 || length === totalLength) return data;

    if (length > totalLength) {
      return {
        x: data.x.slice(0, totalLength),
        re: data.re.slice(0, totalLength),
        im: data.im.slice(0, totalLength)
      };
    }

    const x = data.x;
    const re = data.re;
    const im = data.im;
    const newX = new Float64Array(totalLength);
    const newRE = new Float64Array(totalLength);
    const newIM = new Float64Array(totalLength);

    for (let i = 0; i < length; i++) {
      newX[i] = x[i];
      newRE[i] = re[i];
      newIM[i] = im[i];
    }

    const deltaX = (x[x.length - 1] - x[0]) / (length - 1);

    for (let i = length; i < totalLength; i++) {
      newX[i] = newX[i - 1] + deltaX;
    }

    return {
      x: newX,
      re: newRE,
      im: newIM
    };
  }

  /**
   * Sort object of array, x has to be monotone.
   * @param {object} data Object of kind {x:[], re:[], im:[]}.
   * @return {SD}
   */
  function xreimSortX(data) {
    const {
      x,
      re,
      im
    } = data;

    if (x.length !== re.length || x.length !== im.length) {
      throw TypeError('xreimSortX: length of x, re and im must be identical');
    }

    if (x.length < 2 || x[0] < x[1]) return data;
    return {
      x: x.slice(0).reverse(),
      re: re.slice(0).reverse(),
      im: im.slice(0).reverse()
    };
  }

  /**
   * Throw an error in no an object of x,y arrays
   * @param {DataXY} [data={}]
   */

  function xyCheck(data = {}) {
    if (!isAnyArray(data.x) || !isAnyArray(data.y)) {
      throw new Error('Data must be an object of x and y arrays');
    }

    if (data.x.length !== data.y.length) {
      throw new Error('The x and y arrays mush have the same length');
    }
  }

  /**
   * 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 {DataXY} [data={}] - Object that contains property x (an ordered increasing array) and y (an array)
   * @param {object} [options={}]
   * @param {number|function} [options.delta=1] The range in which the two x values of the spectra must be to be placed on the same line. It may also be a function that allows to change `delta` depending on the X values of the spectrum
   * @return {DataXY} An object with the xyIntegration function
   */

  function xyJoinX(data = {}, options = {}) {
    xyCheck(data);
    const {
      delta = 1
    } = options;
    const deltaIsFunction = typeof delta === 'function';
    const x = Array.from(data.x);
    const y = Array.from(data.y);

    if (x.length < 2) {
      return {
        x,
        y
      };
    }

    let position = 0;

    for (let i = 1; i < x.length; i++) {
      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
    };
  }

  function getSlots(spectra, options = {}) {
    const {
      delta = 1
    } = options;
    const deltaIsFunction = typeof delta === 'function';
    let possibleXs = Float64Array.from([].concat(...spectra.map(spectrum => spectrum.x))).sort();

    if (possibleXs.length < 1) {
      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 spectra
   * @param {Array<DataXY>} spectra
   * @param {object} [options={}]
   * @param {number|function} [options.delta=1] The range in which the two x values of the spectra must be to be placed on the same line. It may also be a function that allows to change `delta` depending on the X values of the spectrum
   * @returns {object} {x:[], ys[[]]}
   */

  function xyArrayAlign(spectra, options = {}) {
    const {
      delta = 1
    } = options; // we start by checking that the spectra don't have peaks too close and we simplify them

    spectra = spectra.map(spectrum => xyJoinX(spectrum, {
      delta
    }));
    const slots = getSlots(spectra, options);
    let x = Float64Array.from(slots.map(slot => slot.average));
    let ys = new Array(spectra.length).fill().map(() => new Float64Array(x.length));
    let positions = new Uint32Array(spectra.length);

    for (let i = 0; i < slots.length; i++) {
      let slot = slots[i];

      for (let j = 0; j < spectra.length; j++) {
        let spectrum = spectra[j];

        while (positions[j] < spectrum.x.length && spectrum.x[positions[j]] <= slot.to) {
          ys[j][i] += spectrum.y[positions[j]];
          positions[j]++;
        }
      }
    }

    return {
      x,
      ys
    };
  }

  /**
   * Merge DataXY
   * We have an array of DataXY and the goal is to merge all the values that are the closest possible
   * @param {Array<DataXY>} spectra
   * @param {object} [options={}]
   * @param {number|function} [options.delta=1] The range in which the two x values of the spectra must be to be placed on the same line. It may also be a function that allows to change `delta` depending on the X values of the spectrum
   */

  function xyArrayMerge(spectra, options = {}) {
    const {
      delta = 1
    } = options; // we start by checking that the spectra don't have peaks too close and we simplify them

    spectra = spectra.map(spectrum => xyJoinX(spectrum, {
      delta
    })); // at first we will calculate the X values (simple mean)

    let slots = getSlots(spectra, options);
    let x = Float64Array.from(slots.map(slot => slot.average));
    let y = new Float64Array(x.length);
    let positions = new Uint32Array(spectra.length);

    for (let i = 0; i < slots.length; i++) {
      let slot = slots[i];

      for (let j = 0; j < spectra.length; j++) {
        let spectrum = spectra[j];

        while (positions[j] < spectrum.x.length && spectrum.x[positions[j]] <= slot.to) {
          y[i] += spectrum.y[positions[j]];
          positions[j]++;
        }
      }
    }

    return {
      x,
      y
    };
  }

  /**
   * 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 {Array<DataXY>} spectra
   * @param {object} [options={}]
   * @param {number|function} [options.delta=1] The range in which the two x values of the spectra must be to be placed on the same line. It may also be a function that allows to change `delta` depending on the X values of the spectrum
   * @returns {DataXY}
   */
  function xyArrayWeightedMerge(spectra, options = {}) {
    let {
      delta = 1
    } = options;

    if (typeof delta === 'number') {
      let deltaNumber = delta;

      delta = () => deltaNumber;
    }

    spectra = spectra.filter(spectrum => spectrum.x.length > 0);
    if (spectra.length === 0) return {
      x: [],
      y: []
    };
    let x = [];
    let y = [];
    const positions = new Array(spectra.length).fill(0);
    const point = {
      x: 0,
      y: 0
    };
    nextValue(spectra, positions, point);
    let slot = {
      maxX: point.x + delta(point.x),
      sumY: point.y,
      sumXY: point.y * point.x
    };

    while (spectra.length !== 0) {
      nextValue(spectra, 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 (spectra.length === 0) {
        if (slot.sumY > 0) {
          x.push(slot.sumXY / slot.sumY);
          y.push(slot.sumY);
        }
      }
    }

    return {
      x,
      y
    };
  }

  function nextValue(spectra, positions, point) {
    let minIndex = 0;
    let minX = spectra[0].x[positions[0]];

    for (let i = 1; i < spectra.length; i++) {
      let currentX = spectra[i].x[positions[i]];

      if (currentX < minX) {
        minX = currentX;
        minIndex = i;
      }
    }

    point.x = minX;
    point.y = spectra[minIndex].y[positions[minIndex]];
    positions[minIndex]++;

    if (positions[minIndex] === spectra[minIndex].x.length) {
      positions.splice(minIndex, 1);
      spectra.splice(minIndex, 1);
    }
  }

  function getSlotsToFirst(spectra, options = {}) {
    const {
      delta = 1
    } = options;
    const deltaIsFunction = typeof delta === 'function';
    let firstXs = spectra[0].x;
    let slots = []; // we first create the slots based on the first spectrum

    for (let i = 0; i < firstXs.length; i++) {
      let currentDelta = deltaIsFunction ? delta(firstXs[i]) : delta;
      slots.push({
        from: firstXs[i] - currentDelta,
        to: firstXs[i] + currentDelta,
        value: firstXs[i]
      });
    }

    let otherXs = xyArrayWeightedMerge(spectra.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 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 specdtrum we will add them
   * @param {Array<DataXY>} spectra
   * @param {object} [options={}]
   * @param {number|function} [options.delta=1] The range in which the two x values of the spectra must be to be placed on the same line. It may also be a function that allows to change `delta` depending on the X values of the spectrum
   * @returns {object} {x:[], ys[[]]
   */

  function xyArrayAlignToFirst(spectra, options = {}) {
    const slots = getSlotsToFirst(spectra, options);
    let x = Float64Array.from(slots.map(slot => slot.value));
    let ys = new Array(spectra.length).fill().map(() => new Float64Array(x.length));
    let positions = new Uint32Array(spectra.length);

    for (let i = 0; i < slots.length; i++) {
      let slot = slots[i];

      for (let j = 0; j < spectra.length; j++) {
        let spectrum = spectra[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
    };
  }

  /**
   * xyAlign will align data of two spectra by verifying wether x values are in a certain range (`delta`).
   * The two spectra should not have two consecutive x values which difference is
   * smaller than `delta` to achieve good results!
   * @param {DataXY} data1 First spectrum data
   * @param {DataXY} data2 Second spectrum data
   * @param {object} [options={}]
   * @param {number|function} [options.delta=1] The range in which the two x values of the spectra must be to be placed on the same line. It may also be a function that allows to change `delta` depending on the X values of the spectrum
   * @param {boolean} [options.common=true] If `true`, only the data considered as common to both spectra is kept. If `false`, the data y arrays are completed with zeroes where no common values are found
   * @param {string} [options.x='x1'] Defines what x values should be kept (`x1` : spectrum 1 x values, `x2` spectrum 2 x values, `weighted`: weighted average of both spectra x values)
   */
  function xyAlign(data1, data2, options = {}) {
    const {
      delta = 1,
      common = true,
      x = 'x1'
    } = options;
    let result = {
      x: [],
      y1: [],
      y2: []
    };
    let i = 0;
    let j = 0;
    let length1 = data1.x.length;
    let length2 = data2.x.length;

    while (i < length1 && j < length2) {
      let maxDiff = 0;

      if (typeof delta === 'function') {
        let mean = (data1.x[i] + data2.x[j]) / 2; // is this a good thing to do?

        maxDiff = delta(mean);
      } else {
        maxDiff = delta;
      }

      let difference = data1.x[i] - data2.x[j];

      if (Math.abs(difference) > maxDiff) {
        if (difference > 0) {
          if (!common) {
            result.x.push(data2.x[j]);
            result.y1.push(0);
            result.y2.push(data2.y[j]);

            if (j === length2 - 1) {
              while (i < length1) {
                result.x.push(data1.x[i]);
                result.y1.push(data1.y[i]);
                result.y2.push(0);
                i++;
              }
            }
          } // console.log({ i, j }, result);


          j++;
        } else {
          if (!common) {
            result.x.push(data1.x[i]);
            result.y1.push(data1.y[i]);
            result.y2.push(0);

            if (i === length1 - 1) {
              while (j < length2) {
                result.x.push(data2.x[j]);
                result.y1.push(0);
                result.y2.push(data2.y[j]);
                j++;
              }
            }
          } // console.log({ i, j }, result);


          i++;
        }
      } else {
        let weightedX = (data1.x[i] * data1.y[i] + data2.x[j] * data2.y[j]) / (data1.y[i] + data2.y[j]);

        switch (x) {
          case 'x1':
            result.x.push(data1.x[i]);
            break;

          case 'x2':
            result.x.push(data2.x[j]);
            break;

          case 'weighted':
            result.x.push(weightedX);
            break;

          default:
            throw new Error(`Error: Unknown x option value: ${x}`);
        }

        result.y1.push(data1.y[i]);
        result.y2.push(data2.y[j]); // console.log({ i, j }, result);

        i++;
        j++;
      }
    }

    return result;
  }

  /**
   * Finds the max y value in a range and return a {x,y} point
   * @param {DataXY} [data={}] - Object that contains property x (an ordered increasing array) and y (an array)
   * @param {object} [options={}]
   * @param {number} [options.from] - First value for xyIntegration in the X scale
   * @param {number} [options.fromIndex=0] - First point for xyIntegration
   * @param {number} [options.to] - Last value for xyIntegration in the X scale
   * @param {number} [options.toIndex=x.length-1] - Last point for xyIntegration
   * @return {object}
   */

  function xyMaxYPoint(data = {}, options = {}) {
    xyCheck(data);
    const {
      x,
      y
    } = data;
    if (x.length < 2) return 0;
    const {
      fromIndex,
      toIndex
    } = xGetFromToIndex(x, options);
    let current = {
      x: x[fromIndex],
      y: y[fromIndex],
      index: fromIndex
    };

    for (let i = fromIndex; i <= toIndex; i++) {
      if (y[i] > current.y) current = {
        x: x[i],
        y: y[i],
        index: i
      };
    }

    return current;
  }

  /**
   * Cumulative Distribution Statistics
   * @param {DataXY} [data] array of points {x,y}
   * @returns {object} x0, x25, x50, x75, x100, mode (x for maxY)
   */

  const STEPS = [0.25, 0.5, 0.75];
  function xyCumulativeDistributionStatistics(data) {
    xyCheck(data);
    const {
      x,
      y
    } = data;

    if (x.length === 0) {
      throw new Error('xyCumulativeDistributionStatistics: Array length must be greater than 0');
    }

    const cumulativeSum = xCumulative(y);
    const maxY = max(cumulativeSum);

    for (let i = 0; i < cumulativeSum.length; i++) {
      cumulativeSum[i] /= maxY;
    }

    const result = {}; // need to find the x values closest to STEPS/100

    result.x0 = x[0];
    result.x100 = x[x.length - 1];
    let currentStep = 0;

    breakPoint: for (let i = 1; i < cumulativeSum.length; i++) {
      while (STEPS[currentStep] < cumulativeSum[i]) {
        result[`x${STEPS[currentStep] * 100}`] = x[i - 1] + (x[i] - x[i - 1]) * ((STEPS[currentStep] - cumulativeSum[i - 1]) / (cumulativeSum[i] - cumulativeSum[i - 1]));
        currentStep++;
        if (currentStep === STEPS.length) break breakPoint;
      }
    }

    result.xMode = xyMaxYPoint(data).x;
    let sumXY = 0;
    let sumY = 0;

    for (let i = 0; i < x.length; i++) {
      sumXY += x[i] * y[i];
      sumY += y[i];
    }

    result.xMean = sumXY / sumY;
    return result;
  }

  /**
   * Filters x,y values to allow strictly growing values in x axis.
   * @param {DataXY} [data={}] - Object that contains property x (an ordered increasing array) and y (an array)
   * @return {DataXY}
   */

  function xyEnsureGrowingX(data = {}) {
    xyCheck(data);
    const x = Array.from(data.x);
    const y = Array.from(data.y);
    let prevX = -Infinity;
    let ansX = [];
    let ansY = [];

    for (let index = 0; index < x.length; index++) {
      if (prevX < x[index]) {
        ansX.push(x[index]);
        ansY.push(y[index]);
        prevX = x[index];
      }
    }

    return {
      x: ansX,
      y: ansY
    };
  }

  /**
   * Normalize an array of zones:
   * - ensure than from < to
   * - merge overlapping zones
   * @param {Array<Zone>} [zones=[]]
   * @param {object} [options={}]
   * @param {number} [options.from=Number.MIN_VALUE]
   * @param {number} [options.to=Number.MAX_VALUE]
   */
  function zonesNormalize(zones = [], options = {}) {
    if (zones.length === 0) return [];
    zones = JSON.parse(JSON.stringify(zones)).map(zone => zone.from > zone.to ? {
      from: zone.to,
      to: zone.from
    } : zone);
    let {
      from = Number.NEGATIVE_INFINITY,
      to = Number.POSITIVE_INFINITY
    } = options;

    if (from > to) {
      [from, to] = [to, from];
    }

    zones = zones.sort((a, b) => {
      if (a.from !== b.from) return a.from - b.from;
      return a.to - b.to;
    });
    zones.forEach(zone => {
      if (from > zone.from) zone.from = from;
      if (to < zone.to) zone.to = to;
    });
    zones = zones.filter(zone => zone.from <= zone.to);
    if (zones.length === 0) return [];
    let currentZone = zones[0];
    let result = [currentZone];

    for (let zone of zones) {
      if (zone.from <= currentZone.to) {
        currentZone.to = zone.to;
      } else {
        currentZone = zone;
        result.push(currentZone);
      }
    }

    return result;
  }

  /**
   * xyExtract zones from a XY data
   * @param {DataXY} [data={}] - Object that contains property x (an ordered increasing array) and y (an array)
   * @param {object} [options={}]
   * @param {Array} [options.zones=[]]
   * @return {Array} Array of points
   */

  function xyExtract(data = {}, options = {}) {
    xyCheck(data);
    const {
      x,
      y
    } = data;
    let {
      zones
    } = options;
    zones = zonesNormalize(zones);
    if (!Array.isArray(zones) || zones.length === 0) return data;
    let newX = [];
    let newY = [];
    let currentZone = zones[0];
    let position = 0;

    loop: for (let i = 0; i < x.length; i++) {
      while (currentZone.to < x[i]) {
        position++;
        currentZone = zones[position];

        if (!currentZone) {
          i = x.length;
          break loop;
        }
      }

      if (x[i] >= currentZone.from) {
        newX.push(x[i]);
        newY.push(y[i]);
      }
    }

    return {
      x: newX,
      y: newY
    };
  }

  /**
   * Filter out all the points for which x <= 0. Useful to display log scale data
   * @param {DataXY} [data={}]
   * @return {{x:[],y:[]}} An object with the filtered data
   */

  function xyFilterXPositive(data = {}) {
    xyCheck(data);
    const {
      x,
      y
    } = data;
    const newX = [];
    const newY = [];

    for (let i = 0; i < x.length; i++) {
      if (x[i] > 0) {
        newX.push(x[i]);
        newY.push(y[i]);
      }
    }

    return {
      x: newX,
      y: newY
    };
  }

  /**
   * Returns the numberMaxPoints points with the bigger y.
   * @param {DataXY} data - Object that contains property x (an ordered increasing array) and y (an array)
   * @param {number} numberMaxPoints Number of points to keep
   * @returns {object} The points filtered to keep the `numberMaxPoints` most intense points of the input
   */

  function xyGetNMaxY(data, numberMaxPoints) {
    xyCheck(data);

    if (data.x.length <= numberMaxPoints) {
      return data;
    } else {
      let newX = new Array(numberMaxPoints);
      let newY = new Array(numberMaxPoints); // slice() is used to make a copy of the array, because sort() is IPM

      let threshold = data.y.slice().sort((a, b) => b - a)[numberMaxPoints - 1];
      let index = 0;

      for (let i = 0; i < data.x.length; i++) {
        if (data.y[i] >= threshold) {
          newX[index] = data.x[i];
          newY[index] = data.y[i];
          index++;
        }

        if (index === numberMaxPoints) {
          return {
            x: newX,
            y: newY
          };
        }
      }
    }
  }

  /**
   * Order object of array, x has to be monotone.
   * Ensure x is growing
   * @param {DataXY} data Object of kind {x:[], y:[]}.
   * @return {SD}
   */
  function xyGrowingX(data) {
    const {
      x,
      y
    } = data;

    if (x.length !== y.length) {
      throw TypeError('sortX: length of x and y must be identical');
    }

    if (x.length < 2 || x[0] < x[1]) return data;
    return {
      x: x.slice(0).reverse(),
      y: y.slice(0).reverse()
    };
  }

  /**
   * Generate a X / Y of the xyIntegral
   * @param {DataXY} [data={}] - Object that contains property x (an ordered increasing array) and y (an array)
   * @param {object} [options={}]
   * @param {number} [options.from] - First value for xyIntegration in the X scale
   * @param {number} [options.fromIndex=0] - First point for xyIntegration
   * @param {number} [options.to] - Last value for xyIntegration in the X scale
   * @param {number} [options.toIndex=x.length-1] - Last point for xyIntegration
   * @param {boolean} [options.reverse=false] - Integrate from the larger value to the smallest value
   * @return {{x:[],y:[]}} An object with the xyIntegration function
   */

  function xyIntegral(data = {}, options = {}) {
    const {
      reverse = false
    } = options;
    xyCheck(data);
    const {
      x,
      y
    } = data;
    if (x.length < 2) return 0;
    const {
      fromIndex,
      toIndex
    } = xGetFromToIndex(x, options);
    let xyIntegration = 0;
    let currentxyIntegral;

    if (reverse) {
      currentxyIntegral = {
        x: [x[toIndex]],
        y: [0]
      };

      for (let i = toIndex; i > fromIndex; i--) {
        xyIntegration += (x[i] - x[i - 1]) * (y[i - 1] + y[i]) / 2;
        currentxyIntegral.x.push(x[i - 1]);
        currentxyIntegral.y.push(xyIntegration);
      }

      currentxyIntegral.x.reverse();
      currentxyIntegral.y.reverse();
    } else {
      currentxyIntegral = {
        x: [x[fromIndex]],
        y: [0]
      };

      for (let i = fromIndex; i < toIndex; i++) {
        xyIntegration += (x[i + 1] - x[i]) * (y[i + 1] + y[i]) / 2;
        currentxyIntegral.x.push(x[i + 1]);
        currentxyIntegral.y.push(xyIntegration);
      }
    }

    return currentxyIntegral;
  }

  /**
   * Calculate integration
   * @param {DataXY} [data={}] - Object that contains property x (an ordered increasing array) and y (an array)
   * @param {object} [options={}]
   * @param {number} [options.from] - First value for xyIntegration in the X scale
   * @param {number} [options.fromIndex=0] - First point for xyIntegration
   * @param {number} [options.to] - Last value for xyIntegration in the X scale
   * @param {number} [options.toIndex=x.length-1] - Last point for xyIntegration
   * @return {number} xyIntegration value on the specified range
   */

  function xyIntegration(data = {}, options = {}) {
    xyCheck(data);
    const {
      x,
      y
    } = data;
    if (x.length < 2) return 0;
    const {
      fromIndex,
      toIndex
    } = xGetFromToIndex(x, options);
    let currentxyIntegration = 0;

    for (let i = fromIndex; i < toIndex; i++) {
      currentxyIntegration += (x[i + 1] - x[i]) * (y[i + 1] + y[i]) / 2;
    }

    return currentxyIntegration;
  }

  /**
   * Find the closest maximum going up hill
   * @param {DataXY} [data={}] - Object that contains property x (an ordered increasing array) and y (an array)
   * @param {object} [options={}]
   * @param {number} [options.target]
   * @param {number} [options.targetIndex=0]
   * @return {{x,y,xIndex}} An object with the x/y value
   */

  function xyMaxClosestYPoint(data, options = {}) {
    xyCheck(data);
    const {
      x,
      y
    } = data;
    let {
      target,
      targetIndex
    } = options;

    if (targetIndex === undefined) {
      if (target !== undefined) {
        targetIndex = xFindClosestIndex$1(x, target);
      } else {
        targetIndex = 0;
      }
    }

    let previousIndex = Number.MIN_SAFE_INTEGER;
    let currentIndex = targetIndex;
    let xyMaxY = y[targetIndex];

    while (currentIndex !== previousIndex) {
      previousIndex = currentIndex;

      if (currentIndex > 0 && y[currentIndex - 1] > xyMaxY) {
        currentIndex--;
      } else if (currentIndex < x.length - 1 && y[currentIndex + 1] > xyMaxY) {
        currentIndex++;
      }

      xyMaxY = y[currentIndex];
    }

    return {
      x: x[currentIndex],
      y: y[currentIndex],
      index: currentIndex
    };
  }

  /**
   * Finds the max value in a zone
   * @param {DataXY} [data={}] - Object that contains property x (an ordered increasing array) and y (an array)
   * @param {object} [options={}]
   * @param {number} [options.from] - First value for xyIntegration in the X scale
   * @param {number} [options.fromIndex=0] - First point for xyIntegration
   * @param {number} [options.to] - Last value for xyIntegration in the X scale
   * @param {number} [options.toIndex=x.length-1] - Last point for xyIntegration
   * @return {number} Max y on the specified range
   */

  function xyMaxY(data = {}, options = {}) {
    xyCheck(data);
    const {
      x,
      y
    } = data;
    if (x.length < 2) return 0;
    const {
      fromIndex,
      toIndex
    } = xGetFromToIndex(x, options);
    let currentxyMaxY = y[fromIndex];

    for (let i = fromIndex; i <= toIndex; i++) {
      if (y[i] > currentxyMaxY) currentxyMaxY = y[i];
    }

    return currentxyMaxY;
  }

  /**
   * Finds all the max values
   * If the values are equal the middle
   * of the equal part will be the position of the signal!
   *
   * @param {DataXY} [data={}] - Object that contains property x (an ordered increasing array) and y (an array)
   * @return {Array} Array of points
   */

  function xyMaximaY(data = {}) {
    xyCheck(data);
    const {
      x,
      y
    } = data;
    if (x.length < 3) return [];
    let maxima = [];
    let startEqualIndex = -1;

    for (let i = 1; i < x.length - 1; i++) {
      if (y[i - 1] < y[i] && y[i + 1] < y[i]) {
        maxima.push({
          x: x[i],
          y: y[i],
          index: i
        });
      } else if (y[i - 1] < y[i] && y[i + 1] === y[i]) {
        startEqualIndex = i;
      } else if (y[i - 1] === y[i] && y[i + 1] < y[i]) {
        let index = (i + startEqualIndex) / 2 >> 0;
        maxima.push({
          x: x[index],
          y: y[index],
          index
        });
      }
    }

    return maxima;
  }

  /* *
   * Finds the median x value for an object with properties x and y (arrays of the same length)
   * @param {object} data x should be sorted in increasing order
   * @returns {number} the median of x values
   */
  function xyMedian(data) {
    const {
      x,
      y
    } = data;
    let sumY = 0;
    let cumSumY = 0;
    let i;

    if (x.length === 0) {
      return NaN;
    }

    if (x.length === 1) {
      return x[0];
    }

    for (i = 0; i < y.length; i++) {
      sumY += y[i];
    }

    for (i = 0; i < y.length; i++) {
      cumSumY += y[i];

      if (cumSumY > sumY / 2) {
        return x[i];
      } else if (cumSumY === sumY / 2) {
        return 0.5 * (x[i] + x[i + 1]);
      }
    }
  }

  /**
   * Find the closest minimum going down hill
   * @param {DataXY} [data={}] - Object that contains property x (an ordered increasing array) and y (an array)
   * @param {object} [options={}]
   * @param {number} [options.target]
   * @param {number} [options.targetIndex=0]
   * @return {{x,y,xIndex}} An object with the x/y value
   */

  function xyMinClosestYPoint(data, options = {}) {
    xyCheck(data);
    const {
      x,
      y
    } = data;
    let {
      target,
      targetIndex
    } = options;

    if (targetIndex === undefined) {
      if (target !== undefined) {
        targetIndex = xFindClosestIndex$1(x, target);
      } else {
        targetIndex = 0;
      }
    }

    let previousIndex = Number.MIN_SAFE_INTEGER;
    let currentIndex = targetIndex;
    let minY = y[targetIndex];

    while (currentIndex !== previousIndex) {
      previousIndex = currentIndex;

      if (currentIndex > 0 && y[currentIndex - 1] < minY) {
        currentIndex--;
      } else if (currentIndex < x.length - 1 && y[currentIndex + 1] < minY) {
        currentIndex++;
      }

      minY = y[currentIndex];
    }

    return {
      x: x[currentIndex],
      y: y[currentIndex],
      index: currentIndex
    };
  }

  /**
   * Finds the max y value in a range and return a {x,y} point
   * @param {DataXY} [data={}] - Object that contains property x (an ordered increasing array) and y (an array)
   * @param {object} [options={}]
   * @param {number} [options.from] - First value for xyIntegration in the X scale
   * @param {number} [options.fromIndex=0] - First point for xyIntegration
   * @param {number} [options.to] - Last value for xyIntegration in the X scale
   * @param {number} [options.toIndex=x.length-1] - Last point for xyIntegration
   * @return {object}
   */

  function xyMinYPoint(data = {}, options = {}) {
    xyCheck(data);
    const {
      x,
      y
    } = data;
    if (x.length < 2) return 0;
    const {
      fromIndex,
      toIndex
    } = xGetFromToIndex(x, options);
    let current = {
      x: x[fromIndex],
      y: y[fromIndex],
      index: fromIndex
    };

    for (let i = fromIndex; i <= toIndex; i++) {
      if (y[i] < current.y) current = {
        x: x[i],
        y: y[i],
        index: i
      };
    }

    return current;
  }

  /**
   * Finds all the min values
   * If the values are equal the middle
   * of the equal part will be the position of the signal!
   *
   * @param {DataXY} [data={}] - Object that contains property x (an ordered increasing array) and y (an array)
   * @return {Array} Array of points
   */

  function xyMinimaY(data = {}) {
    xyCheck(data);
    const {
      x,
      y
    } = data;
    if (x.length < 3) return [];
    let maxima = [];
    let startEqualIndex = -1;

    for (let i = 1; i < x.length - 1; i++) {
      if (y[i - 1] > y[i] && y[i + 1] > y[i]) {
        maxima.push({
          x: x[i],
          y: y[i],
          index: i
        });
      } else if (y[i - 1] > y[i] && y[i + 1] === y[i]) {
        startEqualIndex = i;
      } else if (y[i - 1] === y[i] && y[i + 1] > y[i]) {
        let index = (i + startEqualIndex) / 2 >> 0;
        maxima.push({
          x: x[index],
          y: y[index],
          index
        });
      }
    }

    return maxima;
  }

  /**
   * Returns an information about a signal
   *
   * We expect ordered data and equidistant X axis
   * You can use the method helper if required:
   * ML.ArrayPoints.uniqueX
   * ML.ArrayPoints.sortX
   * ML.ArrayPoints.equallySpaced
   *
   * @param {object} [data={}] - Object that contains property x (an ordered increasing array) and y (an array)
   * @param {object} [options={}]
   * @param {number} [options.target]
   * @param {number} [options.targetIndex]
   * @return {object} Information about signal
   */

  function xyPeakInfo(data = {}, options = {}) {
    xyCheck(data);
    const {
      x,
      y
    } = data;
    if (x.length < 3) return undefined;
    let {
      targetIndex,
      target
    } = options;

    if (targetIndex === undefined) {
      if (target !== undefined) {
        targetIndex = xFindClosestIndex$1(x, target);
      }
    }

    if (targetIndex === undefined) {
      throw new Error('xyPeakInfo: need to specify target or targetIndex');
    }

    let i = targetIndex;
    let currentDiff = y[i] - y[i + 1];
    let multiplier = currentDiff < 0 ? -1 : 1;
    currentDiff *= multiplier;

    while (i < x.length - 1) {
      i++;
      let newDiff = (y[i] - y[i + 1]) * multiplier;
      if (newDiff < currentDiff) break;
      currentDiff = newDiff;
    }

    let after = {
      x: x[i],
      y: y[i]
    };
    i = targetIndex;
    currentDiff = (y[i] - y[i - 1]) * multiplier;

    while (i > 1) {
      i--;
      let newDiff = (y[i] - y[i - 1]) * multiplier;
      if (newDiff < currentDiff) break;
      currentDiff = newDiff;
    }

    let before = {
      x: x[i],
      y: y[i]
    };
    return {
      inflectionBefore: before,
      inflectionAfter: after,
      extrema: {
        x: x[targetIndex],
        y: y[targetIndex]
      },
      inflectionMiddle: {
        x: (before.x + after.x) / 2,
        y: (before.y + after.y) / 2
      },
      width: Math.abs(before.x - after.x)
    };
  }

  /**
   * Find the closest minimum going down hill
   * @param {object} [data={}] - Object that contains property x (an ordered increasing array) and y (an array)
   * @param {object} [options={}]
   * @param {number} [options.target]
   * @param {number} [options.targetIndex=0]
   * @return {{x,y,xIndex}} An object with the x/y value
   */

  function xyRealMaxYPoint(data, options = {}) {
    xyCheck(data);
    const {
      x,
      y
    } = data;
    const targetIndex = xGetTargetIndex(x, options); // interpolation to a sin() function

    if (y[targetIndex - 1] > 0 && y[targetIndex + 1] > 0 && y[targetIndex] >= y[targetIndex - 1] && y[targetIndex] >= y[targetIndex + 1]) {
      let alpha = 20 * Math.log10(y[targetIndex - 1]);
      let beta = 20 * Math.log10(y[targetIndex]);
      let gamma = 20 * Math.log10(y[targetIndex + 1]);
      let p = 0.5 * (alpha - gamma) / (alpha - 2 * beta + gamma);
      return {
        x: x[targetIndex] + (x[targetIndex] - x[targetIndex - 1]) * p,
        y: y[targetIndex] - 0.25 * (y[targetIndex - 1] - y[targetIndex + 1]) * p,
        index: targetIndex
      };
    } else {
      return {
        x: x[targetIndex],
        y: y[targetIndex],
        index: targetIndex
      };
    }
  }

  function xyRealMinYPoint(data, options = {}) {
    xyCheck(data);
    const {
      x,
      y
    } = data;
    const targetIndex = xGetTargetIndex(x, options); // interpolation to a sin() function

    if (y[targetIndex - 1] < 0 && y[targetIndex + 1] < 0 && y[targetIndex] <= y[targetIndex - 1] && y[targetIndex] <= y[targetIndex + 1]) {
      let alpha = 20 * Math.log10(-y[targetIndex - 1]);
      let beta = 20 * Math.log10(-y[targetIndex]);
      let gamma = 20 * Math.log10(-y[targetIndex + 1]);
      let p = 0.5 * (alpha - gamma) / (alpha - 2 * beta + gamma);
      return {
        x: x[targetIndex] + (x[targetIndex] - x[targetIndex - 1]) * p,
        y: y[targetIndex] - 0.25 * (y[targetIndex - 1] - y[targetIndex + 1]) * p,
        index: targetIndex
      };
    } else {
      return {
        x: x[targetIndex],
        y: y[targetIndex],
        index: targetIndex
      };
    }
  }

  /**
   * xyReduce the number of points while keeping visually the same noise. Practical to
   * display many spectra as SVG.
   * SHOULD NOT BE USED FOR DATA PROCESSING !!!
   * You should rather use ml-xy-equally-spaced to make further processing
   * @param {DataXY} [data={}] - Object that contains property x (an ordered increasing array) and y (an array)
   * @param {object} [options={}]
   * @param {number} [options.from=x[0]]
   * @param {number} [options.to=x[x.length-1]]
   * @param {number} [options.nbPoints=4001] Number of points
   * @param {number} [options.zones=[]] Array of zones to keep (from/to object)
   * @param {number} [options.optimize=false] If optimize we may have less than nbPoints at the end
   */

  function xyReduce(data, options = {}) {
    xyCheck(data);
    const {
      x,
      y
    } = data;
    let {
      from = x[0],
      to = x[x.length - 1],
      nbPoints = 4001,
      optimize = false,
      zones = []
    } = options;
    zones = zonesNormalize(zones, {
      from,
      to
    });
    if (zones.length === 0) zones = [{
      from,
      to
    }]; // we take everything
    // for each zone we should know the first index, the last index and the number of points

    let totalPoints = 0;

    for (let zone of zones) {
      zone.fromIndex = xFindClosestIndex$1(x, zone.from);
      zone.toIndex = xFindClosestIndex$1(x, zone.to);

      if (zone.fromIndex > 0 && x[zone.fromIndex] > zone.from) {
        zone.fromIndex--;
      }

      if (zone.toIndex < x.length - 1 && x[zone.toIndex] < zone.to) {
        zone.toIndex++;
      }

      zone.nbPoints = zone.toIndex - zone.fromIndex + 1;
      totalPoints += zone.nbPoints;
    } // we calculate the number of points per zone that we should keep


    if (totalPoints > nbPoints) {
      // need to xyReduce number of points
      let ratio = nbPoints / totalPoints;
      let currentTotal = 0;

      for (let i = 0; i < zones.length - 1; i++) {
        const zone = zones[i];
        zone.nbPoints = Math.round(zone.nbPoints * ratio);
        currentTotal += zone.nbPoints;
      }

      zones[zones.length - 1].nbPoints = nbPoints - currentTotal;
    } else {
      let newX = new Float64Array(totalPoints);
      let newY = new Float64Array(totalPoints);
      let index = 0;

      for (let zone of zones) {
        for (let i = zone.fromIndex; i < zone.toIndex + 1; i++) {
          newX[index] = x[i];
          newY[index] = y[i];
          index++;
        }
      }

      return {
        x: newX,
        y: newY
      };
    }

    let newX = [];
    let newY = [];

    for (let zone of zones) {
      if (!zone.nbPoints) continue;
      appendFromTo(zone.fromIndex, zone.toIndex, zone.nbPoints);
    }

    return {
      x: newX,
      y: newY
    };

    function appendFromTo(fromIndex, toIndex, zoneNbPoints) {
      if (zoneNbPoints === 1) {
        newX.push(x[Math.round((toIndex - fromIndex) / 2)]);
        newY.push(y[Math.round((toIndex - fromIndex) / 2)]);
        return;
      }

      if (zoneNbPoints === 2) {
        newX.push(x[fromIndex], x[toIndex]);
        newY.push(y[fromIndex], y[toIndex]);
        return;
      }

      newX.push(x[fromIndex]);
      newY.push(y[fromIndex]);
      let minY = Number.MAX_VALUE;
      let xyMaxY = Number.MIN_VALUE;

      if (zoneNbPoints % 2 === 0) {
        zoneNbPoints = zoneNbPoints / 2 + 1;
      } else {
        zoneNbPoints = (zoneNbPoints - 1) / 2 + 1;
      } // we will need to make some kind of min / max because there are too many points
      // we will always keep the first point and the last point


      let slot = (x[toIndex] - x[fromIndex]) / (zoneNbPoints - 1);
      let currentX = x[fromIndex] + slot;
      let first = true;

      for (let i = fromIndex + 1; i <= toIndex; i++) {
        if (first) {
          minY = y[i];
          xyMaxY = y[i];
          first = false;
        } else {
          if (y[i] < minY) minY = y[i];
          if (y[i] > xyMaxY) xyMaxY = y[i];
        }

        if (x[i] >= currentX || i === toIndex) {
          if (optimize) {
            if (minY > newY[newX.length - 1]) ; else if (xyMaxY < newY[newX.length - 1]) {
              // we can skip the intermediate value
              xyMaxY = minY;
            } else {
              newX.push(currentX - slot / 2);
              newY.push(minY);
            }
          } else {
            newX.push(currentX - slot / 2);
            newY.push(minY);
          }

          newX.push(currentX);
          newY.push(xyMaxY);
          currentX += slot;
          first = true;
        }
      }
    }
  }

  /**
   * This function calculates a rolling average
   *
   * This methods will recalculate the x values by using xRollingAverage
   * @param {ArrayPoints} [points] array of points {x,y}
   * @param {object} [options={}]
   * @param {number} [options.window=5] rolling window
   * @param {function} [fct] callback function that from an array returns a value.
   * @param {string} [options.padding.size=0] none, value, circular, duplicate
   * @param {string} [options.padding.algorithm='value'] none, value, circular, duplicate
   * @param {number} [options.padding.value=0] value to use for padding (if algorithm='value')
   * @return {Array<Number>}
   */

  function xyRolling(points, fct, options = {}) {
    let {
      x,
      y
    } = points;
    y = xRolling(y, fct, options);

    if (x.length !== y.length) {
      x = xRollingAverage(x, options);
    }

    return {
      x,
      y
    };
  }

  /**
   *
   * @param {DataXY} [data] array of points {x,y}
   * @returns {DataPoints}
   */

  function xyToXYObject(data) {
    xyCheck(data);
    const {
      x,
      y
    } = data;
    let objectArray = [];

    for (let i = 0; i < x.length; i++) {
      objectArray.push({
        x: x[i],
        y: y[i]
      });
    }

    return objectArray;
  }

  /**
   * Convert a DataXY to an array of array containing x,y
   * @param {DataXY} [data] array of points {x,y}
   * @returns {Array<Array<number,number>>}
   */

  function xyToXYArray(data) {
    xyCheck(data);
    const {
      x,
      y
    } = data;
    let objectArray = [];

    for (let i = 0; i < x.length; i++) {
      objectArray.push([x[i], y[i]]);
    }

    return objectArray;
  }

  /**
   * Calibrates the data based on a range and means of peaks in this range
   * Based on a range we will make a peak picking using global spectra deconvolution
   * The selected nbPeaks will then be taken into account to calculate an average X value.
   * The difference between the targetX and the averageX value will be returned
   * @param {DataXY} [data] array of points {x,y}
   * @param {Object} [range={}]
   * @param {number} [range.from] - Beginning of the range where the interest signal is localed
   * @param {number} [range.to] - End of the range where the interest signal is localed
   * @param {Object} [options={}]
   * @param {number} [options.nbPeaks=1] Number of peaks to consider to calculate mean (sorted by height)
   * @param {number} [options.targetX=0] Expected value for the mean of the peaks position
   * @param {number} [options.gsd={}] GSD options. You may check options here: http://mljs.github.io/global-spectral-deconvolution/#gsd
   * @param {number} [options.gsd.minMaxRatio=0.2] - GSD Threshold to determine if a given peak should be considered as a noise.
   * @returns {number} difference between targetX and currentX
   */

  function xyCalibrate(data, range = {}, options = {}) {
    const {
      targetX = 0,
      nbPeaks = 1,
      gsd: gsdOptions = {
        minMaxRatio: 0.1,
        realTopDetection: true,
        smoothY: true,
        sgOptions: {
          windowSize: 7,
          polynomial: 3
        }
      }
    } = options;
    let {
      from,
      to
    } = range;
    if (from === undefined || to === undefined) return 0;
    const fromIndex = xFindClosestIndex$1(data.x, from);
    const toIndex = xFindClosestIndex$1(data.x, to);
    const sliceddata = {
      x: data.x.slice(fromIndex, toIndex),
      y: data.y.slice(fromIndex, toIndex)
    };
    let peaks = gsd(sliceddata, gsdOptions).sort((a, b) => b.y - a.y).slice(0, nbPeaks);
    if (peaks.length === 0) return 0;
    const middle = mean(peaks.map(peak => peak.x));
    return targetX - middle;
  }

  /**
  import { xIsMonotone } from '../x/xIsMonotone';
   * This function performs a quick sort of the x array while transforming the y array to preserve the coordinates.
   * @param {DataXY} [data] Object that contains property x (Array) and y (Array)
   */

  function xySortX(data) {
    const {
      x,
      y
    } = data; // no need to sort if it is already sorted

    if (xIsMonotone(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;
  }

  /**
   * Ensure x values are unique
   * @param {DataXY} [data] Object that contains property x (Array) and y (Array)
   * @param {Object} [options={}] Object containing a property algorithm (can be 'sum' or 'average', the latter being the default value), and a property isSorted (boolean indicating if the x-array is sorted).
   * @param {string} [options.algorithm='average'] either 'average' or 'sum'
   * @param {boolean} [options.isSorted=true] if false the DataXY has to be sorted first
   * @returns {DataXY}
   */

  function xyUniqueX(data, options = {}) {
    xyCheck(data);
    const {
      algorithm = 'average',
      isSorted = true
    } = options;

    if (!isSorted) {
      data = xySortX(data);
    }

    switch (algorithm) {
      case 'average':
        return average(data);

      case 'sum':
        return sum$2(data);

      default:
        throw new Error(`xyUniqueX: unknown algorithm: ${algorithm}`);
    }
  }

  function average(data) {
    let x = [];
    let y = [];
    let cumulativeY = data.y[0];
    let divider = 1;

    for (let i = 1; i < data.x.length; i++) {
      if (!(data.x[i] === data.x[i - 1])) {
        x.push(data.x[i - 1]);
        y.push(cumulativeY / divider);
        cumulativeY = 0;
        divider = 0;
      }

      cumulativeY += data.y[i];
      divider++;
    }

    x.push(data.x[data.x.length - 1]);
    y.push(cumulativeY / divider);
    return {
      x,
      y
    };
  }

  function sum$2(data) {
    let x = [];
    let y = [];
    let cumulativeY = data.y[0];

    for (let i = 1; i < data.x.length; i++) {
      if (!(data.x[i] === data.x[i - 1])) {
        x.push(data.x[i - 1]);
        y.push(cumulativeY);
        cumulativeY = 0;
      }

      cumulativeY += data.y[i];
    }

    x.push(data.x[data.x.length - 1]);
    y.push(cumulativeY);
    return {
      x,
      y
    };
  }

  /**
   * Throw an error in no an object of x,y arrays
   * @param {ArrayPoints} [points=[]]
   */
  function xyObjectCheck(points = []) {
    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 {DataXY} [points=[]] - Object that contains property x (an ordered increasing array) and y (an array)
   * @return {object}
   */

  function xyObjectMaxXPoint(points = []) {
    xyObjectCheck(points);
    if (points.length < 1) return {};
    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 {DataXY} [points=[]] - Object that contains property x (an ordered increasing array) and y (an array)
   * @return {object}
   */

  function xyObjectMinXPoint(points = []) {
    xyObjectCheck(points);
    if (points.length < 1) return {};
    let current = {
      x: points[0].x,
      y: points[0].y,
      index: 0
    };

    for (let i = 1; i < points.length; i++) {
      if (points[i].x < current.x) {
        current = {
          x: points[i].x,
          y: points[i].y,
          index: i
        };
      }
    }

    return current;
  }

  /**
   * Filter the array by taking the higher points (max y value) and only
   * keep one per slot.
   * There are 2 different slots, the smallest one will have the
   * new property `close` to true
   * @param {array} points - array of all the points
   * @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.limit=20] - max number of points
   * @param {number} [options.threshold=0.01] - minimal intensity compare to more intense point
   * @param {number} [options.numberSlots=10] - define the number of slots and indirectly the slot width
   * @param {number} [options.numberCloseSlots=50]
   * @returns {array} - copy of points with 'close' property
   */

  function xyObjectBestPoints(points, options = {}) {
    const {
      from = xyObjectMinXPoint(points).x,
      to = xyObjectMaxXPoint(points).x,
      limit = 20,
      threshold = 0.01,
      numberCloseSlots = 50,
      numberSlots = 10
    } = options;
    let slot = (to - from) / numberSlots;
    let closeSlot = (to - from) / numberCloseSlots;
    let selected = points.filter(point => point.x >= from && point.x <= to).map(point => {
      return {
        point,
        monoisotopic: false
      };
    });
    selected = selected.sort((a, b) => {
      if (a.monoisotopic && !b.monoisotopic) return -1;
      if (b.monoisotopic && !a.monoisotopic) return 1;
      return b.point.y - a.point.y;
    });
    let toReturn = [];
    if (selected.length === 0) return [];
    let minY = selected[0].point.y * threshold;

    peakLoop: for (let item of selected) {
      if (item.point.y < minY) {
        if (item.monoisotopic) {
          continue;
        } else {
          break;
        }
      }

      let close = false;

      for (let existing of toReturn) {
        if (Math.abs(existing.x - item.point.x) < closeSlot) {
          continue peakLoop;
        }

        if (Math.abs(existing.x - item.point.x) < slot) {
          close = true;
        }
      }

      let newPeak = JSON.parse(JSON.stringify(item.point));
      newPeak.close = close;
      toReturn.push(newPeak);
      if (toReturn.length === limit) break;
    }

    return toReturn.sort((a, b) => a.x - b.x);
  }

  /**
   *
   * @param {ArrayPoints} [points] array of growing points {x,y}
   * @param {object} [options={}]
   * @param {object} [xError=Number.EPSILON] limit to join the data
   */
  function xyObjectJoinX(points, options = {}) {
    const {
      xError = Number.EPSILON
    } = options; // when we join we will use the center of mass

    let result = [];
    let current = {
      x: Number.MIN_SAFE_INTEGER,
      y: 0
    };

    for (let point of points) {
      if (point.x - current.x <= xError) {
        // weighted sum
        if (current.y !== 0 || point.y !== 0) {
          current.x = point.y / (current.y + point.y) * (point.x - current.x) + current.x;
          current.y += point.y;
        }
      } else {
        current = {
          x: point.x,
          y: point.y
        };
        result.push(current);
      }
    }

    return result;
  }

  /**
   * Finds the max y value and return a {x,y,index} point
   * @param {DataXY} [points=[]] - Object that contains property x (an ordered increasing array) and y (an array)
   * @return {object}
   */

  function xyObjectMaxYPoint(points = []) {
    xyObjectCheck(points);
    if (points.length < 1) return {};
    let current = {
      x: points[0].x,
      y: points[0].y,
      index: 0
    };

    for (let i = 1; i < points.length; i++) {
      if (points[i].y > current.y) {
        current = {
          x: points[i].x,
          y: points[i].y,
          index: i
        };
      }
    }

    return current;
  }

  /**
   * Finds the min y value and return a {x,y,index} point
   * @param {DataXY} [points=[]] - Object that contains property x (an ordered increasing array) and y (an array)
   * @return {object}
   */

  function xyObjectMinYPoint(points = []) {
    xyObjectCheck(points);
    if (points.length < 1) return {};
    let current = {
      x: points[0].x,
      y: points[0].y,
      index: 0
    };

    for (let i = 1; i < points.length; i++) {
      if (points[i].y < current.y) {
        current = {
          x: points[i].x,
          y: points[i].y,
          index: i
        };
      }
    }

    return current;
  }

  /**
   *
   * @param {ArrayPoints} [points] array of growing points {x,y}
   * @param {object} [options={}]
   * @param {object} [slotWidth=1] limit to join the data
   */
  function xyObjectSlotX(points, options = {}) {
    const {
      slotWidth = 1
    } = options;
    const halfSlot = slotWidth / 2; // when we join we will use the center of mass

    let result = [];
    let current = {
      x: Number.MIN_VALUE,
      y: 0
    };

    for (let point of points) {
      let slot = point.x - (point.x + halfSlot) % slotWidth + halfSlot;

      if (Math.abs(current.x - slot) > Number.EPSILON) {
        current = {
          x: slot,
          y: 0
        };
        result.push(current);
      }

      current.y += point.y;
    }

    return result;
  }

  /**
   * Sorts an array of points
   * @param {ArrayPoints} [points] array of points {x,y}
   */
  function xyObjectSortX(points) {
    return points.sort((a, b) => a.x - b.x);
  }

  /**
   *
   * @param {ArrayPoints} [points] array of points {x,y}
   */
  function xyObjectToXY(points) {
    return {
      x: points.map(entry => entry.x),
      y: points.map(entry => entry.y)
    };
  }

  function zoneToX(zone, size) {
    const {
      from,
      to
    } = zone;
    let array = new Float64Array(size);
    let step = (to - from) / (size - 1);

    for (let i = 0; i < size; i++) {
      array[i] = from + step * i;
    }

    return array;
  }

  /**
   * Center mean of columns
   * @param {Array<Array<Number>>} [matrix] - matrix [rows][cols].
   */
  function matrixCenterZMean(matrix) {
    const nbRows = matrix.length;
    const nbColumns = matrix[0].length;
    const newMatrix = new Array(nbRows);

    for (let row = 0; row < nbRows; row++) {
      newMatrix[row] = new Float64Array(nbColumns);
    }

    for (let column = 0; column < nbColumns; column++) {
      let mean = 0;

      for (let row = 0; row < nbRows; row++) {
        mean += matrix[row][column];
      }

      mean /= nbRows;

      for (let row = 0; row < nbRows; row++) {
        newMatrix[row][column] = matrix[row][column] - mean;
      }
    }

    return newMatrix;
  }

  /**
   * Get min and max of the absolute values of Z
   * @param {Array<Array<Number>>} [matrix] - matrix [rows][cols].
   *
   */
  function matrixMinMaxAbsoluteZ(matrix) {
    if (matrix.length === 0 || matrix[0].length === 0) {
      return {
        min: undefined,
        max: undefined
      };
    }

    const nbRows = matrix.length;
    const nbColumns = matrix[0].length;
    let min = Number.POSITIVE_INFINITY;
    let max = Number.NEGATIVE_INFINITY;

    for (let column = 0; column < nbColumns; column++) {
      for (let row = 0; row < nbRows; row++) {
        let value = matrix[row][column];
        if (value < 0) value *= -1;
        if (value < min) min = value;
        if (value > max) max = value;
      }
    }

    return {
      min,
      max
    };
  }

  /**
   * Get min and max Z
   * @param {Array<Array<Number>>} [matrix] - matrix [rows][cols].
   *
   */
  function matrixMinMaxZ(matrix) {
    if (matrix.length === 0 || matrix[0].length === 0) {
      return {
        min: undefined,
        max: undefined
      };
    }

    const nbRows = matrix.length;
    const nbColumns = matrix[0].length;
    let min = matrix[0][0];
    let max = matrix[0][0];

    for (let column = 0; column < nbColumns; column++) {
      for (let row = 0; row < nbRows; row++) {
        if (matrix[row][column] < min) min = matrix[row][column];
        if (matrix[row][column] > max) max = matrix[row][column];
      }
    }

    return {
      min,
      max
    };
  }

  /**
  import { matrixMinMaxZ } from './matrixMinMaxZ';
  import { xHistogram } from '../x/xHistogram';
  import { matrixMinMaxAbsoluteZ } from './matrixMinMaxAbsoluteZ';
   * Calculates an histogram of defined number of slots
   * @param {Array<Array<Number>>} [matrix] - matrix [rows][cols].
   * @param {number} [options.nbSlots=256] Number of slots
   * @param {number} [options.min=minValue] Minimum value to calculate used to calculate slot size
   * @param {number} [options.max=maxValue] Maximal value to calculate used to calculate slot size
   * @param {number} [options.logBaseX] We can first apply a log on x axi
   * @param {number} [options.logBaseY] We can apply a log on the resulting histogra
   * @param {boolean} [options.absolute] Take the absolute value
   * @param {number} [options.centerX=true] Center the X value. We will enlarge the first and
   * @return {DataXY} {x,y} of the histogram
   *
   */

  function matrixHistogram(matrix, options = {}) {
    const {
      logBaseY,
      logBaseX,
      absolute
    } = options;
    options = JSON.parse(JSON.stringify(options));
    delete options.logBaseY;

    if (matrix.length === 0 || matrix[0].length === 0) {
      throw new Error('matrixHistogram: matrix should have at least one column and one row');
    }

    if (options.min === undefined || options.max === undefined) {
      const minMax = absolute ? matrixMinMaxAbsoluteZ(matrix) : matrixMinMaxZ(matrix);

      if (options.min === undefined) {
        options.min = logBaseX ? Math.log(minMax.min) / Math.log(logBaseX) : minMax.min;
      }

      if (options.max === undefined) {
        options.max = logBaseX ? Math.log(minMax.max) / Math.log(logBaseX) : minMax.max;
      }
    }

    let histogram = xHistogram(matrix[0], options);
    options.histogram = histogram;
    const nbRows = matrix.length;

    for (let row = 1; row < nbRows; row++) {
      xHistogram(matrix[row], options);
    }

    const y = histogram.y;

    if (logBaseY) {
      const logOfBase = Math.log10(logBaseY);

      for (let i = 0; i < y.length; i++) {
        y[i] = Math.log10(y[i] + 1) / logOfBase;
      }
    }

    return histogram;
  }

  /**
   * Performs a Probabilistic quotient normalization (PQN) over the dataset to account dilution based in median spectrum.
   * Dieterle, F., Ross, A., Schlotterbeck, G., & Senn, H. (2006). Probabilistic quotient normalization as robust method to account for dilution of complex biological mixtures. Application in 1H NMR metabonomics. Analytical chemistry, 78(13), 4281-4290.
   * DOI: 10.1021/ac051632c
   * @param {Array<Array<Number>>} [matrix] - matrix [rows][cols].
   * @param {Object} [options={}]
   * @param {number} [options.max=100] - Normalization integral constant.
   * @return {Object} { data, medianOfQuotients }.
   * data: Normalized dataset.
   * medianOfQuotients: The median of quotients of each variables.
   */

  function matrixPQN(matrix, options = {}) {
    const {
      max = 100
    } = options;
    matrix = new Matrix(matrix);

    for (let i = 0; i < matrix.rows; i++) {
      const normalizationFactor = matrix.getRowVector(i).norm() / max;
      const row = matrix.getRowVector(i).div(normalizationFactor);
      matrix.setRow(i, row);
    }

    let referenceSpectrum = [];

    for (let i = 0; i < matrix.columns; i++) {
      const currentVariable = matrix.getColumn(i);
      referenceSpectrum.push(median(currentVariable));
    }

    let medianOfQuotients = [];

    for (let i = 0; i < matrix.columns; i++) {
      let quotients = matrix.getColumnVector(i).div(referenceSpectrum[i]);
      medianOfQuotients.push(median(quotients.getColumn(0)));
    }

    for (let i = 0; i < matrix.rows; i++) {
      matrix.mulRow(i, 1 / medianOfQuotients[i]);
    }

    return {
      data: matrix.to2DArray(),
      medianOfQuotients: medianOfQuotients
    };
  }

  /**
   * Rescale columns
   * @param {Array<Array<Number>>} [matrix] - matrix [rows][cols].
   * @param {object} [options={}]
   * @param {object} [options.min=0]
   * @param {object} [options.max=1]
   *
   */
  function matrixZRescale(matrix, options = {}) {
    const {
      min = 0,
      max = 1
    } = options;
    const nbRows = matrix.length;
    const nbColumns = matrix[0].length;
    const newMatrix = new Array(nbRows);

    for (let row = 0; row < nbRows; row++) {
      newMatrix[row] = new Float64Array(nbColumns);
    }

    for (let column = 0; column < nbColumns; column++) {
      let currentMin = matrix[0][column];
      let currentMax = matrix[0][column];

      for (let row = 1; row < nbRows; row++) {
        if (matrix[row][column] < currentMin) currentMin = matrix[row][column];
        if (matrix[row][column] > currentMax) currentMax = matrix[row][column];
      }

      const factor = (max - min) / (currentMax - currentMin);

      for (let row = 0; row < nbRows; row++) {
        newMatrix[row][column] = (matrix[row][column] - currentMin) * factor + min;
      }
    }

    return newMatrix;
  }

  /**
   * Create an array with sequential numbers between from and to of length
   * @param {number} [options.from=0]
   * @param {number} [options.to=1]
   * @param {number} [options.length=1001]
   */
  function createSequentialArray(options = {}) {
    const {
      from = 0,
      to = 1,
      length = 1000
    } = options;
    const array = new Float64Array(length);
    let step = (to - from) / (array.length - 1);

    for (let i = 0; i < array.length; i++) {
      array[i] = from + step * i;
    }

    return array;
  }

  /**
   * @typedef {Object} DataXY
   * @property {Array<Number>} x Array of x values
   * @property {Array<Number>} y Array of y values
   */

  var src$4 = /*#__PURE__*/Object.freeze({
    __proto__: null,
    xAbsolute: xAbsolute,
    xAbsoluteMedian: xAbsoluteMedian,
    xAdd: xAdd,
    xAutoCorrelation: xAutoCorrelation,
    xBoxPlot: xBoxPlot,
    xCorrelation: xCorrelation,
    xCrossCorrelation: xCrossCorrelation,
    xCumulative: xCumulative,
    xDivide: xDivide,
    xFindClosestIndex: xFindClosestIndex$1,
    xGetFromToIndex: xGetFromToIndex,
    xGetTargetIndex: xGetTargetIndex,
    xHistogram: xHistogram,
    xIsMonotone: xIsMonotone,
    xMaxIndex: xMaxIndex,
    xMaxValue: xMaxValue,
    xMean: xMean,
    xMinIndex: xMinIndex,
    xMinMaxValues: xMinMaxValues,
    xMinValue: xMinValue,
    xMultiply: xMultiply,
    xNoiseSanPlot: xNoiseSanPlot,
    xNorm: xNorm,
    xParetoNormalization: xParetoNormalization,
    xPadding: xPadding,
    xRotate: xRotate,
    xRolling: xRolling,
    xRollingAverage: xRollingAverage,
    xRollingMedian: xRollingMedian,
    xRollingMin: xRollingMin,
    xRollingMax: xRollingMax,
    xSubtract: xSubtract,
    xSum: xSum,
    xMeanAbsoluteError: xMeanAbsoluteError,
    xMeanSquaredError: xMeanSquaredError,
    xDotProduct: xDotProduct,
    reimAbsolute: reimAbsolute,
    reimAutoPhaseCorrection: reimAutoPhaseCorrection,
    reimPhaseCorrection: reimPhaseCorrection,
    reimFFT: reimFFT,
    xreimZeroFilling: xreimZeroFilling,
    xreimSortX: xreimSortX,
    xyArrayAlign: xyArrayAlign,
    xyArrayMerge: xyArrayMerge,
    xyArrayWeightedMerge: xyArrayWeightedMerge,
    xyArrayAlignToFirst: xyArrayAlignToFirst,
    xyAlign: xyAlign,
    xyCheck: xyCheck,
    xyCumulativeDistributionStatistics: xyCumulativeDistributionStatistics,
    xyEnsureGrowingX: xyEnsureGrowingX,
    xyExtract: xyExtract,
    xyFilterXPositive: xyFilterXPositive,
    xyGetNMaxY: xyGetNMaxY,
    xyGrowingX: xyGrowingX,
    xyIntegral: xyIntegral,
    xyIntegration: xyIntegration,
    xyJoinX: xyJoinX,
    xyMaxClosestYPoint: xyMaxClosestYPoint,
    xyMaxY: xyMaxY,
    xyMaxYPoint: xyMaxYPoint,
    xyMaximaY: xyMaximaY,
    xyMedian: xyMedian,
    xyMinClosestYPoint: xyMinClosestYPoint,
    xyMinYPoint: xyMinYPoint,
    xyMinimaY: xyMinimaY,
    xyPeakInfo: xyPeakInfo,
    xyRealMaxYPoint: xyRealMaxYPoint,
    xyRealMinYPoint: xyRealMinYPoint,
    xyReduce: xyReduce,
    xyRolling: xyRolling,
    xyToXYObject: xyToXYObject,
    xyToXYArray: xyToXYArray,
    xyCalibrate: xyCalibrate,
    xyUniqueX: xyUniqueX,
    xySortX: xySortX,
    xyObjectBestPoints: xyObjectBestPoints,
    xyObjectJoinX: xyObjectJoinX,
    xyObjectMaxXPoint: xyObjectMaxXPoint,
    xyObjectMinXPoint: xyObjectMinXPoint,
    xyObjectMaxYPoint: xyObjectMaxYPoint,
    xyObjectMinYPoint: xyObjectMinYPoint,
    xyObjectSlotX: xyObjectSlotX,
    xyObjectSortX: xyObjectSortX,
    xyObjectToXY: xyObjectToXY,
    zoneToX: zoneToX,
    zonesNormalize: zonesNormalize,
    matrixCenterZMean: matrixCenterZMean,
    matrixHistogram: matrixHistogram,
    matrixMinMaxZ: matrixMinMaxZ,
    matrixMinMaxAbsoluteZ: matrixMinMaxAbsoluteZ,
    matrixPQN: matrixPQN,
    matrixZRescale: matrixZRescale,
    createSequentialArray: createSequentialArray
  });

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

  const getMsInfo = getMsInfo$2;
  const xFindClosestIndex = require$$1.xFindClosestIndex;
  /**
   * @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 {Array<number>}  [options.targetMasses] - Target masses: SORTED array of numbers
   * @param {Array<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 {object}         [options.unsaturation={}}]
   * @param {number}         [options.unsaturation.min=-Infinity] - Minimal unsaturation
   * @param {number}         [options.unsaturation.max=+Infinity] - Maximal unsaturation
   * @param {number}         [options.unsaturation.onlyInteger=false] - Integer unsaturation
   * @param {number}         [options.unsaturation.onlyNonInteger=false] - Non integer unsaturation
   * @param {object}         [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
   */

  var msemMatcher = function msemMatcher(entry, options = {}) {
    const {
      ionization = {
        mf: '',
        em: 0,
        charge: 0
      },
      forceIonization = false,
      precision = 1000,
      minCharge = Number.MIN_SAFE_INTEGER,
      maxCharge = Number.MAX_SAFE_INTEGER,
      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,
      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 (entry.charge !== undefined) {
      if (ms.charge < minCharge || ms.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 of Object.keys(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 (targetMasses && targetMasses.length > 0) {
      let index = xFindClosestIndex(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;
  };

  var src$3 = {
    general: generalMatcher,
    msem: msemMatcher
  };

  /**
   * 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$1(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$1;

  const {
    ELECTRON_MASS
  } = constants$3;
  const MF = src$7.MF;
  const matcher = src$3.msem;
  const preprocessIonizations = preprocessIonizations$2;
  const processRange = processRange_1;
  const sum = lib;
  /**
   * Generate all the possible combinations of molecular formula and calculate
   * for each of them the monoisotopic mass and observed moniisotopic mass (m/z)
   * In the molecular formula there may be a comment after the '$' symbol
   *
   * @param keys
   * @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}       [canonizeMF=true] - Canonize molecular formula
   * @param {boolean}       [uniqueMFs=true] - Force canonization and make MF unique
   * @param {string}        [ionizations=''] - Comma separated list of ionizations (to charge the molecule)
   * @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 {Array<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 {object}        [options.filter.unsaturation={}}]
   * @param {number}        [options.filter.unsaturation.min=-Infinity] - Minimal unsaturation
   * @param {number}        [options.filter.unsaturation.max=+Infinity] - Maximal unsaturation
   * @param {number}        [options.filter.unsaturation.onlyInteger=false] - Integer unsaturation
   * @param {number}        [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 {Array}
   */

  var src$2 = function generateMFs(keys, options = {}) {
    options = { ...options
    };
    let {
      limit = 100000,
      uniqueMFs = true,
      estimate = false
    } = options;
    options.filterFctVariables = {};

    for (let i = 0; i < keys.length; i++) {
      const key = keys[i];

      if (typeof key === 'object' && key.name) {
        options.filterFctVariables[key.name] = i;
        keys[i] = key.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(keys)) {
      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 < keys.length; i++) {
      if (!Array.isArray(keys[i])) {
        keys[i] = keys[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 < keys.length; i++) {
      let parts = keys[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 !
        }
      }

      keys[i] = newParts;
    }

    if (estimate) {
      let total = keys.reduce((previous, current) => previous * current.length, 1);
      return total * options.ionizations.length;
    }

    let results = [];
    let sizes = [];
    let currents = [];

    for (let i = 0; i < keys.length; i++) {
      sizes.push(keys[i].length - 1);
      currents.push(0);
    }

    let position = 0;
    let evolution = 0;

    while (position < currents.length) {
      if (currents[position] < sizes[position]) {
        evolution++;
        appendResult(results, currents, keys, 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, keys, 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(atoms, info.atoms);

        if (info.unsaturation && validUnsaturation) {
          unsaturation += info.unsaturation;
        }
      }
    }

    return {
      charge,
      em,
      mw,
      ionization: ionization,
      unsaturation: unsaturation / 2 + 1 ,
      atoms
    };
  }

  function appendResult(results, currents, keys, options = {}) {
    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 stars = [];

      for (let i = 0; i < keys.length; i++) {
        let anchors = keys[i][currents[i]].match(/#[0-9]+/g);
        if (anchors) stars.push(...anchors);
      }

      if (stars.length % 2 === 1) return;
      stars = stars.sort();

      for (let i = 0; i < stars.length; i += 2) {
        if (stars[i] !== stars[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) / Math.abs(ionization.charge), result.charge + result.ionization.charge, result.unsaturation, result.atoms);
        if (!options.filterFct.apply(null, variables)) continue;
      }

      let match = matcher(result, filter);
      if (!match) continue;
      result.ms = match.ms;
      result.ionization = match.ionization;
      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 (canonizeMF) {
        result.mf = new MF(result.mf).toMF();
      }

      if (comments.length > 0) {
        result.comment = comments.join(' ');
      }

      results.push(result);
    }
  }

  var generateMFs = src$2;

  /**
   * Calculate tic for specific molecular formula and ionizations
   *
   * The system will take all the peaks with an intensity over 5% (default value)
   * @param {Chromatogram} chromatogram - GC/MS chromatogram where make the peak picking
   * @param {string} targetMF - mass for which to extract the spectrum
   * @param {object} [options={}]
   * @param {number} [options.slotWidth=1] - Width of the slot around the mass of targetMF
   * @param {number} [options.threshold=0.05] - Minimal height for peaks
   * @param {number} [options.ionizations='H+'] - List of allowed ionisation
   * @return {Array} - Calculated mass for targetMass
   */

  function calculateForMF(chromatogram, targetMF, options = {}) {
    const {
      threshold = 0.05,
      slotWidth = 1,
      ionizations = 'H+'
    } = options;

    if (typeof targetMF !== 'string') {
      throw Error('targetMF must be defined and a string');
    }

    const mfs = generateMFs([targetMF]).map(info => info.mf);
    const halfWidth = slotWidth / 2;
    const ms = chromatogram.getSeries('ms');
    let masses = [];

    for (let mf of mfs) {
      let isotopicDistribution = new IsotopicDistribution$1(mf, {
        ionizations
      }); // we add isotopicDistribution in all the parts

      isotopicDistribution.getDistribution();
      let parts = isotopicDistribution.getParts();
      masses = masses.concat(...parts.map(part => part.isotopicDistribution));
    }

    masses.sort((a, b) => a.x - b.x);
    masses = xyObjectSlotX$1(masses, {
      slotWidth
    }).filter(mass => mass.y > threshold);
    let massSpectra = ms.data;
    let result = new Array(massSpectra.length).fill(0);

    for (let targetMass of masses) {
      for (let i = 0; i < massSpectra.length; i++) {
        let massSpectrum = massSpectra[i];

        for (let j = 0; j < massSpectrum[0].length; j++) {
          if (Math.abs(massSpectrum[0][j] - targetMass.x) <= halfWidth) {
            result[i] += massSpectrum[1][j];
          }
        }
      }
    }

    return result;
  }

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

  function calculateTic(chromatogram) {
    const ms = chromatogram.getSeries('ms');
    const massSpectra = ms.data;
    const tic = [];

    for (const massSpectrum of massSpectra) {
      if (massSpectrum[1].length > 0) {
        tic.push(sum$3(massSpectrum[1]));
      } else {
        tic.push(0);
      }
    }

    return tic;
  }

  function zeroInsteadOfNegative(X) {
    let rows = X.rows;
    let columns = X.columns;
    let newMatrix = new Matrix(X);

    for (let r = 0; r < rows; r++) {
      for (let c = 0; c < columns; c++) {
        if (newMatrix.get(r, c) < 0) {
          newMatrix.set(r, c, 0);
        }
      }
    }

    return newMatrix;
  }

  function checkMatrixS(data, originalMatrix) {
    let {
      A,
      S
    } = data; //check if is there at least one element cero

    let indices = [];
    let sum = S.sum('row');

    for (let i = 0; i < sum.length; i++) {
      if (sum[i] === 0) {
        indices.push(i);
        continue;
      } else {
        for (let j = 0; j < S.columns; j++) {
          if (isNaN(S.get(i, j))) {
            indices.push(i);
            break;
          }
        }
      }
    } // if there than just one zero or NaN element
    // run a NMF with the residual matrix Y - A*B


    if (indices.length > 0) {
      let temp = fastExtractNMF(originalMatrix.clone().subM(A.mmul(S)), indices.length);

      for (let i = 0; i < indices.length; i++) {
        for (let j = 0; j < S.columns; j++) {
          S.set(indices[i], j, temp.S.get(i, j));
        }

        for (let j = 0; j < A.rows; j++) {
          A.set(j, indices[i], temp.A.get(j, i));
        }
      }
    }

    return Object.assign({}, data, {
      A,
      S
    });
  }

  function fastExtractNMF(residual, r) {
    if (r <= 0) return {
      A: [],
      S: []
    };
    const {
      columns,
      rows
    } = residual;
    let A = Matrix.zeros(rows, r);
    let S = Matrix.zeros(r, columns);

    for (let i = 0; i < r; i++) {
      residual = zeroInsteadOfNegative(residual);
      if (residual.sum() === 0) continue;
      let res2 = Matrix.pow(residual, 2).sum('column'); //find the max of the first column

      let maxIndex = 0;

      for (let j = 1; j < res2.length; j++) {
        if (res2[maxIndex] < res2[j]) maxIndex = j;
      }

      if (res2[maxIndex] > 0) {
        let sqrtMaxValue = Math.sqrt(res2[maxIndex]);

        for (let j = 0; j < rows; j++) {
          let value = residual.get(j, maxIndex) / sqrtMaxValue;
          A.set(j, i, value);
        }

        let temp = A.getColumnVector(i).transpose().mmul(residual);

        for (let j = 0; j < columns; j++) {
          S.set(i, j, Math.max(temp.get(0, j), 0));
        }

        let subtracting = A.getColumnVector(i).mmul(S.getRowVector(i));
        residual = residual.sub(subtracting);
      }
    }

    return {
      A,
      S
    };
  }

  function normBy(x, by = 'column') {
    let norms = Matrix.mul(x, x).sum(by);
    let length = norms.length;

    for (let i = 0; i < length; i++) {
      norms[i] = Math.sqrt(norms[i]);
    }

    return by === 'row' ? Matrix.from1DArray(length, 1, norms) : Matrix.from1DArray(1, length, norms);
  }

  function normProj(X, normLimits) {
    let norms;
    let r = X.rows;
    let c = X.columns;

    if (normLimits.rows === r) {
      norms = normBy(X, 'row'); //select rows with norm > 0 then multiply twise by the min

      for (let i = 0; i < r; i++) {
        if (norms.get(i, 0) <= 0) continue;

        for (let j = 0; j < c; j++) {
          let value = X.get(i, j) * Math.min(norms.get(i, 0), normLimits.get(i, 0) / norms.get(i, 0));
          X.set(i, j, value);
        }
      }
    } else {
      norms = normBy(X, 'column');

      for (let i = 0; i < c; i++) {
        if (norms.get(0, i) <= 0) continue;

        for (let j = 0; j < r; j++) {
          let value = X.get(j, i) * Math.min(norms.get(0, i), normLimits.get(0, i) / norms.get(0, i));
          X.set(j, i, value);
        }
      }
    }

    return X;
  }

  function updateMatrixA(Ainit, S, originalMatrix, options) {
    let {
      maxFBIteration,
      toleranceFB,
      normConstrained = false,
      lambda
    } = options;
    let St = S.transpose();
    let H = S.mmul(St);
    let YSt = originalMatrix.mmul(St);
    let evd = new EigenvalueDecomposition(H, {
      assumeSymmetric: true
    });
    let L = Math.max(...evd.realEigenvalues);
    let A = Ainit;
    let prevA = A.clone();
    let t = 1;

    let gradient = a => a.mmul(H).sub(YSt);

    let proximal;

    if (normConstrained) {
      let normLimits = normBy(Ainit, 'column');

      proximal = (x, threshold) => normProj(zeroInsteadOfNegative(x.subS(threshold)), normLimits);
    } else {
      proximal = (x, threshold) => zeroInsteadOfNegative(x.subS(threshold));
    }

    for (let i = 0; i < maxFBIteration; i++) {
      let tNext = (1 + Math.sqrt(1 + 4 * t * t)) / 2;
      let w = (t - 1) / tNext;
      t = tNext;
      let B = Matrix.mul(A, w + 1).sub(Matrix.mul(prevA, w));
      prevA = A.clone();
      A = proximal(B.sub(gradient(B).divS(L)), lambda / L);

      if (Matrix.sub(prevA, A).norm() / A.norm() < toleranceFB) {
        break;
      }
    }

    return A;
  }

  function getMax(array = []) {
    let max = Number.MIN_SAFE_INTEGER;

    for (let i = 0; i < array.length; i++) {
      if (max < array[i]) max = array[i];
    }

    return max;
  }

  function updateMatrixS(A, Sinit, originalMatrix, lambda, options) {
    let {
      maxFBIteration,
      toleranceFB
    } = options;
    let At = A.transpose();
    let H = At.mmul(A);
    let AtY = At.mmul(originalMatrix);
    let evd = new EigenvalueDecomposition(H, {
      assumeSymmetric: true
    });
    let L = getMax(evd.realEigenvalues);
    let t = 1;
    let S = Sinit.clone();
    let prevS = S.clone();

    let gradient = s => H.mmul(s).sub(AtY);

    let proximal = (x, threshold) => zeroInsteadOfNegative(x.subS(threshold));

    for (let i = 0; i < maxFBIteration; i++) {
      let tNext = (1 + Math.sqrt(1 + 4 * t * t)) / 2;
      let w = (t - 1) / tNext;
      t = tNext; // R = S_k + w [S_k - S_(k-1)] = (1 + w) .* S_k - w .* S_(k-1)

      let R = Matrix.mul(S, 1 + w).sub(Matrix.mul(prevS, w));
      prevS = S.clone();
      S = proximal(R.sub(gradient(R).divS(L)), lambda / L);

      if (Matrix.sub(prevS, S).norm() / S.norm() < toleranceFB) {
        break;
      }
    }

    return S;
  }

  function initialize(originalMatrix, options = {}) {
    const {
      rank,
      randGenerator,
      maxInitFBIteration,
      toleranceFBInit,
      maxFBIteration,
      toleranceFB,
      normConstrained
    } = options;
    let result = {};
    let rows = originalMatrix.rows;
    result.A = Matrix.rand(rows, rank, {
      random: randGenerator
    });

    for (let iter = 0; iter < maxInitFBIteration; iter++) {
      //select columns with sum positive from A
      let sumC = result.A.sum('column');

      for (let i = 0; i < sumC.length; i++) {
        while (sumC[i] === 0) {
          sumC[i] = 0;

          for (let j = 0; j < rows; j++) {
            result.A.set(j, i, randGenerator());
            sumC[i] += result.A.get(j, i);
          }
        }
      } //resolve the system of equation Lx = D for x, then select just non negative values;


      result.S = zeroInsteadOfNegative(solve(result.A, originalMatrix)); //select rows with positive sum by row

      let sumR = result.S.sum('row');
      let positiveSumRowIndexS = [];
      let positiveSumRowS = [];

      for (let i = 0; i < sumR.length; i++) {
        if (sumR[i] > 0) {
          positiveSumRowIndexS.push(i);
          positiveSumRowS.push(result.S.getRow(i));
        }
      }

      positiveSumRowS = Matrix.checkMatrix(positiveSumRowS); // solve the system of linear equation xL = D for x. knowing that D/L = (L'\D')'.

      let candidateA = zeroInsteadOfNegative(solve(positiveSumRowS.transpose(), originalMatrix.transpose())); //then, set the columns of A with an index equal to the row index with sum > 0 into S
      //this step complete the last transpose of D/L = (L'\D')'.

      for (let i = 0; i < positiveSumRowIndexS.length; i++) {
        let colCandidate = candidateA.getRow(i);

        for (let j = 0; j < rows; j++) {
          result.A.set(j, positiveSumRowIndexS[i], colCandidate[j]);
        }
      }

      let prevS = result.S.clone();
      result.S = updateMatrixS(result.A, result.S, originalMatrix, 0, {
        maxFBIteration,
        toleranceFB
      });
      result = checkMatrixS(result, originalMatrix);
      result.A = updateMatrixA(result.A, result.S, originalMatrix, 0);

      if (Matrix.sub(prevS, result.S).norm() / result.S.norm() < toleranceFBInit) {
        break;
      }
    }

    return result;
  }

  function normalize(data, options) {
    const {
      normOnA
    } = options;
    let DS = normBy(data.S.transpose(), 'column');
    let DA = normBy(data.A, 'column');
    let D = Matrix.mul(DS, DA);
    let onS, onA;

    if (normOnA) {
      onS = (index, c) => data.S.get(index, c) * D.get(0, index) / DS.get(0, index);

      onA = (index, r) => data.A.get(r, index) / DA.get(0, index);
    } else {
      onS = (index, c) => data.S.get(index, c) / DS.get(0, index);

      onA = (index, r) => data.A.get(r, index) * D.get(0, index) / DA.get(0, index);
    }

    const sColumns = data.S.columns;
    const aRows = data.A.rows;

    for (let index = 0; index < D.columns; index++) {
      let valueForS, valueForA;

      if (D.get(0, index) > 0) {
        valueForS = onS;
        valueForA = onA;
      } else {
        valueForA = () => 0;

        valueForS = () => 0;
      }

      for (let c = 0; c < sColumns; c++) {
        data.S.set(index, c, valueForS(index, c));
      }

      for (let r = 0; r < aRows; r++) {
        data.A.set(r, index, valueForA(index, r));
      }
    }

    return data;
  }

  function getMedians(X, by) {
    let medians = [];
    let rows = X.rows;
    let columns = X.columns;

    switch (by) {
      case 'column':
        for (let i = 0; i < columns; i++) {
          medians.push(median$1(X.getColumn(i)));
        }

        medians = Matrix.from1DArray(1, columns, medians);
        break;

      default:
        for (let i = 0; i < rows; i++) {
          medians.push(median$1(X.getRow(i)));
        }

        medians = Matrix.from1DArray(rows, 1, medians);
    }

    return medians;
  }

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

  function updateLambda(data, originalMatrix, options = {}) {
    let {
      refinementBeginning,
      tauMAD
    } = options;
    let {
      iteration,
      lambda,
      A,
      S
    } = data;
    if (refinementBeginning <= iteration) return lambda;
    let sigmaResidue;

    if (options.lambdaInf !== undefined) {
      sigmaResidue = options.lambdaInf / options.tauMAD;
    } else if (options.addStd !== undefined) {
      sigmaResidue = options.addStd;
    } else {
      let alY = Matrix.sub(originalMatrix, A.mmul(S)).to1DArray();
      let result = dimMADstd(Matrix.from1DArray(1, alY.length, alY), 'row');
      sigmaResidue = result.get(0, 0);
    }

    let nextLambda = Math.max(tauMAD * sigmaResidue, lambda - 1 / (refinementBeginning - iteration));
    return nextLambda;
  }

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

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

    for (let iter = 0; iter < maximumIteration; iter++) {
      data.iteration = iter;
      data.S = updateMatrixS(data.A, data.S, originalMatrix, data.lambda, options);
      data = checkMatrixS(data, originalMatrix);
      data = normalize(data, {
        normOnA: false
      });
      if (iter > refinementBeginning) normConstrained = true;
      data.A = updateMatrixA(data.A, data.S, originalMatrix, {
        maxFBIteration,
        toleranceFB,
        normConstrained,
        lambda: 0
      });
      data = normalize(data, {
        normOnA: true
      });
      data.lambda = updateLambda(data, originalMatrix, {
        refinementBeginning,
        tauMAD
      });
    }

    if (useTranspose) {
      let temp = data.A.transpose();
      data.A = data.S.transpose();
      data.S = temp;
    }

    return data;
  }

  /**
   * Creates new PCA (Principal Component Analysis) from the dataset
   * @param {Matrix} dataset - dataset or covariance matrix.
   * @param {Object} [options]
   * @param {boolean} [options.isCovarianceMatrix=false] - true if the dataset is a covariance matrix.
   * @param {string} [options.method='SVD'] - select which method to use: SVD (default), covarianceMatrirx or NIPALS.
   * @param {number} [options.nCompNIPALS=2] - number of components to be computed with NIPALS.
   * @param {boolean} [options.center=true] - should the data be centered (subtract the mean).
   * @param {boolean} [options.scale=false] - should the data be scaled (divide by the standard deviation).
   * @param {boolean} [options.ignoreZeroVariance=false] - ignore columns with zero variance if `scale` is `true`.
   * */

  class PCA {
    constructor(dataset, options = {}) {
      if (dataset === true) {
        const model = options;
        this.center = model.center;
        this.scale = model.scale;
        this.means = model.means;
        this.stdevs = model.stdevs;
        this.U = Matrix.checkMatrix(model.U);
        this.S = model.S;
        this.R = model.R;
        this.excludedFeatures = model.excludedFeatures || [];
        return;
      }

      dataset = new Matrix(dataset);
      const {
        isCovarianceMatrix = false,
        method = 'SVD',
        nCompNIPALS = 2,
        center = true,
        scale = false,
        ignoreZeroVariance = false
      } = options;
      this.center = center;
      this.scale = scale;
      this.means = null;
      this.stdevs = null;
      this.excludedFeatures = [];

      if (isCovarianceMatrix) {
        // User provided a covariance matrix instead of dataset.
        this._computeFromCovarianceMatrix(dataset);

        return;
      }

      this._adjust(dataset, ignoreZeroVariance);

      switch (method) {
        case 'covarianceMatrix':
          {
            // User provided a dataset but wants us to compute and use the covariance matrix.
            const covarianceMatrix = new MatrixTransposeView(dataset).mmul(dataset).div(dataset.rows - 1);

            this._computeFromCovarianceMatrix(covarianceMatrix);

            break;
          }

        case 'NIPALS':
          {
            this._computeWithNIPALS(dataset, nCompNIPALS);

            break;
          }

        case 'SVD':
          {
            const svd = new SingularValueDecomposition(dataset, {
              computeLeftSingularVectors: false,
              computeRightSingularVectors: true,
              autoTranspose: true
            });
            this.U = svd.rightSingularVectors;
            const singularValues = svd.diagonal;
            const eigenvalues = [];

            for (const singularValue of singularValues) {
              eigenvalues.push(singularValue * singularValue / (dataset.rows - 1));
            }

            this.S = eigenvalues;
            break;
          }

        default:
          {
            throw new Error(`unknown method: ${method}`);
          }
      }
    }
    /**
     * Load a PCA model from JSON
     * @param {Object} model
     * @return {PCA}
     */


    static load(model) {
      if (typeof model.name !== 'string') {
        throw new TypeError('model must have a name property');
      }

      if (model.name !== 'PCA') {
        throw new RangeError(`invalid model: ${model.name}`);
      }

      return new PCA(true, model);
    }
    /**
     * Project the dataset into the PCA space
     * @param {Matrix} dataset
     * @param {Object} options
     * @return {Matrix} dataset projected in the PCA space
     */


    predict(dataset, options = {}) {
      const {
        nComponents = this.U.columns
      } = options;
      dataset = new Matrix(dataset);

      if (this.center) {
        dataset.subRowVector(this.means);

        if (this.scale) {
          for (let i of this.excludedFeatures) {
            dataset.removeColumn(i);
          }

          dataset.divRowVector(this.stdevs);
        }
      }

      let predictions = dataset.mmul(this.U);
      return predictions.subMatrix(0, predictions.rows - 1, 0, nComponents - 1);
    }
    /**
     * Calculates the inverse PCA transform
     * @param {Matrix} dataset
     * @return {Matrix} dataset projected in the PCA space
     */


    invert(dataset) {
      dataset = Matrix.checkMatrix(dataset);
      let inverse = dataset.mmul(this.U.transpose());

      if (this.center) {
        if (this.scale) {
          inverse.mulRowVector(this.stdevs);
        }

        inverse.addRowVector(this.means);
      }

      return inverse;
    }
    /**
     * Returns the proportion of variance for each component
     * @return {[number]}
     */


    getExplainedVariance() {
      let sum = 0;

      for (const s of this.S) {
        sum += s;
      }

      return this.S.map(value => value / sum);
    }
    /**
     * Returns the cumulative proportion of variance
     * @return {[number]}
     */


    getCumulativeVariance() {
      let explained = this.getExplainedVariance();

      for (let i = 1; i < explained.length; i++) {
        explained[i] += explained[i - 1];
      }

      return explained;
    }
    /**
     * Returns the Eigenvectors of the covariance matrix
     * @returns {Matrix}
     */


    getEigenvectors() {
      return this.U;
    }
    /**
     * Returns the Eigenvalues (on the diagonal)
     * @returns {[number]}
     */


    getEigenvalues() {
      return this.S;
    }
    /**
     * Returns the standard deviations of the principal components
     * @returns {[number]}
     */


    getStandardDeviations() {
      return this.S.map(x => Math.sqrt(x));
    }
    /**
     * Returns the loadings matrix
     * @return {Matrix}
     */


    getLoadings() {
      return this.U.transpose();
    }
    /**
     * Export the current model to a JSON object
     * @return {Object} model
     */


    toJSON() {
      return {
        name: 'PCA',
        center: this.center,
        scale: this.scale,
        means: this.means,
        stdevs: this.stdevs,
        U: this.U,
        S: this.S,
        excludedFeatures: this.excludedFeatures
      };
    }

    _adjust(dataset, ignoreZeroVariance) {
      if (this.center) {
        const mean = dataset.mean('column');
        const stdevs = this.scale ? dataset.standardDeviation('column', {
          mean
        }) : null;
        this.means = mean;
        dataset.subRowVector(mean);

        if (this.scale) {
          for (let i = 0; i < stdevs.length; i++) {
            if (stdevs[i] === 0) {
              if (ignoreZeroVariance) {
                dataset.removeColumn(i);
                stdevs.splice(i, 1);
                this.excludedFeatures.push(i);
                i--;
              } else {
                throw new RangeError(`Cannot scale the dataset (standard deviation is zero at index ${i}`);
              }
            }
          }

          this.stdevs = stdevs;
          dataset.divRowVector(stdevs);
        }
      }
    }

    _computeFromCovarianceMatrix(dataset) {
      const evd = new EigenvalueDecomposition(dataset, {
        assumeSymmetric: true
      });
      this.U = evd.eigenvectorMatrix;
      this.U.flipRows();
      this.S = evd.realEigenvalues;
      this.S.reverse();
    }

    _computeWithNIPALS(dataset, nCompNIPALS) {
      this.U = new Matrix(nCompNIPALS, dataset.columns);
      this.S = [];
      let x = dataset;

      for (let i = 0; i < nCompNIPALS; i++) {
        let dc = new nipals(x);
        this.U.setRow(i, dc.w.transpose());
        this.S.push(Math.pow(dc.s.get(0, 0), 2));
        x = dc.xResidual;
      }

      this.U = this.U.transpose(); // to be compatible with API
    }

  }

  /**
   * Estimate the number of pure components of each range by NIPALS PCA
   * @param {Chromatogram} chromatogram - GC/MS chromatogram where make the estimation
   * @param {Object} options - options with range and the matrix
   * @param {Object} [options.range] - Range of retention times.
   * @param {Number} [options.range.from] - lower limit in the retention time.
   * @param {Number} [options.range.to] - upper limit in the retention time.
   * @param {Array<Array>} [options.matrix] - matrix to compute the number of pure components, if does not exist it will be computed.
   */

  function estimateNbPureComponents(chromatogram, options = {}) {
    let {
      range,
      matrix
    } = options;
    if (!matrix) matrix = chromatogram.getMzVsTimesMatrix(range).matrix;
    let pca = new PCA(matrix, {
      method: 'NIPALS',
      nCompNIPALS: 10,
      scale: true,
      ignoreZeroVariance: true
    });
    let s = pca.getExplainedVariance();
    let rank = 1;
    let cumulative = s[0];

    while ((cumulative - s[rank]) / cumulative < 0.88 && rank < s.length) {
      cumulative += s[rank];
      rank++;
    }

    return rank;
  }

  /**
   * Performing non-negative matrix factorization solving argmin_(A >= 0, S >= 0) 1 / 2 * ||Y - AS||_2^2 + lambda * ||S||_1
   * @param {Chromatogram} chromatogram - GC/MS chromatogram where make the estimation.
   * @param {Object} [options = {}] - Options of ngmca factorization method
   * @param {Number} [options.rank] - number of pure components, if it's undefined it will be estimated by explained variance of PCA.
   * @param {Object} [options.range] - Range with from to.
   * @param {Number} [options.range.from] - lower limit in the retention time.
   * @param {Number} [options.range.to] - upper limit in the retention time.
   * @param {Number} [options.nmfOptions = {}] - options to Non negative factorization (deconvolution method).
   * @param {Number} [options.nmfOptions.maximumIteration = 500] - Maximum number of iterations.
   * @param {Number} [options.nmfOptions.maxFBIteration = 80] - Maximum number of iterations of the Forward-Backward subroutine.
   * @param {Number} [options.nmfOptions.toleranceFB = 1e-5] - relative difference tolerance for convergence of the Forward-Backward sub-iterations.
   * @param {Number} [options.nmfOptions.phaseRatio = 0.8] - transition between decreasing thresholding phase and refinement phase in percent of the iterations.
   * @param {Boolean} [options.useTranspose = false] - if true the originalMatrix is transposed.
   */

  function deconvolution(chromatogram, options = {}) {
    let {
      range,
      rank,
      nmfOptions = {}
    } = options;
    let {
      matrix,
      mzAxis,
      times
    } = chromatogram.getMzVsTimesMatrix(range);
    if (!rank) rank = estimateNbPureComponents(chromatogram, {
      range,
      matrix
    });

    if (rank < 1) {
      throw new RangeError(`Rank should be a positive number for ${range.from} - ${range.to}`);
    }

    let result = nGMCA(matrix, rank, nmfOptions);
    let maxByRow = [];

    for (let i = 0; i < result.S.rows; i++) {
      maxByRow.push(result.S.maxRow(i));
    }

    result.S.scale('row', {
      scale: maxByRow
    });
    result.A.scale('column', {
      scale: maxByRow.map(e => 1 / e)
    });
    return Object.assign({
      matrix,
      times,
      mzAxis,
      rank
    }, {
      profile: result.A,
      component: result.S
    });
  }

  function merge$1(chromatogram, options = {}) {
    let {
      mergeThreshold = 0.3,
      seriesName = 'ms',
      range = {}
    } = options;
    const time = chromatogram.getTimes();
    chromatogram.requiresSeries(seriesName);
    let series = chromatogram.series[seriesName];

    if (series.dimension !== 2) {
      throw new Error(`The series "${seriesName}" is not of dimension 2`);
    }

    if (!range || range.from > time[time.length - 1] || range.to < time[0]) {
      return {
        x: [],
        y: []
      };
    }

    let {
      fromIndex,
      toIndex
    } = xGetFromToIndex$1(time, range);
    let data = series.data.slice(fromIndex, toIndex + 1).map(datum => xySortX$1({
      x: datum[0],
      y: datum[1]
    }));
    return { ...xyArrayWeightedMerge$1(data, {
        delta: mergeThreshold
      }),
      from: {
        index: fromIndex,
        time: time[fromIndex]
      },
      to: {
        index: toIndex,
        time: time[toIndex]
      }
    };
  }

  function getPeaks(chromatogram, options = {}) {
    const {
      heightFilter = 2,
      seriesName = 'tic',
      broadenPeaksOptions = {
        factor: 1,
        overlap: false
      }
    } = options;
    const series = chromatogram.getSeries(seriesName).data;
    const times = chromatogram.getTimes(); // first peak selection

    let peakList = gsd({
      x: times,
      y: series
    }, {
      noiseLevel: 0,
      realTopDetection: false,
      smoothY: true,
      sgOptions: {
        windowSize: 5,
        polynomial: 2
      },
      heightFactor: 2
    }); // filter height by factor

    let medianHeight = median(series);
    peakList = peakList.filter(val => val.height > medianHeight * heightFilter);
    peakList.sort((a, b) => a.x - b.x);

    if (broadenPeaksOptions) {
      peakList = broadenPeaks(peakList, broadenPeaksOptions);
    }

    return peakList.map(peak => ({
      from: peak.from,
      to: peak.to,
      inflectionPoints: {
        from: Math.min(peak.left.x, peak.right.x),
        to: Math.max(peak.left.x, peak.right.x)
      },
      retentionTime: peak.x,
      intensity: peak.y
    }));
  }

  function filter(chromatogram, callback, options = {}) {
    const {
      copy = false
    } = options;

    if (copy) {
      chromatogram = chromatogram.copy();
    }

    let times = chromatogram.getTimes();
    let newTimes = [];
    let indexToKeep = [];

    for (let i = 0; i < times.length; i++) {
      if (callback(i, times[i])) {
        indexToKeep.push(i);
        newTimes.push(times[i]);
      }
    }

    chromatogram.times = newTimes;

    for (let key of chromatogram.getSeriesNames()) {
      const series = chromatogram.getSeries(key);
      series.keep(indexToKeep);
    }

    return chromatogram;
  }

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

  /**
   * Return the submatrix, times, and mass x axis for each range
   * @param {Object} range - from - to of the TIC
   * @param {Number} [range.from] - lower limit in the retention time
   * @param {Number} [range.to] - upper limit in the retention time
   * @return {Object} - submatrix, times and m/z axis of the range.
   */

  function getMzVsTimesMatrix(chromatogram, range) {
    let {
      from,
      to
    } = range;
    let fromIndex = chromatogram.getClosestTime(from);
    let toIndex = chromatogram.getClosestTime(to);
    let data = chromatogram.series.ms.data.slice(fromIndex, toIndex);
    let times = chromatogram.times.slice(fromIndex, toIndex);
    let mzAxis = new Set();

    for (let i = 0; i < data.length; i++) {
      let spectrum = data[i];

      for (let j = 0; j < spectrum[0].length; j++) {
        mzAxis.add(Math.round(spectrum[0][j]));
      }
    }

    mzAxis = Array.from(mzAxis).sort((a, b) => a - b);
    const nbPoints = mzAxis.length;
    const matrix = new Matrix(data.length, nbPoints);

    for (let i = 0; i < data.length; i++) {
      let element = data[i];

      for (let j = 0; j < element[0].length; j++) {
        let xValue = Math.round(element[0][j]);
        let index = xFindClosestIndex$2(mzAxis, xValue);
        matrix.set(i, index, element[1][j]);
      }
    }

    return {
      times,
      mzAxis,
      matrix
    };
  }

  function baselineCorrection(points, fromTo, kind) {
    const deltaTime = points.x[fromTo.toIndex] - points.x[fromTo.fromIndex];
    const fromHeight = points.y[fromTo.fromIndex];
    const toHeight = points.y[fromTo.toIndex];
    let baseline = 0;
    let from = 0;
    let to = 0;

    switch (kind) {
      case 'trapezoid':
        baseline = deltaTime * (fromHeight + toHeight) / 2;
        from = fromHeight;
        to = toHeight;
        break;

      case 'min':
        from = Math.min(fromHeight, toHeight);
        to = from;
        baseline = deltaTime * from;
        break;

      default:
        throw new Error(`Unknown baseline method "${kind}"`);
    }

    return {
      value: baseline,
      from,
      to
    };
  }

  function integrate(chromatogram, ranges, options = {}) {
    const {
      baseline,
      seriesName = 'tic'
    } = options;

    if (!Array.isArray(ranges)) {
      throw new Error('Ranges must be an array of type [{from,to}]');
    }

    if (ranges.length === 0) {
      return [];
    }

    chromatogram.requiresSeries(seriesName);
    let series = chromatogram.series[seriesName];

    if (series.dimension !== 1) {
      throw new Error(`The series "${seriesName}" is not of dimension 1`);
    }

    const time = chromatogram.getTimes();
    let results = [];

    for (let range of ranges) {
      const fromTo = xGetFromToIndex$1(time, range);
      const integral = integrateRange({
        x: time,
        y: series.data
      }, fromTo, baseline);
      results.push(integral);
    }

    return results;
  }

  function integrateRange(points, fromTo, baseline) {
    let integration = xyIntegration$1(points, fromTo);

    if (baseline) {
      let correction = baselineCorrection(points, fromTo, baseline);
      return {
        integration: integration - correction.value,
        from: {
          time: points.x[fromTo.fromIndex],
          index: fromTo.fromIndex,
          baseline: correction.from
        },
        to: {
          time: points.x[fromTo.toIndex],
          index: fromTo.toIndex,
          baseline: correction.to
        }
      };
    } else {
      return {
        integration,
        from: {
          time: points.x[fromTo.fromIndex],
          index: fromTo.fromIndex,
          baseline: 0
        },
        to: {
          time: points.x[fromTo.toIndex],
          index: fromTo.toIndex,
          baseline: 0
        }
      };
    }
  }

  class Chromatogram {
    constructor(times, series) {
      this.series = {};
      this.times = [];

      if (!isAnyArray(times)) {
        throw new TypeError('times must be an array');
      }

      this.times = times;

      if (series) {
        for (const [name, value] of Object.entries(series)) {
          this.addSeries(name, value);
        }
      }
    }

    get length() {
      return this.times.length;
    }

    getSeries(seriesName) {
      this.requiresSeries(seriesName);
      return this.series[seriesName];
    }

    getSeries1D(seriesName) {
      const series = this.getSeries(seriesName);

      if (!series.is1D()) {
        throw new Error(`series ${seriesName} is not a 1D series`);
      }

      return series;
    }

    getSeries2D(seriesName) {
      const series = this.getSeries(seriesName);

      if (!series.is2D()) {
        throw new Error(`series ${seriesName} is not a 2D series`);
      }

      return series;
    }

    getSeriesNames() {
      return Object.keys(this.series);
    }

    hasMass() {
      return this.hasSeries('ms');
    }

    deleteSeries(seriesName) {
      this.requiresSeries(seriesName);
      delete this.series[seriesName];
      return this;
    }

    addSeries(seriesName, array, options = {}) {
      if (this.hasSeries(seriesName) && !options.force) {
        throw new Error(`A series with name "${seriesName}" already exists`);
      }

      if (this.times.length !== array.length) {
        throw new Error(`The series size is not the same as the times size`);
      }

      this.series[seriesName] = seriesFromArray(array);
      this.series[seriesName].name = seriesName;
      return this;
    }

    hasSeries(seriesName) {
      return typeof this.series[seriesName] !== 'undefined';
    }

    requiresSeries(seriesName) {
      if (!this.hasSeries(seriesName)) {
        throw new Error(`The series "${seriesName}" does not exist`);
      }
    }

    get firstTime() {
      return this.times[0];
    }

    get lastTime() {
      return this.times[this.length - 1];
    }

    getTimes() {
      return this.times;
    }

    setTimes(times) {
      if (times.length !== this.times.length) {
        throw new Error('New times must have the same length as the old ones');
      }

      this.times = times;
    }

    rescaleTime(conversionFunction) {
      this.times = this.times.map(time => conversionFunction(time));
      return this;
    }

    filter(callback, options) {
      return filter(this, callback, options);
    }

    getPeaks(options) {
      return getPeaks(this, options);
    }

    calculateTic(options = {}) {
      if (!this.hasSeries('tic') || options.force) {
        const tic = calculateTic(this);
        this.addSeries('tic', tic, {
          force: true
        });
      }

      return this;
    }

    calculateLength(seriesName, options = {}) {
      if (!this.hasSeries('length') || options.force) {
        const length = calculateLength(this, seriesName);
        this.addSeries('length', length, {
          force: true
        });
      }

      return this;
    }

    calculateBpc(options = {}) {
      if (!this.hasSeries('bpc') || options.force) {
        const bpc = calculateBpc(this);
        this.addSeries('bpc', bpc, {
          force: true
        });
      }

      return this;
    }

    calculateEic(targetMass, options = {}) {
      const {
        seriesName = `ms${targetMass}±${options.slotWidth / 2 || 0.5}`,
        cache = false
      } = options;
      if (cache && this.hasSeries(seriesName)) return this.getSeries(seriesName);
      const result = calculateEic(this, targetMass, options);
      this.addSeries(seriesName, result, options);
      return this.getSeries(seriesName);
    }

    calculateForMF(targetMF, options = {}) {
      const {
        seriesName = `${targetMF}(${options.ionizations || 'H+'})±${options.slotWidth / 2 || 0.5}${options.threshold ? `(${options.threshold})` : ''}`,
        cache = false
      } = options;
      if (cache && this.hasSeries(seriesName)) return this.getSeries(seriesName);
      const result = calculateForMF(this, targetMF, options);
      this.addSeries(seriesName, result, options);
      return this.getSeries(seriesName);
    }

    integrate(ranges, options) {
      return integrate(this, ranges, options);
    }

    merge(options) {
      return merge$1(this, options);
    }

    getClosestTime(time) {
      return xFindClosestIndex$2(this.getTimes(), time);
    }

    getClosestData(time, options = {}) {
      return getClosestData(this, time, options);
    }

    copy() {
      const json = JSON.parse(JSON.stringify(this));
      return fromJSON(json);
    }

    meanFilter(seriesName, options = {}) {
      const {
        seriesName: newSeriesName = 'msMean'
      } = options;

      if (this.hasSeries(newSeriesName) && !options.force) {
        throw new Error(`A series with name "${seriesName}" already exists`);
      }

      const newSeries = meanFilter(this, seriesName, options);
      this.series[newSeriesName] = newSeries;
      return newSeries;
    }

    percentageFilter(seriesName, options = {}) {
      const {
        seriesName: newSeriesName = 'msPercentage'
      } = options;

      if (this.hasSeries(newSeriesName) && !options.force) {
        throw new Error(`A series with name "${seriesName}" already exists`);
      }

      const newSeries = percentageFilter(this, seriesName, options);
      this.series[newSeriesName] = newSeries;
      return newSeries;
    }

    applyLockMass(mfs, options) {
      return applyLockMass(this, mfs, options);
    }

    getMzVsTimesMatrix(range = {}) {
      return getMzVsTimesMatrix(this, range);
    }

    deconvolution(options = {}) {
      return deconvolution(this, options);
    }

    toJSON() {
      return {
        times: this.times,
        series: this.series
      };
    }

  }
  function fromJSON(json) {
    let series = json.series;
    let times = json.times;
    let chromatogram = new Chromatogram(times);

    if (Array.isArray(series)) {
      for (let i = 0; i < series.length; i++) {
        chromatogram.addSeries(series[i].name, series[i].data);
      }
    } else {
      for (let key of Object.keys(series)) {
        chromatogram.addSeries(key, series[key].data, {
          meta: series[key].meta
        });
      }
    }

    return chromatogram;
  }

  /**
   * Append MS spectra to a list of peaks
   * @param {Chromatogram} chromatogram
   * @param {Array<object>} peaks - Array of range {from:, to:}
   * @param {object} [options={}] - Options for the integral filtering
   * @param {number} [options.mergeThreshold=0.3] - Peaks that are under this value (in Da) will be merged
   * @param {number} [options.seriesName='ms'] - Maximum number of peaks for each mass spectra (when is Number.MAX_VALUE there's no filter)
   * @return {Array<object>} - A copy of ranges with ms appended
   */

  function appendMass(chromatogram, peaks, options = {}) {
    const {
      mergeThreshold = 0.3,
      seriesName = 'ms'
    } = options;
    const result = []; // integrate MS

    for (const peak of peaks) {
      const massSpectrum = merge$1(chromatogram, {
        mergeThreshold,
        seriesName,
        range: peak
      });
      result.push({ ...peak,
        ms: massSpectrum
      });
    }

    return result;
  }

  function massFilter(massXYObject, options = {}) {
    const {
      thresholdFactor = 0,
      maxNumberPeaks = Number.MAX_VALUE,
      groupWidth = 0
    } = options;
    let max = -1;
    let massList = new Array(massXYObject.x.length);

    for (let i = 0; i < massXYObject.x.length; ++i) {
      massList[i] = {
        x: massXYObject.x[i],
        y: massXYObject.y[i]
      };

      if (massXYObject.y[i] > max) {
        max = massXYObject.y[i];
      }
    } // filters based in thresholdFactor


    max *= thresholdFactor;
    let filteredList = massList.filter(val => val.y > max); // filters based in maxNumberPeaks

    if (filteredList.length > maxNumberPeaks || groupWidth !== 0) {
      filteredList.sort((a, b) => b.y - a.y); // filters based in groupWidth

      filteredList = moreDistinct(filteredList, maxNumberPeaks, groupWidth);
      filteredList.sort((a, b) => a.x - b.x);
    }

    let ans = {
      x: new Array(filteredList.length),
      y: new Array(filteredList.length)
    };

    for (let i = 0; i < filteredList.length; ++i) {
      ans.x[i] = filteredList[i].x;
      ans.y[i] = filteredList[i].y;
    }

    return ans;
  }
  /**
   * Filters based in groupWidth
   * @ignore
   * @param {Array<object>} list - Sorted list of XY-objects to be filtered
   * @param {number} maxNumberPeaks - Maximum number of peaks for each mass spectra
   * @param {number} groupWidth - When find a max can't be another max in a radius of this size
   * @return {Array<object>} - List of XY-objects filtered
   */

  function moreDistinct(list, maxNumberPeaks, groupWidth) {
    let len = 0;

    if (maxNumberPeaks > list.length) {
      maxNumberPeaks = list.length;
    }

    let filteredList = new Array(maxNumberPeaks);

    for (let i = 0; i < list.length && len < maxNumberPeaks; ++i) {
      let outRange = true;

      for (let j = 0; j < len && outRange; ++j) {
        outRange = outRange && !(list[i].x > filteredList[j].x - groupWidth && list[i].x < filteredList[j].x + groupWidth);
      }

      if (outRange) {
        filteredList[len++] = list[i];
      }
    }

    filteredList.length = len;
    return filteredList;
  }

  function vectorify(ranges, options = {}) {
    const {
      massPower = 3,
      intPower = 0.6
    } = options;
    let filter = options.thresholdFactor || options.maxNumberPeaks || options.groupWidth;
    let vector = new Array(ranges.length);

    if (filter) {
      const filterOptions = {
        thresholdFactor: options.thresholdFactor,
        maxNumberPeaks: options.maxNumberPeaks,
        groupWidth: options.groupWidth
      };

      for (let i = 0; i < ranges.length; ++i) {
        let len = ranges[i].ms.x.length;
        vector[i] = {
          x: ranges[i].ms.x,
          y: new Array(len)
        };

        for (let j = 0; j < len; ++j) {
          vector[i].y[j] = Math.pow(ranges[i].ms.x[j], massPower) * Math.pow(ranges[i].ms.y[j], intPower);
        }

        vector[i] = massFilter(vector[i], filterOptions);
      }
    } else {
      for (let i = 0; i < ranges.length; ++i) {
        let len = ranges[i].ms.x.length;
        vector[i] = {
          x: ranges[i].ms.x,
          y: new Array(len)
        };

        for (let j = 0; j < len; ++j) {
          vector[i].y[j] = Math.pow(ranges[i].ms.x[j], massPower) * Math.pow(ranges[i].ms.y[j], intPower);
        }
      }
    }

    return vector;
  }

  function cosineSimilarity(ms1x, ms1y, ms2x, ms2y) {
    let index1 = 0;
    let index2 = 0;
    let product = 0;
    let norm1 = 0;
    let norm2 = 0;

    while (index1 < ms1x.length || index2 < ms2x.length) {
      let w1 = ms1y[index1];
      let w2 = ms2y[index2];

      if (index2 === ms2x.length || ms1x[index1] < ms2x[index2]) {
        norm1 += w1 * w1;
        ++index1;
      } else if (index1 === ms1x.length || ms2x[index2] < ms1x[index1]) {
        norm2 += w2 * w2;
        ++index2;
      } else {
        product += w1 * w2;
        norm1 += w1 * w1;
        norm2 += w2 * w2;
        ++index1;
        ++index2;
      }
    }

    let norm1Norm2 = norm1 * norm2;

    if (norm1Norm2 === 0) {
      return 0;
    } else {
      return product * product / norm1Norm2;
    }
  }

  /**
   * Preprocessing task over the signalsj
   * @ignore
   * @param {Chromatogram} chromatogram - Chromatogram to process
   * @param {object} [options] - Options object (same as spectraComparison)
   * @return {{peaks: Array<object>, integratedMs: Array<object>, vector: Array<object>}} - Array of peaks, integrated mass spectra and weighted mass spectra
   */

  function preprocessing(chromatogram, options) {
    // peak picking
    let peaks = getPeaks(chromatogram, options);
    peaks = peaks.sort((a, b) => a.from - b.to); // integrate mass in the peaks

    let integratedMs = appendMass(chromatogram, peaks, options);
    let vector = vectorify(integratedMs, options);
    return {
      peaks,
      integratedMs,
      vector
    };
  }

  const defaultOptions$2 = {
    thresholdFactor: 0,
    maxNumberPeaks: Number.MAX_VALUE,
    groupWidth: 0,
    heightFilter: 2,
    massPower: 3,
    intPower: 0.6,
    similarityThreshold: 0.7
  };
  function spectraComparison(chrom1, chrom2, options = {}) {
    options = Object.assign({}, defaultOptions$2, options); // peak picking

    let reference = preprocessing(chrom1, options);
    let sample = preprocessing(chrom2, options); // similarity between MS

    const len = Math.max(sample.peaks.length, reference.peaks.length);
    let similarityPeaks = {
      chrom1: new Array(len),
      chrom2: new Array(len),
      similarity: new Array(len)
    };
    let similarLen = 0; // Similarity matrix

    for (let i = 0; i < sample.peaks.length; ++i) {
      let max = {
        similarity: -3
      };
      let biggerCounter = 0;

      for (let j = 0; j < reference.peaks.length; ++j) {
        let sim = cosineSimilarity(sample.vector[i].x, sample.vector[i].y, reference.vector[j].x, reference.vector[j].y);

        if (sim > options.similarityThreshold && sim > max.similarity) {
          max = {
            similarity: sim,
            chrom1: reference.peaks[j],
            chrom2: sample.peaks[i]
          };
        }

        if (sim > options.similarityThreshold) {
          ++biggerCounter;
        }
      }

      if (biggerCounter === 1) {
        similarityPeaks.chrom1[similarLen] = max.chrom1;
        similarityPeaks.chrom2[similarLen] = max.chrom2;
        similarityPeaks.similarity[similarLen++] = max.similarity;
      }
    }

    similarityPeaks.chrom1.length = similarLen;
    similarityPeaks.chrom2.length = similarLen;
    let duplicates = {};

    for (let i = 0; i < similarLen; ++i) {
      if (duplicates[similarityPeaks.chrom1[i].retentionTime]) {
        duplicates[similarityPeaks.chrom1[i].retentionTime].push(i);
      } else {
        duplicates[similarityPeaks.chrom1[i].retentionTime] = [i];
      }
    }

    let peaksChrom1 = [];
    let peaksChrom2 = [];
    let peaksSimilarity = [];

    for (let val in duplicates) {
      if (duplicates[val].length === 1) {
        peaksChrom1.push(similarityPeaks.chrom1[duplicates[val][0]]);
        peaksChrom2.push(similarityPeaks.chrom2[duplicates[val][0]]);
        peaksSimilarity.push(similarityPeaks.similarity[duplicates[val][0]]);
      }
    }

    return {
      peaksFirst: peaksChrom1,
      peaksSecond: peaksChrom2,
      peaksSimilarity: peaksSimilarity
    };
  }

  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(x) || !isAnyArray(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(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(x) || !isAnyArray(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 PolynomialRegression extends BaseRegression {
    constructor(x, y, degree) {
      super();

      if (x === true) {
        this.degree = y.degree;
        this.powers = y.powers;
        this.coefficients = y.coefficients;
      } else {
        checkArraySize(x, y);
        regress(this, x, y, degree);
      }
    }

    _predict(x) {
      let y = 0;

      for (let k = 0; k < this.powers.length; k++) {
        y += this.coefficients[k] * Math.pow(x, this.powers[k]);
      }

      return y;
    }

    toJSON() {
      return {
        name: 'polynomialRegression',
        degree: this.degree,
        powers: this.powers,
        coefficients: this.coefficients
      };
    }

    toString(precision) {
      return this._toFormula(precision, false);
    }

    toLaTeX(precision) {
      return this._toFormula(precision, true);
    }

    _toFormula(precision, isLaTeX) {
      let sup = '^';
      let closeSup = '';
      let times = ' * ';

      if (isLaTeX) {
        sup = '^{';
        closeSup = '}';
        times = '';
      }

      let fn = '';
      let str = '';

      for (let k = 0; k < this.coefficients.length; k++) {
        str = '';

        if (this.coefficients[k] !== 0) {
          if (this.powers[k] === 0) {
            str = maybeToPrecision(this.coefficients[k], precision);
          } else {
            if (this.powers[k] === 1) {
              str = `${maybeToPrecision(this.coefficients[k], precision) + times}x`;
            } else {
              str = `${maybeToPrecision(this.coefficients[k], precision) + times}x${sup}${this.powers[k]}${closeSup}`;
            }
          }

          if (this.coefficients[k] > 0 && k !== this.coefficients.length - 1) {
            str = ` + ${str}`;
          } else if (k !== this.coefficients.length - 1) {
            str = ` ${str}`;
          }
        }

        fn = str + fn;
      }

      if (fn.charAt(0) === '+') {
        fn = fn.slice(1);
      }

      return `f(x) = ${fn}`;
    }

    static load(json) {
      if (json.name !== 'polynomialRegression') {
        throw new TypeError('not a polynomial regression model');
      }

      return new PolynomialRegression(true, json);
    }

  }

  function regress(pr, x, y, degree) {
    const n = x.length;
    let powers;

    if (Array.isArray(degree)) {
      powers = degree;
      degree = powers.length;
    } else {
      degree++;
      powers = new Array(degree);

      for (let k = 0; k < degree; k++) {
        powers[k] = k;
      }
    }

    const F = new Matrix(n, degree);
    const Y = new Matrix([y]);

    for (let k = 0; k < degree; k++) {
      for (let i = 0; i < n; i++) {
        if (powers[k] === 0) {
          F.set(i, k, 1);
        } else {
          F.set(i, k, Math.pow(x[i], powers[k]));
        }
      }
    }

    const FT = new MatrixTransposeView(F);
    const A = FT.mmul(F);
    const B = FT.mmul(new MatrixTransposeView(Y));
    pr.degree = degree - 1;
    pr.powers = powers;
    pr.coefficients = solve(A, B).to1DArray();
  }

  function scaleAlignment(reference, sample, options = {}) {
    const {
      computeQuality = false,
      polynomialDegree = 3
    } = options;
    let referenceTime = reference.map(val => val.retentionTime);
    let sampleTime = sample.map(val => val.retentionTime);
    const regression = new PolynomialRegression(sampleTime, referenceTime, polynomialDegree);
    let error = new Array(sample.length);

    for (let i = 0; i < sample.length; i++) {
      error[i] = reference[i].retentionTime - regression.predict(sample[i].retentionTime);
    }

    let ans = {
      scaleRegression: regression
    };

    if (computeQuality) {
      let score = regression.score(sampleTime, referenceTime);
      ans.r2 = score.r2;
      ans.error = error;
    }

    return ans;
  }

  /**
   * Calculates the Kovats retention index for a mass spectra of a n-alkane
   * @param {object} ms - A mass spectra object
   * @param {Array<number>} ms.x - Array of masses
   * @param {Array<number>} ms.y - Array of intensities
   * @return {number} - Kovats retention index
   */

  function kovats(ms, options = {}) {
    const {
      threshold = 0.01
    } = options; // we normalize the data and filter them

    let maxY = max(ms.y);
    let masses = [];
    let intensities = [];

    for (let i = 0; i < ms.x.length; i++) {
      if (ms.y[i] / maxY > threshold) {
        masses.push(ms.x[i]);
        intensities.push(ms.y[i] / maxY);
      }
    } // we find candidates


    let nAlcaneMasses = [];
    let fragmentMasses = [];

    for (let i = 0; i < masses.length; i++) {
      if ((masses[i] - 2) % 14 === 0) {
        nAlcaneMasses.push(masses[i]);
      }

      if ((masses[i] - 1) % 14 === 0) {
        fragmentMasses.push(masses[i]);
      }
    }

    if (nAlcaneMasses.length === 0) return {};
    let biggestMass = nAlcaneMasses.sort((a, b) => b - a)[0];
    fragmentMasses = fragmentMasses.filter(mass => mass < biggestMass);
    return {
      index: 100 * (biggestMass - 2) / 14,
      numberFragments: fragmentMasses.length,
      percentFragments: fragmentMasses.length / ((biggestMass - 2) / 14)
    };
  }

  function appendKovats(peaks) {
    const result = [];

    for (let peak of peaks) {
      result.push({ ...peak,
        kovats: kovats(peak.ms)
      });
    }

    return result;
  }

  var binarySearch = function (haystack, needle, comparator, low, high) {
    var mid, cmp;
    if (low === undefined) low = 0;else {
      low = low | 0;
      if (low < 0 || low >= haystack.length) throw new RangeError("invalid lower bound");
    }
    if (high === undefined) high = haystack.length - 1;else {
      high = high | 0;
      if (high < low || high >= haystack.length) throw new RangeError("invalid upper bound");
    }

    while (low <= high) {
      // The naive `low + high >>> 1` could fail for array lengths > 2**31
      // because `>>>` converts its operands to int32. `low + (high - low >>> 1)`
      // works for array lengths <= 2**32-1 which is also Javascript's max array
      // length.
      mid = low + (high - low >>> 1);
      cmp = +comparator(haystack[mid], needle, mid, haystack); // Too low.

      if (cmp < 0.0) low = mid + 1; // Too high.
      else if (cmp > 0.0) high = mid - 1; // Key found.
      else return mid;
    } // Key not found.


    return ~low;
  };

  var binarySearch$1 = binarySearch;

  const ascValue = (a, b) => a.index - b.index;

  const ascTime = (a, b) => a.time - b.time;

  function getKovatsConversionFunction(peaks, options = {}) {
    const {
      revert = false
    } = options;
    const kovatsConversionTable = peaks.map(peak => ({
      time: peak.x,
      index: peak.kovats.index
    }));

    if (revert) {
      const indexes = kovatsConversionTable.sort(ascValue);
      return index => {
        let position = binarySearch$1(indexes, {
          index
        }, ascValue);

        if (position < 0) {
          position = ~position; // handle extreme cases

          if (position === 0 || position === indexes.length) {
            return 0;
          }

          let smallerAlcane = indexes[position - 1].time;
          let largerAlcane = indexes[position].time;
          return (index - indexes[position - 1].index) * (largerAlcane - smallerAlcane) / 100 + smallerAlcane;
        } else {
          return indexes[position].time;
        }
      };
    } else {
      const times = kovatsConversionTable.sort(ascTime);
      return time => {
        let position = binarySearch$1(times, {
          time
        }, ascTime);

        if (position < 0) {
          position = ~position; // handle extreme cases

          if (position === 0 || position === times.length) {
            return 0;
          }

          let smallerAlcane = times[position - 1].time;
          let largerAlcane = times[position].time;
          return 100 * (time - smallerAlcane) / (largerAlcane - smallerAlcane) + times[position - 1].index;
        } else {
          return times[position].index;
        }
      };
    }
  }

  /**
   * Ensure that the data is string. If it is an ArrayBuffer it will be converted to string using TextDecoder.
   * @param blob
   * @param options
   * @returns
   */
  function ensureString$1(blob, options = {}) {
    if (typeof blob === 'string') {
      return blob;
    }

    if (ArrayBuffer.isView(blob) || blob instanceof ArrayBuffer) {
      const {
        encoding = guessEncoding(blob)
      } = options;
      const decoder = new TextDecoder(encoding);
      return decoder.decode(blob);
    }

    throw new TypeError(`blob must be a string, ArrayBuffer or ArrayBufferView`);
  }

  function guessEncoding(blob) {
    const uint8 = ArrayBuffer.isView(blob) ? new Uint8Array(blob.buffer, blob.byteOffset, blob.byteLength) : new Uint8Array(blob);

    if (uint8.length >= 2) {
      if (uint8[0] === 0xfe && uint8[1] === 0xff) {
        return 'utf-16be';
      }

      if (uint8[0] === 0xff && uint8[1] === 0xfe) {
        return 'utf-16le';
      }
    }

    return 'utf-8';
  }

  /**
   * Dynamically type a string
   * @param {string} value String to dynamically type
   * @returns {boolean|string|number}
   */
  function parseString(value) {
    if (value.length === 4 || value.length === 5) {
      let lowercase = value.toLowerCase();
      if (lowercase === 'true') return true;
      if (lowercase === 'false') return false;
    }

    let number = Number(value);

    if (number === 0 && !value.includes('0')) {
      return value;
    }

    if (!Number.isNaN(number)) return number;
    return value;
  }

  const GC_MS_FIELDS = ['TIC', '.RIC', 'SCANNUMBER'];
  function complexChromatogram(result) {
    let spectra = result.spectra;
    let length = spectra.length;
    let chromatogram = {
      times: new Array(length),
      series: {
        ms: {
          dimension: 2,
          data: new Array(length)
        }
      }
    };
    let existingGCMSFields = [];

    for (let i = 0; i < GC_MS_FIELDS.length; i++) {
      let label = convertMSFieldToLabel(GC_MS_FIELDS[i]);

      if (spectra[0][label]) {
        existingGCMSFields.push(label);
        chromatogram.series[label] = {
          dimension: 1,
          data: new Array(length)
        };
      }
    }

    for (let i = 0; i < length; i++) {
      let spectrum = spectra[i];
      chromatogram.times[i] = spectrum.pageValue;

      for (let j = 0; j < existingGCMSFields.length; j++) {
        chromatogram.series[existingGCMSFields[j]].data[i] = parseFloat(spectrum[existingGCMSFields[j]]);
      }

      if (spectrum.data) {
        chromatogram.series.ms.data[i] = [spectrum.data.x, spectrum.data.y];
      }
    }

    result.chromatogram = chromatogram;
  }
  function isMSField(canonicDataLabel) {
    return GC_MS_FIELDS.indexOf(canonicDataLabel) !== -1;
  }
  function convertMSFieldToLabel(value) {
    return value.toLowerCase().replace(/[^a-z0-9]/g, '');
  }

  function convertToFloatArray(stringArray) {
    let floatArray = [];

    for (let i = 0; i < stringArray.length; i++) {
      floatArray.push(parseFloat(stringArray[i]));
    }

    return floatArray;
  }

  function fastParseXYData(spectrum, value) {
    // TODO need to deal with result
    //  console.log(value);
    // we check if deltaX is defined otherwise we calculate it
    let yFactor = spectrum.yFactor;
    let deltaX = spectrum.deltaX;
    spectrum.isXYdata = true;
    let currentData = {
      x: [],
      y: []
    };
    spectrum.data = currentData;
    let currentX = spectrum.firstX;
    let currentY = spectrum.firstY; // we skip the first line
    //

    let endLine = false;
    let ascii;
    let i = 0;

    for (; i < value.length; i++) {
      ascii = value.charCodeAt(i);

      if (ascii === 13 || ascii === 10) {
        endLine = true;
      } else {
        if (endLine) break;
      }
    } // we proceed taking the i after the first line


    let newLine = true;
    let isDifference = false;
    let isLastDifference = false;
    let lastDifference = 0;
    let isDuplicate = false;
    let inComment = false;
    let currentValue = 0; // can be a difference or a duplicate

    let lastValue = 0; // must be the real last value

    let isNegative = false;
    let inValue = false;
    let skipFirstValue = false;
    let decimalPosition = 0;

    for (; i <= value.length; i++) {
      if (i === value.length) ascii = 13;else ascii = value.charCodeAt(i);

      if (inComment) {
        // we should ignore the text if we are after $$
        if (ascii === 13 || ascii === 10) {
          newLine = true;
          inComment = false;
        }
      } else {
        // when is it a new value ?
        // when it is not a digit, . or comma
        // it is a number that is either new or we continue
        if (ascii <= 57 && ascii >= 48) {
          // a number
          inValue = true;

          if (decimalPosition > 0) {
            currentValue += (ascii - 48) / Math.pow(10, decimalPosition++);
          } else {
            currentValue *= 10;
            currentValue += ascii - 48;
          }
        } else if (ascii === 44 || ascii === 46) {
          // a "," or "."
          inValue = true;
          decimalPosition++;
        } else {
          if (inValue) {
            // need to process the previous value
            if (newLine) {
              newLine = false; // we don't check the X value
              // console.log("NEW LINE",isDifference, lastDifference);
              // if new line and lastDifference, the first value is just a check !
              // that we don't check ...

              if (isLastDifference) skipFirstValue = true;
            } else {
              // need to deal with duplicate and differences
              if (skipFirstValue) {
                skipFirstValue = false;
              } else {
                if (isDifference) {
                  lastDifference = isNegative ? 0 - currentValue : currentValue;
                  isLastDifference = true;
                  isDifference = false;
                } else if (!isDuplicate) {
                  lastValue = isNegative ? 0 - currentValue : currentValue;
                }

                let duplicate = isDuplicate ? currentValue - 1 : 1;

                for (let j = 0; j < duplicate; j++) {
                  if (isLastDifference) {
                    currentY += lastDifference;
                  } else {
                    currentY = lastValue;
                  }

                  currentData.x.push(currentX);
                  currentData.y.push(currentY * yFactor);
                  currentX += deltaX;
                }
              }
            }

            isNegative = false;
            currentValue = 0;
            decimalPosition = 0;
            inValue = false;
            isDuplicate = false;
          } // positive SQZ digits @ A B C D E F G H I (ascii 64-73)


          if (ascii < 74 && ascii > 63) {
            inValue = true;
            isLastDifference = false;
            currentValue = ascii - 64;
          } else if (ascii > 96 && ascii < 106) {
            // negative SQZ digits a b c d e f g h i (ascii 97-105)
            inValue = true;
            isLastDifference = false;
            currentValue = ascii - 96;
            isNegative = true;
          } else if (ascii === 115) {
            // DUP digits S T U V W X Y Z s (ascii 83-90, 115)
            inValue = true;
            isDuplicate = true;
            currentValue = 9;
          } else if (ascii > 82 && ascii < 91) {
            inValue = true;
            isDuplicate = true;
            currentValue = ascii - 82;
          } else if (ascii > 73 && ascii < 83) {
            // positive DIF digits % J K L M N O P Q R (ascii 37, 74-82)
            inValue = true;
            isDifference = true;
            currentValue = ascii - 73;
          } else if (ascii > 105 && ascii < 115) {
            // negative DIF digits j k l m n o p q r (ascii 106-114)
            inValue = true;
            isDifference = true;
            currentValue = ascii - 105;
            isNegative = true;
          } else if (ascii === 36 && value.charCodeAt(i + 1) === 36) {
            // $ sign, we need to check the next one
            inValue = true;
            inComment = true;
          } else if (ascii === 37) {
            // positive DIF digits % J K L M N O P Q R (ascii 37, 74-82)
            inValue = true;
            isDifference = true;
            currentValue = 0;
            isNegative = false;
          } else if (ascii === 45) {
            // a "-"
            // check if after there is a number, decimal or comma
            let ascii2 = value.charCodeAt(i + 1);

            if (ascii2 >= 48 && ascii2 <= 57 || ascii2 === 44 || ascii2 === 46) {
              inValue = true;
              if (!newLine) isLastDifference = false;
              isNegative = true;
            }
          } else if (ascii === 13 || ascii === 10) {
            newLine = true;
            inComment = false;
          } // and now analyse the details ... space or tabulation
          // if "+" we just don't care

        }
      }
    }
  }

  const removeCommentRegExp = /\$\$.*/;
  const peakTableSplitRegExp = /[,\t ]+/;
  function parsePeakTable(spectrum, value, result) {
    spectrum.isPeaktable = true;

    if (!spectrum.variables || Object.keys(spectrum.variables) === 2) {
      parseXY$1(spectrum, value, result);
    } else {
      parseXYZ(spectrum, value, result);
    } // we will add the data in the variables


    if (spectrum.variables) {
      for (let key in spectrum.variables) {
        spectrum.variables[key].data = spectrum.data[key];
      }
    }
  }

  function parseXY$1(spectrum, value, result) {
    let currentData = {
      x: [],
      y: []
    };
    spectrum.data = currentData; // counts for around 20% of the time

    let lines = value.split(/,? *,?[;\r\n]+ */);

    for (let i = 1; i < lines.length; i++) {
      let values = lines[i].trim().replace(removeCommentRegExp, '').split(peakTableSplitRegExp);

      if (values.length % 2 === 0) {
        for (let j = 0; j < values.length; j = j + 2) {
          // takes around 40% of the time to add and parse the 2 values nearly exclusively because of parseFloat
          currentData.x.push(parseFloat(values[j]) * spectrum.xFactor);
          currentData.y.push(parseFloat(values[j + 1]) * spectrum.yFactor);
        }
      } else {
        result.logs.push(`Format error: ${values}`);
      }
    }
  }

  function parseXYZ(spectrum, value, result) {
    let currentData = {};
    let variables = Object.keys(spectrum.variables);
    let numberOfVariables = variables.length;
    variables.forEach(variable => currentData[variable] = []);
    spectrum.data = currentData; // counts for around 20% of the time

    let lines = value.split(/,? *,?[;\r\n]+ */);

    for (let i = 1; i < lines.length; i++) {
      let values = lines[i].trim().replace(removeCommentRegExp, '').split(peakTableSplitRegExp);

      if (values.length % numberOfVariables === 0) {
        for (let j = 0; j < values.length; j++) {
          // todo should try to find a xFactor (y, ...)
          currentData[variables[j % numberOfVariables]].push(parseFloat(values[j]));
        }
      } else {
        result.logs.push(`Format error: ${values}`);
      }
    }
  }

  function parseXYA(spectrum, value) {
    let removeSymbolRegExp = /(\(+|\)+|<+|>+|\s+)/g;
    spectrum.isXYAdata = true;
    let values;
    let currentData = {
      x: [],
      y: []
    };
    spectrum.data = currentData;
    let lines = value.split(/,? *,?[;\r\n]+ */);

    for (let i = 1; i < lines.length; i++) {
      values = lines[i].trim().replace(removeSymbolRegExp, '').split(',');
      currentData.x.push(parseFloat(values[0]));
      currentData.y.push(parseFloat(values[1]));
    }
  }

  function convertTo3DZ(spectra) {
    let minZ = spectra[0].data.y[0];
    let maxZ = minZ;
    let ySize = spectra.length;
    let xSize = spectra[0].data.x.length;
    let z = new Array(ySize);

    for (let i = 0; i < ySize; i++) {
      z[i] = spectra[i].data.y;

      for (let j = 0; j < xSize; j++) {
        let value = z[i][j];
        if (value < minZ) minZ = value;
        if (value > maxZ) maxZ = value;
      }
    }

    const firstX = spectra[0].data.x[0];
    const lastX = spectra[0].data.x[spectra[0].data.x.length - 1]; // has to be -2 because it is a 1D array [x,y,x,y,...]

    const firstY = spectra[0].pageValue;
    const lastY = spectra[ySize - 1].pageValue; // Because the min / max value are the only information about the matrix if we invert
    // min and max we need to invert the array

    if (firstX > lastX) {
      for (let spectrum of z) {
        spectrum.reverse();
      }
    }

    if (firstY > lastY) {
      z.reverse();
    }

    const medians = [];

    for (let i = 0; i < z.length; i++) {
      const row = Float64Array.from(z[i]);

      for (let i = 0; i < row.length; i++) {
        if (row[i] < 0) row[i] = -row[i];
      }

      medians.push(median(row));
    }

    const median$1 = median(medians);
    return {
      z: z,
      minX: Math.min(firstX, lastX),
      maxX: Math.max(firstX, lastX),
      minY: Math.min(firstY, lastY),
      maxY: Math.max(firstY, lastY),
      minZ: minZ,
      maxZ: maxZ,
      noise: median$1
    };
  }

  function generateContourLines(zData, options) {
    let noise = zData.noise;
    let z = zData.z;
    let povarHeight0, povarHeight1, povarHeight2, povarHeight3;
    let isOver0, isOver1, isOver2, isOver3;
    let nbSubSpectra = z.length;
    let nbPovars = z[0].length;
    let pAx, pAy, pBx, pBy;
    let x0 = zData.minX;
    let xN = zData.maxX;
    let dx = (xN - x0) / (nbPovars - 1);
    let y0 = zData.minY;
    let yN = zData.maxY;
    let dy = (yN - y0) / (nbSubSpectra - 1);
    let minZ = zData.minZ;
    let maxZ = zData.maxZ; // System.out.prvarln('y0 '+y0+' yN '+yN);
    // -------------------------
    // Povars attribution
    //
    // 0----1
    // |  / |
    // | /  |
    // 2----3
    //
    // ---------------------d------

    let iter = options.nbContourLevels * 2;
    let contourLevels = new Array(iter);
    let lineZValue;

    for (let level = 0; level < iter; level++) {
      // multiply by 2 for positif and negatif
      let contourLevel = {};
      contourLevels[level] = contourLevel;
      let side = level % 2;
      let factor = (maxZ - options.noiseMultiplier * noise) * Math.exp((level >> 1) - options.nbContourLevels);

      if (side === 0) {
        lineZValue = factor + options.noiseMultiplier * noise;
      } else {
        lineZValue = 0 - factor - options.noiseMultiplier * noise;
      }

      let lines = [];
      contourLevel.zValue = lineZValue;
      contourLevel.lines = lines;
      if (lineZValue <= minZ || lineZValue >= maxZ) continue;

      for (let iSubSpectra = 0; iSubSpectra < nbSubSpectra - 1; iSubSpectra++) {
        let subSpectra = z[iSubSpectra];
        let subSpectraAfter = z[iSubSpectra + 1];

        for (let povar = 0; povar < nbPovars - 1; povar++) {
          povarHeight0 = subSpectra[povar];
          povarHeight1 = subSpectra[povar + 1];
          povarHeight2 = subSpectraAfter[povar];
          povarHeight3 = subSpectraAfter[povar + 1];
          isOver0 = povarHeight0 > lineZValue;
          isOver1 = povarHeight1 > lineZValue;
          isOver2 = povarHeight2 > lineZValue;
          isOver3 = povarHeight3 > lineZValue; // Example povar0 is over the plane and povar1 and
          // povar2 are below, we find the varersections and add
          // the segment

          if (isOver0 !== isOver1 && isOver0 !== isOver2) {
            pAx = povar + (lineZValue - povarHeight0) / (povarHeight1 - povarHeight0);
            pAy = iSubSpectra;
            pBx = povar;
            pBy = iSubSpectra + (lineZValue - povarHeight0) / (povarHeight2 - povarHeight0);
            lines.push(pAx * dx + x0);
            lines.push(pAy * dy + y0);
            lines.push(pBx * dx + x0);
            lines.push(pBy * dy + y0);
          } // remove push does not help !!!!


          if (isOver3 !== isOver1 && isOver3 !== isOver2) {
            pAx = povar + 1;
            pAy = iSubSpectra + 1 - (lineZValue - povarHeight3) / (povarHeight1 - povarHeight3);
            pBx = povar + 1 - (lineZValue - povarHeight3) / (povarHeight2 - povarHeight3);
            pBy = iSubSpectra + 1;
            lines.push(pAx * dx + x0);
            lines.push(pAy * dy + y0);
            lines.push(pBx * dx + x0);
            lines.push(pBy * dy + y0);
          } // test around the diagonal


          if (isOver1 !== isOver2) {
            pAx = (povar + 1 - (lineZValue - povarHeight1) / (povarHeight2 - povarHeight1)) * dx + x0;
            pAy = (iSubSpectra + (lineZValue - povarHeight1) / (povarHeight2 - povarHeight1)) * dy + y0;

            if (isOver1 !== isOver0) {
              pBx = povar + 1 - (lineZValue - povarHeight1) / (povarHeight0 - povarHeight1);
              pBy = iSubSpectra;
              lines.push(pAx);
              lines.push(pAy);
              lines.push(pBx * dx + x0);
              lines.push(pBy * dy + y0);
            }

            if (isOver2 !== isOver0) {
              pBx = povar;
              pBy = iSubSpectra + 1 - (lineZValue - povarHeight2) / (povarHeight0 - povarHeight2);
              lines.push(pAx);
              lines.push(pAy);
              lines.push(pBx * dx + x0);
              lines.push(pBy * dy + y0);
            }

            if (isOver1 !== isOver3) {
              pBx = povar + 1;
              pBy = iSubSpectra + (lineZValue - povarHeight1) / (povarHeight3 - povarHeight1);
              lines.push(pAx);
              lines.push(pAy);
              lines.push(pBx * dx + x0);
              lines.push(pBy * dy + y0);
            }

            if (isOver2 !== isOver3) {
              pBx = povar + (lineZValue - povarHeight2) / (povarHeight3 - povarHeight2);
              pBy = iSubSpectra + 1;
              lines.push(pAx);
              lines.push(pAy);
              lines.push(pBx * dx + x0);
              lines.push(pBy * dy + y0);
            }
          }
        }
      }
    }

    return {
      minX: zData.minX,
      maxX: zData.maxX,
      minY: zData.minY,
      maxY: zData.maxY,
      segments: contourLevels
    };
  }

  function add2D(result, options) {
    let zData = convertTo3DZ(result.spectra);

    if (!options.noContour) {
      result.contourLines = generateContourLines(zData, options);
      delete zData.z;
    }

    result.minMax = zData;
  }

  // sources:
  // https://en.wikipedia.org/wiki/Gyromagnetic_ratio
  // TODO: #13 can we have a better source and more digits ? @jwist
  const gyromagneticRatio = {
    '1H': 267.52218744e6,
    '2H': 41.065e6,
    '3H': 285.3508e6,
    '3He': -203.789e6,
    '7Li': 103.962e6,
    '13C': 67.28284e6,
    '14N': 19.331e6,
    '15N': -27.116e6,
    '17O': -36.264e6,
    '19F': 251.662e6,
    '23Na': 70.761e6,
    '27Al': 69.763e6,
    '29Si': -53.19e6,
    '31P': 108.291e6,
    '57Fe': 8.681e6,
    '63Cu': 71.118e6,
    '67Zn': 16.767e6,
    '129Xe': -73.997e6
  };

  function postProcessingNMR(entriesFlat) {
    // specific NMR functions
    for (let entry of entriesFlat) {
      let observeFrequency = 0;
      let shiftOffsetVal = 0;

      for (let spectrum of entry.spectra) {
        if (entry.ntuples && entry.ntuples.symbol) {
          if (!observeFrequency && spectrum.observeFrequency) {
            observeFrequency = spectrum.observeFrequency;
          }

          if (!shiftOffsetVal && spectrum.shiftOffsetVal) {
            shiftOffsetVal = spectrum.shiftOffsetVal;
          }
        } else {
          observeFrequency = spectrum.observeFrequency;
          shiftOffsetVal = spectrum.shiftOffsetVal;
        }

        if (observeFrequency) {
          if (spectrum.xUnits && spectrum.xUnits.toUpperCase().includes('HZ')) {
            spectrum.xUnits = 'PPM';
            spectrum.xFactor = spectrum.xFactor / observeFrequency;
            spectrum.firstX = spectrum.firstX / observeFrequency;
            spectrum.lastX = spectrum.lastX / observeFrequency;
            spectrum.deltaX = spectrum.deltaX / observeFrequency;

            for (let i = 0; i < spectrum.data.x.length; i++) {
              spectrum.data.x[i] /= observeFrequency;
            }
          }
        }

        if (shiftOffsetVal) {
          let shift = spectrum.firstX - shiftOffsetVal;
          spectrum.firstX = spectrum.firstX - shift;
          spectrum.lastX = spectrum.lastX - shift;

          for (let i = 0; i < spectrum.data.x.length; i++) {
            spectrum.data.x[i] -= shift;
          }
        } // we will check if some nucleus are missing ...


        if (entry.ntuples && entry.ntuples.nucleus && entry.ntuples.symbol) {
          for (let i = 0; i < entry.ntuples.nucleus.length; i++) {
            let symbol = entry.ntuples.symbol[i];
            let nucleus = entry.ntuples.nucleus[i];

            if (symbol.startsWith('F') && !nucleus) {
              if (symbol === 'F1') {
                // if F1 is defined we will use F2
                if (entry.tmp.$NUC2) {
                  entry.ntuples.nucleus[i] = entry.tmp.$NUC2;
                } else {
                  let f2index = entry.ntuples.symbol.indexOf('F2');

                  if (f2index && entry.ntuples.nucleus[f2index]) {
                    entry.ntuples.nucleus[i] = entry.ntuples.nucleus[f2index];
                  }
                }
              }

              if (symbol === 'F2') entry.ntuples.nucleus[i] = entry.tmp.$NUC1;
            }

            if (symbol === 'F2') {
              entry.yType = entry.ntuples.nucleus[0];
            }
          }
        }

        if (observeFrequency && entry.ntuples && entry.ntuples.symbol && entry.ntuples.nucleus) {
          let unit = '';
          let pageSymbolIndex = entry.ntuples.symbol.indexOf(spectrum.pageSymbol);

          if (entry.ntuples.units && entry.ntuples.units[pageSymbolIndex]) {
            unit = entry.ntuples.units[pageSymbolIndex];
          }

          if (unit !== 'PPM') {
            if (pageSymbolIndex !== 0) {
              throw Error('Not sure about this ntuples format');
            }

            let ratio0 = gyromagneticRatio[entry.ntuples.nucleus[0]];
            let ratio1 = gyromagneticRatio[entry.ntuples.nucleus[1]];

            if (!ratio0 || !ratio1) {
              throw Error('Problem with determination of gyromagnetic ratio');
            }

            let ratio = ratio0 / ratio1 * observeFrequency;
            spectrum.pageValue /= ratio;
          }
        }
      }
    }
  }

  function profiling(result, action, options) {
    if (result.profiling) {
      result.profiling.push({
        action,
        time: Date.now() - options.start
      });
    }
  }

  function simpleChromatogram(result) {
    let data = result.spectra[0].data;
    result.chromatogram = {
      times: data.x.slice(),
      series: {
        intensity: {
          dimension: 1,
          data: data.y.slice()
        }
      }
    };
  }

  function postProcessing(entriesFlat, result, options) {
    // converting Hz to ppm
    postProcessingNMR(entriesFlat);

    for (let entry of entriesFlat) {
      if (Object.keys(entry.ntuples).length > 0) {
        let newNtuples = [];
        let keys = Object.keys(entry.ntuples);

        for (let i = 0; i < keys.length; i++) {
          let key = keys[i];
          let values = entry.ntuples[key];

          for (let j = 0; j < values.length; j++) {
            if (!newNtuples[j]) newNtuples[j] = {};
            newNtuples[j][key] = values[j];
          }
        }

        entry.ntuples = newNtuples;
      }

      if (entry.twoD && options.wantXY) {
        add2D(entry, options);
        profiling(result, 'Finished countour plot calculation', options);

        if (!options.keepSpectra) {
          delete entry.spectra;
        }
      } // maybe it is a GC (HPLC) / MS. In this case we add a new format


      if (options.chromatogram) {
        if (entry.spectra.length > 1) {
          complexChromatogram(entry);
        } else {
          simpleChromatogram(entry);
        }

        profiling(result, 'Finished chromatogram calculation', options);
      }

      delete entry.tmp;
    }
  }

  function prepareNtuplesDatatable(currentEntry, spectrum, kind) {
    let xIndex = -1;
    let yIndex = -1;
    let firstVariable = '';
    let secondVariable = '';

    if (kind.indexOf('++') > 0) {
      firstVariable = kind.replace(/.*\(([a-zA-Z0-9]+)\+\+.*/, '$1');
      secondVariable = kind.replace(/.*\.\.([a-zA-Z0-9]+).*/, '$1');
    } else {
      kind = kind.replace(/[^a-zA-Z]/g, '');
      firstVariable = kind.charAt(0);
      secondVariable = kind.charAt(1);
      spectrum.variables = {};

      for (let symbol of kind) {
        let lowerCaseSymbol = symbol.toLowerCase();
        let index = currentEntry.ntuples.symbol.indexOf(symbol);
        if (index === -1) throw Error(`Symbol undefined: ${symbol}`);
        spectrum.variables[lowerCaseSymbol] = {};

        for (let key in currentEntry.ntuples) {
          if (currentEntry.ntuples[key][index]) {
            spectrum.variables[lowerCaseSymbol][key.replace(/^var/, '')] = currentEntry.ntuples[key][index];
          }
        }
      }
    }

    xIndex = currentEntry.ntuples.symbol.indexOf(firstVariable);
    yIndex = currentEntry.ntuples.symbol.indexOf(secondVariable);
    if (xIndex === -1) xIndex = 0;
    if (yIndex === -1) yIndex = 0;

    if (currentEntry.ntuples.first) {
      if (currentEntry.ntuples.first.length > xIndex) {
        spectrum.firstX = currentEntry.ntuples.first[xIndex];
      }

      if (currentEntry.ntuples.first.length > yIndex) {
        spectrum.firstY = currentEntry.ntuples.first[yIndex];
      }
    }

    if (currentEntry.ntuples.last) {
      if (currentEntry.ntuples.last.length > xIndex) {
        spectrum.lastX = currentEntry.ntuples.last[xIndex];
      }

      if (currentEntry.ntuples.last.length > yIndex) {
        spectrum.lastY = currentEntry.ntuples.last[yIndex];
      }
    }

    if (currentEntry.ntuples.vardim && currentEntry.ntuples.vardim.length > xIndex) {
      spectrum.nbPoints = currentEntry.ntuples.vardim[xIndex];
    }

    if (currentEntry.ntuples.factor) {
      if (currentEntry.ntuples.factor.length > xIndex) {
        spectrum.xFactor = currentEntry.ntuples.factor[xIndex];
      }

      if (currentEntry.ntuples.factor.length > yIndex) {
        spectrum.yFactor = currentEntry.ntuples.factor[yIndex];
      }
    }

    if (currentEntry.ntuples.units) {
      if (currentEntry.ntuples.units.length > xIndex) {
        if (currentEntry.ntuples.varname && currentEntry.ntuples.varname[xIndex]) {
          spectrum.xUnits = `${currentEntry.ntuples.varname[xIndex]} [${currentEntry.ntuples.units[xIndex]}]`;
        } else {
          spectrum.xUnits = currentEntry.ntuples.units[xIndex];
        }
      }

      if (currentEntry.ntuples.units.length > yIndex) {
        if (currentEntry.ntuples.varname && currentEntry.ntuples.varname[yIndex]) {
          spectrum.yUnits = `${currentEntry.ntuples.varname[yIndex]} [${currentEntry.ntuples.units[yIndex]}]`;
        } else {
          spectrum.yUnits = currentEntry.ntuples.units[yIndex];
        }
      }
    }
  }

  function prepareSpectrum(spectrum) {
    if (!spectrum.xFactor) spectrum.xFactor = 1;
    if (!spectrum.yFactor) spectrum.yFactor = 1;
  }

  const ntuplesSeparatorRegExp = /[ \t]*,[ \t]*/;

  class Spectrum {}

  const defaultOptions$1 = {
    keepRecordsRegExp: /^$/,
    canonicDataLabels: true,
    canonicMetadataLabels: false,
    dynamicTyping: true,
    withoutXY: false,
    chromatogram: false,
    keepSpectra: false,
    noContour: false,
    nbContourLevels: 7,
    noiseMultiplier: 5,
    profiling: false
  };
  /**
   *
   * @param {string|ArrayBuffer} jcamp
   * @param {object} [options]
   * @param {number} [options.keepRecordsRegExp=/^$/] By default we don't keep meta information
   * @param {number} [options.canonicDataLabels=true] Canonize the Labels (uppercase without symbol)
   * @param {number} [options.canonicMetadataLabels=false] Canonize the metadata Labels (uppercase without symbol)
   * @param {number} [options.dynamicTyping=false] Convert numbers to Number
   * @param {number} [options.withoutXY=false] Remove the XY data
   * @param {number} [options.chromatogram=false] Special post-processing for GC / HPLC / MS
   * @param {number} [options.keepSpectra=false] Force to keep the spectra in case of 2D
   * @param {number} [options.noContour=false] Don't calculate countour in case of 2D
   * @param {number} [options.nbContourLevels=7] Number of positive / negative contour levels to calculate
   * @param {number} [options.noiseMultiplier=5] Define for 2D the level as 5 times the median as default
   * @param {number} [options.profiling=false] Add profiling information
   */

  function convert(jcamp, options = {}) {
    jcamp = ensureString$1(jcamp);
    options = { ...defaultOptions$1,
      ...options
    };
    options.wantXY = !options.withoutXY;
    options.start = Date.now();
    let entriesFlat = [];
    let result = {
      profiling: options.profiling ? [] : false,
      logs: [],
      entries: []
    };
    let tmpResult = {
      children: []
    };
    let currentEntry = tmpResult;
    let parentsStack = [];
    let spectrum = new Spectrum();

    if (typeof jcamp !== 'string') {
      throw new TypeError('the JCAMP should be a string');
    }

    profiling(result, 'Before split to LDRS', options);
    let ldrs = jcamp.replace(/[\r\n]+##/g, '\n##').split('\n##');
    profiling(result, 'Split to LDRS', options);
    if (ldrs[0]) ldrs[0] = ldrs[0].replace(/^[\r\n ]*##/, '');

    for (let ldr of ldrs) {
      // This is a new LDR
      let position = ldr.indexOf('=');
      let dataLabel = position > 0 ? ldr.substring(0, position) : ldr;
      let dataValue = position > 0 ? ldr.substring(position + 1).trim() : '';
      let canonicDataLabel = dataLabel.replace(/[_ -]/g, '').toUpperCase();

      if (canonicDataLabel === 'DATATABLE') {
        let endLine = dataValue.indexOf('\n');
        if (endLine === -1) endLine = dataValue.indexOf('\r');

        if (endLine > 0) {
          // ##DATA TABLE= (X++(I..I)), XYDATA
          // We need to find the variables
          let infos = dataValue.substring(0, endLine).split(/[ ,;\t]+/);
          prepareNtuplesDatatable(currentEntry, spectrum, infos[0]);
          spectrum.datatable = infos[0];

          if (infos[1] && infos[1].indexOf('PEAKS') > -1) {
            canonicDataLabel = 'PEAKTABLE';
          } else if (infos[1] && (infos[1].indexOf('XYDATA') || infos[0].indexOf('++') > 0)) {
            canonicDataLabel = 'XYDATA';
            spectrum.deltaX = (spectrum.lastX - spectrum.firstX) / (spectrum.nbPoints - 1);
          }
        }
      }

      if (canonicDataLabel === 'XYDATA') {
        if (options.wantXY) {
          prepareSpectrum(spectrum); // well apparently we should still consider it is a PEAK TABLE if there are no '++' after

          if (dataValue.match(/.*\+\+.*/)) {
            // ex: (X++(Y..Y))
            spectrum.deltaX = (spectrum.lastX - spectrum.firstX) / (spectrum.nbPoints - 1);
            fastParseXYData(spectrum, dataValue);
          } else {
            parsePeakTable(spectrum, dataValue, result);
          }

          currentEntry.spectra.push(spectrum);
          spectrum = new Spectrum();
        }

        continue;
      } else if (canonicDataLabel === 'PEAKTABLE') {
        if (options.wantXY) {
          prepareSpectrum(spectrum);
          parsePeakTable(spectrum, dataValue, result);
          currentEntry.spectra.push(spectrum);
          spectrum = new Spectrum();
        }

        continue;
      }

      if (canonicDataLabel === 'PEAKASSIGNMENTS') {
        if (options.wantXY) {
          if (dataValue.match(/.*(XYA).*/)) {
            // ex: (XYA)
            parseXYA(spectrum, dataValue);
          }

          currentEntry.spectra.push(spectrum);
          spectrum = new Spectrum();
        }

        continue;
      }

      if (canonicDataLabel === 'TITLE') {
        let parentEntry = currentEntry;

        if (!parentEntry.children) {
          parentEntry.children = [];
        }

        currentEntry = {
          spectra: [],
          ntuples: {},
          info: {},
          meta: {},
          tmp: {} // tmp information we need to keep for postprocessing

        };
        parentEntry.children.push(currentEntry);
        parentsStack.push(parentEntry);
        entriesFlat.push(currentEntry);
        currentEntry.title = dataValue;
      } else if (canonicDataLabel === 'DATATYPE') {
        currentEntry.dataType = dataValue;

        if (dataValue.match(/(^nd|\snd\s)/i)) {
          currentEntry.twoD = true;
        }
      } else if (canonicDataLabel === 'NTUPLES') {
        if (dataValue.match(/(^nd|\snd\s)/i)) {
          currentEntry.twoD = true;
        }
      } else if (canonicDataLabel === 'DATACLASS') {
        currentEntry.dataClass = dataValue;
      } else if (canonicDataLabel === 'XUNITS') {
        spectrum.xUnits = dataValue;
      } else if (canonicDataLabel === 'YUNITS') {
        spectrum.yUnits = dataValue;
      } else if (canonicDataLabel === 'FIRSTX') {
        spectrum.firstX = parseFloat(dataValue);
      } else if (canonicDataLabel === 'LASTX') {
        spectrum.lastX = parseFloat(dataValue);
      } else if (canonicDataLabel === 'FIRSTY') {
        spectrum.firstY = parseFloat(dataValue);
      } else if (canonicDataLabel === 'LASTY') {
        spectrum.lastY = parseFloat(dataValue);
      } else if (canonicDataLabel === 'NPOINTS') {
        spectrum.nbPoints = parseFloat(dataValue);
      } else if (canonicDataLabel === 'XFACTOR') {
        spectrum.xFactor = parseFloat(dataValue);
      } else if (canonicDataLabel === 'YFACTOR') {
        spectrum.yFactor = parseFloat(dataValue);
      } else if (canonicDataLabel === 'MAXX') {
        spectrum.maxX = parseFloat(dataValue);
      } else if (canonicDataLabel === 'MINX') {
        spectrum.minX = parseFloat(dataValue);
      } else if (canonicDataLabel === 'MAXY') {
        spectrum.maxY = parseFloat(dataValue);
      } else if (canonicDataLabel === 'MINY') {
        spectrum.minY = parseFloat(dataValue);
      } else if (canonicDataLabel === 'DELTAX') {
        spectrum.deltaX = parseFloat(dataValue);
      } else if (canonicDataLabel === '.OBSERVEFREQUENCY' || canonicDataLabel === '$SFO1') {
        if (!spectrum.observeFrequency) {
          spectrum.observeFrequency = parseFloat(dataValue);
        }
      } else if (canonicDataLabel === '.OBSERVENUCLEUS') {
        if (!spectrum.xType) {
          currentEntry.xType = dataValue.replace(/[^a-zA-Z0-9]/g, '');
        }
      } else if (canonicDataLabel === '$OFFSET') {
        // OFFSET for Bruker spectra
        currentEntry.shiftOffsetNum = 0;

        if (!spectrum.shiftOffsetVal) {
          spectrum.shiftOffsetVal = parseFloat(dataValue);
        }
      } else if (canonicDataLabel === '$REFERENCEPOINT') ; else if (canonicDataLabel === 'VARNAME') {
        currentEntry.ntuples.varname = dataValue.split(ntuplesSeparatorRegExp);
      } else if (canonicDataLabel === 'SYMBOL') {
        currentEntry.ntuples.symbol = dataValue.split(ntuplesSeparatorRegExp);
      } else if (canonicDataLabel === 'VARTYPE') {
        currentEntry.ntuples.vartype = dataValue.split(ntuplesSeparatorRegExp);
      } else if (canonicDataLabel === 'VARFORM') {
        currentEntry.ntuples.varform = dataValue.split(ntuplesSeparatorRegExp);
      } else if (canonicDataLabel === 'VARDIM') {
        currentEntry.ntuples.vardim = convertToFloatArray(dataValue.split(ntuplesSeparatorRegExp));
      } else if (canonicDataLabel === 'UNITS') {
        currentEntry.ntuples.units = dataValue.split(ntuplesSeparatorRegExp);
      } else if (canonicDataLabel === 'FACTOR') {
        currentEntry.ntuples.factor = convertToFloatArray(dataValue.split(ntuplesSeparatorRegExp));
      } else if (canonicDataLabel === 'FIRST') {
        currentEntry.ntuples.first = convertToFloatArray(dataValue.split(ntuplesSeparatorRegExp));
      } else if (canonicDataLabel === 'LAST') {
        currentEntry.ntuples.last = convertToFloatArray(dataValue.split(ntuplesSeparatorRegExp));
      } else if (canonicDataLabel === 'MIN') {
        currentEntry.ntuples.min = convertToFloatArray(dataValue.split(ntuplesSeparatorRegExp));
      } else if (canonicDataLabel === 'MAX') {
        currentEntry.ntuples.max = convertToFloatArray(dataValue.split(ntuplesSeparatorRegExp));
      } else if (canonicDataLabel === '.NUCLEUS') {
        if (currentEntry.ntuples) {
          currentEntry.ntuples.nucleus = dataValue.split(ntuplesSeparatorRegExp);
        }
      } else if (canonicDataLabel === 'PAGE') {
        spectrum.page = dataValue.trim();
        spectrum.pageValue = parseFloat(dataValue.replace(/^.*=/, ''));
        spectrum.pageSymbol = spectrum.page.replace(/[=].*/, '');
      } else if (canonicDataLabel === 'RETENTIONTIME') {
        spectrum.pageValue = parseFloat(dataValue);
      } else if (isMSField(canonicDataLabel)) {
        spectrum[convertMSFieldToLabel(canonicDataLabel)] = dataValue;
      } else if (canonicDataLabel === 'SAMPLEDESCRIPTION') {
        spectrum.sampleDescription = dataValue;
      } else if (canonicDataLabel.startsWith('$NUC')) {
        if (!currentEntry.tmp[canonicDataLabel] && !dataValue.includes('off')) {
          currentEntry.tmp[canonicDataLabel] = dataValue.replace(/[<>]/g, '');
        }
      } else if (canonicDataLabel === 'END') {
        currentEntry = parentsStack.pop();
      }

      if (currentEntry && currentEntry.info && currentEntry.meta && canonicDataLabel.match(options.keepRecordsRegExp)) {
        let value = dataValue.trim();
        let target, label;

        if (dataLabel.startsWith('$')) {
          label = options.canonicMetadataLabels ? canonicDataLabel.substring(1) : dataLabel.substring(1);
          target = currentEntry.meta;
        } else {
          label = options.canonicDataLabels ? canonicDataLabel : dataLabel;
          target = currentEntry.info;
        }

        if (options.dynamicTyping) {
          value = parseString(value);
        }

        if (target[label]) {
          if (!Array.isArray(target[label])) {
            target[label] = [target[label]];
          }

          target[label].push(value);
        } else {
          target[label] = value;
        }
      }
    }

    profiling(result, 'Finished parsing', options);
    postProcessing(entriesFlat, result, options);
    profiling(result, 'Total time', options);
    /*
    if (result.children && result.children.length>0) {
      result = { ...result, ...result.children[0] };
    }
    */

    result.entries = tmpResult.children;
    result.flatten = entriesFlat;
    return result;
  }

  function fromJcamp(jcamp) {
    const data = convert(jcamp, {
      chromatogram: true
    }).flatten[0].chromatogram;
    return fromJSON(data);
  }

  /**
   * Ensure that the data is string. If it is an ArrayBuffer it will be converted to string using TextDecoder.
   * @param {string|ArrayBuffer} blob
   * @param {object} [options={}]
   * @param {string} [options.encoding='utf8']
   * @returns {string}
   */
  function ensureString(blob, options = {}) {
    const {
      encoding = 'utf8'
    } = options;

    if (ArrayBuffer.isView(blob) || blob instanceof ArrayBuffer) {
      const decoder = new TextDecoder(encoding);
      return decoder.decode(blob);
    }

    return blob;
  }

  /**
   * In place modification of the 2 arrays to make X unique and sum the Y if X has the same value
   * @param {object} [points={}] : Object of points contains property x (an array) and y (an array)
   * @return points
   */
  function uniqueX(points = {}) {
    const {
      x,
      y
    } = points;
    if (x.length < 2) return;

    if (x.length !== y.length) {
      throw new Error('The X and Y arrays mush have the same length');
    }

    let current = x[0];
    let counter = 0;

    for (let i = 1; i < x.length; i++) {
      if (current !== x[i]) {
        counter++;
        current = x[i];
        x[counter] = x[i];

        if (i !== counter) {
          y[counter] = 0;
        }
      }

      if (i !== counter) {
        y[counter] += y[i];
      }
    }

    x.length = counter + 1;
    y.length = counter + 1;
  }

  /**
   * Parse a text-file and convert it to an array of XY points
   * @param {string} text - csv or tsv strings
   * @param {object} [options={}]
   * @param {boolean} [options.rescale = false] - will set the maximum value to 1
   * @param {boolean} [options.uniqueX = false] - Make the X values unique (works only with 'xxyy' format). If the X value is repeated the sum of Y is done.
   * @param {number} [options.xColumn = 0] - A number that specifies the x column
   * @param {number} [options.yColumn = 1] - A number that specifies the y column
   * @param {boolean} [options.bestGuess=false] Will try to guess which columns are the best
   * @param {number} [options.numberColumns=Number.MAX_SAFE_INTEGER] If the file has 10 columns and you specify here 2 it will reflow the file
   * @param {number} [options.maxNumberColumns = (Math.max(xColumn, yColumn)+1)] - A number that specifies the maximum number of y columns
   * @param {number} [options.minNumberColumns = (Math.min(xColumn, yColumn)+1)] - A number that specifies the minimum number of y columns
   * @param {boolean} [options.keepInfo = false] - shoud we keep the non numeric lines. In this case the system will return an object {data, info}
   * @return {object{x:<Array<number>>,y:<Array<number>>}
   */

  function parseXY(text, options = {}) {
    let {
      rescale = false,
      uniqueX: uniqueX$1 = false,
      xColumn = 0,
      yColumn = 1,
      keepInfo = false,
      bestGuess = false,
      numberColumns = Number.MAX_SAFE_INTEGER,
      maxNumberColumns = Number.MAX_SAFE_INTEGER,
      minNumberColumns = 2
    } = options;
    text = ensureString(text);
    maxNumberColumns = Math.max(maxNumberColumns, xColumn + 1, yColumn + 1);
    minNumberColumns = Math.max(xColumn + 1, yColumn + 1, minNumberColumns);
    let lines = text.split(/[\r\n]+/);
    let matrix = [];
    let info = [];
    let position = 0;

    for (let l = 0; l < lines.length; l++) {
      let line = lines[l].trim(); // we will consider only lines that contains only numbers

      if (line.match(/[0-9]+/) && line.match(/^[0-9eE,;. \t+-]+$/)) {
        let fields = line.split(/,[; \t]+|[; \t]+/);

        if (fields.length === 1) {
          fields = line.split(/[,; \t]+/);
        }

        if (fields && fields.length >= minNumberColumns && // we filter lines that have not enough or too many columns
        fields.length <= maxNumberColumns) {
          matrix.push(fields.map(value => parseFloat(value.replace(",", "."))));
          position++;
        }
      } else if (line) {
        info.push({
          position,
          value: line
        });
      }
    }

    if (bestGuess) {
      if (matrix[0] && matrix[0].length === 3 && options.xColumn === undefined && options.yColumn === undefined) {
        // is the first column a seuqnetial number ?
        let skipFirstColumn = true;

        for (let i = 0; i < matrix.length - 1; i++) {
          if (Math.abs(matrix[i][0] - matrix[i + 1][0]) !== 1) {
            skipFirstColumn = false;
          }
        }

        if (skipFirstColumn) {
          xColumn = 1;
          yColumn = 2;
        }
      }

      if (matrix[0] && matrix[0].length > 3) {
        let xs = [];

        for (let row of matrix) {
          for (let i = xColumn; i < row.length; i += 2) {
            xs.push(row[i]);
          }
        }

        if (xIsMonotone$1(xs)) {
          numberColumns = 2;
        }
      }
    }

    if (numberColumns) {
      const newMatrix = [];

      for (const row of matrix) {
        for (let i = 0; i < row.length; i += numberColumns) {
          newMatrix.push(row.slice(i, i + numberColumns));
        }
      }

      matrix = newMatrix;
    }

    const result = {
      x: matrix.map(row => row[xColumn]),
      y: matrix.map(row => row[yColumn])
    };

    if (uniqueX$1) {
      uniqueX(result);
    }

    if (rescale) {
      let maxY = max(result.y);

      for (let i = 0; i < result.y.length; i++) {
        result.y[i] /= maxY;
      }
    }

    if (!keepInfo) return result;
    return {
      info,
      data: result
    };
  }

  function fromText(text, options = {}) {
    const data = parseXY(text, options);
    const time = data.x;
    let series = {
      intensity: data.y
    };
    return new Chromatogram(time, series);
  }

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

  var utf8 = {};

  /*! https://mths.be/utf8js v3.0.0 by @mathias */

  (function (exports) {

    (function (root) {
      var stringFromCharCode = String.fromCharCode; // Taken from https://mths.be/punycode

      function ucs2decode(string) {
        var output = [];
        var counter = 0;
        var length = string.length;
        var value;
        var extra;

        while (counter < length) {
          value = string.charCodeAt(counter++);

          if (value >= 0xD800 && value <= 0xDBFF && counter < length) {
            // high surrogate, and there is a next character
            extra = string.charCodeAt(counter++);

            if ((extra & 0xFC00) == 0xDC00) {
              // low surrogate
              output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000);
            } else {
              // unmatched surrogate; only append this code unit, in case the next
              // code unit is the high surrogate of a surrogate pair
              output.push(value);
              counter--;
            }
          } else {
            output.push(value);
          }
        }

        return output;
      } // Taken from https://mths.be/punycode


      function ucs2encode(array) {
        var length = array.length;
        var index = -1;
        var value;
        var output = '';

        while (++index < length) {
          value = array[index];

          if (value > 0xFFFF) {
            value -= 0x10000;
            output += stringFromCharCode(value >>> 10 & 0x3FF | 0xD800);
            value = 0xDC00 | value & 0x3FF;
          }

          output += stringFromCharCode(value);
        }

        return output;
      }

      function checkScalarValue(codePoint) {
        if (codePoint >= 0xD800 && codePoint <= 0xDFFF) {
          throw Error('Lone surrogate U+' + codePoint.toString(16).toUpperCase() + ' is not a scalar value');
        }
      }
      /*--------------------------------------------------------------------------*/


      function createByte(codePoint, shift) {
        return stringFromCharCode(codePoint >> shift & 0x3F | 0x80);
      }

      function encodeCodePoint(codePoint) {
        if ((codePoint & 0xFFFFFF80) == 0) {
          // 1-byte sequence
          return stringFromCharCode(codePoint);
        }

        var symbol = '';

        if ((codePoint & 0xFFFFF800) == 0) {
          // 2-byte sequence
          symbol = stringFromCharCode(codePoint >> 6 & 0x1F | 0xC0);
        } else if ((codePoint & 0xFFFF0000) == 0) {
          // 3-byte sequence
          checkScalarValue(codePoint);
          symbol = stringFromCharCode(codePoint >> 12 & 0x0F | 0xE0);
          symbol += createByte(codePoint, 6);
        } else if ((codePoint & 0xFFE00000) == 0) {
          // 4-byte sequence
          symbol = stringFromCharCode(codePoint >> 18 & 0x07 | 0xF0);
          symbol += createByte(codePoint, 12);
          symbol += createByte(codePoint, 6);
        }

        symbol += stringFromCharCode(codePoint & 0x3F | 0x80);
        return symbol;
      }

      function utf8encode(string) {
        var codePoints = ucs2decode(string);
        var length = codePoints.length;
        var index = -1;
        var codePoint;
        var byteString = '';

        while (++index < length) {
          codePoint = codePoints[index];
          byteString += encodeCodePoint(codePoint);
        }

        return byteString;
      }
      /*--------------------------------------------------------------------------*/


      function readContinuationByte() {
        if (byteIndex >= byteCount) {
          throw Error('Invalid byte index');
        }

        var continuationByte = byteArray[byteIndex] & 0xFF;
        byteIndex++;

        if ((continuationByte & 0xC0) == 0x80) {
          return continuationByte & 0x3F;
        } // If we end up here, it’s not a continuation byte


        throw Error('Invalid continuation byte');
      }

      function decodeSymbol() {
        var byte1;
        var byte2;
        var byte3;
        var byte4;
        var codePoint;

        if (byteIndex > byteCount) {
          throw Error('Invalid byte index');
        }

        if (byteIndex == byteCount) {
          return false;
        } // Read first byte


        byte1 = byteArray[byteIndex] & 0xFF;
        byteIndex++; // 1-byte sequence (no continuation bytes)

        if ((byte1 & 0x80) == 0) {
          return byte1;
        } // 2-byte sequence


        if ((byte1 & 0xE0) == 0xC0) {
          byte2 = readContinuationByte();
          codePoint = (byte1 & 0x1F) << 6 | byte2;

          if (codePoint >= 0x80) {
            return codePoint;
          } else {
            throw Error('Invalid continuation byte');
          }
        } // 3-byte sequence (may include unpaired surrogates)


        if ((byte1 & 0xF0) == 0xE0) {
          byte2 = readContinuationByte();
          byte3 = readContinuationByte();
          codePoint = (byte1 & 0x0F) << 12 | byte2 << 6 | byte3;

          if (codePoint >= 0x0800) {
            checkScalarValue(codePoint);
            return codePoint;
          } else {
            throw Error('Invalid continuation byte');
          }
        } // 4-byte sequence


        if ((byte1 & 0xF8) == 0xF0) {
          byte2 = readContinuationByte();
          byte3 = readContinuationByte();
          byte4 = readContinuationByte();
          codePoint = (byte1 & 0x07) << 0x12 | byte2 << 0x0C | byte3 << 0x06 | byte4;

          if (codePoint >= 0x010000 && codePoint <= 0x10FFFF) {
            return codePoint;
          }
        }

        throw Error('Invalid UTF-8 detected');
      }

      var byteArray;
      var byteCount;
      var byteIndex;

      function utf8decode(byteString) {
        byteArray = ucs2decode(byteString);
        byteCount = byteArray.length;
        byteIndex = 0;
        var codePoints = [];
        var tmp;

        while ((tmp = decodeSymbol()) !== false) {
          codePoints.push(tmp);
        }

        return ucs2encode(codePoints);
      }
      /*--------------------------------------------------------------------------*/


      root.version = '3.0.0';
      root.encode = utf8encode;
      root.decode = utf8decode;
    })(exports);
  })(utf8);

  const defaultByteLength = 1024 * 8;
  class IOBuffer$1 {
    /**
     * @param data - The data to construct the IOBuffer with.
     * If data is a number, it will be the new buffer's length<br>
     * If data is `undefined`, the buffer will be initialized with a default length of 8Kb<br>
     * If data is an ArrayBuffer, SharedArrayBuffer, an ArrayBufferView (Typed Array), an IOBuffer instance,
     * or a Node.js Buffer, a view will be created over the underlying ArrayBuffer.
     * @param options
     */
    constructor(data = defaultByteLength, options = {}) {
      let dataIsGiven = false;

      if (typeof data === 'number') {
        data = new ArrayBuffer(data);
      } else {
        dataIsGiven = true;
        this.lastWrittenByte = data.byteLength;
      }

      const offset = options.offset ? options.offset >>> 0 : 0;
      const byteLength = data.byteLength - offset;
      let dvOffset = offset;

      if (ArrayBuffer.isView(data) || data instanceof IOBuffer$1) {
        if (data.byteLength !== data.buffer.byteLength) {
          dvOffset = data.byteOffset + offset;
        }

        data = data.buffer;
      }

      if (dataIsGiven) {
        this.lastWrittenByte = byteLength;
      } else {
        this.lastWrittenByte = 0;
      }

      this.buffer = data;
      this.length = byteLength;
      this.byteLength = byteLength;
      this.byteOffset = dvOffset;
      this.offset = 0;
      this.littleEndian = true;
      this._data = new DataView(this.buffer, dvOffset, byteLength);
      this._mark = 0;
      this._marks = [];
    }
    /**
     * Checks if the memory allocated to the buffer is sufficient to store more
     * bytes after the offset.
     * @param byteLength - The needed memory in bytes.
     * @returns `true` if there is sufficient space and `false` otherwise.
     */


    available(byteLength = 1) {
      return this.offset + byteLength <= this.length;
    }
    /**
     * Check if little-endian mode is used for reading and writing multi-byte
     * values.
     * @returns `true` if little-endian mode is used, `false` otherwise.
     */


    isLittleEndian() {
      return this.littleEndian;
    }
    /**
     * Set little-endian mode for reading and writing multi-byte values.
     */


    setLittleEndian() {
      this.littleEndian = true;
      return this;
    }
    /**
     * Check if big-endian mode is used for reading and writing multi-byte values.
     * @returns `true` if big-endian mode is used, `false` otherwise.
     */


    isBigEndian() {
      return !this.littleEndian;
    }
    /**
     * Switches to big-endian mode for reading and writing multi-byte values.
     */


    setBigEndian() {
      this.littleEndian = false;
      return this;
    }
    /**
     * Move the pointer n bytes forward.
     * @param n - Number of bytes to skip.
     */


    skip(n = 1) {
      this.offset += n;
      return this;
    }
    /**
     * Move the pointer to the given offset.
     * @param offset
     */


    seek(offset) {
      this.offset = offset;
      return this;
    }
    /**
     * Store the current pointer offset.
     * @see {@link IOBuffer#reset}
     */


    mark() {
      this._mark = this.offset;
      return this;
    }
    /**
     * Move the pointer back to the last pointer offset set by mark.
     * @see {@link IOBuffer#mark}
     */


    reset() {
      this.offset = this._mark;
      return this;
    }
    /**
     * Push the current pointer offset to the mark stack.
     * @see {@link IOBuffer#popMark}
     */


    pushMark() {
      this._marks.push(this.offset);

      return this;
    }
    /**
     * Pop the last pointer offset from the mark stack, and set the current
     * pointer offset to the popped value.
     * @see {@link IOBuffer#pushMark}
     */


    popMark() {
      const offset = this._marks.pop();

      if (offset === undefined) {
        throw new Error('Mark stack empty');
      }

      this.seek(offset);
      return this;
    }
    /**
     * Move the pointer offset back to 0.
     */


    rewind() {
      this.offset = 0;
      return this;
    }
    /**
     * Make sure the buffer has sufficient memory to write a given byteLength at
     * the current pointer offset.
     * If the buffer's memory is insufficient, this method will create a new
     * buffer (a copy) with a length that is twice (byteLength + current offset).
     * @param byteLength
     */


    ensureAvailable(byteLength = 1) {
      if (!this.available(byteLength)) {
        const lengthNeeded = this.offset + byteLength;
        const newLength = lengthNeeded * 2;
        const newArray = new Uint8Array(newLength);
        newArray.set(new Uint8Array(this.buffer));
        this.buffer = newArray.buffer;
        this.length = this.byteLength = newLength;
        this._data = new DataView(this.buffer);
      }

      return this;
    }
    /**
     * Read a byte and return false if the byte's value is 0, or true otherwise.
     * Moves pointer forward by one byte.
     */


    readBoolean() {
      return this.readUint8() !== 0;
    }
    /**
     * Read a signed 8-bit integer and move pointer forward by 1 byte.
     */


    readInt8() {
      return this._data.getInt8(this.offset++);
    }
    /**
     * Read an unsigned 8-bit integer and move pointer forward by 1 byte.
     */


    readUint8() {
      return this._data.getUint8(this.offset++);
    }
    /**
     * Alias for {@link IOBuffer#readUint8}.
     */


    readByte() {
      return this.readUint8();
    }
    /**
     * Read `n` bytes and move pointer forward by `n` bytes.
     */


    readBytes(n = 1) {
      const bytes = new Uint8Array(n);

      for (let i = 0; i < n; i++) {
        bytes[i] = this.readByte();
      }

      return bytes;
    }
    /**
     * Read a 16-bit signed integer and move pointer forward by 2 bytes.
     */


    readInt16() {
      const value = this._data.getInt16(this.offset, this.littleEndian);

      this.offset += 2;
      return value;
    }
    /**
     * Read a 16-bit unsigned integer and move pointer forward by 2 bytes.
     */


    readUint16() {
      const value = this._data.getUint16(this.offset, this.littleEndian);

      this.offset += 2;
      return value;
    }
    /**
     * Read a 32-bit signed integer and move pointer forward by 4 bytes.
     */


    readInt32() {
      const value = this._data.getInt32(this.offset, this.littleEndian);

      this.offset += 4;
      return value;
    }
    /**
     * Read a 32-bit unsigned integer and move pointer forward by 4 bytes.
     */


    readUint32() {
      const value = this._data.getUint32(this.offset, this.littleEndian);

      this.offset += 4;
      return value;
    }
    /**
     * Read a 32-bit floating number and move pointer forward by 4 bytes.
     */


    readFloat32() {
      const value = this._data.getFloat32(this.offset, this.littleEndian);

      this.offset += 4;
      return value;
    }
    /**
     * Read a 64-bit floating number and move pointer forward by 8 bytes.
     */


    readFloat64() {
      const value = this._data.getFloat64(this.offset, this.littleEndian);

      this.offset += 8;
      return value;
    }
    /**
     * Read a 1-byte ASCII character and move pointer forward by 1 byte.
     */


    readChar() {
      return String.fromCharCode(this.readInt8());
    }
    /**
     * Read `n` 1-byte ASCII characters and move pointer forward by `n` bytes.
     */


    readChars(n = 1) {
      let result = '';

      for (let i = 0; i < n; i++) {
        result += this.readChar();
      }

      return result;
    }
    /**
     * Read the next `n` bytes, return a UTF-8 decoded string and move pointer
     * forward by `n` bytes.
     */


    readUtf8(n = 1) {
      const bString = this.readChars(n);
      return utf8.decode(bString);
    }
    /**
     * Write 0xff if the passed value is truthy, 0x00 otherwise and move pointer
     * forward by 1 byte.
     */


    writeBoolean(value) {
      this.writeUint8(value ? 0xff : 0x00);
      return this;
    }
    /**
     * Write `value` as an 8-bit signed integer and move pointer forward by 1 byte.
     */


    writeInt8(value) {
      this.ensureAvailable(1);

      this._data.setInt8(this.offset++, value);

      this._updateLastWrittenByte();

      return this;
    }
    /**
     * Write `value` as an 8-bit unsigned integer and move pointer forward by 1
     * byte.
     */


    writeUint8(value) {
      this.ensureAvailable(1);

      this._data.setUint8(this.offset++, value);

      this._updateLastWrittenByte();

      return this;
    }
    /**
     * An alias for {@link IOBuffer#writeUint8}.
     */


    writeByte(value) {
      return this.writeUint8(value);
    }
    /**
     * Write all elements of `bytes` as uint8 values and move pointer forward by
     * `bytes.length` bytes.
     */


    writeBytes(bytes) {
      this.ensureAvailable(bytes.length);

      for (let i = 0; i < bytes.length; i++) {
        this._data.setUint8(this.offset++, bytes[i]);
      }

      this._updateLastWrittenByte();

      return this;
    }
    /**
     * Write `value` as a 16-bit signed integer and move pointer forward by 2
     * bytes.
     */


    writeInt16(value) {
      this.ensureAvailable(2);

      this._data.setInt16(this.offset, value, this.littleEndian);

      this.offset += 2;

      this._updateLastWrittenByte();

      return this;
    }
    /**
     * Write `value` as a 16-bit unsigned integer and move pointer forward by 2
     * bytes.
     */


    writeUint16(value) {
      this.ensureAvailable(2);

      this._data.setUint16(this.offset, value, this.littleEndian);

      this.offset += 2;

      this._updateLastWrittenByte();

      return this;
    }
    /**
     * Write `value` as a 32-bit signed integer and move pointer forward by 4
     * bytes.
     */


    writeInt32(value) {
      this.ensureAvailable(4);

      this._data.setInt32(this.offset, value, this.littleEndian);

      this.offset += 4;

      this._updateLastWrittenByte();

      return this;
    }
    /**
     * Write `value` as a 32-bit unsigned integer and move pointer forward by 4
     * bytes.
     */


    writeUint32(value) {
      this.ensureAvailable(4);

      this._data.setUint32(this.offset, value, this.littleEndian);

      this.offset += 4;

      this._updateLastWrittenByte();

      return this;
    }
    /**
     * Write `value` as a 32-bit floating number and move pointer forward by 4
     * bytes.
     */


    writeFloat32(value) {
      this.ensureAvailable(4);

      this._data.setFloat32(this.offset, value, this.littleEndian);

      this.offset += 4;

      this._updateLastWrittenByte();

      return this;
    }
    /**
     * Write `value` as a 64-bit floating number and move pointer forward by 8
     * bytes.
     */


    writeFloat64(value) {
      this.ensureAvailable(8);

      this._data.setFloat64(this.offset, value, this.littleEndian);

      this.offset += 8;

      this._updateLastWrittenByte();

      return this;
    }
    /**
     * Write the charCode of `str`'s first character as an 8-bit unsigned integer
     * and move pointer forward by 1 byte.
     */


    writeChar(str) {
      return this.writeUint8(str.charCodeAt(0));
    }
    /**
     * Write the charCodes of all `str`'s characters as 8-bit unsigned integers
     * and move pointer forward by `str.length` bytes.
     */


    writeChars(str) {
      for (let i = 0; i < str.length; i++) {
        this.writeUint8(str.charCodeAt(i));
      }

      return this;
    }
    /**
     * UTF-8 encode and write `str` to the current pointer offset and move pointer
     * forward according to the encoded length.
     */


    writeUtf8(str) {
      const bString = utf8.encode(str);
      return this.writeChars(bString);
    }
    /**
     * Export a Uint8Array view of the internal buffer.
     * The view starts at the byte offset and its length
     * is calculated to stop at the last written byte or the original length.
     */


    toArray() {
      return new Uint8Array(this.buffer, this.byteOffset, this.lastWrittenByte);
    }
    /**
     * Update the last written byte offset
     * @private
     */


    _updateLastWrittenByte() {
      if (this.offset > this.lastWrittenByte) {
        this.lastWrittenByte = this.offset;
      }
    }

  }

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

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

  var utils$2 = {};

  /**
   * Throws a non-valid NetCDF exception if the statement it's true
   * @ignore
   * @param {boolean} statement - Throws if true
   * @param {string} reason - Reason to throw
   */


  function notNetcdf$1(statement, reason) {
    if (statement) {
      throw new TypeError(`Not a valid NetCDF v3.x file: ${reason}`);
    }
  }
  /**
   * Moves 1, 2, or 3 bytes to next 4-byte boundary
   * @ignore
   * @param {IOBuffer} buffer - Buffer for the file data
   */


  function padding(buffer) {
    if (buffer.offset % 4 !== 0) {
      buffer.skip(4 - buffer.offset % 4);
    }
  }
  /**
   * Reads the name
   * @ignore
   * @param {IOBuffer} buffer - Buffer for the file data
   * @return {string} - Name
   */


  function readName(buffer) {
    // Read name
    var nameLength = buffer.readUint32();
    var name = buffer.readChars(nameLength); // validate name
    // TODO
    // Apply padding

    padding(buffer);
    return name;
  }

  utils$2.notNetcdf = notNetcdf$1;
  utils$2.padding = padding;
  utils$2.readName = readName;

  var data$1 = {};

  var types$3 = {exports: {}};

  const notNetcdf = utils$2.notNetcdf;
  const types$2 = {
    BYTE: 1,
    CHAR: 2,
    SHORT: 3,
    INT: 4,
    FLOAT: 5,
    DOUBLE: 6
  };
  /**
   * Parse a number into their respective type
   * @ignore
   * @param {number} type - integer that represents the type
   * @return {string} - parsed value of the type
   */

  function num2str(type) {
    switch (Number(type)) {
      case types$2.BYTE:
        return 'byte';

      case types$2.CHAR:
        return 'char';

      case types$2.SHORT:
        return 'short';

      case types$2.INT:
        return 'int';

      case types$2.FLOAT:
        return 'float';

      case types$2.DOUBLE:
        return 'double';

      /* istanbul ignore next */

      default:
        return 'undefined';
    }
  }
  /**
   * Parse a number type identifier to his size in bytes
   * @ignore
   * @param {number} type - integer that represents the type
   * @return {number} -size of the type
   */


  function num2bytes(type) {
    switch (Number(type)) {
      case types$2.BYTE:
        return 1;

      case types$2.CHAR:
        return 1;

      case types$2.SHORT:
        return 2;

      case types$2.INT:
        return 4;

      case types$2.FLOAT:
        return 4;

      case types$2.DOUBLE:
        return 8;

      /* istanbul ignore next */

      default:
        return -1;
    }
  }
  /**
   * Reverse search of num2str
   * @ignore
   * @param {string} type - string that represents the type
   * @return {number} - parsed value of the type
   */


  function str2num(type) {
    switch (String(type)) {
      case 'byte':
        return types$2.BYTE;

      case 'char':
        return types$2.CHAR;

      case 'short':
        return types$2.SHORT;

      case 'int':
        return types$2.INT;

      case 'float':
        return types$2.FLOAT;

      case 'double':
        return types$2.DOUBLE;

      /* istanbul ignore next */

      default:
        return -1;
    }
  }
  /**
   * Auxiliary function to read numeric data
   * @ignore
   * @param {number} size - Size of the element to read
   * @param {function} bufferReader - Function to read next value
   * @return {Array<number>|number}
   */


  function readNumber(size, bufferReader) {
    if (size !== 1) {
      var numbers = new Array(size);

      for (var i = 0; i < size; i++) {
        numbers[i] = bufferReader();
      }

      return numbers;
    } else {
      return bufferReader();
    }
  }
  /**
   * Given a type and a size reads the next element
   * @ignore
   * @param {IOBuffer} buffer - Buffer for the file data
   * @param {number} type - Type of the data to read
   * @param {number} size - Size of the element to read
   * @return {string|Array<number>|number}
   */


  function readType(buffer, type, size) {
    switch (type) {
      case types$2.BYTE:
        return buffer.readBytes(size);

      case types$2.CHAR:
        return trimNull(buffer.readChars(size));

      case types$2.SHORT:
        return readNumber(size, buffer.readInt16.bind(buffer));

      case types$2.INT:
        return readNumber(size, buffer.readInt32.bind(buffer));

      case types$2.FLOAT:
        return readNumber(size, buffer.readFloat32.bind(buffer));

      case types$2.DOUBLE:
        return readNumber(size, buffer.readFloat64.bind(buffer));

      /* istanbul ignore next */

      default:
        notNetcdf(true, `non valid type ${type}`);
        return undefined;
    }
  }
  /**
   * Removes null terminate value
   * @ignore
   * @param {string} value - String to trim
   * @return {string} - Trimmed string
   */


  function trimNull(value) {
    if (value.charCodeAt(value.length - 1) === 0) {
      return value.substring(0, value.length - 1);
    }

    return value;
  }

  types$3.exports = types$2;
  types$3.exports.num2str = num2str;
  types$3.exports.num2bytes = num2bytes;
  types$3.exports.str2num = str2num;
  types$3.exports.readType = readType;

  const types$1 = types$3.exports; // const STREAMING = 4294967295;

  /**
   * Read data for the given non-record variable
   * @ignore
   * @param {IOBuffer} buffer - Buffer for the file data
   * @param {object} variable - Variable metadata
   * @return {Array} - Data of the element
   */

  function nonRecord(buffer, variable) {
    // variable type
    const type = types$1.str2num(variable.type); // size of the data

    var size = variable.size / types$1.num2bytes(type); // iterates over the data

    var data = new Array(size);

    for (var i = 0; i < size; i++) {
      data[i] = types$1.readType(buffer, type, 1);
    }

    return data;
  }
  /**
   * Read data for the given record variable
   * @ignore
   * @param {IOBuffer} buffer - Buffer for the file data
   * @param {object} variable - Variable metadata
   * @param {object} recordDimension - Record dimension metadata
   * @return {Array} - Data of the element
   */


  function record(buffer, variable, recordDimension) {
    // variable type
    const type = types$1.str2num(variable.type);
    const width = variable.size ? variable.size / types$1.num2bytes(type) : 1; // size of the data
    // TODO streaming data

    var size = recordDimension.length; // iterates over the data

    var data = new Array(size);
    const step = recordDimension.recordStep;

    for (var i = 0; i < size; i++) {
      var currentOffset = buffer.offset;
      data[i] = types$1.readType(buffer, type, width);
      buffer.seek(currentOffset + step);
    }

    return data;
  }

  data$1.nonRecord = nonRecord;
  data$1.record = record;

  const utils$1 = utils$2;
  const types = types$3.exports; // Grammar constants

  const ZERO = 0;
  const NC_DIMENSION = 10;
  const NC_VARIABLE = 11;
  const NC_ATTRIBUTE = 12;
  /**
   * Read the header of the file
   * @ignore
   * @param {IOBuffer} buffer - Buffer for the file data
   * @param {number} version - Version of the file
   * @return {object} - Object with the fields:
   *  * `recordDimension`: Number with the length of record dimension
   *  * `dimensions`: List of dimensions
   *  * `globalAttributes`: List of global attributes
   *  * `variables`: List of variables
   */

  function header(buffer, version) {
    // Length of record dimension
    // sum of the varSize's of all the record variables.
    var header = {
      recordDimension: {
        length: buffer.readUint32()
      }
    }; // Version

    header.version = version; // List of dimensions

    var dimList = dimensionsList(buffer);
    header.recordDimension.id = dimList.recordId; // id of the unlimited dimension

    header.recordDimension.name = dimList.recordName; // name of the unlimited dimension

    header.dimensions = dimList.dimensions; // List of global attributes

    header.globalAttributes = attributesList(buffer); // List of variables

    var variables = variablesList(buffer, dimList.recordId, version);
    header.variables = variables.variables;
    header.recordDimension.recordStep = variables.recordStep;
    return header;
  }

  const NC_UNLIMITED = 0;
  /**
   * List of dimensions
   * @ignore
   * @param {IOBuffer} buffer - Buffer for the file data
   * @return {object} - Ojbect containing the following properties:
   *  * `dimensions` that is an array of dimension object:
    *  * `name`: String with the name of the dimension
    *  * `size`: Number with the size of the dimension dimensions: dimensions
   *  * `recordId`: the id of the dimension that has unlimited size or undefined,
   *  * `recordName`: name of the dimension that has unlimited size
   */

  function dimensionsList(buffer) {
    var recordId, recordName;
    const dimList = buffer.readUint32();

    if (dimList === ZERO) {
      utils$1.notNetcdf(buffer.readUint32() !== ZERO, 'wrong empty tag for list of dimensions');
      return [];
    } else {
      utils$1.notNetcdf(dimList !== NC_DIMENSION, 'wrong tag for list of dimensions'); // Length of dimensions

      const dimensionSize = buffer.readUint32();
      var dimensions = new Array(dimensionSize);

      for (var dim = 0; dim < dimensionSize; dim++) {
        // Read name
        var name = utils$1.readName(buffer); // Read dimension size

        const size = buffer.readUint32();

        if (size === NC_UNLIMITED) {
          // in netcdf 3 one field can be of size unlimmited
          recordId = dim;
          recordName = name;
        }

        dimensions[dim] = {
          name: name,
          size: size
        };
      }
    }

    return {
      dimensions: dimensions,
      recordId: recordId,
      recordName: recordName
    };
  }
  /**
   * List of attributes
   * @ignore
   * @param {IOBuffer} buffer - Buffer for the file data
   * @return {Array<object>} - List of attributes with:
   *  * `name`: String with the name of the attribute
   *  * `type`: String with the type of the attribute
   *  * `value`: A number or string with the value of the attribute
   */


  function attributesList(buffer) {
    const gAttList = buffer.readUint32();

    if (gAttList === ZERO) {
      utils$1.notNetcdf(buffer.readUint32() !== ZERO, 'wrong empty tag for list of attributes');
      return [];
    } else {
      utils$1.notNetcdf(gAttList !== NC_ATTRIBUTE, 'wrong tag for list of attributes'); // Length of attributes

      const attributeSize = buffer.readUint32();
      var attributes = new Array(attributeSize);

      for (var gAtt = 0; gAtt < attributeSize; gAtt++) {
        // Read name
        var name = utils$1.readName(buffer); // Read type

        var type = buffer.readUint32();
        utils$1.notNetcdf(type < 1 || type > 6, `non valid type ${type}`); // Read attribute

        var size = buffer.readUint32();
        var value = types.readType(buffer, type, size); // Apply padding

        utils$1.padding(buffer);
        attributes[gAtt] = {
          name: name,
          type: types.num2str(type),
          value: value
        };
      }
    }

    return attributes;
  }
  /**
   * List of variables
   * @ignore
   * @param {IOBuffer} buffer - Buffer for the file data
   * @param {number} recordId - Id of the unlimited dimension (also called record dimension)
   *                            This value may be undefined if there is no unlimited dimension
   * @param {number} version - Version of the file
   * @return {object} - Number of recordStep and list of variables with:
   *  * `name`: String with the name of the variable
   *  * `dimensions`: Array with the dimension IDs of the variable
   *  * `attributes`: Array with the attributes of the variable
   *  * `type`: String with the type of the variable
   *  * `size`: Number with the size of the variable
   *  * `offset`: Number with the offset where of the variable begins
   *  * `record`: True if is a record variable, false otherwise (unlimited size)
   */


  function variablesList(buffer, recordId, version) {
    const varList = buffer.readUint32();
    var recordStep = 0;

    if (varList === ZERO) {
      utils$1.notNetcdf(buffer.readUint32() !== ZERO, 'wrong empty tag for list of variables');
      return [];
    } else {
      utils$1.notNetcdf(varList !== NC_VARIABLE, 'wrong tag for list of variables'); // Length of variables

      const variableSize = buffer.readUint32();
      var variables = new Array(variableSize);

      for (var v = 0; v < variableSize; v++) {
        // Read name
        var name = utils$1.readName(buffer); // Read dimensionality of the variable

        const dimensionality = buffer.readUint32(); // Index into the list of dimensions

        var dimensionsIds = new Array(dimensionality);

        for (var dim = 0; dim < dimensionality; dim++) {
          dimensionsIds[dim] = buffer.readUint32();
        } // Read variables size


        var attributes = attributesList(buffer); // Read type

        var type = buffer.readUint32();
        utils$1.notNetcdf(type < 1 && type > 6, `non valid type ${type}`); // Read variable size
        // The 32-bit varSize field is not large enough to contain the size of variables that require
        // more than 2^32 - 4 bytes, so 2^32 - 1 is used in the varSize field for such variables.

        const varSize = buffer.readUint32(); // Read offset

        var offset = buffer.readUint32();

        if (version === 2) {
          utils$1.notNetcdf(offset > 0, 'offsets larger than 4GB not supported');
          offset = buffer.readUint32();
        }

        let record = false; // Count amount of record variables

        if (typeof recordId !== 'undefined' && dimensionsIds[0] === recordId) {
          recordStep += varSize;
          record = true;
        }

        variables[v] = {
          name: name,
          dimensions: dimensionsIds,
          attributes,
          type: types.num2str(type),
          size: varSize,
          offset,
          record
        };
      }
    }

    return {
      variables: variables,
      recordStep: recordStep
    };
  }

  var header_1 = header;

  function toString$3() {
    let result = [];
    result.push('DIMENSIONS');

    for (let dimension of this.dimensions) {
      result.push(`  ${dimension.name.padEnd(30)} = size: ${dimension.size}`);
    }

    result.push('');
    result.push('GLOBAL ATTRIBUTES');

    for (let attribute of this.globalAttributes) {
      result.push(`  ${attribute.name.padEnd(30)} = ${attribute.value}`);
    }

    let variables = JSON.parse(JSON.stringify(this.variables));
    result.push('');
    result.push('VARIABLES:');

    for (let variable of variables) {
      variable.value = this.getDataVariable(variable);
      let stringify = JSON.stringify(variable.value);
      if (stringify.length > 50) stringify = stringify.substring(0, 50);

      if (!isNaN(variable.value.length)) {
        stringify += ` (length: ${variable.value.length})`;
      }

      result.push(`  ${variable.name.padEnd(30)} = ${stringify}`);
    }

    return result.join('\n');
  }

  var toString_1 = toString$3;

  const {
    IOBuffer
  } = require$$0;
  const utils = utils$2;
  const data = data$1;
  const readHeader = header_1;
  const toString$2 = toString_1;
  /**
   * Reads a NetCDF v3.x file
   * https://www.unidata.ucar.edu/software/netcdf/docs/file_format_specifications.html
   * @param {ArrayBuffer} data - ArrayBuffer or any Typed Array (including Node.js' Buffer from v4) with the data
   * @constructor
   */

  class NetCDFReader$1 {
    constructor(data) {
      const buffer = new IOBuffer(data);
      buffer.setBigEndian(); // Validate that it's a NetCDF file

      utils.notNetcdf(buffer.readChars(3) !== 'CDF', 'should start with CDF'); // Check the NetCDF format

      const version = buffer.readByte();
      utils.notNetcdf(version > 2, 'unknown version'); // Read the header

      this.header = readHeader(buffer, version);
      this.buffer = buffer;
    }
    /**
     * @return {string} - Version for the NetCDF format
     */


    get version() {
      if (this.header.version === 1) {
        return 'classic format';
      } else {
        return '64-bit offset format';
      }
    }
    /**
     * @return {object} - Metadata for the record dimension
     *  * `length`: Number of elements in the record dimension
     *  * `id`: Id number in the list of dimensions for the record dimension
     *  * `name`: String with the name of the record dimension
     *  * `recordStep`: Number with the record variables step size
     */


    get recordDimension() {
      return this.header.recordDimension;
    }
    /**
     * @return {Array<object>} - List of dimensions with:
     *  * `name`: String with the name of the dimension
     *  * `size`: Number with the size of the dimension
     */


    get dimensions() {
      return this.header.dimensions;
    }
    /**
     * @return {Array<object>} - List of global attributes with:
     *  * `name`: String with the name of the attribute
     *  * `type`: String with the type of the attribute
     *  * `value`: A number or string with the value of the attribute
     */


    get globalAttributes() {
      return this.header.globalAttributes;
    }
    /**
     * Returns the value of an attribute
     * @param {string} attributeName
     * @return {string} Value of the attributeName or null
     */


    getAttribute(attributeName) {
      const attribute = this.globalAttributes.find(val => val.name === attributeName);
      if (attribute) return attribute.value;
      return null;
    }
    /**
     * Returns the value of a variable as a string
     * @param {string} variableName
     * @return {string} Value of the variable as a string or null
     */


    getDataVariableAsString(variableName) {
      const variable = this.getDataVariable(variableName);
      if (variable) return variable.join('');
      return null;
    }
    /**
     * @return {Array<object>} - List of variables with:
     *  * `name`: String with the name of the variable
     *  * `dimensions`: Array with the dimension IDs of the variable
     *  * `attributes`: Array with the attributes of the variable
     *  * `type`: String with the type of the variable
     *  * `size`: Number with the size of the variable
     *  * `offset`: Number with the offset where of the variable begins
     *  * `record`: True if is a record variable, false otherwise
     */


    get variables() {
      return this.header.variables;
    }

    toString() {
      return toString$2.call(this);
    }
    /**
     * Retrieves the data for a given variable
     * @param {string|object} variableName - Name of the variable to search or variable object
     * @return {Array} - List with the variable values
     */


    getDataVariable(variableName) {
      let variable;

      if (typeof variableName === 'string') {
        // search the variable
        variable = this.header.variables.find(function (val) {
          return val.name === variableName;
        });
      } else {
        variable = variableName;
      } // throws if variable not found


      utils.notNetcdf(variable === undefined, `variable not found: ${variableName}`); // go to the offset position

      this.buffer.seek(variable.offset);

      if (variable.record) {
        // record variable case
        return data.record(this.buffer, variable, this.header.recordDimension);
      } else {
        // non-record variable case
        return data.nonRecord(this.buffer, variable);
      }
    }
    /**
     * Check if a dataVariable exists
     * @param {string} variableName - Name of the variable to find
     * @return {boolean}
     */


    dataVariableExists(variableName) {
      const variable = this.header.variables.find(function (val) {
        return val.name === variableName;
      });
      return variable !== undefined;
    }
    /**
     * Check if an attribute exists
     * @param {string} attributeName - Name of the attribute to find
     * @return {boolean}
     */


    attributeExists(attributeName) {
      const attribute = this.globalAttributes.find(val => val.name === attributeName);
      return attribute !== undefined;
    }

  }

  var src = NetCDFReader$1;

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

      VARIABLES:
        error_log                      = [" "," "," "," "," "," "," "," "," "," "," "," "," (length: 64)
        a_d_sampling_rate              = [-9999,-9999,-9999,-9999,-9999,-9999,-9999,-9999,- (length: 6401)
        a_d_coaddition_factor          = [-9999,-9999,-9999,-9999,-9999,-9999,-9999,-9999,- (length: 6402)
        scan_acquisition_time          = [5.25,5.84,6.428999999999999,7.019,7.609,8.199,8.7 (length: 6401)
        scan_duration                  = [-9999,-9999,-9999,-9999,-9999,-9999,-9999,-9999,- (length: 6401)
        inter_scan_time                = [-9999,-9999,-9999,-9999,-9999,-9999,-9999,-9999,- (length: 6401)
        resolution                     = [-9999,-9999,-9999,-9999,-9999,-9999,-9999,-9999,- (length: 6401)
        actual_scan_number             = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19 (length: 6401)
        total_intensity                = [3134,3157,3085,3134,3093,3113,3061,3057,3030,3166 (length: 6401)
        mass_range_min                 = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 (length: 6401)
        mass_range_max                 = [206.89999389648438,206.89999389648438,207,207.100 (length: 6401)
        time_range_min                 = [-9999,-9999,-9999,-9999,-9999,-9999,-9999,-9999,- (length: 6401)
        time_range_max                 = [-9999,-9999,-9999,-9999,-9999,-9999,-9999,-9999,- (length: 6401)
        scan_index                     = [0,11,22,33,44,55,66,76,88,99,111,122,134,145,156, (length: 6401)
        point_count                    = [11,11,11,11,11,11,10,12,11,12,11,12,11,11,11,11,1 (length: 6401)
        flag_count                     = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 (length: 6401)
        mass_values                    = [16,17,18.100000381469727,28,32,35,36,38,40,44.099 (length: 157201)
        time_values                    = [9.969209968386869e+36,9.969209968386869e+36,9.969 (length: 157201)
        intensity_values               = [37,293,1243,737,420,45,196,72,22,35,34,28,299,123 (length: 157201)
        instrument_name                = ["G","a","s"," ","C","h","r","o","m","a","t","o"," (length: 32)
        instrument_id                  = [" "," "," "," "," "," "," "," "," "," "," "," "," (length: 32)
        instrument_mfr                 = [" "," "," "," "," "," "," "," "," "," "," "," "," (length: 32)
        instrument_model               = [" "," "," "," "," "," "," "," "," "," "," "," "," (length: 32)
        instrument_serial_no           = [" "," "," "," "," "," "," "," "," "," "," "," "," (length: 32)
        instrument_sw_version          = [" "," "," "," "," "," "," "," "," "," "," "," "," (length: 32)
        instrument_fw_version          = [" "," "," "," "," "," "," "," "," "," "," "," "," (length: 32)
        instrument_os_version          = [" "," "," "," "," "," "," "," "," "," "," "," "," (length: 32)
        instrument_app_version         = [" "," "," "," "," "," "," "," "," "," "," "," "," (length: 32)
        instrument_comments            = [" "," "," "," "," "," "," "," "," "," "," "," "," (length: 32)
  */


  function agilentGCMS$1(reader) {
    const time = reader.getDataVariable('scan_acquisition_time');
    const tic = reader.getDataVariable('total_intensity'); // variables to get the mass-intensity values

    const pointCount = reader.getDataVariable('point_count');
    const massValues = reader.getDataVariable('mass_values');
    const intensityValues = reader.getDataVariable('intensity_values');
    let ms = new Array(pointCount.length);
    let index = 0;

    for (let i = 0; i < ms.length; i++) {
      let size = pointCount[i];
      ms[i] = [new Array(size), new Array(size)];

      for (let j = 0; j < size; j++) {
        ms[i][0][j] = massValues[index];
        ms[i][1][j] = intensityValues[index++];
      }
    }

    return {
      times: time,
      series: [{
        name: 'tic',
        dimension: 1,
        data: tic
      }, {
        name: 'ms',
        dimension: 2,
        data: ms
      }]
    };
  }

  var agilentGCMS_1 = agilentGCMS$1;

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

      VARIABLES:
        error_log                      = [" "," "," "," "," "," "," "," "," "," "," "," "," (length: 64)
        a_d_sampling_rate              = [-9999,-9999,-9999,-9999,-9999,-9999,-9999,-9999,- (length: 4513)
        a_d_coaddition_factor          = [-9999,-9999,-9999,-9999,-9999,-9999,-9999,-9999,- (length: 4514)
        scan_acquisition_time          = [0.329,0.73,1.132,1.534,1.936,2.337,2.739,3.14,3.5 (length: 4513)
        scan_duration                  = [-9999,-9999,-9999,-9999,-9999,-9999,-9999,-9999,- (length: 4513)
        inter_scan_time                = [-9999,-9999,-9999,-9999,-9999,-9999,-9999,-9999,- (length: 4513)
        resolution                     = [106.6623112889557,110.7855343519544,104.407495112 (length: 4513)
        actual_scan_number             = [0,2,4,6,8,10,12,14,16,18,20,22,24,26,28,30,32,34, (length: 4513)
        total_intensity                = [5297.4945068359375,6172.123912811279,5934.7557412 (length: 4513)
        mass_range_min                 = [49.99999997418507,49.99999997418507,49.9999999741 (length: 4513)
        mass_range_max                 = [1599.9999564432276,1599.9999564432276,1599.999956 (length: 4513)
        time_range_min                 = [-9999,-9999,-9999,-9999,-9999,-9999,-9999,-9999,- (length: 4513)
        time_range_max                 = [-9999,-9999,-9999,-9999,-9999,-9999,-9999,-9999,- (length: 4513)
        scan_index                     = [0,60,128,195,265,324,399,472,542,596,671,738,803, (length: 4513)
        point_count                    = [60,68,67,70,59,75,73,70,54,75,67,65,64,73,56,69,6 (length: 4513)
        flag_count                     = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 (length: 4513)
        mass_values                    = [51.53516375878996,95.32974890044508,106.334477231 (length: 1176507)
        intensity_values               = [76.99999237060547,80,90,78.99799346923828,80.9352 (length: 1176507)
        instrument_name                = ["m","i","c","r","O","T","O","F",""," "," "," ","  (length: 32)
        instrument_id                  = [" "," "," "," "," "," "," "," "," "," "," "," "," (length: 32)
        instrument_mfr                 = ["B","r","u","k","e","r"," ","D","a","l","t","o"," (length: 32)
        instrument_model               = [" "," "," "," "," "," "," "," "," "," "," "," "," (length: 32)
        instrument_serial_no           = ["2","1","3","7","5","0",".","1","0","3","5","9"," (length: 32)
        instrument_sw_version          = [" "," "," "," "," "," "," "," "," "," "," "," "," (length: 32)
        instrument_fw_version          = [""," "," "," "," "," "," "," "," "," "," "," ","  (length: 32)
        instrument_os_version          = [" "," "," "," "," "," "," "," "," "," "," "," "," (length: 32)
        instrument_app_version         = [" "," "," "," "," "," "," "," "," "," "," "," "," (length: 32)
        instrument_comments            = [" "," "," "," "," "," "," "," "," "," "," "," "," (length: 32)
  */


  function finniganGCMS$2(reader) {
    const time = reader.getDataVariable('scan_acquisition_time');
    const tic = reader.getDataVariable('total_intensity'); // variables to get the mass-intensity values

    let scanIndex = reader.getDataVariable('scan_index');
    const massValues = reader.getDataVariable('mass_values');
    const intensityValues = reader.getDataVariable('intensity_values');
    scanIndex.push(massValues.length);
    let ms = new Array(time.length);
    let index = 0;

    for (let i = 0; i < ms.length; i++) {
      let size = scanIndex[i + 1] - scanIndex[i];
      ms[i] = [new Array(size), new Array(size)];

      for (let j = 0; j < size; j++) {
        ms[i][0][j] = massValues[index];
        ms[i][1][j] = intensityValues[index++];
      }
    }

    return {
      times: time,
      series: [{
        name: 'tic',
        dimension: 1,
        data: tic
      }, {
        name: 'ms',
        dimension: 2,
        data: ms
      }]
    };
  }

  var brukerGCMS$1 = finniganGCMS$2;

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

     VARIABLES:
        detector_maximum_value         = [130.9263458251953] (length: 1)
        detector_minimum_value         = [-0.1758841574192047] (length: 1)
        actual_run_time_length         = [1860] (length: 1)
        actual_delay_time              = [0.012000000104308128] (length: 1)
        actual_sampling_interval       = [0.4000000059604645] (length: 1)
        ordinate_values                = [-0.07588416337966919,-0.07525086402893066,-0.0740 (length: 4651)
        peak_retention_time            = [196.0651397705078,332.5663757324219,527.549865722 (length: 8)
        peak_start_time                = [186.81199645996094,239.21200561523438,502.4119873 (length: 8)
        peak_end_time                  = [220.81201171875,471.5176696777344,572.47869873046 (length: 8)
        peak_width                     = [4.974428176879883,62.90694808959961,11.9328641891 (length: 8)
        peak_area                      = [556.7650146484375,419.825439453125,66.56610107421 (length: 8)
        peak_area_percent              = [7.0321502685546875,5.302552223205566,0.8407546877 (length: 8)
        peak_height                    = [100.07515716552734,5.1860527992248535,4.827196121 (length: 8)
        peak_height_percent            = [29.76352310180664,1.5423927307128906,1.4356645345 (length: 8)
        peak_asymmetry                 = [1.4555920362472534,0.8351489901542664,1.707817316 (length: 8)
        baseline_start_time            = [186.81199645996094,239.21200561523438,502.4119873 (length: 8)
        baseline_start_value           = [1.9561424255371094,0.9857341647148132,1.127734780 (length: 8)
        baseline_stop_time             = [220.81201171875,471.5176696777344,572.47869873046 (length: 8)
        baseline_stop_value            = [1.1907591819763184,1.10896897315979,1.18347382545 (length: 8)
        peak_start_detection_code      = ["B","","B","","B","","B","","V","","B","","B","", (length: 16)
        peak_stop_detection_code       = ["B","","B","","B","","V","","B","","B","","B","", (length: 16)
        migration_time                 = [196.0651397705078,332.5663757324219,527.549865722 (length: 8)
        peak_area_square_root          = [23.595869064331055,20.489643096923828,8.158804893 (length: 8)
        manually_reintegrated_peaks    = [0,0,0,0,0,0,0,0] (length: 8)
  */


  function agilentHPLC$1(reader) {
    const intensities = reader.getDataVariable('ordinate_values');
    const numberPoints = intensities.length;
    const detector = reader.getAttribute('detector_name');
    let channel;

    if (detector.match(/dad/i)) {
      channel = `uv${Number(detector.replace(/.*Sig=([0-9]+).*/, '$1'))}`;
    } else if (detector.match(/tic/i)) {
      channel = 'tic';
    } else {
      channel = 'unknown';
    }

    const delayTime = reader.getDataVariable('actual_delay_time')[0];
    const runtimeLength = reader.getDataVariable('actual_run_time_length')[0];
    let samplingInterval;

    if (reader.dataVariableExists('actual_sampling_interval')) {
      samplingInterval = reader.getDataVariable('actual_sampling_interval')[0];

      if (Math.abs(delayTime + samplingInterval * numberPoints - runtimeLength) > 3) {
        throw new Error('The expected last time does not correspond to the runtimeLength');
      }
    } else {
      samplingInterval = (runtimeLength - delayTime) / numberPoints;
    }

    let times = [];
    let time = delayTime;

    for (let i = 0; i < numberPoints; i++) {
      times.push(time);
      time += samplingInterval;
    }

    return {
      times,
      series: [{
        name: channel,
        dimension: 1,
        data: intensities
      }]
    };
  }

  var agilentHPLC_1 = agilentHPLC$1;

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

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

  */


  function finniganGCMS$1(reader) {
    const time = reader.getDataVariable('scan_acquisition_time');
    const tic = reader.getDataVariable('total_intensity'); // variables to get the mass-intensity values

    let scanIndex = reader.getDataVariable('scan_index');
    const massValues = reader.getDataVariable('mass_values');
    const intensityValues = reader.getDataVariable('intensity_values');
    scanIndex.push(massValues.length);
    let ms = new Array(time.length);
    let index = 0;

    for (let i = 0; i < ms.length; i++) {
      let size = scanIndex[i + 1] - scanIndex[i];
      ms[i] = [new Array(size), new Array(size)];

      for (let j = 0; j < size; j++) {
        ms[i][0][j] = massValues[index];
        ms[i][1][j] = intensityValues[index++];
      }
    }

    return {
      times: time,
      series: [{
        name: 'tic',
        dimension: 1,
        data: tic
      }, {
        name: 'ms',
        dimension: 2,
        data: ms
      }]
    };
  }

  var finniganGCMS_1 = finniganGCMS$1;

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

      VARIABLES:
        error_log                      = [" "," "," "," "," "," "," "," "," "," "," "," "," (length: 64)
        a_d_sampling_rate              = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 (length: 3820)
        a_d_coaddition_factor          = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 (length: 3820)
        scan_acquisition_time          = [144,144.3,144.6,144.9,145.2,145.5,145.8,146.1,146 (length: 3820)
        scan_duration                  = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 (length: 3820)
        inter_scan_time                = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 (length: 3820)
        resolution                     = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 (length: 3820)
        actual_scan_number             = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,2 (length: 3820)
        total_intensity                = [63566,61702,61873,59738,58321,59001,59364,59871,6 (length: 3820)
        mass_range_min                 = [35,35,35,35,35,35,35,35,35,35,35,35,35,35,35,35,3 (length: 3820)
        mass_range_max                 = [500,500,500,500,500,500,500,500,500,500,500,500,5 (length: 3820)
        time_range_min                 = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 (length: 3820)
        time_range_max                 = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 (length: 3820)
        scan_index                     = [0,466,932,1398,1863,2329,2795,3260,3726,4192,4658 (length: 3820)
        point_count                    = [466,466,466,465,466,466,465,466,466,466,466,466,4 (length: 3820)
        flag_count                     = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 (length: 3820)
        mass_values                    = [35,36,37.1,38.1,39.1,40.15,41.1,42.1,43.15,44.1,4 (length: 1779397)
        intensity_values               = [26,111,412,785,3098,485,5772,7391,11213,711,687,1 (length: 1779397)
        instrument_name                = [" "," "," "," "," "," "," "," "," "," "," "," "," (length: 32)
        instrument_id                  = [" "," "," "," "," "," "," "," "," "," "," "," "," (length: 32)
        instrument_mfr                 = ["S","h","i","m","a","d","z","u"," ","C","o","r"," (length: 32)
        instrument_model               = ["G","C","M","S","","","","","","","","","","","", (length: 32)
        instrument_serial_no           = ["","","","","","","","","","","","","","","",""," (length: 32)
        instrument_sw_version          = ["4",".","2","0","","","","","","","","","","","", (length: 32)
        instrument_fw_version          = ["G","C","M","S","-","Q","P","2","0","1","0","1"," (length: 32)
        instrument_os_version          = ["W","i","n","d","o","w","s","","","","","","","", (length: 32)
        instrument_app_version         = ["G","C","M","S","s","o","l","u","t","i","o","n"," (length: 32)
        instrument_comments            = [" "," "," "," "," "," "," "," "," "," "," "," "," (length: 32)
  */


  function shimadzuGCMS$1(reader) {
    const time = reader.getDataVariable('scan_acquisition_time');
    const tic = reader.getDataVariable('total_intensity'); // variables to get the mass-intensity values

    let scanIndex = reader.getDataVariable('scan_index');
    const massValues = reader.getDataVariable('mass_values');
    const intensityValues = reader.getDataVariable('intensity_values');
    scanIndex.push(massValues.length);
    let ms = new Array(time.length);
    let index = 0;

    for (let i = 0; i < ms.length; i++) {
      let size = scanIndex[i + 1] - scanIndex[i];
      ms[i] = [new Array(size), new Array(size)];

      for (let j = 0; j < size; j++) {
        ms[i][0][j] = massValues[index];
        ms[i][1][j] = intensityValues[index++];
      }
    }

    return {
      times: time,
      series: [{
        name: 'tic',
        dimension: 1,
        data: tic
      }, {
        name: 'ms',
        dimension: 2,
        data: ms
      }]
    };
  }

  var shimadzuGCMS_1 = shimadzuGCMS$1;

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

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

      VARIABLES:
        error_log                      = ["","","","","","","","","","","","","","","",""," (length: 64)
        scan_index                     = [0,3096,6282,9865,13409,16765,20281,23603,27099,30 (length: 60)
        point_count                    = [3096,3186,3583,3544,3356,3516,3322,3496,3351,3031 (length: 60)
        flag_count                     = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 (length: 60)
        actual_scan_number             = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19 (length: 60)
        a_d_coaddition_factor          = [-9999,-9999,-9999,-9999,-9999,-9999,-9999,-9999,- (length: 60)
        a_d_sampling_rate              = [-9999,-9999,-9999,-9999,-9999,-9999,-9999,-9999,- (length: 60)
        inter_scan_time                = [-9999,-9999,-9999,-9999,-9999,-9999,-9999,-9999,- (length: 60)
        mass_range_min                 = [9.949999809265137,9.949999809265137,9.94999980926 (length: 60)
        mass_range_max                 = [164.6999969482422,169.1999969482422,189.050003051 (length: 60)
        scan_acquisition_time          = [0.08100000023841858,2.3420000076293945,4.60300016 (length: 60)
        scan_duration                  = [2.261000007390976,2.261000156402588,2.25999975204 (length: 60)
        resolution                     = [-9999,-9999,-9999,-9999,-9999,-9999,-9999,-9999,- (length: 60)
        time_range_min                 = [-9999,-9999,-9999,-9999,-9999,-9999,-9999,-9999,- (length: 60)
        time_range_max                 = [-9999,-9999,-9999,-9999,-9999,-9999,-9999,-9999,- (length: 60)
        total_intensity                = [4498210816,4468554240,5001547264,5405233152,50000 (length: 60)
        mass_values                    = [9.949999809265137,83.5,83.55000305175781,83.59999 (length: 199393)
        intensity_values               = [0,818716,462148,0,735558,952901,0,165241,421829,0 (length: 199393)
  */


  function advionGCMS$1(reader) {
    const time = reader.getDataVariable('scan_acquisition_time');
    const tic = reader.getDataVariable('total_intensity'); // variables to get the mass-intensity values

    let scanIndex = reader.getDataVariable('scan_index');
    const massValues = reader.getDataVariable('mass_values');
    const intensityValues = reader.getDataVariable('intensity_values');
    scanIndex.push(massValues.length);
    let ms = new Array(time.length);
    let index = 0;

    for (let i = 0; i < ms.length; i++) {
      let size = scanIndex[i + 1] - scanIndex[i];
      ms[i] = [new Array(size), new Array(size)];

      for (let j = 0; j < size; j++) {
        ms[i][0][j] = massValues[index];
        ms[i][1][j] = intensityValues[index++];
      }
    }

    return {
      times: time,
      series: [{
        name: 'tic',
        dimension: 1,
        data: tic
      }, {
        name: 'ms',
        dimension: 2,
        data: ms
      }]
    };
  }

  var advionGCMS_1 = advionGCMS$1;

  function aiaTemplate$1(reader) {
    let time = [];
    const tic = reader.getDataVariable('ordinate_values'); // variables to get the time

    const delayTime = Number(reader.getDataVariable('actual_delay_time'));
    const interval = Number(reader.getDataVariable('actual_sampling_interval'));
    let currentTime = delayTime;

    for (let i = 0; i < tic.length; i++) {
      time.push(currentTime);
      currentTime += interval;
    }

    return {
      times: time,
      series: [{
        name: 'tic',
        dimension: 1,
        data: tic
      }]
    };
  }

  var aiaTemplate_1 = aiaTemplate$1;

  const NetCDFReader = src;
  const agilentGCMS = agilentGCMS_1;
  const brukerGCMS = brukerGCMS$1;
  const agilentHPLC = agilentHPLC_1;
  const finniganGCMS = finniganGCMS_1;
  const shimadzuGCMS = shimadzuGCMS_1;
  const advionGCMS = advionGCMS_1;
  const aiaTemplate = aiaTemplate_1;
  /**
   * Reads a NetCDF file and returns a formatted JSON with the data from it
   * @param {ArrayBuffer} data - ArrayBuffer or any Typed Array (including Node.js' Buffer from v4) with the data
   * @param {object} [options={}]
   * @param {boolean} [options.meta] - add meta information
   * @param {boolean} [options.variables] -add variables information
   * @return {{times, series}} - JSON with the time, TIC and mass spectra values
   */

  function netcdfGcms(data, options = {}) {
    let reader = new NetCDFReader(data);
    const globalAttributes = reader.globalAttributes;
    let instrument_mfr = reader.dataVariableExists('instrument_mfr') && reader.getDataVariableAsString('instrument_mfr');
    let dataset_origin = reader.attributeExists('dataset_origin');
    let mass_values = reader.dataVariableExists('mass_values');
    let detector_name = reader.getAttribute('detector_name');
    let aia_template_revision = reader.attributeExists('aia_template_revision');
    let source_file_format = reader.getAttribute('source_file_format');
    let ans;

    if (mass_values && dataset_origin) {
      ans = agilentGCMS(reader);
    } else if (mass_values && instrument_mfr && instrument_mfr.match(/finnigan/i)) {
      ans = finniganGCMS(reader);
    } else if (mass_values && instrument_mfr && instrument_mfr.match(/bruker/i)) {
      ans = brukerGCMS(reader);
    } else if (mass_values && instrument_mfr && instrument_mfr.match(/bruker/i)) {
      ans = brukerGCMS(reader);
    } else if (mass_values && source_file_format && source_file_format.match(/shimadzu/i)) {
      ans = shimadzuGCMS(reader);
    } else if (mass_values && source_file_format && source_file_format.match(/advion/i)) {
      ans = advionGCMS(reader);
    } else if (detector_name && detector_name.match(/(dad|tic)/i)) {
      // diode array agilent HPLC
      ans = agilentHPLC(reader);
    } else if (aia_template_revision) {
      ans = aiaTemplate(reader);
    } else {
      throw new TypeError('Unknown file format');
    }

    if (options.meta) {
      ans.meta = addMeta(globalAttributes);
    }

    if (options.variables) {
      ans.variables = addVariables(reader);
    }

    return ans;
  }
  /**
   * Reads a NetCDF file with Agilent GCMS format and returns a formatted JSON with the data from it
   * @param {ArrayBuffer} data - ArrayBuffer or any Typed Array (including Node.js' Buffer from v4) with the data
   * @return {{times, series}} - JSON with the time, TIC and mass spectra values
   */


  function fromAgilentGCMS(data) {
    return agilentGCMS(new NetCDFReader(data));
  }
  /**
   * Reads a NetCDF file with Agilent HPLC format and returns a formatted JSON with the data from it
   * @param {ArrayBuffer} data - ArrayBuffer or any Typed Array (including Node.js' Buffer from v4) with the data
   * @return {{times, series}} - JSON with the time, TIC and mass spectra values
   */


  function fromAgilentHPLC(data) {
    return agilentHPLC(new NetCDFReader(data));
  }
  /**
   * Reads a NetCDF file with Finnigan format and returns a formatted JSON with the data from it
   * @param {ArrayBuffer} data - ArrayBuffer or any Typed Array (including Node.js' Buffer from v4) with the data
   * @return {{times, series}} - JSON with the time, TIC and mass spectra values
   */


  function fromFinniganGCMS(data) {
    return finniganGCMS(new NetCDFReader(data));
  }

  function fromAiaTemplate(data) {
    return aiaTemplate(new NetCDFReader(data));
  }

  function addMeta(globalAttributes) {
    let ans = {};

    for (const item of globalAttributes) {
      ans[item.name] = item.value;
    }

    return ans;
  }

  function addVariables(reader) {
    for (let variable of reader.variables) {
      variable.value = reader.getDataVariable(variable);
    }

    return reader.variables;
  }

  src$1.exports = netcdfGcms;
  src$1.exports.fromAgilentGCMS = fromAgilentGCMS;
  src$1.exports.fromAgilentHPLC = fromAgilentHPLC;
  src$1.exports.fromFinniganGCMS = fromFinniganGCMS;
  src$1.exports.fromAiaTemplate = fromAiaTemplate;
  var netcdfJSON = src$1.exports;

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

  const utf8Decoder$1 = new TextDecoder();
  const decoder$5 = {
    decode: array => {
      return utf8Decoder$1.decode(array);
    }
  };
  const defaultOptions = {
    trimValues: true,
    attributeNamePrefix: '$',
    attributesNodeName: false,
    ignoreAttributes: false,
    ignoreNameSpace: false,
    allowBooleanAttributes: false,
    dynamicTypingAttributeValue: true,
    textNodeName: '#text',
    dynamicTypingNodeValue: true,
    arrayMode: false,
    cdataTagName: false,
    tagValueProcessor: value => {
      return decoder$5.decode(value).replace(/\r/g, '');
    },
    attributeValueProcessor: value => value,
    stopNodes: []
  };

  class XMLNode {
    constructor(tagName, parent, value) {
      this.tagName = tagName;
      this.parent = parent;
      this.children = Object.create({}); //child tags

      this.attributes = Object.create({}); //attributes map

      this.value = value; //text only

      this.startIndex = -1;
    }

    addChild(child) {
      if (Array.isArray(this.children[child.tagName])) {
        //already presents
        this.children[child.tagName].push(child);
      } else {
        this.children[child.tagName] = [child];
      }
    }

  }

  function arrayIndexOf(array, referenceArray, index = 0) {
    let found = 0;
    let foundIndex = -1;

    for (let i = index; i < array.length && found < referenceArray.length; i++) {
      if (array[i] === referenceArray[found]) {
        if (!found) {
          foundIndex = i;
        }

        found++;
      } else {
        if (found > 0) {
          let j = 0;

          for (; j <= found && array[foundIndex + j] === array[foundIndex + found]; j++);

          if (j < found + 1) {
            foundIndex = -1;
            found = 0;
          } else {
            foundIndex++;
          }
        } else {
          found = 0;
          foundIndex = -1;
        }
      }
    }

    if (found !== referenceArray.length) {
      foundIndex = -1;
    }

    return foundIndex;
  }

  function arrayTrim(array) {
    let i = 0;
    let j = array.length - 1;

    for (; i < array.length && array[i] <= 0x20; i++);

    for (; j >= i && array[j] <= 0x20; j--);

    if (i === 0 && j === array.length - 1) return array;
    return array.subarray(i, j + 1);
  }

  function closingIndexForOpeningTag(data, i) {
    let attrBoundary;
    let endIndex = 0;

    for (let index = i; index < data.length; index++) {
      let byte = data[index];

      if (attrBoundary) {
        if (byte === attrBoundary) attrBoundary = 0; //reset
      } else if (byte === 0x22 || byte === 0x27) {
        attrBoundary = byte;
      } else if (byte === 0x3e) {
        return {
          data: decoder$4.decode(data.subarray(i, i + endIndex)),
          index
        };
      } else if (byte === 0x09) {
        byte = 0x20;
      }

      endIndex++;
    }
  }

  function findClosingIndex(xmlData, str, i, errMsg) {
    const closingIndex = arrayIndexOf(xmlData, str, i);

    if (closingIndex === -1) {
      throw new Error(errMsg);
    } else {
      return closingIndex + str.length - 1;
    }
  }

  function getAllMatches(string, regex) {
    return Array.from(string.matchAll(regex));
  }
  function isEmptyObject(obj) {
    // fastest implementation: https://jsbench.me/qfkqv692c8/1
    // eslint-disable-next-line no-unreachable-loop
    for (const key in obj) {
      return false;
    }

    return true;
  }
  /**
   * Copy all the properties of a into b.
   * @param {object} target
   * @param {object} source
   */

  function merge(target, source, arrayMode) {
    if (!source) return;

    for (const key in source) {
      if (arrayMode === 'strict') {
        target[key] = [source[key]];
      } else {
        target[key] = source[key];
      }
    }
  }
  /**
   * Check if a tag name should be treated as array
   *
   * @param tagName the node tagName
   * @param arrayMode the array mode option
   * @param parentTagName the parent tag name
   * @returns {boolean} true if node should be parsed as array
   */

  function isTagNameInArrayMode(tagName, arrayMode, parentTagName) {
    if (arrayMode === false) {
      return false;
    } else if (arrayMode instanceof RegExp) {
      return arrayMode.test(tagName);
    } else if (typeof arrayMode === 'function') {
      return !!arrayMode(tagName, parentTagName);
    }

    return arrayMode === 'strict';
  }

  const newLocal = '([^\\s=]+)\\s*(=\\s*([\'"])(.*?)\\3)?';
  const attrsRegx = new RegExp(newLocal, 'g'); //Attributes are strings so no point in using arrayBuffers here

  function parseAttributesString(string, options) {
    if (options.ignoreAttributes) {
      return;
    }

    string = string.replace(/\r?\n/g, ' ');
    const matches = getAllMatches(string, attrsRegx);
    const attributes = {};

    for (let match of matches) {
      const attrName = resolveNameSpace(match[1], options);

      if (attrName.length) {
        if (match[4] !== undefined) {
          if (options.trimValues) {
            match[4] = match[4].trim();
          }

          match[4] = options.attributeValueProcessor(match[4], attrName);
          attributes[attrName] = stringParseValue(match[4], options.dynamicTypingAttributeValue);
        } else if (options.allowBooleanAttributes) {
          attributes[attrName] = true;
        }
      }
    }

    if (isEmptyObject(attributes)) return;
    return attributes;
  }

  function stringParseValue(value, shouldParse) {
    if (shouldParse && typeof value === 'string') {
      return parseString(value);
    } else {
      return value === undefined ? '' : value;
    }
  }

  function resolveNameSpace(tagName, options) {
    if (options.ignoreNameSpace) {
      const tags = tagName.split(':');
      const prefix = tagName.charAt(0) === '/' ? '/' : '';

      if (tags[0] === 'xmlns') {
        return '';
      }

      if (tags.length === 2) {
        tagName = prefix + tags[1];
      }
    }

    return tagName;
  }

  const utf8Decoder = new TextDecoder();
  const decoder$4 = {
    decode: array => {
      return utf8Decoder.decode(array);
    }
  };
  function getTraversable(xmlData, options) {
    const traversable = new XMLNode('!xml');
    let currentNode = traversable;
    let dataSize = 0;
    let dataIndex = 0;

    for (let i = 0; i < xmlData.length; i++) {
      if (xmlData[i] === 0x3c) {
        // <
        const xmlData1 = xmlData[i + 1];
        const xmlData2 = xmlData[i + 2];

        if (xmlData1 === 0x2f) {
          // </ Closing Tag
          const closeIndex = findClosingIndex(xmlData, [0x3e], //>
          i, 'Closing Tag is not closed.');
          let tagName = decoder$4.decode(arrayTrim(xmlData.subarray(i + 2, closeIndex)));
          tagName = removeNameSpaceIfNeeded(tagName, options);

          if (currentNode) {
            const value = options.trimValues ? arrayTrim(xmlData.subarray(dataIndex, dataIndex + dataSize)) : xmlData.subarray(dataIndex, dataIndex + dataSize);

            if (currentNode.value === undefined) {
              currentNode.value = value;
            } else {
              currentNode.value = concat(currentNode.value, value);
            }
          }

          if (options.stopNodes.length && options.stopNodes.includes(currentNode.tagName)) {
            currentNode.children = [];

            if (currentNode.attributes === undefined) {
              currentNode.attributes = {};
            }

            currentNode.value = xmlData.subarray(currentNode.startIndex + 1, i);
          }

          currentNode = currentNode.parent;
          i = closeIndex;
          dataSize = 0;
          dataIndex = i + 1;
        } else if (xmlData1 === 0x3f) {
          // <? PI, processing instruction
          i = findClosingIndex(xmlData, [0x3f, 0x3e], i, 'Pi Tag is not closed.');
        } else if ( //!-- comment
        xmlData1 === 0x21 && xmlData2 === 0x2d && xmlData[i + 3] === 0x2d) {
          i = findClosingIndex(xmlData, [0x2d, 0x2d, 0x3e], //-->
          i, 'Comment is not closed.');

          if (currentNode && dataSize !== 0) {
            if (currentNode.tagName !== '!xml') {
              currentNode.value = concat(currentNode.value, options.trimValues ? arrayTrim(xmlData.subarray(dataIndex, dataSize + dataIndex)) : xmlData.subarray(dataIndex, dataSize + dataIndex));
            }
          }

          dataSize = 0;
          dataIndex = i + 1; //!D
        } else if (xmlData1 === 0x21 && xmlData2 === 0x44) {
          // <!D
          const closeIndex = findClosingIndex(xmlData, [0x3e], //>
          i, 'DOCTYPE is not closed.');
          const tagExp = xmlData.subarray(i, closeIndex);

          if (arrayIndexOf(tagExp, [0x5b]) >= 0) {
            i = arrayIndexOf(xmlData, [0x5d, 0x3e], i) + 1;
          } else {
            i = closeIndex;
          } //![

        } else if (xmlData1 === 0x21 && xmlData2 === 0x5b) {
          // <![CDATA[some stuff]]>
          const closeIndex = findClosingIndex(xmlData, [0x5d, 0x5d, 0x3e], //]]>
          i, 'CDATA is not closed.') - 2;
          const tagExp = xmlData.subarray(i + 9, closeIndex); //considerations
          //1. CDATA will always have parent node
          //2. A tag with CDATA is not a leaf node so it's value would be string type.

          if (dataSize !== 0) {
            const value = options.trimValues ? arrayTrim(xmlData.subarray(dataIndex, dataIndex + dataSize)) : xmlData.subarray(dataIndex, dataIndex + dataSize);
            currentNode.value = concat(currentNode.value, value);
          }

          if (options.cdataTagName) {
            //add cdata node
            const childNode = new XMLNode(options.cdataTagName, currentNode, tagExp);
            currentNode.addChild(childNode); //add rest value to parent node

            if (tagExp) {
              childNode.value = tagExp;
            }
          } else {
            currentNode.value = concat(currentNode.value, tagExp);
          }

          i = closeIndex + 2;
          dataSize = 0;
          dataIndex = i + 1;
        } else {
          //Opening a normal tag
          const parsedOpeningTag = closingIndexForOpeningTag(xmlData, i + 1);
          let tagData = parsedOpeningTag.data.replace(/\r?\n|\t/g, ' ');
          const closeIndex = parsedOpeningTag.index;
          const separatorIndex = tagData.indexOf(' ');
          let shouldBuildAttributesMap = true;
          let tagName = separatorIndex >= 0 ? tagData.substr(0, separatorIndex).replace(/\s+$/, '') : tagData;
          let tagAttributes = separatorIndex >= 0 ? tagData.substr(separatorIndex + 1) : '';

          if (options.ignoreNameSpace) {
            const colonIndex = tagName.indexOf(':');

            if (colonIndex !== -1) {
              tagName = tagName.substr(colonIndex + 1);
              shouldBuildAttributesMap = tagName !== parsedOpeningTag.data.substr(colonIndex + 1);
            }
          } //save text to parent node


          if (currentNode && dataSize !== 0) {
            if (currentNode.tagName !== '!xml') {
              currentNode.value = concat(currentNode.value, options.trimValues ? arrayTrim(xmlData.subarray(dataIndex, dataIndex + dataSize)) : xmlData.subarray(dataIndex, dataIndex + dataSize));
            }
          }

          if (tagData.length > 0 && tagData[tagData.length - 1] === '/') {
            //selfClosing tag
            if (tagAttributes) {
              // <abc def="123"/>
              tagAttributes = tagAttributes.substr(0, tagAttributes.length - 1);
            } else {
              // <abc/>
              tagName = tagName.substr(0, tagName.length - 1);
            }

            const childNode = new XMLNode(tagName, currentNode, '');

            if (tagAttributes) {
              childNode.attributes = parseAttributesString(tagAttributes, options);
            }

            currentNode.addChild(childNode);
          } else {
            //opening tag
            const childNode = new XMLNode(tagName, currentNode);

            if (options.stopNodes.length && options.stopNodes.includes(childNode.tagName)) {
              childNode.startIndex = closeIndex;
            }

            if (tagAttributes && shouldBuildAttributesMap) {
              childNode.attributes = parseAttributesString(tagAttributes, options);
            }

            currentNode.addChild(childNode);
            currentNode = childNode;
          }

          i = closeIndex;
          dataSize = 0;
          dataIndex = i + 1;
        }
      } else {
        dataSize++;
      }
    }

    return traversable;
  }

  function concat(a, b) {
    if (a === undefined) {
      a = typeof b === 'string' ? '' : new Uint8Array(0);
    }

    if (b === undefined) {
      b = typeof a === 'string' ? '' : new Uint8Array(0);
    }

    if (typeof a === 'string' && typeof b === 'string') {
      return a + b;
    } else if (ArrayBuffer.isView(a) && ArrayBuffer.isView(b)) {
      const arrayConcat = new Uint8Array(a.length + b.length);
      arrayConcat.set(a);
      arrayConcat.set(b, a.length);
      return arrayConcat;
    } else {
      throw new Error(`Unsuported value type for concatenation: ${typeof a} ${typeof b}`);
    }
  }

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

    if (colonIndex !== -1) {
      tagName = tagName.substr(colonIndex + 1);
    }
  }

  /**
   *
   * @param {*} node
   * @param {*} options
   * @param {*} parentTagName
   * @returns
   */

  function traversableToJSON(node, options, parentTagName) {
    const {
      dynamicTypingNodeValue,
      tagValueProcessor,
      arrayMode
    } = options;
    const result = {};

    if (tagValueProcessor) {
      node.value = node.value && tagValueProcessor(node.value, node);
    }

    if (typeof node.value === 'string' && dynamicTypingNodeValue) {
      node.value = parseString(node.value);
    } // when no child node or attr is present


    if ((!node.children || isEmptyObject(node.children)) && (!node.attributes || isEmptyObject(node.attributes))) {
      return node.value === undefined ? '' : node.value;
    } // otherwise create a textnode if node has some text


    if (node.value !== undefined && node.value.length !== 0) {
      const asArray = isTagNameInArrayMode(node.tagName, arrayMode, parentTagName);
      result[options.textNodeName] = asArray ? [node.value] : node.value;
    }

    if (node.attributes && !isEmptyObject(node.attributes)) {
      let attributes = options.parseAttributesString ? {} : node.attributes;

      if (options.attributeNamePrefix) {
        // need to rename the attributes
        const renamedAttributes = {};

        for (let key in node.attributes) {
          renamedAttributes[options.attributeNamePrefix + key] = node.attributes[key];
        }

        attributes = renamedAttributes;
      }

      if (options.attributesNodeName) {
        let encapsulatedAttributes = {};
        encapsulatedAttributes[options.attributesNodeName] = attributes;
        attributes = encapsulatedAttributes;
      }

      merge(result, attributes, arrayMode);
    }

    const keys = Object.keys(node.children);

    for (let index = 0; index < keys.length; index++) {
      const tagName = keys[index];

      if (node.children[tagName] && node.children[tagName].length > 1) {
        result[tagName] = [];

        for (let tag in node.children[tagName]) {
          if (Object.prototype.hasOwnProperty.call(node.children[tagName], tag)) {
            result[tagName].push(traversableToJSON(node.children[tagName][tag], options, tagName));
          }
        }
      } else {
        const subResult = traversableToJSON(node.children[tagName][0], options, tagName);
        const asArray = arrayMode === true && typeof subResult === 'object' || isTagNameInArrayMode(tagName, arrayMode, parentTagName);
        result[tagName] = asArray ? [subResult] : subResult;
      }
    }

    return result;
  }

  /**
   * Parse an ArrayBuffer or Uint8Array representing an XML
   * @param {ArrayBuffer|Uint8Arra} xmlData
   * @param {object} [options={}]
   * @param {string} [attributeNamePrefix='$']
   * @param {boolean} [attributesNodeName=false]
   * @param {string} [textNodeName='#text']
   * @param {boolean} [trimValues=true] should we remove ascii < 32
   * @param {boolean} [ignoreAttributes=false] skip attributes
   * @param {boolean} [ignoreNameSpace=false]
   * @param {boolean} [dynamicTypingAttributeValue=true] Parse attribute values that looks like number or boolean
   * @param {boolean} [allowBooleanAttributes=false]
   * @param {boolean} [dynamicTypingNodeValue=true] Parse tag values that looks like number or boolean
   * @param {boolean} [arrayMode=false]
   * @param {boolean} [cdataTagName=false]
   * @param {function} [tagValueProcessor=(v, node) => decoder.decode(v)] Tag values can be modified during parsing. By default we decode the tag value (a Uint8Array) using TextDecoder
   * @param {function} [attributeValueProcessor=(v) => v] Attribute values can be modified during parsing
   * @param {boolean} [stopNodes=[]] prevent further parsing
   *
   * @returns {object}
   */

  function parse(xmlData, options = {}) {
    if (typeof xmlData === 'string') {
      const encoder = new TextEncoder();
      xmlData = encoder.encode(xmlData);
    }

    if (!ArrayBuffer.isView(xmlData)) {
      xmlData = new Uint8Array(xmlData);
    }

    options = { ...defaultOptions,
      ...options
    };
    const traversable = getTraversable(xmlData, options);
    return traversableToJSON(traversable, options);
  }

  /*! pako 2.0.4 https://github.com/nodeca/pako @license (MIT AND Zlib) */
  // (C) 1995-2013 Jean-loup Gailly and Mark Adler
  // (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin
  //
  // This software is provided 'as-is', without any express or implied
  // warranty. In no event will the authors be held liable for any damages
  // arising from the use of this software.
  //
  // Permission is granted to anyone to use this software for any purpose,
  // including commercial applications, and to alter it and redistribute it
  // freely, subject to the following restrictions:
  //
  // 1. The origin of this software must not be misrepresented; you must not
  //   claim that you wrote the original software. If you use this software
  //   in a product, an acknowledgment in the product documentation would be
  //   appreciated but is not required.
  // 2. Altered source versions must be plainly marked as such, and must not be
  //   misrepresented as being the original software.
  // 3. This notice may not be removed or altered from any source distribution.

  /* eslint-disable space-unary-ops */

  /* Public constants ==========================================================*/

  /* ===========================================================================*/
  //const Z_FILTERED          = 1;
  //const Z_HUFFMAN_ONLY      = 2;
  //const Z_RLE               = 3;
  const Z_FIXED$1 = 4; //const Z_DEFAULT_STRATEGY  = 0;

  /* Possible values of the data_type field (though see inflate()) */

  const Z_BINARY = 0;
  const Z_TEXT = 1; //const Z_ASCII             = 1; // = Z_TEXT

  const Z_UNKNOWN$1 = 2;
  /*============================================================================*/

  function zero$1(buf) {
    let len = buf.length;

    while (--len >= 0) {
      buf[len] = 0;
    }
  } // From zutil.h


  const STORED_BLOCK = 0;
  const STATIC_TREES = 1;
  const DYN_TREES = 2;
  /* The three kinds of block type */

  const MIN_MATCH$1 = 3;
  const MAX_MATCH$1 = 258;
  /* The minimum and maximum match lengths */
  // From deflate.h

  /* ===========================================================================
   * Internal compression state.
   */

  const LENGTH_CODES$1 = 29;
  /* number of length codes, not counting the special END_BLOCK code */

  const LITERALS$1 = 256;
  /* number of literal bytes 0..255 */

  const L_CODES$1 = LITERALS$1 + 1 + LENGTH_CODES$1;
  /* number of Literal or Length codes, including the END_BLOCK code */

  const D_CODES$1 = 30;
  /* number of distance codes */

  const BL_CODES$1 = 19;
  /* number of codes used to transfer the bit lengths */

  const HEAP_SIZE$1 = 2 * L_CODES$1 + 1;
  /* maximum heap size */

  const MAX_BITS$1 = 15;
  /* All codes must not exceed MAX_BITS bits */

  const Buf_size = 16;
  /* size of bit buffer in bi_buf */

  /* ===========================================================================
   * Constants
   */

  const MAX_BL_BITS = 7;
  /* Bit length codes must not exceed MAX_BL_BITS bits */

  const END_BLOCK = 256;
  /* end of block literal code */

  const REP_3_6 = 16;
  /* repeat previous bit length 3-6 times (2 bits of repeat count) */

  const REPZ_3_10 = 17;
  /* repeat a zero length 3-10 times  (3 bits of repeat count) */

  const REPZ_11_138 = 18;
  /* repeat a zero length 11-138 times  (7 bits of repeat count) */

  /* eslint-disable comma-spacing,array-bracket-spacing */

  const extra_lbits =
  /* extra bits for each length code */
  new Uint8Array([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]);
  const extra_dbits =
  /* extra bits for each distance code */
  new Uint8Array([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]);
  const extra_blbits =
  /* extra bits for each bit length code */
  new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 7]);
  const bl_order = new Uint8Array([16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]);
  /* eslint-enable comma-spacing,array-bracket-spacing */

  /* The lengths of the bit length codes are sent in order of decreasing
   * probability, to avoid transmitting the lengths for unused bit length codes.
   */

  /* ===========================================================================
   * Local data. These are initialized only once.
   */
  // We pre-fill arrays with 0 to avoid uninitialized gaps

  const DIST_CODE_LEN = 512;
  /* see definition of array dist_code below */
  // !!!! Use flat array instead of structure, Freq = i*2, Len = i*2+1

  const static_ltree = new Array((L_CODES$1 + 2) * 2);
  zero$1(static_ltree);
  /* The static literal tree. Since the bit lengths are imposed, there is no
   * need for the L_CODES extra codes used during heap construction. However
   * The codes 286 and 287 are needed to build a canonical tree (see _tr_init
   * below).
   */

  const static_dtree = new Array(D_CODES$1 * 2);
  zero$1(static_dtree);
  /* The static distance tree. (Actually a trivial tree since all codes use
   * 5 bits.)
   */

  const _dist_code = new Array(DIST_CODE_LEN);

  zero$1(_dist_code);
  /* Distance codes. The first 256 values correspond to the distances
   * 3 .. 258, the last 256 values correspond to the top 8 bits of
   * the 15 bit distances.
   */

  const _length_code = new Array(MAX_MATCH$1 - MIN_MATCH$1 + 1);

  zero$1(_length_code);
  /* length code for each normalized match length (0 == MIN_MATCH) */

  const base_length = new Array(LENGTH_CODES$1);
  zero$1(base_length);
  /* First normalized length for each code (0 = MIN_MATCH) */

  const base_dist = new Array(D_CODES$1);
  zero$1(base_dist);
  /* First normalized distance for each code (0 = distance of 1) */

  function StaticTreeDesc(static_tree, extra_bits, extra_base, elems, max_length) {
    this.static_tree = static_tree;
    /* static tree or NULL */

    this.extra_bits = extra_bits;
    /* extra bits for each code or NULL */

    this.extra_base = extra_base;
    /* base index for extra_bits */

    this.elems = elems;
    /* max number of elements in the tree */

    this.max_length = max_length;
    /* max bit length for the codes */
    // show if `static_tree` has data or dummy - needed for monomorphic objects

    this.has_stree = static_tree && static_tree.length;
  }

  let static_l_desc;
  let static_d_desc;
  let static_bl_desc;

  function TreeDesc(dyn_tree, stat_desc) {
    this.dyn_tree = dyn_tree;
    /* the dynamic tree */

    this.max_code = 0;
    /* largest code with non zero frequency */

    this.stat_desc = stat_desc;
    /* the corresponding static tree */
  }

  const d_code = dist => {
    return dist < 256 ? _dist_code[dist] : _dist_code[256 + (dist >>> 7)];
  };
  /* ===========================================================================
   * Output a short LSB first on the stream.
   * IN assertion: there is enough room in pendingBuf.
   */


  const put_short = (s, w) => {
    //    put_byte(s, (uch)((w) & 0xff));
    //    put_byte(s, (uch)((ush)(w) >> 8));
    s.pending_buf[s.pending++] = w & 0xff;
    s.pending_buf[s.pending++] = w >>> 8 & 0xff;
  };
  /* ===========================================================================
   * Send a value on a given number of bits.
   * IN assertion: length <= 16 and value fits in length bits.
   */


  const send_bits = (s, value, length) => {
    if (s.bi_valid > Buf_size - length) {
      s.bi_buf |= value << s.bi_valid & 0xffff;
      put_short(s, s.bi_buf);
      s.bi_buf = value >> Buf_size - s.bi_valid;
      s.bi_valid += length - Buf_size;
    } else {
      s.bi_buf |= value << s.bi_valid & 0xffff;
      s.bi_valid += length;
    }
  };

  const send_code = (s, c, tree) => {
    send_bits(s, tree[c * 2]
    /*.Code*/
    , tree[c * 2 + 1]
    /*.Len*/
    );
  };
  /* ===========================================================================
   * Reverse the first len bits of a code, using straightforward code (a faster
   * method would use a table)
   * IN assertion: 1 <= len <= 15
   */


  const bi_reverse = (code, len) => {
    let res = 0;

    do {
      res |= code & 1;
      code >>>= 1;
      res <<= 1;
    } while (--len > 0);

    return res >>> 1;
  };
  /* ===========================================================================
   * Flush the bit buffer, keeping at most 7 bits in it.
   */


  const bi_flush = s => {
    if (s.bi_valid === 16) {
      put_short(s, s.bi_buf);
      s.bi_buf = 0;
      s.bi_valid = 0;
    } else if (s.bi_valid >= 8) {
      s.pending_buf[s.pending++] = s.bi_buf & 0xff;
      s.bi_buf >>= 8;
      s.bi_valid -= 8;
    }
  };
  /* ===========================================================================
   * Compute the optimal bit lengths for a tree and update the total bit length
   * for the current block.
   * IN assertion: the fields freq and dad are set, heap[heap_max] and
   *    above are the tree nodes sorted by increasing frequency.
   * OUT assertions: the field len is set to the optimal bit length, the
   *     array bl_count contains the frequencies for each bit length.
   *     The length opt_len is updated; static_len is also updated if stree is
   *     not null.
   */


  const gen_bitlen = (s, desc) => //    deflate_state *s;
  //    tree_desc *desc;    /* the tree descriptor */
  {
    const tree = desc.dyn_tree;
    const max_code = desc.max_code;
    const stree = desc.stat_desc.static_tree;
    const has_stree = desc.stat_desc.has_stree;
    const extra = desc.stat_desc.extra_bits;
    const base = desc.stat_desc.extra_base;
    const max_length = desc.stat_desc.max_length;
    let h;
    /* heap index */

    let n, m;
    /* iterate over the tree elements */

    let bits;
    /* bit length */

    let xbits;
    /* extra bits */

    let f;
    /* frequency */

    let overflow = 0;
    /* number of elements with bit length too large */

    for (bits = 0; bits <= MAX_BITS$1; bits++) {
      s.bl_count[bits] = 0;
    }
    /* In a first pass, compute the optimal bit lengths (which may
     * overflow in the case of the bit length tree).
     */


    tree[s.heap[s.heap_max] * 2 + 1]
    /*.Len*/
    = 0;
    /* root of the heap */

    for (h = s.heap_max + 1; h < HEAP_SIZE$1; h++) {
      n = s.heap[h];
      bits = tree[tree[n * 2 + 1]
      /*.Dad*/
      * 2 + 1]
      /*.Len*/
      + 1;

      if (bits > max_length) {
        bits = max_length;
        overflow++;
      }

      tree[n * 2 + 1]
      /*.Len*/
      = bits;
      /* We overwrite tree[n].Dad which is no longer needed */

      if (n > max_code) {
        continue;
      }
      /* not a leaf node */


      s.bl_count[bits]++;
      xbits = 0;

      if (n >= base) {
        xbits = extra[n - base];
      }

      f = tree[n * 2]
      /*.Freq*/
      ;
      s.opt_len += f * (bits + xbits);

      if (has_stree) {
        s.static_len += f * (stree[n * 2 + 1]
        /*.Len*/
        + xbits);
      }
    }

    if (overflow === 0) {
      return;
    } // Trace((stderr,"\nbit length overflow\n"));

    /* This happens for example on obj2 and pic of the Calgary corpus */

    /* Find the first bit length which could increase: */


    do {
      bits = max_length - 1;

      while (s.bl_count[bits] === 0) {
        bits--;
      }

      s.bl_count[bits]--;
      /* move one leaf down the tree */

      s.bl_count[bits + 1] += 2;
      /* move one overflow item as its brother */

      s.bl_count[max_length]--;
      /* The brother of the overflow item also moves one step up,
       * but this does not affect bl_count[max_length]
       */

      overflow -= 2;
    } while (overflow > 0);
    /* Now recompute all bit lengths, scanning in increasing frequency.
     * h is still equal to HEAP_SIZE. (It is simpler to reconstruct all
     * lengths instead of fixing only the wrong ones. This idea is taken
     * from 'ar' written by Haruhiko Okumura.)
     */


    for (bits = max_length; bits !== 0; bits--) {
      n = s.bl_count[bits];

      while (n !== 0) {
        m = s.heap[--h];

        if (m > max_code) {
          continue;
        }

        if (tree[m * 2 + 1]
        /*.Len*/
        !== bits) {
          // Trace((stderr,"code %d bits %d->%d\n", m, tree[m].Len, bits));
          s.opt_len += (bits - tree[m * 2 + 1]
          /*.Len*/
          ) * tree[m * 2]
          /*.Freq*/
          ;
          tree[m * 2 + 1]
          /*.Len*/
          = bits;
        }

        n--;
      }
    }
  };
  /* ===========================================================================
   * Generate the codes for a given tree and bit counts (which need not be
   * optimal).
   * IN assertion: the array bl_count contains the bit length statistics for
   * the given tree and the field len is set for all tree elements.
   * OUT assertion: the field code is set for all tree elements of non
   *     zero code length.
   */


  const gen_codes = (tree, max_code, bl_count) => //    ct_data *tree;             /* the tree to decorate */
  //    int max_code;              /* largest code with non zero frequency */
  //    ushf *bl_count;            /* number of codes at each bit length */
  {
    const next_code = new Array(MAX_BITS$1 + 1);
    /* next code value for each bit length */

    let code = 0;
    /* running code value */

    let bits;
    /* bit index */

    let n;
    /* code index */

    /* The distribution counts are first used to generate the code values
     * without bit reversal.
     */

    for (bits = 1; bits <= MAX_BITS$1; bits++) {
      next_code[bits] = code = code + bl_count[bits - 1] << 1;
    }
    /* Check that the bit counts in bl_count are consistent. The last code
     * must be all ones.
     */
    //Assert (code + bl_count[MAX_BITS]-1 == (1<<MAX_BITS)-1,
    //        "inconsistent bit counts");
    //Tracev((stderr,"\ngen_codes: max_code %d ", max_code));


    for (n = 0; n <= max_code; n++) {
      let len = tree[n * 2 + 1]
      /*.Len*/
      ;

      if (len === 0) {
        continue;
      }
      /* Now reverse the bits */


      tree[n * 2]
      /*.Code*/
      = bi_reverse(next_code[len]++, len); //Tracecv(tree != static_ltree, (stderr,"\nn %3d %c l %2d c %4x (%x) ",
      //     n, (isgraph(n) ? n : ' '), len, tree[n].Code, next_code[len]-1));
    }
  };
  /* ===========================================================================
   * Initialize the various 'constant' tables.
   */


  const tr_static_init = () => {
    let n;
    /* iterates over tree elements */

    let bits;
    /* bit counter */

    let length;
    /* length value */

    let code;
    /* code value */

    let dist;
    /* distance index */

    const bl_count = new Array(MAX_BITS$1 + 1);
    /* number of codes at each bit length for an optimal tree */
    // do check in _tr_init()
    //if (static_init_done) return;

    /* For some embedded targets, global variables are not initialized: */

    /*#ifdef NO_INIT_GLOBAL_POINTERS
      static_l_desc.static_tree = static_ltree;
      static_l_desc.extra_bits = extra_lbits;
      static_d_desc.static_tree = static_dtree;
      static_d_desc.extra_bits = extra_dbits;
      static_bl_desc.extra_bits = extra_blbits;
    #endif*/

    /* Initialize the mapping length (0..255) -> length code (0..28) */

    length = 0;

    for (code = 0; code < LENGTH_CODES$1 - 1; code++) {
      base_length[code] = length;

      for (n = 0; n < 1 << extra_lbits[code]; n++) {
        _length_code[length++] = code;
      }
    } //Assert (length == 256, "tr_static_init: length != 256");

    /* Note that the length 255 (match length 258) can be represented
     * in two different ways: code 284 + 5 bits or code 285, so we
     * overwrite length_code[255] to use the best encoding:
     */


    _length_code[length - 1] = code;
    /* Initialize the mapping dist (0..32K) -> dist code (0..29) */

    dist = 0;

    for (code = 0; code < 16; code++) {
      base_dist[code] = dist;

      for (n = 0; n < 1 << extra_dbits[code]; n++) {
        _dist_code[dist++] = code;
      }
    } //Assert (dist == 256, "tr_static_init: dist != 256");


    dist >>= 7;
    /* from now on, all distances are divided by 128 */

    for (; code < D_CODES$1; code++) {
      base_dist[code] = dist << 7;

      for (n = 0; n < 1 << extra_dbits[code] - 7; n++) {
        _dist_code[256 + dist++] = code;
      }
    } //Assert (dist == 256, "tr_static_init: 256+dist != 512");

    /* Construct the codes of the static literal tree */


    for (bits = 0; bits <= MAX_BITS$1; bits++) {
      bl_count[bits] = 0;
    }

    n = 0;

    while (n <= 143) {
      static_ltree[n * 2 + 1]
      /*.Len*/
      = 8;
      n++;
      bl_count[8]++;
    }

    while (n <= 255) {
      static_ltree[n * 2 + 1]
      /*.Len*/
      = 9;
      n++;
      bl_count[9]++;
    }

    while (n <= 279) {
      static_ltree[n * 2 + 1]
      /*.Len*/
      = 7;
      n++;
      bl_count[7]++;
    }

    while (n <= 287) {
      static_ltree[n * 2 + 1]
      /*.Len*/
      = 8;
      n++;
      bl_count[8]++;
    }
    /* Codes 286 and 287 do not exist, but we must include them in the
     * tree construction to get a canonical Huffman tree (longest code
     * all ones)
     */


    gen_codes(static_ltree, L_CODES$1 + 1, bl_count);
    /* The static distance tree is trivial: */

    for (n = 0; n < D_CODES$1; n++) {
      static_dtree[n * 2 + 1]
      /*.Len*/
      = 5;
      static_dtree[n * 2]
      /*.Code*/
      = bi_reverse(n, 5);
    } // Now data ready and we can init static trees


    static_l_desc = new StaticTreeDesc(static_ltree, extra_lbits, LITERALS$1 + 1, L_CODES$1, MAX_BITS$1);
    static_d_desc = new StaticTreeDesc(static_dtree, extra_dbits, 0, D_CODES$1, MAX_BITS$1);
    static_bl_desc = new StaticTreeDesc(new Array(0), extra_blbits, 0, BL_CODES$1, MAX_BL_BITS); //static_init_done = true;
  };
  /* ===========================================================================
   * Initialize a new block.
   */


  const init_block = s => {
    let n;
    /* iterates over tree elements */

    /* Initialize the trees. */

    for (n = 0; n < L_CODES$1; n++) {
      s.dyn_ltree[n * 2]
      /*.Freq*/
      = 0;
    }

    for (n = 0; n < D_CODES$1; n++) {
      s.dyn_dtree[n * 2]
      /*.Freq*/
      = 0;
    }

    for (n = 0; n < BL_CODES$1; n++) {
      s.bl_tree[n * 2]
      /*.Freq*/
      = 0;
    }

    s.dyn_ltree[END_BLOCK * 2]
    /*.Freq*/
    = 1;
    s.opt_len = s.static_len = 0;
    s.last_lit = s.matches = 0;
  };
  /* ===========================================================================
   * Flush the bit buffer and align the output on a byte boundary
   */


  const bi_windup = s => {
    if (s.bi_valid > 8) {
      put_short(s, s.bi_buf);
    } else if (s.bi_valid > 0) {
      //put_byte(s, (Byte)s->bi_buf);
      s.pending_buf[s.pending++] = s.bi_buf;
    }

    s.bi_buf = 0;
    s.bi_valid = 0;
  };
  /* ===========================================================================
   * Copy a stored block, storing first the length and its
   * one's complement if requested.
   */


  const copy_block = (s, buf, len, header) => //DeflateState *s;
  //charf    *buf;    /* the input data */
  //unsigned len;     /* its length */
  //int      header;  /* true if block header must be written */
  {
    bi_windup(s);
    /* align on byte boundary */

    if (header) {
      put_short(s, len);
      put_short(s, ~len);
    } //  while (len--) {
    //    put_byte(s, *buf++);
    //  }


    s.pending_buf.set(s.window.subarray(buf, buf + len), s.pending);
    s.pending += len;
  };
  /* ===========================================================================
   * Compares to subtrees, using the tree depth as tie breaker when
   * the subtrees have equal frequency. This minimizes the worst case length.
   */


  const smaller = (tree, n, m, depth) => {
    const _n2 = n * 2;

    const _m2 = m * 2;

    return tree[_n2]
    /*.Freq*/
    < tree[_m2]
    /*.Freq*/
    || tree[_n2]
    /*.Freq*/
    === tree[_m2]
    /*.Freq*/
    && depth[n] <= depth[m];
  };
  /* ===========================================================================
   * Restore the heap property by moving down the tree starting at node k,
   * exchanging a node with the smallest of its two sons if necessary, stopping
   * when the heap property is re-established (each father smaller than its
   * two sons).
   */


  const pqdownheap = (s, tree, k) => //    deflate_state *s;
  //    ct_data *tree;  /* the tree to restore */
  //    int k;               /* node to move down */
  {
    const v = s.heap[k];
    let j = k << 1;
    /* left son of k */

    while (j <= s.heap_len) {
      /* Set j to the smallest of the two sons: */
      if (j < s.heap_len && smaller(tree, s.heap[j + 1], s.heap[j], s.depth)) {
        j++;
      }
      /* Exit if v is smaller than both sons */


      if (smaller(tree, v, s.heap[j], s.depth)) {
        break;
      }
      /* Exchange v with the smallest son */


      s.heap[k] = s.heap[j];
      k = j;
      /* And continue down the tree, setting j to the left son of k */

      j <<= 1;
    }

    s.heap[k] = v;
  }; // inlined manually
  // const SMALLEST = 1;

  /* ===========================================================================
   * Send the block data compressed using the given Huffman trees
   */


  const compress_block = (s, ltree, dtree) => //    deflate_state *s;
  //    const ct_data *ltree; /* literal tree */
  //    const ct_data *dtree; /* distance tree */
  {
    let dist;
    /* distance of matched string */

    let lc;
    /* match length or unmatched char (if dist == 0) */

    let lx = 0;
    /* running index in l_buf */

    let code;
    /* the code to send */

    let extra;
    /* number of extra bits to send */

    if (s.last_lit !== 0) {
      do {
        dist = s.pending_buf[s.d_buf + lx * 2] << 8 | s.pending_buf[s.d_buf + lx * 2 + 1];
        lc = s.pending_buf[s.l_buf + lx];
        lx++;

        if (dist === 0) {
          send_code(s, lc, ltree);
          /* send a literal byte */
          //Tracecv(isgraph(lc), (stderr," '%c' ", lc));
        } else {
          /* Here, lc is the match length - MIN_MATCH */
          code = _length_code[lc];
          send_code(s, code + LITERALS$1 + 1, ltree);
          /* send the length code */

          extra = extra_lbits[code];

          if (extra !== 0) {
            lc -= base_length[code];
            send_bits(s, lc, extra);
            /* send the extra length bits */
          }

          dist--;
          /* dist is now the match distance - 1 */

          code = d_code(dist); //Assert (code < D_CODES, "bad d_code");

          send_code(s, code, dtree);
          /* send the distance code */

          extra = extra_dbits[code];

          if (extra !== 0) {
            dist -= base_dist[code];
            send_bits(s, dist, extra);
            /* send the extra distance bits */
          }
        }
        /* literal or match pair ? */

        /* Check that the overlay between pending_buf and d_buf+l_buf is ok: */
        //Assert((uInt)(s->pending) < s->lit_bufsize + 2*lx,
        //       "pendingBuf overflow");

      } while (lx < s.last_lit);
    }

    send_code(s, END_BLOCK, ltree);
  };
  /* ===========================================================================
   * Construct one Huffman tree and assigns the code bit strings and lengths.
   * Update the total bit length for the current block.
   * IN assertion: the field freq is set for all tree elements.
   * OUT assertions: the fields len and code are set to the optimal bit length
   *     and corresponding code. The length opt_len is updated; static_len is
   *     also updated if stree is not null. The field max_code is set.
   */


  const build_tree = (s, desc) => //    deflate_state *s;
  //    tree_desc *desc; /* the tree descriptor */
  {
    const tree = desc.dyn_tree;
    const stree = desc.stat_desc.static_tree;
    const has_stree = desc.stat_desc.has_stree;
    const elems = desc.stat_desc.elems;
    let n, m;
    /* iterate over heap elements */

    let max_code = -1;
    /* largest code with non zero frequency */

    let node;
    /* new node being created */

    /* Construct the initial heap, with least frequent element in
     * heap[SMALLEST]. The sons of heap[n] are heap[2*n] and heap[2*n+1].
     * heap[0] is not used.
     */

    s.heap_len = 0;
    s.heap_max = HEAP_SIZE$1;

    for (n = 0; n < elems; n++) {
      if (tree[n * 2]
      /*.Freq*/
      !== 0) {
        s.heap[++s.heap_len] = max_code = n;
        s.depth[n] = 0;
      } else {
        tree[n * 2 + 1]
        /*.Len*/
        = 0;
      }
    }
    /* The pkzip format requires that at least one distance code exists,
     * and that at least one bit should be sent even if there is only one
     * possible code. So to avoid special checks later on we force at least
     * two codes of non zero frequency.
     */


    while (s.heap_len < 2) {
      node = s.heap[++s.heap_len] = max_code < 2 ? ++max_code : 0;
      tree[node * 2]
      /*.Freq*/
      = 1;
      s.depth[node] = 0;
      s.opt_len--;

      if (has_stree) {
        s.static_len -= stree[node * 2 + 1]
        /*.Len*/
        ;
      }
      /* node is 0 or 1 so it does not have extra bits */

    }

    desc.max_code = max_code;
    /* The elements heap[heap_len/2+1 .. heap_len] are leaves of the tree,
     * establish sub-heaps of increasing lengths:
     */

    for (n = s.heap_len >> 1
    /*int /2*/
    ; n >= 1; n--) {
      pqdownheap(s, tree, n);
    }
    /* Construct the Huffman tree by repeatedly combining the least two
     * frequent nodes.
     */


    node = elems;
    /* next internal node of the tree */

    do {
      //pqremove(s, tree, n);  /* n = node of least frequency */

      /*** pqremove ***/
      n = s.heap[1
      /*SMALLEST*/
      ];
      s.heap[1
      /*SMALLEST*/
      ] = s.heap[s.heap_len--];
      pqdownheap(s, tree, 1
      /*SMALLEST*/
      );
      /***/

      m = s.heap[1
      /*SMALLEST*/
      ];
      /* m = node of next least frequency */

      s.heap[--s.heap_max] = n;
      /* keep the nodes sorted by frequency */

      s.heap[--s.heap_max] = m;
      /* Create a new node father of n and m */

      tree[node * 2]
      /*.Freq*/
      = tree[n * 2]
      /*.Freq*/
      + tree[m * 2]
      /*.Freq*/
      ;
      s.depth[node] = (s.depth[n] >= s.depth[m] ? s.depth[n] : s.depth[m]) + 1;
      tree[n * 2 + 1]
      /*.Dad*/
      = tree[m * 2 + 1]
      /*.Dad*/
      = node;
      /* and insert the new node in the heap */

      s.heap[1
      /*SMALLEST*/
      ] = node++;
      pqdownheap(s, tree, 1
      /*SMALLEST*/
      );
    } while (s.heap_len >= 2);

    s.heap[--s.heap_max] = s.heap[1
    /*SMALLEST*/
    ];
    /* At this point, the fields freq and dad are set. We can now
     * generate the bit lengths.
     */

    gen_bitlen(s, desc);
    /* The field len is now set, we can generate the bit codes */

    gen_codes(tree, max_code, s.bl_count);
  };
  /* ===========================================================================
   * Scan a literal or distance tree to determine the frequencies of the codes
   * in the bit length tree.
   */


  const scan_tree = (s, tree, max_code) => //    deflate_state *s;
  //    ct_data *tree;   /* the tree to be scanned */
  //    int max_code;    /* and its largest code of non zero frequency */
  {
    let n;
    /* iterates over all tree elements */

    let prevlen = -1;
    /* last emitted length */

    let curlen;
    /* length of current code */

    let nextlen = tree[0 * 2 + 1]
    /*.Len*/
    ;
    /* length of next code */

    let count = 0;
    /* repeat count of the current code */

    let max_count = 7;
    /* max repeat count */

    let min_count = 4;
    /* min repeat count */

    if (nextlen === 0) {
      max_count = 138;
      min_count = 3;
    }

    tree[(max_code + 1) * 2 + 1]
    /*.Len*/
    = 0xffff;
    /* guard */

    for (n = 0; n <= max_code; n++) {
      curlen = nextlen;
      nextlen = tree[(n + 1) * 2 + 1]
      /*.Len*/
      ;

      if (++count < max_count && curlen === nextlen) {
        continue;
      } else if (count < min_count) {
        s.bl_tree[curlen * 2]
        /*.Freq*/
        += count;
      } else if (curlen !== 0) {
        if (curlen !== prevlen) {
          s.bl_tree[curlen * 2] /*.Freq*/++;
        }

        s.bl_tree[REP_3_6 * 2] /*.Freq*/++;
      } else if (count <= 10) {
        s.bl_tree[REPZ_3_10 * 2] /*.Freq*/++;
      } else {
        s.bl_tree[REPZ_11_138 * 2] /*.Freq*/++;
      }

      count = 0;
      prevlen = curlen;

      if (nextlen === 0) {
        max_count = 138;
        min_count = 3;
      } else if (curlen === nextlen) {
        max_count = 6;
        min_count = 3;
      } else {
        max_count = 7;
        min_count = 4;
      }
    }
  };
  /* ===========================================================================
   * Send a literal or distance tree in compressed form, using the codes in
   * bl_tree.
   */


  const send_tree = (s, tree, max_code) => //    deflate_state *s;
  //    ct_data *tree; /* the tree to be scanned */
  //    int max_code;       /* and its largest code of non zero frequency */
  {
    let n;
    /* iterates over all tree elements */

    let prevlen = -1;
    /* last emitted length */

    let curlen;
    /* length of current code */

    let nextlen = tree[0 * 2 + 1]
    /*.Len*/
    ;
    /* length of next code */

    let count = 0;
    /* repeat count of the current code */

    let max_count = 7;
    /* max repeat count */

    let min_count = 4;
    /* min repeat count */

    /* tree[max_code+1].Len = -1; */

    /* guard already set */

    if (nextlen === 0) {
      max_count = 138;
      min_count = 3;
    }

    for (n = 0; n <= max_code; n++) {
      curlen = nextlen;
      nextlen = tree[(n + 1) * 2 + 1]
      /*.Len*/
      ;

      if (++count < max_count && curlen === nextlen) {
        continue;
      } else if (count < min_count) {
        do {
          send_code(s, curlen, s.bl_tree);
        } while (--count !== 0);
      } else if (curlen !== 0) {
        if (curlen !== prevlen) {
          send_code(s, curlen, s.bl_tree);
          count--;
        } //Assert(count >= 3 && count <= 6, " 3_6?");


        send_code(s, REP_3_6, s.bl_tree);
        send_bits(s, count - 3, 2);
      } else if (count <= 10) {
        send_code(s, REPZ_3_10, s.bl_tree);
        send_bits(s, count - 3, 3);
      } else {
        send_code(s, REPZ_11_138, s.bl_tree);
        send_bits(s, count - 11, 7);
      }

      count = 0;
      prevlen = curlen;

      if (nextlen === 0) {
        max_count = 138;
        min_count = 3;
      } else if (curlen === nextlen) {
        max_count = 6;
        min_count = 3;
      } else {
        max_count = 7;
        min_count = 4;
      }
    }
  };
  /* ===========================================================================
   * Construct the Huffman tree for the bit lengths and return the index in
   * bl_order of the last bit length code to send.
   */


  const build_bl_tree = s => {
    let max_blindex;
    /* index of last bit length code of non zero freq */

    /* Determine the bit length frequencies for literal and distance trees */

    scan_tree(s, s.dyn_ltree, s.l_desc.max_code);
    scan_tree(s, s.dyn_dtree, s.d_desc.max_code);
    /* Build the bit length tree: */

    build_tree(s, s.bl_desc);
    /* opt_len now includes the length of the tree representations, except
     * the lengths of the bit lengths codes and the 5+5+4 bits for the counts.
     */

    /* Determine the number of bit length codes to send. The pkzip format
     * requires that at least 4 bit length codes be sent. (appnote.txt says
     * 3 but the actual value used is 4.)
     */

    for (max_blindex = BL_CODES$1 - 1; max_blindex >= 3; max_blindex--) {
      if (s.bl_tree[bl_order[max_blindex] * 2 + 1]
      /*.Len*/
      !== 0) {
        break;
      }
    }
    /* Update opt_len to include the bit length tree and counts */


    s.opt_len += 3 * (max_blindex + 1) + 5 + 5 + 4; //Tracev((stderr, "\ndyn trees: dyn %ld, stat %ld",
    //        s->opt_len, s->static_len));

    return max_blindex;
  };
  /* ===========================================================================
   * Send the header for a block using dynamic Huffman trees: the counts, the
   * lengths of the bit length codes, the literal tree and the distance tree.
   * IN assertion: lcodes >= 257, dcodes >= 1, blcodes >= 4.
   */


  const send_all_trees = (s, lcodes, dcodes, blcodes) => //    deflate_state *s;
  //    int lcodes, dcodes, blcodes; /* number of codes for each tree */
  {
    let rank;
    /* index in bl_order */
    //Assert (lcodes >= 257 && dcodes >= 1 && blcodes >= 4, "not enough codes");
    //Assert (lcodes <= L_CODES && dcodes <= D_CODES && blcodes <= BL_CODES,
    //        "too many codes");
    //Tracev((stderr, "\nbl counts: "));

    send_bits(s, lcodes - 257, 5);
    /* not +255 as stated in appnote.txt */

    send_bits(s, dcodes - 1, 5);
    send_bits(s, blcodes - 4, 4);
    /* not -3 as stated in appnote.txt */

    for (rank = 0; rank < blcodes; rank++) {
      //Tracev((stderr, "\nbl code %2d ", bl_order[rank]));
      send_bits(s, s.bl_tree[bl_order[rank] * 2 + 1]
      /*.Len*/
      , 3);
    } //Tracev((stderr, "\nbl tree: sent %ld", s->bits_sent));


    send_tree(s, s.dyn_ltree, lcodes - 1);
    /* literal tree */
    //Tracev((stderr, "\nlit tree: sent %ld", s->bits_sent));

    send_tree(s, s.dyn_dtree, dcodes - 1);
    /* distance tree */
    //Tracev((stderr, "\ndist tree: sent %ld", s->bits_sent));
  };
  /* ===========================================================================
   * Check if the data type is TEXT or BINARY, using the following algorithm:
   * - TEXT if the two conditions below are satisfied:
   *    a) There are no non-portable control characters belonging to the
   *       "black list" (0..6, 14..25, 28..31).
   *    b) There is at least one printable character belonging to the
   *       "white list" (9 {TAB}, 10 {LF}, 13 {CR}, 32..255).
   * - BINARY otherwise.
   * - The following partially-portable control characters form a
   *   "gray list" that is ignored in this detection algorithm:
   *   (7 {BEL}, 8 {BS}, 11 {VT}, 12 {FF}, 26 {SUB}, 27 {ESC}).
   * IN assertion: the fields Freq of dyn_ltree are set.
   */


  const detect_data_type = s => {
    /* black_mask is the bit mask of black-listed bytes
     * set bits 0..6, 14..25, and 28..31
     * 0xf3ffc07f = binary 11110011111111111100000001111111
     */
    let black_mask = 0xf3ffc07f;
    let n;
    /* Check for non-textual ("black-listed") bytes. */

    for (n = 0; n <= 31; n++, black_mask >>>= 1) {
      if (black_mask & 1 && s.dyn_ltree[n * 2]
      /*.Freq*/
      !== 0) {
        return Z_BINARY;
      }
    }
    /* Check for textual ("white-listed") bytes. */


    if (s.dyn_ltree[9 * 2]
    /*.Freq*/
    !== 0 || s.dyn_ltree[10 * 2]
    /*.Freq*/
    !== 0 || s.dyn_ltree[13 * 2]
    /*.Freq*/
    !== 0) {
      return Z_TEXT;
    }

    for (n = 32; n < LITERALS$1; n++) {
      if (s.dyn_ltree[n * 2]
      /*.Freq*/
      !== 0) {
        return Z_TEXT;
      }
    }
    /* There are no "black-listed" or "white-listed" bytes:
     * this stream either is empty or has tolerated ("gray-listed") bytes only.
     */


    return Z_BINARY;
  };

  let static_init_done = false;
  /* ===========================================================================
   * Initialize the tree data structures for a new zlib stream.
   */

  const _tr_init$1 = s => {
    if (!static_init_done) {
      tr_static_init();
      static_init_done = true;
    }

    s.l_desc = new TreeDesc(s.dyn_ltree, static_l_desc);
    s.d_desc = new TreeDesc(s.dyn_dtree, static_d_desc);
    s.bl_desc = new TreeDesc(s.bl_tree, static_bl_desc);
    s.bi_buf = 0;
    s.bi_valid = 0;
    /* Initialize the first block of the first file: */

    init_block(s);
  };
  /* ===========================================================================
   * Send a stored block
   */


  const _tr_stored_block$1 = (s, buf, stored_len, last) => //DeflateState *s;
  //charf *buf;       /* input block */
  //ulg stored_len;   /* length of input block */
  //int last;         /* one if this is the last block for a file */
  {
    send_bits(s, (STORED_BLOCK << 1) + (last ? 1 : 0), 3);
    /* send block type */

    copy_block(s, buf, stored_len, true);
    /* with header */
  };
  /* ===========================================================================
   * Send one empty static block to give enough lookahead for inflate.
   * This takes 10 bits, of which 7 may remain in the bit buffer.
   */


  const _tr_align$1 = s => {
    send_bits(s, STATIC_TREES << 1, 3);
    send_code(s, END_BLOCK, static_ltree);
    bi_flush(s);
  };
  /* ===========================================================================
   * Determine the best encoding for the current block: dynamic trees, static
   * trees or store, and output the encoded block to the zip file.
   */


  const _tr_flush_block$1 = (s, buf, stored_len, last) => //DeflateState *s;
  //charf *buf;       /* input block, or NULL if too old */
  //ulg stored_len;   /* length of input block */
  //int last;         /* one if this is the last block for a file */
  {
    let opt_lenb, static_lenb;
    /* opt_len and static_len in bytes */

    let max_blindex = 0;
    /* index of last bit length code of non zero freq */

    /* Build the Huffman trees unless a stored block is forced */

    if (s.level > 0) {
      /* Check if the file is binary or text */
      if (s.strm.data_type === Z_UNKNOWN$1) {
        s.strm.data_type = detect_data_type(s);
      }
      /* Construct the literal and distance trees */


      build_tree(s, s.l_desc); // Tracev((stderr, "\nlit data: dyn %ld, stat %ld", s->opt_len,
      //        s->static_len));

      build_tree(s, s.d_desc); // Tracev((stderr, "\ndist data: dyn %ld, stat %ld", s->opt_len,
      //        s->static_len));

      /* At this point, opt_len and static_len are the total bit lengths of
       * the compressed block data, excluding the tree representations.
       */

      /* Build the bit length tree for the above two trees, and get the index
       * in bl_order of the last bit length code to send.
       */

      max_blindex = build_bl_tree(s);
      /* Determine the best encoding. Compute the block lengths in bytes. */

      opt_lenb = s.opt_len + 3 + 7 >>> 3;
      static_lenb = s.static_len + 3 + 7 >>> 3; // Tracev((stderr, "\nopt %lu(%lu) stat %lu(%lu) stored %lu lit %u ",
      //        opt_lenb, s->opt_len, static_lenb, s->static_len, stored_len,
      //        s->last_lit));

      if (static_lenb <= opt_lenb) {
        opt_lenb = static_lenb;
      }
    } else {
      // Assert(buf != (char*)0, "lost buf");
      opt_lenb = static_lenb = stored_len + 5;
      /* force a stored block */
    }

    if (stored_len + 4 <= opt_lenb && buf !== -1) {
      /* 4: two words for the lengths */

      /* The test buf != NULL is only necessary if LIT_BUFSIZE > WSIZE.
       * Otherwise we can't have processed more than WSIZE input bytes since
       * the last block flush, because compression would have been
       * successful. If LIT_BUFSIZE <= WSIZE, it is never too late to
       * transform a block into a stored block.
       */
      _tr_stored_block$1(s, buf, stored_len, last);
    } else if (s.strategy === Z_FIXED$1 || static_lenb === opt_lenb) {
      send_bits(s, (STATIC_TREES << 1) + (last ? 1 : 0), 3);
      compress_block(s, static_ltree, static_dtree);
    } else {
      send_bits(s, (DYN_TREES << 1) + (last ? 1 : 0), 3);
      send_all_trees(s, s.l_desc.max_code + 1, s.d_desc.max_code + 1, max_blindex + 1);
      compress_block(s, s.dyn_ltree, s.dyn_dtree);
    } // Assert (s->compressed_len == s->bits_sent, "bad compressed size");

    /* The above check is made mod 2^32, for files larger than 512 MB
     * and uLong implemented on 32 bits.
     */


    init_block(s);

    if (last) {
      bi_windup(s);
    } // Tracev((stderr,"\ncomprlen %lu(%lu) ", s->compressed_len>>3,
    //       s->compressed_len-7*last));

  };
  /* ===========================================================================
   * Save the match info and tally the frequency counts. Return true if
   * the current block must be flushed.
   */


  const _tr_tally$1 = (s, dist, lc) => //    deflate_state *s;
  //    unsigned dist;  /* distance of matched string */
  //    unsigned lc;    /* match length-MIN_MATCH or unmatched char (if dist==0) */
  {
    //let out_length, in_length, dcode;
    s.pending_buf[s.d_buf + s.last_lit * 2] = dist >>> 8 & 0xff;
    s.pending_buf[s.d_buf + s.last_lit * 2 + 1] = dist & 0xff;
    s.pending_buf[s.l_buf + s.last_lit] = lc & 0xff;
    s.last_lit++;

    if (dist === 0) {
      /* lc is the unmatched char */
      s.dyn_ltree[lc * 2] /*.Freq*/++;
    } else {
      s.matches++;
      /* Here, lc is the match length - MIN_MATCH */

      dist--;
      /* dist = match distance - 1 */
      //Assert((ush)dist < (ush)MAX_DIST(s) &&
      //       (ush)lc <= (ush)(MAX_MATCH-MIN_MATCH) &&
      //       (ush)d_code(dist) < (ush)D_CODES,  "_tr_tally: bad match");

      s.dyn_ltree[(_length_code[lc] + LITERALS$1 + 1) * 2] /*.Freq*/++;
      s.dyn_dtree[d_code(dist) * 2] /*.Freq*/++;
    } // (!) This block is disabled in zlib defaults,
    // don't enable it for binary compatibility
    //#ifdef TRUNCATE_BLOCK
    //  /* Try to guess if it is profitable to stop the current block here */
    //  if ((s.last_lit & 0x1fff) === 0 && s.level > 2) {
    //    /* Compute an upper bound for the compressed length */
    //    out_length = s.last_lit*8;
    //    in_length = s.strstart - s.block_start;
    //
    //    for (dcode = 0; dcode < D_CODES; dcode++) {
    //      out_length += s.dyn_dtree[dcode*2]/*.Freq*/ * (5 + extra_dbits[dcode]);
    //    }
    //    out_length >>>= 3;
    //    //Tracev((stderr,"\nlast_lit %u, in %ld, out ~%ld(%ld%%) ",
    //    //       s->last_lit, in_length, out_length,
    //    //       100L - out_length*100L/in_length));
    //    if (s.matches < (s.last_lit>>1)/*int /2*/ && out_length < (in_length>>1)/*int /2*/) {
    //      return true;
    //    }
    //  }
    //#endif


    return s.last_lit === s.lit_bufsize - 1;
    /* We avoid equality with lit_bufsize because of wraparound at 64K
     * on 16 bit machines and because stored blocks are restricted to
     * 64K-1 bytes.
     */
  };

  var _tr_init_1 = _tr_init$1;
  var _tr_stored_block_1 = _tr_stored_block$1;
  var _tr_flush_block_1 = _tr_flush_block$1;
  var _tr_tally_1 = _tr_tally$1;
  var _tr_align_1 = _tr_align$1;
  var trees = {
    _tr_init: _tr_init_1,
    _tr_stored_block: _tr_stored_block_1,
    _tr_flush_block: _tr_flush_block_1,
    _tr_tally: _tr_tally_1,
    _tr_align: _tr_align_1
  }; // Note: adler32 takes 12% for level 0 and 2% for level 6.
  // It isn't worth it to make additional optimizations as in original.
  // Small size is preferable.
  // (C) 1995-2013 Jean-loup Gailly and Mark Adler
  // (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin
  //
  // This software is provided 'as-is', without any express or implied
  // warranty. In no event will the authors be held liable for any damages
  // arising from the use of this software.
  //
  // Permission is granted to anyone to use this software for any purpose,
  // including commercial applications, and to alter it and redistribute it
  // freely, subject to the following restrictions:
  //
  // 1. The origin of this software must not be misrepresented; you must not
  //   claim that you wrote the original software. If you use this software
  //   in a product, an acknowledgment in the product documentation would be
  //   appreciated but is not required.
  // 2. Altered source versions must be plainly marked as such, and must not be
  //   misrepresented as being the original software.
  // 3. This notice may not be removed or altered from any source distribution.

  const adler32 = (adler, buf, len, pos) => {
    let s1 = adler & 0xffff | 0,
        s2 = adler >>> 16 & 0xffff | 0,
        n = 0;

    while (len !== 0) {
      // Set limit ~ twice less than 5552, to keep
      // s2 in 31-bits, because we force signed ints.
      // in other case %= will fail.
      n = len > 2000 ? 2000 : len;
      len -= n;

      do {
        s1 = s1 + buf[pos++] | 0;
        s2 = s2 + s1 | 0;
      } while (--n);

      s1 %= 65521;
      s2 %= 65521;
    }

    return s1 | s2 << 16 | 0;
  };

  var adler32_1 = adler32; // Note: we can't get significant speed boost here.
  // So write code to minimize size - no pregenerated tables
  // and array tools dependencies.
  // (C) 1995-2013 Jean-loup Gailly and Mark Adler
  // (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin
  //
  // This software is provided 'as-is', without any express or implied
  // warranty. In no event will the authors be held liable for any damages
  // arising from the use of this software.
  //
  // Permission is granted to anyone to use this software for any purpose,
  // including commercial applications, and to alter it and redistribute it
  // freely, subject to the following restrictions:
  //
  // 1. The origin of this software must not be misrepresented; you must not
  //   claim that you wrote the original software. If you use this software
  //   in a product, an acknowledgment in the product documentation would be
  //   appreciated but is not required.
  // 2. Altered source versions must be plainly marked as such, and must not be
  //   misrepresented as being the original software.
  // 3. This notice may not be removed or altered from any source distribution.
  // Use ordinary array, since untyped makes no boost here

  const makeTable = () => {
    let c,
        table = [];

    for (var n = 0; n < 256; n++) {
      c = n;

      for (var k = 0; k < 8; k++) {
        c = c & 1 ? 0xEDB88320 ^ c >>> 1 : c >>> 1;
      }

      table[n] = c;
    }

    return table;
  }; // Create table on load. Just 255 signed longs. Not a problem.


  const crcTable = new Uint32Array(makeTable());

  const crc32 = (crc, buf, len, pos) => {
    const t = crcTable;
    const end = pos + len;
    crc ^= -1;

    for (let i = pos; i < end; i++) {
      crc = crc >>> 8 ^ t[(crc ^ buf[i]) & 0xFF];
    }

    return crc ^ -1; // >>> 0;
  };

  var crc32_1 = crc32; // (C) 1995-2013 Jean-loup Gailly and Mark Adler
  // (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin
  //
  // This software is provided 'as-is', without any express or implied
  // warranty. In no event will the authors be held liable for any damages
  // arising from the use of this software.
  //
  // Permission is granted to anyone to use this software for any purpose,
  // including commercial applications, and to alter it and redistribute it
  // freely, subject to the following restrictions:
  //
  // 1. The origin of this software must not be misrepresented; you must not
  //   claim that you wrote the original software. If you use this software
  //   in a product, an acknowledgment in the product documentation would be
  //   appreciated but is not required.
  // 2. Altered source versions must be plainly marked as such, and must not be
  //   misrepresented as being the original software.
  // 3. This notice may not be removed or altered from any source distribution.

  var messages = {
    2: 'need dictionary',

    /* Z_NEED_DICT       2  */
    1: 'stream end',

    /* Z_STREAM_END      1  */
    0: '',

    /* Z_OK              0  */
    '-1': 'file error',

    /* Z_ERRNO         (-1) */
    '-2': 'stream error',

    /* Z_STREAM_ERROR  (-2) */
    '-3': 'data error',

    /* Z_DATA_ERROR    (-3) */
    '-4': 'insufficient memory',

    /* Z_MEM_ERROR     (-4) */
    '-5': 'buffer error',

    /* Z_BUF_ERROR     (-5) */
    '-6': 'incompatible version'
    /* Z_VERSION_ERROR (-6) */

  }; // (C) 1995-2013 Jean-loup Gailly and Mark Adler
  // (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin
  //
  // This software is provided 'as-is', without any express or implied
  // warranty. In no event will the authors be held liable for any damages
  // arising from the use of this software.
  //
  // Permission is granted to anyone to use this software for any purpose,
  // including commercial applications, and to alter it and redistribute it
  // freely, subject to the following restrictions:
  //
  // 1. The origin of this software must not be misrepresented; you must not
  //   claim that you wrote the original software. If you use this software
  //   in a product, an acknowledgment in the product documentation would be
  //   appreciated but is not required.
  // 2. Altered source versions must be plainly marked as such, and must not be
  //   misrepresented as being the original software.
  // 3. This notice may not be removed or altered from any source distribution.

  var constants$2 = {
    /* Allowed flush values; see deflate() and inflate() below for details */
    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,

    /* Return codes for the compression/decompression functions. Negative values
    * are errors, positive values are used for special but normal events.
    */
    Z_OK: 0,
    Z_STREAM_END: 1,
    Z_NEED_DICT: 2,
    Z_ERRNO: -1,
    Z_STREAM_ERROR: -2,
    Z_DATA_ERROR: -3,
    Z_MEM_ERROR: -4,
    Z_BUF_ERROR: -5,
    //Z_VERSION_ERROR: -6,

    /* compression levels */
    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,

    /* Possible values of the data_type field (though see inflate()) */
    Z_BINARY: 0,
    Z_TEXT: 1,
    //Z_ASCII:                1, // = Z_TEXT (deprecated)
    Z_UNKNOWN: 2,

    /* The deflate compression method */
    Z_DEFLATED: 8 //Z_NULL:                 null // Use -1 or null inline, depending on var type

  }; // (C) 1995-2013 Jean-loup Gailly and Mark Adler
  // (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin
  //
  // This software is provided 'as-is', without any express or implied
  // warranty. In no event will the authors be held liable for any damages
  // arising from the use of this software.
  //
  // Permission is granted to anyone to use this software for any purpose,
  // including commercial applications, and to alter it and redistribute it
  // freely, subject to the following restrictions:
  //
  // 1. The origin of this software must not be misrepresented; you must not
  //   claim that you wrote the original software. If you use this software
  //   in a product, an acknowledgment in the product documentation would be
  //   appreciated but is not required.
  // 2. Altered source versions must be plainly marked as such, and must not be
  //   misrepresented as being the original software.
  // 3. This notice may not be removed or altered from any source distribution.

  const {
    _tr_init,
    _tr_stored_block,
    _tr_flush_block,
    _tr_tally,
    _tr_align
  } = trees;
  /* Public constants ==========================================================*/

  /* ===========================================================================*/

  const {
    Z_NO_FLUSH: Z_NO_FLUSH$2,
    Z_PARTIAL_FLUSH,
    Z_FULL_FLUSH: Z_FULL_FLUSH$1,
    Z_FINISH: Z_FINISH$3,
    Z_BLOCK: Z_BLOCK$1,
    Z_OK: Z_OK$3,
    Z_STREAM_END: Z_STREAM_END$3,
    Z_STREAM_ERROR: Z_STREAM_ERROR$2,
    Z_DATA_ERROR: Z_DATA_ERROR$2,
    Z_BUF_ERROR: Z_BUF_ERROR$1,
    Z_DEFAULT_COMPRESSION: Z_DEFAULT_COMPRESSION$1,
    Z_FILTERED,
    Z_HUFFMAN_ONLY,
    Z_RLE,
    Z_FIXED,
    Z_DEFAULT_STRATEGY: Z_DEFAULT_STRATEGY$1,
    Z_UNKNOWN,
    Z_DEFLATED: Z_DEFLATED$2
  } = constants$2;
  /*============================================================================*/

  const MAX_MEM_LEVEL = 9;
  /* Maximum value for memLevel in deflateInit2 */

  const MAX_WBITS$1 = 15;
  /* 32K LZ77 window */

  const DEF_MEM_LEVEL = 8;
  const LENGTH_CODES = 29;
  /* number of length codes, not counting the special END_BLOCK code */

  const LITERALS = 256;
  /* number of literal bytes 0..255 */

  const L_CODES = LITERALS + 1 + LENGTH_CODES;
  /* number of Literal or Length codes, including the END_BLOCK code */

  const D_CODES = 30;
  /* number of distance codes */

  const BL_CODES = 19;
  /* number of codes used to transfer the bit lengths */

  const HEAP_SIZE = 2 * L_CODES + 1;
  /* maximum heap size */

  const MAX_BITS = 15;
  /* All codes must not exceed MAX_BITS bits */

  const MIN_MATCH = 3;
  const MAX_MATCH = 258;
  const MIN_LOOKAHEAD = MAX_MATCH + MIN_MATCH + 1;
  const PRESET_DICT = 0x20;
  const INIT_STATE = 42;
  const EXTRA_STATE = 69;
  const NAME_STATE = 73;
  const COMMENT_STATE = 91;
  const HCRC_STATE = 103;
  const BUSY_STATE = 113;
  const FINISH_STATE = 666;
  const BS_NEED_MORE = 1;
  /* block not completed, need more input or more output */

  const BS_BLOCK_DONE = 2;
  /* block flush performed */

  const BS_FINISH_STARTED = 3;
  /* finish started, need only more output at next deflate */

  const BS_FINISH_DONE = 4;
  /* finish done, accept no more input or output */

  const OS_CODE = 0x03; // Unix :) . Don't detect, use this default.

  const err = (strm, errorCode) => {
    strm.msg = messages[errorCode];
    return errorCode;
  };

  const rank = f => {
    return (f << 1) - (f > 4 ? 9 : 0);
  };

  const zero = buf => {
    let len = buf.length;

    while (--len >= 0) {
      buf[len] = 0;
    }
  };
  /* eslint-disable new-cap */


  let HASH_ZLIB = (s, prev, data) => (prev << s.hash_shift ^ data) & s.hash_mask; // This hash causes less collisions, https://github.com/nodeca/pako/issues/135
  // But breaks binary compatibility
  //let HASH_FAST = (s, prev, data) => ((prev << 8) + (prev >> 8) + (data << 4)) & s.hash_mask;


  let HASH = HASH_ZLIB;
  /* =========================================================================
   * Flush as much pending output as possible. All deflate() output goes
   * through this function so some applications may wish to modify it
   * to avoid allocating a large strm->output buffer and copying into it.
   * (See also read_buf()).
   */

  const flush_pending = strm => {
    const s = strm.state; //_tr_flush_bits(s);

    let len = s.pending;

    if (len > strm.avail_out) {
      len = strm.avail_out;
    }

    if (len === 0) {
      return;
    }

    strm.output.set(s.pending_buf.subarray(s.pending_out, s.pending_out + len), strm.next_out);
    strm.next_out += len;
    s.pending_out += len;
    strm.total_out += len;
    strm.avail_out -= len;
    s.pending -= len;

    if (s.pending === 0) {
      s.pending_out = 0;
    }
  };

  const flush_block_only = (s, last) => {
    _tr_flush_block(s, s.block_start >= 0 ? s.block_start : -1, s.strstart - s.block_start, last);

    s.block_start = s.strstart;
    flush_pending(s.strm);
  };

  const put_byte = (s, b) => {
    s.pending_buf[s.pending++] = b;
  };
  /* =========================================================================
   * Put a short in the pending buffer. The 16-bit value is put in MSB order.
   * IN assertion: the stream state is correct and there is enough room in
   * pending_buf.
   */


  const putShortMSB = (s, b) => {
    //  put_byte(s, (Byte)(b >> 8));
    //  put_byte(s, (Byte)(b & 0xff));
    s.pending_buf[s.pending++] = b >>> 8 & 0xff;
    s.pending_buf[s.pending++] = b & 0xff;
  };
  /* ===========================================================================
   * Read a new buffer from the current input stream, update the adler32
   * and total number of bytes read.  All deflate() input goes through
   * this function so some applications may wish to modify it to avoid
   * allocating a large strm->input buffer and copying from it.
   * (See also flush_pending()).
   */


  const read_buf = (strm, buf, start, size) => {
    let len = strm.avail_in;

    if (len > size) {
      len = size;
    }

    if (len === 0) {
      return 0;
    }

    strm.avail_in -= len; // zmemcpy(buf, strm->next_in, len);

    buf.set(strm.input.subarray(strm.next_in, strm.next_in + len), start);

    if (strm.state.wrap === 1) {
      strm.adler = adler32_1(strm.adler, buf, len, start);
    } else if (strm.state.wrap === 2) {
      strm.adler = crc32_1(strm.adler, buf, len, start);
    }

    strm.next_in += len;
    strm.total_in += len;
    return len;
  };
  /* ===========================================================================
   * Set match_start to the longest match starting at the given string and
   * return its length. Matches shorter or equal to prev_length are discarded,
   * in which case the result is equal to prev_length and match_start is
   * garbage.
   * IN assertions: cur_match is the head of the hash chain for the current
   *   string (strstart) and its distance is <= MAX_DIST, and prev_length >= 1
   * OUT assertion: the match length is not greater than s->lookahead.
   */


  const longest_match = (s, cur_match) => {
    let chain_length = s.max_chain_length;
    /* max hash chain length */

    let scan = s.strstart;
    /* current string */

    let match;
    /* matched string */

    let len;
    /* length of current match */

    let best_len = s.prev_length;
    /* best match length so far */

    let nice_match = s.nice_match;
    /* stop if match long enough */

    const limit = s.strstart > s.w_size - MIN_LOOKAHEAD ? s.strstart - (s.w_size - MIN_LOOKAHEAD) : 0
    /*NIL*/
    ;
    const _win = s.window; // shortcut

    const wmask = s.w_mask;
    const prev = s.prev;
    /* Stop when cur_match becomes <= limit. To simplify the code,
     * we prevent matches with the string of window index 0.
     */

    const strend = s.strstart + MAX_MATCH;
    let scan_end1 = _win[scan + best_len - 1];
    let scan_end = _win[scan + best_len];
    /* The code is optimized for HASH_BITS >= 8 and MAX_MATCH-2 multiple of 16.
     * It is easy to get rid of this optimization if necessary.
     */
    // Assert(s->hash_bits >= 8 && MAX_MATCH == 258, "Code too clever");

    /* Do not waste too much time if we already have a good match: */

    if (s.prev_length >= s.good_match) {
      chain_length >>= 2;
    }
    /* Do not look for matches beyond the end of the input. This is necessary
     * to make deflate deterministic.
     */


    if (nice_match > s.lookahead) {
      nice_match = s.lookahead;
    } // Assert((ulg)s->strstart <= s->window_size-MIN_LOOKAHEAD, "need lookahead");


    do {
      // Assert(cur_match < s->strstart, "no future");
      match = cur_match;
      /* Skip to next match if the match length cannot increase
       * or if the match length is less than 2.  Note that the checks below
       * for insufficient lookahead only occur occasionally for performance
       * reasons.  Therefore uninitialized memory will be accessed, and
       * conditional jumps will be made that depend on those values.
       * However the length of the match is limited to the lookahead, so
       * the output of deflate is not affected by the uninitialized values.
       */

      if (_win[match + best_len] !== scan_end || _win[match + best_len - 1] !== scan_end1 || _win[match] !== _win[scan] || _win[++match] !== _win[scan + 1]) {
        continue;
      }
      /* The check at best_len-1 can be removed because it will be made
       * again later. (This heuristic is not always a win.)
       * It is not necessary to compare scan[2] and match[2] since they
       * are always equal when the other bytes match, given that
       * the hash keys are equal and that HASH_BITS >= 8.
       */


      scan += 2;
      match++; // Assert(*scan == *match, "match[2]?");

      /* We check for insufficient lookahead only every 8th comparison;
       * the 256th check will be made at strstart+258.
       */

      do {
        /*jshint noempty:false*/
      } while (_win[++scan] === _win[++match] && _win[++scan] === _win[++match] && _win[++scan] === _win[++match] && _win[++scan] === _win[++match] && _win[++scan] === _win[++match] && _win[++scan] === _win[++match] && _win[++scan] === _win[++match] && _win[++scan] === _win[++match] && scan < strend); // Assert(scan <= s->window+(unsigned)(s->window_size-1), "wild scan");


      len = MAX_MATCH - (strend - scan);
      scan = strend - MAX_MATCH;

      if (len > best_len) {
        s.match_start = cur_match;
        best_len = len;

        if (len >= nice_match) {
          break;
        }

        scan_end1 = _win[scan + best_len - 1];
        scan_end = _win[scan + best_len];
      }
    } while ((cur_match = prev[cur_match & wmask]) > limit && --chain_length !== 0);

    if (best_len <= s.lookahead) {
      return best_len;
    }

    return s.lookahead;
  };
  /* ===========================================================================
   * Fill the window when the lookahead becomes insufficient.
   * Updates strstart and lookahead.
   *
   * IN assertion: lookahead < MIN_LOOKAHEAD
   * OUT assertions: strstart <= window_size-MIN_LOOKAHEAD
   *    At least one byte has been read, or avail_in == 0; reads are
   *    performed for at least two bytes (required for the zip translate_eol
   *    option -- not supported here).
   */


  const fill_window = s => {
    const _w_size = s.w_size;
    let p, n, m, more, str; //Assert(s->lookahead < MIN_LOOKAHEAD, "already enough lookahead");

    do {
      more = s.window_size - s.lookahead - s.strstart; // JS ints have 32 bit, block below not needed

      /* Deal with !@#$% 64K limit: */
      //if (sizeof(int) <= 2) {
      //    if (more == 0 && s->strstart == 0 && s->lookahead == 0) {
      //        more = wsize;
      //
      //  } else if (more == (unsigned)(-1)) {
      //        /* Very unlikely, but possible on 16 bit machine if
      //         * strstart == 0 && lookahead == 1 (input done a byte at time)
      //         */
      //        more--;
      //    }
      //}

      /* If the window is almost full and there is insufficient lookahead,
       * move the upper half to the lower one to make room in the upper half.
       */

      if (s.strstart >= _w_size + (_w_size - MIN_LOOKAHEAD)) {
        s.window.set(s.window.subarray(_w_size, _w_size + _w_size), 0);
        s.match_start -= _w_size;
        s.strstart -= _w_size;
        /* we now have strstart >= MAX_DIST */

        s.block_start -= _w_size;
        /* Slide the hash table (could be avoided with 32 bit values
         at the expense of memory usage). We slide even when level == 0
         to keep the hash table consistent if we switch back to level > 0
         later. (Using level 0 permanently is not an optimal usage of
         zlib, so we don't care about this pathological case.)
         */

        n = s.hash_size;
        p = n;

        do {
          m = s.head[--p];
          s.head[p] = m >= _w_size ? m - _w_size : 0;
        } while (--n);

        n = _w_size;
        p = n;

        do {
          m = s.prev[--p];
          s.prev[p] = m >= _w_size ? m - _w_size : 0;
          /* If n is not on any hash chain, prev[n] is garbage but
           * its value will never be used.
           */
        } while (--n);

        more += _w_size;
      }

      if (s.strm.avail_in === 0) {
        break;
      }
      /* If there was no sliding:
       *    strstart <= WSIZE+MAX_DIST-1 && lookahead <= MIN_LOOKAHEAD - 1 &&
       *    more == window_size - lookahead - strstart
       * => more >= window_size - (MIN_LOOKAHEAD-1 + WSIZE + MAX_DIST-1)
       * => more >= window_size - 2*WSIZE + 2
       * In the BIG_MEM or MMAP case (not yet supported),
       *   window_size == input_size + MIN_LOOKAHEAD  &&
       *   strstart + s->lookahead <= input_size => more >= MIN_LOOKAHEAD.
       * Otherwise, window_size == 2*WSIZE so more >= 2.
       * If there was sliding, more >= WSIZE. So in all cases, more >= 2.
       */
      //Assert(more >= 2, "more < 2");


      n = read_buf(s.strm, s.window, s.strstart + s.lookahead, more);
      s.lookahead += n;
      /* Initialize the hash value now that we have some input: */

      if (s.lookahead + s.insert >= MIN_MATCH) {
        str = s.strstart - s.insert;
        s.ins_h = s.window[str];
        /* UPDATE_HASH(s, s->ins_h, s->window[str + 1]); */

        s.ins_h = HASH(s, s.ins_h, s.window[str + 1]); //#if MIN_MATCH != 3
        //        Call update_hash() MIN_MATCH-3 more times
        //#endif

        while (s.insert) {
          /* UPDATE_HASH(s, s->ins_h, s->window[str + MIN_MATCH-1]); */
          s.ins_h = HASH(s, s.ins_h, s.window[str + MIN_MATCH - 1]);
          s.prev[str & s.w_mask] = s.head[s.ins_h];
          s.head[s.ins_h] = str;
          str++;
          s.insert--;

          if (s.lookahead + s.insert < MIN_MATCH) {
            break;
          }
        }
      }
      /* If the whole input has less than MIN_MATCH bytes, ins_h is garbage,
       * but this is not important since only literal bytes will be emitted.
       */

    } while (s.lookahead < MIN_LOOKAHEAD && s.strm.avail_in !== 0);
    /* If the WIN_INIT bytes after the end of the current data have never been
     * written, then zero those bytes in order to avoid memory check reports of
     * the use of uninitialized (or uninitialised as Julian writes) bytes by
     * the longest match routines.  Update the high water mark for the next
     * time through here.  WIN_INIT is set to MAX_MATCH since the longest match
     * routines allow scanning to strstart + MAX_MATCH, ignoring lookahead.
     */
    //  if (s.high_water < s.window_size) {
    //    const curr = s.strstart + s.lookahead;
    //    let init = 0;
    //
    //    if (s.high_water < curr) {
    //      /* Previous high water mark below current data -- zero WIN_INIT
    //       * bytes or up to end of window, whichever is less.
    //       */
    //      init = s.window_size - curr;
    //      if (init > WIN_INIT)
    //        init = WIN_INIT;
    //      zmemzero(s->window + curr, (unsigned)init);
    //      s->high_water = curr + init;
    //    }
    //    else if (s->high_water < (ulg)curr + WIN_INIT) {
    //      /* High water mark at or above current data, but below current data
    //       * plus WIN_INIT -- zero out to current data plus WIN_INIT, or up
    //       * to end of window, whichever is less.
    //       */
    //      init = (ulg)curr + WIN_INIT - s->high_water;
    //      if (init > s->window_size - s->high_water)
    //        init = s->window_size - s->high_water;
    //      zmemzero(s->window + s->high_water, (unsigned)init);
    //      s->high_water += init;
    //    }
    //  }
    //
    //  Assert((ulg)s->strstart <= s->window_size - MIN_LOOKAHEAD,
    //    "not enough room for search");

  };
  /* ===========================================================================
   * Copy without compression as much as possible from the input stream, return
   * the current block state.
   * This function does not insert new strings in the dictionary since
   * uncompressible data is probably not useful. This function is used
   * only for the level=0 compression option.
   * NOTE: this function should be optimized to avoid extra copying from
   * window to pending_buf.
   */


  const deflate_stored = (s, flush) => {
    /* Stored blocks are limited to 0xffff bytes, pending_buf is limited
     * to pending_buf_size, and each stored block has a 5 byte header:
     */
    let max_block_size = 0xffff;

    if (max_block_size > s.pending_buf_size - 5) {
      max_block_size = s.pending_buf_size - 5;
    }
    /* Copy as much as possible from input to output: */


    for (;;) {
      /* Fill the window as much as possible: */
      if (s.lookahead <= 1) {
        //Assert(s->strstart < s->w_size+MAX_DIST(s) ||
        //  s->block_start >= (long)s->w_size, "slide too late");
        //      if (!(s.strstart < s.w_size + (s.w_size - MIN_LOOKAHEAD) ||
        //        s.block_start >= s.w_size)) {
        //        throw  new Error("slide too late");
        //      }
        fill_window(s);

        if (s.lookahead === 0 && flush === Z_NO_FLUSH$2) {
          return BS_NEED_MORE;
        }

        if (s.lookahead === 0) {
          break;
        }
        /* flush the current block */

      } //Assert(s->block_start >= 0L, "block gone");
      //    if (s.block_start < 0) throw new Error("block gone");


      s.strstart += s.lookahead;
      s.lookahead = 0;
      /* Emit a stored block if pending_buf will be full: */

      const max_start = s.block_start + max_block_size;

      if (s.strstart === 0 || s.strstart >= max_start) {
        /* strstart == 0 is possible when wraparound on 16-bit machine */
        s.lookahead = s.strstart - max_start;
        s.strstart = max_start;
        /*** FLUSH_BLOCK(s, 0); ***/

        flush_block_only(s, false);

        if (s.strm.avail_out === 0) {
          return BS_NEED_MORE;
        }
        /***/

      }
      /* Flush if we may have to slide, otherwise block_start may become
       * negative and the data will be gone:
       */


      if (s.strstart - s.block_start >= s.w_size - MIN_LOOKAHEAD) {
        /*** FLUSH_BLOCK(s, 0); ***/
        flush_block_only(s, false);

        if (s.strm.avail_out === 0) {
          return BS_NEED_MORE;
        }
        /***/

      }
    }

    s.insert = 0;

    if (flush === Z_FINISH$3) {
      /*** FLUSH_BLOCK(s, 1); ***/
      flush_block_only(s, true);

      if (s.strm.avail_out === 0) {
        return BS_FINISH_STARTED;
      }
      /***/


      return BS_FINISH_DONE;
    }

    if (s.strstart > s.block_start) {
      /*** FLUSH_BLOCK(s, 0); ***/
      flush_block_only(s, false);

      if (s.strm.avail_out === 0) {
        return BS_NEED_MORE;
      }
      /***/

    }

    return BS_NEED_MORE;
  };
  /* ===========================================================================
   * Compress as much as possible from the input stream, return the current
   * block state.
   * This function does not perform lazy evaluation of matches and inserts
   * new strings in the dictionary only for unmatched strings or for short
   * matches. It is used only for the fast compression options.
   */


  const deflate_fast = (s, flush) => {
    let hash_head;
    /* head of the hash chain */

    let bflush;
    /* set if current block must be flushed */

    for (;;) {
      /* Make sure that we always have enough lookahead, except
       * at the end of the input file. We need MAX_MATCH bytes
       * for the next match, plus MIN_MATCH bytes to insert the
       * string following the next match.
       */
      if (s.lookahead < MIN_LOOKAHEAD) {
        fill_window(s);

        if (s.lookahead < MIN_LOOKAHEAD && flush === Z_NO_FLUSH$2) {
          return BS_NEED_MORE;
        }

        if (s.lookahead === 0) {
          break;
          /* flush the current block */
        }
      }
      /* Insert the string window[strstart .. strstart+2] in the
       * dictionary, and set hash_head to the head of the hash chain:
       */


      hash_head = 0
      /*NIL*/
      ;

      if (s.lookahead >= MIN_MATCH) {
        /*** INSERT_STRING(s, s.strstart, hash_head); ***/
        s.ins_h = HASH(s, s.ins_h, s.window[s.strstart + MIN_MATCH - 1]);
        hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h];
        s.head[s.ins_h] = s.strstart;
        /***/
      }
      /* Find the longest match, discarding those <= prev_length.
       * At this point we have always match_length < MIN_MATCH
       */


      if (hash_head !== 0
      /*NIL*/
      && s.strstart - hash_head <= s.w_size - MIN_LOOKAHEAD) {
        /* To simplify the code, we prevent matches with the string
         * of window index 0 (in particular we have to avoid a match
         * of the string with itself at the start of the input file).
         */
        s.match_length = longest_match(s, hash_head);
        /* longest_match() sets match_start */
      }

      if (s.match_length >= MIN_MATCH) {
        // check_match(s, s.strstart, s.match_start, s.match_length); // for debug only

        /*** _tr_tally_dist(s, s.strstart - s.match_start,
                       s.match_length - MIN_MATCH, bflush); ***/
        bflush = _tr_tally(s, s.strstart - s.match_start, s.match_length - MIN_MATCH);
        s.lookahead -= s.match_length;
        /* Insert new strings in the hash table only if the match length
         * is not too large. This saves time but degrades compression.
         */

        if (s.match_length <= s.max_lazy_match
        /*max_insert_length*/
        && s.lookahead >= MIN_MATCH) {
          s.match_length--;
          /* string at strstart already in table */

          do {
            s.strstart++;
            /*** INSERT_STRING(s, s.strstart, hash_head); ***/

            s.ins_h = HASH(s, s.ins_h, s.window[s.strstart + MIN_MATCH - 1]);
            hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h];
            s.head[s.ins_h] = s.strstart;
            /***/

            /* strstart never exceeds WSIZE-MAX_MATCH, so there are
             * always MIN_MATCH bytes ahead.
             */
          } while (--s.match_length !== 0);

          s.strstart++;
        } else {
          s.strstart += s.match_length;
          s.match_length = 0;
          s.ins_h = s.window[s.strstart];
          /* UPDATE_HASH(s, s.ins_h, s.window[s.strstart+1]); */

          s.ins_h = HASH(s, s.ins_h, s.window[s.strstart + 1]); //#if MIN_MATCH != 3
          //                Call UPDATE_HASH() MIN_MATCH-3 more times
          //#endif

          /* If lookahead < MIN_MATCH, ins_h is garbage, but it does not
           * matter since it will be recomputed at next deflate call.
           */
        }
      } else {
        /* No match, output a literal byte */
        //Tracevv((stderr,"%c", s.window[s.strstart]));

        /*** _tr_tally_lit(s, s.window[s.strstart], bflush); ***/
        bflush = _tr_tally(s, 0, s.window[s.strstart]);
        s.lookahead--;
        s.strstart++;
      }

      if (bflush) {
        /*** FLUSH_BLOCK(s, 0); ***/
        flush_block_only(s, false);

        if (s.strm.avail_out === 0) {
          return BS_NEED_MORE;
        }
        /***/

      }
    }

    s.insert = s.strstart < MIN_MATCH - 1 ? s.strstart : MIN_MATCH - 1;

    if (flush === Z_FINISH$3) {
      /*** FLUSH_BLOCK(s, 1); ***/
      flush_block_only(s, true);

      if (s.strm.avail_out === 0) {
        return BS_FINISH_STARTED;
      }
      /***/


      return BS_FINISH_DONE;
    }

    if (s.last_lit) {
      /*** FLUSH_BLOCK(s, 0); ***/
      flush_block_only(s, false);

      if (s.strm.avail_out === 0) {
        return BS_NEED_MORE;
      }
      /***/

    }

    return BS_BLOCK_DONE;
  };
  /* ===========================================================================
   * Same as above, but achieves better compression. We use a lazy
   * evaluation for matches: a match is finally adopted only if there is
   * no better match at the next window position.
   */


  const deflate_slow = (s, flush) => {
    let hash_head;
    /* head of hash chain */

    let bflush;
    /* set if current block must be flushed */

    let max_insert;
    /* Process the input block. */

    for (;;) {
      /* Make sure that we always have enough lookahead, except
       * at the end of the input file. We need MAX_MATCH bytes
       * for the next match, plus MIN_MATCH bytes to insert the
       * string following the next match.
       */
      if (s.lookahead < MIN_LOOKAHEAD) {
        fill_window(s);

        if (s.lookahead < MIN_LOOKAHEAD && flush === Z_NO_FLUSH$2) {
          return BS_NEED_MORE;
        }

        if (s.lookahead === 0) {
          break;
        }
        /* flush the current block */

      }
      /* Insert the string window[strstart .. strstart+2] in the
       * dictionary, and set hash_head to the head of the hash chain:
       */


      hash_head = 0
      /*NIL*/
      ;

      if (s.lookahead >= MIN_MATCH) {
        /*** INSERT_STRING(s, s.strstart, hash_head); ***/
        s.ins_h = HASH(s, s.ins_h, s.window[s.strstart + MIN_MATCH - 1]);
        hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h];
        s.head[s.ins_h] = s.strstart;
        /***/
      }
      /* Find the longest match, discarding those <= prev_length.
       */


      s.prev_length = s.match_length;
      s.prev_match = s.match_start;
      s.match_length = MIN_MATCH - 1;

      if (hash_head !== 0
      /*NIL*/
      && s.prev_length < s.max_lazy_match && s.strstart - hash_head <= s.w_size - MIN_LOOKAHEAD
      /*MAX_DIST(s)*/
      ) {
        /* To simplify the code, we prevent matches with the string
         * of window index 0 (in particular we have to avoid a match
         * of the string with itself at the start of the input file).
         */
        s.match_length = longest_match(s, hash_head);
        /* longest_match() sets match_start */

        if (s.match_length <= 5 && (s.strategy === Z_FILTERED || s.match_length === MIN_MATCH && s.strstart - s.match_start > 4096
        /*TOO_FAR*/
        )) {
          /* If prev_match is also MIN_MATCH, match_start is garbage
           * but we will ignore the current match anyway.
           */
          s.match_length = MIN_MATCH - 1;
        }
      }
      /* If there was a match at the previous step and the current
       * match is not better, output the previous match:
       */


      if (s.prev_length >= MIN_MATCH && s.match_length <= s.prev_length) {
        max_insert = s.strstart + s.lookahead - MIN_MATCH;
        /* Do not insert strings in hash table beyond this. */
        //check_match(s, s.strstart-1, s.prev_match, s.prev_length);

        /***_tr_tally_dist(s, s.strstart - 1 - s.prev_match,
                       s.prev_length - MIN_MATCH, bflush);***/

        bflush = _tr_tally(s, s.strstart - 1 - s.prev_match, s.prev_length - MIN_MATCH);
        /* Insert in hash table all strings up to the end of the match.
         * strstart-1 and strstart are already inserted. If there is not
         * enough lookahead, the last two strings are not inserted in
         * the hash table.
         */

        s.lookahead -= s.prev_length - 1;
        s.prev_length -= 2;

        do {
          if (++s.strstart <= max_insert) {
            /*** INSERT_STRING(s, s.strstart, hash_head); ***/
            s.ins_h = HASH(s, s.ins_h, s.window[s.strstart + MIN_MATCH - 1]);
            hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h];
            s.head[s.ins_h] = s.strstart;
            /***/
          }
        } while (--s.prev_length !== 0);

        s.match_available = 0;
        s.match_length = MIN_MATCH - 1;
        s.strstart++;

        if (bflush) {
          /*** FLUSH_BLOCK(s, 0); ***/
          flush_block_only(s, false);

          if (s.strm.avail_out === 0) {
            return BS_NEED_MORE;
          }
          /***/

        }
      } else if (s.match_available) {
        /* If there was no match at the previous position, output a
         * single literal. If there was a match but the current match
         * is longer, truncate the previous match to a single literal.
         */
        //Tracevv((stderr,"%c", s->window[s->strstart-1]));

        /*** _tr_tally_lit(s, s.window[s.strstart-1], bflush); ***/
        bflush = _tr_tally(s, 0, s.window[s.strstart - 1]);

        if (bflush) {
          /*** FLUSH_BLOCK_ONLY(s, 0) ***/
          flush_block_only(s, false);
          /***/
        }

        s.strstart++;
        s.lookahead--;

        if (s.strm.avail_out === 0) {
          return BS_NEED_MORE;
        }
      } else {
        /* There is no previous match to compare with, wait for
         * the next step to decide.
         */
        s.match_available = 1;
        s.strstart++;
        s.lookahead--;
      }
    } //Assert (flush != Z_NO_FLUSH, "no flush?");


    if (s.match_available) {
      //Tracevv((stderr,"%c", s->window[s->strstart-1]));

      /*** _tr_tally_lit(s, s.window[s.strstart-1], bflush); ***/
      bflush = _tr_tally(s, 0, s.window[s.strstart - 1]);
      s.match_available = 0;
    }

    s.insert = s.strstart < MIN_MATCH - 1 ? s.strstart : MIN_MATCH - 1;

    if (flush === Z_FINISH$3) {
      /*** FLUSH_BLOCK(s, 1); ***/
      flush_block_only(s, true);

      if (s.strm.avail_out === 0) {
        return BS_FINISH_STARTED;
      }
      /***/


      return BS_FINISH_DONE;
    }

    if (s.last_lit) {
      /*** FLUSH_BLOCK(s, 0); ***/
      flush_block_only(s, false);

      if (s.strm.avail_out === 0) {
        return BS_NEED_MORE;
      }
      /***/

    }

    return BS_BLOCK_DONE;
  };
  /* ===========================================================================
   * For Z_RLE, simply look for runs of bytes, generate matches only of distance
   * one.  Do not maintain a hash table.  (It will be regenerated if this run of
   * deflate switches away from Z_RLE.)
   */


  const deflate_rle = (s, flush) => {
    let bflush;
    /* set if current block must be flushed */

    let prev;
    /* byte at distance one to match */

    let scan, strend;
    /* scan goes up to strend for length of run */

    const _win = s.window;

    for (;;) {
      /* Make sure that we always have enough lookahead, except
       * at the end of the input file. We need MAX_MATCH bytes
       * for the longest run, plus one for the unrolled loop.
       */
      if (s.lookahead <= MAX_MATCH) {
        fill_window(s);

        if (s.lookahead <= MAX_MATCH && flush === Z_NO_FLUSH$2) {
          return BS_NEED_MORE;
        }

        if (s.lookahead === 0) {
          break;
        }
        /* flush the current block */

      }
      /* See how many times the previous byte repeats */


      s.match_length = 0;

      if (s.lookahead >= MIN_MATCH && s.strstart > 0) {
        scan = s.strstart - 1;
        prev = _win[scan];

        if (prev === _win[++scan] && prev === _win[++scan] && prev === _win[++scan]) {
          strend = s.strstart + MAX_MATCH;

          do {
            /*jshint noempty:false*/
          } while (prev === _win[++scan] && prev === _win[++scan] && prev === _win[++scan] && prev === _win[++scan] && prev === _win[++scan] && prev === _win[++scan] && prev === _win[++scan] && prev === _win[++scan] && scan < strend);

          s.match_length = MAX_MATCH - (strend - scan);

          if (s.match_length > s.lookahead) {
            s.match_length = s.lookahead;
          }
        } //Assert(scan <= s->window+(uInt)(s->window_size-1), "wild scan");

      }
      /* Emit match if have run of MIN_MATCH or longer, else emit literal */


      if (s.match_length >= MIN_MATCH) {
        //check_match(s, s.strstart, s.strstart - 1, s.match_length);

        /*** _tr_tally_dist(s, 1, s.match_length - MIN_MATCH, bflush); ***/
        bflush = _tr_tally(s, 1, s.match_length - MIN_MATCH);
        s.lookahead -= s.match_length;
        s.strstart += s.match_length;
        s.match_length = 0;
      } else {
        /* No match, output a literal byte */
        //Tracevv((stderr,"%c", s->window[s->strstart]));

        /*** _tr_tally_lit(s, s.window[s.strstart], bflush); ***/
        bflush = _tr_tally(s, 0, s.window[s.strstart]);
        s.lookahead--;
        s.strstart++;
      }

      if (bflush) {
        /*** FLUSH_BLOCK(s, 0); ***/
        flush_block_only(s, false);

        if (s.strm.avail_out === 0) {
          return BS_NEED_MORE;
        }
        /***/

      }
    }

    s.insert = 0;

    if (flush === Z_FINISH$3) {
      /*** FLUSH_BLOCK(s, 1); ***/
      flush_block_only(s, true);

      if (s.strm.avail_out === 0) {
        return BS_FINISH_STARTED;
      }
      /***/


      return BS_FINISH_DONE;
    }

    if (s.last_lit) {
      /*** FLUSH_BLOCK(s, 0); ***/
      flush_block_only(s, false);

      if (s.strm.avail_out === 0) {
        return BS_NEED_MORE;
      }
      /***/

    }

    return BS_BLOCK_DONE;
  };
  /* ===========================================================================
   * For Z_HUFFMAN_ONLY, do not look for matches.  Do not maintain a hash table.
   * (It will be regenerated if this run of deflate switches away from Huffman.)
   */


  const deflate_huff = (s, flush) => {
    let bflush;
    /* set if current block must be flushed */

    for (;;) {
      /* Make sure that we have a literal to write. */
      if (s.lookahead === 0) {
        fill_window(s);

        if (s.lookahead === 0) {
          if (flush === Z_NO_FLUSH$2) {
            return BS_NEED_MORE;
          }

          break;
          /* flush the current block */
        }
      }
      /* Output a literal byte */


      s.match_length = 0; //Tracevv((stderr,"%c", s->window[s->strstart]));

      /*** _tr_tally_lit(s, s.window[s.strstart], bflush); ***/

      bflush = _tr_tally(s, 0, s.window[s.strstart]);
      s.lookahead--;
      s.strstart++;

      if (bflush) {
        /*** FLUSH_BLOCK(s, 0); ***/
        flush_block_only(s, false);

        if (s.strm.avail_out === 0) {
          return BS_NEED_MORE;
        }
        /***/

      }
    }

    s.insert = 0;

    if (flush === Z_FINISH$3) {
      /*** FLUSH_BLOCK(s, 1); ***/
      flush_block_only(s, true);

      if (s.strm.avail_out === 0) {
        return BS_FINISH_STARTED;
      }
      /***/


      return BS_FINISH_DONE;
    }

    if (s.last_lit) {
      /*** FLUSH_BLOCK(s, 0); ***/
      flush_block_only(s, false);

      if (s.strm.avail_out === 0) {
        return BS_NEED_MORE;
      }
      /***/

    }

    return BS_BLOCK_DONE;
  };
  /* Values for max_lazy_match, good_match and max_chain_length, depending on
   * the desired pack level (0..9). The values given below have been tuned to
   * exclude worst case performance for pathological files. Better values may be
   * found for specific files.
   */


  function Config(good_length, max_lazy, nice_length, max_chain, func) {
    this.good_length = good_length;
    this.max_lazy = max_lazy;
    this.nice_length = nice_length;
    this.max_chain = max_chain;
    this.func = func;
  }

  const configuration_table = [
  /*      good lazy nice chain */
  new Config(0, 0, 0, 0, deflate_stored),
  /* 0 store only */
  new Config(4, 4, 8, 4, deflate_fast),
  /* 1 max speed, no lazy matches */
  new Config(4, 5, 16, 8, deflate_fast),
  /* 2 */
  new Config(4, 6, 32, 32, deflate_fast),
  /* 3 */
  new Config(4, 4, 16, 16, deflate_slow),
  /* 4 lazy matches */
  new Config(8, 16, 32, 32, deflate_slow),
  /* 5 */
  new Config(8, 16, 128, 128, deflate_slow),
  /* 6 */
  new Config(8, 32, 128, 256, deflate_slow),
  /* 7 */
  new Config(32, 128, 258, 1024, deflate_slow),
  /* 8 */
  new Config(32, 258, 258, 4096, deflate_slow)
  /* 9 max compression */
  ];
  /* ===========================================================================
   * Initialize the "longest match" routines for a new zlib stream
   */

  const lm_init = s => {
    s.window_size = 2 * s.w_size;
    /*** CLEAR_HASH(s); ***/

    zero(s.head); // Fill with NIL (= 0);

    /* Set the default configuration parameters:
     */

    s.max_lazy_match = configuration_table[s.level].max_lazy;
    s.good_match = configuration_table[s.level].good_length;
    s.nice_match = configuration_table[s.level].nice_length;
    s.max_chain_length = configuration_table[s.level].max_chain;
    s.strstart = 0;
    s.block_start = 0;
    s.lookahead = 0;
    s.insert = 0;
    s.match_length = s.prev_length = MIN_MATCH - 1;
    s.match_available = 0;
    s.ins_h = 0;
  };

  function DeflateState() {
    this.strm = null;
    /* pointer back to this zlib stream */

    this.status = 0;
    /* as the name implies */

    this.pending_buf = null;
    /* output still pending */

    this.pending_buf_size = 0;
    /* size of pending_buf */

    this.pending_out = 0;
    /* next pending byte to output to the stream */

    this.pending = 0;
    /* nb of bytes in the pending buffer */

    this.wrap = 0;
    /* bit 0 true for zlib, bit 1 true for gzip */

    this.gzhead = null;
    /* gzip header information to write */

    this.gzindex = 0;
    /* where in extra, name, or comment */

    this.method = Z_DEFLATED$2;
    /* can only be DEFLATED */

    this.last_flush = -1;
    /* value of flush param for previous deflate call */

    this.w_size = 0;
    /* LZ77 window size (32K by default) */

    this.w_bits = 0;
    /* log2(w_size)  (8..16) */

    this.w_mask = 0;
    /* w_size - 1 */

    this.window = null;
    /* Sliding window. Input bytes are read into the second half of the window,
     * and move to the first half later to keep a dictionary of at least wSize
     * bytes. With this organization, matches are limited to a distance of
     * wSize-MAX_MATCH bytes, but this ensures that IO is always
     * performed with a length multiple of the block size.
     */

    this.window_size = 0;
    /* Actual size of window: 2*wSize, except when the user input buffer
     * is directly used as sliding window.
     */

    this.prev = null;
    /* Link to older string with same hash index. To limit the size of this
     * array to 64K, this link is maintained only for the last 32K strings.
     * An index in this array is thus a window index modulo 32K.
     */

    this.head = null;
    /* Heads of the hash chains or NIL. */

    this.ins_h = 0;
    /* hash index of string to be inserted */

    this.hash_size = 0;
    /* number of elements in hash table */

    this.hash_bits = 0;
    /* log2(hash_size) */

    this.hash_mask = 0;
    /* hash_size-1 */

    this.hash_shift = 0;
    /* Number of bits by which ins_h must be shifted at each input
     * step. It must be such that after MIN_MATCH steps, the oldest
     * byte no longer takes part in the hash key, that is:
     *   hash_shift * MIN_MATCH >= hash_bits
     */

    this.block_start = 0;
    /* Window position at the beginning of the current output block. Gets
     * negative when the window is moved backwards.
     */

    this.match_length = 0;
    /* length of best match */

    this.prev_match = 0;
    /* previous match */

    this.match_available = 0;
    /* set if previous match exists */

    this.strstart = 0;
    /* start of string to insert */

    this.match_start = 0;
    /* start of matching string */

    this.lookahead = 0;
    /* number of valid bytes ahead in window */

    this.prev_length = 0;
    /* Length of the best match at previous step. Matches not greater than this
     * are discarded. This is used in the lazy match evaluation.
     */

    this.max_chain_length = 0;
    /* To speed up deflation, hash chains are never searched beyond this
     * length.  A higher limit improves compression ratio but degrades the
     * speed.
     */

    this.max_lazy_match = 0;
    /* Attempt to find a better match only when the current match is strictly
     * smaller than this value. This mechanism is used only for compression
     * levels >= 4.
     */
    // That's alias to max_lazy_match, don't use directly
    //this.max_insert_length = 0;

    /* Insert new strings in the hash table only if the match length is not
     * greater than this length. This saves time but degrades compression.
     * max_insert_length is used only for compression levels <= 3.
     */

    this.level = 0;
    /* compression level (1..9) */

    this.strategy = 0;
    /* favor or force Huffman coding*/

    this.good_match = 0;
    /* Use a faster search when the previous match is longer than this */

    this.nice_match = 0;
    /* Stop searching when current match exceeds this */

    /* used by trees.c: */

    /* Didn't use ct_data typedef below to suppress compiler warning */
    // struct ct_data_s dyn_ltree[HEAP_SIZE];   /* literal and length tree */
    // struct ct_data_s dyn_dtree[2*D_CODES+1]; /* distance tree */
    // struct ct_data_s bl_tree[2*BL_CODES+1];  /* Huffman tree for bit lengths */
    // Use flat array of DOUBLE size, with interleaved fata,
    // because JS does not support effective

    this.dyn_ltree = new Uint16Array(HEAP_SIZE * 2);
    this.dyn_dtree = new Uint16Array((2 * D_CODES + 1) * 2);
    this.bl_tree = new Uint16Array((2 * BL_CODES + 1) * 2);
    zero(this.dyn_ltree);
    zero(this.dyn_dtree);
    zero(this.bl_tree);
    this.l_desc = null;
    /* desc. for literal tree */

    this.d_desc = null;
    /* desc. for distance tree */

    this.bl_desc = null;
    /* desc. for bit length tree */
    //ush bl_count[MAX_BITS+1];

    this.bl_count = new Uint16Array(MAX_BITS + 1);
    /* number of codes at each bit length for an optimal tree */
    //int heap[2*L_CODES+1];      /* heap used to build the Huffman trees */

    this.heap = new Uint16Array(2 * L_CODES + 1);
    /* heap used to build the Huffman trees */

    zero(this.heap);
    this.heap_len = 0;
    /* number of elements in the heap */

    this.heap_max = 0;
    /* element of largest frequency */

    /* The sons of heap[n] are heap[2*n] and heap[2*n+1]. heap[0] is not used.
     * The same heap array is used to build all trees.
     */

    this.depth = new Uint16Array(2 * L_CODES + 1); //uch depth[2*L_CODES+1];

    zero(this.depth);
    /* Depth of each subtree used as tie breaker for trees of equal frequency
     */

    this.l_buf = 0;
    /* buffer index for literals or lengths */

    this.lit_bufsize = 0;
    /* Size of match buffer for literals/lengths.  There are 4 reasons for
     * limiting lit_bufsize to 64K:
     *   - frequencies can be kept in 16 bit counters
     *   - if compression is not successful for the first block, all input
     *     data is still in the window so we can still emit a stored block even
     *     when input comes from standard input.  (This can also be done for
     *     all blocks if lit_bufsize is not greater than 32K.)
     *   - if compression is not successful for a file smaller than 64K, we can
     *     even emit a stored file instead of a stored block (saving 5 bytes).
     *     This is applicable only for zip (not gzip or zlib).
     *   - creating new Huffman trees less frequently may not provide fast
     *     adaptation to changes in the input data statistics. (Take for
     *     example a binary file with poorly compressible code followed by
     *     a highly compressible string table.) Smaller buffer sizes give
     *     fast adaptation but have of course the overhead of transmitting
     *     trees more frequently.
     *   - I can't count above 4
     */

    this.last_lit = 0;
    /* running index in l_buf */

    this.d_buf = 0;
    /* Buffer index for distances. To simplify the code, d_buf and l_buf have
     * the same number of elements. To use different lengths, an extra flag
     * array would be necessary.
     */

    this.opt_len = 0;
    /* bit length of current block with optimal trees */

    this.static_len = 0;
    /* bit length of current block with static trees */

    this.matches = 0;
    /* number of string matches in current block */

    this.insert = 0;
    /* bytes at end of window left to insert */

    this.bi_buf = 0;
    /* Output buffer. bits are inserted starting at the bottom (least
     * significant bits).
     */

    this.bi_valid = 0;
    /* Number of valid bits in bi_buf.  All bits above the last valid bit
     * are always zero.
     */
    // Used for window memory init. We safely ignore it for JS. That makes
    // sense only for pointers and memory check tools.
    //this.high_water = 0;

    /* High water mark offset in window for initialized bytes -- bytes above
     * this are set to zero in order to avoid memory check warnings when
     * longest match routines access bytes past the input.  This is then
     * updated to the new high water mark.
     */
  }

  const deflateResetKeep = strm => {
    if (!strm || !strm.state) {
      return err(strm, Z_STREAM_ERROR$2);
    }

    strm.total_in = strm.total_out = 0;
    strm.data_type = Z_UNKNOWN;
    const s = strm.state;
    s.pending = 0;
    s.pending_out = 0;

    if (s.wrap < 0) {
      s.wrap = -s.wrap;
      /* was made negative by deflate(..., Z_FINISH); */
    }

    s.status = s.wrap ? INIT_STATE : BUSY_STATE;
    strm.adler = s.wrap === 2 ? 0 // crc32(0, Z_NULL, 0)
    : 1; // adler32(0, Z_NULL, 0)

    s.last_flush = Z_NO_FLUSH$2;

    _tr_init(s);

    return Z_OK$3;
  };

  const deflateReset = strm => {
    const ret = deflateResetKeep(strm);

    if (ret === Z_OK$3) {
      lm_init(strm.state);
    }

    return ret;
  };

  const deflateSetHeader = (strm, head) => {
    if (!strm || !strm.state) {
      return Z_STREAM_ERROR$2;
    }

    if (strm.state.wrap !== 2) {
      return Z_STREAM_ERROR$2;
    }

    strm.state.gzhead = head;
    return Z_OK$3;
  };

  const deflateInit2 = (strm, level, method, windowBits, memLevel, strategy) => {
    if (!strm) {
      // === Z_NULL
      return Z_STREAM_ERROR$2;
    }

    let wrap = 1;

    if (level === Z_DEFAULT_COMPRESSION$1) {
      level = 6;
    }

    if (windowBits < 0) {
      /* suppress zlib wrapper */
      wrap = 0;
      windowBits = -windowBits;
    } else if (windowBits > 15) {
      wrap = 2;
      /* write gzip wrapper instead */

      windowBits -= 16;
    }

    if (memLevel < 1 || memLevel > MAX_MEM_LEVEL || method !== Z_DEFLATED$2 || windowBits < 8 || windowBits > 15 || level < 0 || level > 9 || strategy < 0 || strategy > Z_FIXED) {
      return err(strm, Z_STREAM_ERROR$2);
    }

    if (windowBits === 8) {
      windowBits = 9;
    }
    /* until 256-byte window bug fixed */


    const s = new DeflateState();
    strm.state = s;
    s.strm = strm;
    s.wrap = wrap;
    s.gzhead = null;
    s.w_bits = windowBits;
    s.w_size = 1 << s.w_bits;
    s.w_mask = s.w_size - 1;
    s.hash_bits = memLevel + 7;
    s.hash_size = 1 << s.hash_bits;
    s.hash_mask = s.hash_size - 1;
    s.hash_shift = ~~((s.hash_bits + MIN_MATCH - 1) / MIN_MATCH);
    s.window = new Uint8Array(s.w_size * 2);
    s.head = new Uint16Array(s.hash_size);
    s.prev = new Uint16Array(s.w_size); // Don't need mem init magic for JS.
    //s.high_water = 0;  /* nothing written to s->window yet */

    s.lit_bufsize = 1 << memLevel + 6;
    /* 16K elements by default */

    s.pending_buf_size = s.lit_bufsize * 4; //overlay = (ushf *) ZALLOC(strm, s->lit_bufsize, sizeof(ush)+2);
    //s->pending_buf = (uchf *) overlay;

    s.pending_buf = new Uint8Array(s.pending_buf_size); // It is offset from `s.pending_buf` (size is `s.lit_bufsize * 2`)
    //s->d_buf = overlay + s->lit_bufsize/sizeof(ush);

    s.d_buf = 1 * s.lit_bufsize; //s->l_buf = s->pending_buf + (1+sizeof(ush))*s->lit_bufsize;

    s.l_buf = (1 + 2) * s.lit_bufsize;
    s.level = level;
    s.strategy = strategy;
    s.method = method;
    return deflateReset(strm);
  };

  const deflateInit = (strm, level) => {
    return deflateInit2(strm, level, Z_DEFLATED$2, MAX_WBITS$1, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY$1);
  };

  const deflate$2 = (strm, flush) => {
    let beg, val; // for gzip header write only

    if (!strm || !strm.state || flush > Z_BLOCK$1 || flush < 0) {
      return strm ? err(strm, Z_STREAM_ERROR$2) : Z_STREAM_ERROR$2;
    }

    const s = strm.state;

    if (!strm.output || !strm.input && strm.avail_in !== 0 || s.status === FINISH_STATE && flush !== Z_FINISH$3) {
      return err(strm, strm.avail_out === 0 ? Z_BUF_ERROR$1 : Z_STREAM_ERROR$2);
    }

    s.strm = strm;
    /* just in case */

    const old_flush = s.last_flush;
    s.last_flush = flush;
    /* Write the header */

    if (s.status === INIT_STATE) {
      if (s.wrap === 2) {
        // GZIP header
        strm.adler = 0; //crc32(0L, Z_NULL, 0);

        put_byte(s, 31);
        put_byte(s, 139);
        put_byte(s, 8);

        if (!s.gzhead) {
          // s->gzhead == Z_NULL
          put_byte(s, 0);
          put_byte(s, 0);
          put_byte(s, 0);
          put_byte(s, 0);
          put_byte(s, 0);
          put_byte(s, s.level === 9 ? 2 : s.strategy >= Z_HUFFMAN_ONLY || s.level < 2 ? 4 : 0);
          put_byte(s, OS_CODE);
          s.status = BUSY_STATE;
        } else {
          put_byte(s, (s.gzhead.text ? 1 : 0) + (s.gzhead.hcrc ? 2 : 0) + (!s.gzhead.extra ? 0 : 4) + (!s.gzhead.name ? 0 : 8) + (!s.gzhead.comment ? 0 : 16));
          put_byte(s, s.gzhead.time & 0xff);
          put_byte(s, s.gzhead.time >> 8 & 0xff);
          put_byte(s, s.gzhead.time >> 16 & 0xff);
          put_byte(s, s.gzhead.time >> 24 & 0xff);
          put_byte(s, s.level === 9 ? 2 : s.strategy >= Z_HUFFMAN_ONLY || s.level < 2 ? 4 : 0);
          put_byte(s, s.gzhead.os & 0xff);

          if (s.gzhead.extra && s.gzhead.extra.length) {
            put_byte(s, s.gzhead.extra.length & 0xff);
            put_byte(s, s.gzhead.extra.length >> 8 & 0xff);
          }

          if (s.gzhead.hcrc) {
            strm.adler = crc32_1(strm.adler, s.pending_buf, s.pending, 0);
          }

          s.gzindex = 0;
          s.status = EXTRA_STATE;
        }
      } else // DEFLATE header
        {
          let header = Z_DEFLATED$2 + (s.w_bits - 8 << 4) << 8;
          let level_flags = -1;

          if (s.strategy >= Z_HUFFMAN_ONLY || s.level < 2) {
            level_flags = 0;
          } else if (s.level < 6) {
            level_flags = 1;
          } else if (s.level === 6) {
            level_flags = 2;
          } else {
            level_flags = 3;
          }

          header |= level_flags << 6;

          if (s.strstart !== 0) {
            header |= PRESET_DICT;
          }

          header += 31 - header % 31;
          s.status = BUSY_STATE;
          putShortMSB(s, header);
          /* Save the adler32 of the preset dictionary: */

          if (s.strstart !== 0) {
            putShortMSB(s, strm.adler >>> 16);
            putShortMSB(s, strm.adler & 0xffff);
          }

          strm.adler = 1; // adler32(0L, Z_NULL, 0);
        }
    } //#ifdef GZIP


    if (s.status === EXTRA_STATE) {
      if (s.gzhead.extra
      /* != Z_NULL*/
      ) {
        beg = s.pending;
        /* start of bytes to update crc */

        while (s.gzindex < (s.gzhead.extra.length & 0xffff)) {
          if (s.pending === s.pending_buf_size) {
            if (s.gzhead.hcrc && s.pending > beg) {
              strm.adler = crc32_1(strm.adler, s.pending_buf, s.pending - beg, beg);
            }

            flush_pending(strm);
            beg = s.pending;

            if (s.pending === s.pending_buf_size) {
              break;
            }
          }

          put_byte(s, s.gzhead.extra[s.gzindex] & 0xff);
          s.gzindex++;
        }

        if (s.gzhead.hcrc && s.pending > beg) {
          strm.adler = crc32_1(strm.adler, s.pending_buf, s.pending - beg, beg);
        }

        if (s.gzindex === s.gzhead.extra.length) {
          s.gzindex = 0;
          s.status = NAME_STATE;
        }
      } else {
        s.status = NAME_STATE;
      }
    }

    if (s.status === NAME_STATE) {
      if (s.gzhead.name
      /* != Z_NULL*/
      ) {
        beg = s.pending;
        /* start of bytes to update crc */
        //int val;

        do {
          if (s.pending === s.pending_buf_size) {
            if (s.gzhead.hcrc && s.pending > beg) {
              strm.adler = crc32_1(strm.adler, s.pending_buf, s.pending - beg, beg);
            }

            flush_pending(strm);
            beg = s.pending;

            if (s.pending === s.pending_buf_size) {
              val = 1;
              break;
            }
          } // JS specific: little magic to add zero terminator to end of string


          if (s.gzindex < s.gzhead.name.length) {
            val = s.gzhead.name.charCodeAt(s.gzindex++) & 0xff;
          } else {
            val = 0;
          }

          put_byte(s, val);
        } while (val !== 0);

        if (s.gzhead.hcrc && s.pending > beg) {
          strm.adler = crc32_1(strm.adler, s.pending_buf, s.pending - beg, beg);
        }

        if (val === 0) {
          s.gzindex = 0;
          s.status = COMMENT_STATE;
        }
      } else {
        s.status = COMMENT_STATE;
      }
    }

    if (s.status === COMMENT_STATE) {
      if (s.gzhead.comment
      /* != Z_NULL*/
      ) {
        beg = s.pending;
        /* start of bytes to update crc */
        //int val;

        do {
          if (s.pending === s.pending_buf_size) {
            if (s.gzhead.hcrc && s.pending > beg) {
              strm.adler = crc32_1(strm.adler, s.pending_buf, s.pending - beg, beg);
            }

            flush_pending(strm);
            beg = s.pending;

            if (s.pending === s.pending_buf_size) {
              val = 1;
              break;
            }
          } // JS specific: little magic to add zero terminator to end of string


          if (s.gzindex < s.gzhead.comment.length) {
            val = s.gzhead.comment.charCodeAt(s.gzindex++) & 0xff;
          } else {
            val = 0;
          }

          put_byte(s, val);
        } while (val !== 0);

        if (s.gzhead.hcrc && s.pending > beg) {
          strm.adler = crc32_1(strm.adler, s.pending_buf, s.pending - beg, beg);
        }

        if (val === 0) {
          s.status = HCRC_STATE;
        }
      } else {
        s.status = HCRC_STATE;
      }
    }

    if (s.status === HCRC_STATE) {
      if (s.gzhead.hcrc) {
        if (s.pending + 2 > s.pending_buf_size) {
          flush_pending(strm);
        }

        if (s.pending + 2 <= s.pending_buf_size) {
          put_byte(s, strm.adler & 0xff);
          put_byte(s, strm.adler >> 8 & 0xff);
          strm.adler = 0; //crc32(0L, Z_NULL, 0);

          s.status = BUSY_STATE;
        }
      } else {
        s.status = BUSY_STATE;
      }
    } //#endif

    /* Flush as much pending output as possible */


    if (s.pending !== 0) {
      flush_pending(strm);

      if (strm.avail_out === 0) {
        /* Since avail_out is 0, deflate will be called again with
         * more output space, but possibly with both pending and
         * avail_in equal to zero. There won't be anything to do,
         * but this is not an error situation so make sure we
         * return OK instead of BUF_ERROR at next call of deflate:
         */
        s.last_flush = -1;
        return Z_OK$3;
      }
      /* Make sure there is something to do and avoid duplicate consecutive
       * flushes. For repeated and useless calls with Z_FINISH, we keep
       * returning Z_STREAM_END instead of Z_BUF_ERROR.
       */

    } else if (strm.avail_in === 0 && rank(flush) <= rank(old_flush) && flush !== Z_FINISH$3) {
      return err(strm, Z_BUF_ERROR$1);
    }
    /* User must not provide more input after the first FINISH: */


    if (s.status === FINISH_STATE && strm.avail_in !== 0) {
      return err(strm, Z_BUF_ERROR$1);
    }
    /* Start a new block or continue the current one.
     */


    if (strm.avail_in !== 0 || s.lookahead !== 0 || flush !== Z_NO_FLUSH$2 && s.status !== FINISH_STATE) {
      let bstate = s.strategy === Z_HUFFMAN_ONLY ? deflate_huff(s, flush) : s.strategy === Z_RLE ? deflate_rle(s, flush) : configuration_table[s.level].func(s, flush);

      if (bstate === BS_FINISH_STARTED || bstate === BS_FINISH_DONE) {
        s.status = FINISH_STATE;
      }

      if (bstate === BS_NEED_MORE || bstate === BS_FINISH_STARTED) {
        if (strm.avail_out === 0) {
          s.last_flush = -1;
          /* avoid BUF_ERROR next call, see above */
        }

        return Z_OK$3;
        /* If flush != Z_NO_FLUSH && avail_out == 0, the next call
         * of deflate should use the same flush parameter to make sure
         * that the flush is complete. So we don't have to output an
         * empty block here, this will be done at next call. This also
         * ensures that for a very small output buffer, we emit at most
         * one empty block.
         */
      }

      if (bstate === BS_BLOCK_DONE) {
        if (flush === Z_PARTIAL_FLUSH) {
          _tr_align(s);
        } else if (flush !== Z_BLOCK$1) {
          /* FULL_FLUSH or SYNC_FLUSH */
          _tr_stored_block(s, 0, 0, false);
          /* For a full flush, this empty block will be recognized
           * as a special marker by inflate_sync().
           */


          if (flush === Z_FULL_FLUSH$1) {
            /*** CLEAR_HASH(s); ***/

            /* forget history */
            zero(s.head); // Fill with NIL (= 0);

            if (s.lookahead === 0) {
              s.strstart = 0;
              s.block_start = 0;
              s.insert = 0;
            }
          }
        }

        flush_pending(strm);

        if (strm.avail_out === 0) {
          s.last_flush = -1;
          /* avoid BUF_ERROR at next call, see above */

          return Z_OK$3;
        }
      }
    } //Assert(strm->avail_out > 0, "bug2");
    //if (strm.avail_out <= 0) { throw new Error("bug2");}


    if (flush !== Z_FINISH$3) {
      return Z_OK$3;
    }

    if (s.wrap <= 0) {
      return Z_STREAM_END$3;
    }
    /* Write the trailer */


    if (s.wrap === 2) {
      put_byte(s, strm.adler & 0xff);
      put_byte(s, strm.adler >> 8 & 0xff);
      put_byte(s, strm.adler >> 16 & 0xff);
      put_byte(s, strm.adler >> 24 & 0xff);
      put_byte(s, strm.total_in & 0xff);
      put_byte(s, strm.total_in >> 8 & 0xff);
      put_byte(s, strm.total_in >> 16 & 0xff);
      put_byte(s, strm.total_in >> 24 & 0xff);
    } else {
      putShortMSB(s, strm.adler >>> 16);
      putShortMSB(s, strm.adler & 0xffff);
    }

    flush_pending(strm);
    /* If avail_out is zero, the application will call deflate again
     * to flush the rest.
     */

    if (s.wrap > 0) {
      s.wrap = -s.wrap;
    }
    /* write the trailer only once! */


    return s.pending !== 0 ? Z_OK$3 : Z_STREAM_END$3;
  };

  const deflateEnd = strm => {
    if (!strm
    /*== Z_NULL*/
    || !strm.state
    /*== Z_NULL*/
    ) {
      return Z_STREAM_ERROR$2;
    }

    const status = strm.state.status;

    if (status !== INIT_STATE && status !== EXTRA_STATE && status !== NAME_STATE && status !== COMMENT_STATE && status !== HCRC_STATE && status !== BUSY_STATE && status !== FINISH_STATE) {
      return err(strm, Z_STREAM_ERROR$2);
    }

    strm.state = null;
    return status === BUSY_STATE ? err(strm, Z_DATA_ERROR$2) : Z_OK$3;
  };
  /* =========================================================================
   * Initializes the compression dictionary from the given byte
   * sequence without producing any compressed output.
   */


  const deflateSetDictionary = (strm, dictionary) => {
    let dictLength = dictionary.length;

    if (!strm
    /*== Z_NULL*/
    || !strm.state
    /*== Z_NULL*/
    ) {
      return Z_STREAM_ERROR$2;
    }

    const s = strm.state;
    const wrap = s.wrap;

    if (wrap === 2 || wrap === 1 && s.status !== INIT_STATE || s.lookahead) {
      return Z_STREAM_ERROR$2;
    }
    /* when using zlib wrappers, compute Adler-32 for provided dictionary */


    if (wrap === 1) {
      /* adler32(strm->adler, dictionary, dictLength); */
      strm.adler = adler32_1(strm.adler, dictionary, dictLength, 0);
    }

    s.wrap = 0;
    /* avoid computing Adler-32 in read_buf */

    /* if dictionary would fill window, just replace the history */

    if (dictLength >= s.w_size) {
      if (wrap === 0) {
        /* already empty otherwise */

        /*** CLEAR_HASH(s); ***/
        zero(s.head); // Fill with NIL (= 0);

        s.strstart = 0;
        s.block_start = 0;
        s.insert = 0;
      }
      /* use the tail */
      // dictionary = dictionary.slice(dictLength - s.w_size);


      let tmpDict = new Uint8Array(s.w_size);
      tmpDict.set(dictionary.subarray(dictLength - s.w_size, dictLength), 0);
      dictionary = tmpDict;
      dictLength = s.w_size;
    }
    /* insert dictionary into window and hash */


    const avail = strm.avail_in;
    const next = strm.next_in;
    const input = strm.input;
    strm.avail_in = dictLength;
    strm.next_in = 0;
    strm.input = dictionary;
    fill_window(s);

    while (s.lookahead >= MIN_MATCH) {
      let str = s.strstart;
      let n = s.lookahead - (MIN_MATCH - 1);

      do {
        /* UPDATE_HASH(s, s->ins_h, s->window[str + MIN_MATCH-1]); */
        s.ins_h = HASH(s, s.ins_h, s.window[str + MIN_MATCH - 1]);
        s.prev[str & s.w_mask] = s.head[s.ins_h];
        s.head[s.ins_h] = str;
        str++;
      } while (--n);

      s.strstart = str;
      s.lookahead = MIN_MATCH - 1;
      fill_window(s);
    }

    s.strstart += s.lookahead;
    s.block_start = s.strstart;
    s.insert = s.lookahead;
    s.lookahead = 0;
    s.match_length = s.prev_length = MIN_MATCH - 1;
    s.match_available = 0;
    strm.next_in = next;
    strm.input = input;
    strm.avail_in = avail;
    s.wrap = wrap;
    return Z_OK$3;
  };

  var deflateInit_1 = deflateInit;
  var deflateInit2_1 = deflateInit2;
  var deflateReset_1 = deflateReset;
  var deflateResetKeep_1 = deflateResetKeep;
  var deflateSetHeader_1 = deflateSetHeader;
  var deflate_2$1 = deflate$2;
  var deflateEnd_1 = deflateEnd;
  var deflateSetDictionary_1 = deflateSetDictionary;
  var deflateInfo = 'pako deflate (from Nodeca project)';
  /* Not implemented
  module.exports.deflateBound = deflateBound;
  module.exports.deflateCopy = deflateCopy;
  module.exports.deflateParams = deflateParams;
  module.exports.deflatePending = deflatePending;
  module.exports.deflatePrime = deflatePrime;
  module.exports.deflateTune = deflateTune;
  */

  var deflate_1$2 = {
    deflateInit: deflateInit_1,
    deflateInit2: deflateInit2_1,
    deflateReset: deflateReset_1,
    deflateResetKeep: deflateResetKeep_1,
    deflateSetHeader: deflateSetHeader_1,
    deflate: deflate_2$1,
    deflateEnd: deflateEnd_1,
    deflateSetDictionary: deflateSetDictionary_1,
    deflateInfo: deflateInfo
  };

  const _has = (obj, key) => {
    return Object.prototype.hasOwnProperty.call(obj, key);
  };

  var assign = function (obj
  /*from1, from2, from3, ...*/
  ) {
    const sources = Array.prototype.slice.call(arguments, 1);

    while (sources.length) {
      const source = sources.shift();

      if (!source) {
        continue;
      }

      if (typeof source !== 'object') {
        throw new TypeError(source + 'must be non-object');
      }

      for (const p in source) {
        if (_has(source, p)) {
          obj[p] = source[p];
        }
      }
    }

    return obj;
  }; // Join array of chunks to single array.


  var flattenChunks = chunks => {
    // calculate data length
    let len = 0;

    for (let i = 0, l = chunks.length; i < l; i++) {
      len += chunks[i].length;
    } // join chunks


    const result = new Uint8Array(len);

    for (let i = 0, pos = 0, l = chunks.length; i < l; i++) {
      let chunk = chunks[i];
      result.set(chunk, pos);
      pos += chunk.length;
    }

    return result;
  };

  var common = {
    assign: assign,
    flattenChunks: flattenChunks
  }; // String encode/decode helpers
  // Quick check if we can use fast array to bin string conversion
  //
  // - apply(Array) can fail on Android 2.2
  // - apply(Uint8Array) can fail on iOS 5.1 Safari
  //

  let STR_APPLY_UIA_OK = true;

  try {
    String.fromCharCode.apply(null, new Uint8Array(1));
  } catch (__) {
    STR_APPLY_UIA_OK = false;
  } // Table with utf8 lengths (calculated by first byte of sequence)
  // Note, that 5 & 6-byte values and some 4-byte values can not be represented in JS,
  // because max possible codepoint is 0x10ffff


  const _utf8len = new Uint8Array(256);

  for (let q = 0; q < 256; q++) {
    _utf8len[q] = q >= 252 ? 6 : q >= 248 ? 5 : q >= 240 ? 4 : q >= 224 ? 3 : q >= 192 ? 2 : 1;
  }

  _utf8len[254] = _utf8len[254] = 1; // Invalid sequence start
  // convert string to array (typed, when possible)

  var string2buf = str => {
    if (typeof TextEncoder === 'function' && TextEncoder.prototype.encode) {
      return new TextEncoder().encode(str);
    }

    let buf,
        c,
        c2,
        m_pos,
        i,
        str_len = str.length,
        buf_len = 0; // count binary size

    for (m_pos = 0; m_pos < str_len; m_pos++) {
      c = str.charCodeAt(m_pos);

      if ((c & 0xfc00) === 0xd800 && m_pos + 1 < str_len) {
        c2 = str.charCodeAt(m_pos + 1);

        if ((c2 & 0xfc00) === 0xdc00) {
          c = 0x10000 + (c - 0xd800 << 10) + (c2 - 0xdc00);
          m_pos++;
        }
      }

      buf_len += c < 0x80 ? 1 : c < 0x800 ? 2 : c < 0x10000 ? 3 : 4;
    } // allocate buffer


    buf = new Uint8Array(buf_len); // convert

    for (i = 0, m_pos = 0; i < buf_len; m_pos++) {
      c = str.charCodeAt(m_pos);

      if ((c & 0xfc00) === 0xd800 && m_pos + 1 < str_len) {
        c2 = str.charCodeAt(m_pos + 1);

        if ((c2 & 0xfc00) === 0xdc00) {
          c = 0x10000 + (c - 0xd800 << 10) + (c2 - 0xdc00);
          m_pos++;
        }
      }

      if (c < 0x80) {
        /* one byte */
        buf[i++] = c;
      } else if (c < 0x800) {
        /* two bytes */
        buf[i++] = 0xC0 | c >>> 6;
        buf[i++] = 0x80 | c & 0x3f;
      } else if (c < 0x10000) {
        /* three bytes */
        buf[i++] = 0xE0 | c >>> 12;
        buf[i++] = 0x80 | c >>> 6 & 0x3f;
        buf[i++] = 0x80 | c & 0x3f;
      } else {
        /* four bytes */
        buf[i++] = 0xf0 | c >>> 18;
        buf[i++] = 0x80 | c >>> 12 & 0x3f;
        buf[i++] = 0x80 | c >>> 6 & 0x3f;
        buf[i++] = 0x80 | c & 0x3f;
      }
    }

    return buf;
  }; // Helper


  const buf2binstring = (buf, len) => {
    // On Chrome, the arguments in a function call that are allowed is `65534`.
    // If the length of the buffer is smaller than that, we can use this optimization,
    // otherwise we will take a slower path.
    if (len < 65534) {
      if (buf.subarray && STR_APPLY_UIA_OK) {
        return String.fromCharCode.apply(null, buf.length === len ? buf : buf.subarray(0, len));
      }
    }

    let result = '';

    for (let i = 0; i < len; i++) {
      result += String.fromCharCode(buf[i]);
    }

    return result;
  }; // convert array to string


  var buf2string = (buf, max) => {
    const len = max || buf.length;

    if (typeof TextDecoder === 'function' && TextDecoder.prototype.decode) {
      return new TextDecoder().decode(buf.subarray(0, max));
    }

    let i, out; // Reserve max possible length (2 words per char)
    // NB: by unknown reasons, Array is significantly faster for
    //     String.fromCharCode.apply than Uint16Array.

    const utf16buf = new Array(len * 2);

    for (out = 0, i = 0; i < len;) {
      let c = buf[i++]; // quick process ascii

      if (c < 0x80) {
        utf16buf[out++] = c;
        continue;
      }

      let c_len = _utf8len[c]; // skip 5 & 6 byte codes

      if (c_len > 4) {
        utf16buf[out++] = 0xfffd;
        i += c_len - 1;
        continue;
      } // apply mask on first byte


      c &= c_len === 2 ? 0x1f : c_len === 3 ? 0x0f : 0x07; // join the rest

      while (c_len > 1 && i < len) {
        c = c << 6 | buf[i++] & 0x3f;
        c_len--;
      } // terminated by end of string?


      if (c_len > 1) {
        utf16buf[out++] = 0xfffd;
        continue;
      }

      if (c < 0x10000) {
        utf16buf[out++] = c;
      } else {
        c -= 0x10000;
        utf16buf[out++] = 0xd800 | c >> 10 & 0x3ff;
        utf16buf[out++] = 0xdc00 | c & 0x3ff;
      }
    }

    return buf2binstring(utf16buf, out);
  }; // Calculate max possible position in utf8 buffer,
  // that will not break sequence. If that's not possible
  // - (very small limits) return max size as is.
  //
  // buf[] - utf8 bytes array
  // max   - length limit (mandatory);


  var utf8border = (buf, max) => {
    max = max || buf.length;

    if (max > buf.length) {
      max = buf.length;
    } // go back from last position, until start of sequence found


    let pos = max - 1;

    while (pos >= 0 && (buf[pos] & 0xC0) === 0x80) {
      pos--;
    } // Very small and broken sequence,
    // return max, because we should return something anyway.


    if (pos < 0) {
      return max;
    } // If we came to start of buffer - that means buffer is too small,
    // return max too.


    if (pos === 0) {
      return max;
    }

    return pos + _utf8len[buf[pos]] > max ? pos : max;
  };

  var strings = {
    string2buf: string2buf,
    buf2string: buf2string,
    utf8border: utf8border
  }; // (C) 1995-2013 Jean-loup Gailly and Mark Adler
  // (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin
  //
  // This software is provided 'as-is', without any express or implied
  // warranty. In no event will the authors be held liable for any damages
  // arising from the use of this software.
  //
  // Permission is granted to anyone to use this software for any purpose,
  // including commercial applications, and to alter it and redistribute it
  // freely, subject to the following restrictions:
  //
  // 1. The origin of this software must not be misrepresented; you must not
  //   claim that you wrote the original software. If you use this software
  //   in a product, an acknowledgment in the product documentation would be
  //   appreciated but is not required.
  // 2. Altered source versions must be plainly marked as such, and must not be
  //   misrepresented as being the original software.
  // 3. This notice may not be removed or altered from any source distribution.

  function ZStream() {
    /* next input byte */
    this.input = null; // JS specific, because we have no pointers

    this.next_in = 0;
    /* number of bytes available at input */

    this.avail_in = 0;
    /* total number of input bytes read so far */

    this.total_in = 0;
    /* next output byte should be put there */

    this.output = null; // JS specific, because we have no pointers

    this.next_out = 0;
    /* remaining free space at output */

    this.avail_out = 0;
    /* total number of bytes output so far */

    this.total_out = 0;
    /* last error message, NULL if no error */

    this.msg = ''
    /*Z_NULL*/
    ;
    /* not visible by applications */

    this.state = null;
    /* best guess about the data type: binary or text */

    this.data_type = 2
    /*Z_UNKNOWN*/
    ;
    /* adler32 value of the uncompressed data */

    this.adler = 0;
  }

  var zstream = ZStream;
  const toString$1 = Object.prototype.toString;
  /* Public constants ==========================================================*/

  /* ===========================================================================*/

  const {
    Z_NO_FLUSH: Z_NO_FLUSH$1,
    Z_SYNC_FLUSH,
    Z_FULL_FLUSH,
    Z_FINISH: Z_FINISH$2,
    Z_OK: Z_OK$2,
    Z_STREAM_END: Z_STREAM_END$2,
    Z_DEFAULT_COMPRESSION,
    Z_DEFAULT_STRATEGY,
    Z_DEFLATED: Z_DEFLATED$1
  } = constants$2;
  /* ===========================================================================*/

  /**
   * class Deflate
   *
   * Generic JS-style wrapper for zlib calls. If you don't need
   * streaming behaviour - use more simple functions: [[deflate]],
   * [[deflateRaw]] and [[gzip]].
   **/

  /* internal
   * Deflate.chunks -> Array
   *
   * Chunks of output data, if [[Deflate#onData]] not overridden.
   **/

  /**
   * Deflate.result -> Uint8Array
   *
   * Compressed result, generated by default [[Deflate#onData]]
   * and [[Deflate#onEnd]] handlers. Filled after you push last chunk
   * (call [[Deflate#push]] with `Z_FINISH` / `true` param).
   **/

  /**
   * Deflate.err -> Number
   *
   * Error code after deflate finished. 0 (Z_OK) on success.
   * You will not need it in real life, because deflate errors
   * are possible only on wrong options or bad `onData` / `onEnd`
   * custom handlers.
   **/

  /**
   * Deflate.msg -> String
   *
   * Error message, if [[Deflate.err]] != 0
   **/

  /**
   * new Deflate(options)
   * - options (Object): zlib deflate options.
   *
   * Creates new deflator instance with specified params. Throws exception
   * on bad params. Supported options:
   *
   * - `level`
   * - `windowBits`
   * - `memLevel`
   * - `strategy`
   * - `dictionary`
   *
   * [http://zlib.net/manual.html#Advanced](http://zlib.net/manual.html#Advanced)
   * for more information on these.
   *
   * Additional options, for internal needs:
   *
   * - `chunkSize` - size of generated data chunks (16K by default)
   * - `raw` (Boolean) - do raw deflate
   * - `gzip` (Boolean) - create gzip wrapper
   * - `header` (Object) - custom header for gzip
   *   - `text` (Boolean) - true if compressed data believed to be text
   *   - `time` (Number) - modification time, unix timestamp
   *   - `os` (Number) - operation system code
   *   - `extra` (Array) - array of bytes with extra data (max 65536)
   *   - `name` (String) - file name (binary string)
   *   - `comment` (String) - comment (binary string)
   *   - `hcrc` (Boolean) - true if header crc should be added
   *
   * ##### Example:
   *
   * ```javascript
   * const pako = require('pako')
   *   , chunk1 = new Uint8Array([1,2,3,4,5,6,7,8,9])
   *   , chunk2 = new Uint8Array([10,11,12,13,14,15,16,17,18,19]);
   *
   * const deflate = new pako.Deflate({ level: 3});
   *
   * deflate.push(chunk1, false);
   * deflate.push(chunk2, true);  // true -> last chunk
   *
   * if (deflate.err) { throw new Error(deflate.err); }
   *
   * console.log(deflate.result);
   * ```
   **/

  function Deflate$1(options) {
    this.options = common.assign({
      level: Z_DEFAULT_COMPRESSION,
      method: Z_DEFLATED$1,
      chunkSize: 16384,
      windowBits: 15,
      memLevel: 8,
      strategy: Z_DEFAULT_STRATEGY
    }, options || {});
    let opt = this.options;

    if (opt.raw && opt.windowBits > 0) {
      opt.windowBits = -opt.windowBits;
    } else if (opt.gzip && opt.windowBits > 0 && opt.windowBits < 16) {
      opt.windowBits += 16;
    }

    this.err = 0; // error code, if happens (0 = Z_OK)

    this.msg = ''; // error message

    this.ended = false; // used to avoid multiple onEnd() calls

    this.chunks = []; // chunks of compressed data

    this.strm = new zstream();
    this.strm.avail_out = 0;
    let status = deflate_1$2.deflateInit2(this.strm, opt.level, opt.method, opt.windowBits, opt.memLevel, opt.strategy);

    if (status !== Z_OK$2) {
      throw new Error(messages[status]);
    }

    if (opt.header) {
      deflate_1$2.deflateSetHeader(this.strm, opt.header);
    }

    if (opt.dictionary) {
      let dict; // Convert data if needed

      if (typeof opt.dictionary === 'string') {
        // If we need to compress text, change encoding to utf8.
        dict = strings.string2buf(opt.dictionary);
      } else if (toString$1.call(opt.dictionary) === '[object ArrayBuffer]') {
        dict = new Uint8Array(opt.dictionary);
      } else {
        dict = opt.dictionary;
      }

      status = deflate_1$2.deflateSetDictionary(this.strm, dict);

      if (status !== Z_OK$2) {
        throw new Error(messages[status]);
      }

      this._dict_set = true;
    }
  }
  /**
   * Deflate#push(data[, flush_mode]) -> Boolean
   * - data (Uint8Array|ArrayBuffer|String): input data. Strings will be
   *   converted to utf8 byte sequence.
   * - flush_mode (Number|Boolean): 0..6 for corresponding Z_NO_FLUSH..Z_TREE modes.
   *   See constants. Skipped or `false` means Z_NO_FLUSH, `true` means Z_FINISH.
   *
   * Sends input data to deflate pipe, generating [[Deflate#onData]] calls with
   * new compressed chunks. Returns `true` on success. The last data block must
   * have `flush_mode` Z_FINISH (or `true`). That will flush internal pending
   * buffers and call [[Deflate#onEnd]].
   *
   * On fail call [[Deflate#onEnd]] with error code and return false.
   *
   * ##### Example
   *
   * ```javascript
   * push(chunk, false); // push one of data chunks
   * ...
   * push(chunk, true);  // push last chunk
   * ```
   **/


  Deflate$1.prototype.push = function (data, flush_mode) {
    const strm = this.strm;
    const chunkSize = this.options.chunkSize;

    let status, _flush_mode;

    if (this.ended) {
      return false;
    }

    if (flush_mode === ~~flush_mode) _flush_mode = flush_mode;else _flush_mode = flush_mode === true ? Z_FINISH$2 : Z_NO_FLUSH$1; // Convert data if needed

    if (typeof data === 'string') {
      // If we need to compress text, change encoding to utf8.
      strm.input = strings.string2buf(data);
    } else if (toString$1.call(data) === '[object ArrayBuffer]') {
      strm.input = new Uint8Array(data);
    } else {
      strm.input = data;
    }

    strm.next_in = 0;
    strm.avail_in = strm.input.length;

    for (;;) {
      if (strm.avail_out === 0) {
        strm.output = new Uint8Array(chunkSize);
        strm.next_out = 0;
        strm.avail_out = chunkSize;
      } // Make sure avail_out > 6 to avoid repeating markers


      if ((_flush_mode === Z_SYNC_FLUSH || _flush_mode === Z_FULL_FLUSH) && strm.avail_out <= 6) {
        this.onData(strm.output.subarray(0, strm.next_out));
        strm.avail_out = 0;
        continue;
      }

      status = deflate_1$2.deflate(strm, _flush_mode); // Ended => flush and finish

      if (status === Z_STREAM_END$2) {
        if (strm.next_out > 0) {
          this.onData(strm.output.subarray(0, strm.next_out));
        }

        status = deflate_1$2.deflateEnd(this.strm);
        this.onEnd(status);
        this.ended = true;
        return status === Z_OK$2;
      } // Flush if out buffer full


      if (strm.avail_out === 0) {
        this.onData(strm.output);
        continue;
      } // Flush if requested and has data


      if (_flush_mode > 0 && strm.next_out > 0) {
        this.onData(strm.output.subarray(0, strm.next_out));
        strm.avail_out = 0;
        continue;
      }

      if (strm.avail_in === 0) break;
    }

    return true;
  };
  /**
   * Deflate#onData(chunk) -> Void
   * - chunk (Uint8Array): output data.
   *
   * By default, stores data blocks in `chunks[]` property and glue
   * those in `onEnd`. Override this handler, if you need another behaviour.
   **/


  Deflate$1.prototype.onData = function (chunk) {
    this.chunks.push(chunk);
  };
  /**
   * Deflate#onEnd(status) -> Void
   * - status (Number): deflate status. 0 (Z_OK) on success,
   *   other if not.
   *
   * Called once after you tell deflate that the input stream is
   * complete (Z_FINISH). By default - join collected chunks,
   * free memory and fill `results` / `err` properties.
   **/


  Deflate$1.prototype.onEnd = function (status) {
    // On success - join
    if (status === Z_OK$2) {
      this.result = common.flattenChunks(this.chunks);
    }

    this.chunks = [];
    this.err = status;
    this.msg = this.strm.msg;
  };
  /**
   * deflate(data[, options]) -> Uint8Array
   * - data (Uint8Array|String): input data to compress.
   * - options (Object): zlib deflate options.
   *
   * Compress `data` with deflate algorithm and `options`.
   *
   * Supported options are:
   *
   * - level
   * - windowBits
   * - memLevel
   * - strategy
   * - dictionary
   *
   * [http://zlib.net/manual.html#Advanced](http://zlib.net/manual.html#Advanced)
   * for more information on these.
   *
   * Sugar (options):
   *
   * - `raw` (Boolean) - say that we work with raw stream, if you don't wish to specify
   *   negative windowBits implicitly.
   *
   * ##### Example:
   *
   * ```javascript
   * const pako = require('pako')
   * const data = new Uint8Array([1,2,3,4,5,6,7,8,9]);
   *
   * console.log(pako.deflate(data));
   * ```
   **/


  function deflate$1(input, options) {
    const deflator = new Deflate$1(options);
    deflator.push(input, true); // That will never happens, if you don't cheat with options :)

    if (deflator.err) {
      throw deflator.msg || messages[deflator.err];
    }

    return deflator.result;
  }
  /**
   * deflateRaw(data[, options]) -> Uint8Array
   * - data (Uint8Array|String): input data to compress.
   * - options (Object): zlib deflate options.
   *
   * The same as [[deflate]], but creates raw data, without wrapper
   * (header and adler32 crc).
   **/


  function deflateRaw$1(input, options) {
    options = options || {};
    options.raw = true;
    return deflate$1(input, options);
  }
  /**
   * gzip(data[, options]) -> Uint8Array
   * - data (Uint8Array|String): input data to compress.
   * - options (Object): zlib deflate options.
   *
   * The same as [[deflate]], but create gzip wrapper instead of
   * deflate one.
   **/


  function gzip$1(input, options) {
    options = options || {};
    options.gzip = true;
    return deflate$1(input, options);
  }

  var Deflate_1$1 = Deflate$1;
  var deflate_2 = deflate$1;
  var deflateRaw_1$1 = deflateRaw$1;
  var gzip_1$1 = gzip$1;
  var constants$1 = constants$2;
  var deflate_1$1 = {
    Deflate: Deflate_1$1,
    deflate: deflate_2,
    deflateRaw: deflateRaw_1$1,
    gzip: gzip_1$1,
    constants: constants$1
  }; // (C) 1995-2013 Jean-loup Gailly and Mark Adler
  // (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin
  //
  // This software is provided 'as-is', without any express or implied
  // warranty. In no event will the authors be held liable for any damages
  // arising from the use of this software.
  //
  // Permission is granted to anyone to use this software for any purpose,
  // including commercial applications, and to alter it and redistribute it
  // freely, subject to the following restrictions:
  //
  // 1. The origin of this software must not be misrepresented; you must not
  //   claim that you wrote the original software. If you use this software
  //   in a product, an acknowledgment in the product documentation would be
  //   appreciated but is not required.
  // 2. Altered source versions must be plainly marked as such, and must not be
  //   misrepresented as being the original software.
  // 3. This notice may not be removed or altered from any source distribution.
  // See state defs from inflate.js

  const BAD$1 = 30;
  /* got a data error -- remain here until reset */

  const TYPE$1 = 12;
  /* i: waiting for type bits, including last-flag bit */

  /*
     Decode literal, length, and distance codes and write out the resulting
     literal and match bytes until either not enough input or output is
     available, an end-of-block is encountered, or a data error is encountered.
     When large enough input and output buffers are supplied to inflate(), for
     example, a 16K input buffer and a 64K output buffer, more than 95% of the
     inflate execution time is spent in this routine.

     Entry assumptions:

          state.mode === LEN
          strm.avail_in >= 6
          strm.avail_out >= 258
          start >= strm.avail_out
          state.bits < 8

     On return, state.mode is one of:

          LEN -- ran out of enough output space or enough available input
          TYPE -- reached end of block code, inflate() to interpret next block
          BAD -- error in block data

     Notes:

      - The maximum input bits used by a length/distance pair is 15 bits for the
        length code, 5 bits for the length extra, 15 bits for the distance code,
        and 13 bits for the distance extra.  This totals 48 bits, or six bytes.
        Therefore if strm.avail_in >= 6, then there is enough input to avoid
        checking for available input while decoding.

      - The maximum bytes that a single length/distance pair can output is 258
        bytes, which is the maximum length that can be coded.  inflate_fast()
        requires strm.avail_out >= 258 for each loop to avoid checking for
        output space.
   */

  var inffast = function inflate_fast(strm, start) {
    let _in;
    /* local strm.input */


    let last;
    /* have enough input while in < last */

    let _out;
    /* local strm.output */


    let beg;
    /* inflate()'s initial strm.output */

    let end;
    /* while out < end, enough space available */
    //#ifdef INFLATE_STRICT

    let dmax;
    /* maximum distance from zlib header */
    //#endif

    let wsize;
    /* window size or zero if not using window */

    let whave;
    /* valid bytes in the window */

    let wnext;
    /* window write index */
    // Use `s_window` instead `window`, avoid conflict with instrumentation tools

    let s_window;
    /* allocated sliding window, if wsize != 0 */

    let hold;
    /* local strm.hold */

    let bits;
    /* local strm.bits */

    let lcode;
    /* local strm.lencode */

    let dcode;
    /* local strm.distcode */

    let lmask;
    /* mask for first level of length codes */

    let dmask;
    /* mask for first level of distance codes */

    let here;
    /* retrieved table entry */

    let op;
    /* code bits, operation, extra bits, or */

    /*  window position, window bytes to copy */

    let len;
    /* match length, unused bytes */

    let dist;
    /* match distance */

    let from;
    /* where to copy match from */

    let from_source;
    let input, output; // JS specific, because we have no pointers

    /* copy state to local variables */

    const state = strm.state; //here = state.here;

    _in = strm.next_in;
    input = strm.input;
    last = _in + (strm.avail_in - 5);
    _out = strm.next_out;
    output = strm.output;
    beg = _out - (start - strm.avail_out);
    end = _out + (strm.avail_out - 257); //#ifdef INFLATE_STRICT

    dmax = state.dmax; //#endif

    wsize = state.wsize;
    whave = state.whave;
    wnext = state.wnext;
    s_window = state.window;
    hold = state.hold;
    bits = state.bits;
    lcode = state.lencode;
    dcode = state.distcode;
    lmask = (1 << state.lenbits) - 1;
    dmask = (1 << state.distbits) - 1;
    /* decode literals and length/distances until end-of-block or not enough
       input data or output space */

    top: do {
      if (bits < 15) {
        hold += input[_in++] << bits;
        bits += 8;
        hold += input[_in++] << bits;
        bits += 8;
      }

      here = lcode[hold & lmask];

      dolen: for (;;) {
        // Goto emulation
        op = here >>> 24
        /*here.bits*/
        ;
        hold >>>= op;
        bits -= op;
        op = here >>> 16 & 0xff
        /*here.op*/
        ;

        if (op === 0) {
          /* literal */
          //Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ?
          //        "inflate:         literal '%c'\n" :
          //        "inflate:         literal 0x%02x\n", here.val));
          output[_out++] = here & 0xffff
          /*here.val*/
          ;
        } else if (op & 16) {
          /* length base */
          len = here & 0xffff
          /*here.val*/
          ;
          op &= 15;
          /* number of extra bits */

          if (op) {
            if (bits < op) {
              hold += input[_in++] << bits;
              bits += 8;
            }

            len += hold & (1 << op) - 1;
            hold >>>= op;
            bits -= op;
          } //Tracevv((stderr, "inflate:         length %u\n", len));


          if (bits < 15) {
            hold += input[_in++] << bits;
            bits += 8;
            hold += input[_in++] << bits;
            bits += 8;
          }

          here = dcode[hold & dmask];

          dodist: for (;;) {
            // goto emulation
            op = here >>> 24
            /*here.bits*/
            ;
            hold >>>= op;
            bits -= op;
            op = here >>> 16 & 0xff
            /*here.op*/
            ;

            if (op & 16) {
              /* distance base */
              dist = here & 0xffff
              /*here.val*/
              ;
              op &= 15;
              /* number of extra bits */

              if (bits < op) {
                hold += input[_in++] << bits;
                bits += 8;

                if (bits < op) {
                  hold += input[_in++] << bits;
                  bits += 8;
                }
              }

              dist += hold & (1 << op) - 1; //#ifdef INFLATE_STRICT

              if (dist > dmax) {
                strm.msg = 'invalid distance too far back';
                state.mode = BAD$1;
                break top;
              } //#endif


              hold >>>= op;
              bits -= op; //Tracevv((stderr, "inflate:         distance %u\n", dist));

              op = _out - beg;
              /* max distance in output */

              if (dist > op) {
                /* see if copy from window */
                op = dist - op;
                /* distance back in window */

                if (op > whave) {
                  if (state.sane) {
                    strm.msg = 'invalid distance too far back';
                    state.mode = BAD$1;
                    break top;
                  } // (!) This block is disabled in zlib defaults,
                  // don't enable it for binary compatibility
                  //#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR
                  //                if (len <= op - whave) {
                  //                  do {
                  //                    output[_out++] = 0;
                  //                  } while (--len);
                  //                  continue top;
                  //                }
                  //                len -= op - whave;
                  //                do {
                  //                  output[_out++] = 0;
                  //                } while (--op > whave);
                  //                if (op === 0) {
                  //                  from = _out - dist;
                  //                  do {
                  //                    output[_out++] = output[from++];
                  //                  } while (--len);
                  //                  continue top;
                  //                }
                  //#endif

                }

                from = 0; // window index

                from_source = s_window;

                if (wnext === 0) {
                  /* very common case */
                  from += wsize - op;

                  if (op < len) {
                    /* some from window */
                    len -= op;

                    do {
                      output[_out++] = s_window[from++];
                    } while (--op);

                    from = _out - dist;
                    /* rest from output */

                    from_source = output;
                  }
                } else if (wnext < op) {
                  /* wrap around window */
                  from += wsize + wnext - op;
                  op -= wnext;

                  if (op < len) {
                    /* some from end of window */
                    len -= op;

                    do {
                      output[_out++] = s_window[from++];
                    } while (--op);

                    from = 0;

                    if (wnext < len) {
                      /* some from start of window */
                      op = wnext;
                      len -= op;

                      do {
                        output[_out++] = s_window[from++];
                      } while (--op);

                      from = _out - dist;
                      /* rest from output */

                      from_source = output;
                    }
                  }
                } else {
                  /* contiguous in window */
                  from += wnext - op;

                  if (op < len) {
                    /* some from window */
                    len -= op;

                    do {
                      output[_out++] = s_window[from++];
                    } while (--op);

                    from = _out - dist;
                    /* rest from output */

                    from_source = output;
                  }
                }

                while (len > 2) {
                  output[_out++] = from_source[from++];
                  output[_out++] = from_source[from++];
                  output[_out++] = from_source[from++];
                  len -= 3;
                }

                if (len) {
                  output[_out++] = from_source[from++];

                  if (len > 1) {
                    output[_out++] = from_source[from++];
                  }
                }
              } else {
                from = _out - dist;
                /* copy direct from output */

                do {
                  /* minimum length is three */
                  output[_out++] = output[from++];
                  output[_out++] = output[from++];
                  output[_out++] = output[from++];
                  len -= 3;
                } while (len > 2);

                if (len) {
                  output[_out++] = output[from++];

                  if (len > 1) {
                    output[_out++] = output[from++];
                  }
                }
              }
            } else if ((op & 64) === 0) {
              /* 2nd level distance code */
              here = dcode[(here & 0xffff) + (hold & (1 << op) - 1)];
              continue dodist;
            } else {
              strm.msg = 'invalid distance code';
              state.mode = BAD$1;
              break top;
            }

            break; // need to emulate goto via "continue"
          }
        } else if ((op & 64) === 0) {
          /* 2nd level length code */
          here = lcode[(here & 0xffff) + (hold & (1 << op) - 1)];
          continue dolen;
        } else if (op & 32) {
          /* end-of-block */
          //Tracevv((stderr, "inflate:         end of block\n"));
          state.mode = TYPE$1;
          break top;
        } else {
          strm.msg = 'invalid literal/length code';
          state.mode = BAD$1;
          break top;
        }

        break; // need to emulate goto via "continue"
      }
    } while (_in < last && _out < end);
    /* return unused bytes (on entry, bits < 8, so in won't go too far back) */


    len = bits >> 3;
    _in -= len;
    bits -= len << 3;
    hold &= (1 << bits) - 1;
    /* update state and return */

    strm.next_in = _in;
    strm.next_out = _out;
    strm.avail_in = _in < last ? 5 + (last - _in) : 5 - (_in - last);
    strm.avail_out = _out < end ? 257 + (end - _out) : 257 - (_out - end);
    state.hold = hold;
    state.bits = bits;
    return;
  }; // (C) 1995-2013 Jean-loup Gailly and Mark Adler
  // (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin
  //
  // This software is provided 'as-is', without any express or implied
  // warranty. In no event will the authors be held liable for any damages
  // arising from the use of this software.
  //
  // Permission is granted to anyone to use this software for any purpose,
  // including commercial applications, and to alter it and redistribute it
  // freely, subject to the following restrictions:
  //
  // 1. The origin of this software must not be misrepresented; you must not
  //   claim that you wrote the original software. If you use this software
  //   in a product, an acknowledgment in the product documentation would be
  //   appreciated but is not required.
  // 2. Altered source versions must be plainly marked as such, and must not be
  //   misrepresented as being the original software.
  // 3. This notice may not be removed or altered from any source distribution.


  const MAXBITS = 15;
  const ENOUGH_LENS$1 = 852;
  const ENOUGH_DISTS$1 = 592; //const ENOUGH = (ENOUGH_LENS+ENOUGH_DISTS);

  const CODES$1 = 0;
  const LENS$1 = 1;
  const DISTS$1 = 2;
  const lbase = new Uint16Array([
  /* Length codes 257..285 base */
  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]);
  const lext = new Uint8Array([
  /* Length codes 257..285 extra */
  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]);
  const dbase = new Uint16Array([
  /* Distance codes 0..29 base */
  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]);
  const dext = new Uint8Array([
  /* Distance codes 0..29 extra */
  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]);

  const inflate_table = (type, lens, lens_index, codes, table, table_index, work, opts) => {
    const bits = opts.bits; //here = opts.here; /* table entry for duplication */

    let len = 0;
    /* a code's length in bits */

    let sym = 0;
    /* index of code symbols */

    let min = 0,
        max = 0;
    /* minimum and maximum code lengths */

    let root = 0;
    /* number of index bits for root table */

    let curr = 0;
    /* number of index bits for current table */

    let drop = 0;
    /* code bits to drop for sub-table */

    let left = 0;
    /* number of prefix codes available */

    let used = 0;
    /* code entries in table used */

    let huff = 0;
    /* Huffman code */

    let incr;
    /* for incrementing code, index */

    let fill;
    /* index for replicating entries */

    let low;
    /* low bits for current root entry */

    let mask;
    /* mask for low root bits */

    let next;
    /* next available space in table */

    let base = null;
    /* base value table to use */

    let base_index = 0; //  let shoextra;    /* extra bits table to use */

    let end;
    /* use base and extra for symbol > end */

    const count = new Uint16Array(MAXBITS + 1); //[MAXBITS+1];    /* number of codes of each length */

    const offs = new Uint16Array(MAXBITS + 1); //[MAXBITS+1];     /* offsets in table for each length */

    let extra = null;
    let extra_index = 0;
    let here_bits, here_op, here_val;
    /*
     Process a set of code lengths to create a canonical Huffman code.  The
     code lengths are lens[0..codes-1].  Each length corresponds to the
     symbols 0..codes-1.  The Huffman code is generated by first sorting the
     symbols by length from short to long, and retaining the symbol order
     for codes with equal lengths.  Then the code starts with all zero bits
     for the first code of the shortest length, and the codes are integer
     increments for the same length, and zeros are appended as the length
     increases.  For the deflate format, these bits are stored backwards
     from their more natural integer increment ordering, and so when the
     decoding tables are built in the large loop below, the integer codes
     are incremented backwards.
      This routine assumes, but does not check, that all of the entries in
     lens[] are in the range 0..MAXBITS.  The caller must assure this.
     1..MAXBITS is interpreted as that code length.  zero means that that
     symbol does not occur in this code.
      The codes are sorted by computing a count of codes for each length,
     creating from that a table of starting indices for each length in the
     sorted table, and then entering the symbols in order in the sorted
     table.  The sorted table is work[], with that space being provided by
     the caller.
      The length counts are used for other purposes as well, i.e. finding
     the minimum and maximum length codes, determining if there are any
     codes at all, checking for a valid set of lengths, and looking ahead
     at length counts to determine sub-table sizes when building the
     decoding tables.
     */

    /* accumulate lengths for codes (assumes lens[] all in 0..MAXBITS) */

    for (len = 0; len <= MAXBITS; len++) {
      count[len] = 0;
    }

    for (sym = 0; sym < codes; sym++) {
      count[lens[lens_index + sym]]++;
    }
    /* bound code lengths, force root to be within code lengths */


    root = bits;

    for (max = MAXBITS; max >= 1; max--) {
      if (count[max] !== 0) {
        break;
      }
    }

    if (root > max) {
      root = max;
    }

    if (max === 0) {
      /* no symbols to code at all */
      //table.op[opts.table_index] = 64;  //here.op = (var char)64;    /* invalid code marker */
      //table.bits[opts.table_index] = 1;   //here.bits = (var char)1;
      //table.val[opts.table_index++] = 0;   //here.val = (var short)0;
      table[table_index++] = 1 << 24 | 64 << 16 | 0; //table.op[opts.table_index] = 64;
      //table.bits[opts.table_index] = 1;
      //table.val[opts.table_index++] = 0;

      table[table_index++] = 1 << 24 | 64 << 16 | 0;
      opts.bits = 1;
      return 0;
      /* no symbols, but wait for decoding to report error */
    }

    for (min = 1; min < max; min++) {
      if (count[min] !== 0) {
        break;
      }
    }

    if (root < min) {
      root = min;
    }
    /* check for an over-subscribed or incomplete set of lengths */


    left = 1;

    for (len = 1; len <= MAXBITS; len++) {
      left <<= 1;
      left -= count[len];

      if (left < 0) {
        return -1;
      }
      /* over-subscribed */

    }

    if (left > 0 && (type === CODES$1 || max !== 1)) {
      return -1;
      /* incomplete set */
    }
    /* generate offsets into symbol table for each length for sorting */


    offs[1] = 0;

    for (len = 1; len < MAXBITS; len++) {
      offs[len + 1] = offs[len] + count[len];
    }
    /* sort symbols by length, by symbol order within each length */


    for (sym = 0; sym < codes; sym++) {
      if (lens[lens_index + sym] !== 0) {
        work[offs[lens[lens_index + sym]]++] = sym;
      }
    }
    /*
     Create and fill in decoding tables.  In this loop, the table being
     filled is at next and has curr index bits.  The code being used is huff
     with length len.  That code is converted to an index by dropping drop
     bits off of the bottom.  For codes where len is less than drop + curr,
     those top drop + curr - len bits are incremented through all values to
     fill the table with replicated entries.
      root is the number of index bits for the root table.  When len exceeds
     root, sub-tables are created pointed to by the root entry with an index
     of the low root bits of huff.  This is saved in low to check for when a
     new sub-table should be started.  drop is zero when the root table is
     being filled, and drop is root when sub-tables are being filled.
      When a new sub-table is needed, it is necessary to look ahead in the
     code lengths to determine what size sub-table is needed.  The length
     counts are used for this, and so count[] is decremented as codes are
     entered in the tables.
      used keeps track of how many table entries have been allocated from the
     provided *table space.  It is checked for LENS and DIST tables against
     the constants ENOUGH_LENS and ENOUGH_DISTS to guard against changes in
     the initial root table size constants.  See the comments in inftrees.h
     for more information.
      sym increments through all symbols, and the loop terminates when
     all codes of length max, i.e. all codes, have been processed.  This
     routine permits incomplete codes, so another loop after this one fills
     in the rest of the decoding tables with invalid code markers.
     */

    /* set up for code type */
    // poor man optimization - use if-else instead of switch,
    // to avoid deopts in old v8


    if (type === CODES$1) {
      base = extra = work;
      /* dummy value--not used */

      end = 19;
    } else if (type === LENS$1) {
      base = lbase;
      base_index -= 257;
      extra = lext;
      extra_index -= 257;
      end = 256;
    } else {
      /* DISTS */
      base = dbase;
      extra = dext;
      end = -1;
    }
    /* initialize opts for loop */


    huff = 0;
    /* starting code */

    sym = 0;
    /* starting code symbol */

    len = min;
    /* starting code length */

    next = table_index;
    /* current table to fill in */

    curr = root;
    /* current table index bits */

    drop = 0;
    /* current bits to drop from code for index */

    low = -1;
    /* trigger new sub-table when len > root */

    used = 1 << root;
    /* use root table entries */

    mask = used - 1;
    /* mask for comparing low */

    /* check available table space */

    if (type === LENS$1 && used > ENOUGH_LENS$1 || type === DISTS$1 && used > ENOUGH_DISTS$1) {
      return 1;
    }
    /* process all codes and make table entries */


    for (;;) {
      /* create table entry */
      here_bits = len - drop;

      if (work[sym] < end) {
        here_op = 0;
        here_val = work[sym];
      } else if (work[sym] > end) {
        here_op = extra[extra_index + work[sym]];
        here_val = base[base_index + work[sym]];
      } else {
        here_op = 32 + 64;
        /* end of block */

        here_val = 0;
      }
      /* replicate for those indices with low len bits equal to huff */


      incr = 1 << len - drop;
      fill = 1 << curr;
      min = fill;
      /* save offset to next table */

      do {
        fill -= incr;
        table[next + (huff >> drop) + fill] = here_bits << 24 | here_op << 16 | here_val | 0;
      } while (fill !== 0);
      /* backwards increment the len-bit code huff */


      incr = 1 << len - 1;

      while (huff & incr) {
        incr >>= 1;
      }

      if (incr !== 0) {
        huff &= incr - 1;
        huff += incr;
      } else {
        huff = 0;
      }
      /* go to next symbol, update count, len */


      sym++;

      if (--count[len] === 0) {
        if (len === max) {
          break;
        }

        len = lens[lens_index + work[sym]];
      }
      /* create new sub-table if needed */


      if (len > root && (huff & mask) !== low) {
        /* if first time, transition to sub-tables */
        if (drop === 0) {
          drop = root;
        }
        /* increment past last table */


        next += min;
        /* here min is 1 << curr */

        /* determine length of next table */

        curr = len - drop;
        left = 1 << curr;

        while (curr + drop < max) {
          left -= count[curr + drop];

          if (left <= 0) {
            break;
          }

          curr++;
          left <<= 1;
        }
        /* check for enough space */


        used += 1 << curr;

        if (type === LENS$1 && used > ENOUGH_LENS$1 || type === DISTS$1 && used > ENOUGH_DISTS$1) {
          return 1;
        }
        /* point entry in root table to sub-table */


        low = huff & mask;
        /*table.op[low] = curr;
        table.bits[low] = root;
        table.val[low] = next - opts.table_index;*/

        table[low] = root << 24 | curr << 16 | next - table_index | 0;
      }
    }
    /* fill in remaining table entry if code is incomplete (guaranteed to have
     at most one remaining entry, since if the code is incomplete, the
     maximum code length that was allowed to get this far is one bit) */


    if (huff !== 0) {
      //table.op[next + huff] = 64;            /* invalid code marker */
      //table.bits[next + huff] = len - drop;
      //table.val[next + huff] = 0;
      table[next + huff] = len - drop << 24 | 64 << 16 | 0;
    }
    /* set return parameters */
    //opts.table_index += used;


    opts.bits = root;
    return 0;
  };

  var inftrees = inflate_table; // (C) 1995-2013 Jean-loup Gailly and Mark Adler
  // (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin
  //
  // This software is provided 'as-is', without any express or implied
  // warranty. In no event will the authors be held liable for any damages
  // arising from the use of this software.
  //
  // Permission is granted to anyone to use this software for any purpose,
  // including commercial applications, and to alter it and redistribute it
  // freely, subject to the following restrictions:
  //
  // 1. The origin of this software must not be misrepresented; you must not
  //   claim that you wrote the original software. If you use this software
  //   in a product, an acknowledgment in the product documentation would be
  //   appreciated but is not required.
  // 2. Altered source versions must be plainly marked as such, and must not be
  //   misrepresented as being the original software.
  // 3. This notice may not be removed or altered from any source distribution.

  const CODES = 0;
  const LENS = 1;
  const DISTS = 2;
  /* Public constants ==========================================================*/

  /* ===========================================================================*/

  const {
    Z_FINISH: Z_FINISH$1,
    Z_BLOCK,
    Z_TREES,
    Z_OK: Z_OK$1,
    Z_STREAM_END: Z_STREAM_END$1,
    Z_NEED_DICT: Z_NEED_DICT$1,
    Z_STREAM_ERROR: Z_STREAM_ERROR$1,
    Z_DATA_ERROR: Z_DATA_ERROR$1,
    Z_MEM_ERROR: Z_MEM_ERROR$1,
    Z_BUF_ERROR,
    Z_DEFLATED
  } = constants$2;
  /* STATES ====================================================================*/

  /* ===========================================================================*/

  const HEAD = 1;
  /* i: waiting for magic header */

  const FLAGS = 2;
  /* i: waiting for method and flags (gzip) */

  const TIME = 3;
  /* i: waiting for modification time (gzip) */

  const OS = 4;
  /* i: waiting for extra flags and operating system (gzip) */

  const EXLEN = 5;
  /* i: waiting for extra length (gzip) */

  const EXTRA = 6;
  /* i: waiting for extra bytes (gzip) */

  const NAME = 7;
  /* i: waiting for end of file name (gzip) */

  const COMMENT = 8;
  /* i: waiting for end of comment (gzip) */

  const HCRC = 9;
  /* i: waiting for header crc (gzip) */

  const DICTID = 10;
  /* i: waiting for dictionary check value */

  const DICT = 11;
  /* waiting for inflateSetDictionary() call */

  const TYPE = 12;
  /* i: waiting for type bits, including last-flag bit */

  const TYPEDO = 13;
  /* i: same, but skip check to exit inflate on new block */

  const STORED = 14;
  /* i: waiting for stored size (length and complement) */

  const COPY_ = 15;
  /* i/o: same as COPY below, but only first time in */

  const COPY = 16;
  /* i/o: waiting for input or output to copy stored block */

  const TABLE = 17;
  /* i: waiting for dynamic block table lengths */

  const LENLENS = 18;
  /* i: waiting for code length code lengths */

  const CODELENS = 19;
  /* i: waiting for length/lit and distance code lengths */

  const LEN_ = 20;
  /* i: same as LEN below, but only first time in */

  const LEN = 21;
  /* i: waiting for length/lit/eob code */

  const LENEXT = 22;
  /* i: waiting for length extra bits */

  const DIST = 23;
  /* i: waiting for distance code */

  const DISTEXT = 24;
  /* i: waiting for distance extra bits */

  const MATCH = 25;
  /* o: waiting for output space to copy string */

  const LIT = 26;
  /* o: waiting for output space to write literal */

  const CHECK = 27;
  /* i: waiting for 32-bit check value */

  const LENGTH = 28;
  /* i: waiting for 32-bit length (gzip) */

  const DONE = 29;
  /* finished check, done -- remain here until reset */

  const BAD = 30;
  /* got a data error -- remain here until reset */

  const MEM = 31;
  /* got an inflate() memory error -- remain here until reset */

  const SYNC = 32;
  /* looking for synchronization bytes to restart inflate() */

  /* ===========================================================================*/

  const ENOUGH_LENS = 852;
  const ENOUGH_DISTS = 592; //const ENOUGH =  (ENOUGH_LENS+ENOUGH_DISTS);

  const MAX_WBITS = 15;
  /* 32K LZ77 window */

  const DEF_WBITS = MAX_WBITS;

  const zswap32 = q => {
    return (q >>> 24 & 0xff) + (q >>> 8 & 0xff00) + ((q & 0xff00) << 8) + ((q & 0xff) << 24);
  };

  function InflateState() {
    this.mode = 0;
    /* current inflate mode */

    this.last = false;
    /* true if processing last block */

    this.wrap = 0;
    /* bit 0 true for zlib, bit 1 true for gzip */

    this.havedict = false;
    /* true if dictionary provided */

    this.flags = 0;
    /* gzip header method and flags (0 if zlib) */

    this.dmax = 0;
    /* zlib header max distance (INFLATE_STRICT) */

    this.check = 0;
    /* protected copy of check value */

    this.total = 0;
    /* protected copy of output count */
    // TODO: may be {}

    this.head = null;
    /* where to save gzip header information */

    /* sliding window */

    this.wbits = 0;
    /* log base 2 of requested window size */

    this.wsize = 0;
    /* window size or zero if not using window */

    this.whave = 0;
    /* valid bytes in the window */

    this.wnext = 0;
    /* window write index */

    this.window = null;
    /* allocated sliding window, if needed */

    /* bit accumulator */

    this.hold = 0;
    /* input bit accumulator */

    this.bits = 0;
    /* number of bits in "in" */

    /* for string and stored block copying */

    this.length = 0;
    /* literal or length of data to copy */

    this.offset = 0;
    /* distance back to copy string from */

    /* for table and code decoding */

    this.extra = 0;
    /* extra bits needed */

    /* fixed and dynamic code tables */

    this.lencode = null;
    /* starting table for length/literal codes */

    this.distcode = null;
    /* starting table for distance codes */

    this.lenbits = 0;
    /* index bits for lencode */

    this.distbits = 0;
    /* index bits for distcode */

    /* dynamic table building */

    this.ncode = 0;
    /* number of code length code lengths */

    this.nlen = 0;
    /* number of length code lengths */

    this.ndist = 0;
    /* number of distance code lengths */

    this.have = 0;
    /* number of code lengths in lens[] */

    this.next = null;
    /* next available space in codes[] */

    this.lens = new Uint16Array(320);
    /* temporary storage for code lengths */

    this.work = new Uint16Array(288);
    /* work area for code table building */

    /*
     because we don't have pointers in js, we use lencode and distcode directly
     as buffers so we don't need codes
    */
    //this.codes = new Int32Array(ENOUGH);       /* space for code tables */

    this.lendyn = null;
    /* dynamic table for length/literal codes (JS specific) */

    this.distdyn = null;
    /* dynamic table for distance codes (JS specific) */

    this.sane = 0;
    /* if false, allow invalid distance too far */

    this.back = 0;
    /* bits back of last unprocessed length/lit */

    this.was = 0;
    /* initial length of match */
  }

  const inflateResetKeep = strm => {
    if (!strm || !strm.state) {
      return Z_STREAM_ERROR$1;
    }

    const state = strm.state;
    strm.total_in = strm.total_out = state.total = 0;
    strm.msg = '';
    /*Z_NULL*/

    if (state.wrap) {
      /* to support ill-conceived Java test suite */
      strm.adler = state.wrap & 1;
    }

    state.mode = HEAD;
    state.last = 0;
    state.havedict = 0;
    state.dmax = 32768;
    state.head = null
    /*Z_NULL*/
    ;
    state.hold = 0;
    state.bits = 0; //state.lencode = state.distcode = state.next = state.codes;

    state.lencode = state.lendyn = new Int32Array(ENOUGH_LENS);
    state.distcode = state.distdyn = new Int32Array(ENOUGH_DISTS);
    state.sane = 1;
    state.back = -1; //Tracev((stderr, "inflate: reset\n"));

    return Z_OK$1;
  };

  const inflateReset = strm => {
    if (!strm || !strm.state) {
      return Z_STREAM_ERROR$1;
    }

    const state = strm.state;
    state.wsize = 0;
    state.whave = 0;
    state.wnext = 0;
    return inflateResetKeep(strm);
  };

  const inflateReset2 = (strm, windowBits) => {
    let wrap;
    /* get the state */

    if (!strm || !strm.state) {
      return Z_STREAM_ERROR$1;
    }

    const state = strm.state;
    /* extract wrap request from windowBits parameter */

    if (windowBits < 0) {
      wrap = 0;
      windowBits = -windowBits;
    } else {
      wrap = (windowBits >> 4) + 1;

      if (windowBits < 48) {
        windowBits &= 15;
      }
    }
    /* set number of window bits, free window if different */


    if (windowBits && (windowBits < 8 || windowBits > 15)) {
      return Z_STREAM_ERROR$1;
    }

    if (state.window !== null && state.wbits !== windowBits) {
      state.window = null;
    }
    /* update state and reset the rest of it */


    state.wrap = wrap;
    state.wbits = windowBits;
    return inflateReset(strm);
  };

  const inflateInit2 = (strm, windowBits) => {
    if (!strm) {
      return Z_STREAM_ERROR$1;
    } //strm.msg = Z_NULL;                 /* in case we return an error */


    const state = new InflateState(); //if (state === Z_NULL) return Z_MEM_ERROR;
    //Tracev((stderr, "inflate: allocated\n"));

    strm.state = state;
    state.window = null
    /*Z_NULL*/
    ;
    const ret = inflateReset2(strm, windowBits);

    if (ret !== Z_OK$1) {
      strm.state = null
      /*Z_NULL*/
      ;
    }

    return ret;
  };

  const inflateInit = strm => {
    return inflateInit2(strm, DEF_WBITS);
  };
  /*
   Return state with length and distance decoding tables and index sizes set to
   fixed code decoding.  Normally this returns fixed tables from inffixed.h.
   If BUILDFIXED is defined, then instead this routine builds the tables the
   first time it's called, and returns those tables the first time and
   thereafter.  This reduces the size of the code by about 2K bytes, in
   exchange for a little execution time.  However, BUILDFIXED should not be
   used for threaded applications, since the rewriting of the tables and virgin
   may not be thread-safe.
   */


  let virgin = true;
  let lenfix, distfix; // We have no pointers in JS, so keep tables separate

  const fixedtables = state => {
    /* build fixed huffman tables if first call (may not be thread safe) */
    if (virgin) {
      lenfix = new Int32Array(512);
      distfix = new Int32Array(32);
      /* literal/length table */

      let sym = 0;

      while (sym < 144) {
        state.lens[sym++] = 8;
      }

      while (sym < 256) {
        state.lens[sym++] = 9;
      }

      while (sym < 280) {
        state.lens[sym++] = 7;
      }

      while (sym < 288) {
        state.lens[sym++] = 8;
      }

      inftrees(LENS, state.lens, 0, 288, lenfix, 0, state.work, {
        bits: 9
      });
      /* distance table */

      sym = 0;

      while (sym < 32) {
        state.lens[sym++] = 5;
      }

      inftrees(DISTS, state.lens, 0, 32, distfix, 0, state.work, {
        bits: 5
      });
      /* do this just once */

      virgin = false;
    }

    state.lencode = lenfix;
    state.lenbits = 9;
    state.distcode = distfix;
    state.distbits = 5;
  };
  /*
   Update the window with the last wsize (normally 32K) bytes written before
   returning.  If window does not exist yet, create it.  This is only called
   when a window is already in use, or when output has been written during this
   inflate call, but the end of the deflate stream has not been reached yet.
   It is also called to create a window for dictionary data when a dictionary
   is loaded.

   Providing output buffers larger than 32K to inflate() should provide a speed
   advantage, since only the last 32K of output is copied to the sliding window
   upon return from inflate(), and since all distances after the first 32K of
   output will fall in the output data, making match copies simpler and faster.
   The advantage may be dependent on the size of the processor's data caches.
   */


  const updatewindow = (strm, src, end, copy) => {
    let dist;
    const state = strm.state;
    /* if it hasn't been done already, allocate space for the window */

    if (state.window === null) {
      state.wsize = 1 << state.wbits;
      state.wnext = 0;
      state.whave = 0;
      state.window = new Uint8Array(state.wsize);
    }
    /* copy state->wsize or less output bytes into the circular window */


    if (copy >= state.wsize) {
      state.window.set(src.subarray(end - state.wsize, end), 0);
      state.wnext = 0;
      state.whave = state.wsize;
    } else {
      dist = state.wsize - state.wnext;

      if (dist > copy) {
        dist = copy;
      } //zmemcpy(state->window + state->wnext, end - copy, dist);


      state.window.set(src.subarray(end - copy, end - copy + dist), state.wnext);
      copy -= dist;

      if (copy) {
        //zmemcpy(state->window, end - copy, copy);
        state.window.set(src.subarray(end - copy, end), 0);
        state.wnext = copy;
        state.whave = state.wsize;
      } else {
        state.wnext += dist;

        if (state.wnext === state.wsize) {
          state.wnext = 0;
        }

        if (state.whave < state.wsize) {
          state.whave += dist;
        }
      }
    }

    return 0;
  };

  const inflate$2 = (strm, flush) => {
    let state;
    let input, output; // input/output buffers

    let next;
    /* next input INDEX */

    let put;
    /* next output INDEX */

    let have, left;
    /* available input and output */

    let hold;
    /* bit buffer */

    let bits;
    /* bits in bit buffer */

    let _in, _out;
    /* save starting available input and output */


    let copy;
    /* number of stored or match bytes to copy */

    let from;
    /* where to copy match bytes from */

    let from_source;
    let here = 0;
    /* current decoding table entry */

    let here_bits, here_op, here_val; // paked "here" denormalized (JS specific)
    //let last;                   /* parent table entry */

    let last_bits, last_op, last_val; // paked "last" denormalized (JS specific)

    let len;
    /* length to copy for repeats, bits to drop */

    let ret;
    /* return code */

    const hbuf = new Uint8Array(4);
    /* buffer for gzip header crc calculation */

    let opts;
    let n; // temporary variable for NEED_BITS

    const order =
    /* permutation of code lengths */
    new Uint8Array([16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]);

    if (!strm || !strm.state || !strm.output || !strm.input && strm.avail_in !== 0) {
      return Z_STREAM_ERROR$1;
    }

    state = strm.state;

    if (state.mode === TYPE) {
      state.mode = TYPEDO;
    }
    /* skip check */
    //--- LOAD() ---


    put = strm.next_out;
    output = strm.output;
    left = strm.avail_out;
    next = strm.next_in;
    input = strm.input;
    have = strm.avail_in;
    hold = state.hold;
    bits = state.bits; //---

    _in = have;
    _out = left;
    ret = Z_OK$1;

    inf_leave: // goto emulation
    for (;;) {
      switch (state.mode) {
        case HEAD:
          if (state.wrap === 0) {
            state.mode = TYPEDO;
            break;
          } //=== NEEDBITS(16);


          while (bits < 16) {
            if (have === 0) {
              break inf_leave;
            }

            have--;
            hold += input[next++] << bits;
            bits += 8;
          } //===//


          if (state.wrap & 2 && hold === 0x8b1f) {
            /* gzip header */
            state.check = 0
            /*crc32(0L, Z_NULL, 0)*/
            ; //=== CRC2(state.check, hold);

            hbuf[0] = hold & 0xff;
            hbuf[1] = hold >>> 8 & 0xff;
            state.check = crc32_1(state.check, hbuf, 2, 0); //===//
            //=== INITBITS();

            hold = 0;
            bits = 0; //===//

            state.mode = FLAGS;
            break;
          }

          state.flags = 0;
          /* expect zlib header */

          if (state.head) {
            state.head.done = false;
          }

          if (!(state.wrap & 1) ||
          /* check if zlib header allowed */
          (((hold & 0xff) << 8) + (hold >> 8)) % 31) {
            strm.msg = 'incorrect header check';
            state.mode = BAD;
            break;
          }

          if ((hold & 0x0f) !== Z_DEFLATED) {
            strm.msg = 'unknown compression method';
            state.mode = BAD;
            break;
          } //--- DROPBITS(4) ---//


          hold >>>= 4;
          bits -= 4; //---//

          len = (hold & 0x0f) + 8;

          if (state.wbits === 0) {
            state.wbits = len;
          } else if (len > state.wbits) {
            strm.msg = 'invalid window size';
            state.mode = BAD;
            break;
          } // !!! pako patch. Force use `options.windowBits` if passed.
          // Required to always use max window size by default.


          state.dmax = 1 << state.wbits; //state.dmax = 1 << len;
          //Tracev((stderr, "inflate:   zlib header ok\n"));

          strm.adler = state.check = 1
          /*adler32(0L, Z_NULL, 0)*/
          ;
          state.mode = hold & 0x200 ? DICTID : TYPE; //=== INITBITS();

          hold = 0;
          bits = 0; //===//

          break;

        case FLAGS:
          //=== NEEDBITS(16); */
          while (bits < 16) {
            if (have === 0) {
              break inf_leave;
            }

            have--;
            hold += input[next++] << bits;
            bits += 8;
          } //===//


          state.flags = hold;

          if ((state.flags & 0xff) !== Z_DEFLATED) {
            strm.msg = 'unknown compression method';
            state.mode = BAD;
            break;
          }

          if (state.flags & 0xe000) {
            strm.msg = 'unknown header flags set';
            state.mode = BAD;
            break;
          }

          if (state.head) {
            state.head.text = hold >> 8 & 1;
          }

          if (state.flags & 0x0200) {
            //=== CRC2(state.check, hold);
            hbuf[0] = hold & 0xff;
            hbuf[1] = hold >>> 8 & 0xff;
            state.check = crc32_1(state.check, hbuf, 2, 0); //===//
          } //=== INITBITS();


          hold = 0;
          bits = 0; //===//

          state.mode = TIME;

        /* falls through */

        case TIME:
          //=== NEEDBITS(32); */
          while (bits < 32) {
            if (have === 0) {
              break inf_leave;
            }

            have--;
            hold += input[next++] << bits;
            bits += 8;
          } //===//


          if (state.head) {
            state.head.time = hold;
          }

          if (state.flags & 0x0200) {
            //=== CRC4(state.check, hold)
            hbuf[0] = hold & 0xff;
            hbuf[1] = hold >>> 8 & 0xff;
            hbuf[2] = hold >>> 16 & 0xff;
            hbuf[3] = hold >>> 24 & 0xff;
            state.check = crc32_1(state.check, hbuf, 4, 0); //===
          } //=== INITBITS();


          hold = 0;
          bits = 0; //===//

          state.mode = OS;

        /* falls through */

        case OS:
          //=== NEEDBITS(16); */
          while (bits < 16) {
            if (have === 0) {
              break inf_leave;
            }

            have--;
            hold += input[next++] << bits;
            bits += 8;
          } //===//


          if (state.head) {
            state.head.xflags = hold & 0xff;
            state.head.os = hold >> 8;
          }

          if (state.flags & 0x0200) {
            //=== CRC2(state.check, hold);
            hbuf[0] = hold & 0xff;
            hbuf[1] = hold >>> 8 & 0xff;
            state.check = crc32_1(state.check, hbuf, 2, 0); //===//
          } //=== INITBITS();


          hold = 0;
          bits = 0; //===//

          state.mode = EXLEN;

        /* falls through */

        case EXLEN:
          if (state.flags & 0x0400) {
            //=== NEEDBITS(16); */
            while (bits < 16) {
              if (have === 0) {
                break inf_leave;
              }

              have--;
              hold += input[next++] << bits;
              bits += 8;
            } //===//


            state.length = hold;

            if (state.head) {
              state.head.extra_len = hold;
            }

            if (state.flags & 0x0200) {
              //=== CRC2(state.check, hold);
              hbuf[0] = hold & 0xff;
              hbuf[1] = hold >>> 8 & 0xff;
              state.check = crc32_1(state.check, hbuf, 2, 0); //===//
            } //=== INITBITS();


            hold = 0;
            bits = 0; //===//
          } else if (state.head) {
            state.head.extra = null
            /*Z_NULL*/
            ;
          }

          state.mode = EXTRA;

        /* falls through */

        case EXTRA:
          if (state.flags & 0x0400) {
            copy = state.length;

            if (copy > have) {
              copy = have;
            }

            if (copy) {
              if (state.head) {
                len = state.head.extra_len - state.length;

                if (!state.head.extra) {
                  // Use untyped array for more convenient processing later
                  state.head.extra = new Uint8Array(state.head.extra_len);
                }

                state.head.extra.set(input.subarray(next, // extra field is limited to 65536 bytes
                // - no need for additional size check
                next + copy),
                /*len + copy > state.head.extra_max - len ? state.head.extra_max : copy,*/
                len); //zmemcpy(state.head.extra + len, next,
                //        len + copy > state.head.extra_max ?
                //        state.head.extra_max - len : copy);
              }

              if (state.flags & 0x0200) {
                state.check = crc32_1(state.check, input, copy, next);
              }

              have -= copy;
              next += copy;
              state.length -= copy;
            }

            if (state.length) {
              break inf_leave;
            }
          }

          state.length = 0;
          state.mode = NAME;

        /* falls through */

        case NAME:
          if (state.flags & 0x0800) {
            if (have === 0) {
              break inf_leave;
            }

            copy = 0;

            do {
              // TODO: 2 or 1 bytes?
              len = input[next + copy++];
              /* use constant limit because in js we should not preallocate memory */

              if (state.head && len && state.length < 65536
              /*state.head.name_max*/
              ) {
                state.head.name += String.fromCharCode(len);
              }
            } while (len && copy < have);

            if (state.flags & 0x0200) {
              state.check = crc32_1(state.check, input, copy, next);
            }

            have -= copy;
            next += copy;

            if (len) {
              break inf_leave;
            }
          } else if (state.head) {
            state.head.name = null;
          }

          state.length = 0;
          state.mode = COMMENT;

        /* falls through */

        case COMMENT:
          if (state.flags & 0x1000) {
            if (have === 0) {
              break inf_leave;
            }

            copy = 0;

            do {
              len = input[next + copy++];
              /* use constant limit because in js we should not preallocate memory */

              if (state.head && len && state.length < 65536
              /*state.head.comm_max*/
              ) {
                state.head.comment += String.fromCharCode(len);
              }
            } while (len && copy < have);

            if (state.flags & 0x0200) {
              state.check = crc32_1(state.check, input, copy, next);
            }

            have -= copy;
            next += copy;

            if (len) {
              break inf_leave;
            }
          } else if (state.head) {
            state.head.comment = null;
          }

          state.mode = HCRC;

        /* falls through */

        case HCRC:
          if (state.flags & 0x0200) {
            //=== NEEDBITS(16); */
            while (bits < 16) {
              if (have === 0) {
                break inf_leave;
              }

              have--;
              hold += input[next++] << bits;
              bits += 8;
            } //===//


            if (hold !== (state.check & 0xffff)) {
              strm.msg = 'header crc mismatch';
              state.mode = BAD;
              break;
            } //=== INITBITS();


            hold = 0;
            bits = 0; //===//
          }

          if (state.head) {
            state.head.hcrc = state.flags >> 9 & 1;
            state.head.done = true;
          }

          strm.adler = state.check = 0;
          state.mode = TYPE;
          break;

        case DICTID:
          //=== NEEDBITS(32); */
          while (bits < 32) {
            if (have === 0) {
              break inf_leave;
            }

            have--;
            hold += input[next++] << bits;
            bits += 8;
          } //===//


          strm.adler = state.check = zswap32(hold); //=== INITBITS();

          hold = 0;
          bits = 0; //===//

          state.mode = DICT;

        /* falls through */

        case DICT:
          if (state.havedict === 0) {
            //--- RESTORE() ---
            strm.next_out = put;
            strm.avail_out = left;
            strm.next_in = next;
            strm.avail_in = have;
            state.hold = hold;
            state.bits = bits; //---

            return Z_NEED_DICT$1;
          }

          strm.adler = state.check = 1
          /*adler32(0L, Z_NULL, 0)*/
          ;
          state.mode = TYPE;

        /* falls through */

        case TYPE:
          if (flush === Z_BLOCK || flush === Z_TREES) {
            break inf_leave;
          }

        /* falls through */

        case TYPEDO:
          if (state.last) {
            //--- BYTEBITS() ---//
            hold >>>= bits & 7;
            bits -= bits & 7; //---//

            state.mode = CHECK;
            break;
          } //=== NEEDBITS(3); */


          while (bits < 3) {
            if (have === 0) {
              break inf_leave;
            }

            have--;
            hold += input[next++] << bits;
            bits += 8;
          } //===//


          state.last = hold & 0x01
          /*BITS(1)*/
          ; //--- DROPBITS(1) ---//

          hold >>>= 1;
          bits -= 1; //---//

          switch (hold & 0x03) {
            case 0:
              /* stored block */
              //Tracev((stderr, "inflate:     stored block%s\n",
              //        state.last ? " (last)" : ""));
              state.mode = STORED;
              break;

            case 1:
              /* fixed block */
              fixedtables(state); //Tracev((stderr, "inflate:     fixed codes block%s\n",
              //        state.last ? " (last)" : ""));

              state.mode = LEN_;
              /* decode codes */

              if (flush === Z_TREES) {
                //--- DROPBITS(2) ---//
                hold >>>= 2;
                bits -= 2; //---//

                break inf_leave;
              }

              break;

            case 2:
              /* dynamic block */
              //Tracev((stderr, "inflate:     dynamic codes block%s\n",
              //        state.last ? " (last)" : ""));
              state.mode = TABLE;
              break;

            case 3:
              strm.msg = 'invalid block type';
              state.mode = BAD;
          } //--- DROPBITS(2) ---//


          hold >>>= 2;
          bits -= 2; //---//

          break;

        case STORED:
          //--- BYTEBITS() ---// /* go to byte boundary */
          hold >>>= bits & 7;
          bits -= bits & 7; //---//
          //=== NEEDBITS(32); */

          while (bits < 32) {
            if (have === 0) {
              break inf_leave;
            }

            have--;
            hold += input[next++] << bits;
            bits += 8;
          } //===//


          if ((hold & 0xffff) !== (hold >>> 16 ^ 0xffff)) {
            strm.msg = 'invalid stored block lengths';
            state.mode = BAD;
            break;
          }

          state.length = hold & 0xffff; //Tracev((stderr, "inflate:       stored length %u\n",
          //        state.length));
          //=== INITBITS();

          hold = 0;
          bits = 0; //===//

          state.mode = COPY_;

          if (flush === Z_TREES) {
            break inf_leave;
          }

        /* falls through */

        case COPY_:
          state.mode = COPY;

        /* falls through */

        case COPY:
          copy = state.length;

          if (copy) {
            if (copy > have) {
              copy = have;
            }

            if (copy > left) {
              copy = left;
            }

            if (copy === 0) {
              break inf_leave;
            } //--- zmemcpy(put, next, copy); ---


            output.set(input.subarray(next, next + copy), put); //---//

            have -= copy;
            next += copy;
            left -= copy;
            put += copy;
            state.length -= copy;
            break;
          } //Tracev((stderr, "inflate:       stored end\n"));


          state.mode = TYPE;
          break;

        case TABLE:
          //=== NEEDBITS(14); */
          while (bits < 14) {
            if (have === 0) {
              break inf_leave;
            }

            have--;
            hold += input[next++] << bits;
            bits += 8;
          } //===//


          state.nlen = (hold & 0x1f) + 257; //--- DROPBITS(5) ---//

          hold >>>= 5;
          bits -= 5; //---//

          state.ndist = (hold & 0x1f) + 1; //--- DROPBITS(5) ---//

          hold >>>= 5;
          bits -= 5; //---//

          state.ncode = (hold & 0x0f) + 4; //--- DROPBITS(4) ---//

          hold >>>= 4;
          bits -= 4; //---//
          //#ifndef PKZIP_BUG_WORKAROUND

          if (state.nlen > 286 || state.ndist > 30) {
            strm.msg = 'too many length or distance symbols';
            state.mode = BAD;
            break;
          } //#endif
          //Tracev((stderr, "inflate:       table sizes ok\n"));


          state.have = 0;
          state.mode = LENLENS;

        /* falls through */

        case LENLENS:
          while (state.have < state.ncode) {
            //=== NEEDBITS(3);
            while (bits < 3) {
              if (have === 0) {
                break inf_leave;
              }

              have--;
              hold += input[next++] << bits;
              bits += 8;
            } //===//


            state.lens[order[state.have++]] = hold & 0x07; //BITS(3);
            //--- DROPBITS(3) ---//

            hold >>>= 3;
            bits -= 3; //---//
          }

          while (state.have < 19) {
            state.lens[order[state.have++]] = 0;
          } // We have separate tables & no pointers. 2 commented lines below not needed.
          //state.next = state.codes;
          //state.lencode = state.next;
          // Switch to use dynamic table


          state.lencode = state.lendyn;
          state.lenbits = 7;
          opts = {
            bits: state.lenbits
          };
          ret = inftrees(CODES, state.lens, 0, 19, state.lencode, 0, state.work, opts);
          state.lenbits = opts.bits;

          if (ret) {
            strm.msg = 'invalid code lengths set';
            state.mode = BAD;
            break;
          } //Tracev((stderr, "inflate:       code lengths ok\n"));


          state.have = 0;
          state.mode = CODELENS;

        /* falls through */

        case CODELENS:
          while (state.have < state.nlen + state.ndist) {
            for (;;) {
              here = state.lencode[hold & (1 << state.lenbits) - 1];
              /*BITS(state.lenbits)*/

              here_bits = here >>> 24;
              here_op = here >>> 16 & 0xff;
              here_val = here & 0xffff;

              if (here_bits <= bits) {
                break;
              } //--- PULLBYTE() ---//


              if (have === 0) {
                break inf_leave;
              }

              have--;
              hold += input[next++] << bits;
              bits += 8; //---//
            }

            if (here_val < 16) {
              //--- DROPBITS(here.bits) ---//
              hold >>>= here_bits;
              bits -= here_bits; //---//

              state.lens[state.have++] = here_val;
            } else {
              if (here_val === 16) {
                //=== NEEDBITS(here.bits + 2);
                n = here_bits + 2;

                while (bits < n) {
                  if (have === 0) {
                    break inf_leave;
                  }

                  have--;
                  hold += input[next++] << bits;
                  bits += 8;
                } //===//
                //--- DROPBITS(here.bits) ---//


                hold >>>= here_bits;
                bits -= here_bits; //---//

                if (state.have === 0) {
                  strm.msg = 'invalid bit length repeat';
                  state.mode = BAD;
                  break;
                }

                len = state.lens[state.have - 1];
                copy = 3 + (hold & 0x03); //BITS(2);
                //--- DROPBITS(2) ---//

                hold >>>= 2;
                bits -= 2; //---//
              } else if (here_val === 17) {
                //=== NEEDBITS(here.bits + 3);
                n = here_bits + 3;

                while (bits < n) {
                  if (have === 0) {
                    break inf_leave;
                  }

                  have--;
                  hold += input[next++] << bits;
                  bits += 8;
                } //===//
                //--- DROPBITS(here.bits) ---//


                hold >>>= here_bits;
                bits -= here_bits; //---//

                len = 0;
                copy = 3 + (hold & 0x07); //BITS(3);
                //--- DROPBITS(3) ---//

                hold >>>= 3;
                bits -= 3; //---//
              } else {
                //=== NEEDBITS(here.bits + 7);
                n = here_bits + 7;

                while (bits < n) {
                  if (have === 0) {
                    break inf_leave;
                  }

                  have--;
                  hold += input[next++] << bits;
                  bits += 8;
                } //===//
                //--- DROPBITS(here.bits) ---//


                hold >>>= here_bits;
                bits -= here_bits; //---//

                len = 0;
                copy = 11 + (hold & 0x7f); //BITS(7);
                //--- DROPBITS(7) ---//

                hold >>>= 7;
                bits -= 7; //---//
              }

              if (state.have + copy > state.nlen + state.ndist) {
                strm.msg = 'invalid bit length repeat';
                state.mode = BAD;
                break;
              }

              while (copy--) {
                state.lens[state.have++] = len;
              }
            }
          }
          /* handle error breaks in while */


          if (state.mode === BAD) {
            break;
          }
          /* check for end-of-block code (better have one) */


          if (state.lens[256] === 0) {
            strm.msg = 'invalid code -- missing end-of-block';
            state.mode = BAD;
            break;
          }
          /* build code tables -- note: do not change the lenbits or distbits
             values here (9 and 6) without reading the comments in inftrees.h
             concerning the ENOUGH constants, which depend on those values */


          state.lenbits = 9;
          opts = {
            bits: state.lenbits
          };
          ret = inftrees(LENS, state.lens, 0, state.nlen, state.lencode, 0, state.work, opts); // We have separate tables & no pointers. 2 commented lines below not needed.
          // state.next_index = opts.table_index;

          state.lenbits = opts.bits; // state.lencode = state.next;

          if (ret) {
            strm.msg = 'invalid literal/lengths set';
            state.mode = BAD;
            break;
          }

          state.distbits = 6; //state.distcode.copy(state.codes);
          // Switch to use dynamic table

          state.distcode = state.distdyn;
          opts = {
            bits: state.distbits
          };
          ret = inftrees(DISTS, state.lens, state.nlen, state.ndist, state.distcode, 0, state.work, opts); // We have separate tables & no pointers. 2 commented lines below not needed.
          // state.next_index = opts.table_index;

          state.distbits = opts.bits; // state.distcode = state.next;

          if (ret) {
            strm.msg = 'invalid distances set';
            state.mode = BAD;
            break;
          } //Tracev((stderr, 'inflate:       codes ok\n'));


          state.mode = LEN_;

          if (flush === Z_TREES) {
            break inf_leave;
          }

        /* falls through */

        case LEN_:
          state.mode = LEN;

        /* falls through */

        case LEN:
          if (have >= 6 && left >= 258) {
            //--- RESTORE() ---
            strm.next_out = put;
            strm.avail_out = left;
            strm.next_in = next;
            strm.avail_in = have;
            state.hold = hold;
            state.bits = bits; //---

            inffast(strm, _out); //--- LOAD() ---

            put = strm.next_out;
            output = strm.output;
            left = strm.avail_out;
            next = strm.next_in;
            input = strm.input;
            have = strm.avail_in;
            hold = state.hold;
            bits = state.bits; //---

            if (state.mode === TYPE) {
              state.back = -1;
            }

            break;
          }

          state.back = 0;

          for (;;) {
            here = state.lencode[hold & (1 << state.lenbits) - 1];
            /*BITS(state.lenbits)*/

            here_bits = here >>> 24;
            here_op = here >>> 16 & 0xff;
            here_val = here & 0xffff;

            if (here_bits <= bits) {
              break;
            } //--- PULLBYTE() ---//


            if (have === 0) {
              break inf_leave;
            }

            have--;
            hold += input[next++] << bits;
            bits += 8; //---//
          }

          if (here_op && (here_op & 0xf0) === 0) {
            last_bits = here_bits;
            last_op = here_op;
            last_val = here_val;

            for (;;) {
              here = state.lencode[last_val + ((hold & (1 << last_bits + last_op) - 1) >> last_bits)];
              here_bits = here >>> 24;
              here_op = here >>> 16 & 0xff;
              here_val = here & 0xffff;

              if (last_bits + here_bits <= bits) {
                break;
              } //--- PULLBYTE() ---//


              if (have === 0) {
                break inf_leave;
              }

              have--;
              hold += input[next++] << bits;
              bits += 8; //---//
            } //--- DROPBITS(last.bits) ---//


            hold >>>= last_bits;
            bits -= last_bits; //---//

            state.back += last_bits;
          } //--- DROPBITS(here.bits) ---//


          hold >>>= here_bits;
          bits -= here_bits; //---//

          state.back += here_bits;
          state.length = here_val;

          if (here_op === 0) {
            //Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ?
            //        "inflate:         literal '%c'\n" :
            //        "inflate:         literal 0x%02x\n", here.val));
            state.mode = LIT;
            break;
          }

          if (here_op & 32) {
            //Tracevv((stderr, "inflate:         end of block\n"));
            state.back = -1;
            state.mode = TYPE;
            break;
          }

          if (here_op & 64) {
            strm.msg = 'invalid literal/length code';
            state.mode = BAD;
            break;
          }

          state.extra = here_op & 15;
          state.mode = LENEXT;

        /* falls through */

        case LENEXT:
          if (state.extra) {
            //=== NEEDBITS(state.extra);
            n = state.extra;

            while (bits < n) {
              if (have === 0) {
                break inf_leave;
              }

              have--;
              hold += input[next++] << bits;
              bits += 8;
            } //===//


            state.length += hold & (1 << state.extra) - 1
            /*BITS(state.extra)*/
            ; //--- DROPBITS(state.extra) ---//

            hold >>>= state.extra;
            bits -= state.extra; //---//

            state.back += state.extra;
          } //Tracevv((stderr, "inflate:         length %u\n", state.length));


          state.was = state.length;
          state.mode = DIST;

        /* falls through */

        case DIST:
          for (;;) {
            here = state.distcode[hold & (1 << state.distbits) - 1];
            /*BITS(state.distbits)*/

            here_bits = here >>> 24;
            here_op = here >>> 16 & 0xff;
            here_val = here & 0xffff;

            if (here_bits <= bits) {
              break;
            } //--- PULLBYTE() ---//


            if (have === 0) {
              break inf_leave;
            }

            have--;
            hold += input[next++] << bits;
            bits += 8; //---//
          }

          if ((here_op & 0xf0) === 0) {
            last_bits = here_bits;
            last_op = here_op;
            last_val = here_val;

            for (;;) {
              here = state.distcode[last_val + ((hold & (1 << last_bits + last_op) - 1) >> last_bits)];
              here_bits = here >>> 24;
              here_op = here >>> 16 & 0xff;
              here_val = here & 0xffff;

              if (last_bits + here_bits <= bits) {
                break;
              } //--- PULLBYTE() ---//


              if (have === 0) {
                break inf_leave;
              }

              have--;
              hold += input[next++] << bits;
              bits += 8; //---//
            } //--- DROPBITS(last.bits) ---//


            hold >>>= last_bits;
            bits -= last_bits; //---//

            state.back += last_bits;
          } //--- DROPBITS(here.bits) ---//


          hold >>>= here_bits;
          bits -= here_bits; //---//

          state.back += here_bits;

          if (here_op & 64) {
            strm.msg = 'invalid distance code';
            state.mode = BAD;
            break;
          }

          state.offset = here_val;
          state.extra = here_op & 15;
          state.mode = DISTEXT;

        /* falls through */

        case DISTEXT:
          if (state.extra) {
            //=== NEEDBITS(state.extra);
            n = state.extra;

            while (bits < n) {
              if (have === 0) {
                break inf_leave;
              }

              have--;
              hold += input[next++] << bits;
              bits += 8;
            } //===//


            state.offset += hold & (1 << state.extra) - 1
            /*BITS(state.extra)*/
            ; //--- DROPBITS(state.extra) ---//

            hold >>>= state.extra;
            bits -= state.extra; //---//

            state.back += state.extra;
          } //#ifdef INFLATE_STRICT


          if (state.offset > state.dmax) {
            strm.msg = 'invalid distance too far back';
            state.mode = BAD;
            break;
          } //#endif
          //Tracevv((stderr, "inflate:         distance %u\n", state.offset));


          state.mode = MATCH;

        /* falls through */

        case MATCH:
          if (left === 0) {
            break inf_leave;
          }

          copy = _out - left;

          if (state.offset > copy) {
            /* copy from window */
            copy = state.offset - copy;

            if (copy > state.whave) {
              if (state.sane) {
                strm.msg = 'invalid distance too far back';
                state.mode = BAD;
                break;
              } // (!) This block is disabled in zlib defaults,
              // don't enable it for binary compatibility
              //#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR
              //          Trace((stderr, "inflate.c too far\n"));
              //          copy -= state.whave;
              //          if (copy > state.length) { copy = state.length; }
              //          if (copy > left) { copy = left; }
              //          left -= copy;
              //          state.length -= copy;
              //          do {
              //            output[put++] = 0;
              //          } while (--copy);
              //          if (state.length === 0) { state.mode = LEN; }
              //          break;
              //#endif

            }

            if (copy > state.wnext) {
              copy -= state.wnext;
              from = state.wsize - copy;
            } else {
              from = state.wnext - copy;
            }

            if (copy > state.length) {
              copy = state.length;
            }

            from_source = state.window;
          } else {
            /* copy from output */
            from_source = output;
            from = put - state.offset;
            copy = state.length;
          }

          if (copy > left) {
            copy = left;
          }

          left -= copy;
          state.length -= copy;

          do {
            output[put++] = from_source[from++];
          } while (--copy);

          if (state.length === 0) {
            state.mode = LEN;
          }

          break;

        case LIT:
          if (left === 0) {
            break inf_leave;
          }

          output[put++] = state.length;
          left--;
          state.mode = LEN;
          break;

        case CHECK:
          if (state.wrap) {
            //=== NEEDBITS(32);
            while (bits < 32) {
              if (have === 0) {
                break inf_leave;
              }

              have--; // Use '|' instead of '+' to make sure that result is signed

              hold |= input[next++] << bits;
              bits += 8;
            } //===//


            _out -= left;
            strm.total_out += _out;
            state.total += _out;

            if (_out) {
              strm.adler = state.check = state.flags ? crc32_1(state.check, output, _out, put - _out) : adler32_1(state.check, output, _out, put - _out);
            }

            _out = left; // NB: crc32 stored as signed 32-bit int, zswap32 returns signed too

            if ((state.flags ? hold : zswap32(hold)) !== state.check) {
              strm.msg = 'incorrect data check';
              state.mode = BAD;
              break;
            } //=== INITBITS();


            hold = 0;
            bits = 0; //===//
            //Tracev((stderr, "inflate:   check matches trailer\n"));
          }

          state.mode = LENGTH;

        /* falls through */

        case LENGTH:
          if (state.wrap && state.flags) {
            //=== NEEDBITS(32);
            while (bits < 32) {
              if (have === 0) {
                break inf_leave;
              }

              have--;
              hold += input[next++] << bits;
              bits += 8;
            } //===//


            if (hold !== (state.total & 0xffffffff)) {
              strm.msg = 'incorrect length check';
              state.mode = BAD;
              break;
            } //=== INITBITS();


            hold = 0;
            bits = 0; //===//
            //Tracev((stderr, "inflate:   length matches trailer\n"));
          }

          state.mode = DONE;

        /* falls through */

        case DONE:
          ret = Z_STREAM_END$1;
          break inf_leave;

        case BAD:
          ret = Z_DATA_ERROR$1;
          break inf_leave;

        case MEM:
          return Z_MEM_ERROR$1;

        case SYNC:
        /* falls through */

        default:
          return Z_STREAM_ERROR$1;
      }
    } // inf_leave <- here is real place for "goto inf_leave", emulated via "break inf_leave"

    /*
       Return from inflate(), updating the total counts and the check value.
       If there was no progress during the inflate() call, return a buffer
       error.  Call updatewindow() to create and/or update the window state.
       Note: a memory error from inflate() is non-recoverable.
     */
    //--- RESTORE() ---


    strm.next_out = put;
    strm.avail_out = left;
    strm.next_in = next;
    strm.avail_in = have;
    state.hold = hold;
    state.bits = bits; //---

    if (state.wsize || _out !== strm.avail_out && state.mode < BAD && (state.mode < CHECK || flush !== Z_FINISH$1)) {
      if (updatewindow(strm, strm.output, strm.next_out, _out - strm.avail_out)) ;
    }

    _in -= strm.avail_in;
    _out -= strm.avail_out;
    strm.total_in += _in;
    strm.total_out += _out;
    state.total += _out;

    if (state.wrap && _out) {
      strm.adler = state.check = state.flags ? crc32_1(state.check, output, _out, strm.next_out - _out) : adler32_1(state.check, output, _out, strm.next_out - _out);
    }

    strm.data_type = state.bits + (state.last ? 64 : 0) + (state.mode === TYPE ? 128 : 0) + (state.mode === LEN_ || state.mode === COPY_ ? 256 : 0);

    if ((_in === 0 && _out === 0 || flush === Z_FINISH$1) && ret === Z_OK$1) {
      ret = Z_BUF_ERROR;
    }

    return ret;
  };

  const inflateEnd = strm => {
    if (!strm || !strm.state
    /*|| strm->zfree == (free_func)0*/
    ) {
      return Z_STREAM_ERROR$1;
    }

    let state = strm.state;

    if (state.window) {
      state.window = null;
    }

    strm.state = null;
    return Z_OK$1;
  };

  const inflateGetHeader = (strm, head) => {
    /* check state */
    if (!strm || !strm.state) {
      return Z_STREAM_ERROR$1;
    }

    const state = strm.state;

    if ((state.wrap & 2) === 0) {
      return Z_STREAM_ERROR$1;
    }
    /* save header structure */


    state.head = head;
    head.done = false;
    return Z_OK$1;
  };

  const inflateSetDictionary = (strm, dictionary) => {
    const dictLength = dictionary.length;
    let state;
    let dictid;
    let ret;
    /* check state */

    if (!strm
    /* == Z_NULL */
    || !strm.state
    /* == Z_NULL */
    ) {
      return Z_STREAM_ERROR$1;
    }

    state = strm.state;

    if (state.wrap !== 0 && state.mode !== DICT) {
      return Z_STREAM_ERROR$1;
    }
    /* check for correct dictionary identifier */


    if (state.mode === DICT) {
      dictid = 1;
      /* adler32(0, null, 0)*/

      /* dictid = adler32(dictid, dictionary, dictLength); */

      dictid = adler32_1(dictid, dictionary, dictLength, 0);

      if (dictid !== state.check) {
        return Z_DATA_ERROR$1;
      }
    }
    /* copy dictionary to window using updatewindow(), which will amend the
     existing dictionary if appropriate */


    ret = updatewindow(strm, dictionary, dictLength, dictLength);

    if (ret) {
      state.mode = MEM;
      return Z_MEM_ERROR$1;
    }

    state.havedict = 1; // Tracev((stderr, "inflate:   dictionary set\n"));

    return Z_OK$1;
  };

  var inflateReset_1 = inflateReset;
  var inflateReset2_1 = inflateReset2;
  var inflateResetKeep_1 = inflateResetKeep;
  var inflateInit_1 = inflateInit;
  var inflateInit2_1 = inflateInit2;
  var inflate_2$1 = inflate$2;
  var inflateEnd_1 = inflateEnd;
  var inflateGetHeader_1 = inflateGetHeader;
  var inflateSetDictionary_1 = inflateSetDictionary;
  var inflateInfo = 'pako inflate (from Nodeca project)';
  /* Not implemented
  module.exports.inflateCopy = inflateCopy;
  module.exports.inflateGetDictionary = inflateGetDictionary;
  module.exports.inflateMark = inflateMark;
  module.exports.inflatePrime = inflatePrime;
  module.exports.inflateSync = inflateSync;
  module.exports.inflateSyncPoint = inflateSyncPoint;
  module.exports.inflateUndermine = inflateUndermine;
  */

  var inflate_1$2 = {
    inflateReset: inflateReset_1,
    inflateReset2: inflateReset2_1,
    inflateResetKeep: inflateResetKeep_1,
    inflateInit: inflateInit_1,
    inflateInit2: inflateInit2_1,
    inflate: inflate_2$1,
    inflateEnd: inflateEnd_1,
    inflateGetHeader: inflateGetHeader_1,
    inflateSetDictionary: inflateSetDictionary_1,
    inflateInfo: inflateInfo
  }; // (C) 1995-2013 Jean-loup Gailly and Mark Adler
  // (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin
  //
  // This software is provided 'as-is', without any express or implied
  // warranty. In no event will the authors be held liable for any damages
  // arising from the use of this software.
  //
  // Permission is granted to anyone to use this software for any purpose,
  // including commercial applications, and to alter it and redistribute it
  // freely, subject to the following restrictions:
  //
  // 1. The origin of this software must not be misrepresented; you must not
  //   claim that you wrote the original software. If you use this software
  //   in a product, an acknowledgment in the product documentation would be
  //   appreciated but is not required.
  // 2. Altered source versions must be plainly marked as such, and must not be
  //   misrepresented as being the original software.
  // 3. This notice may not be removed or altered from any source distribution.

  function GZheader() {
    /* true if compressed data believed to be text */
    this.text = 0;
    /* modification time */

    this.time = 0;
    /* extra flags (not used when writing a gzip file) */

    this.xflags = 0;
    /* operating system */

    this.os = 0;
    /* pointer to extra field or Z_NULL if none */

    this.extra = null;
    /* extra field length (valid if extra != Z_NULL) */

    this.extra_len = 0; // Actually, we don't need it in JS,
    // but leave for few code modifications
    //
    // Setup limits is not necessary because in js we should not preallocate memory
    // for inflate use constant limit in 65536 bytes
    //

    /* space at extra (only when reading header) */
    // this.extra_max  = 0;

    /* pointer to zero-terminated file name or Z_NULL */

    this.name = '';
    /* space at name (only when reading header) */
    // this.name_max   = 0;

    /* pointer to zero-terminated comment or Z_NULL */

    this.comment = '';
    /* space at comment (only when reading header) */
    // this.comm_max   = 0;

    /* true if there was or will be a header crc */

    this.hcrc = 0;
    /* true when done reading gzip header (not used when writing a gzip file) */

    this.done = false;
  }

  var gzheader = GZheader;
  const toString = Object.prototype.toString;
  /* Public constants ==========================================================*/

  /* ===========================================================================*/

  const {
    Z_NO_FLUSH,
    Z_FINISH,
    Z_OK,
    Z_STREAM_END,
    Z_NEED_DICT,
    Z_STREAM_ERROR,
    Z_DATA_ERROR,
    Z_MEM_ERROR
  } = constants$2;
  /* ===========================================================================*/

  /**
   * class Inflate
   *
   * Generic JS-style wrapper for zlib calls. If you don't need
   * streaming behaviour - use more simple functions: [[inflate]]
   * and [[inflateRaw]].
   **/

  /* internal
   * inflate.chunks -> Array
   *
   * Chunks of output data, if [[Inflate#onData]] not overridden.
   **/

  /**
   * Inflate.result -> Uint8Array|String
   *
   * Uncompressed result, generated by default [[Inflate#onData]]
   * and [[Inflate#onEnd]] handlers. Filled after you push last chunk
   * (call [[Inflate#push]] with `Z_FINISH` / `true` param).
   **/

  /**
   * Inflate.err -> Number
   *
   * Error code after inflate finished. 0 (Z_OK) on success.
   * Should be checked if broken data possible.
   **/

  /**
   * Inflate.msg -> String
   *
   * Error message, if [[Inflate.err]] != 0
   **/

  /**
   * new Inflate(options)
   * - options (Object): zlib inflate options.
   *
   * Creates new inflator instance with specified params. Throws exception
   * on bad params. Supported options:
   *
   * - `windowBits`
   * - `dictionary`
   *
   * [http://zlib.net/manual.html#Advanced](http://zlib.net/manual.html#Advanced)
   * for more information on these.
   *
   * Additional options, for internal needs:
   *
   * - `chunkSize` - size of generated data chunks (16K by default)
   * - `raw` (Boolean) - do raw inflate
   * - `to` (String) - if equal to 'string', then result will be converted
   *   from utf8 to utf16 (javascript) string. When string output requested,
   *   chunk length can differ from `chunkSize`, depending on content.
   *
   * By default, when no options set, autodetect deflate/gzip data format via
   * wrapper header.
   *
   * ##### Example:
   *
   * ```javascript
   * const pako = require('pako')
   * const chunk1 = new Uint8Array([1,2,3,4,5,6,7,8,9])
   * const chunk2 = new Uint8Array([10,11,12,13,14,15,16,17,18,19]);
   *
   * const inflate = new pako.Inflate({ level: 3});
   *
   * inflate.push(chunk1, false);
   * inflate.push(chunk2, true);  // true -> last chunk
   *
   * if (inflate.err) { throw new Error(inflate.err); }
   *
   * console.log(inflate.result);
   * ```
   **/

  function Inflate$1(options) {
    this.options = common.assign({
      chunkSize: 1024 * 64,
      windowBits: 15,
      to: ''
    }, options || {});
    const opt = this.options; // Force window size for `raw` data, if not set directly,
    // because we have no header for autodetect.

    if (opt.raw && opt.windowBits >= 0 && opt.windowBits < 16) {
      opt.windowBits = -opt.windowBits;

      if (opt.windowBits === 0) {
        opt.windowBits = -15;
      }
    } // If `windowBits` not defined (and mode not raw) - set autodetect flag for gzip/deflate


    if (opt.windowBits >= 0 && opt.windowBits < 16 && !(options && options.windowBits)) {
      opt.windowBits += 32;
    } // Gzip header has no info about windows size, we can do autodetect only
    // for deflate. So, if window size not set, force it to max when gzip possible


    if (opt.windowBits > 15 && opt.windowBits < 48) {
      // bit 3 (16) -> gzipped data
      // bit 4 (32) -> autodetect gzip/deflate
      if ((opt.windowBits & 15) === 0) {
        opt.windowBits |= 15;
      }
    }

    this.err = 0; // error code, if happens (0 = Z_OK)

    this.msg = ''; // error message

    this.ended = false; // used to avoid multiple onEnd() calls

    this.chunks = []; // chunks of compressed data

    this.strm = new zstream();
    this.strm.avail_out = 0;
    let status = inflate_1$2.inflateInit2(this.strm, opt.windowBits);

    if (status !== Z_OK) {
      throw new Error(messages[status]);
    }

    this.header = new gzheader();
    inflate_1$2.inflateGetHeader(this.strm, this.header); // Setup dictionary

    if (opt.dictionary) {
      // Convert data if needed
      if (typeof opt.dictionary === 'string') {
        opt.dictionary = strings.string2buf(opt.dictionary);
      } else if (toString.call(opt.dictionary) === '[object ArrayBuffer]') {
        opt.dictionary = new Uint8Array(opt.dictionary);
      }

      if (opt.raw) {
        //In raw mode we need to set the dictionary early
        status = inflate_1$2.inflateSetDictionary(this.strm, opt.dictionary);

        if (status !== Z_OK) {
          throw new Error(messages[status]);
        }
      }
    }
  }
  /**
   * Inflate#push(data[, flush_mode]) -> Boolean
   * - data (Uint8Array|ArrayBuffer): input data
   * - flush_mode (Number|Boolean): 0..6 for corresponding Z_NO_FLUSH..Z_TREE
   *   flush modes. See constants. Skipped or `false` means Z_NO_FLUSH,
   *   `true` means Z_FINISH.
   *
   * Sends input data to inflate pipe, generating [[Inflate#onData]] calls with
   * new output chunks. Returns `true` on success. If end of stream detected,
   * [[Inflate#onEnd]] will be called.
   *
   * `flush_mode` is not needed for normal operation, because end of stream
   * detected automatically. You may try to use it for advanced things, but
   * this functionality was not tested.
   *
   * On fail call [[Inflate#onEnd]] with error code and return false.
   *
   * ##### Example
   *
   * ```javascript
   * push(chunk, false); // push one of data chunks
   * ...
   * push(chunk, true);  // push last chunk
   * ```
   **/


  Inflate$1.prototype.push = function (data, flush_mode) {
    const strm = this.strm;
    const chunkSize = this.options.chunkSize;
    const dictionary = this.options.dictionary;

    let status, _flush_mode, last_avail_out;

    if (this.ended) return false;
    if (flush_mode === ~~flush_mode) _flush_mode = flush_mode;else _flush_mode = flush_mode === true ? Z_FINISH : Z_NO_FLUSH; // Convert data if needed

    if (toString.call(data) === '[object ArrayBuffer]') {
      strm.input = new Uint8Array(data);
    } else {
      strm.input = data;
    }

    strm.next_in = 0;
    strm.avail_in = strm.input.length;

    for (;;) {
      if (strm.avail_out === 0) {
        strm.output = new Uint8Array(chunkSize);
        strm.next_out = 0;
        strm.avail_out = chunkSize;
      }

      status = inflate_1$2.inflate(strm, _flush_mode);

      if (status === Z_NEED_DICT && dictionary) {
        status = inflate_1$2.inflateSetDictionary(strm, dictionary);

        if (status === Z_OK) {
          status = inflate_1$2.inflate(strm, _flush_mode);
        } else if (status === Z_DATA_ERROR) {
          // Replace code with more verbose
          status = Z_NEED_DICT;
        }
      } // Skip snyc markers if more data follows and not raw mode


      while (strm.avail_in > 0 && status === Z_STREAM_END && strm.state.wrap > 0 && data[strm.next_in] !== 0) {
        inflate_1$2.inflateReset(strm);
        status = inflate_1$2.inflate(strm, _flush_mode);
      }

      switch (status) {
        case Z_STREAM_ERROR:
        case Z_DATA_ERROR:
        case Z_NEED_DICT:
        case Z_MEM_ERROR:
          this.onEnd(status);
          this.ended = true;
          return false;
      } // Remember real `avail_out` value, because we may patch out buffer content
      // to align utf8 strings boundaries.


      last_avail_out = strm.avail_out;

      if (strm.next_out) {
        if (strm.avail_out === 0 || status === Z_STREAM_END) {
          if (this.options.to === 'string') {
            let next_out_utf8 = strings.utf8border(strm.output, strm.next_out);
            let tail = strm.next_out - next_out_utf8;
            let utf8str = strings.buf2string(strm.output, next_out_utf8); // move tail & realign counters

            strm.next_out = tail;
            strm.avail_out = chunkSize - tail;
            if (tail) strm.output.set(strm.output.subarray(next_out_utf8, next_out_utf8 + tail), 0);
            this.onData(utf8str);
          } else {
            this.onData(strm.output.length === strm.next_out ? strm.output : strm.output.subarray(0, strm.next_out));
          }
        }
      } // Must repeat iteration if out buffer is full


      if (status === Z_OK && last_avail_out === 0) continue; // Finalize if end of stream reached.

      if (status === Z_STREAM_END) {
        status = inflate_1$2.inflateEnd(this.strm);
        this.onEnd(status);
        this.ended = true;
        return true;
      }

      if (strm.avail_in === 0) break;
    }

    return true;
  };
  /**
   * Inflate#onData(chunk) -> Void
   * - chunk (Uint8Array|String): output data. When string output requested,
   *   each chunk will be string.
   *
   * By default, stores data blocks in `chunks[]` property and glue
   * those in `onEnd`. Override this handler, if you need another behaviour.
   **/


  Inflate$1.prototype.onData = function (chunk) {
    this.chunks.push(chunk);
  };
  /**
   * Inflate#onEnd(status) -> Void
   * - status (Number): inflate status. 0 (Z_OK) on success,
   *   other if not.
   *
   * Called either after you tell inflate that the input stream is
   * complete (Z_FINISH). By default - join collected chunks,
   * free memory and fill `results` / `err` properties.
   **/


  Inflate$1.prototype.onEnd = function (status) {
    // On success - join
    if (status === Z_OK) {
      if (this.options.to === 'string') {
        this.result = this.chunks.join('');
      } else {
        this.result = common.flattenChunks(this.chunks);
      }
    }

    this.chunks = [];
    this.err = status;
    this.msg = this.strm.msg;
  };
  /**
   * inflate(data[, options]) -> Uint8Array|String
   * - data (Uint8Array): input data to decompress.
   * - options (Object): zlib inflate options.
   *
   * Decompress `data` with inflate/ungzip and `options`. Autodetect
   * format via wrapper header by default. That's why we don't provide
   * separate `ungzip` method.
   *
   * Supported options are:
   *
   * - windowBits
   *
   * [http://zlib.net/manual.html#Advanced](http://zlib.net/manual.html#Advanced)
   * for more information.
   *
   * Sugar (options):
   *
   * - `raw` (Boolean) - say that we work with raw stream, if you don't wish to specify
   *   negative windowBits implicitly.
   * - `to` (String) - if equal to 'string', then result will be converted
   *   from utf8 to utf16 (javascript) string. When string output requested,
   *   chunk length can differ from `chunkSize`, depending on content.
   *
   *
   * ##### Example:
   *
   * ```javascript
   * const pako = require('pako');
   * const input = pako.deflate(new Uint8Array([1,2,3,4,5,6,7,8,9]));
   * let output;
   *
   * try {
   *   output = pako.inflate(input);
   * } catch (err) {
   *   console.log(err);
   * }
   * ```
   **/


  function inflate$1(input, options) {
    const inflator = new Inflate$1(options);
    inflator.push(input); // That will never happens, if you don't cheat with options :)

    if (inflator.err) throw inflator.msg || messages[inflator.err];
    return inflator.result;
  }
  /**
   * inflateRaw(data[, options]) -> Uint8Array|String
   * - data (Uint8Array): input data to decompress.
   * - options (Object): zlib inflate options.
   *
   * The same as [[inflate]], but creates raw data, without wrapper
   * (header and adler32 crc).
   **/


  function inflateRaw$1(input, options) {
    options = options || {};
    options.raw = true;
    return inflate$1(input, options);
  }
  /**
   * ungzip(data[, options]) -> Uint8Array|String
   * - data (Uint8Array): input data to decompress.
   * - options (Object): zlib inflate options.
   *
   * Just shortcut to [[inflate]], because it autodetects format
   * by header.content. Done for convenience.
   **/


  var Inflate_1$1 = Inflate$1;
  var inflate_2 = inflate$1;
  var inflateRaw_1$1 = inflateRaw$1;
  var ungzip$1 = inflate$1;
  var constants = constants$2;
  var inflate_1$1 = {
    Inflate: Inflate_1$1,
    inflate: inflate_2,
    inflateRaw: inflateRaw_1$1,
    ungzip: ungzip$1,
    constants: constants
  };
  const {
    Deflate,
    deflate,
    deflateRaw,
    gzip
  } = deflate_1$1;
  const {
    Inflate,
    inflate,
    inflateRaw,
    ungzip
  } = inflate_1$1;
  var Deflate_1 = Deflate;
  var deflate_1 = deflate;
  var deflateRaw_1 = deflateRaw;
  var gzip_1 = gzip;
  var Inflate_1 = Inflate;
  var inflate_1 = inflate;
  var inflateRaw_1 = inflateRaw;
  var ungzip_1 = ungzip;
  var constants_1 = constants$2;
  var pako = {
    Deflate: Deflate_1,
    deflate: deflate_1,
    deflateRaw: deflateRaw_1,
    gzip: gzip_1,
    Inflate: Inflate_1,
    inflate: inflate_1,
    inflateRaw: inflateRaw_1,
    ungzip: ungzip_1,
    constants: constants_1
  };

  const base64codes = Uint8Array.from([255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 62, 255, 255, 255, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 255, 255, 255, 0, 255, 255, 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 255, 255, 255, 255, 255, 255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51]);
  /**
   * Convert a Uint8Array containing a base64 encoded bytes to a Uint8Array containing decoded values
   * @returns a Uint8Array containing the decoded bytes
   */

  function decode(input) {
    if (!ArrayBuffer.isView(input)) {
      input = new Uint8Array(input);
    }

    if (input.length % 4 !== 0) {
      throw new Error('Unable to parse base64 string.');
    }

    let output = new Uint8Array(3 * (input.length / 4));
    if (input.length === 0) return output;
    const missingOctets = input[input.length - 2] === 61 ? 2 : input[input.length - 1] === 61 ? 1 : 0;

    for (let i = 0, j = 0; i < input.length; i += 4, j += 3) {
      const buffer = base64codes[input[i]] << 18 | base64codes[input[i + 1]] << 12 | base64codes[input[i + 2]] << 6 | base64codes[input[i + 3]];
      output[j] = buffer >> 16;
      output[j + 1] = buffer >> 8 & 0xff;
      output[j + 2] = buffer & 0xff;
    }

    return output.subarray(0, output.length - missingOctets);
  }

  function decodeBase64(base64, options = {}) {
    let {
      endian = 'little',
      precision,
      float = true,
      compression = '',
      ontologies
    } = options;

    if (ontologies) {
      if (ontologies.includes('MS:1000519')) {
        precision = 32;
        float = false;
      }

      if (ontologies.includes('MS:1000520')) precision = 16;
      if (ontologies.includes('MS:1000521')) precision = 32;

      if (ontologies.includes('MS:1000522')) {
        float = false;
        precision = 64;
      }

      if (ontologies.includes('MS:1000523')) precision = 64;
      if (ontologies.includes('MS:1000574')) compression = 'zlib';
    }

    let uint8Array = decode(base64);

    switch (compression.toLowerCase()) {
      case 'zlib':
        uint8Array = pako.inflate(uint8Array);
        break;

      case '':
      case 'none':
        break;

      default:
        throw new Error(`Unknow compression algorithm: ${compression}`);
    }

    switch (endian.toLowerCase()) {
      case 'little':
        break;

      case 'network':
      case 'big':
        {
          // we will invert in place the data
          let step;

          switch (precision) {
            case 32:
              step = 4;
              break;

            case 64:
              step = 8;
              break;

            default:
              throw new Error('Can not process bigendian file');
          }

          for (let i = 0; i < uint8Array.length - uint8Array.length % step; i += step) {
            for (let j = 0; j < step / 2; j++) {
              const temp = uint8Array[i + j];
              uint8Array[i + j] = uint8Array[i + step - 1 - j];
              uint8Array[i + step - 1 - j] = temp;
            }
          }
        }
        break;

      default:
        throw new TypeError(`Attributes endian not correct: ${endian}`);
    }
    /*
         We should take care that the length of the Uint8Array is correct but the buffer
         may be a little bit bigger because when decoding base 64 it may end with = or ==
         and we plan the size in the buffer.
        */


    if (float) {
      switch (precision) {
        case 32:
          return new Float32Array(uint8Array.buffer, 0, uint8Array.length / 4);

        case 64:
          return new Float64Array(uint8Array.buffer, 0, uint8Array.length / 8);

        default:
          throw new TypeError(`Incorrect precision: ${precision}`);
      }
    } else {
      switch (precision) {
        case 32:
          return new Int32Array(uint8Array.buffer, 0, uint8Array.length / 4);

        case 64:
          // eslint-disable-next-line no-undef
          return new BigInt64Array(uint8Array.buffer, 0, uint8Array.length / 8);

        default:
          throw new TypeError(`Incorrect precision: ${precision}`);
      }
    }
  }

  function parseCvParam$1(cvParam) {
    let result = {};
    if (!cvParam) return result;
    let cvParams;

    if (Array.isArray(cvParam)) {
      cvParams = cvParam;
    } else {
      cvParams = [cvParam];
    }

    for (let param of cvParams) {
      let attributes = param.attributes;

      if (attributes.name) {
        result[attributes.name.toLowerCase()] = {
          accession: attributes.accession,
          cvLabel: attributes.cvLabel,
          value: attributes.value,
          name: attributes.name
        };
      }
    }

    return result;
  }

  function processMetadata(parsed, metadata) {
    if (!parsed || !parsed.description) return;
    let description = parsed.description;

    if (description.dataProcessing) {
      let dataProcessing = description.dataProcessing;

      if (dataProcessing.software && dataProcessing.software.name) {
        metadata.software = dataProcessing.software.name;
      }
    }

    if (description.instrument) {
      let instrument = description.instrument;

      if (instrument.analyzerList && instrument.analyzerList.analyzer) {
        let analyzer = instrument.analyzerList.analyzer;
        let cvParam = parseCvParam$1(analyzer.cvParam);

        if (cvParam.analyzertype) {
          metadata.analyzer = cvParam.analyzertype.value;
        }
      }

      if (instrument.detector) {
        let detector = instrument.detector;
        let cvParam = parseCvParam$1(detector.cvParam);

        if (cvParam.detectortype) {
          metadata.detector = cvParam.detectortype.value;
        }
      }
    }
  }

  function processSpectrumList$2(parsed, times, msData) {
    if (!parsed || !parsed.spectrumList || !parsed.spectrumList.spectrum) return;
    let spectrumList = parsed.spectrumList.spectrum;

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

  const decoder$3 = new TextDecoder();
  function parseMzData(arrayBuffer) {
    const result = {
      metadata: {},
      times: [],
      series: {
        ms: {
          data: []
        }
      }
    };
    let parsed = parse(arrayBuffer, {
      attributeNamePrefix: '',
      attributesNodeName: 'attributes',
      tagValueProcessor: (value, node) => {
        if (node.tagName !== 'data') return decoder$3.decode(value);
        return decodeBase64(node.value, node.attributes);
      }
    });
    processMetadata(parsed.mzData, result.metadata);
    processSpectrumList$2(parsed.mzData, result.times, result.series.ms.data);
    return result;
  }

  var camelcase = {exports: {}};

  const preserveCamelCase = (string, locale) => {
    let isLastCharLower = false;
    let isLastCharUpper = false;
    let isLastLastCharUpper = false;

    for (let i = 0; i < string.length; i++) {
      const character = string[i];

      if (isLastCharLower && /[\p{Lu}]/u.test(character)) {
        string = string.slice(0, i) + '-' + string.slice(i);
        isLastCharLower = false;
        isLastLastCharUpper = isLastCharUpper;
        isLastCharUpper = true;
        i++;
      } else if (isLastCharUpper && isLastLastCharUpper && /[\p{Ll}]/u.test(character)) {
        string = string.slice(0, i - 1) + '-' + string.slice(i - 1);
        isLastLastCharUpper = isLastCharUpper;
        isLastCharUpper = false;
        isLastCharLower = true;
      } else {
        isLastCharLower = character.toLocaleLowerCase(locale) === character && character.toLocaleUpperCase(locale) !== character;
        isLastLastCharUpper = isLastCharUpper;
        isLastCharUpper = character.toLocaleUpperCase(locale) === character && character.toLocaleLowerCase(locale) !== character;
      }
    }

    return string;
  };

  const preserveConsecutiveUppercase = input => {
    return input.replace(/^[\p{Lu}](?![\p{Lu}])/gu, m1 => m1.toLowerCase());
  };

  const postProcess = (input, options) => {
    return input.replace(/[_.\- ]+([\p{Alpha}\p{N}_]|$)/gu, (_, p1) => p1.toLocaleUpperCase(options.locale)).replace(/\d+([\p{Alpha}\p{N}_]|$)/gu, m => m.toLocaleUpperCase(options.locale));
  };

  const camelCase = (input, options) => {
    if (!(typeof input === 'string' || Array.isArray(input))) {
      throw new TypeError('Expected the input to be `string | string[]`');
    }

    options = {
      pascalCase: false,
      preserveConsecutiveUppercase: false,
      ...options
    };

    if (Array.isArray(input)) {
      input = input.map(x => x.trim()).filter(x => x.length).join('-');
    } else {
      input = input.trim();
    }

    if (input.length === 0) {
      return '';
    }

    if (input.length === 1) {
      return options.pascalCase ? input.toLocaleUpperCase(options.locale) : input.toLocaleLowerCase(options.locale);
    }

    const hasUpperCase = input !== input.toLocaleLowerCase(options.locale);

    if (hasUpperCase) {
      input = preserveCamelCase(input, options.locale);
    }

    input = input.replace(/^[_.\- ]+/, '');

    if (options.preserveConsecutiveUppercase) {
      input = preserveConsecutiveUppercase(input);
    } else {
      input = input.toLocaleLowerCase();
    }

    if (options.pascalCase) {
      input = input.charAt(0).toLocaleUpperCase(options.locale) + input.slice(1);
    }

    return postProcess(input, options);
  };

  camelcase.exports = camelCase; // TODO: Remove this for the next major release

  camelcase.exports.default = camelCase;

  var camelCase$1 = camelcase.exports;

  function parseCvParam(cvParam) {
    let result = {};
    if (!cvParam) return result;
    let cvParams;

    if (Array.isArray(cvParam)) {
      cvParams = cvParam;
    } else {
      cvParams = [cvParam];
    }

    for (let parameter of cvParams) {
      let attribute = parameter.attributes;

      if (attribute.name) {
        result[camelCase$1(attribute.name.toLowerCase().replace(/[^ a-z0-9]/g, ''))] = {
          accession: attribute.accession,
          cvLabel: attribute.cvLabel,
          value: attribute.value,
          name: attribute.name
        };
      }
    }

    return result;
  }

  function processSpectrumList$1(parsed, times, msData) {
    if (!parsed || !parsed.run || !parsed.run.spectrumList || !parsed.run.spectrumList.spectrum) {
      return;
    }

    let spectrumList = parsed.run.spectrumList.spectrum;

    for (let spectrum of spectrumList) {
      if (!spectrum.binaryDataArrayList) continue;
      let scanList = spectrum.scanList;
      if (Array.isArray(scanList)) throw new Error('Unsupported scanList');
      let scan = scanList.scan;
      if (typeof scan !== 'object') continue;

      if (Array.isArray(scan)) {
        throw new Error('processSpectrumList: scan may not be an array');
      }

      const cvParam = parseCvParam(scan.cvParam);
      times.push(cvParam.scanStartTime.value);
      const dataArrayList = spectrum.binaryDataArrayList.binaryDataArray;

      if (dataArrayList.length !== 2) {
        throw new Error('Can not decodeData because length !== 2');
      }

      const first = dataArrayList[0];
      const firstCVParams = parseCvParam(first.cvParam);
      const second = dataArrayList[1];
      const secondCVParams = parseCvParam(second.cvParam);

      if (firstCVParams.mzArray && secondCVParams.intensityArray) {
        msData.push([first.binary, second.binary]);
      }

      if (firstCVParams.intensityArray && secondCVParams.mzArray) {
        msData.push([second.binary, first.binary]);
      }
    }
  }

  const decoder$2 = new TextDecoder();
  function parseMzML(arrayBuffer) {
    const result = {
      metadata: {},
      times: [],
      series: {
        ms: {
          data: []
        }
      }
    };
    let parsed = parse(arrayBuffer, {
      attributeNamePrefix: '',
      attributesNodeName: 'attributes',
      tagValueProcessor: (value, node) => {
        if (node.tagName !== 'binary') return decoder$2.decode(value);
        const ontologies = node.parent.children.cvParam.map(entry => entry.attributes.accession);
        return decodeBase64(node.value, {
          ontologies
        });
      }
    });
    const mzML = parsed.mzML || parsed.indexedmzML.mzML;
    processSpectrumList$1(mzML, result.times, result.series.ms.data);
    return result;
  }

  function processSpectrumList(parsed, times, msData) {
    if (!parsed.msRun.scan) return;
    let scanList = parsed.msRun.scan;
    if (Array.isArray(scanList) === false) scanList = [scanList];
    if (scanList[0].attributes) msData.info = [];

    for (let scan of scanList) {
      if (typeof scan !== 'object') continue;

      if (Array.isArray(scan)) {
        throw new Error('processSpectrumList: scan may not be an array');
      }

      const dataArray = scan.peaks['#text'];
      let length = dataArray.length / 2;
      let first = new Float64Array(length);
      let second = new Float64Array(length);

      for (let i = 0; i < length; i++) {
        first[i] = dataArray[i * 2];
        second[i] = dataArray[i * 2 + 1];
      }

      msData.data.push([first, second]);
      msData.info.push(scan.attributes);
      times.push(parseFloat(scan.attributes.retentionTime.replace(/(?:P*)(?:T*)(?:S*)/gi, '')));
    }
  }

  const decoder$1 = new TextDecoder();
  function parseMzXML(arrayBuffer) {
    const result = {
      metadata: {},
      times: [],
      series: {
        ms: {
          data: []
        }
      }
    };
    let parsed = parse(arrayBuffer, {
      attributeNamePrefix: '',
      attributesNodeName: 'attributes',
      tagValueProcessor: (value, node) => {
        if (node.tagName !== 'peaks') return decoder$1.decode(value);
        return decodeBase64(node.value, {
          precision: node.attributes.precision,
          endian: node.attributes.byteOrder,
          compression: node.attributes.compressionType
        });
      }
    });
    processSpectrumList(parsed.mzXML, result.times, result.series.ms);
    return result;
  }

  const decoder = new TextDecoder();
  /**
   * Reads a mzData v1.05 file
   * @param {ArrayBuffer|string} xml - ArrayBuffer or String or any Typed Array (including Node.js' Buffer from v4) with the data
   * @return {{times: Array<number>, series: { ms: { data:Array<Array<number>>}}}}
   */

  function parseMZ(xml) {
    if (typeof xml === 'string') {
      const encoder = new TextEncoder();
      xml = encoder.encode(xml);
    }

    if (!ArrayBuffer.isView(xml)) {
      xml = new Uint8Array(xml);
    }

    const header = xml.subarray ? decoder.decode(xml.subarray(0, 200)) : xml.substring(0, 200);

    if (header.includes('mzData')) {
      return parseMzData(xml);
    } else if (header.includes('mzML')) {
      return parseMzML(xml);
    } else if (header.includes('mzXML')) {
      return parseMzXML(xml);
    } else {
      throw new Error(`MZ parser: unknown format`);
    }
  }

  function fromXML(xml) {
    return fromJSON(parseMZ(xml));
  }

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

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

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