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

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

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

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

	var src$k = {};

	const toString$3 = Object.prototype.toString;
	/**
	 * Checks if an object is an instance of an Array (array or typed array).
	 *
	 * @param {any} value - Object to check.
	 * @returns {boolean} True if the object is an array.
	 */

	function isAnyArray$9(value) {
	  return toString$3.call(value).endsWith('Array]');
	}

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

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

	var isAnyArray$8 = require$$0$6;

	function max$8(input) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};

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

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

	  const {
	    fromIndex = 0,
	    toIndex = input.length
	  } = options;

	  if (fromIndex < 0 || fromIndex >= input.length || !Number.isInteger(fromIndex)) {
	    throw new Error('fromIndex must be a positive integer smaller than length');
	  }

	  if (toIndex <= fromIndex || toIndex > input.length || !Number.isInteger(toIndex)) {
	    throw new Error('toIndex must be an integer greater than fromIndex and at most equal to length');
	  }

	  let maxValue = input[fromIndex];

	  for (let i = fromIndex + 1; i < toIndex; i++) {
	    if (input[i] > maxValue) maxValue = input[i];
	  }

	  return maxValue;
	}

	var lib$7 = max$8;

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

	  if (!isAnyArray$9(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;
	}

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

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

	function sum$5(input) {
	  if (!isAnyArray$9(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;
	}

	var libEs6$1 = /*#__PURE__*/Object.freeze({
		__proto__: null,
		'default': sum$5
	});

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

	var isAnyArray$7 = require$$0$6;
	var max$6 = require$$1$4;
	var sum$4 = require$$2;

	function _interopDefaultLegacy$1(e) {
	  return e && typeof e === 'object' && 'default' in e ? e : {
	    'default': e
	  };
	}

	var max__default = /*#__PURE__*/_interopDefaultLegacy$1(max$6);

	var sum__default = /*#__PURE__*/_interopDefaultLegacy$1(sum$4);

	function norm(input) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const {
	    algorithm = 'absolute',
	    sumValue = 1,
	    maxValue = 1
	  } = options;

	  if (!isAnyArray$7.isAnyArray(input)) {
	    throw new Error('input must be an array');
	  }

	  let output;

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

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

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

	  switch (algorithm.toLowerCase()) {
	    case 'absolute':
	      {
	        let absoluteSumValue = absoluteSum$2(input) / sumValue;
	        if (absoluteSumValue === 0) return input.slice(0);

	        for (let i = 0; i < input.length; i++) {
	          output[i] = input[i] / absoluteSumValue;
	        }

	        return output;
	      }

	    case 'max':
	      {
	        let currentMaxValue = max__default['default'](input);
	        if (currentMaxValue === 0) return input.slice(0);
	        const factor = maxValue / currentMaxValue;

	        for (let i = 0; i < input.length; i++) {
	          output[i] = input[i] * factor;
	        }

	        return output;
	      }

	    case 'sum':
	      {
	        let sumFactor = sum__default['default'](input) / sumValue;
	        if (sumFactor === 0) return input.slice(0);

	        for (let i = 0; i < input.length; i++) {
	          output[i] = input[i] / sumFactor;
	        }

	        return output;
	      }

	    default:
	      throw new Error(`norm: unknown algorithm: ${algorithm}`);
	  }
	}

	function absoluteSum$2(input) {
	  let sumValue = 0;

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

	  return sumValue;
	}

	var lib$6 = norm;

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

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

	    UTF8-1    = %x00-7F

	    UTF8-2    = %xC2-DF UTF8-tail

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

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

	    UTF8-tail = %x80-BF
	*/

	/**
	 * Check if a Node.js Buffer or Uint8Array is UTF-8.
	 */
	function isUtf8(buf) {
	  if (!buf) {
	    return false;
	  }

	  var i = 0;
	  var len = buf.length;

	  while (i < len) {
	    // UTF8-1 = %x00-7F
	    if (buf[i] <= 0x7F) {
	      i++;
	      continue;
	    } // UTF8-2 = %xC2-DF UTF8-tail


	    if (buf[i] >= 0xC2 && buf[i] <= 0xDF) {
	      // if(buf[i + 1] >= 0x80 && buf[i + 1] <= 0xBF) {
	      if (buf[i + 1] >> 6 === 2) {
	        i += 2;
	        continue;
	      } else {
	        return false;
	      }
	    } // UTF8-3 = %xE0 %xA0-BF UTF8-tail
	    // UTF8-3 = %xED %x80-9F UTF8-tail


	    if ((buf[i] === 0xE0 && buf[i + 1] >= 0xA0 && buf[i + 1] <= 0xBF || buf[i] === 0xED && buf[i + 1] >= 0x80 && buf[i + 1] <= 0x9F) && buf[i + 2] >> 6 === 2) {
	      i += 3;
	      continue;
	    } // UTF8-3 = %xE1-EC 2( UTF8-tail )
	    // UTF8-3 = %xEE-EF 2( UTF8-tail )


	    if ((buf[i] >= 0xE1 && buf[i] <= 0xEC || buf[i] >= 0xEE && buf[i] <= 0xEF) && buf[i + 1] >> 6 === 2 && buf[i + 2] >> 6 === 2) {
	      i += 3;
	      continue;
	    } // UTF8-4 = %xF0 %x90-BF 2( UTF8-tail )
	    //          %xF1-F3 3( UTF8-tail )
	    //          %xF4 %x80-8F 2( UTF8-tail )


	    if ((buf[i] === 0xF0 && buf[i + 1] >= 0x90 && buf[i + 1] <= 0xBF || buf[i] >= 0xF1 && buf[i] <= 0xF3 && buf[i + 1] >> 6 === 2 || buf[i] === 0xF4 && buf[i + 1] >= 0x80 && buf[i + 1] <= 0x8F) && buf[i + 2] >> 6 === 2 && buf[i + 3] >> 6 === 2) {
	      i += 4;
	      continue;
	    }

	    return false;
	  }

	  return true;
	}

	/**
	 * Ensure that the data is string. If it is an ArrayBuffer it will be converted to string using TextDecoder.
	 * @param blob
	 * @param options
	 * @returns
	 */

	function ensureString(blob) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};

	  if (typeof blob === 'string') {
	    return blob;
	  }

	  if (ArrayBuffer.isView(blob) || blob instanceof ArrayBuffer) {
	    const {
	      encoding = guessEncoding(blob)
	    } = options;
	    const decoder = new TextDecoder(encoding);
	    return decoder.decode(blob);
	  }

	  throw new TypeError(`blob must be a string, ArrayBuffer or ArrayBufferView`);
	}

	function guessEncoding(blob) {
	  const uint8 = ArrayBuffer.isView(blob) ? new Uint8Array(blob.buffer, blob.byteOffset, blob.byteLength) : new Uint8Array(blob);

	  if (uint8.length >= 2) {
	    if (uint8[0] === 0xfe && uint8[1] === 0xff) {
	      return 'utf-16be';
	    }

	    if (uint8[0] === 0xff && uint8[1] === 0xfe) {
	      return 'utf-16le';
	    }
	  } //@ts-expect-error an ArrayBuffer is also ok


	  if (!isUtf8(blob)) return 'latin1';
	  return 'utf-8';
	}

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

	/**
	 * Calculates reimAbsolute value of a complex spectrum
	 *
	 * @param data - complex spectrum
	 * @returns - reimAbsolute value
	 */
	function reimAbsolute$1(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;
	}

	var d3Array$1 = {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$1, d3Array$1.exports);

	const {
	  bisectRight: bisectRight$1
	} = d3Array$1.exports;

	const quincunx$1 = (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$1 = (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$1(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$2 {
	  constructor(xIn, yIn) {
	    let lambda = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 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$1(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$1(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() {
	    let step = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 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() {
	    let step = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 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) {
	    let domain = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 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$1 = SplineInterpolator$2;
	var SplineInterpolator$3 = splineInterpolator$1;

	/**
	 * Create an array with numbers between "from" and "to" of length "length"
	 *
	 * @param options - options
	 * @return - array of distributed numbers between "from" and "to"
	 */
	function createFromToArray$1() {
	  let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
	  let {
	    from = 0,
	    to = 1,
	    length = 1000,
	    includeFrom = true,
	    includeTo = true,
	    distribution = 'uniform'
	  } = options;
	  const array = new Float64Array(length);
	  let div = length;

	  if (includeFrom === true && includeTo === true) {
	    div = length - 1;
	  } else if (includeFrom === false && includeTo === true || includeFrom === true && includeTo === false) {
	    div = length;
	  } else if (includeFrom === false && includeTo === false) {
	    div = length + 1;
	  }

	  let delta = (to - from) / div;

	  if (distribution === 'uniform') {
	    if (includeFrom === true) {
	      let index = 0;

	      while (index < length) {
	        array[index] = from + delta * index;
	        index++;
	      }
	    } else {
	      let index = 0;

	      while (index < length) {
	        array[index] = from + delta * (index + 1);
	        index++;
	      }
	    }
	  } else if (distribution === 'log') {
	    let base = (to / from) ** (1 / div);
	    let firstExponent = Math.log(from) / Math.log(base);

	    if (includeFrom === true) {
	      let index = 0;

	      while (index < length) {
	        array[index] = base ** (firstExponent + index);
	        index++;
	      }
	    } else {
	      let index = 0;

	      while (index < length) {
	        array[index] = base ** (firstExponent + index + 1);
	        index++;
	      }
	    }
	  } else {
	    throw new Error('Please choose for the distribution either uniform or log. By default the distribution chosen is uniform.');
	  }

	  return array;
	}

	/* eslint-disable @typescript-eslint/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$1 = 8.91314744949340820313e-2;
	const P1$1 = [-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$1 = [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$1 = 2.249481201171875;
	const P2$1 = [-3.67192254707729348546, 2.11294655448340526258e1, 1.7445385985570866523e1, -4.46382324441786960818e1, -1.88510648058714251895e1, 1.76447298408374015486e1, 8.37050328343119927838, 1.05264680699391713268e-1, -2.02433508355938759655e-1];
	const Q2$1 = [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$1 = 8.07220458984375e-1;
	const P3$1 = [-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 = [1.105924229346489121e-2, 1.52264338295331783612e-1, 8.48854343457902036425e-1, 2.59301921623620271374, 4.77846592945843778382, 5.38168345707006855425, 3.46625407242567245975, 1];
	const Y4$1 = 9.3995571136474609375e-1;
	const P4$1 = [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$1 = [7.64675292302794483503e-5, 2.63861676657015992959e-3, 3.41589143670947727934e-2, 2.20091105764131249824e-1, 7.62059164553623404043e-1, 1.3653349817554063097, 1];
	const Y5$1 = 9.8362827301025390625e-1;
	const P5$1 = [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$1 = [2.82243172016108031869e-7, 2.75335474764726041141e-5, 9.64011807005165528527e-4, 1.60746087093676504695e-2, 1.38151865749083321638e-1, 5.91429344886417493481e-1, 1];
	/**
	 * Polyval.
	 *
	 * @param c - Array of Number.
	 * @param x - Number.
	 * @returns Number.
	 */

	function polyval$1(c, x) {
	  let p = 0;

	  for (const coef of c) {
	    p = p * x + coef;
	  }

	  return p;
	}
	/**
	 * Calculates a rational approximation.
	 *
	 * @private
	 * @param x - Number.
	 * @param v - Number.
	 * @param P - Array of polynomial coefficients.
	 * @param Q - Array of polynomial coefficients.
	 * @param Y - Number.
	 * @returns Rational approximation.
	 */


	function calc$1(x, v, P, Q, Y) {
	  const s = x - v;
	  const r = polyval$1(P, s) / polyval$1(Q, s);
	  return Y * x + r * x;
	}
	/**
	 * Evaluates the complementary inverse error function for an input value.
	 *
	 * @private
	 * @param x - Input value.
	 * @returns Evaluated complementary inverse error function.
	 */


	function erfcinv$1(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$1(P1$1, x) / polyval$1(Q1$1, x);
	    val = g * Y1$1 + 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$1(P2$1, q) / polyval$1(Q2$1, q);
	    val = g / (Y2$1 + r);
	    return sign ? -val : val;
	  }

	  q = Math.sqrt(-Math.log(q)); // [5] q < 3

	  if (q < 3) {
	    return calc$1(q, 1.125, P3$1, Q3$1, Y3$1);
	  } // [6] q < 6


	  if (q < 6) {
	    return calc$1(q, 3, P4$1, Q4$1, Y4$1);
	  } // Note that the smallest number in JavaScript is 5e-324. Math.sqrt( -Math.log( 5e-324 ) ) ~27.2844


	  return calc$1(q, 6, P5$1, Q5$1, Y5$1); // 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.
	}

	/**
	 * RayleighCdf.
	 *
	 * @param x - data
	 * @param sigma - standard deviation
	 * @returns - rayleigh cdf
	 */
	function rayleighCdf$1(x) {
	  let sigma = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 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 - real or magnitude spectra data.
	 * @param options - options
	 * @returns noise level
	 */

	function xNoiseSanPlot$1(array) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const {
	    mask,
	    cutOff,
	    refine = true,
	    magnitudeMode = false,
	    scaleFactor = 1,
	    factorStd = 5,
	    fixOffset = true
	  } = options;
	  let input;

	  if (Array.isArray(mask) && mask.length === array.length) {
	    input = new Float64Array(array.filter((_e, i) => !mask[i]));
	  } else {
	    input = new Float64Array(array);
	  }

	  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$1(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$1(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$1(effectiveCutOffDist / 2, {
	      magnitudeMode
	    });
	    noiseLevelPositive /= refinedCorrectionFactor;

	    if (cutOffSignalsIndexNeg > -1) {
	      effectiveCutOffDist = (cutOffDist * cloneSignNegative.length + cutOffSignalsIndexNeg) / (cloneSignNegative.length + cutOffSignalsIndexNeg);
	      refinedCorrectionFactor = -1 * simpleNormInv$1(effectiveCutOffDist / 2, {
	        magnitudeMode
	      });

	      if (noiseLevelNegative !== 0) {
	        noiseLevelNegative /= refinedCorrectionFactor;
	      }
	    }
	  } else {
	    noiseLevelPositive /= correctionFactor;
	    noiseLevelNegative /= correctionFactor;
	  }

	  return {
	    positive: noiseLevelPositive,
	    negative: noiseLevelNegative,
	    snr: skyPoint / noiseLevelPositive,
	    sanplot: generateSanPlot$1(input, {
	      fromTo: {
	        positive: {
	          from: 0,
	          to: lastPositiveValueIndex
	        },
	        negative: {
	          from: firstNegativeValueIndex,
	          to: input.length
	        }
	      }
	    })
	  };
	}
	/**
	 * DetermineCutOff.
	 *
	 * @param signPositive - Array of numbers.
	 * @param [options = {}] - Options.
	 * @param [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 [options.scaleFactor=1] - Factor to scale the data input[i]*=scaleFactor.
	 * @param [options.cutOff] - Percent of positive signal distribution where the noise level will be determined, if it is not defined the program calculate it.
	 * @param [options.factorStd=5] - Factor times std to determine what will be marked as signals.
	 * @param [options.refine=true] - If true the noise level will be recalculated get out the signals using factorStd.
	 * @param [options.fixOffset=true] - If the baseline is correct, the midpoint of distribution should be zero. If true, the distribution will be centered.
	 * @param [options.logBaseY=2] - Log scale to apply in the intensity axis in order to avoid big numbers.
	 * @param options.magnitudeMode -
	 * @param options.considerList -
	 * @param options.considerList.from -
	 * @param options.considerList.step -
	 * @param options.considerList.to -
	 * @param options.fromTo -
	 * @returns Result.
	 */

	function determineCutOff$1(signPositive) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  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$1([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;
	    elementsOfCutOff.forEach(element => {
	      kiSqrt += Math.pow(element[1] - averageValue, 2);
	    });

	    if (kiSqrt < minKi) {
	      minKi = kiSqrt;
	      whereToCutStat = i;
	    }
	  }

	  return whereToCutStat;
	}
	/**
	 * SimpleNormInvs.
	 *
	 * @param data - Data array.
	 * @param [options = {}] - Options.
	 * @param [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 [options.scaleFactor=1] - Factor to scale the data input[i]*=scaleFactor.
	 * @param [options.cutOff] - Percent of positive signal distribution where the noise level will be determined, if it is not defined the program calculate it.
	 * @param [options.factorStd=5] - Factor times std to determine what will be marked as signals.
	 * @param [options.refine=true] - If true the noise level will be recalculated get out the signals using factorStd.
	 * @param [options.fixOffset=true] - If the baseline is correct, the midpoint of distribution should be zero. If true, the distribution will be centered.
	 * @param [options.logBaseY=2] - Log scale to apply in the intensity axis in order to avoid big numbers.
	 * @param options.magnitudeMode -
	 * @param options.considerList -
	 * @param options.considerList.from -
	 * @param options.considerList.step -
	 * @param options.considerList.to -
	 * @param options.fromTo -
	 * @returns Result.
	 */


	function simpleNormInv$1(data) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const {
	    magnitudeMode = false
	  } = options;
	  if (!Array.isArray(data)) data = [data];
	  let from = 0;
	  let to = 2;
	  let step = 0.01;
	  let xTraining = Array.from(createArray$1(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$1(finalInput);
	    }

	    let interp = new SplineInterpolator$3(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$1(2 * data[i]);
	    }
	  }

	  return result.length === 1 ? result[0] : result;
	}
	/**
	 * CreateArray.
	 *
	 * @param from - From.
	 * @param to - To.
	 * @param step - Step.
	 * @returns Array of results.
	 */


	function createArray$1(from, to, step) {
	  // Changed Array to Float64Array
	  let result = new Float64Array(Math.abs((from - to) / step + 1));

	  for (let i = 0; i < result.length; i++) {
	    result[i] = from + i * step;
	  }

	  return result;
	}
	/**
	 * GenerateSanPlot.
	 *
	 * @param array - Array.
	 * @param [options = {}] - Options.
	 * @param [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 [options.scaleFactor=1] - Factor to scale the data input[i]*=scaleFactor.
	 * @param [options.cutOff] - Percent of positive signal distribution where the noise level will be determined, if it is not defined the program calculate it.
	 * @param [options.factorStd=5] - Factor times std to determine what will be marked as signals.
	 * @param [options.refine=true] - If true the noise level will be recalculated get out the signals using factorStd.
	 * @param [options.fixOffset=true] - If the baseline is correct, the midpoint of distribution should be zero. If true, the distribution will be centered.
	 * @param [options.logBaseY=2] - Log scale to apply in the intensity axis in order to avoid big numbers.
	 * @param options.magnitudeMode -
	 * @param options.considerList -
	 * @param options.considerList.from -
	 * @param options.considerList.step -
	 * @param options.considerList.to -
	 * @param options.fromTo -
	 * @returns Results.
	 */


	function generateSanPlot$1(array) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const {
	    fromTo,
	    logBaseY = 2
	  } = options;
	  let sanplot = {};

	  for (let key in fromTo) {
	    let {
	      from,
	      to
	    } = fromTo[key];
	    sanplot[key] = from !== to ? scale$1(array.slice(from, to), {
	      logBaseY
	    }) : {
	      x: [],
	      y: []
	    };

	    if (key === 'negative') {
	      sanplot[key].y.reverse();
	    }
	  }

	  return sanplot;
	}
	/**
	 * Scale.
	 *
	 * @param array - Array.
	 * @param [options = {}] - Options.
	 * @param [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 [options.scaleFactor=1] - Factor to scale the data input[i]*=scaleFactor.
	 * @param [options.cutOff] - Percent of positive signal distribution where the noise level will be determined, if it is not defined the program calculate it.
	 * @param [options.factorStd=5] - Factor times std to determine what will be marked as signals.
	 * @param [options.refine=true] - If true the noise level will be recalculated get out the signals using factorStd.
	 * @param [options.fixOffset=true] - If the baseline is correct, the midpoint of distribution should be zero. If true, the distribution will be centered.
	 * @param [options.logBaseY=2] - Log scale to apply in the intensity axis in order to avoid big numbers.
	 * @param options.magnitudeMode -
	 * @param options.considerList -
	 * @param options.considerList.from -
	 * @param options.considerList.step -
	 * @param options.considerList.to -
	 * @param options.fromTo -
	 * @returns Results.
	 */


	function scale$1(array) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  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 = createFromToArray$1({
	    from: 0,
	    to: array.length - 1,
	    length: array.length
	  });
	  return {
	    x: xAxis,
	    y: array
	  };
	}

	/**
	 * Phase correction filter
	 *
	 * @param data - complex spectrum
	 * @param phi0 - Angle in radians for zero order phase correction
	 * @param phi1 - Angle in radians for first order phase correction
	 * @returns - returns a new object {re:[], im:[]}
	 */
	function reimPhaseCorrection$1(data) {
	  let phi0 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
	  let phi1 = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
	  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 data - complex spectrum
	 * @param options - options
	 */

	function reimAutoPhaseCorrection$1(data) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const {
	    re,
	    im
	  } = data;
	  const length = re.length;
	  const {
	    magnitudeMode = true,
	    minRegSize = 30,
	    factorNoise = 3,
	    maxDistanceToJoin = 256
	  } = options;
	  let magnitudeData = magnitudeMode ? reimAbsolute$1(data) : re;
	  let ds = holoborodko$1(magnitudeData);
	  let peaksDs = robustBaseLineRegionsDetection$1(ds, {
	    maxDistanceToJoin,
	    magnitudeMode,
	    factorNoise
	  });
	  let peaksSp = robustBaseLineRegionsDetection$1(magnitudeData, {
	    maxDistanceToJoin,
	    magnitudeMode,
	    factorNoise
	  });
	  let finalPeaks = new Uint8Array(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.
	  // 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) {
	      //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$1(reTmp, imTmp, x0));
	    }
	  } // 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$1(res.map(r => r.x0 / length), res.map(r => r.ph0), res.map(r => r.area / 1e11));
	  let phased = reimPhaseCorrection$1({
	    re,
	    im
	  }, ph0 * Math.PI / 180, ph1 * Math.PI / 180);
	  return {
	    data: phased,
	    ph0,
	    ph1
	  };
	}
	/**
	 * AutoPhaseRegion.
	 *
	 * @param re - Array of Number.
	 * @param im - Array of Number.
	 * @param x0 - Number.
	 * @returns Region.
	 */

	function autoPhaseRegion$1(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$1({
	        re,
	        im
	      }, toRadians$1(i), 0);
	      let negArea = getNegArea$1(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$1({
	    re,
	    im
	  }, toRadians$1(bestAng), 0);
	  let area = 0;
	  let sumX = 0;

	  for (let j = 0; j < re.length; j++) {
	    area += phased.re[j];
	    sumX += phased.re[j] * (j + x0);
	  }

	  return {
	    ph0: bestAng,
	    area,
	    x0: sumX / area
	  };
	}
	/**
	 * Holoborodko.
	 *
	 * @param s - Array of float.
	 * @returns Array of float.
	 */


	function holoborodko$1(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;
	}
	/**
	 * RobustBaseLineRegionsDetection.
	 *
	 * @param s
	 * @param options
	 * @param options.magnitudeMode
	 * @param options.maxDistanceToJoin
	 * @param options.factorNoise
	 */


	function robustBaseLineRegionsDetection$1(s, options) {
	  const {
	    maxDistanceToJoin,
	    magnitudeMode,
	    factorNoise
	  } = options;
	  let mask = new Uint8Array(s.length);

	  for (let i = 0; i < s.length; i++) {
	    mask[i] = 0;
	  }

	  let change = true;

	  while (change) {
	    let noiseLevel = xNoiseSanPlot$1(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] = 1;
	      }
	    }
	  } // Clean up mask by merging peaks blocks, separated by just a few points(4??).


	  let count = 0;
	  let prev = 0;

	  for (let i = 0; i < s.length; i++) {
	    if (!mask[i]) {
	      count++;
	    } else {
	      if (count < maxDistanceToJoin) {
	        for (let j = 0; j <= count; j++) {
	          mask[prev + j] = 1;
	        }
	      }

	      while (mask[++i] && i < s.length);

	      prev = i;
	      count = 0;
	    }
	  }

	  return mask;
	}
	/**
	 * WeightedLinearRegression.
	 *
	 * @param x
	 * @param y
	 * @param w
	 */


	function weightedLinearRegression$1(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$1 = degree => degree * Math.PI / 180;

	const getNegArea$1 = data => {
	  let area = 0;

	  for (let element of data) {
	    if (element < 0) area -= element;
	  }

	  return area;
	};

	function FFT$2(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$1 = FFT$2;

	FFT$2.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$2.prototype.createComplexArray = function createComplexArray() {
	  const res = new Array(this._csize);

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

	  return res;
	};

	FFT$2.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$2.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$2.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$2.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$2.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$2.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$2.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$2.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$2.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$2.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$2.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$3 = fft$1;

	/**
	 * This function performs a circular shift to an array
	 * Positive values of shifts will shift to the right and negative values will do to the left
	 *
	 * @example xRotate([1,2,3,4],1) -> [4,1,2,3]
	 * @example xRotate([1,2,3,4],-1) -> [2,3,4,1]
	 * @param array - array
	 * @param shift - shift
	 * @returns - rotated array
	 */
	function xRotate$1(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;
	}

	/**
	 * ReimFFT.
	 *
	 * @param data - complex spectrum
	 * @param options - options.
	 * @returns FFT of complex spectrum.
	 */

	function reimFFT$1(data) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  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$3(size);
	  let output = new Float64Array(csize);

	  if (inverse) {
	    if (applyZeroShift) complexArray = zeroShift$1(complexArray, true);
	    fft.inverseTransform(output, complexArray);
	  } else {
	    fft.transform(output, complexArray);
	    if (applyZeroShift) output = zeroShift$1(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$1 = (data, inverse) => {
	  let middle = inverse ? Math.ceil(data.length / 2) : Math.floor(data.length / 2);
	  return xRotate$1(data, middle);
	};

	/**
	 * This function returns an array with absolute values
	 *
	 * @param array - array of data
	 * @returns - array with absolute values
	 */
	function xAbsolute$1(array) {
	  let tmpArray = array.slice();

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

	  return tmpArray;
	}

	var medianQuickselect_min$2 = {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$2);

	var quickSelectMedian$2 = medianQuickselect_min$2.exports;

	/**
	 * Calculates the median of an array
	 *
	 * @param input - Array containing values
	 * @returns - median
	 */

	function xMedian$1(input) {
	  if (!isAnyArray$9(input)) {
	    throw new TypeError('input must be an array');
	  }

	  if (input.length === 0) {
	    throw new TypeError('input must not be empty');
	  } // we need to slice because the order of elements is changed in the quickselect
	  // https://github.com/mad-gooze/median-quickselect


	  return quickSelectMedian$2(input.slice());
	}

	/**
	 * This function calculates the median after taking the reimAbsolute values of the points
	 *
	 * @param array - the array for which we want to calculate the absolute value
	 * @returns - median
	 */

	function xAbsoluteMedian$1(array) {
	  return xMedian$1(xAbsolute$1(array));
	}

	/**
	 * This function xAdd the first array by the second array or a constant value to each element of the first array
	 *
	 * @param array1 - the first array
	 * @param array2 - the second array or number
	 */

	function xAdd$2(array1, array2) {
	  let isConstant = false;
	  let constant = 0;

	  if (isAnyArray$9(array2)) {
	    if (array1.length !== array2.length) {
	      throw new Error('xAdd: size of array1 and array2 must be identical');
	    }
	  } else {
	    isConstant = true;
	    constant = array2;
	  }

	  let array3 = new Float64Array(array1.length);

	  if (isConstant) {
	    for (let i = 0; i < array1.length; i++) {
	      array3[i] = array1[i] + constant;
	    }
	  } else {
	    for (let i = 0; i < array1.length; i++) {
	      array3[i] = array1[i] + array2[i];
	    }
	  }

	  return array3;
	}

	/**
	 * This function
	 * @param output - undefined or a new array
	 * @param length - length of the output array
	 * @returns
	 */

	function getOutputArray$1(output, length) {
	  if (output !== undefined) {
	    if (!isAnyArray$9(output)) {
	      throw new TypeError('output option must be an array if specified');
	    }

	    if (output.length !== length) {
	      throw new TypeError('the output array does not have the correct length');
	    }

	    return output;
	  } else {
	    return new Float64Array(length);
	  }
	}

	/**
	 * This function xMultiply the first array by the second array or a constant value to each element of the first array
	 *
	 * @param array1 - first array
	 * @param array2 - second array
	 * @param options - options
	 */

	function xMultiply$1(array1, array2) {
	  let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
	  let isConstant = false;
	  let constant = 0;

	  if (isAnyArray$9(array2)) {
	    if (array1.length !== array2.length) {
	      throw new Error('xMultiply: size of array1 and array2 must be identical');
	    }
	  } else {
	    isConstant = true;
	    constant = Number(array2);
	  }

	  let array3 = getOutputArray$1(options.output, array1.length);

	  if (isConstant) {
	    for (let i = 0; i < array1.length; i++) {
	      array3[i] = array1[i] * constant;
	    }
	  } else {
	    for (let i = 0; i < array1.length; i++) {
	      array3[i] = array1[i] * array2[i];
	    }
	  }

	  return array3;
	}

	/**
	 * Dot product between two arrays
	 *
	 * @param A - First array.
	 * @param B - Second array.
	 */

	function xDotProduct$1(A, B) {
	  let g = xMultiply$1(A, B);
	  let result = 0;

	  for (let i = 0; i < A.length; i++) {
	    result += g[i];
	  }

	  return result;
	}

	/**
	 * Calculates the cross-correlation between 2 arrays
	 *
	 * @param A - fixed array
	 * @param B - sweeping array
	 * @param options - Options
	 */

	function xCrossCorrelation$1(A, B) {
	  let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
	  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$1(A, w);
	    }
	  }

	  return result;
	}

	/**
	 * Calculates the auto-correlation of an array
	 *
	 * @param A - the array for which to calculate the auto-correlation
	 * @param options - Options
	 */

	function xAutoCorrelation$1(A) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  return xCrossCorrelation$1(A, A, options);
	}

	/**
	 * Calculating the box plot of the array
	 *
	 * @param array - data
	 */
	function xBoxPlot$1(array) {
	  array = Float64Array.from(array).sort();

	  if (array.length < 5) {
	    throw Error('xBoxPlot: can not calculate info if array contains less than 3 elements');
	  }

	  let info = {
	    q1: 0.0,
	    median: 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.median = array[middle];
	    q1max = middle - 1;
	    q3min = middle + 1;
	  } else {
	    // even
	    q3min = array.length / 2;
	    q1max = q3min - 1;
	    info.median = (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;
	}

	/**
	 * Checks if input is of type array
	 *
	 * @param input - input
	 */

	function xCheck$2(input) {
	  if (!isAnyArray$9(input)) {
	    throw new TypeError('input must be an array');
	  }

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

	/**
	 * Calculates the correlation between 2 vectors
	 * https://en.wikipedia.org/wiki/Correlation_and_dependence
	 *
	 * @param A - first array
	 * @param B - sencond array
	 */
	function xCorrelation$1(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 an array of the same size that is the cumulative values
	 *
	 * @param array - initial array
	 */

	function xCumulative$1(array) {
	  if (!isAnyArray$9(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 array1 - first array
	 * @param array2 - second array or number
	 * @param options - options
	 */

	function xDivide$1(array1, array2) {
	  let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
	  let isConstant = false;
	  let constant = 0;

	  if (isAnyArray$9(array2)) {
	    if (array1.length !== array2.length) {
	      throw new Error('xDivide: size of array1 and array2 must be identical');
	    }
	  } else {
	    isConstant = true;
	    constant = Number(array2);
	  }

	  let array3 = getOutputArray$1(options.output, array1.length);

	  if (isConstant) {
	    for (let i = 0; i < array1.length; i++) {
	      array3[i] = array1[i] / constant;
	    }
	  } else {
	    for (let i = 0; i < array1.length; i++) {
	      array3[i] = array1[i] / array2[i];
	    }
	  }

	  return array3;
	}

	/**
	 * Returns a copy of the data as Float64
	 *
	 * @param array - array of numbers
	 */

	function xEnsureFloat64$1(array) {
	  xCheck$2(array);

	  if (array instanceof Float64Array) {
	    return array.slice(0);
	  }

	  return Float64Array.from(array);
	}

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

	  if (sorted) {
	    let low = 0;
	    let high = array.length - 1;
	    let middle = 0;

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

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

	    if (low < array.length - 1) {
	      if (Math.abs(target - array[low]) < Math.abs(array[low + 1] - target)) {
	        return low;
	      } else {
	        return low + 1;
	      }
	    } else {
	      return low;
	    }
	  } else {
	    let index = 0;
	    let diff = Number.POSITIVE_INFINITY;

	    for (let i = 0; i < array.length; i++) {
	      const currentDiff = Math.abs(array[i] - target);

	      if (currentDiff < diff) {
	        diff = currentDiff;
	        index = i;
	      }
	    }

	    return index;
	  }
	}

	/**
	 * Returns an object with {fromIndex, toIndex} for a specific from / to
	 *
	 * @param x - array of numbers
	 * @param options - Options
	 */

	function xGetFromToIndex$2(x) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  let {
	    fromIndex,
	    toIndex,
	    from,
	    to
	  } = options;

	  if (fromIndex === undefined) {
	    if (from !== undefined) {
	      fromIndex = xFindClosestIndex$3(x, from);
	    } else {
	      fromIndex = 0;
	    }
	  }

	  if (toIndex === undefined) {
	    if (to !== undefined) {
	      toIndex = xFindClosestIndex$3(x, to);
	    } else {
	      toIndex = x.length - 1;
	    }
	  }

	  if (fromIndex < 0) fromIndex = 0;
	  if (toIndex < 0) toIndex = 0;
	  if (fromIndex >= x.length) fromIndex = x.length - 1;
	  if (toIndex >= x.length) toIndex = x.length - 1;
	  if (fromIndex > toIndex) [fromIndex, toIndex] = [toIndex, fromIndex];
	  return {
	    fromIndex,
	    toIndex
	  };
	}

	/**
	 *  Returns the targetIndex
	 *
	 * @param x - array of numbers
	 * @param options - options
	 */

	function xGetTargetIndex$1(x) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  let {
	    target,
	    targetIndex
	  } = options;

	  if (targetIndex === undefined) {
	    if (target !== undefined) {
	      return xFindClosestIndex$3(x, target);
	    } else {
	      return 0;
	    }
	  }

	  return targetIndex;
	}

	/**
	 * Computes the maximal value of an array of values
	 *
	 * @param array - array of numbers
	 * @param options - options
	 */

	function xMaxValue$2(array) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  xCheck$2(array);
	  const {
	    fromIndex,
	    toIndex
	  } = xGetFromToIndex$2(array, options);
	  let maxValue = array[fromIndex];

	  for (let i = fromIndex + 1; i <= toIndex; i++) {
	    if (array[i] > maxValue) {
	      maxValue = array[i];
	    }
	  }

	  return maxValue;
	}

	/**
	 * Computes the minimal value of an array of values
	 *
	 * @param array - array of numbers
	 * @param options - options
	 */

	function xMinValue$1(array) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  xCheck$2(array);
	  const {
	    fromIndex,
	    toIndex
	  } = xGetFromToIndex$2(array, options);
	  let minValue = array[fromIndex];

	  for (let i = fromIndex + 1; i <= toIndex; i++) {
	    if (array[i] < minValue) {
	      minValue = array[i];
	    }
	  }

	  return minValue;
	}

	/**
	 * Calculates a histogram of defined number of slots
	 *
	 * @param array - Array containing values
	 * @param options - options
	 * @returns - result of the histogram
	 */

	function xHistogram$1(array) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  xCheck$2(array);
	  let histogram = options.histogram;
	  const {
	    centerX = true,
	    nbSlots = histogram === undefined ? 256 : histogram.x.length,
	    logBaseX,
	    logBaseY,
	    absolute = false
	  } = options;

	  if (absolute) {
	    array = xAbsolute$1(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$1(array),
	    max = xMaxValue$2(array)
	  } = options;
	  const slotSize = (max - min) / (nbSlots + Number.EPSILON);
	  const y = histogram === undefined ? new Float64Array(nbSlots) : histogram.y;
	  const x = histogram === undefined ? Array.from(createFromToArray$1({
	    from: min + (centerX ? slotSize / 2 : 0),
	    to: max - (centerX ? slotSize / 2 : 0),
	    length: nbSlots
	  })) : histogram.x;
	  array.forEach(element => {
	    const index = Math.max(Math.min((element - 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
	  };
	}

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

	  for (let i = 0; i < array.length - 1; ++i) {
	    let absoluteDifference = array[i + 1] - array[i];

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

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

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

	/**
	 * Returns true if x is monotone
	 *
	 * @param array - array of numbers
	 */
	function 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;
	}

	/**
	 * Returns true if x is monotone
	 *
	 * @param array - array of numbers
	 */
	function xIsMonotoneIncreasing$1(array) {
	  if (array.length < 2) {
	    return true;
	  }

	  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 - array of numbers
	 * @param  options - options
	 * @returns - index
	 */

	function xMaxIndex$1(array) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  xCheck$2(array);
	  const {
	    fromIndex,
	    toIndex
	  } = xGetFromToIndex$2(array, options);
	  let maxIndex = fromIndex;

	  for (let i = fromIndex + 1; i <= toIndex; i++) {
	    if (array[i] > array[maxIndex]) {
	      maxIndex = i;
	    }
	  }

	  return maxIndex;
	}

	/**
	 * Computes the mean value of an array of values
	 *
	 * @param array - array of numbers
	 * @param options - options
	 */

	function xMean$1(array) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  xCheck$2(array);
	  const {
	    fromIndex,
	    toIndex
	  } = xGetFromToIndex$2(array, options);
	  let sumValue = array[fromIndex];

	  for (let i = fromIndex + 1; i <= toIndex; i++) {
	    sumValue += array[i];
	  }

	  return sumValue / (toIndex - fromIndex + 1);
	}

	/**
	 * XCheckLengths.
	 *
	 * @param array1 - First array.
	 * @param array2 - Second array.
	 */
	function xCheckLengths$1(array1, array2) {
	  if (array1.length !== array2.length) {
	    throw new TypeError('Length of array1 and array2 must be identical');
	  }
	}

	/**
	 * This function calculates the mean absolute error
	 *
	 * @param array1 - first array
	 * @param array2 - second array
	 */

	function xMeanAbsoluteError$1(array1, array2) {
	  xCheckLengths$1(array1, array2);
	  let sum = 0;

	  for (let i = 0; i < array1.length; i++) {
	    sum += Math.abs(array1[i] - array2[i]);
	  }

	  return sum / array1.length;
	}

	/**
	 * This function calculates the mean squared error
	 *
	 * @param array1 -first array
	 * @param array2 - second array
	 */

	function xMeanSquaredError$1(array1, array2) {
	  xCheckLengths$1(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;
	}

	/**
	 * This function calculates the median absolute deviation (MAD)
	 * https://en.wikipedia.org/wiki/Median_absolute_deviation
	 * @param array
	 */

	function xMedianAbsoluteDeviation$1(array) {
	  const median = xMedian$1(array);
	  const averageDeviations = new Float64Array(array.length);

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

	  return {
	    median,
	    mad: xMedian$1(averageDeviations)
	  };
	}

	/**
	 * Computes the index of the minimum of the given values
	 *
	 * @param array - array of numbers
	 * @param options - options
	 * @returns - index
	 */

	function xMinIndex$1(array) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  xCheck$2(array);
	  const {
	    fromIndex,
	    toIndex
	  } = xGetFromToIndex$2(array, options);
	  let minIndex = fromIndex;

	  for (let i = fromIndex + 1; i <= toIndex; i++) {
	    if (array[i] < array[minIndex]) {
	      minIndex = i;
	    }
	  }

	  return minIndex;
	}

	/**
	 * Return min and max values of an array
	 *
	 * @param array - array of number
	 * @returns - Object with 2 properties, min and max
	 */

	function xMinMaxValues$1(array) {
	  xCheck$2(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
	  };
	}

	/**
	 * Calculates the mode of an array
	 *
	 * @param input - Array containing values
	 * @returns - mode
	 */
	function xMode$1(input) {
	  if (input.length === 0) {
	    throw new TypeError('input must not be empty');
	  }

	  let maxValue = 0;
	  let maxCount = 0;
	  let count = 0;
	  let counts = {};

	  for (let i = 0; i < input.length; ++i) {
	    let element = input[i];
	    count = counts[element];

	    if (count) {
	      counts[element]++;
	      count++;
	    } else {
	      counts[element] = count = 1;
	    }

	    if (count > maxCount) {
	      maxCount = count;
	      maxValue = input[i];
	    }
	  }

	  return maxValue;
	}

	/**
	 * Determine noise level using MAD https://en.wikipedia.org/wiki/Median_absolute_deviation
	 * Constant to convert mad to sd calculated using https://www.wolframalpha.com/input?i=sqrt%282%29+inverse+erf%280.5%29
	 * This assumes a gaussian distribution of the noise
	 * @param array
	 * @returns noise level corresponding to one standard deviation
	 */

	function xNoiseStandardDeviation$1(array) {
	  const {
	    mad,
	    median
	  } = xMedianAbsoluteDeviation$1(array);
	  return {
	    sd: mad / 0.6744897501960817,
	    mad,
	    median
	  };
	}

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

	/**
	 * Calculate the sum of the values
	 *
	 * @param array - Object that contains property x (an ordered increasing array) and y (an array).
	 * @param options - Options.
	 * @returns XSum value on the specified range.
	 */

	function xSum$1(array) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  xCheck$2(array);
	  const {
	    fromIndex,
	    toIndex
	  } = xGetFromToIndex$2(array, options);
	  let sumValue = array[fromIndex];

	  for (let i = fromIndex + 1; i <= toIndex; i++) {
	    sumValue += array[i];
	  }

	  return sumValue;
	}

	/**
	 * Divides the data with either the sum, the absolute sum or the maximum of the data
	 * @param array - Array containing values
	 * @param options - options
	 * @returns - normalized data
	 */

	function xNormed$1(input) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const {
	    algorithm = 'absolute',
	    value = 1
	  } = options;
	  xCheck$2(input);
	  const output = getOutputArray$1(options.output, input.length);

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

	  switch (algorithm.toLowerCase()) {
	    case 'absolute':
	      {
	        let absoluteSumValue = absoluteSum$1(input) / value;

	        if (absoluteSumValue === 0) {
	          throw new Error('xNormed: trying to divide by 0');
	        }

	        for (let i = 0; i < input.length; i++) {
	          output[i] = input[i] / absoluteSumValue;
	        }

	        return output;
	      }

	    case 'max':
	      {
	        let currentMaxValue = xMaxValue$2(input);

	        if (currentMaxValue === 0) {
	          throw new Error('xNormed: trying to divide by 0');
	        }

	        const factor = value / currentMaxValue;

	        for (let i = 0; i < input.length; i++) {
	          output[i] = input[i] * factor;
	        }

	        return output;
	      }

	    case 'sum':
	      {
	        let sumFactor = xSum$1(input) / value;

	        if (sumFactor === 0) {
	          throw new Error('xNormed: trying to divide by 0');
	        }

	        for (let i = 0; i < input.length; i++) {
	          output[i] = input[i] / sumFactor;
	        }

	        return output;
	      }

	    default:
	      throw new Error(`norm: unknown algorithm: ${algorithm}`);
	  }
	}

	function absoluteSum$1(input) {
	  let sumValue = 0;

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

	  return sumValue;
	}

	/**
	 * This function pads an array
	 *s
	 * @param array - the array that will be padded
	 * @param options - options
	 */

	function xPadding$1(array) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const {
	    size = 0,
	    value = 0,
	    algorithm = ''
	  } = options;
	  xCheck$2(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;
	}

	/** Finds the variance of the data
	 *
	 * @param values - the values of the array
	 * @param options - options
	 * @returns variance
	 */

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

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

	  const {
	    unbiased = true,
	    mean = xMean$1(values)
	  } = options;
	  let sqrError = 0;

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

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

	/** Finds the standard deviation for the data at hand
	 *
	 * @param values - values in the data
	 * @param options - options
	 * @returns standard deviation
	 */

	function xStandardDeviation$1(values) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  return Math.sqrt(xVariance$1(values, options));
	}

	/**
	 * Pareto scaling, which uses the square root of standard deviation as the scaling factor, circumvents the amplification of noise by retaining a small portion of magnitude information.
	 * Noda, I. (2008). Scaling techniques to enhance two-dimensional correlation spectra. Journal of Molecular Structure, 883, 216-227.
	 * DOI: 10.1016/j.molstruc.2007.12.026
	 *
	 * @param array - array of number
	 */

	function xParetoNormalization$1(array) {
	  xCheck$2(array);
	  let result = [];
	  const sqrtSD = Math.sqrt(xStandardDeviation$1(array));

	  for (let item of array) {
	    result.push(item / sqrtSD);
	  }

	  return result;
	}

	/** Function used to rescale data
	 *
	 * @param input - input for the rescale
	 * @param options - options
	 * @returns rescaled data
	 */

	function xRescale$1(input) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  xCheck$2(input);
	  const output = getOutputArray$1(options.output, input.length);
	  const currentMin = xMinValue$1(input);
	  const currentMax = xMaxValue$2(input);

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

	  const {
	    min = 0,
	    max = 1
	  } = options;

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

	  const factor = (max - min) / (currentMax - currentMin);

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

	  return output;
	}

	/**
	 * This function calculates a rolling average
	 *
	 * @param array - array
	 * @param fct - callback function that from an array returns a value
	 * @param options - options
	 */

	function xRolling$1(array, fct) {
	  let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
	  xCheck$2(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$1(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++) {
	    // we will send a view to the original buffer
	    newArray.push(fct(array.subarray(i, i + window)));
	  }

	  return newArray;
	}

	/**
	 * This function calculates a rolling average
	 *
	 * @param array - array
	 * @param options - option
	 */

	function xRollingAverage$1(array) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  return xRolling$1(array, xMean$1, options);
	}

	/**
	 * This function calculates a maximum within a rolling window
	 *
	 * @param array - array
	 * @param options - options
	 */

	function xRollingMax$1(array) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  return xRolling$1(array, xMaxValue$2, options);
	}

	/**
	 * This function calculates a rolling average
	 *
	 * @param array - array
	 * @param options - options
	 */

	function xRollingMedian$1(array) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  return xRolling$1(array, xMedian$1, options);
	}

	/**
	 * This function calculates a minimum within a rolling window
	 *
	 * @param array - array
	 * @param options - options
	 */

	function xRollingMin$1(array) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  return xRolling$1(array, xMinValue$1, options);
	}

	/**
	 * Sample within the array
	 *
	 * @param array - array from which to sample
	 * @param options - options
	 * @return - array with evenly spaced elements
	 */
	function xSampling$1(array) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const {
	    length = 10
	  } = options;
	  let returnArray = [];

	  if (length > array.length) {
	    throw new Error('Choose sample number smaller than the number of elements in the array');
	  }

	  let clonedArray = array.slice();
	  returnArray.push(clonedArray[0]);
	  clonedArray.shift();
	  let delta = Math.floor(clonedArray.length / (length - 1));

	  for (let i = delta - 1, j = 0; i < clonedArray.length && j < length - 1; i = i + delta, j++) {
	    returnArray.push(clonedArray[i]);
	  }

	  return returnArray;
	}

	/** Fill an array with sequential numbers
	 *
	 * @param input - optional destination array (if not provided a new array will be created)
	 * @param options - options
	 * @return array with sequential numbers
	 */

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

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

	  if (!isAnyArray$9(input)) {
	    throw new TypeError('input must be an array');
	  } // maybe should not specify default step and size


	  let {
	    from = 0,
	    to = 10,
	    size = Array.isArray(input) || input.constructor === Float64Array || input.constructor === Uint16Array ? input.length : 0,
	    step = null
	  } = options;

	  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 (let i = 0; i < size; i++) {
	      input.push(from);
	      if (step) from += step;
	    }
	  } else {
	    if (Array.isArray(input) || input.constructor === Float64Array || input.constructor === Uint16Array && input.length !== size) {
	      throw new Error('sequentialFill typed array must have the correct length');
	    }

	    for (let i = 0; i < size; i++) {
	      if (Array.isArray(input) || input.constructor === Float64Array || input.constructor === Uint16Array) {
	        input[i] = from;
	      }

	      if (step) {
	        from += step;
	      }
	    }
	  }

	  return Array.isArray(input) || input.constructor === Float64Array || input.constructor === Uint16Array ? Array.from(input) : [];
	}

	/**
	 * Will apply a function on each element of the array described as a string
	 * By default we will use as variable 'x'
	 * In front of sequence of lowercase we will add 'Math.'. This allows to write
	 * `sin(x) + cos(x)` and it will be replace internally by (x) => (Math.sin(x) + Math.cos(x))
	 * @param array
	 * @param options
	 * @returns
	 */
	function xApplyFunctionStr$1(array) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const {
	    variableLabel = 'x',
	    fctString = variableLabel
	  } = options;
	  const fct = new Function(variableLabel, `return Number(${fctString.replace(/(^|\W)([a-z]{2,})/g, '$1Math.$2').replace(/Math\.Math/g, 'Math')})`);
	  const toReturn = Float64Array.from(array);

	  for (let i = 0; i < array.length; i++) {
	    toReturn[i] = fct(array[i]);

	    if (isNaN(toReturn[i])) {
	      throw Error(`The callback ${fctString} does not return a number: ${array[i]}`);
	    }
	  }

	  return toReturn;
	}

	/**
	 * This function xSubtract the first array by the second array or a constant value from each element of the first array
	 *
	 * @param array1 - the array that will be rotated
	 * @param array2 - second array or number
	 * @returns array after subtraction
	 */

	function xSubtract$1(array1, array2) {
	  let isConstant = false;
	  let constant = 0;

	  if (isAnyArray$9(array2)) {
	    if (array1.length !== array2.length) {
	      throw new Error('xSubtract: 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;
	}

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

	/**
	 * 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 data1 - First spectrum data
	 * @param data2 - Second spectrum data
	 * @param options - Options
	 */
	function xyAlign$1(data1, data2) {
	  let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
	  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;
	}

	/**
	 * Throw an error in no an object of x,y arrays
	 *
	 * @param data - array of points {x,y,z}
	 */

	function xyCheck$1(data) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const {
	    minLength
	  } = options;

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

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

	  if (minLength) {
	    if (data.x.length < minLength) {
	      throw new Error(`data.x must have a length of at least ${minLength}`);
	    }
	  }
	}

	/**
	 * Finds the closest point
	 *
	 * @param data - x array should be sorted and ascending
	 * @param target - target to search
	 * @returns - closest point
	 */

	function xyFindClosestPoint$1(
	/** points */
	data, target) {
	  const {
	    x,
	    y
	  } = data;
	  const index = xFindClosestIndex$3(x, target);
	  return {
	    x: x[index],
	    y: y[index]
	  };
	}

	/**Finds the covariance of the points
	 *
	 * @param data
	 * @param options
	 * @return the covariance
	 */

	function xyCovariance$1(data) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const {
	    x,
	    y
	  } = data;
	  const {
	    unbiased = true
	  } = options;
	  const meanX = xMean$1(x);
	  const meanY = xMean$1(y);
	  let error = 0;

	  for (let i = 0; i < x.length; i++) {
	    error += (x[i] - meanX) * (y[i] - meanY);
	  }

	  if (unbiased) {
	    return error / (x.length - 1);
	  } else {
	    return error / x.length;
	  }
	}

	/**
	 * Finds the max y value in a range and return a {x,y} point
	 *
	 * @param data - Object that contains property x (an ordered increasing array) and y (an array)
	 * @param options Options
	 */

	function xyMaxYPoint$1(data) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  xyCheck$1(data, {
	    minLength: 1
	  });
	  const {
	    x,
	    y
	  } = data;

	  if (x.length === 1) {
	    return {
	      x: x[0],
	      y: y[0],
	      index: 0
	    };
	  }

	  const {
	    fromIndex,
	    toIndex
	  } = xGetFromToIndex$2(x, options);
	  let current = {
	    x: x[fromIndex],
	    y: y[fromIndex],
	    index: fromIndex
	  };

	  for (let i = fromIndex; i <= toIndex; i++) {
	    if (y[i] > current.y) current = {
	      x: x[i],
	      y: y[i],
	      index: i
	    };
	  }

	  return current;
	}

	const STEPS$1 = [0.25, 0.5, 0.75];
	/** Cumulative Distribution Statistics
	 *
	 * @param data - array of points {x,y}
	 * @returns x0, x25, x50, x75, x100, mode (x for maxY)
	 */

	function xyCumulativeDistributionStatistics$1(data) {
	  xyCheck$1(data);
	  const {
	    x,
	    y
	  } = data;

	  if (x.length === 0) {
	    throw new Error('xyCumulativeDistributionStatistics: Array length must be greater than 0');
	  }

	  const cumulativeSum = xCumulative$1(y);
	  const maxY = xMaxValue$2(cumulativeSum);

	  for (let i = 0; i < cumulativeSum.length; i++) {
	    cumulativeSum[i] /= maxY;
	  } // eslint-disable-next-line @typescript-eslint/no-explicit-any


	  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$1[currentStep] < cumulativeSum[i]) {
	      result[`x${STEPS$1[currentStep] * 100}`] = x[i - 1] + (x[i] - x[i - 1]) * ((STEPS$1[currentStep] - cumulativeSum[i - 1]) / (cumulativeSum[i] - cumulativeSum[i - 1]));
	      currentStep++;
	      if (currentStep === STEPS$1.length) break breakPoint;
	    }
	  }

	  result.xMode = xyMaxYPoint$1(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 data - Object that contains property x (an ordered increasing array) and y (an array).
	 */

	function xyEnsureGrowingX$2(data) {
	  xyCheck$1(data);
	  if (xIsMonotoneIncreasing$1(data.x)) return data;
	  const x = Array.from(data.x);
	  const y = Array.from(data.y);
	  let prevX = Number.NEGATIVE_INFINITY;
	  let currentIndex = 0;

	  for (let index = 0; index < x.length; index++) {
	    if (prevX < x[index]) {
	      if (currentIndex < index) {
	        x[currentIndex] = x[index];
	        y[currentIndex] = y[index];
	      }

	      currentIndex++;
	      prevX = x[index];
	    }
	  }

	  x.length = currentIndex;
	  y.length = currentIndex;
	  return {
	    x,
	    y
	  };
	}

	/**
	 * Normalize an array of zones:
	 * - ensure than from < to
	 * - merge overlapping zones
	 * - deal with exclusions zones
	 * - if no zones is specified add one between -Infinity and +Infinity
	 * @param zones - array of zones
	 * @param options - options
	 * @returns array of zones
	 */
	function zonesNormalize$1() {
	  let zones = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  let {
	    from = Number.NEGATIVE_INFINITY,
	    to = Number.POSITIVE_INFINITY,
	    exclusions = []
	  } = options;
	  if (from > to) [from, to] = [to, from];
	  zones = JSON.parse(JSON.stringify(zones)).map(zone => zone.from > zone.to ? {
	    from: zone.to,
	    to: zone.from
	  } : zone);
	  zones = zones.sort((a, b) => {
	    if (a.from !== b.from) return a.from - b.from;
	    return a.to - b.to;
	  });

	  if (zones.length === 0) {
	    zones.push({
	      from,
	      to
	    });
	  }

	  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 beforeExclusionsZones = [currentZone];

	  for (let i = 1; i < zones.length; i++) {
	    let zone = zones[i];

	    if (zone.from <= currentZone.to) {
	      if (currentZone.to < zone.to) {
	        currentZone.to = zone.to;
	      }
	    } else {
	      currentZone = zone;
	      beforeExclusionsZones.push(currentZone);
	    }
	  }

	  if (exclusions.length === 0) return beforeExclusionsZones;
	  const normalizedExclusions = zonesNormalize$1(exclusions);
	  let currentExclusionIndex = 0;
	  const results = [];
	  let counter = 0;

	  for (let zoneIndex = 0; zoneIndex < beforeExclusionsZones.length; zoneIndex++) {
	    if (counter++ > 5) break;
	    const zone = beforeExclusionsZones[zoneIndex];

	    if (currentExclusionIndex === normalizedExclusions.length) {
	      // we analysed all the exclusion zones
	      results.push(zone);
	      continue;
	    }

	    while (currentExclusionIndex < normalizedExclusions.length && normalizedExclusions[currentExclusionIndex].to <= zone.from) {
	      currentExclusionIndex++;
	    }

	    if (currentExclusionIndex === normalizedExclusions.length) {
	      // we analysed all the exclusion zones
	      results.push(zone);
	      continue;
	    }

	    if (zone.to < normalizedExclusions[currentExclusionIndex].from) {
	      // no problems, not yet in exclusion
	      results.push(zone);
	      continue;
	    }

	    if (normalizedExclusions[currentExclusionIndex].to >= zone.to) {
	      // could be totally excluded
	      if (normalizedExclusions[currentExclusionIndex].from <= zone.from) {
	        continue;
	      }

	      results.push({
	        from: normalizedExclusions[currentExclusionIndex].to,
	        to: zone.to
	      });
	    } // we cut in the middle, we need to create more zones, annoying !


	    if (normalizedExclusions[currentExclusionIndex].from > zone.from) {
	      results.push({
	        from: zone.from,
	        to: normalizedExclusions[currentExclusionIndex].from
	      });
	    }

	    zone.from = normalizedExclusions[currentExclusionIndex].to;
	    zoneIndex--;
	  }

	  return results;
	}

	/**
	 * Add the number of points per zone to reach a specified total
	 *
	 * @param zones - array of zones
	 * @param numberOfPoints - total number of points to distribute between zones
	 * @param options - options
	 * @returns array of zones with points
	 */

	function zonesWithPoints$1() {
	  let zones = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
	  let numberOfPoints = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 10;
	  let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
	  if (zones.length === 0) return zones;
	  let returnZones = zonesNormalize$1(zones, options);
	  const totalSize = returnZones.reduce((previous, current) => {
	    return previous + (current.to - current.from);
	  }, 0);
	  let unitsPerPoint = totalSize / numberOfPoints;
	  let currentTotal = 0;

	  for (let i = 0; i < returnZones.length - 1; i++) {
	    let zone = returnZones[i];
	    zone.numberOfPoints = Math.min(Math.round((zone.to - zone.from) / unitsPerPoint), numberOfPoints - currentTotal);
	    currentTotal += zone.numberOfPoints;
	  }

	  let zone = returnZones[returnZones.length - 1];
	  zone.numberOfPoints = numberOfPoints - currentTotal;
	  return returnZones;
	}

	/**
	 * function that retrieves the getEquallySpacedData with the variant "slot"
	 *
	 * @param x
	 * @param y
	 * @param from
	 * @param to
	 * @param numberOfPoints
	 * @return Array of y's equally spaced with the variant "slot"
	 */
	function equallySpacedSlot$1(
	/** x coordinates */
	x,
	/** y coordinates */
	y,
	/** from value */
	from,
	/** to value */
	to,
	/** number of points */
	numberOfPoints) {
	  let xLength = x.length;
	  let step = (to - from) / (numberOfPoints > 1 ? numberOfPoints - 1 : 1);
	  let halfStep = step / 2;
	  let lastStep = x[x.length - 1] - x[x.length - 2];
	  let start = from - halfStep; // Changed Array to Float64Array

	  let output = new Float64Array(numberOfPoints); // Init main variables

	  let min = start;
	  let max = start + step;
	  let previousX = -Number.MAX_VALUE;
	  let previousY = 0;
	  let nextX = x[0];
	  let nextY = y[0];
	  let frontOutsideSpectra = 0;
	  let backOutsideSpectra = true;
	  let currentValue = 0; // for slot algorithm

	  let currentPoints = 0;
	  let i = 1; // index of input

	  let j = 0; // index of output

	  main: while (true) {
	    if (previousX >= nextX) throw new Error('x must be a growing series');

	    while (previousX - max > 0) {
	      // no overlap with original point, just consume current value
	      if (backOutsideSpectra) {
	        currentPoints++;
	        backOutsideSpectra = false;
	      }

	      output[j] = currentPoints <= 0 ? 0 : currentValue / currentPoints;
	      j++;

	      if (j === numberOfPoints) {
	        break main;
	      }

	      min = max;
	      max += step;
	      currentValue = 0;
	      currentPoints = 0;
	    }

	    if (previousX > min) {
	      currentValue += previousY;
	      currentPoints++;
	    }

	    if (previousX === -Number.MAX_VALUE || frontOutsideSpectra > 1) {
	      currentPoints--;
	    }

	    previousX = nextX;
	    previousY = nextY;

	    if (i < xLength) {
	      nextX = x[i];
	      nextY = y[i];
	      i++;
	    } else {
	      nextX += lastStep;
	      nextY = 0;
	      frontOutsideSpectra++;
	    }
	  }

	  return output;
	}

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

	/**
	 * function that retrieves the getEquallySpacedData with the variant "smooth"
	 *
	 * @param x
	 * @param y
	 * @param from
	 * @param to
	 * @param numberOfPoints
	 * @return - Array of y's equally spaced with the variant "smooth"
	 */

	function equallySpacedSmooth$1(
	/** x coordinates */
	x,
	/** y coordinates */
	y,
	/** from value */
	from,
	/** to value */
	to,
	/** number of points */
	numberOfPoints) {
	  let xLength = x.length;
	  let step = (to - from) / (numberOfPoints > 1 ? numberOfPoints - 1 : 1);
	  let halfStep = step / 2; // Changed Array to Float64Array

	  let output = new Float64Array(numberOfPoints);
	  let initialOriginalStep = x[1] - x[0];
	  let lastOriginalStep = x[xLength - 1] - x[xLength - 2]; // Init main variables

	  let min = from - halfStep;
	  let max = from + halfStep;
	  let previousX = Number.MIN_SAFE_INTEGER;
	  let previousY = 0;
	  let nextX = x[0] - initialOriginalStep;
	  let nextY = 0;
	  let currentValue = 0;
	  let slope = 0;
	  let intercept = 0;
	  let sumAtMin = 0;
	  let sumAtMax = 0;
	  let i = 0; // index of input

	  let j = 0; // index of output

	  function getSlope(x0, y0, x1, y1) {
	    return (y1 - y0) / (x1 - x0);
	  }

	  let add = 0;

	  main: while (true) {
	    if (previousX >= nextX) throw new Error('x must be a growing series');

	    if (previousX <= min && min <= nextX) {
	      add = integral$1(0, min - previousX, slope, previousY);
	      sumAtMin = currentValue + add;
	    }

	    while (nextX - max >= 0) {
	      // no overlap with original point, just consume current value
	      add = integral$1(0, max - previousX, slope, previousY);
	      sumAtMax = currentValue + add;
	      output[j++] = (sumAtMax - sumAtMin) / step;

	      if (j === numberOfPoints) {
	        break main;
	      }

	      min = max;
	      max += step;
	      sumAtMin = sumAtMax;
	    }

	    currentValue += integral$1(previousX, nextX, slope, intercept);
	    previousX = nextX;
	    previousY = nextY;

	    if (i < xLength) {
	      nextX = x[i];
	      nextY = y[i];
	      i++;
	    } else if (i === xLength) {
	      nextX += lastOriginalStep;
	      nextY = 0;
	    }

	    slope = getSlope(previousX, previousY, nextX, nextY);
	    intercept = -slope * previousX + previousY;
	  }

	  return output;
	}

	/**
	 * Function that returns a Number array of equally spaced numberOfPoints
	 * containing a representation of intensities of the spectra arguments x
	 * and y.
	 *
	 * The options parameter contains an object in the following form:
	 * from: starting point
	 * to: last point
	 * numberOfPoints: number of points between from and to
	 * variant: "slot" or "smooth" - smooth is the default option
	 *
	 * The slot variant consist that each point in an array is calculated
	 * averaging the existing points between the slot that belongs to the current
	 * value. The smooth variant is the same but takes the integral of the range
	 * of the slot and divide by the step size between two points in an array.
	 *
	 * If exclusions zone are present, zones are ignored !
	 *
	 * @param data - object containing 2 properties x and y
	 * @param options - options
	 * @return new object with x / y array with the equally spaced data.
	 */

	function xyEquallySpaced$1(data) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  let {
	    x,
	    y
	  } = data;
	  let xLength = x.length;
	  const {
	    from = x[0],
	    to = x[xLength - 1],
	    variant = 'smooth',
	    numberOfPoints = 100,
	    exclusions = [],
	    zones = [{
	      from,
	      to
	    }]
	  } = options;

	  if (from > to) {
	    throw new RangeError('from should be larger than to');
	  }

	  xyCheck$1(data);

	  if (numberOfPoints < 2) {
	    throw new RangeError("'numberOfPoints' option must be greater than 1");
	  }

	  const normalizedZones = zonesNormalize$1(zones, {
	    from,
	    to,
	    exclusions
	  });
	  const zonesWithPointsRes = zonesWithPoints$1(normalizedZones, numberOfPoints, {
	    from,
	    to
	  });
	  let xResult = [];
	  let yResult = [];

	  for (let zone of zonesWithPointsRes) {
	    if (!zone.numberOfPoints) {
	      zone.numberOfPoints = 0;
	    }

	    let zoneResult = processZone$1(Array.from(x), Array.from(y), zone.from, zone.to, zone.numberOfPoints, variant);
	    xResult = xResult.concat(zoneResult.x);
	    yResult = yResult.concat(zoneResult.y);
	  }

	  return {
	    x: xResult,
	    y: yResult
	  };
	}

	function processZone$1(x, y, from, to, numberOfPoints, variant) {
	  if (numberOfPoints < 1) {
	    throw new RangeError('the number of points must be at least 1');
	  }

	  let output = variant === 'slot' ? Array.from(equallySpacedSlot$1(x, y, from, to, numberOfPoints)) : Array.from(equallySpacedSmooth$1(x, y, from, to, numberOfPoints));
	  return {
	    x: Array.from(createFromToArray$1({
	      from,
	      to,
	      length: numberOfPoints
	    })),
	    y: output
	  };
	}

	/**
	 * XyExtract zones from a XY data
	 *
	 * @param data - Object that contains property x (an ordered increasing array) and y (an array)
	 * @param options - options
	 * @returns - Array of points
	 */

	function xyExtract$1(data) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  xyCheck$1(data);
	  const {
	    x,
	    y
	  } = data;
	  let {
	    zones
	  } = options;
	  zones = zonesNormalize$1(zones);

	  if (x === undefined || y === undefined || !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 an array x/y based on various criteria x points are expected to be sorted
	 *
	 * @param data - points
	 * @param options - options
	 * @return filtered array
	 */

	function xyFilterX$1(data) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const {
	    x,
	    y
	  } = data;
	  const {
	    from = x[0],
	    to = x[x.length - 1],
	    zones = [{
	      from,
	      to
	    }],
	    exclusions = []
	  } = options;
	  let normalizedZones = zonesNormalize$1(zones, {
	    from,
	    to,
	    exclusions
	  });
	  let currentZoneIndex = 0;
	  let newX = [];
	  let newY = [];
	  let position = 0;

	  while (position < x.length) {
	    if (x[position] <= normalizedZones[currentZoneIndex].to && x[position] >= normalizedZones[currentZoneIndex].from) {
	      newX.push(x[position]);
	      newY.push(y[position]);
	    } else {
	      if (x[position] > normalizedZones[currentZoneIndex].to) {
	        currentZoneIndex++;
	        if (!normalizedZones[currentZoneIndex]) break;
	      }
	    }

	    position++;
	  }

	  return {
	    x: newX,
	    y: newY
	  };
	}

	/**
	 * Filter out all the points for which x <= 0. Useful to display log scale data
	 *
	 * @param data - data
	 * @returns - An object with the filtered data
	 */

	function xyFilterXPositive$1(data) {
	  xyCheck$1(data);
	  const {
	    x,
	    y
	  } = data;
	  const newX = [];
	  const newY = [];
	  if (x === undefined || y === undefined) return {
	    x: newX,
	    y: newY
	  };

	  for (let i = 0; i < x.length; i++) {
	    if (x[i] > 0) {
	      newX.push(x[i]);
	      newY.push(y[i]);
	    }
	  }

	  return {
	    x: newX,
	    y: newY
	  };
	}

	/**
	 * Returns the numberMaxPoints points with the bigger y.
	 *
	 * @param data - Object that contains property x (an ordered increasing array) and y (an array)
	 * @param numberMaxPoints - Number of points to keep
	 * @returns - The points filtered to keep the `numberMaxPoints` most intense points of the input.
	 */

	function xyGetNMaxY$1(data, numberMaxPoints) {
	  xyCheck$1(data);

	  if (data.x.length <= numberMaxPoints) {
	    return data;
	  } else {
	    let newX = new Float64Array(numberMaxPoints);
	    let newY = new Float64Array(numberMaxPoints); // slice() is used to make a copy of the array, because sort() is IPM

	    let threshold = Float64Array.from(data.y).sort().reverse()[numberMaxPoints - 1];
	    let index = 0;

	    for (let i = 0; i < data.x.length; i++) {
	      if (data.y[i] >= threshold) {
	        newX[index] = data.x[i];
	        newY[index] = data.y[i];
	        index++;
	      }

	      if (index === numberMaxPoints) {
	        return {
	          x: newX,
	          y: newY
	        };
	      }
	    }

	    return data;
	  }
	}

	/**
	 * Order object of array, x has to be monotone. Ensure x is growing
	 *
	 * @param data - Object of kind {x:[], y:[]}.
	 */
	function xyGrowingX$1(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 data - Object that contains property x (an ordered increasing array) and y (an array)
	 * @param options - Options
	 * @returns - An object with the xyIntegration function
	 */

	function xyIntegral$1(data) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const {
	    reverse = false
	  } = options;
	  xyCheck$1(data, {
	    minLength: 1
	  });
	  const {
	    x,
	    y
	  } = data;
	  const {
	    fromIndex,
	    toIndex
	  } = xGetFromToIndex$2(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 data - Object that contains property x (an ordered increasing array) and y (an array)
	 * @param options - Options
	 * @returns - xyIntegration value on the specified range
	 */

	function xyIntegration$1(data) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  xyCheck$1(data, {
	    minLength: 1
	  });
	  const {
	    x,
	    y
	  } = data;
	  if (x.length === 1) return 0;
	  const {
	    fromIndex,
	    toIndex
	  } = xGetFromToIndex$2(x, options);
	  let currentxyIntegration = 0;

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

	  return currentxyIntegration;
	}

	/**
	 * Join x / y values when difference in X is closer than delta.
	 * When joining, y values are summed and x values are weighted average
	 *
	 * @param data - Object that contains property x (an ordered increasing array) and y (an array)
	 * @param options - Options
	 * @returns - An object with the xyIntegration function
	 */

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

	/**
	 * Find the closest maximum going up hill
	 *
	 * @param data - Object that contains property x (an ordered increasing array) and y (an array)
	 * @param options - options
	 * @returns - An object with the x/y value
	 */

	function xyMaxClosestYPoint$1(data) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  xyCheck$1(data);
	  const {
	    x,
	    y
	  } = data;
	  let {
	    target,
	    targetIndex
	  } = options;

	  if (targetIndex === undefined) {
	    if (target !== undefined) {
	      targetIndex = xFindClosestIndex$3(x, target);
	    } else {
	      targetIndex = 0;
	    }
	  }

	  let previousIndex = Number.MIN_SAFE_INTEGER;
	  let currentIndex = targetIndex;
	  let xyMaxY = y[targetIndex];

	  while (currentIndex !== previousIndex) {
	    previousIndex = currentIndex;

	    if (currentIndex > 0 && y[currentIndex - 1] > xyMaxY) {
	      currentIndex--;
	    } else if (currentIndex < x.length - 1 && y[currentIndex + 1] > xyMaxY) {
	      currentIndex++;
	    }

	    xyMaxY = y[currentIndex];
	  }

	  return {
	    x: x[currentIndex],
	    y: y[currentIndex],
	    index: currentIndex
	  };
	}

	/**
	 * Finds all the max values
	 * If the values are equal the middle
	 * of the equal part will be the position of the signal!
	 *
	 * @param data - Object that contains property x (an ordered increasing array) and y (an array)
	 * @returns - Array of points
	 */

	function xyMaximaY$1(data) {
	  xyCheck$1(data, {
	    minLength: 2
	  });
	  const {
	    x,
	    y
	  } = data;
	  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;
	}

	/**
	 * Merge abscissas values on similar ordinates and weight the group of abscissas
	 *
	 * @param data - points
	 * @param options - options
	 * @return array of merged and weighted points
	 */
	function xyMaxMerge$1(data) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const {
	    x,
	    y
	  } = data;
	  const {
	    groupWidth = 0.001
	  } = options;
	  let merged = {
	    x: [],
	    y: []
	  };
	  let maxAbscissa = {
	    x: [],
	    y: []
	  };
	  let size = 0;
	  let index = 0;

	  while (index < x.length) {
	    if (size === 0 || x[index] - merged.x[size - 1] > groupWidth) {
	      maxAbscissa.x.push(x[index]);
	      maxAbscissa.y.push(y[index]);
	      merged.x.push(x[index]);
	      merged.y.push(y[index]);
	      index++;
	      size++;
	    } else {
	      if (y[index] > maxAbscissa.y[size - 1]) {
	        maxAbscissa.x[size - 1] = x[index];
	        maxAbscissa.y[size - 1] = y[index];
	      }

	      merged.x[size - 1] = x[index];
	      merged.y[size - 1] += y[index];
	      index++;
	    }
	  }

	  merged.x = maxAbscissa.x.slice();
	  return merged;
	}

	/**
	 * Finds the max value in a zone
	 *
	 * @param data - Object that contains property x (an ordered increasing array) and y (an array)
	 * @param options - Options
	 * @returns - Max y on the specified range
	 */

	function xyMaxY$1(data) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  xyCheck$1(data);
	  const {
	    x,
	    y
	  } = data;
	  const {
	    fromIndex,
	    toIndex
	  } = xGetFromToIndex$2(x, options);
	  let currentxyMaxY = y[fromIndex];

	  for (let i = fromIndex; i <= toIndex; i++) {
	    if (y[i] > currentxyMaxY) currentxyMaxY = y[i];
	  }

	  return currentxyMaxY;
	}

	/**
	 * Finds the median x value for an object with properties x and y (arrays of the same length)
	 *
	 * @param data - x should be sorted in increasing order
	 * @returns - the median of x values
	 */
	function xyMedian$1(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]);
	    }
	  }

	  return NaN;
	}

	/**
	 * Merge abscissa values if the ordinate value is in a list of centroids
	 *
	 * @param data - points
	 * @param centroids - centroids
	 * @param options - options
	 * @return merged points
	 */
	function xyMergeByCentroids$1(data, centroids) {
	  let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
	  const {
	    window = 0.01
	  } = options;
	  let mergedPoints = {
	    x: centroids.slice(),
	    y: new Float64Array(centroids.length).fill(0)
	  };
	  let originalIndex = 0;
	  let mergedIndex = 0;

	  while (originalIndex < data.x.length && mergedIndex < centroids.length) {
	    let diff = data.x[originalIndex] - centroids[mergedIndex];

	    if (Math.abs(diff) < window) {
	      mergedPoints.y[mergedIndex] += data.y[originalIndex++];
	    } else if (diff < 0) {
	      originalIndex++;
	    } else {
	      mergedIndex++;
	    }
	  }

	  return mergedPoints;
	}

	/**
	 * Find the closest minimum going down hill
	 *
	 * @param data - Object that contains property x (an ordered increasing array) and y (an array)
	 * @param options - Options
	 * @returns - An object with the x/y value
	 */

	function xyMinClosestYPoint$1(data) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  xyCheck$1(data);
	  const {
	    x,
	    y
	  } = data;
	  let {
	    target,
	    targetIndex
	  } = options;

	  if (targetIndex === undefined) {
	    if (target !== undefined) {
	      targetIndex = xFindClosestIndex$3(x, target);
	    } else {
	      targetIndex = 0;
	    }
	  }

	  let previousIndex = Number.MIN_SAFE_INTEGER;
	  let currentIndex = targetIndex;
	  let minY = y[targetIndex];

	  while (currentIndex !== previousIndex) {
	    previousIndex = currentIndex;

	    if (currentIndex > 0 && y[currentIndex - 1] < minY) {
	      currentIndex--;
	    } else if (currentIndex < x.length - 1 && y[currentIndex + 1] < minY) {
	      currentIndex++;
	    }

	    minY = y[currentIndex];
	  }

	  return {
	    x: x[currentIndex],
	    y: y[currentIndex],
	    index: currentIndex
	  };
	}

	/**
	 * Finds all the min values
	 * If the values are equal the middle
	 * of the equal part will be the position of the signal!
	 *
	 * @param data - Object that contains property X (an ordered increasing array) and y (an arraY)
	 * @returns - Array of points.
	 */

	function xyMinimaY$1(data) {
	  xyCheck$1(data, {
	    minLength: 2
	  });
	  const {
	    x,
	    y
	  } = data;
	  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 min y value in a range and return a {x,y} point
	 *
	 * @param data - Object that contains property x (an ordered increasing array) and y (an array)
	 * @param options - Options
	 */

	function xyMinYPoint$1(data) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  xyCheck$1(data, {
	    minLength: 1
	  });
	  const {
	    x,
	    y
	  } = data;
	  if (x.length === 1) return {
	    x: x[0],
	    y: y[0],
	    index: 0
	  };
	  const {
	    fromIndex,
	    toIndex
	  } = xGetFromToIndex$2(x, options);
	  let current = {
	    x: x[fromIndex],
	    y: y[fromIndex],
	    index: fromIndex
	  };

	  for (let i = fromIndex; i <= toIndex; i++) {
	    if (y[i] < current.y) current = {
	      x: x[i],
	      y: y[i],
	      index: i
	    };
	  }

	  return current;
	}

	/**
	 * Returns an information about a signal.
	 *
	 *
	 * We expect ordered data and equidistant X axis
	 * You can use the method helper if required:
	 * ML.ArrayPoints.uniqueX
	 * ML.ArrayPoints.sortX
	 * ML.ArrayPoints.equallySpaced
	 *
	 * @param data - Object that contains property x (an ordered increasing array) and y (an array)
	 * @param options - options
	 * @returns - Information about signal
	 */

	function xyPeakInfo$1(data) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  xyCheck$1(data);
	  const {
	    x,
	    y
	  } = data;
	  if (x === undefined || y === undefined || x.length < 3) return undefined;
	  let {
	    targetIndex,
	    target
	  } = options;

	  if (targetIndex === undefined) {
	    if (target !== undefined) {
	      targetIndex = xFindClosestIndex$3(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 data - Object that contains property x (an ordered increasing array) and y (an array)
	 * @param options - options
	 * @returns - An object with the x/y value
	 */

	function xyRealMaxYPoint$1(data) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  xyCheck$1(data);
	  const {
	    x,
	    y
	  } = data;
	  const targetIndex = xGetTargetIndex$1(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
	    };
	  }
	}

	/**
	 * XyRealMinYPoint.
	 *
	 * @param data - Data.
	 * @param options - Options.
	 */

	function xyRealMinYPoint$1(data) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  xyCheck$1(data);
	  const {
	    x,
	    y
	  } = data;
	  const targetIndex = xGetTargetIndex$1(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. If you want a similar looking spectrum you should still however generate 4x the nbPoints that is being displayed.
	 * SHOULD NOT BE USED FOR DATA PROCESSING !!!
	 * You should rather use ml-xy-equally-spaced to make further processing
	 *
	 * @param data - Object that contains property x (an ordered increasing array) and y (an array)
	 * @param options - options
	 */

	function xyReduce$1(data) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  xyCheck$1(data);
	  const {
	    x,
	    y
	  } = data;
	  let {
	    from = x[0],
	    to = x[x.length - 1],
	    nbPoints = 4001,
	    optimize = false,
	    zones = []
	  } = options;
	  zones = zonesNormalize$1(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

	  const internalZones = zones;
	  let totalPoints = 0;

	  for (let zone of internalZones) {
	    zone.fromIndex = xFindClosestIndex$3(x, zone.from);
	    zone.toIndex = xFindClosestIndex$3(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 < internalZones.length - 1; i++) {
	      const zone = internalZones[i];
	      zone.nbPoints = Math.round(zone.nbPoints * ratio);
	      currentTotal += zone.nbPoints;
	    }

	    internalZones[internalZones.length - 1].nbPoints = nbPoints - currentTotal;
	  } else {
	    let newX = new Float64Array(totalPoints);
	    let newY = new Float64Array(totalPoints);
	    let index = 0;

	    for (let zone of internalZones) {
	      for (let i = zone.fromIndex; i < zone.toIndex + 1; i++) {
	        newX[index] = x[i];
	        newY[index] = y[i];
	        index++;
	      }
	    }

	    return {
	      x: newX,
	      y: newY
	    };
	  }

	  let newX = [];
	  let newY = [];

	  for (let zone of internalZones) {
	    if (!zone.nbPoints) continue;
	    appendFromTo(zone.fromIndex, zone.toIndex, zone.nbPoints);
	  }

	  return {
	    x: newX,
	    y: newY
	  };
	  /**
	   * AppendFromTo.
	   *
	   * @param fromIndex - From.
	   * @param  toIndex - To.
	   * @param zoneNbPoints - NbPoints.
	   */

	  function appendFromTo(fromIndex, toIndex, zoneNbPoints) {
	    if (zoneNbPoints === 1) {
	      newX.push(x[Math.round((toIndex - fromIndex) / 2)]);
	      newY.push(y[Math.round((toIndex - fromIndex) / 2)]);
	      return;
	    }

	    if (zoneNbPoints === 2) {
	      newX.push(x[fromIndex], x[toIndex]);
	      newY.push(y[fromIndex], y[toIndex]);
	      return;
	    }

	    newX.push(x[fromIndex]);
	    newY.push(y[fromIndex]);

	    if (zoneNbPoints % 2 === 0) {
	      zoneNbPoints = zoneNbPoints / 2 + 1;
	    } else {
	      zoneNbPoints = (zoneNbPoints - 1) / 2 + 1;
	    } // we will need to make some kind of min / max because there are too many points
	    // we will always keep the first point and the last point


	    let slot = (x[toIndex] - x[fromIndex]) / (zoneNbPoints - 1);
	    let currentX = x[fromIndex] + slot;
	    let first = true;
	    let minY = Number.POSITIVE_INFINITY;
	    let xyMaxY = Number.NEGATIVE_INFINITY;

	    for (let i = fromIndex + 1; i <= toIndex; i++) {
	      if (first) {
	        minY = y[i];
	        xyMaxY = y[i];
	        first = false;
	      } else {
	        if (y[i] < minY) minY = y[i];
	        if (y[i] > xyMaxY) xyMaxY = y[i];
	      }

	      if (x[i] >= currentX || i === toIndex) {
	        if (optimize) {
	          if (minY > newY[newX.length - 1]) ; else if (xyMaxY < newY[newX.length - 1]) {
	            // we can skip the intermediate value
	            xyMaxY = minY;
	          } else {
	            newX.push(currentX - slot / 2);
	            newY.push(minY);
	          }
	        } else {
	          newX.push(currentX - slot / 2);
	          newY.push(minY);
	        }

	        newX.push(currentX);
	        newY.push(xyMaxY);
	        currentX += slot;
	        first = true;
	      }
	    }
	  }
	}

	/**
	 * This function calculates a rolling average.
	 * This methods will recalculate the x values by using xRollingAverage
	 *
	 * @param data - array of points {x,y}
	 * @param fct - callback function that from an array returns a value.
	 * @param options - options
	 */

	function xyRolling$1(data, fct) {
	  let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
	  let {
	    x,
	    y
	  } = data;
	  y = xRolling$1(y, fct, options);

	  if (x.length !== y.length) {
	    x = xRollingAverage$1(x, options);
	  }

	  return {
	    x,
	    y
	  };
	}

	/**
	 * Set a value (default 0) to specific zones.
	 *
	 * @param data - Object that contains property x (an ordered increasing array) and y (an array)
	 * @param options - options
	 * @returns - Array of points
	 */

	function xySetYValue$1(data) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  xyCheck$1(data);
	  const {
	    x,
	    y
	  } = data;
	  let {
	    zones,
	    value = 0
	  } = options;

	  if (!Array.isArray(zones) || zones.length === 0) {
	    return data;
	  }

	  zones = zonesNormalize$1(zones);
	  const newX = x.slice();
	  const newY = y.slice();
	  let currentZone = zones[0];
	  let position = 0;

	  loop: for (let i = 0; i < x.length; i++) {
	    while (currentZone.to < x[i]) {
	      position++;
	      currentZone = zones[position];

	      if (!currentZone) {
	        i = x.length;
	        break loop;
	      }
	    }

	    if (x[i] >= currentZone.from) {
	      newY[i] = value;
	    }
	  }

	  return {
	    x: newX,
	    y: newY
	  };
	}

	/**
	 * This function performs a quick sort of the x array while transforming the y array to preserve the coordinates.
	 *
	 * @param data - Object that contains property x (Array) and y (Array)
	 */

	function xySortX$2(data) {
	  const {
	    x,
	    y
	  } = data;

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

	/**
	 * Convert a DataXY to an array of array containing x,y
	 *
	 * @param data - array of points {x,y}
	 */

	function xyToXYArray$1(data) {
	  xyCheck$1(data);
	  const {
	    x,
	    y
	  } = data;
	  let objectArray = [];

	  for (let i = 0; i < x.length; i++) {
	    objectArray.push([x[i], y[i]]);
	  }

	  return objectArray;
	}

	/**
	 * XyToXYObject.
	 *
	 * @param data - Array of points {x,y}.
	 */

	function xyToXYObject$1(data) {
	  xyCheck$1(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;
	}

	/**
	 * Ensure x values are unique
	 *
	 * @param data - Object that contains property x (Array) and y (Array)
	 * @param options - Object containing a property algorithm (can be 'sum' or 'average', the latter being the default value), and a property isSorted (boolean indicating if the x-array is sorted).
	 */

	function xyUniqueX$1(data) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  xyCheck$1(data);
	  const {
	    algorithm = 'average',
	    isSorted = true
	  } = options;

	  if (!isSorted) {
	    data = xySortX$2(data);
	  }

	  switch (algorithm) {
	    case 'average':
	      return average$1(data);

	    case 'sum':
	      return sum$3(data);

	    default:
	      throw new Error(`xyUniqueX: unknown algorithm: ${algorithm}`);
	  }
	}
	/**
	 * Average.
	 *
	 * @param data - Input.
	 * @returns Result.
	 */

	function average$1(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
	  };
	}
	/**
	 * Sum.
	 *
	 * @param data - Input.
	 * @returns Result.
	 */


	function sum$3(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
	  };
	}

	/**
	 * Merge abscissas values on similar ordinates and weight the group of abscissas
	 *
	 * @param data - points
	 * @param options - options
	 * @return array of merged and weighted points
	 */
	function xyWeightedMerge$1(data) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const {
	    x,
	    y
	  } = data;
	  const {
	    groupWidth = 0.001
	  } = options;
	  let merged = {
	    x: [],
	    y: []
	  };
	  let weightedAbscissa = {
	    x: [],
	    y: []
	  };
	  let size = 0;
	  let index = 0;

	  while (index < x.length) {
	    if (size === 0 || x[index] - merged.x[size - 1] > groupWidth) {
	      weightedAbscissa.x.push(x[index] * y[index]);
	      weightedAbscissa.y.push(y[index]);
	      merged.x.push(x[index]);
	      merged.y.push(y[index]);
	      index++;
	      size++;
	    } else {
	      weightedAbscissa.x[size - 1] += x[index] * y[index];
	      weightedAbscissa.y[size - 1] += y[index];
	      merged.x[size - 1] = x[index];
	      merged.y[size - 1] += y[index];
	      index++;
	    }
	  }

	  for (let i = 0; i < merged.x.length; i++) {
	    merged.x[i] = weightedAbscissa.x[i] / weightedAbscissa.y[i];
	  }

	  return merged;
	}

	/**
	 * This function make a zero filling to re and im part.
	 *
	 * @param data - object of kind {x:[], re:[], im:[]}
	 * @param totalLength - final number of points
	 * @returns - data.
	 */
	function xreimZeroFilling$1(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 data - object of kind {x:[], re:[], im:[]}
	 * @returns - sorted array
	 */
	function xreimSortX$1(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()
	  };
	}

	/**
	 * GetSlots.
	 *
	 * @param data - data.
	 * @param options - Options.
	 */
	function getSlots$1(data) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const {
	    delta = 1
	  } = options;
	  const deltaIsFunction = typeof delta === 'function';
	  let possibleXs = Float64Array.from([].concat(...data.map(spectrum => spectrum.x))).sort();

	  if (possibleXs.length < 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 data, can be used for spectra
	 *
	 * @param data - data
	 * @param options - Options
	 */

	function xyArrayAlign$1(data) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const {
	    delta = 1
	  } = options;
	  data = data.map(spectrum => xyJoinX$2(spectrum, {
	    delta
	  }));
	  const slots = getSlots$1(data, options);
	  let x = Float64Array.from(slots.map(slot => slot.average));
	  let ys = new Array(data.length).fill(0).map(() => new Float64Array(x.length));
	  let positions = new Uint32Array(data.length);

	  for (let i = 0; i < slots.length; i++) {
	    let slot = slots[i];

	    for (let j = 0; j < data.length; j++) {
	      let spectrum = data[j];

	      while (positions[j] < spectrum.x.length && spectrum.x[positions[j]] <= slot.to) {
	        ys[j][i] += spectrum.y[positions[j]];
	        positions[j]++;
	      }
	    }
	  }

	  return {
	    x,
	    ys
	  };
	}

	/**
	 * Merge DataXY
	 * We have an array of DataXY and the goal is to merge all the values that are the closest possible
	 *
	 * @param data - data
	 * @param options - Options
	 */

	function xyArrayMerge$1(data) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const {
	    delta = 1
	  } = options; // we start by checking that the data/spectra don't have peaks too close and we simplify them

	  data = data.map(spectrum => xyJoinX$2(spectrum, {
	    delta
	  })); // at first we will calculate the X values (simple mean)

	  let slots = getSlots$1(data, options);
	  let x = Float64Array.from(slots.map(slot => slot.average));
	  let y = new Float64Array(x.length);
	  let positions = new Uint32Array(data.length);

	  for (let i = 0; i < slots.length; i++) {
	    let slot = slots[i];

	    for (let j = 0; j < data.length; j++) {
	      let spectrum = data[j];

	      while (positions[j] < spectrum.x.length && spectrum.x[positions[j]] <= slot.to) {
	        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 data - data
	 * @param options - Options
	 */
	function xyArrayWeightedMerge$1(data) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  let {
	    delta = 1
	  } = options;

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

	    delta = () => deltaNumber;
	  }

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

	  while (data.length !== 0) {
	    nextValue$1(data, positions, point);
	    let sameSlot = point.x <= slot.maxX;

	    if (!sameSlot) {
	      if (slot.sumY > 0) {
	        x.push(slot.sumXY / slot.sumY);
	        y.push(slot.sumY);
	      }

	      slot.sumY = 0;
	      slot.sumXY = 0;
	    }

	    slot.sumY += point.y;
	    slot.sumXY += point.x * point.y;
	    slot.maxX = point.x + delta(point.x);

	    if (data.length === 0) {
	      if (slot.sumY > 0) {
	        x.push(slot.sumXY / slot.sumY);
	        y.push(slot.sumY);
	      }
	    }
	  }

	  return {
	    x,
	    y
	  };
	}
	/**
	 * NextValue.
	 *
	 * @param data - data.
	 * @param positions - Positions array.
	 * @param point - Point.
	 */

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

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

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

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

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

	/**
	 * GetSlotsToFirst.
	 *
	 * @param data - data.
	 * @param options - Options.
	 */

	function getSlotsToFirst$1(data) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const {
	    delta = 1
	  } = options;
	  const deltaIsFunction = typeof delta === 'function';
	  let firstXs = data[0].x;
	  let slots = []; // we first create the slots based on the first spectrum

	  firstXs.forEach(element => {
	    let currentDelta = deltaIsFunction ? delta(element) : delta;
	    slots.push({
	      from: element - currentDelta,
	      to: element + currentDelta,
	      value: element
	    });
	  });
	  let otherXs = xyArrayWeightedMerge$1(data.slice(1), options).x;
	  let currentPosition = 0;

	  for (let slot of slots) {
	    while (otherXs[currentPosition] < slot.to && currentPosition < otherXs.length) {
	      if (otherXs[currentPosition] < slot.from) {
	        let currentDelta = deltaIsFunction ? delta(otherXs[currentPosition]) : delta;
	        slots.push({
	          from: otherXs[currentPosition] - currentDelta,
	          to: otherXs[currentPosition] + currentDelta,
	          value: otherXs[currentPosition]
	        });
	      }

	      currentPosition++;
	    }
	  }

	  for (let i = currentPosition; i < otherXs.length; i++) {
	    let currentDelta = deltaIsFunction ? delta(otherXs[i]) : delta;
	    slots.push({
	      from: otherXs[i] - currentDelta,
	      to: otherXs[i] + currentDelta,
	      value: otherXs[i]
	    });
	  }

	  slots.sort((a, b) => a.value - b.value); // we prevent slots overlap in the first spectrum

	  for (let i = 0; i < slots.length - 1; i++) {
	    if (slots[i].to > slots[i + 1].from) {
	      let middle = (slots[i].value + slots[i + 1].value) / 2;
	      slots[i].to = middle;
	      slots[i + 1].from = middle;
	    }
	  }

	  return slots;
	}

	/**
	 * We align all the data/spectra to the first array of X.
	 * The alignment is based on the X values of the first spectrum and the `delta` error allowed. If some x values are missing in the first specdtrum we will add them
	 *
	 * @param data data
	 * @param options options
	 */

	function xyArrayAlignToFirst$1(data) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const slots = getSlotsToFirst$1(data, options);
	  let x = Float64Array.from(slots.map(slot => slot.value));
	  let ys = new Array(data.length).fill(0).map(() => new Float64Array(x.length));
	  let positions = new Uint32Array(data.length);

	  for (let i = 0; i < slots.length; i++) {
	    let slot = slots[i];

	    for (let j = 0; j < data.length; j++) {
	      let spectrum = data[j];

	      while (positions[j] < spectrum.x.length && spectrum.x[positions[j]] < slot.to) {
	        ys[j][i] += spectrum.y[positions[j]];
	        positions[j]++;
	      }
	    }
	  }

	  return {
	    x,
	    ys
	  };
	}

	/**
	 * Throw an error in no an object of x,y arrays
	 *
	 * @param points - list of points
	 */
	function xyObjectCheck$1() {
	  let points = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];

	  if (!Array.isArray(points)) {
	    throw new Error('ArrayPoints must be an array of {x,y} object');
	  }

	  if (points.length > 0 && (points[0].x === undefined || points[0].y === undefined)) {
	    throw new Error('ArrayPoints must be an array of {x,y} object');
	  }
	}

	/**
	 * Finds the max x value and return a {x,y,index} point
	 *
	 * @param points - Object that contains property x (an ordered increasing array) and y (an array)
	 */

	function xyObjectMaxXPoint$3() {
	  let points = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
	  xyObjectCheck$1(points);
	  if (points.length < 1) return {
	    x: 0,
	    y: 0
	  };
	  let current = {
	    x: points[0].x,
	    y: points[0].y,
	    index: 0
	  };

	  for (let i = 1; i < points.length; i++) {
	    if (points[i].x > current.x) {
	      current = {
	        x: points[i].x,
	        y: points[i].y,
	        index: i
	      };
	    }
	  }

	  return current;
	}

	/**
	 * Finds the min x value and return a {x,y,index} point
	 *
	 * @param points - Object that contains property x (an ordered increasing array) and y (an array)
	 */

	function xyObjectMinXPoint$3() {
	  let points = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
	  xyObjectCheck$1(points);
	  if (points.length < 1) return {
	    x: 0,
	    y: 0
	  };
	  let current = {
	    x: points[0].x,
	    y: points[0].y,
	    index: 0
	  };

	  for (let i = 1; i < points.length; i++) {
	    if (points[i].x < current.x) {
	      current = {
	        x: points[i].x,
	        y: points[i].y,
	        index: i
	      };
	    }
	  }

	  return current;
	}

	/**
	 * Filter the array by taking the higher points (max y value) and only.
	 * Keep one per slot. There are 2 different slots, the smallest one will have the
	 * new property `close` to true
	 *
	 * @param points - array of all the points
	 * @param options - Options
	 * @returns - copy of points with 'close' property
	 */

	function xyObjectBestPoints$1(points) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const {
	    from = xyObjectMinXPoint$3(points).x,
	    to = xyObjectMaxXPoint$3(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);
	}

	/**
	 * xyObjectJoinX.
	 *
	 * @param points - Array of growing points {x,y}.
	 * @param options - Options.
	 */
	function xyObjectJoinX$1(points) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  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 points - Object that contains property x (an ordered increasing array) and y (an array)
	 */

	function xyObjectMaxYPoint$1() {
	  let points = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
	  xyObjectCheck$1(points);
	  if (points.length < 1) return {
	    x: 0,
	    y: 0
	  };
	  let current = {
	    x: points[0].x,
	    y: points[0].y,
	    index: 0
	  };

	  for (let i = 1; i < points.length; i++) {
	    if (points[i].y > current.y) {
	      current = {
	        x: points[i].x,
	        y: points[i].y,
	        index: i
	      };
	    }
	  }

	  return current;
	}

	/**
	 * Finds the min y value and return a {x,y,index} point
	 *
	 * @param points - Object that contains property x (an ordered increasing array) and y (an array)
	 */

	function xyObjectMinYPoint$1() {
	  let points = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
	  xyObjectCheck$1(points);
	  if (points.length < 1) return {
	    x: 0,
	    y: 0
	  };
	  let current = {
	    x: points[0].x,
	    y: points[0].y,
	    index: 0
	  };

	  for (let i = 1; i < points.length; i++) {
	    if (points[i].y < current.y) {
	      current = {
	        x: points[i].x,
	        y: points[i].y,
	        index: i
	      };
	    }
	  }

	  return current;
	}

	/**
	 * XyObjectSlotX
	 *
	 * @param points - Array of growing points {x,y}.
	 * @param options - Options.
	 */
	function xyObjectSlotX$1(points) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const {
	    slotWidth = 1
	  } = options;
	  const halfSlot = slotWidth / 2; // when we join we will use the center of mass

	  let result = [];
	  let current = {
	    x: Number.NEGATIVE_INFINITY,
	    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 points - array of points {x,y}
	 * @returns - sorted array of points {x,y}
	 */
	function xyObjectSortX$1(points) {
	  return points.sort((a, b) => a.x - b.x);
	}

	/**
	 * Calculate the sum of Y values
	 *
	 * @param points - Object that contains property x and y (an array)
	 */

	function xyObjectSumY$2() {
	  let points = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
	  xyObjectCheck$1(points);
	  let sum = 0;

	  for (let point of points) {
	    sum += point.y;
	  }

	  return sum;
	}

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

	/**
	 * Deprecated !!! Should use utils/createFromToArray
	 *
	 * @param zone.
	 * @param size - Size.
	 * @returns - Array of float.
	 */
	function zoneToX$1(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 matrix - matrix [rows][cols]
	 */
	function matrixCenterZMean$1(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 matrix - matrix [rows][cols].
	 */
	function matrixMinMaxAbsoluteZ$1(matrix) {
	  if (matrix.length === 0 || matrix[0].length === 0) {
	    throw new Error('matrixMinMaxAbsoluteZ requires at least 1 row and 1 column');
	  }

	  const nbRows = matrix.length;
	  const nbColumns = matrix[0].length;
	  let min = Number.POSITIVE_INFINITY;
	  let max = Number.NEGATIVE_INFINITY;

	  for (let column = 0; column < nbColumns; column++) {
	    for (let row = 0; row < nbRows; row++) {
	      let value = matrix[row][column];
	      if (value < 0) value *= -1;
	      if (value < min) min = value;
	      if (value > max) max = value;
	    }
	  }

	  return {
	    min,
	    max
	  };
	}

	function matrixCheck$2(data) {
	  if (data.length === 0 || data[0].length === 0) {
	    throw RangeError('matrix should contain data');
	  }

	  const firstLength = data[0].length;

	  for (let i = 1; i < data.length; i++) {
	    if (data[i].length !== firstLength) {
	      throw new RangeError('All rows should has the same length');
	    }
	  }
	}

	/**
	 * Get min and max Z
	 *
	 * @param matrix - matrix [rows][cols].
	 */

	function matrixMinMaxZ$2(matrix) {
	  matrixCheck$2(matrix);
	  const nbRows = matrix.length;
	  const nbColumns = matrix[0].length;
	  let min = matrix[0][0];
	  let max = matrix[0][0];

	  for (let column = 0; column < nbColumns; column++) {
	    for (let row = 0; row < nbRows; row++) {
	      if (matrix[row][column] < min) min = matrix[row][column];
	      if (matrix[row][column] > max) max = matrix[row][column];
	    }
	  }

	  return {
	    min,
	    max
	  };
	}

	/**
	 * Calculates an histogram of defined number of slots
	 *
	 * @param matrix - matrix [rows][cols].
	 * @param options - options
	 * @returns - result of the histogram
	 */

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

	    if (options.min === undefined) {
	      options.min = logBaseX && minMax.min ? Math.log(minMax.min) / Math.log(logBaseX) : minMax.min;
	    }

	    if (options.max === undefined) {
	      options.max = logBaseX && minMax.max ? Math.log(minMax.max) / Math.log(logBaseX) : minMax.max;
	    }
	  }

	  let histogram = xHistogram$1(matrix[0], options);
	  options.histogram = histogram;
	  const nbRows = matrix.length;

	  for (let row = 1; row < nbRows; row++) {
	    xHistogram$1(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;
	}

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

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

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

	  var _options$fromIndex = options.fromIndex,
	      fromIndex = _options$fromIndex === void 0 ? 0 : _options$fromIndex,
	      _options$toIndex = options.toIndex,
	      toIndex = _options$toIndex === void 0 ? input.length : _options$toIndex;

	  if (fromIndex < 0 || fromIndex >= input.length || !Number.isInteger(fromIndex)) {
	    throw new Error('fromIndex must be a positive integer smaller than length');
	  }

	  if (toIndex <= fromIndex || toIndex > input.length || !Number.isInteger(toIndex)) {
	    throw new Error('toIndex must be an integer greater than fromIndex and at most equal to length');
	  }

	  var minValue = input[fromIndex];

	  for (var i = fromIndex + 1; i < toIndex; i++) {
	    if (input[i] < minValue) minValue = input[i];
	  }

	  return minValue;
	}

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

	  if (!isAnyArray$9(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$9(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$5(input);
	  var currentMax = max$7(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$1 = ' '.repeat(2);
	const indentData$1 = ' '.repeat(4);
	function inspectMatrix$1() {
	  return inspectMatrixWithOptions$1(this);
	}
	function inspectMatrixWithOptions$1(matrix) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const {
	    maxRows = 15,
	    maxColumns = 10,
	    maxNumSize = 8
	  } = options;
	  return `${matrix.constructor.name} {
${indent$1}[
${indentData$1}${inspectData$1(matrix, maxRows, maxColumns, maxNumSize)}
${indent$1}]
${indent$1}rows: ${matrix.rows}
${indent$1}columns: ${matrix.columns}
}`;
	}

	function inspectData$1(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$1(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$1}`);
	}

	function formatNumber$1(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$1(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$1(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$1(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$1(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$1(matrix, vector) {
	  if (vector.to1DArray) {
	    vector = vector.to1DArray();
	  }

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

	  return vector;
	}
	function checkRowIndices$1(matrix, rowIndices) {
	  if (!isAnyArray$9(rowIndices)) {
	    throw new TypeError('row indices must be an array');
	  }

	  for (let i = 0; i < rowIndices.length; i++) {
	    if (rowIndices[i] < 0 || rowIndices[i] >= matrix.rows) {
	      throw new RangeError('row indices are out of range');
	    }
	  }
	}
	function checkColumnIndices$1(matrix, columnIndices) {
	  if (!isAnyArray$9(columnIndices)) {
	    throw new TypeError('column indices must be an array');
	  }

	  for (let i = 0; i < columnIndices.length; i++) {
	    if (columnIndices[i] < 0 || columnIndices[i] >= matrix.columns) {
	      throw new RangeError('column indices are out of range');
	    }
	  }
	}
	function checkRange$1(matrix, startRow, endRow, startColumn, endColumn) {
	  if (arguments.length !== 5) {
	    throw new RangeError('expected 4 arguments');
	  }

	  checkNumber$1('startRow', startRow);
	  checkNumber$1('endRow', endRow);
	  checkNumber$1('startColumn', startColumn);
	  checkNumber$1('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$1(length) {
	  let value = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
	  let array = [];

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

	  return array;
	}

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

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

	function sumByRow$1(matrix) {
	  let sum = newArray$1(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$1(matrix) {
	  let sum = newArray$1(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$1(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$1(matrix) {
	  let sum = newArray$1(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$1(matrix) {
	  let sum = newArray$1(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$1(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$1(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$1(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$1(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$1(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$1(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$1(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$1(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$1(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$1(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$1(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$1(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$1(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$1 {
	  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$2(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$2(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$2(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$2(rows, columns);
	  }

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

	  static rand(rows, columns) {
	    let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};

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

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

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

	    return matrix;
	  }

	  static randInt(rows, columns) {
	    let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};

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

	    const {
	      min = 0,
	      max = 1000,
	      random = Math.random
	    } = options;
	    if (!Number.isInteger(min)) throw new TypeError('min must be an integer');
	    if (!Number.isInteger(max)) throw new TypeError('max must be an integer');
	    if (min >= max) throw new RangeError('min must be smaller than max');
	    let interval = max - min;
	    let matrix = new Matrix$2(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$2(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$1.isMatrix(value) ? value : new Matrix$2(value);
	  }

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

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

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

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

	    return this;
	  }

	  to1DArray() {
	    let array = [];

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

	    return array;
	  }

	  to2DArray() {
	    let copy = [];

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

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

	    return copy;
	  }

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

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

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

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

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

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

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

	      return true;
	    }

	    return false;
	  }

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

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

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

	      i++;
	    }

	    return isEchelonForm;
	  }

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

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

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

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

	      i++;
	    }

	    return isReducedEchelonForm;
	  }

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

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

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

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

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

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

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

	        h++;
	        k++;
	      }
	    }

	    return result;
	  }

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

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

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

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

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

	        h--;
	      }
	    }

	    return result;
	  }

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

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

	  repeat() {
	    let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};

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

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

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

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

	    let matrix = new Matrix$2(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$1(this, index);
	    let row = [];

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

	    return row;
	  }

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

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

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

	    return this;
	  }

	  swapRows(row1, row2) {
	    checkRowIndex$1(this, row1);
	    checkRowIndex$1(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$1(this, index);
	    let column = [];

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

	    return column;
	  }

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

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

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

	    return this;
	  }

	  swapColumns(column1, column2) {
	    checkColumnIndex$1(this, column1);
	    checkColumnIndex$1(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$1(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$1(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$1(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$1(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$1(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$1(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$1(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$1(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$1(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$1(this, index);

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

	    return this;
	  }

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

	    switch (by) {
	      case 'row':
	        {
	          const max = new Array(this.rows).fill(Number.NEGATIVE_INFINITY);

	          for (let row = 0; row < this.rows; row++) {
	            for (let column = 0; column < this.columns; column++) {
	              if (this.get(row, column) > max[row]) {
	                max[row] = this.get(row, column);
	              }
	            }
	          }

	          return max;
	        }

	      case 'column':
	        {
	          const max = new Array(this.columns).fill(Number.NEGATIVE_INFINITY);

	          for (let row = 0; row < this.rows; row++) {
	            for (let column = 0; column < this.columns; column++) {
	              if (this.get(row, column) > max[column]) {
	                max[column] = this.get(row, column);
	              }
	            }
	          }

	          return max;
	        }

	      case undefined:
	        {
	          let max = this.get(0, 0);

	          for (let row = 0; row < this.rows; row++) {
	            for (let column = 0; column < this.columns; column++) {
	              if (this.get(row, column) > max) {
	                max = this.get(row, column);
	              }
	            }
	          }

	          return max;
	        }

	      default:
	        throw new Error(`invalid option: ${by}`);
	    }
	  }

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

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

	    return idx;
	  }

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

	    switch (by) {
	      case 'row':
	        {
	          const min = new Array(this.rows).fill(Number.POSITIVE_INFINITY);

	          for (let row = 0; row < this.rows; row++) {
	            for (let column = 0; column < this.columns; column++) {
	              if (this.get(row, column) < min[row]) {
	                min[row] = this.get(row, column);
	              }
	            }
	          }

	          return min;
	        }

	      case 'column':
	        {
	          const min = new Array(this.columns).fill(Number.POSITIVE_INFINITY);

	          for (let row = 0; row < this.rows; row++) {
	            for (let column = 0; column < this.columns; column++) {
	              if (this.get(row, column) < min[column]) {
	                min[column] = this.get(row, column);
	              }
	            }
	          }

	          return min;
	        }

	      case undefined:
	        {
	          let min = this.get(0, 0);

	          for (let row = 0; row < this.rows; row++) {
	            for (let column = 0; column < this.columns; column++) {
	              if (this.get(row, column) < min) {
	                min = this.get(row, column);
	              }
	            }
	          }

	          return min;
	        }

	      default:
	        throw new Error(`invalid option: ${by}`);
	    }
	  }

	  minIndex() {
	    checkNonEmpty$1(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$1(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$1(this, row);
	    checkNonEmpty$1(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$1(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$1(this, row);
	    checkNonEmpty$1(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$1(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$1(this, column);
	    checkNonEmpty$1(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$1(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$1(this, column);
	    checkNonEmpty$1(this);
	    let v = this.get(0, column);
	    let idx = [0, column];

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

	    return idx;
	  }

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

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

	    return diag;
	  }

	  norm() {
	    let type = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'frobenius';
	    let result = 0;

	    if (type === 'max') {
	      return this.max();
	    } else if (type === 'frobenius') {
	      for (let i = 0; i < this.rows; i++) {
	        for (let j = 0; j < this.columns; j++) {
	          result = result + this.get(i, j) * this.get(i, j);
	        }
	      }

	      return Math.sqrt(result);
	    } else {
	      throw new RangeError(`unknown norm type: ${type}`);
	    }
	  }

	  cumulativeSum() {
	    let sum = 0;

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

	    return this;
	  }

	  dot(vector2) {
	    if (AbstractMatrix$1.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$2.checkMatrix(other);
	    let m = this.rows;
	    let n = this.columns;
	    let p = other.columns;
	    let result = new Matrix$2(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$2.checkMatrix(other);
	    let result = new Matrix$2(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$2.checkMatrix(other);
	    let result = new Matrix$2(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$2.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$1.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$1.add(a11, a22), AbstractMatrix$1.add(b11, b22), halfRows, halfCols);
	      let m2 = blockMult(AbstractMatrix$1.add(a21, a22), b11, halfRows, halfCols);
	      let m3 = blockMult(a11, AbstractMatrix$1.sub(b12, b22), halfRows, halfCols);
	      let m4 = blockMult(a22, AbstractMatrix$1.sub(b21, b11), halfRows, halfCols);
	      let m5 = blockMult(AbstractMatrix$1.add(a11, a12), b22, halfRows, halfCols);
	      let m6 = blockMult(AbstractMatrix$1.sub(a21, a11), AbstractMatrix$1.add(b11, b12), halfRows, halfCols);
	      let m7 = blockMult(AbstractMatrix$1.sub(a12, a22), AbstractMatrix$1.add(b21, b22), halfRows, halfCols); // Combine intermediate values into the output.

	      let c11 = AbstractMatrix$1.add(m1, m4);
	      c11.sub(m5);
	      c11.add(m7);
	      let c12 = AbstractMatrix$1.add(m3, m5);
	      let c21 = AbstractMatrix$1.add(m2, m4);
	      let c22 = AbstractMatrix$1.sub(m1, m2);
	      c22.add(m3);
	      c22.add(m6); // Crop output to the desired size (undo dynamic padding).

	      let resultat = AbstractMatrix$1.zeros(2 * c11.rows, 2 * c11.columns);
	      resultat = resultat.setSubMatrix(c11, 0, 0);
	      resultat = resultat.setSubMatrix(c12, c11.rows, 0);
	      resultat = resultat.setSubMatrix(c21, 0, c11.columns);
	      resultat = resultat.setSubMatrix(c22, c11.rows, c11.columns);
	      return resultat.subMatrix(0, rows - 1, 0, cols - 1);
	    }

	    return blockMult(x, y, r, c);
	  }

	  scaleRows() {
	    let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};

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

	    const {
	      min = 0,
	      max = 1
	    } = options;
	    if (!Number.isFinite(min)) throw new TypeError('min must be a number');
	    if (!Number.isFinite(max)) throw new TypeError('max must be a number');
	    if (min >= max) throw new RangeError('min must be smaller than max');
	    let newMatrix = new Matrix$2(this.rows, this.columns);

	    for (let i = 0; i < this.rows; i++) {
	      const row = this.getRow(i);

	      if (row.length > 0) {
	        rescale$1(row, {
	          min,
	          max,
	          output: row
	        });
	      }

	      newMatrix.setRow(i, row);
	    }

	    return newMatrix;
	  }

	  scaleColumns() {
	    let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};

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

	    const {
	      min = 0,
	      max = 1
	    } = options;
	    if (!Number.isFinite(min)) throw new TypeError('min must be a number');
	    if (!Number.isFinite(max)) throw new TypeError('max must be a number');
	    if (min >= max) throw new RangeError('min must be smaller than max');
	    let newMatrix = new Matrix$2(this.rows, this.columns);

	    for (let i = 0; i < this.columns; i++) {
	      const column = this.getColumn(i);

	      if (column.length) {
	        rescale$1(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$2.checkMatrix(other);
	    let m = this.rows;
	    let n = this.columns;
	    let p = other.rows;
	    let q = other.columns;
	    let result = new Matrix$2(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$2.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$2.eye(n, n));
	    let IxB = Matrix$2.eye(m, m).kroneckerProduct(other);
	    return AxI.add(IxB);
	  }

	  transpose() {
	    let result = new Matrix$2(this.columns, this.rows);

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

	    return result;
	  }

	  sortRows() {
	    let compareFunction = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : compareNumbers$1;

	    for (let i = 0; i < this.rows; i++) {
	      this.setRow(i, this.getRow(i).sort(compareFunction));
	    }

	    return this;
	  }

	  sortColumns() {
	    let compareFunction = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : compareNumbers$1;

	    for (let i = 0; i < this.columns; i++) {
	      this.setColumn(i, this.getColumn(i).sort(compareFunction));
	    }

	    return this;
	  }

	  subMatrix(startRow, endRow, startColumn, endColumn) {
	    checkRange$1(this, startRow, endRow, startColumn, endColumn);
	    let newMatrix = new Matrix$2(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$2(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$2(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$2.checkMatrix(matrix);

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

	    let endRow = startRow + matrix.rows - 1;
	    let endColumn = startColumn + matrix.columns - 1;
	    checkRange$1(this, startRow, endRow, startColumn, endColumn);

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

	    return this;
	  }

	  selection(rowIndices, columnIndices) {
	    checkRowIndices$1(this, rowIndices);
	    checkColumnIndices$1(this, columnIndices);
	    let newMatrix = new Matrix$2(rowIndices.length, columnIndices.length);

	    for (let i = 0; i < rowIndices.length; i++) {
	      let rowIndex = rowIndices[i];

	      for (let j = 0; j < columnIndices.length; j++) {
	        let columnIndex = columnIndices[j];
	        newMatrix.set(i, j, this.get(rowIndex, columnIndex));
	      }
	    }

	    return newMatrix;
	  }

	  trace() {
	    let min = Math.min(this.rows, this.columns);
	    let trace = 0;

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

	    return trace;
	  }

	  clone() {
	    let newMatrix = new Matrix$2(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$1(this);

	      case 'column':
	        return sumByColumn$1(this);

	      case undefined:
	        return sumAll$1(this);

	      default:
	        throw new Error(`invalid option: ${by}`);
	    }
	  }

	  product(by) {
	    switch (by) {
	      case 'row':
	        return productByRow$1(this);

	      case 'column':
	        return productByColumn$1(this);

	      case undefined:
	        return productAll$1(this);

	      default:
	        throw new Error(`invalid option: ${by}`);
	    }
	  }

	  mean(by) {
	    const sum = this.sum(by);

	    switch (by) {
	      case 'row':
	        {
	          for (let i = 0; i < this.rows; i++) {
	            sum[i] /= this.columns;
	          }

	          return sum;
	        }

	      case 'column':
	        {
	          for (let i = 0; i < this.columns; i++) {
	            sum[i] /= this.rows;
	          }

	          return sum;
	        }

	      case undefined:
	        return sum / this.size;

	      default:
	        throw new Error(`invalid option: ${by}`);
	    }
	  }

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

	    if (typeof by === 'object') {
	      options = by;
	      by = undefined;
	    }

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

	    const {
	      unbiased = true,
	      mean = this.mean(by)
	    } = options;

	    if (typeof unbiased !== 'boolean') {
	      throw new TypeError('unbiased must be a boolean');
	    }

	    switch (by) {
	      case 'row':
	        {
	          if (!isAnyArray$9(mean)) {
	            throw new TypeError('mean must be an array');
	          }

	          return varianceByRow$1(this, unbiased, mean);
	        }

	      case 'column':
	        {
	          if (!isAnyArray$9(mean)) {
	            throw new TypeError('mean must be an array');
	          }

	          return varianceByColumn$1(this, unbiased, mean);
	        }

	      case undefined:
	        {
	          if (typeof mean !== 'number') {
	            throw new TypeError('mean must be a number');
	          }

	          return varianceAll$1(this, unbiased, mean);
	        }

	      default:
	        throw new Error(`invalid option: ${by}`);
	    }
	  }

	  standardDeviation(by, options) {
	    if (typeof by === 'object') {
	      options = by;
	      by = undefined;
	    }

	    const variance = this.variance(by, options);

	    if (by === undefined) {
	      return Math.sqrt(variance);
	    } else {
	      for (let i = 0; i < variance.length; i++) {
	        variance[i] = Math.sqrt(variance[i]);
	      }

	      return variance;
	    }
	  }

	  center(by) {
	    let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};

	    if (typeof by === 'object') {
	      options = by;
	      by = undefined;
	    }

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

	    const {
	      center = this.mean(by)
	    } = options;

	    switch (by) {
	      case 'row':
	        {
	          if (!isAnyArray$9(center)) {
	            throw new TypeError('center must be an array');
	          }

	          centerByRow$1(this, center);
	          return this;
	        }

	      case 'column':
	        {
	          if (!isAnyArray$9(center)) {
	            throw new TypeError('center must be an array');
	          }

	          centerByColumn$1(this, center);
	          return this;
	        }

	      case undefined:
	        {
	          if (typeof center !== 'number') {
	            throw new TypeError('center must be a number');
	          }

	          centerAll$1(this, center);
	          return this;
	        }

	      default:
	        throw new Error(`invalid option: ${by}`);
	    }
	  }

	  scale(by) {
	    let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};

	    if (typeof by === 'object') {
	      options = by;
	      by = undefined;
	    }

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

	    let scale = options.scale;

	    switch (by) {
	      case 'row':
	        {
	          if (scale === undefined) {
	            scale = getScaleByRow$1(this);
	          } else if (!isAnyArray$9(scale)) {
	            throw new TypeError('scale must be an array');
	          }

	          scaleByRow$1(this, scale);
	          return this;
	        }

	      case 'column':
	        {
	          if (scale === undefined) {
	            scale = getScaleByColumn$1(this);
	          } else if (!isAnyArray$9(scale)) {
	            throw new TypeError('scale must be an array');
	          }

	          scaleByColumn$1(this, scale);
	          return this;
	        }

	      case undefined:
	        {
	          if (scale === undefined) {
	            scale = getScaleAll$1(this);
	          } else if (typeof scale !== 'number') {
	            throw new TypeError('scale must be a number');
	          }

	          scaleAll$1(this, scale);
	          return this;
	        }

	      default:
	        throw new Error(`invalid option: ${by}`);
	    }
	  }

	  toString(options) {
	    return inspectMatrixWithOptions$1(this, options);
	  }

	}
	AbstractMatrix$1.prototype.klass = 'Matrix';

	if (typeof Symbol !== 'undefined') {
	  AbstractMatrix$1.prototype[Symbol.for('nodejs.util.inspect.custom')] = inspectMatrix$1;
	}

	function compareNumbers$1(a, b) {
	  return a - b;
	} // Synonyms


	AbstractMatrix$1.random = AbstractMatrix$1.rand;
	AbstractMatrix$1.randomInt = AbstractMatrix$1.randInt;
	AbstractMatrix$1.diagonal = AbstractMatrix$1.diag;
	AbstractMatrix$1.prototype.diagonal = AbstractMatrix$1.prototype.diag;
	AbstractMatrix$1.identity = AbstractMatrix$1.eye;
	AbstractMatrix$1.prototype.negate = AbstractMatrix$1.prototype.neg;
	AbstractMatrix$1.prototype.tensorProduct = AbstractMatrix$1.prototype.kroneckerProduct;
	class Matrix$2 extends AbstractMatrix$1 {
	  constructor(nRows, nColumns) {
	    super();

	    if (Matrix$2.isMatrix(nRows)) {
	      // eslint-disable-next-line no-constructor-return
	      return nRows.clone();
	    } else if (Number.isInteger(nRows) && nRows >= 0) {
	      // Create an empty matrix
	      this.data = [];

	      if (Number.isInteger(nColumns) && nColumns >= 0) {
	        for (let i = 0; i < nRows; i++) {
	          this.data.push(new Float64Array(nColumns));
	        }
	      } else {
	        throw new TypeError('nColumns must be a positive integer');
	      }
	    } else if (isAnyArray$9(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$1(this, index);
	    this.data.splice(index, 1);
	    this.rows -= 1;
	    return this;
	  }

	  addRow(index, array) {
	    if (array === undefined) {
	      array = index;
	      index = this.rows;
	    }

	    checkRowIndex$1(this, index, true);
	    array = Float64Array.from(checkRowVector$1(this, array));
	    this.data.splice(index, 0, array);
	    this.rows += 1;
	    return this;
	  }

	  removeColumn(index) {
	    checkColumnIndex$1(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$1(this, index, true);
	    array = checkColumnVector$1(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$1(AbstractMatrix$1, Matrix$2);

	class WrapperMatrix2D extends AbstractMatrix$1 {
	  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$2.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$2(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$2(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$2.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$2(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$2(rows, columns);
	    let i, j, k, s;

	    for (k = columns - 1; k >= 0; k--) {
	      for (i = 0; i < rows; i++) {
	        X.set(i, k, 0);
	      }

	      X.set(k, k, 1);

	      for (j = k; j < columns; j++) {
	        if (qr.get(k, k) !== 0) {
	          s = 0;

	          for (i = k; i < rows; i++) {
	            s += qr.get(i, k) * X.get(i, j);
	          }

	          s = -s / qr.get(k, k);

	          for (i = k; i < rows; i++) {
	            X.set(i, j, X.get(i, j) + s * qr.get(i, k));
	          }
	        }
	      }
	    }

	    return X;
	  }

	}

	class SingularValueDecomposition {
	  constructor(value) {
	    let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	    value = WrapperMatrix2D.checkMatrix(value);

	    if (value.isEmpty()) {
	      throw new Error('Matrix must be non-empty');
	    }

	    let m = value.rows;
	    let n = value.columns;
	    const {
	      computeLeftSingularVectors = true,
	      computeRightSingularVectors = true,
	      autoTranspose = false
	    } = options;
	    let wantu = Boolean(computeLeftSingularVectors);
	    let wantv = Boolean(computeRightSingularVectors);
	    let swapped = false;
	    let a;

	    if (m < n) {
	      if (!autoTranspose) {
	        a = value.clone(); // eslint-disable-next-line no-console

	        console.warn('Computing SVD on a matrix with more columns than rows. Consider enabling autoTranspose');
	      } else {
	        a = value.transpose();
	        m = a.rows;
	        n = a.columns;
	        swapped = true;
	        let aux = wantu;
	        wantu = wantv;
	        wantv = aux;
	      }
	    } else {
	      a = value.clone();
	    }

	    let nu = Math.min(m, n);
	    let ni = Math.min(m + 1, n);
	    let s = new Float64Array(ni);
	    let U = new Matrix$2(m, nu);
	    let V = new Matrix$2(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$2.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$2.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$2.diag(value));
	  }

	  inverse() {
	    let V = this.V;
	    let e = this.threshold;
	    let vrows = V.rows;
	    let vcols = V.columns;
	    let X = new Matrix$2(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$2(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$2.diag(this.s);
	  }

	}

	function inverse(matrix) {
	  let useSVD = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
	  matrix = WrapperMatrix2D.checkMatrix(matrix);

	  if (useSVD) {
	    return new SingularValueDecomposition(matrix).inverse();
	  } else {
	    return solve(matrix, Matrix$2.eye(matrix.rows));
	  }
	}
	function solve(leftHandSide, rightHandSide) {
	  let useSVD = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
	  leftHandSide = WrapperMatrix2D.checkMatrix(leftHandSide);
	  rightHandSide = WrapperMatrix2D.checkMatrix(rightHandSide);

	  if (useSVD) {
	    return new SingularValueDecomposition(leftHandSide).solve(rightHandSide);
	  } else {
	    return leftHandSide.isSquare() ? new LuDecomposition(leftHandSide).solve(rightHandSide) : new QrDecomposition(leftHandSide).solve(rightHandSide);
	  }
	}

	/**
	 * Performs a Probabilistic quotient normalization (PQN) over the dataset to account dilution based in median spectrum.
	 * Dieterle, F., Ross, A., Schlotterbeck, G., & Senn, H. (2006). Probabilistic quotient normalization as robust method to account for dilution of complex biological mixtures. Application in 1H NMR metabonomics. Analytical chemistry, 78(13), 4281-4290.
	 * DOI: 10.1021/ac051632c
	 *
	 * @param matrix - matrix [rows][cols].
	 * @param options - options
	 * @returns - {data: Normalized dataset, medianOfQuotients: The median of quotients of each variables}
	 */

	function matrixPQN$1(matrix) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const {
	    max = 100
	  } = options;
	  let matrixB = new Matrix$2(matrix);

	  for (let i = 0; i < matrixB.rows; i++) {
	    const normalizationFactor = matrixB.getRowVector(i).norm('frobenius') / max;
	    const row = matrixB.getRowVector(i).div(normalizationFactor);
	    matrixB.setRow(i, row);
	  }

	  let referenceSpectrum = [];

	  for (let i = 0; i < matrixB.columns; i++) {
	    const currentVariable = matrixB.getColumn(i);
	    referenceSpectrum.push(xMedian$1(currentVariable));
	  }

	  let medianOfQuotients = [];

	  for (let i = 0; i < matrixB.columns; i++) {
	    let quotients = matrixB.getColumnVector(i).div(referenceSpectrum[i]);
	    medianOfQuotients.push(xMedian$1(quotients.getColumn(0)));
	  }

	  for (let i = 0; i < matrixB.rows; i++) {
	    matrixB.mulRow(i, 1 / medianOfQuotients[i]);
	  }

	  return {
	    data: matrixB.to2DArray(),
	    medianOfQuotients: medianOfQuotients
	  };
	}

	/**
	 * Rescale columns
	 *
	 * @param matrix - matrix [rows][cols].
	 * @param options - Options
	 */
	function matrixZRescale$1(matrix) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  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;
	}

	/**
	 * Clone the matrix
	 */
	function matrixClone$1(matrix) {
	  return matrix.map(row => row.slice(0));
	}

	/**
	 * Numerically encodes the strings in the matrix and returns an encoding dictionnary which can be used to encode other matrices
	 * @param matrix - original matrix before encoding
	 * @param dictCategoricalToNumerical - dictionnary for encoding
	 * @returns - dictionnary from string to number
	 */

	function matrixNumericalEncoding$1(matrixInitial) {
	  let matrix = matrixClone$1(matrixInitial);
	  let nRows = matrix.length;
	  let nColumns = matrix[0].length;
	  let dictCategoricalToNumerical = {};
	  let k = 0;

	  for (let i = 0; i < nRows; i++) {
	    for (let j = 0; j < nColumns; j++) {
	      if (typeof matrix[i][j] === 'number' && matrix[i][j] > k) {
	        k = matrix[i][j];
	      }
	    }
	  }

	  for (let i = 0; i < nRows; i++) {
	    for (let j = 0; j < nColumns; j++) {
	      if (typeof matrix[i][j] === 'string') {
	        if (matrix[i][j] in dictCategoricalToNumerical) {
	          matrix[i][j] = dictCategoricalToNumerical[matrix[i][j]];
	        } else {
	          k++;
	          dictCategoricalToNumerical[matrix[i][j]] = k;
	          matrix[i][j] = k;
	        }
	      }
	    }
	  }

	  return {
	    matrix,
	    dictCategoricalToNumerical
	  };
	}

	/**
	 * Numerically decodes the matrix using the dictionnary
	 * @param matrixInitial - original matrix before encoding
	 * @param dictionnary - dictionary against which to do the encoding
	 * @returns - decoded matrix
	 */

	function swap$1(dictionnary) {
	  let ret = {};

	  for (let key in dictionnary) {
	    ret[Number(dictionnary[key])] = key;
	  }

	  return ret;
	}

	function matrixNumericalDecoding$1(matrixInitial, dictionnary) {
	  let matrix = matrixClone$1(matrixInitial);
	  let invertedDictionnary = swap$1(dictionnary);

	  for (let i = 0; i < matrix.length; i++) {
	    for (let j = 0; j < matrix[0].length; j++) {
	      if (matrix[i][j] in invertedDictionnary) {
	        matrix[i][j] = invertedDictionnary[matrix[i][j]];
	      }
	    }
	  }

	  return matrix;
	}

	/**
	 * Numerically encodes the strings in the matrix with an encoding dictionary
	 * @param matrixInitial - original matrix before encoding
	 * @param dictionnary - dictionary against which to do the encoding
	 * @returns - encoded matrix
	 */

	function matrixApplyNumericalEncoding$1(matrixInitial, dictionnary) {
	  let matrix = matrixClone$1(matrixInitial);
	  let arrayOfValues = [];

	  for (let key in dictionnary) {
	    arrayOfValues.push(dictionnary[key]);
	  }

	  let k = xMaxValue$2(arrayOfValues);

	  for (let i = 0; i < matrix.length; i++) {
	    for (let j = 0; j < matrix[0].length; j++) {
	      if (typeof matrix[i][j] === 'string') {
	        if (matrix[i][j] in dictionnary) {
	          matrix[i][j] = dictionnary[matrix[i][j]];
	        } else {
	          k++;
	          dictionnary[matrix[i][j]] = k;
	          matrix[i][j] = k;
	        }
	      }
	    }
	  }

	  return matrix;
	}

	const LOOP$2 = 8;
	const FLOAT_MUL$2 = 1 / 16777216;
	const sh1$2 = 15;
	const sh2$2 = 18;
	const sh3$2 = 11;

	function multiply_uint32$2(n, m) {
	  n >>>= 0;
	  m >>>= 0;
	  const nlo = n & 0xffff;
	  const nhi = n - nlo;
	  return (nhi * m >>> 0) + nlo * m >>> 0;
	}

	class XSadd$2 {
	  constructor() {
	    let seed = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : Date.now();
	    this.state = new Uint32Array(4);
	    this.init(seed);
	    this.random = this.getFloat.bind(this);
	  }
	  /**
	   * Returns a 32-bit integer r (0 <= r < 2^32)
	   */


	  getUint32() {
	    this.nextState();
	    return this.state[3] + this.state[2] >>> 0;
	  }
	  /**
	   * Returns a floating point number r (0.0 <= r < 1.0)
	   */


	  getFloat() {
	    return (this.getUint32() >>> 8) * FLOAT_MUL$2;
	  }

	  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$2; i++) {
	      this.state[i & 3] ^= i + multiply_uint32$2(1812433253, this.state[i - 1 & 3] ^ this.state[i - 1 & 3] >>> 30 >>> 0) >>> 0;
	    }

	    this.periodCertification();

	    for (let i = 0; i < LOOP$2; 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$2;
	    t ^= t >>> sh2$2;
	    t ^= this.state[3] << sh3$2;
	    this.state[0] = this.state[1];
	    this.state[1] = this.state[2];
	    this.state[2] = this.state[3];
	    this.state[3] = t;
	  }

	}

	/**
	 * Create a random array of numbers of a specific length
	 *
	 * @return - array of random floats normally distributed
	 */

	let spare$2;
	let hasSpare$2 = false;
	function createRandomArray$2() {
	  let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
	  let {
	    mean = 0,
	    standardDeviation = 1,
	    length = 1000,
	    range = 1,
	    seed,
	    distribution = 'normal'
	  } = options;
	  const generator = new XSadd$2(seed);
	  let returnArray = new Float64Array(length);

	  switch (distribution) {
	    case 'normal':
	      for (let i = 0; i < length; i++) {
	        returnArray[i] = generateGaussian$2(mean, standardDeviation, generator);
	      }

	      break;

	    case 'uniform':
	      for (let i = 0; i < length; i++) {
	        returnArray[i] = (generator.random() - 0.5) * range + mean;
	      }

	      break;

	    default:
	      // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
	      throw new Error(`unknown distribution: ${distribution}`);
	  }

	  return returnArray;
	}

	function generateGaussian$2(mean, standardDeviation, generator) {
	  let val, u, v, s;

	  if (hasSpare$2) {
	    hasSpare$2 = false;
	    val = spare$2 * standardDeviation + mean;
	  } else {
	    do {
	      u = generator.random() * 2 - 1;
	      v = generator.random() * 2 - 1;
	      s = u * u + v * v;
	    } while (s >= 1 || s === 0);

	    s = Math.sqrt(-2.0 * Math.log(s) / s);
	    spare$2 = v * s;
	    hasSpare$2 = true;
	    val = mean + standardDeviation * u * s;
	  }

	  return val;
	}

	/**
	 * Create an array with numbers starting from "from" with step "step" of length "length"
	 *
	 * @param options - options
	 * @return - array of distributed numbers with step "step" from "from"
	 */
	function createStepArray$1() {
	  let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
	  let {
	    from = 0,
	    step = 1,
	    length = 1000
	  } = options;
	  const array = new Float64Array(length);
	  let index = 0;

	  while (index < length) {
	    array[index] = from + step * index;
	    index++;
	  }

	  return array;
	}

	var libEsm$5 = /*#__PURE__*/Object.freeze({
		__proto__: null,
		reimAbsolute: reimAbsolute$1,
		reimAutoPhaseCorrection: reimAutoPhaseCorrection$1,
		reimPhaseCorrection: reimPhaseCorrection$1,
		reimFFT: reimFFT$1,
		xAbsolute: xAbsolute$1,
		xAbsoluteMedian: xAbsoluteMedian$1,
		xAdd: xAdd$2,
		xAutoCorrelation: xAutoCorrelation$1,
		xBoxPlot: xBoxPlot$1,
		xCheck: xCheck$2,
		xCorrelation: xCorrelation$1,
		xCrossCorrelation: xCrossCorrelation$1,
		xCumulative: xCumulative$1,
		xDivide: xDivide$1,
		xDotProduct: xDotProduct$1,
		xEnsureFloat64: xEnsureFloat64$1,
		xFindClosestIndex: xFindClosestIndex$3,
		xGetFromToIndex: xGetFromToIndex$2,
		xGetTargetIndex: xGetTargetIndex$1,
		xHistogram: xHistogram$1,
		xIsEquallySpaced: xIsEquallySpaced$1,
		xIsMonotone: xIsMonotone$1,
		xIsMonotoneIncreasing: xIsMonotoneIncreasing$1,
		xMaxIndex: xMaxIndex$1,
		xMaxValue: xMaxValue$2,
		xMean: xMean$1,
		xMeanAbsoluteError: xMeanAbsoluteError$1,
		xMeanSquaredError: xMeanSquaredError$1,
		xMedian: xMedian$1,
		xMedianAbsoluteDeviation: xMedianAbsoluteDeviation$1,
		xMinIndex: xMinIndex$1,
		xMinMaxValues: xMinMaxValues$1,
		xMinValue: xMinValue$1,
		xMode: xMode$1,
		xMultiply: xMultiply$1,
		xNoiseSanPlot: xNoiseSanPlot$1,
		xNoiseStandardDeviation: xNoiseStandardDeviation$1,
		xNorm: xNorm$1,
		xNormed: xNormed$1,
		xPadding: xPadding$1,
		xParetoNormalization: xParetoNormalization$1,
		xRescale: xRescale$1,
		xRolling: xRolling$1,
		xRollingAverage: xRollingAverage$1,
		xRollingMax: xRollingMax$1,
		xRollingMedian: xRollingMedian$1,
		xRollingMin: xRollingMin$1,
		xRotate: xRotate$1,
		xSampling: xSampling$1,
		xSequentialFill: xSequentialFill$1,
		xStandardDeviation: xStandardDeviation$1,
		xApplyFunctionStr: xApplyFunctionStr$1,
		xSubtract: xSubtract$1,
		xSum: xSum$1,
		xUniqueSorted: xUniqueSorted$1,
		getOutputArray: getOutputArray$1,
		xyAlign: xyAlign$1,
		xyCheck: xyCheck$1,
		xyFindClosestPoint: xyFindClosestPoint$1,
		xyCovariance: xyCovariance$1,
		xyCumulativeDistributionStatistics: xyCumulativeDistributionStatistics$1,
		xyEnsureGrowingX: xyEnsureGrowingX$2,
		xyEquallySpaced: xyEquallySpaced$1,
		xyExtract: xyExtract$1,
		xyFilterX: xyFilterX$1,
		xyFilterXPositive: xyFilterXPositive$1,
		xyGetNMaxY: xyGetNMaxY$1,
		xyGrowingX: xyGrowingX$1,
		xyIntegral: xyIntegral$1,
		xyIntegration: xyIntegration$1,
		xyJoinX: xyJoinX$2,
		xyMaxClosestYPoint: xyMaxClosestYPoint$1,
		xyMaximaY: xyMaximaY$1,
		xyMaxMerge: xyMaxMerge$1,
		xyMaxY: xyMaxY$1,
		xyMaxYPoint: xyMaxYPoint$1,
		xyMedian: xyMedian$1,
		xyMergeByCentroids: xyMergeByCentroids$1,
		xyMinClosestYPoint: xyMinClosestYPoint$1,
		xyMinimaY: xyMinimaY$1,
		xyMinYPoint: xyMinYPoint$1,
		xyPeakInfo: xyPeakInfo$1,
		xyRealMaxYPoint: xyRealMaxYPoint$1,
		xyRealMinYPoint: xyRealMinYPoint$1,
		xyReduce: xyReduce$1,
		xyRolling: xyRolling$1,
		xySetYValue: xySetYValue$1,
		xySortX: xySortX$2,
		xyToXYArray: xyToXYArray$1,
		xyToXYObject: xyToXYObject$1,
		xyUniqueX: xyUniqueX$1,
		xyWeightedMerge: xyWeightedMerge$1,
		xreimZeroFilling: xreimZeroFilling$1,
		xreimSortX: xreimSortX$1,
		xyArrayAlign: xyArrayAlign$1,
		xyArrayMerge: xyArrayMerge$1,
		xyArrayWeightedMerge: xyArrayWeightedMerge$1,
		xyArrayAlignToFirst: xyArrayAlignToFirst$1,
		xyObjectBestPoints: xyObjectBestPoints$1,
		xyObjectJoinX: xyObjectJoinX$1,
		xyObjectMaxXPoint: xyObjectMaxXPoint$3,
		xyObjectMinXPoint: xyObjectMinXPoint$3,
		xyObjectMaxYPoint: xyObjectMaxYPoint$1,
		xyObjectMinYPoint: xyObjectMinYPoint$1,
		xyObjectSlotX: xyObjectSlotX$1,
		xyObjectSortX: xyObjectSortX$1,
		xyObjectSumY: xyObjectSumY$2,
		xyObjectToXY: xyObjectToXY$1,
		zoneToX: zoneToX$1,
		zonesNormalize: zonesNormalize$1,
		matrixCenterZMean: matrixCenterZMean$1,
		matrixHistogram: matrixHistogram$1,
		matrixMinMaxZ: matrixMinMaxZ$2,
		matrixMinMaxAbsoluteZ: matrixMinMaxAbsoluteZ$1,
		matrixPQN: matrixPQN$1,
		matrixZRescale: matrixZRescale$1,
		matrixClone: matrixClone$1,
		matrixNumericalEncoding: matrixNumericalEncoding$1,
		matrixNumericalDecoding: matrixNumericalDecoding$1,
		matrixApplyNumericalEncoding: matrixApplyNumericalEncoding$1,
		createFromToArray: createFromToArray$1,
		createRandomArray: createRandomArray$2,
		createStepArray: createStepArray$1
	});

	/**
	 * General internal parsing function
	 *
	 * @param text - Csv or tsv strings.
	 * @param options - Parsing options
	 * @returns parsed text file with column information
	 */

	function parse$5(text) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  let {
	    rescale = false,
	    uniqueX: uniqueX$1 = false,
	    xColumn = 0,
	    yColumn = 1,
	    bestGuess = false,
	    //@ts-expect-error old library used this property and we want to throw an error so that people are forced to migrate
	    keepInfo,
	    numberColumns = Number.MAX_SAFE_INTEGER,
	    maxNumberColumns = Number.MAX_SAFE_INTEGER,
	    minNumberColumns = 2
	  } = options;

	  if (keepInfo !== undefined) {
	    throw new Error('keepInfo has been deprecated, pelase use the new method parseXYAndKeepInfo');
	  }

	  text = ensureString(text);
	  maxNumberColumns = Math.max(maxNumberColumns, xColumn + 1, yColumn + 1);
	  minNumberColumns = Math.max(xColumn + 1, yColumn + 1, minNumberColumns);
	  let lines = text.split(/[\r\n]+/);
	  let matrix = [];
	  let info = [];
	  let position = 0;
	  lines.forEach(line => {
	    line = line.trim(); // we will consider only lines that contains only numbers

	    if (/[0-9]+/.test(line) && /^[0-9eE,;. \t+-]+$/.test(line)) {
	      let fields = line.split(/,[; \t]+|[; \t]+/);

	      if (fields.length === 1) {
	        fields = line.split(/[,; \t]+/);
	      }

	      if (fields && fields.length >= minNumberColumns && // we filter lines that have not enough or too many columns
	      fields.length <= maxNumberColumns) {
	        matrix.push(fields.map(value => parseFloat(value.replace(',', '.'))));
	        position++;
	      }
	    } else if (line) {
	      info.push({
	        position,
	        value: line
	      });
	    }
	  });

	  if (bestGuess) {
	    if (matrix[0] && matrix[0].length === 3 && options.xColumn === undefined && options.yColumn === undefined) {
	      // is the first column a seuqnetial number ?
	      let skipFirstColumn = true;

	      for (let i = 0; i < matrix.length - 1; i++) {
	        if (Math.abs(matrix[i][0] - matrix[i + 1][0]) !== 1) {
	          skipFirstColumn = false;
	        }
	      }

	      if (skipFirstColumn) {
	        xColumn = 1;
	        yColumn = 2;
	      }
	    }

	    if (matrix[0] && matrix[0].length > 3) {
	      const xs = [];

	      for (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$7(result.y);

	    for (let i = 0; i < result.y.length; i++) {
	      result.y[i] /= maxY;
	    }
	  }

	  return {
	    info,
	    data: result
	  };
	}

	/**
	 * Parse a text-file and convert it to an object {x:[], y:[]}
	 *
	 * @param text - Csv or tsv strings.
	 * @param options - Parsing options
	 * @returns - The parsed data
	 */

	function parseXY$1(text) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  return parse$5(text, options).data;
	}
	/**
	 * Parse a text-file and returns the parsed data and information about the columns
	 *
	 * @param text - Csv or tsv strings.
	 * @param options - Parsing options
	 * @returns - The parsed data with information about the columns
	 */

	function parseXYAndKeepInfo(text) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  return parse$5(text, options);
	}

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

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

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

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

	function getBestPeaks$1(peaks) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const {
	    searchMonoisotopicRatio = 0,
	    from = xyObjectMinXPoint$2(peaks).x,
	    to = xyObjectMaxXPoint$2(peaks).x,
	    limit = 20,
	    threshold = 0.01,
	    numberCloseSlots = 50,
	    numberSlots = 10
	  } = options;
	  let slot = (to - from) / numberSlots;
	  let closeSlot = (to - from) / numberCloseSlots;
	  let selected = peaks.filter(peak => peak.x >= from && peak.x <= to).map(peak => {
	    return {
	      peak,
	      monoisotopic: false
	    };
	  });

	  if (searchMonoisotopicRatio) {
	    selected = selected.sort((a, b) => b.peak.x - a.peak.x);

	    for (let i = 0; i < selected.length; i++) {
	      let item = selected[i];

	      for (let j = i + 1; j < selected.length; j++) {
	        let nextItem = selected[j];
	        if (item.peak.x - nextItem.peak.x < 0.09) continue;
	        if (item.peak.x - nextItem.peak.x > 1.1) break;

	        if (nextItem.peak.y > item.peak.y * searchMonoisotopicRatio) {
	          item.monoisotopic = false;
	          nextItem.monoisotopic = true;
	          break;
	        }
	      }
	    }
	  }

	  selected = selected.sort((a, b) => {
	    if (a.monoisotopic && !b.monoisotopic) return -1;
	    if (b.monoisotopic && !a.monoisotopic) return 1;
	    return b.peak.y - a.peak.y;
	  });
	  let toReturn = [];
	  if (selected.length === 0) return [];
	  let minY = selected[0].peak.y * threshold;

	  peakLoop: for (let item of selected) {
	    if (item.peak.y < minY) {
	      if (item.monoisotopic) {
	        continue;
	      } else {
	        break;
	      }
	    }

	    let close = false;

	    for (let existing of toReturn) {
	      if (Math.abs(existing.x - item.peak.x) < closeSlot) {
	        continue peakLoop;
	      }

	      if (Math.abs(existing.x - item.peak.x) < slot) {
	        close = true;
	      }
	    }

	    let newPeak = JSON.parse(JSON.stringify(item.peak));
	    newPeak.close = close;
	    toReturn.push(newPeak);
	    if (toReturn.length === limit) break;
	  }

	  return toReturn.sort((a, b) => a.x - b.x);
	}

	var getBestPeaks_1 = getBestPeaks$1;

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


	var Kind$c = {
	  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$b = Kind$c;
	const parseCharge = parseCharge$1;
	/**
	 * Parse a mf to an array of kind / value
	 * @param {String} mf
	 */

	var parse$4 = function parse(mf) {
	  return new MFParser().parse(mf);
	};

	class MFParser {
	  parse() {
	    let mf = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
	    this.mf = mf;
	    this.i = 0;
	    this.result = [];
	    let lastKind = Kind$b.BEGIN;

	    while (this.i < mf.length) {
	      if (this.result.length > 0 && this.result[this.result.length - 1].kind !== Kind$b.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$b.SALT || lastKind === Kind$b.BEGIN || lastKind === Kind$b.OPENING_PARENTHESIS) {
	          if (value.to) {
	            throw new MFError(this.mf, this.i, 'Premultiplier may not contain a -');
	          }

	          this.result.push({
	            kind: Kind$b.PRE_MULTIPLIER,
	            value: value.from
	          });
	        } else if (lastKind === Kind$b.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$b.MULTIPLIER_RANGE,
	              value: {
	                from: Math.min(value.from, value.to),
	                to: Math.max(value.from, value.to)
	              }
	            });
	          } else {
	            this.result.push({
	              kind: Kind$b.MULTIPLIER,
	              value: value.from
	            });
	          }
	        }

	        continue;
	      } else if (char === '.') {
	        // a point
	        this.result.push({
	          kind: Kind$b.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$b.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$b.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$b.CHARGE,
	            value: charge
	          });
	        } else {
	          this.result.push({
	            kind: Kind$b.OPENING_PARENTHESIS,
	            value: '('
	          });
	        }
	      } else if (char === ')') {
	        this.result.push({
	          kind: Kind$b.CLOSING_PARENTHESIS,
	          value: ')'
	        });
	      } else if (char === '[') {
	        // defines an isotope
	        let isotope = this.getIsotope(ascii);
	        this.result.push({
	          kind: Kind$b.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$b.ATOM) {
	          let lastResult = this.result[this.result.length - 1];
	          lastResult.kind = Kind$b.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$b.CHARGE,
	          value: charge
	        });
	      } else if (char === '-') {
	        // charge not in parenthesis
	        let charge = this.getNonParenthesisCharge(ascii);
	        this.result.push({
	          kind: Kind$b.CHARGE,
	          value: charge
	        });
	      } else if (char === '$') {
	        // it is a comment after
	        this.result.push({
	          kind: Kind$b.COMMENT,
	          value: this.mf.substring(this.i + 1)
	        });
	        break;
	      } else {
	        this.result.push({
	          kind: Kind$b.TEXT,
	          value: char
	        });
	      }

	      this.i++;
	    }

	    this.checkParenthesis();
	    return this.result;
	  }

	  checkParenthesis() {
	    let counter = 0;

	    for (let line of this.result) {
	      if (line.kind === Kind$b.OPENING_PARENTHESIS) counter++;
	      if (line.kind === Kind$b.CLOSING_PARENTHESIS) counter--;
	    }

	    if (counter !== 0) {
	      throw new MFError(this.mf, this.i, 'number of opening and closing parenthesis not equal');
	    }
	  }

	  getNumber(ascii) {
	    let number = '';
	    let previous;

	    do {
	      previous = ascii;
	      number += String.fromCharCode(ascii);
	      this.i++;
	      ascii = this.mf.charCodeAt(this.i);
	    } while (ascii > 47 && ascii < 58 || ascii === 46 || ascii === 45 || ascii === 47); // number . - /
	    // we need to deal with the case there is a from / to


	    if (previous === 46) this.i--;
	    let indexOfDash = number.indexOf('-', 1);

	    if (indexOfDash > -1) {
	      return {
	        from: parseNumberWithDivision(number.substr(0, indexOfDash)),
	        to: parseNumberWithDivision(number.substr(indexOfDash + 1))
	      };
	    }

	    return {
	      from: parseNumberWithDivision(number)
	    };
	  }

	  getAtom(ascii) {
	    let atom = '';

	    do {
	      atom += String.fromCharCode(ascii);
	      this.i++;
	      ascii = this.mf.charCodeAt(this.i);
	    } while (ascii > 96 && ascii < 123);

	    return atom;
	  }

	  getIsotope(ascii) {
	    // [13C]
	    let substring = '';

	    do {
	      substring += String.fromCharCode(ascii);
	      this.i++;
	      ascii = this.mf.charCodeAt(this.i);
	    } while (ascii !== 93 && this.i <= this.mf.length);

	    let atom = substring.replace(/[^a-zA-Z]/g, '');
	    let isotope = Number(substring.replace(/[^0-9]/g, ''));
	    return {
	      atom,
	      isotope
	    };
	  }

	  getCurlyBracketIsotopeRatio(ascii) {
	    let substring = '';
	    let first = true;

	    do {
	      if (!first) {
	        substring += String.fromCharCode(ascii);
	      } else {
	        first = false;
	      }

	      this.i++;
	      ascii = this.mf.charCodeAt(this.i);
	    } while (ascii !== 125 && this.i <= this.mf.length); // closing curly bracket


	    if (substring.match(/^[0-9,]+$/)) {
	      return substring.split(',').map(a => Number(a));
	    }

	    throw new MFError(this.mf, this.i, 'Curly brackets should contain only number and comma');
	  }

	  getParenthesisCharge(ascii) {
	    let substring = '';
	    let begin = this.i;

	    do {
	      substring += String.fromCharCode(ascii);
	      this.i++;
	      ascii = this.mf.charCodeAt(this.i);
	    } while (ascii !== 41 && this.i <= this.mf.length); // closing parenthesis


	    if (substring.match(/^\([0-9+-]+$/)) {
	      return parseCharge(substring.substring(1));
	    } else {
	      this.i = begin;
	      return undefined;
	    }
	  }

	  getNonParenthesisCharge(ascii) {
	    let substring = '';

	    do {
	      substring += String.fromCharCode(ascii);
	      this.i++;
	      ascii = this.mf.charCodeAt(this.i);
	    } while (ascii === 43 || ascii === 45 || ascii > 47 && ascii < 58);

	    this.i--;
	    return parseCharge(substring);
	  }

	}

	class MFError extends SyntaxError {
	  constructor(mf, i, message) {
	    let text = `${message}\n\n${mf}\n${' '.repeat(i)}^`;
	    super(text);
	  }

	}

	function parseNumberWithDivision(string) {
	  if (string.includes('/')) {
	    let parts = string.split('/');

	    if (parts.length !== 2) {
	      throw new TypeError('Can not parse MF with number like: ', string);
	    }

	    return Number(parts[0]) / Number(parts[1]);
	  } else {
	    return Number(string);
	  }
	}

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

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


	var Format$3 = {
	  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$2 = Format$3;
	const Kind$a = Kind$c;
	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$a.MULTIPLIER:
	        if (line.value !== 1) {
	          result = {
	            kind: Format$2.SUBSCRIPT,
	            value: String(line.value)
	          };
	          results.push(result);
	        }

	        break;

	      case Kind$a.MULTIPLIER_RANGE:
	        result = {
	          kind: Format$2.SUBSCRIPT,
	          value: `${String(line.value.from)}-${line.value.to}`
	        };
	        results.push(result);
	        break;

	      case Kind$a.CHARGE:
	        if (result.kind === Format$2.SUBSCRIPT) {
	          result.kind = Format$2.SUPERIMPOSE;
	          result.over = formatCharge(line.value);
	          result.under = result.value;
	          result.value = undefined;
	        } else {
	          result = {
	            kind: Format$2.SUPERSCRIPT,
	            value: formatCharge(line.value)
	          };
	          results.push(result);
	        }

	        break;

	      case Kind$a.ISOTOPE:
	        result = {
	          kind: Format$2.SUPERSCRIPT,
	          value: line.value.isotope
	        };
	        results.push(result);
	        result = {
	          kind: Format$2.TEXT,
	          value: line.value.atom
	        };
	        results.push(result);
	        break;

	      case Kind$a.ISOTOPE_RATIO:
	        if (result.kind === Format$2.TEXT) {
	          result.value += line.value.atom;
	        } else {
	          result = {
	            kind: Format$2.TEXT,
	            value: line.value.atom
	          };
	          results.push(result);
	        }

	        result = {
	          kind: Format$2.SUPERSCRIPT,
	          value: `{${line.value.ratio.join(',')}}`
	        };
	        results.push(result);
	        break;

	      case Kind$a.SALT:
	        if (result.kind === Format$2.TEXT) {
	          result.value += ' • ';
	        } else {
	          result = {
	            kind: Format$2.TEXT,
	            value: ' • '
	          };
	          results.push(result);
	        }

	        break;

	      default:
	        if (result.kind === Format$2.TEXT) {
	          result.value += line.value;
	        } else {
	          result = {
	            kind: Format$2.TEXT,
	            value: line.value
	          };
	          results.push(result);
	        }

	    }
	  }

	  return results;
	};

	var Style$2 = {
	  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$1 = Format$3;
	const Style$1 = Style$2;

	var toHtml$2 = function toHtml(lines) {
	  let html = [];

	  for (let line of lines) {
	    switch (line.kind) {
	      case Format$1.SUBSCRIPT:
	        html.push(`<sub>${line.value}</sub>`);
	        break;

	      case Format$1.SUPERSCRIPT:
	        html.push(`<sup>${line.value}</sup>`);
	        break;

	      case Format$1.SUPERIMPOSE:
	        html.push(`<span style="${Style$1.SUPERIMPOSE}">`);
	        html.push(`<sup style="${Style$1.SUPERIMPOSE_SUP_SUB}">${line.over}</sup>`);
	        html.push(`<sub style="${Style$1.SUPERIMPOSE_SUP_SUB}">${line.under}</sub>`);
	        html.push('</span>');
	        break;

	      default:
	        html.push(line.value);
	    }
	  }

	  return html.join('');
	};

	var require$$0$5 = [
		{
			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$c = JSON.parse(JSON.stringify(require$$0$5));
	elements$c.forEach(element => {
	  element.isotopes = element.isotopes.filter(i => i.abundance > 0);
	});
	var elementsAndStableIsotopes$2 = elements$c;

	const elementsAndStableIsotopes$1 = elementsAndStableIsotopes$2;
	let elementsAndStableIsotopesObject$1 = {};
	elementsAndStableIsotopes$1.forEach(element => {
	  elementsAndStableIsotopesObject$1[element.symbol] = element;
	});
	var elementsAndStableIsotopesObject_1 = elementsAndStableIsotopesObject$1;

	const elements$b = 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$1(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$b.includes(two)) {
	            newPart += two;
	            j++;
	          } else {
	            if (elements$b.includes(one)) {
	              newPart += one;
	            } else {
	              return mf;
	            }
	          }
	        }
	      }

	      parts[i] = newPart;
	    }
	  }

	  return parts.join('');
	}

	var ensureCase$1 = capitalize$1;

	var constants = {
	  ELECTRON_MASS: 5.4857990907e-4
	};

	const elements$a = require$$0$5;
	const data$1 = elements$a.map(element => ({
	  number: element.number,
	  symbol: element.symbol,
	  mass: element.mass,
	  name: element.name,
	  monoisotopicMass: element.monoisotopicMass
	}));
	var elements_1 = data$1;

	var elementsAndIsotopes$1 = require$$0$5;

	const elements$9 = require$$0$5;
	let elementsAndIsotopesObject$2 = {};
	elements$9.forEach(element => {
	  elementsAndIsotopesObject$2[element.symbol] = element;
	});
	var elementsAndIsotopesObject_1 = elementsAndIsotopesObject$2;

	const elements$8 = elements_1;
	let elementsObject$2 = {};
	elements$8.forEach(element => {
	  elementsObject$2[element.symbol] = element;
	});
	var elementsObject_1 = elementsObject$2;

	const elements$7 = require$$0$5;
	let isotopeObject = {};

	for (const element of elements$7) {
	  let abundance = 0;
	  let mostAbundant = 0;

	  for (const isotope of element.isotopes) {
	    if (isotope.abundance > abundance) {
	      abundance = isotope.abundance;
	      mostAbundant = isotope.nominal;
	    }
	  }

	  for (const isotope of element.isotopes) {
	    if (isotope.abundance === 0) continue;
	    const entry = {
	      name: element.name,
	      mass: isotope.mass,
	      symbol: element.symbol
	    };

	    if (isotope.nominal === mostAbundant) {
	      entry.mostAbundant = true;
	    }

	    isotopeObject[isotope.nominal + element.symbol] = entry;
	  }
	}

	var stableIsotopesObject$2 = isotopeObject;

	const {
	  ELECTRON_MASS: ELECTRON_MASS$6
	} = constants;
	const elements$6 = elements_1;
	const elementsAndIsotopes = elementsAndIsotopes$1;
	const elementsAndIsotopesObject$1 = elementsAndIsotopesObject_1;
	const elementsAndStableIsotopes = elementsAndStableIsotopes$2;
	const elementsAndStableIsotopesObject = elementsAndStableIsotopesObject_1;
	const elementsObject$1 = elementsObject_1;
	const stableIsotopesObject$1 = stableIsotopesObject$2;
	var src$j = {
	  elements: elements$6,
	  elementsObject: elementsObject$1,
	  elementsAndIsotopes,
	  elementsAndIsotopesObject: elementsAndIsotopesObject$1,
	  elementsAndStableIsotopes,
	  elementsAndStableIsotopesObject,
	  stableIsotopesObject: stableIsotopesObject$1,
	  ELECTRON_MASS: ELECTRON_MASS$6
	};

	var groups$7 = [{
	  "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$6 = groups$7;
	const groupsObject$1 = {};
	groups$6.forEach(e => {
	  if (groupsObject$1[e.symbol]) {
	    console.log('The symbol ' + e.symbol + ' is used more than once');
	  }

	  groupsObject$1[e.symbol] = e;
	});
	var groupsObject_1 = groupsObject$1;

	const elements$5 = src$j.elementsAndStableIsotopesObject;

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

	var getIsotopeRatioInfo_1 = getIsotopeRatioInfo$2;

	const elements$4 = elementsAndIsotopesObject_1;
	const isotopes$3 = {};
	Object.keys(elements$4).forEach(key => {
	  let e = elements$4[key];
	  e.isotopes.forEach(i => {
	    isotopes$3[i.nominal + key] = {
	      abundance: i.abundance,
	      mass: i.mass
	    };
	  });
	});
	var getIsotopesObject = isotopes$3;

	const elements$3 = src$j.elementsObject;
	const groups$5 = groupsObject_1;
	const Kind$9 = Kind$c;
	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$9.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$9.ISOTOPE_RATIO:
	          {
	            let isotopeRatioInfo = getIsotopeRatioInfo$1(line.value);
	            addMass(results, line.value.atom, isotopeRatioInfo.mass * line.multiplier);
	            break;
	          }

	        case Kind$9.ATOM:
	          {
	            let element = elements$3[line.value];

	            if (!element) {
	              element = groups$5[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$9.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$j;
	const Kind$8 = Kind$c;
	/**
	 *
	 * @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$8.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$8.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$7 = Kind$c;
	/**
	 * 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$7.ISOTOPE:
	        if (!atoms[line.value.atom]) atoms[line.value.atom] = 0;
	        atoms[line.value.atom] += line.multiplier;
	        break;

	      case Kind$7.ISOTOPE_RATIO:
	        if (!atoms[line.value.atom]) atoms[line.value.atom] = 0;
	        atoms[line.value.atom] += line.multiplier;
	        break;

	      case Kind$7.ATOM:
	        if (!atoms[line.value]) atoms[line.value] = 0;
	        atoms[line.value] += line.multiplier;
	        break;

	      case Kind$7.CHARGE:
	        break;

	      case Kind$7.ANCHOR:
	        break;

	      default:
	        throw new Error('partToMF unhandled Kind: ', line.kind);
	    }
	  }

	  return atoms;
	};

	const Kind$6 = Kind$c;

	var partToMF$2 = function partToMF(part) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  let mf = [];

	  for (let line of part) {
	    switch (line.kind) {
	      case Kind$6.ISOTOPE:
	        if (line.multiplier !== 0) {
	          mf.push(`[${line.value.isotope}${line.value.atom}]${line.multiplier !== 1 ? line.multiplier : ''}`);
	        }

	        break;

	      case Kind$6.ISOTOPE_RATIO:
	        if (line.multiplier !== 0) {
	          mf.push(`${line.value.atom}{${line.value.ratio.join(',')}}${line.multiplier !== 1 ? line.multiplier : ''}`);
	        }

	        break;

	      case Kind$6.ATOM:
	        if (line.multiplier !== 0) {
	          mf.push(line.value + (line.multiplier !== 1 ? line.multiplier : ''));
	        }

	        break;

	      case Kind$6.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$5
	} = constants;
	const elements$2 = elementsAndIsotopesObject_1;
	const unsaturations = unsaturationsObject;
	const groups$4 = groupsObject_1;
	const Kind$5 = Kind$c;
	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) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  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$5.ATOM:
	        {
	          currentElement = line.value;
	          let element = elements$2[line.value]; // todo should we have a kind GROUP ?

	          if (!element) {
	            element = groups$4[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$5.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$5.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$5.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$5) / Math.abs(currentPart.charge);
	  }

	  if (validUnsaturation) {
	    currentPart.unsaturation = unsaturation / 2 + 1;
	  }

	  return currentPart;
	}

	const elements$1 = elementsAndStableIsotopesObject_1;
	const Kind$4 = Kind$c;
	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$4.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$4.ISOTOPE_RATIO:
	        {
	          let element = elements$1[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$4.ATOM:
	        {
	          let element = elements$1[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$4.CHARGE:
	        result.charge += line.value;
	        break;

	      default:
	        throw new Error('partToMF unhandled Kind: ', line.kind);
	    }
	  }

	  return result;
	}

	function getDistribution(isotopesArray, ratio) {
	  let ratios = normalize$3(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$3(array) {
	  let sum = array.reduce((prev, current) => prev + current, 0);
	  return array.map(a => a / sum);
	}

	const Kind$3 = Kind$c;
	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$3.SALT,
	      value: '•'
	    });

	    for (let partLine of part) {
	      lines.push(partLine);

	      if (partLine.multiplier) {
	        lines.push({
	          kind: Kind$3.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$3(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$i = atomSorter$3;

	const atomSorter$2 = src$i;
	const groups$3 = groupsObject_1;
	const Kind$2 = Kind$c;
	/**
	 *
	 * @param {*} lines
	 * @param {object} [options={}]
	 * @param {boolean} [options.expand=true] - Should we expand the groups
	 */

	var toParts$1 = function toParts(lines) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const {
	    expand: shouldExpandGroups = true
	  } = options;
	  let parts = [];
	  let currentPart = createNewPart();
	  let previousKind = Kind$2.BEGIN;
	  parts.push(currentPart);

	  for (let line of lines) {
	    switch (line.kind) {
	      case Kind$2.ATOM:
	      case Kind$2.ISOTOPE_RATIO:
	      case Kind$2.ISOTOPE:
	      case Kind$2.CHARGE:
	        currentPart.lines.push(Object.assign({}, line, {
	          multiplier: 1
	        }));
	        break;

	      case Kind$2.OPENING_PARENTHESIS:
	        openingParenthesis(currentPart);
	        break;

	      case Kind$2.CLOSING_PARENTHESIS:
	        closingParenthesis(currentPart);
	        break;

	      case Kind$2.PRE_MULTIPLIER:
	        preMultiplier(currentPart, line);
	        break;

	      case Kind$2.MULTIPLIER:
	        postMultiplier(currentPart, line.value, previousKind);
	        break;

	      case Kind$2.SALT:
	        globalPartMultiplier(currentPart);
	        currentPart = createNewPart();
	        parts.push(currentPart);
	        break;

	      case Kind$2.ANCHOR:
	        // we ignore anchors to create the parts and canonized MF
	        break;

	      case Kind$2.COMMENT:
	        // we ignore comments to create the parts and canonized MF
	        break;

	      case Kind$2.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$2.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$2.ATOM) {
	        let group = groups$3[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$2.CHARGE) {
	        if (currentKey !== key.key) {
	          result.push({
	            kind: Kind$2.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$2.CHARGE) return 1;
	      if (b.kind === Kind$2.CHARGE) return -1;
	      let atomA = a.kind === Kind$2.ATOM ? a.value : a.value.atom;
	      let atomB = b.kind === Kind$2.ATOM ? b.value : b.value.atom;
	      if (atomA !== atomB) return atomSorter$2(atomA, atomB); // same atome but some isotopes ...

	      if (a.kind === Kind$2.ATOM) return -1;
	      if (b.kind === Kind$2.ATOM) return 1;
	      if (a.kind === Kind$2.ISOTOPE) return -1;
	      if (b.kind === Kind$2.ISOTOPE) return 1;
	      if (a.kind === Kind$2.ISOTOPE_RATIO) return -1;
	      if (b.kind === Kind$2.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$2.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 Format = Format$3;
	const {
	  superscript: superscript$2,
	  subscript: subscript$2
	} = subSuperscript;

	var toText$1 = function toText(lines) {
	  let text = [];

	  for (let line of lines) {
	    switch (line.kind) {
	      case Format.SUBSCRIPT:
	        {
	          const value = String(line.value);

	          for (let i = 0; i < value.length; i++) {
	            const char = value[i];

	            if (subscript$2[char]) {
	              text.push(subscript$2[char]);
	            } else {
	              throw new Error(`Subscript problem with: ${char}`);
	            }
	          }
	        }
	        break;

	      case Format.SUPERSCRIPT:
	        {
	          const value = String(line.value);

	          for (let i = 0; i < value.length; i++) {
	            const char = value[i];

	            if (superscript$2[char]) {
	              text.push(superscript$2[char]);
	            } else {
	              throw new Error(`Superscript problem with: ${char}`);
	            }
	          }

	          break;
	        }

	      case Format.SUPERIMPOSE:
	        {
	          const under = String(line.under);

	          for (let i = 0; i < under.length; i++) {
	            const char = under[i];

	            if (subscript$2[char]) {
	              text.push(subscript$2[char]);
	            } else {
	              throw new Error(`Subscript problem with: ${char}`);
	            }
	          }

	          const over = String(line.over);

	          for (let i = 0; i < over.length; i++) {
	            const char = over[i];

	            if (superscript$2[char]) {
	              text.push(superscript$2[char]);
	            } else {
	              throw new Error(`Superscript problem with: ${char}`);
	            }
	          }

	          break;
	        }

	      default:
	        text.push(line.value);
	    }
	  }

	  return text.join('');
	};

	const ensureCase = ensureCase$1;
	const parse$3 = parse$4;
	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;
	const toText = toText$1;

	class MF$8 {
	  constructor(mf) {
	    let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};

	    if (options.ensureCase) {
	      mf = ensureCase(mf);
	    }

	    this.parsed = parse$3(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;
	  }

	  toText() {
	    if (!this.cache.text) {
	      this.toDisplay();
	      this.cache.text = toText(this.cache.displayed);
	    }

	    return this.cache.text;
	  }

	  toCanonicText() {
	    if (!this.cache.canonicText) {
	      this.cache.canonicText = new MF$8(this.toMF()).toText(this.cache.displayed);
	    }

	    return this.cache.canonicText;
	  }

	  toParts(options) {
	    if (!this.cache.parts) {
	      this.cache.parts = toParts(this.parsed, options);
	    }

	    return this.cache.parts;
	  }
	  /**
	   * Returns an object with the global MF, global charge, monoisotopic mass and mass
	   * as well as the same information for all the parts
	   * @param {object} [options={}] options
	   */


	  getInfo() {
	    let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};

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

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

	    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$8;

	const parse$2 = parse$4;
	const {
	  subscript: subscript$1,
	  superscript: superscript$1
	} = subSuperscript;
	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$2(mf);
	  let display = toDisplay(parsed);
	  return toHtml(display);
	}

	var src$h = {
	  Kind: Kind$c,
	  Format: Format$3,
	  Style: Style$2,
	  parse: parse$4,
	  ensureCase: ensureCase$1,
	  toDisplay,
	  toHtml,
	  parseToHtml,
	  subscript: subscript$1,
	  superscript: superscript$1,
	  MF: MF_1
	};

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

	function getPeaks$2(peaks) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const {
	    from = xyObjectMinXPoint$1(peaks).x,
	    to = xyObjectMaxXPoint$1(peaks).x,
	    threshold = 0.01,
	    limit,
	    sumValue
	  } = options;
	  let maxY = Number.MIN_SAFE_INTEGER;

	  for (let peak of peaks) {
	    if (peak.y > maxY) maxY = peak.y;
	  }

	  let minY = maxY * threshold;
	  peaks = peaks.filter(peak => peak.x >= from && peak.x <= to && peak.y >= minY);

	  if (limit && peaks.length > limit) {
	    peaks.sort((a, b) => b.y - a.y);
	    peaks = peaks.slice(0, limit);
	  }

	  if (sumValue) {
	    peaks = JSON.parse(JSON.stringify(peaks));
	    const currentSum = xyObjectSumY$1(peaks);
	    const ratio = sumValue / currentSum;
	    peaks.forEach(peak => peak.y *= ratio);
	  }

	  return peaks.sort((a, b) => a.x - b.x);
	}

	var getPeaks_1 = getPeaks$2;

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

	async function getFragmentPeaks$1(peaks, mf) {
	  let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
	  const emdb = new src$3();
	  const {
	    ionizations = '',
	    precision
	  } = options;
	  const mfInfo = new MF$7(mf).getInfo();
	  const ranges = Object.keys(mfInfo.atoms).map(key => `${key}0-${mfInfo.atoms[key]}`).join(' ');
	  peaks = getPeaks$1(peaks, options);

	  for (let peak of peaks) {
	    peak.mfs = (await emdb.fromMonoisotopicMass(peak.x, {
	      precision,
	      ranges,
	      ionizations
	    })).mfs;
	  }

	  peaks = peaks.filter(peak => peak.mfs.length > 0);
	  return peaks;
	}

	var getFragmentPeaks_1 = getFragmentPeaks$1;

	const {
	  xySortX: xySortX$1,
	  xyJoinX: xyJoinX$1
	} = require$$1$3;
	/**
	 * Remove an integer number of time the specifiedd monoisotopic mass
	 * Mass remainder analysis (MARA): https://doi.org/10.1021/acs.analchem.7b04730
	 * @param {object} spectrum
	 * @param {number} mass
	 * @param {object} [options={}
	 * @param {number} [options.delta=0.001]
	 */

	function getMassRemainder$1(spectrum, mass) {
	  let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
	  const {
	    delta = 0.001
	  } = options;
	  const x = spectrum.x.slice();
	  const y = spectrum.y;

	  for (let i = 0; i < x.length; i++) {
	    const factor = Math.floor(x[i] / mass);
	    x[i] = x[i] - factor * mass;
	  } // we sort and join


	  return xyJoinX$1(xySortX$1({
	    x,
	    y
	  }), {
	    delta
	  });
	}

	var getMassRemainder_1 = getMassRemainder$1;

	var mlStat$1 = {};

	var array$2 = {};

	(function (exports) {

	  function compareNumbers(a, b) {
	    return a - b;
	  }
	  /**
	   * Computes the sum of the given values
	   * @param {Array} values
	   * @returns {number}
	   */


	  exports.sum = function sum(values) {
	    var sum = 0;

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

	    return sum;
	  };
	  /**
	   * Computes the maximum of the given values
	   * @param {Array} values
	   * @returns {number}
	   */


	  exports.max = function max(values) {
	    var max = values[0];
	    var l = values.length;

	    for (var i = 1; i < l; i++) {
	      if (values[i] > max) max = values[i];
	    }

	    return max;
	  };
	  /**
	   * Computes the minimum of the given values
	   * @param {Array} values
	   * @returns {number}
	   */


	  exports.min = function min(values) {
	    var min = values[0];
	    var l = values.length;

	    for (var i = 1; i < l; i++) {
	      if (values[i] < min) min = values[i];
	    }

	    return min;
	  };
	  /**
	   * Computes the min and max of the given values
	   * @param {Array} values
	   * @returns {{min: number, max: number}}
	   */


	  exports.minMax = function minMax(values) {
	    var min = values[0];
	    var max = values[0];
	    var l = values.length;

	    for (var i = 1; i < l; i++) {
	      if (values[i] < min) min = values[i];
	      if (values[i] > max) max = values[i];
	    }

	    return {
	      min: min,
	      max: max
	    };
	  };
	  /**
	   * Computes the arithmetic mean of the given values
	   * @param {Array} values
	   * @returns {number}
	   */


	  exports.arithmeticMean = function arithmeticMean(values) {
	    var sum = 0;
	    var l = values.length;

	    for (var i = 0; i < l; i++) {
	      sum += values[i];
	    }

	    return sum / l;
	  };
	  /**
	   * {@link arithmeticMean}
	   */


	  exports.mean = exports.arithmeticMean;
	  /**
	   * Computes the geometric mean of the given values
	   * @param {Array} values
	   * @returns {number}
	   */

	  exports.geometricMean = function geometricMean(values) {
	    var mul = 1;
	    var l = values.length;

	    for (var i = 0; i < l; i++) {
	      mul *= values[i];
	    }

	    return Math.pow(mul, 1 / l);
	  };
	  /**
	   * Computes the mean of the log of the given values
	   * If the return value is exponentiated, it gives the same result as the
	   * geometric mean.
	   * @param {Array} values
	   * @returns {number}
	   */


	  exports.logMean = function logMean(values) {
	    var lnsum = 0;
	    var l = values.length;

	    for (var i = 0; i < l; i++) {
	      lnsum += Math.log(values[i]);
	    }

	    return lnsum / l;
	  };
	  /**
	   * Computes the weighted grand mean for a list of means and sample sizes
	   * @param {Array} means - Mean values for each set of samples
	   * @param {Array} samples - Number of original values for each set of samples
	   * @returns {number}
	   */


	  exports.grandMean = function grandMean(means, samples) {
	    var sum = 0;
	    var n = 0;
	    var l = means.length;

	    for (var i = 0; i < l; i++) {
	      sum += samples[i] * means[i];
	      n += samples[i];
	    }

	    return sum / n;
	  };
	  /**
	   * Computes the truncated mean of the given values using a given percentage
	   * @param {Array} values
	   * @param {number} percent - The percentage of values to keep (range: [0,1])
	   * @param {boolean} [alreadySorted=false]
	   * @returns {number}
	   */


	  exports.truncatedMean = function truncatedMean(values, percent, alreadySorted) {
	    if (alreadySorted === undefined) alreadySorted = false;

	    if (!alreadySorted) {
	      values = [].concat(values).sort(compareNumbers);
	    }

	    var l = values.length;
	    var k = Math.floor(l * percent);
	    var sum = 0;

	    for (var i = k; i < l - k; i++) {
	      sum += values[i];
	    }

	    return sum / (l - 2 * k);
	  };
	  /**
	   * Computes the harmonic mean of the given values
	   * @param {Array} values
	   * @returns {number}
	   */


	  exports.harmonicMean = function harmonicMean(values) {
	    var sum = 0;
	    var l = values.length;

	    for (var i = 0; i < l; i++) {
	      if (values[i] === 0) {
	        throw new RangeError('value at index ' + i + 'is zero');
	      }

	      sum += 1 / values[i];
	    }

	    return l / sum;
	  };
	  /**
	   * Computes the contraharmonic mean of the given values
	   * @param {Array} values
	   * @returns {number}
	   */


	  exports.contraHarmonicMean = function contraHarmonicMean(values) {
	    var r1 = 0;
	    var r2 = 0;
	    var l = values.length;

	    for (var i = 0; i < l; i++) {
	      r1 += values[i] * values[i];
	      r2 += values[i];
	    }

	    if (r2 < 0) {
	      throw new RangeError('sum of values is negative');
	    }

	    return r1 / r2;
	  };
	  /**
	   * Computes the median of the given values
	   * @param {Array} values
	   * @param {boolean} [alreadySorted=false]
	   * @returns {number}
	   */


	  exports.median = function median(values, alreadySorted) {
	    if (alreadySorted === undefined) alreadySorted = false;

	    if (!alreadySorted) {
	      values = [].concat(values).sort(compareNumbers);
	    }

	    var l = values.length;
	    var half = Math.floor(l / 2);

	    if (l % 2 === 0) {
	      return (values[half - 1] + values[half]) * 0.5;
	    } else {
	      return values[half];
	    }
	  };
	  /**
	   * Computes the variance of the given values
	   * @param {Array} values
	   * @param {boolean} [unbiased=true] - if true, divide by (n-1); if false, divide by n.
	   * @returns {number}
	   */


	  exports.variance = function variance(values, unbiased) {
	    if (unbiased === undefined) unbiased = true;
	    var theMean = exports.mean(values);
	    var theVariance = 0;
	    var l = values.length;

	    for (var i = 0; i < l; i++) {
	      var x = values[i] - theMean;
	      theVariance += x * x;
	    }

	    if (unbiased) {
	      return theVariance / (l - 1);
	    } else {
	      return theVariance / l;
	    }
	  };
	  /**
	   * Computes the standard deviation of the given values
	   * @param {Array} values
	   * @param {boolean} [unbiased=true] - if true, divide by (n-1); if false, divide by n.
	   * @returns {number}
	   */


	  exports.standardDeviation = function standardDeviation(values, unbiased) {
	    return Math.sqrt(exports.variance(values, unbiased));
	  };

	  exports.standardError = function standardError(values) {
	    return exports.standardDeviation(values) / Math.sqrt(values.length);
	  };
	  /**
	   * IEEE Transactions on biomedical engineering, vol. 52, no. 1, january 2005, p. 76-
	   * Calculate the standard deviation via the Median of the absolute deviation
	   *  The formula for the standard deviation only holds for Gaussian random variables.
	   * @returns {{mean: number, stdev: number}}
	   */


	  exports.robustMeanAndStdev = function robustMeanAndStdev(y) {
	    var mean = 0,
	        stdev = 0;
	    var length = y.length,
	        i = 0;

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

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

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

	    averageDeviations.sort(compareNumbers);

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

	    return {
	      mean: mean,
	      stdev: stdev
	    };
	  };

	  exports.quartiles = function quartiles(values, alreadySorted) {
	    if (typeof alreadySorted === 'undefined') alreadySorted = false;

	    if (!alreadySorted) {
	      values = [].concat(values).sort(compareNumbers);
	    }

	    var quart = values.length / 4;
	    var q1 = values[Math.ceil(quart) - 1];
	    var q2 = exports.median(values, true);
	    var q3 = values[Math.ceil(quart * 3) - 1];
	    return {
	      q1: q1,
	      q2: q2,
	      q3: q3
	    };
	  };

	  exports.pooledStandardDeviation = function pooledStandardDeviation(samples, unbiased) {
	    return Math.sqrt(exports.pooledVariance(samples, unbiased));
	  };

	  exports.pooledVariance = function pooledVariance(samples, unbiased) {
	    if (typeof unbiased === 'undefined') unbiased = true;
	    var sum = 0;
	    var length = 0,
	        l = samples.length;

	    for (var i = 0; i < l; i++) {
	      var values = samples[i];
	      var vari = exports.variance(values);
	      sum += (values.length - 1) * vari;
	      if (unbiased) length += values.length - 1;else length += values.length;
	    }

	    return sum / length;
	  };

	  exports.mode = function mode(values) {
	    var l = values.length,
	        itemCount = new Array(l),
	        i;

	    for (i = 0; i < l; i++) {
	      itemCount[i] = 0;
	    }

	    var itemArray = new Array(l);
	    var count = 0;

	    for (i = 0; i < l; i++) {
	      var index = itemArray.indexOf(values[i]);
	      if (index >= 0) itemCount[index]++;else {
	        itemArray[count] = values[i];
	        itemCount[count] = 1;
	        count++;
	      }
	    }

	    var maxValue = 0,
	        maxIndex = 0;

	    for (i = 0; i < count; i++) {
	      if (itemCount[i] > maxValue) {
	        maxValue = itemCount[i];
	        maxIndex = i;
	      }
	    }

	    return itemArray[maxIndex];
	  };

	  exports.covariance = function covariance(vector1, vector2, unbiased) {
	    if (typeof unbiased === 'undefined') unbiased = true;
	    var mean1 = exports.mean(vector1);
	    var mean2 = exports.mean(vector2);
	    if (vector1.length !== vector2.length) throw 'Vectors do not have the same dimensions';
	    var cov = 0,
	        l = vector1.length;

	    for (var i = 0; i < l; i++) {
	      var x = vector1[i] - mean1;
	      var y = vector2[i] - mean2;
	      cov += x * y;
	    }

	    if (unbiased) return cov / (l - 1);else return cov / l;
	  };

	  exports.skewness = function skewness(values, unbiased) {
	    if (typeof unbiased === 'undefined') unbiased = true;
	    var theMean = exports.mean(values);
	    var s2 = 0,
	        s3 = 0,
	        l = values.length;

	    for (var i = 0; i < l; i++) {
	      var dev = values[i] - theMean;
	      s2 += dev * dev;
	      s3 += dev * dev * dev;
	    }

	    var m2 = s2 / l;
	    var m3 = s3 / l;
	    var g = m3 / Math.pow(m2, 3 / 2.0);

	    if (unbiased) {
	      var a = Math.sqrt(l * (l - 1));
	      var b = l - 2;
	      return a / b * g;
	    } else {
	      return g;
	    }
	  };

	  exports.kurtosis = function kurtosis(values, unbiased) {
	    if (typeof unbiased === 'undefined') unbiased = true;
	    var theMean = exports.mean(values);
	    var n = values.length,
	        s2 = 0,
	        s4 = 0;

	    for (var i = 0; i < n; i++) {
	      var dev = values[i] - theMean;
	      s2 += dev * dev;
	      s4 += dev * dev * dev * dev;
	    }

	    var m2 = s2 / n;
	    var m4 = s4 / n;

	    if (unbiased) {
	      var v = s2 / (n - 1);
	      var a = n * (n + 1) / ((n - 1) * (n - 2) * (n - 3));
	      var b = s4 / (v * v);
	      var c = (n - 1) * (n - 1) / ((n - 2) * (n - 3));
	      return a * b - 3 * c;
	    } else {
	      return m4 / (m2 * m2) - 3;
	    }
	  };

	  exports.entropy = function entropy(values, eps) {
	    if (typeof eps === 'undefined') eps = 0;
	    var sum = 0,
	        l = values.length;

	    for (var i = 0; i < l; i++) sum += values[i] * Math.log(values[i] + eps);

	    return -sum;
	  };

	  exports.weightedMean = function weightedMean(values, weights) {
	    var sum = 0,
	        l = values.length;

	    for (var i = 0; i < l; i++) sum += values[i] * weights[i];

	    return sum;
	  };

	  exports.weightedStandardDeviation = function weightedStandardDeviation(values, weights) {
	    return Math.sqrt(exports.weightedVariance(values, weights));
	  };

	  exports.weightedVariance = function weightedVariance(values, weights) {
	    var theMean = exports.weightedMean(values, weights);
	    var vari = 0,
	        l = values.length;
	    var a = 0,
	        b = 0;

	    for (var i = 0; i < l; i++) {
	      var z = values[i] - theMean;
	      var w = weights[i];
	      vari += w * (z * z);
	      b += w;
	      a += w * w;
	    }

	    return vari * (b / (b * b - a));
	  };

	  exports.center = function center(values, inPlace) {
	    if (typeof inPlace === 'undefined') inPlace = false;
	    var result = values;
	    if (!inPlace) result = [].concat(values);
	    var theMean = exports.mean(result),
	        l = result.length;

	    for (var i = 0; i < l; i++) result[i] -= theMean;
	  };

	  exports.standardize = function standardize(values, standardDev, inPlace) {
	    if (typeof standardDev === 'undefined') standardDev = exports.standardDeviation(values);
	    if (typeof inPlace === 'undefined') inPlace = false;
	    var l = values.length;
	    var result = inPlace ? values : new Array(l);

	    for (var i = 0; i < l; i++) result[i] = values[i] / standardDev;

	    return result;
	  };

	  exports.cumulativeSum = function cumulativeSum(array) {
	    var l = array.length;
	    var result = new Array(l);
	    result[0] = array[0];

	    for (var i = 1; i < l; i++) result[i] = result[i - 1] + array[i];

	    return result;
	  };
	})(array$2);

	var matrix$1 = {};

	(function (exports) {

	  var arrayStat = array$2;

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

	  exports.max = function max(matrix) {
	    var max = -Infinity;

	    for (var i = 0; i < matrix.length; i++) {
	      for (var j = 0; j < matrix[i].length; j++) {
	        if (matrix[i][j] > max) max = matrix[i][j];
	      }
	    }

	    return max;
	  };

	  exports.min = function min(matrix) {
	    var min = Infinity;

	    for (var i = 0; i < matrix.length; i++) {
	      for (var j = 0; j < matrix[i].length; j++) {
	        if (matrix[i][j] < min) min = matrix[i][j];
	      }
	    }

	    return min;
	  };

	  exports.minMax = function minMax(matrix) {
	    var min = Infinity;
	    var max = -Infinity;

	    for (var i = 0; i < matrix.length; i++) {
	      for (var j = 0; j < matrix[i].length; j++) {
	        if (matrix[i][j] < min) min = matrix[i][j];
	        if (matrix[i][j] > max) max = matrix[i][j];
	      }
	    }

	    return {
	      min: min,
	      max: max
	    };
	  };

	  exports.entropy = function entropy(matrix, eps) {
	    if (typeof eps === 'undefined') {
	      eps = 0;
	    }

	    var sum = 0,
	        l1 = matrix.length,
	        l2 = matrix[0].length;

	    for (var i = 0; i < l1; i++) {
	      for (var j = 0; j < l2; j++) {
	        sum += matrix[i][j] * Math.log(matrix[i][j] + eps);
	      }
	    }

	    return -sum;
	  };

	  exports.mean = function mean(matrix, dimension) {
	    if (typeof dimension === 'undefined') {
	      dimension = 0;
	    }

	    var rows = matrix.length,
	        cols = matrix[0].length,
	        theMean,
	        N,
	        i,
	        j;

	    if (dimension === -1) {
	      theMean = [0];
	      N = rows * cols;

	      for (i = 0; i < rows; i++) {
	        for (j = 0; j < cols; j++) {
	          theMean[0] += matrix[i][j];
	        }
	      }

	      theMean[0] /= N;
	    } else if (dimension === 0) {
	      theMean = new Array(cols);
	      N = rows;

	      for (j = 0; j < cols; j++) {
	        theMean[j] = 0;

	        for (i = 0; i < rows; i++) {
	          theMean[j] += matrix[i][j];
	        }

	        theMean[j] /= N;
	      }
	    } else if (dimension === 1) {
	      theMean = new Array(rows);
	      N = cols;

	      for (j = 0; j < rows; j++) {
	        theMean[j] = 0;

	        for (i = 0; i < cols; i++) {
	          theMean[j] += matrix[j][i];
	        }

	        theMean[j] /= N;
	      }
	    } else {
	      throw new Error('Invalid dimension');
	    }

	    return theMean;
	  };

	  exports.sum = function sum(matrix, dimension) {
	    if (typeof dimension === 'undefined') {
	      dimension = 0;
	    }

	    var rows = matrix.length,
	        cols = matrix[0].length,
	        theSum,
	        i,
	        j;

	    if (dimension === -1) {
	      theSum = [0];

	      for (i = 0; i < rows; i++) {
	        for (j = 0; j < cols; j++) {
	          theSum[0] += matrix[i][j];
	        }
	      }
	    } else if (dimension === 0) {
	      theSum = new Array(cols);

	      for (j = 0; j < cols; j++) {
	        theSum[j] = 0;

	        for (i = 0; i < rows; i++) {
	          theSum[j] += matrix[i][j];
	        }
	      }
	    } else if (dimension === 1) {
	      theSum = new Array(rows);

	      for (j = 0; j < rows; j++) {
	        theSum[j] = 0;

	        for (i = 0; i < cols; i++) {
	          theSum[j] += matrix[j][i];
	        }
	      }
	    } else {
	      throw new Error('Invalid dimension');
	    }

	    return theSum;
	  };

	  exports.product = function product(matrix, dimension) {
	    if (typeof dimension === 'undefined') {
	      dimension = 0;
	    }

	    var rows = matrix.length,
	        cols = matrix[0].length,
	        theProduct,
	        i,
	        j;

	    if (dimension === -1) {
	      theProduct = [1];

	      for (i = 0; i < rows; i++) {
	        for (j = 0; j < cols; j++) {
	          theProduct[0] *= matrix[i][j];
	        }
	      }
	    } else if (dimension === 0) {
	      theProduct = new Array(cols);

	      for (j = 0; j < cols; j++) {
	        theProduct[j] = 1;

	        for (i = 0; i < rows; i++) {
	          theProduct[j] *= matrix[i][j];
	        }
	      }
	    } else if (dimension === 1) {
	      theProduct = new Array(rows);

	      for (j = 0; j < rows; j++) {
	        theProduct[j] = 1;

	        for (i = 0; i < cols; i++) {
	          theProduct[j] *= matrix[j][i];
	        }
	      }
	    } else {
	      throw new Error('Invalid dimension');
	    }

	    return theProduct;
	  };

	  exports.standardDeviation = function standardDeviation(matrix, means, unbiased) {
	    var vari = exports.variance(matrix, means, unbiased),
	        l = vari.length;

	    for (var i = 0; i < l; i++) {
	      vari[i] = Math.sqrt(vari[i]);
	    }

	    return vari;
	  };

	  exports.variance = function variance(matrix, means, unbiased) {
	    if (typeof unbiased === 'undefined') {
	      unbiased = true;
	    }

	    means = means || exports.mean(matrix);
	    var rows = matrix.length;
	    if (rows === 0) return [];
	    var cols = matrix[0].length;
	    var vari = new Array(cols);

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

	      for (var i = 0; i < rows; i++) {
	        x = matrix[i][j] - means[j];
	        sum1 += x;
	        sum2 += x * x;
	      }

	      if (unbiased) {
	        vari[j] = (sum2 - sum1 * sum1 / rows) / (rows - 1);
	      } else {
	        vari[j] = (sum2 - sum1 * sum1 / rows) / rows;
	      }
	    }

	    return vari;
	  };

	  exports.median = function median(matrix) {
	    var rows = matrix.length,
	        cols = matrix[0].length;
	    var medians = new Array(cols);

	    for (var i = 0; i < cols; i++) {
	      var data = new Array(rows);

	      for (var j = 0; j < rows; j++) {
	        data[j] = matrix[j][i];
	      }

	      data.sort(compareNumbers);
	      var N = data.length;

	      if (N % 2 === 0) {
	        medians[i] = (data[N / 2] + data[N / 2 - 1]) * 0.5;
	      } else {
	        medians[i] = data[Math.floor(N / 2)];
	      }
	    }

	    return medians;
	  };

	  exports.mode = function mode(matrix) {
	    var rows = matrix.length,
	        cols = matrix[0].length,
	        modes = new Array(cols),
	        i,
	        j;

	    for (i = 0; i < cols; i++) {
	      var itemCount = new Array(rows);

	      for (var k = 0; k < rows; k++) {
	        itemCount[k] = 0;
	      }

	      var itemArray = new Array(rows);
	      var count = 0;

	      for (j = 0; j < rows; j++) {
	        var index = itemArray.indexOf(matrix[j][i]);

	        if (index >= 0) {
	          itemCount[index]++;
	        } else {
	          itemArray[count] = matrix[j][i];
	          itemCount[count] = 1;
	          count++;
	        }
	      }

	      var maxValue = 0,
	          maxIndex = 0;

	      for (j = 0; j < count; j++) {
	        if (itemCount[j] > maxValue) {
	          maxValue = itemCount[j];
	          maxIndex = j;
	        }
	      }

	      modes[i] = itemArray[maxIndex];
	    }

	    return modes;
	  };

	  exports.skewness = function skewness(matrix, unbiased) {
	    if (typeof unbiased === 'undefined') unbiased = true;
	    var means = exports.mean(matrix);
	    var n = matrix.length,
	        l = means.length;
	    var skew = new Array(l);

	    for (var j = 0; j < l; j++) {
	      var s2 = 0,
	          s3 = 0;

	      for (var i = 0; i < n; i++) {
	        var dev = matrix[i][j] - means[j];
	        s2 += dev * dev;
	        s3 += dev * dev * dev;
	      }

	      var m2 = s2 / n;
	      var m3 = s3 / n;
	      var g = m3 / Math.pow(m2, 3 / 2);

	      if (unbiased) {
	        var a = Math.sqrt(n * (n - 1));
	        var b = n - 2;
	        skew[j] = a / b * g;
	      } else {
	        skew[j] = g;
	      }
	    }

	    return skew;
	  };

	  exports.kurtosis = function kurtosis(matrix, unbiased) {
	    if (typeof unbiased === 'undefined') unbiased = true;
	    var means = exports.mean(matrix);
	    var n = matrix.length,
	        m = matrix[0].length;
	    var kurt = new Array(m);

	    for (var j = 0; j < m; j++) {
	      var s2 = 0,
	          s4 = 0;

	      for (var i = 0; i < n; i++) {
	        var dev = matrix[i][j] - means[j];
	        s2 += dev * dev;
	        s4 += dev * dev * dev * dev;
	      }

	      var m2 = s2 / n;
	      var m4 = s4 / n;

	      if (unbiased) {
	        var v = s2 / (n - 1);
	        var a = n * (n + 1) / ((n - 1) * (n - 2) * (n - 3));
	        var b = s4 / (v * v);
	        var c = (n - 1) * (n - 1) / ((n - 2) * (n - 3));
	        kurt[j] = a * b - 3 * c;
	      } else {
	        kurt[j] = m4 / (m2 * m2) - 3;
	      }
	    }

	    return kurt;
	  };

	  exports.standardError = function standardError(matrix) {
	    var samples = matrix.length;
	    var standardDeviations = exports.standardDeviation(matrix);
	    var l = standardDeviations.length;
	    var standardErrors = new Array(l);
	    var sqrtN = Math.sqrt(samples);

	    for (var i = 0; i < l; i++) {
	      standardErrors[i] = standardDeviations[i] / sqrtN;
	    }

	    return standardErrors;
	  };

	  exports.covariance = function covariance(matrix, dimension) {
	    return exports.scatter(matrix, undefined, dimension);
	  };

	  exports.scatter = function scatter(matrix, divisor, dimension) {
	    if (typeof dimension === 'undefined') {
	      dimension = 0;
	    }

	    if (typeof divisor === 'undefined') {
	      if (dimension === 0) {
	        divisor = matrix.length - 1;
	      } else if (dimension === 1) {
	        divisor = matrix[0].length - 1;
	      }
	    }

	    var means = exports.mean(matrix, dimension);
	    var rows = matrix.length;

	    if (rows === 0) {
	      return [[]];
	    }

	    var cols = matrix[0].length,
	        cov,
	        i,
	        j,
	        s,
	        k;

	    if (dimension === 0) {
	      cov = new Array(cols);

	      for (i = 0; i < cols; i++) {
	        cov[i] = new Array(cols);
	      }

	      for (i = 0; i < cols; i++) {
	        for (j = i; j < cols; j++) {
	          s = 0;

	          for (k = 0; k < rows; k++) {
	            s += (matrix[k][j] - means[j]) * (matrix[k][i] - means[i]);
	          }

	          s /= divisor;
	          cov[i][j] = s;
	          cov[j][i] = s;
	        }
	      }
	    } else if (dimension === 1) {
	      cov = new Array(rows);

	      for (i = 0; i < rows; i++) {
	        cov[i] = new Array(rows);
	      }

	      for (i = 0; i < rows; i++) {
	        for (j = i; j < rows; j++) {
	          s = 0;

	          for (k = 0; k < cols; k++) {
	            s += (matrix[j][k] - means[j]) * (matrix[i][k] - means[i]);
	          }

	          s /= divisor;
	          cov[i][j] = s;
	          cov[j][i] = s;
	        }
	      }
	    } else {
	      throw new Error('Invalid dimension');
	    }

	    return cov;
	  };

	  exports.correlation = function correlation(matrix) {
	    var means = exports.mean(matrix),
	        standardDeviations = exports.standardDeviation(matrix, true, means),
	        scores = exports.zScores(matrix, means, standardDeviations),
	        rows = matrix.length,
	        cols = matrix[0].length,
	        i,
	        j;
	    var cor = new Array(cols);

	    for (i = 0; i < cols; i++) {
	      cor[i] = new Array(cols);
	    }

	    for (i = 0; i < cols; i++) {
	      for (j = i; j < cols; j++) {
	        var c = 0;

	        for (var k = 0, l = scores.length; k < l; k++) {
	          c += scores[k][j] * scores[k][i];
	        }

	        c /= rows - 1;
	        cor[i][j] = c;
	        cor[j][i] = c;
	      }
	    }

	    return cor;
	  };

	  exports.zScores = function zScores(matrix, means, standardDeviations) {
	    means = means || exports.mean(matrix);
	    if (typeof standardDeviations === 'undefined') standardDeviations = exports.standardDeviation(matrix, true, means);
	    return exports.standardize(exports.center(matrix, means, false), standardDeviations, true);
	  };

	  exports.center = function center(matrix, means, inPlace) {
	    means = means || exports.mean(matrix);
	    var result = matrix,
	        l = matrix.length,
	        i,
	        j,
	        jj;

	    if (!inPlace) {
	      result = new Array(l);

	      for (i = 0; i < l; i++) {
	        result[i] = new Array(matrix[i].length);
	      }
	    }

	    for (i = 0; i < l; i++) {
	      var row = result[i];

	      for (j = 0, jj = row.length; j < jj; j++) {
	        row[j] = matrix[i][j] - means[j];
	      }
	    }

	    return result;
	  };

	  exports.standardize = function standardize(matrix, standardDeviations, inPlace) {
	    if (typeof standardDeviations === 'undefined') standardDeviations = exports.standardDeviation(matrix);
	    var result = matrix,
	        l = matrix.length,
	        i,
	        j,
	        jj;

	    if (!inPlace) {
	      result = new Array(l);

	      for (i = 0; i < l; i++) {
	        result[i] = new Array(matrix[i].length);
	      }
	    }

	    for (i = 0; i < l; i++) {
	      var resultRow = result[i];
	      var sourceRow = matrix[i];

	      for (j = 0, jj = resultRow.length; j < jj; j++) {
	        if (standardDeviations[j] !== 0 && !isNaN(standardDeviations[j])) {
	          resultRow[j] = sourceRow[j] / standardDeviations[j];
	        }
	      }
	    }

	    return result;
	  };

	  exports.weightedVariance = function weightedVariance(matrix, weights) {
	    var means = exports.mean(matrix);
	    var rows = matrix.length;
	    if (rows === 0) return [];
	    var cols = matrix[0].length;
	    var vari = new Array(cols);

	    for (var j = 0; j < cols; j++) {
	      var sum = 0;
	      var a = 0,
	          b = 0;

	      for (var i = 0; i < rows; i++) {
	        var z = matrix[i][j] - means[j];
	        var w = weights[i];
	        sum += w * (z * z);
	        b += w;
	        a += w * w;
	      }

	      vari[j] = sum * (b / (b * b - a));
	    }

	    return vari;
	  };

	  exports.weightedMean = function weightedMean(matrix, weights, dimension) {
	    if (typeof dimension === 'undefined') {
	      dimension = 0;
	    }

	    var rows = matrix.length;
	    if (rows === 0) return [];
	    var cols = matrix[0].length,
	        means,
	        i,
	        ii,
	        j,
	        w,
	        row;

	    if (dimension === 0) {
	      means = new Array(cols);

	      for (i = 0; i < cols; i++) {
	        means[i] = 0;
	      }

	      for (i = 0; i < rows; i++) {
	        row = matrix[i];
	        w = weights[i];

	        for (j = 0; j < cols; j++) {
	          means[j] += row[j] * w;
	        }
	      }
	    } else if (dimension === 1) {
	      means = new Array(rows);

	      for (i = 0; i < rows; i++) {
	        means[i] = 0;
	      }

	      for (j = 0; j < rows; j++) {
	        row = matrix[j];
	        w = weights[j];

	        for (i = 0; i < cols; i++) {
	          means[j] += row[i] * w;
	        }
	      }
	    } else {
	      throw new Error('Invalid dimension');
	    }

	    var weightSum = arrayStat.sum(weights);

	    if (weightSum !== 0) {
	      for (i = 0, ii = means.length; i < ii; i++) {
	        means[i] /= weightSum;
	      }
	    }

	    return means;
	  };

	  exports.weightedCovariance = function weightedCovariance(matrix, weights, means, dimension) {
	    dimension = dimension || 0;
	    means = means || exports.weightedMean(matrix, weights, dimension);
	    var s1 = 0,
	        s2 = 0;

	    for (var i = 0, ii = weights.length; i < ii; i++) {
	      s1 += weights[i];
	      s2 += weights[i] * weights[i];
	    }

	    var factor = s1 / (s1 * s1 - s2);
	    return exports.weightedScatter(matrix, weights, means, factor, dimension);
	  };

	  exports.weightedScatter = function weightedScatter(matrix, weights, means, factor, dimension) {
	    dimension = dimension || 0;
	    means = means || exports.weightedMean(matrix, weights, dimension);

	    if (typeof factor === 'undefined') {
	      factor = 1;
	    }

	    var rows = matrix.length;

	    if (rows === 0) {
	      return [[]];
	    }

	    var cols = matrix[0].length,
	        cov,
	        i,
	        j,
	        k,
	        s;

	    if (dimension === 0) {
	      cov = new Array(cols);

	      for (i = 0; i < cols; i++) {
	        cov[i] = new Array(cols);
	      }

	      for (i = 0; i < cols; i++) {
	        for (j = i; j < cols; j++) {
	          s = 0;

	          for (k = 0; k < rows; k++) {
	            s += weights[k] * (matrix[k][j] - means[j]) * (matrix[k][i] - means[i]);
	          }

	          cov[i][j] = s * factor;
	          cov[j][i] = s * factor;
	        }
	      }
	    } else if (dimension === 1) {
	      cov = new Array(rows);

	      for (i = 0; i < rows; i++) {
	        cov[i] = new Array(rows);
	      }

	      for (i = 0; i < rows; i++) {
	        for (j = i; j < rows; j++) {
	          s = 0;

	          for (k = 0; k < cols; k++) {
	            s += weights[k] * (matrix[j][k] - means[j]) * (matrix[i][k] - means[i]);
	          }

	          cov[i][j] = s * factor;
	          cov[j][i] = s * factor;
	        }
	      }
	    } else {
	      throw new Error('Invalid dimension');
	    }

	    return cov;
	  };
	})(matrix$1);

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

	var COMMON_NO$1 = 0;
	var COMMON_FIRST$1 = 1;
	var COMMON_SECOND$1 = 2;
	var COMMON_BOTH$1 = 3; // should be a binary operation !

	var Stat$1 = mlStat$1.array;
	/**
	 * Create a comparator class
	 * {object} [options={}]
	 * {string} [options.common=''] should we take only common peaks 'first', 'second', 'both', ''
	 * {number} [options.widthBottom=2] bottom trapezoid width for similarity evaluation
	 * {number} [options.widthTop=1] top trapezoid width for similarity evaluation
	 * {number} [options.from] from region used for similarity calculation
	 * {number} [options.to] to region used for similarity calculation
	 */

	var src$g = function Comparator() {
	  let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
	  var widthTop, widthBottom, from, to;
	  var array1Extract, array2Extract, widthSlope, array1ExtractInfo, array2ExtractInfo;
	  var common, commonFactor;
	  setOptions(options);
	  var array1 = [];
	  var array2 = [];
	  /*
	     2 formats are allowed:
	     [[x1,x2,...],[y1,y2,...]] or [[x1,y1],[x2,y2], ...]
	    */

	  function setOptions(newOptions) {
	    options = newOptions || {};

	    if (typeof options.common === 'string') {
	      if (options.common.toLowerCase() === 'first') {
	        common = COMMON_FIRST$1;
	      } else if (options.common.toLowerCase() === 'second') {
	        common = COMMON_SECOND$1;
	      } else if (options.common.toLowerCase() === 'both') {
	        common = COMMON_BOTH$1;
	      } else {
	        common = COMMON_NO$1;
	      }
	    } else {
	      if (options.common === true) {
	        common = COMMON_BOTH$1;
	      } else {
	        common = COMMON_NO$1;
	      }
	    }

	    commonFactor = options.commonFactor || commonFactor || 4;

	    if (options.widthBottom === undefined) {
	      options.widthBottom = widthBottom || 2;
	    }

	    if (options.widthTop === undefined) {
	      options.widthTop = widthTop || 1;
	    }

	    setTrapezoid(options.widthBottom, options.widthTop);
	    setFromTo(options.from || from, options.to || to);
	  }

	  function setPeaks1(anArray) {
	    array1 = checkArray(anArray);

	    if (common) {
	      var extracts = commonExtractAndNormalize$1(array1, array2, widthBottom, from, to, common);
	      array1Extract = extracts.data1;
	      array1ExtractInfo = extracts.info1;
	      array2Extract = extracts.data2;
	      array2ExtractInfo = extracts.info2;
	    } else {
	      var extract = extractAndNormalize$1(array1, from, to);
	      array1Extract = extract.data;
	      array1ExtractInfo = extract.info;
	    }
	  }

	  function setPeaks2(anArray) {
	    array2 = checkArray(anArray);

	    if (common) {
	      var extracts = commonExtractAndNormalize$1(array1, array2, widthBottom, from, to, common);
	      array1Extract = extracts.data1;
	      array1ExtractInfo = extracts.info1;
	      array2Extract = extracts.data2;
	      array2ExtractInfo = extracts.info2;
	    } else {
	      var extract = extractAndNormalize$1(array2, from, to);
	      array2Extract = extract.data;
	      array2ExtractInfo = extract.info;
	    }
	  }

	  function getExtract1() {
	    return array1Extract;
	  }

	  function getExtract2() {
	    return array2Extract;
	  }

	  function getExtractInfo1() {
	    return array1ExtractInfo;
	  }

	  function getExtractInfo2() {
	    return array2ExtractInfo;
	  }

	  function setTrapezoid(newWidthBottom, newWidthTop) {
	    widthTop = newWidthTop;
	    widthBottom = newWidthBottom;
	    widthSlope = (widthBottom - widthTop) / 2;
	    if (widthBottom < widthTop) throw new Error('widthBottom has to be larger than widthTop');
	  }

	  function setFromTo(newFrom, newTo) {
	    if (newFrom === from && newTo === to) return;
	    from = newFrom;
	    to = newTo;

	    if (common) {
	      var extracts = commonExtractAndNormalize$1(array1, array2, widthBottom, from, to, common);
	      array1Extract = extracts.data1;
	      array1ExtractInfo = extracts.info1;
	      array2Extract = extracts.data2;
	      array2ExtractInfo = extracts.info2;
	    } else {
	      var extract = extractAndNormalize$1(array1, from, to);
	      array1Extract = extract.data;
	      array1ExtractInfo = extract.info;
	      extract = extractAndNormalize$1(array2, from, to);
	      array2Extract = extract.data;
	      array2ExtractInfo = extract.info;
	    }
	  }

	  function getOverlap(x1, y1, x2, y2) {
	    if (y1 === 0 || y2 === 0) return 0; // TAKE CARE !!! We multiply the diff by 2 !!!

	    var diff = Math.abs(x1 - x2) * 2;
	    if (diff > widthBottom) return 0;

	    if (diff <= widthTop) {
	      return Math.min(y1, y2);
	    }

	    var maxValue = Math.max(y1, y2) * (widthBottom - diff) / (widthBottom - widthTop);
	    return Math.min(y1, y2, maxValue);
	  } // This is the old trapezoid similarity


	  function getOverlapTrapezoid(x1, y1, x2, y2) {
	    var factor = 2 / (widthTop + widthBottom); // correction for surface=1

	    if (y1 === 0 || y2 === 0) return 0;

	    if (x1 === x2) {
	      // they have the same position
	      return Math.min(y1, y2);
	    }

	    var diff = Math.abs(x1 - x2);
	    if (diff >= widthBottom) return 0;

	    if (y1 === y2) {
	      // do they have the same height ???
	      // we need to find the common length
	      if (diff <= widthTop) {
	        return ((widthTop + widthBottom) / 2 - diff) * y1 * factor;
	      } else if (diff <= widthBottom) {
	        return (widthBottom - diff) * y1 / 2 * (diff - widthTop) / (widthBottom - widthTop) * factor;
	      }

	      return 0;
	    } else {
	      // the height are different and not the same position ...
	      // we need to consider only one segment to find its intersection
	      var small = Math.min(y1, y2);
	      var big = Math.max(y1, y2);
	      var targets = [[[0, 0], [widthSlope, small]], [[widthSlope, small], [widthSlope + widthTop, small]], [[widthTop + widthSlope, small], [widthBottom, 0]]];
	      var segment;

	      if (x1 > x2 && y1 > y2 || x1 < x2 && y1 < y2) {
	        segment = [[diff, 0], [diff + widthSlope, big]];
	      } else {
	        segment = [[diff + widthSlope, big], [diff, 0]];
	      }

	      for (var i = 0; i < 3; i++) {
	        var intersection = getIntersection$1(targets[i], segment);

	        if (intersection) {
	          switch (i) {
	            case 0:
	              return small - diff * intersection.y / 2 * factor;

	            case 1:
	              // to simplify ...
	              //     console.log("           ",widthSlope,small,big,intersection.x)
	              return (widthSlope * small / (2 * big) * small + (widthTop + widthSlope - intersection.x) * small + widthSlope * small / 2) * factor;

	            case 2:
	              return (widthBottom - diff) * intersection.y / 2 * factor;

	            default:
	              throw new Error(`unexpected intersection value: ${i}`);
	          }
	        }
	      }
	    }

	    return NaN;
	  } // this method calculates the total diff. The sum of positive value will yield to overlap


	  function calculateDiff() {
	    // we need to take 2 pointers
	    // and travel progressively between them ...
	    var newFirst = [[].concat(array1Extract[0]), [].concat(array1Extract[1])];
	    var newSecond = [[].concat(array2Extract[0]), [].concat(array2Extract[1])];
	    var array1Length = array1Extract[0] ? array1Extract[0].length : 0;
	    var array2Length = array2Extract[0] ? array2Extract[0].length : 0;
	    var pos1 = 0;
	    var pos2 = 0;
	    var previous2 = 0;

	    while (pos1 < array1Length) {
	      var diff = newFirst[0][pos1] - array2Extract[0][pos2];

	      if (Math.abs(diff) < widthBottom) {
	        // there is some overlap
	        var overlap;

	        if (options.trapezoid) {
	          overlap = getOverlapTrapezoid(newFirst[0][pos1], newFirst[1][pos1], newSecond[0][pos2], newSecond[1][pos2]);
	        } else {
	          overlap = getOverlap(newFirst[0][pos1], newFirst[1][pos1], newSecond[0][pos2], newSecond[1][pos2]);
	        }

	        newFirst[1][pos1] -= overlap;
	        newSecond[1][pos2] -= overlap;

	        if (pos2 < array2Length - 1) {
	          pos2++;
	        } else {
	          pos1++;
	          pos2 = previous2;
	        }
	      } else {
	        if (diff > 0 && pos2 < array2Length - 1) {
	          pos2++;
	          previous2 = pos2;
	        } else {
	          pos1++;
	          pos2 = previous2;
	        }
	      }
	    }

	    return newSecond;
	  }
	  /*
	        This code requires the use of an array like  [[x1,y1],[x2,y2], ...]
	        If it is not the right format, we will just convert it
	        Otherwise we return the correct format
	     */


	  function checkArray(points) {
	    // if it is already a 2D array of points, we just return them
	    if (Array.isArray(points) && Array.isArray(points[0]) && points.length === 2) return points;
	    var x = new Array(points.length);
	    var y = new Array(points.length);

	    for (var i = 0; i < points.length; i++) {
	      x[i] = points[i][0];
	      y[i] = points[i][1];
	    }

	    return [x, y];
	  }

	  function getSimilarity(newPeaks1, newPeaks2) {
	    if (newPeaks1) setPeaks1(newPeaks1);
	    if (newPeaks2) setPeaks2(newPeaks2);
	    var result = {};
	    result.diff = calculateDiff();
	    result.extract1 = getExtract1();
	    result.extract2 = getExtract2();
	    result.extractInfo1 = getExtractInfo1();
	    result.extractInfo2 = getExtractInfo2();
	    result.similarity = calculateOverlapFromDiff$1(result.diff);
	    result.widthBottom = widthBottom;
	    result.widthTop = widthTop;
	    return result;
	  }
	  /*
	    This works mainly when you have a array1 that is fixed
	    newPeaks2 have to be normalized ! (sum to 1)
	  */


	  function fastSimilarity(newPeaks2, from, to) {
	    array1Extract = extract$1(array1, from, to);
	    array2Extract = newPeaks2;
	    if (common & COMMON_SECOND$1) array1Extract = getCommonArray$1(array1Extract, array2Extract, widthBottom);
	    normalize$2(array1Extract);
	    var diff = calculateDiff();
	    return calculateOverlapFromDiff$1(diff);
	  }

	  this.setPeaks1 = setPeaks1;
	  this.setPeaks2 = setPeaks2;
	  this.getExtract1 = getExtract1;
	  this.getExtract2 = getExtract2;
	  this.getExtractInfo1 = getExtractInfo1;
	  this.getExtractInfo2 = getExtractInfo2;
	  this.setFromTo = setFromTo;
	  this.setOptions = setOptions;
	  this.setTrapezoid = setTrapezoid;
	  this.getSimilarity = getSimilarity;
	  this.getCommonArray = getCommonArray$1;
	  this.fastSimilarity = fastSimilarity;
	}; // returns an new array based on array1 where there is a peak of array2 at a distance under width/2


	function getCommonArray$1(array1, array2, width) {
	  var newArray = [[], []];
	  var pos2 = 0;
	  width /= 2;
	  var j = 0;
	  var array1Length = array1[0] ? array1[0].length : 0;
	  var array2Length = array2[0] ? array2[0].length : 0;

	  for (var i = 0; i < array1Length; i++) {
	    while (pos2 < array2Length && array1[0][i] > array2[0][pos2] + width) {
	      pos2++;
	    }

	    if (pos2 < array2Length && array1[0][i] > array2[0][pos2] - width) {
	      newArray[0][j] = array1[0][i];
	      newArray[1][j] = array1[1][i];
	      j++;
	    }
	  }

	  return newArray;
	} // Adapted from: http://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect/1968345#1968345


	function getIntersection$1(segment1, segment2) {
	  var p0X = segment1[0][0];
	  var p0Y = segment1[0][1];
	  var p1X = segment1[1][0];
	  var p1Y = segment1[1][1];
	  var p2X = segment2[0][0];
	  var p2Y = segment2[0][1];
	  var p3X = segment2[1][0];
	  var p3Y = segment2[1][1];
	  var s1X, s1Y, s2X, s2Y;
	  s1X = p1X - p0X;
	  s1Y = p1Y - p0Y;
	  s2X = p3X - p2X;
	  s2Y = p3Y - p2Y;
	  var s, t;
	  s = (-s1Y * (p0X - p2X) + s1X * (p0Y - p2Y)) / (-s2X * s1Y + s1X * s2Y);
	  t = (s2X * (p0Y - p2Y) - s2Y * (p0X - p2X)) / (-s2X * s1Y + s1X * s2Y);

	  if (s >= 0 && s <= 1 && t >= 0 && t <= 1) {
	    return {
	      x: p0X + t * s1X,
	      y: p0Y + t * s1Y
	    };
	  }

	  return null; // No collision
	}

	function normalize$2(array) {
	  var min = Stat$1.min(array[1]);
	  var max = Stat$1.max(array[1]);
	  var sum = Stat$1.sum(array[1]);
	  var length = array[1] ? array[1].length : 0;

	  if (sum !== 0) {
	    for (var i = 0; i < length; i++) {
	      array[1][i] /= sum;
	    }
	  }

	  return {
	    sum: sum,
	    min: min,
	    max: max
	  };
	} // this method will systematically take care of both array


	function commonExtractAndNormalize$1(array1, array2, width, from, to, common) {
	  if (!Array.isArray(array1) || !Array.isArray(array2)) {
	    return {
	      info: undefined,
	      data: undefined
	    };
	  }

	  var extract1 = extract$1(array1, from, to);
	  var extract2 = extract$1(array2, from, to);
	  var common1, common2, info1, info2;

	  if (common & COMMON_SECOND$1) {
	    common1 = getCommonArray$1(extract1, extract2, width);
	    info1 = normalize$2(common1);
	  } else {
	    common1 = extract1;
	    info1 = normalize$2(common1);
	  }

	  if (common & COMMON_FIRST$1) {
	    common2 = getCommonArray$1(extract2, extract1, width);
	    info2 = normalize$2(common2);
	  } else {
	    common2 = extract2;
	    info2 = normalize$2(common2);
	  }

	  return {
	    info1: info1,
	    info2: info2,
	    data1: common1,
	    data2: common2
	  };
	}

	function extract$1(array, from, to) {
	  var newArray = [[], []];
	  var j = 0;
	  var length = array[0] ? array[0].length : 0;

	  for (var i = 0; i < length; i++) {
	    if ((!from || array[0][i] >= from) && (!to || array[0][i] <= to)) {
	      newArray[0][j] = array[0][i];
	      newArray[1][j] = array[1][i];
	      j++;
	    }
	  }

	  return newArray;
	}

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

	  var newArray = extract$1(array, from, to);
	  var info = normalize$2(newArray);
	  return {
	    info: info,
	    data: newArray
	  };
	}

	function calculateOverlapFromDiff$1(diffs) {
	  if (diffs[1].length === 0) return 0;
	  var sumPos = 0;

	  for (var i = 0; i < diffs[1].length; i++) {
	    sumPos += Math.abs(diffs[1][i]);
	  }

	  return 1 - sumPos;
	}

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

	const NEUTRON_MASS$1 = 1;

	function getPeakChargeBySimilarity$1(spectrum, targetMass) {
	  let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
	  let {
	    similarity = {},
	    minCharge = 1,
	    maxCharge = 10
	  } = options;
	  let {
	    zone = {},
	    widthFunction
	  } = similarity;
	  let {
	    low = -0.5,
	    high = 2.5
	  } = zone;

	  if (!spectrum || !spectrum.data.x.length > 0) {
	    throw Error('You need to add an experimental spectrum first using setMassSpectrum');
	  }

	  let width = {
	    bottom: similarity.widthBottom,
	    top: similarity.widthTop
	  };
	  similarity = JSON.parse(JSON.stringify(similarity));
	  similarity.common = 'second';
	  let experimentalData = spectrum.data;
	  let similarityProcessor = new Similarity$1(similarity);
	  similarityProcessor.setPeaks1([experimentalData.x, experimentalData.y]);

	  if (widthFunction && typeof widthFunction === 'string') {
	    // eslint-disable-next-line no-new-func
	    widthFunction = new Function('mass', widthFunction);
	    let checkTopBottom = widthFunction(123);

	    if (!checkTopBottom.bottom || !checkTopBottom.top) {
	      throw Error('widthFunction should return an object with bottom and top properties');
	    }
	  }

	  let fromCharge = minCharge * maxCharge > 0 ? Math.round(Math.min(Math.abs(minCharge), Math.abs(maxCharge))) : 1;
	  let toCharge = Math.round(Math.max(Math.abs(minCharge), Math.abs(maxCharge)));
	  let fromIsotope = Math.ceil(low);
	  let toIsotope = Math.floor(high);
	  let isotopeHeight = 1 / (toIsotope - fromIsotope + 1);
	  let results = [];

	  for (let charge = fromCharge; charge < toCharge + 1; charge++) {
	    let isotopePositions = {
	      x: [],
	      y: []
	    };

	    for (let isotopePosition = fromIsotope; isotopePosition < toIsotope + 1; isotopePosition++) {
	      isotopePositions.x.push(targetMass + isotopePosition * NEUTRON_MASS$1 / charge);
	      isotopePositions.y.push(isotopeHeight);
	    }

	    let from = targetMass + low / Math.abs(charge);
	    let to = targetMass + high / Math.abs(charge);
	    similarityProcessor.setFromTo(from, to);

	    if (widthFunction) {
	      width = widthFunction(targetMass);
	      similarityProcessor.setTrapezoid(width.bottom, width.top);
	    }

	    similarityProcessor.setPeaks2([isotopePositions.x, isotopePositions.y]);
	    let result = similarityProcessor.getSimilarity();
	    results.push({
	      charge,
	      similarity: result.similarity
	    });
	  }

	  return results.sort((a, b) => b.similarity - a.similarity)[0].charge;
	}

	var getPeakChargeBySimilarity_1 = getPeakChargeBySimilarity$1;

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

	function isContinuous$1(spectrum) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const {
	    minLength = 100,
	    maxDeltaRatio = 3,
	    relativeHeightThreshold = 0.001
	  } = options;
	  const minHeight = max$5(spectrum.data.y) * relativeHeightThreshold;
	  const minRadio = 1 / maxDeltaRatio;
	  const maxRatio = 1 * maxDeltaRatio;

	  if (spectrum.continuous === undefined) {
	    let xs = spectrum.data.x;
	    let ys = spectrum.data.y;

	    if (xs.length < minLength) {
	      spectrum.continuous = false;
	    } else {
	      let previousDelta = xs[1] - xs[0];
	      spectrum.continuous = true;
	      let success = 0;
	      let failed = 0;

	      for (let i = 0; i < xs.length - 1; i++) {
	        if (ys[i] < minHeight || ys[i + 1] < minHeight) {
	          previousDelta = 0;
	          continue;
	        }

	        let delta = xs[i + 1] - xs[i];

	        if (previousDelta) {
	          let ratio = delta / previousDelta;

	          if ((Math.abs(delta) > 0.1 || ratio < minRadio || ratio > maxRatio) && ys[i] !== 0 && ys[i + 1] !== 0) {
	            failed++;
	            break;
	          } else {
	            success++;
	          }
	        }

	        previousDelta = delta;
	      }

	      if (success / failed < 10) {
	        spectrum.continuous = false;
	      }
	    }
	  }

	  return spectrum.continuous;
	}

	var isContinuous_1 = isContinuous$1;

	/**
	 * Apply Savitzky Golay algorithm
	 * @param [ys] Array of y values
	 * @param [xs] Array of X or deltaX
	 * @return  Array containing the new ys (same length)
	 */

	function sgg(ys, xs) {
	  let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
	  let {
	    windowSize = 9,
	    derivative = 0,
	    polynomial = 3
	  } = options;

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

	  if (!isAnyArray$9(ys)) {
	    throw new TypeError('Y values must be an array');
	  }

	  if (typeof xs === 'undefined') {
	    throw new TypeError('X must be defined');
	  }

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

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

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

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

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

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


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

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

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


	  let wg = weights[half];

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

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

	    if (!constantH) {
	      hs = getHs(xs, i - half - 1, half, derivative);
	    }

	    ans[i - half - 1] = d / hs;
	  }

	  return ans;
	}

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

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

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

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

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

	  return Grampoly;
	}

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

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

	  return gf;
	}

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

	  for (let k = 0; k <= n; k++) {
	    sum += (2 * k + 1) * (genFact(2 * m, k) / genFact(2 * m + k + 1, k + 1)) * gramPoly(i, m, k, 0) * gramPoly(t, m, k, s);
	  }

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


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

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

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

	  return weights;
	}

	/**
	 * Correction of the x and y coordinates using a quadratic optimizations with the peak and its 3 closest neighbors to determine the true x,y values of the peak.
	 * This process is done in place and is very fast.
	 * @param data
	 * @param peaks
	 */
	function optimizeTop(data, peaks) {
	  const {
	    x,
	    y
	  } = data;

	  for (const peak of peaks) {
	    let currentIndex = peak.index; // The detected peak could be moved 1 or 2 units to left or right.

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


	    if (y[currentIndex - 1] > 0 && y[currentIndex + 1] > 0 && y[currentIndex] >= y[currentIndex - 1] && y[currentIndex] >= y[currentIndex + 1] && (y[currentIndex] !== y[currentIndex - 1] || y[currentIndex] !== y[currentIndex + 1])) {
	      let alpha = 20 * Math.log10(y[currentIndex - 1]);
	      let beta = 20 * Math.log10(y[currentIndex]);
	      let gamma = 20 * Math.log10(y[currentIndex + 1]);
	      let p = 0.5 * (alpha - gamma) / (alpha - 2 * beta + gamma);
	      peak.x = x[currentIndex] + (x[currentIndex] - x[currentIndex - 1]) * p;
	      peak.y = y[currentIndex] - 0.25 * (y[currentIndex - 1] - y[currentIndex + 1]) * p;
	    }
	  }
	}

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

	 */

	function gsd$1(data) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  let {
	    sgOptions = {
	      windowSize: 9,
	      polynomial: 3
	    },
	    noiseLevel,
	    smoothY = false,
	    maxCriteria = true,
	    minMaxRatio = 0.00025,
	    realTopDetection = false
	  } = options;
	  let {
	    x,
	    y
	  } = data;

	  if (!xIsMonotoneIncreasing$1(x)) {
	    throw new Error('GSD only accepts monotone increasing x values');
	  } //rescale;


	  y = y.slice(); // If the max difference between delta x is less than 5%, then,
	  // we can assume it to be equally spaced variable

	  let equallySpaced = xIsEquallySpaced$1(x);

	  if (noiseLevel === undefined) {
	    if (equallySpaced) {
	      const noiseInfo = xNoiseStandardDeviation$1(y);

	      if (maxCriteria) {
	        noiseLevel = noiseInfo.median + 1.5 * noiseInfo.sd;
	      } else {
	        noiseLevel = -noiseInfo.median + 1.5 * noiseInfo.sd;
	      }
	    } else {
	      noiseLevel = 0;
	    }
	  } else {
	    if (maxCriteria === false) {
	      noiseLevel *= -1;
	    }
	  }

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

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

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

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

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

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

	  const minY = xMinValue$1(yData);
	  const maxY = xMaxValue$2(yData);
	  if (minY > maxY || minY === maxY) return [];
	  const yThreshold = minY + (maxY - minY) * minMaxRatio;
	  const dX = x[1] - x[0];
	  let lastMax = null;
	  let lastMin = null;
	  let minddY = [];
	  let intervalL = [];
	  let intervalR = []; // By the intermediate value theorem We cannot find 2 consecutive maximum or minimum

	  for (let i = 1; i < yData.length - 1; ++i) {
	    if (dY[i] < dY[i - 1] && dY[i] <= dY[i + 1] || dY[i] <= dY[i - 1] && dY[i] < dY[i + 1]) {
	      lastMin = {
	        x: x[i],
	        index: i
	      };

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


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

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


	    if (ddY[i] < ddY[i - 1] && ddY[i] < ddY[i + 1]) {
	      minddY.push(i);
	    }
	  }

	  let lastK = -1;
	  const peaks = [];

	  for (const minddYIndex of minddY) {
	    let deltaX = x[minddYIndex];
	    let possible = -1;
	    let k = lastK + 1;
	    let minDistance = Number.POSITIVE_INFINITY;
	    let currentDistance = 0;

	    while (possible === -1 && k < intervalL.length) {
	      currentDistance = Math.abs(deltaX - (intervalL[k].x + intervalR[k].x) / 2);

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

	      ++k; // Not getting closer?

	      if (currentDistance >= minDistance) {
	        break;
	      }

	      minDistance = currentDistance;
	    }

	    if (possible !== -1) {
	      if (yData[minddYIndex] > yThreshold) {
	        let width = Math.abs(intervalR[possible].x - intervalL[possible].x);
	        peaks.push({
	          x: deltaX,
	          y: yData[minddYIndex],
	          width: width,
	          index: minddYIndex,
	          ddY: ddY[minddYIndex],
	          inflectionPoints: {
	            from: intervalL[possible],
	            to: intervalR[possible]
	          }
	        });
	      }
	    }
	  }

	  if (realTopDetection) {
	    optimizeTop({
	      x,
	      y: yData
	    }, peaks);
	  }

	  peaks.forEach(peak => {
	    if (!maxCriteria) {
	      peak.y *= -1;
	      peak.ddY = peak.ddY * -1;
	    }
	  });
	  peaks.sort((a, b) => {
	    return a.x - b.x;
	  });
	  return peaks;
	}

	const GAUSSIAN_EXP_FACTOR$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 Gaussian$1 {
	  constructor() {
	    let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
	    const {
	      fwhm = 500,
	      sd
	    } = options;
	    this.fwhm = sd ? gaussianWidthToFWHM$1(2 * sd) : fwhm;
	  }

	  fwhmToWidth() {
	    let fwhm = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.fwhm;
	    return gaussianFwhmToWidth$1(fwhm);
	  }

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

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

	  getArea() {
	    let height = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : calculateGaussianHeight$1({
	      fwhm: this.fwhm
	    });
	    return getGaussianArea$1({
	      fwhm: this.fwhm,
	      height
	    });
	  }

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

	  getData() {
	    let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
	    return getGaussianData$1(this, options);
	  }

	  calculateHeight() {
	    let area = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1;
	    return calculateGaussianHeight$1({
	      fwhm: this.fwhm,
	      area
	    });
	  }

	  getParameters() {
	    return ['fwhm'];
	  }

	}
	function calculateGaussianHeight$1(options) {
	  let {
	    fwhm = 500,
	    area = 1,
	    sd
	  } = options;
	  if (sd) fwhm = gaussianWidthToFWHM$1(2 * sd);
	  return 2 * area / ROOT_PI_OVER_LN2$1 / fwhm;
	}
	function gaussianFct$1(x, fwhm) {
	  return Math.exp(GAUSSIAN_EXP_FACTOR$1 * Math.pow(x / fwhm, 2));
	}
	function gaussianWidthToFWHM$1(width) {
	  return width * ROOT_2LN2$1;
	}
	function gaussianFwhmToWidth$1(fwhm) {
	  return fwhm / ROOT_2LN2$1;
	}
	function getGaussianArea$1(options) {
	  let {
	    fwhm = 500,
	    sd,
	    height = 1
	  } = options;
	  if (sd) fwhm = gaussianWidthToFWHM$1(2 * sd);
	  return height * ROOT_PI_OVER_LN2$1 * fwhm / 2;
	}
	function getGaussianFactor$1() {
	  let area = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0.9999;
	  return Math.sqrt(2) * erfinv$1(area);
	}
	function getGaussianData$1() {
	  let shape = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  let {
	    fwhm = 500,
	    sd
	  } = shape;
	  if (sd) fwhm = gaussianWidthToFWHM$1(2 * sd);
	  let {
	    length,
	    factor = getGaussianFactor$1(),
	    height = calculateGaussianHeight$1({
	      fwhm
	    })
	  } = options;

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

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

	  for (let i = 0; i <= center; i++) {
	    data[i] = gaussianFct$1(i - center, fwhm) * height;
	    data[length - 1 - i] = data[i];
	  }

	  return data;
	}

	class Lorentzian$1 {
	  constructor() {
	    let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
	    const {
	      fwhm = 500
	    } = options;
	    this.fwhm = fwhm;
	  }

	  fwhmToWidth() {
	    let fwhm = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.fwhm;
	    return lorentzianFwhmToWidth$1(fwhm);
	  }

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

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

	  getArea() {
	    let height = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1;
	    return getLorentzianArea$1({
	      fwhm: this.fwhm,
	      height
	    });
	  }

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

	  getData() {
	    let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
	    return getLorentzianData$1(this, options);
	  }

	  calculateHeight() {
	    let area = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1;
	    return calculateLorentzianHeight$1({
	      fwhm: this.fwhm,
	      area
	    });
	  }

	  getParameters() {
	    return ['fwhm'];
	  }

	}
	const calculateLorentzianHeight$1 = _ref => {
	  let {
	    fwhm = 1,
	    area = 1
	  } = _ref;
	  return 2 * area / Math.PI / fwhm;
	};
	const lorentzianFct$1 = (x, fwhm) => {
	  return Math.pow(fwhm, 2) / (4 * Math.pow(x, 2) + Math.pow(fwhm, 2));
	};
	const lorentzianWidthToFWHM$1 = width => {
	  return width * ROOT_THREE$1;
	};
	const lorentzianFwhmToWidth$1 = fwhm => {
	  return fwhm / ROOT_THREE$1;
	};
	const getLorentzianArea$1 = options => {
	  const {
	    fwhm = 500,
	    height = 1
	  } = options;
	  return height * Math.PI * fwhm / 2;
	};
	const getLorentzianFactor$1 = function () {
	  let area = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0.9999;
	  return 2 * Math.tan(Math.PI * (area - 0.5));
	};
	const getLorentzianData$1 = function () {
	  let shape = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  let {
	    fwhm = 500
	  } = shape;
	  let {
	    length,
	    factor = getLorentzianFactor$1(),
	    height = calculateLorentzianHeight$1({
	      fwhm,
	      area: 1
	    })
	  } = options;

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

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

	  for (let i = 0; i <= center; i++) {
	    data[i] = lorentzianFct$1(i - center, fwhm) * height;
	    data[length - 1 - i] = data[i];
	  }

	  return data;
	};

	class PseudoVoigt$1 {
	  constructor() {
	    let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
	    const {
	      fwhm = 500,
	      mu = 0.5
	    } = options;
	    this.mu = mu;
	    this.fwhm = fwhm;
	  }

	  fwhmToWidth() {
	    let fwhm = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.fwhm;
	    let mu = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.mu;
	    return pseudoVoigtFwhmToWidth$1(fwhm, mu);
	  }

	  widthToFWHM(width) {
	    let mu = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.mu;
	    return pseudoVoigtWidthToFWHM$1(width, mu);
	  }

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

	  getArea() {
	    let height = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1;
	    return getPseudoVoigtArea$1({
	      fwhm: this.fwhm,
	      height,
	      mu: this.mu
	    });
	  }

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

	  getData() {
	    let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
	    const {
	      length,
	      factor,
	      height = calculatePseudoVoigtHeight$1({
	        fwhm: this.fwhm,
	        mu: this.mu,
	        area: 1
	      })
	    } = options;
	    return getPseudoVoigtData$1(this, {
	      factor,
	      length,
	      height
	    });
	  }

	  calculateHeight() {
	    let area = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1;
	    return calculatePseudoVoigtHeight$1({
	      fwhm: this.fwhm,
	      mu: this.mu,
	      area
	    });
	  }

	  getParameters() {
	    return ['fwhm', 'mu'];
	  }

	}
	const calculatePseudoVoigtHeight$1 = function () {
	  let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
	  let {
	    fwhm = 1,
	    mu = 0.5,
	    area = 1
	  } = options;
	  return 2 * area / (fwhm * (mu * ROOT_PI_OVER_LN2$1 + (1 - mu) * Math.PI));
	};
	const pseudoVoigtFct$1 = (x, fwhm, mu) => {
	  return (1 - mu) * lorentzianFct$1(x, fwhm) + mu * gaussianFct$1(x, fwhm);
	};
	const pseudoVoigtWidthToFWHM$1 = function (width) {
	  let mu = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0.5;
	  return width * (mu * ROOT_2LN2_MINUS_ONE$1 + 1);
	};
	const pseudoVoigtFwhmToWidth$1 = function (fwhm) {
	  let mu = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0.5;
	  return fwhm / (mu * ROOT_2LN2_MINUS_ONE$1 + 1);
	};
	const getPseudoVoigtArea$1 = options => {
	  const {
	    fwhm = 500,
	    height = 1,
	    mu = 0.5
	  } = options;
	  return fwhm * height * (mu * ROOT_PI_OVER_LN2$1 + (1 - mu) * Math.PI) / 2;
	};
	const getPseudoVoigtFactor$1 = function () {
	  let area = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0.9999;
	  let mu = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0.5;
	  return mu < 1 ? getLorentzianFactor$1(area) : getGaussianFactor$1(area);
	};
	const getPseudoVoigtData$1 = function () {
	  let shape = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  let {
	    fwhm = 500,
	    mu = 0.5
	  } = shape;
	  let {
	    length,
	    factor = getPseudoVoigtFactor$1(0.999, mu),
	    height = calculatePseudoVoigtHeight$1({
	      fwhm,
	      mu,
	      area: 1
	    })
	  } = 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] = pseudoVoigtFct$1(i - center, fwhm, mu) * height;
	    data[length - 1 - i] = data[i];
	  }

	  return data;
	};

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

	function getShape1D$1(shape) {
	  const {
	    kind
	  } = shape;

	  switch (kind) {
	    case 'gaussian':
	      return new Gaussian$1(shape);

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

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

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

	/**
	 * This function calculates the spectrum as a sum of linear combination of gaussian and lorentzian functions. The pseudo voigt
	 * parameters are divided in 4 batches. 1st: centers; 2nd: heights; 3th: widths; 4th: mu's ;
	 * @param t Ordinate value
	 * @param p Lorentzian parameters
	 * @returns {*}
	 */
	// const pseudoVoigtFct = PseudoVoigt.fct;

	function sumOfGaussianLorentzians(p) {
	  const pseudoVoigt = new PseudoVoigt$1();
	  return t => {
	    let nL = p.length / 4;
	    let result = 0;

	    for (let i = 0; i < nL; i++) {
	      pseudoVoigt.fwhm = p[i + nL * 2];
	      pseudoVoigt.mu = p[i + nL * 3];
	      result += p[i + nL] * pseudoVoigt.fct(t - p[i]);
	    }

	    return result;
	  };
	}

	/**
	 * This function calculates the spectrum as a sum of gaussian functions. The Gaussian
	 * parameters are divided in 3 batches. 1st: centers; 2nd: height; 3th: widths;
	 * @param t Ordinate values
	 * @param p Gaussian parameters
	 * @returns {*}
	 */

	function sumOfGaussians(p) {
	  const nL = p.length / 3;
	  const gaussian = new Gaussian$1();
	  return t => {
	    let result = 0;

	    for (let i = 0; i < nL; i++) {
	      gaussian.fwhm = p[i + nL * 2];
	      result += p[i + nL] * gaussian.fct(t - p[i]);
	    }

	    return result;
	  };
	}

	/**
	 * This function calculates the spectrum as a sum of lorentzian functions. The Lorentzian
	 * parameters are divided in 3 batches. 1st: centers; 2nd: heights; 3th: widths;
	 * @param t Ordinate values
	 * @param p Lorentzian parameters
	 * @returns {*}
	 */
	// const lorentzianFct = Lorentzian.fct;

	function sumOfLorentzians(p) {
	  const lorentzian = new Lorentzian$1();
	  return t => {
	    let nL = p.length / 3;
	    let result = 0;

	    for (let i = 0; i < nL; i++) {
	      lorentzian.fwhm = p[i + nL * 2];
	      result += p[i + nL] * lorentzian.fct(t - p[i]);
	    }

	    return result;
	  };
	}

	const isValidKey = key => {
	  return key !== '__proto__' && key !== 'constructor' && key !== 'prototype';
	};

	const isObject = val => {
	  return typeof val === 'object';
	};

	const isPrimitive = val => {
	  return typeof val === 'object' ? val === null : typeof val !== 'function';
	};

	function assignDeep(target) {
	  let index = 0;

	  for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
	    args[_key - 1] = arguments[_key];
	  }

	  if (isPrimitive(target)) target = args[index++];
	  if (!target) target = {};

	  for (; index < args.length; index++) {
	    if (!isObject(args[index])) continue;

	    for (const key in args[index]) {
	      if (!isValidKey(key)) continue;

	      if (isObject(target[key]) && isObject(args[index][key])) {
	        assignDeep(target[key], args[index][key]);
	      } else {
	        target[key] = args[index][key];
	      }
	    }
	  }

	  return target;
	}

	function checkInput(data, peakList, options) {
	  let {
	    shape = {
	      kind: 'gaussian'
	    },
	    optimization = {
	      kind: 'lm'
	    }
	  } = options;
	  let peaks = JSON.parse(JSON.stringify(peakList));

	  if (typeof shape.kind !== 'string') {
	    throw new Error('kind should be a string');
	  }

	  let kind = shape.kind.toLowerCase().replace(/[^a-z]/g, '');
	  let paramsFunc;
	  let defaultParameters;

	  switch (kind) {
	    case 'gaussian':
	      paramsFunc = sumOfGaussians;
	      defaultParameters = {
	        x: {
	          init: peak => peak.x,
	          max: peak => peak.x + peak.fwhm * 2,
	          min: peak => peak.x - peak.fwhm * 2,
	          gradientDifference: peak => peak.fwhm * 2e-3
	        },
	        y: {
	          init: peak => peak.y,
	          max: () => 1.5,
	          min: () => 0,
	          gradientDifference: () => 1e-3
	        },
	        fwhm: {
	          init: peak => peak.fwhm,
	          max: peak => peak.fwhm * 4,
	          min: peak => peak.fwhm * 0.25,
	          gradientDifference: peak => peak.fwhm * 2e-3
	        }
	      };
	      break;

	    case 'lorentzian':
	      paramsFunc = sumOfLorentzians;
	      defaultParameters = {
	        x: {
	          init: peak => peak.x,
	          max: peak => peak.x + peak.fwhm * 2,
	          min: peak => peak.x - peak.fwhm * 2,
	          gradientDifference: peak => peak.fwhm * 2e-3
	        },
	        y: {
	          init: peak => peak.y,
	          max: () => 1.5,
	          min: () => 0,
	          gradientDifference: () => 1e-3
	        },
	        fwhm: {
	          init: peak => peak.fwhm,
	          max: peak => peak.fwhm * 4,
	          min: peak => peak.fwhm * 0.25,
	          gradientDifference: peak => peak.fwhm * 2e-3
	        }
	      };
	      break;

	    case 'pseudovoigt':
	      paramsFunc = sumOfGaussianLorentzians;
	      defaultParameters = {
	        x: {
	          init: peak => peak.x,
	          max: peak => peak.x + peak.fwhm * 2,
	          min: peak => peak.x - peak.fwhm * 2,
	          gradientDifference: peak => peak.fwhm * 2e-3
	        },
	        y: {
	          init: peak => peak.y,
	          max: () => 1.5,
	          min: () => 0,
	          gradientDifference: () => 1e-3
	        },
	        fwhm: {
	          init: peak => peak.fwhm,
	          max: peak => peak.fwhm * 4,
	          min: peak => peak.fwhm * 0.25,
	          gradientDifference: peak => peak.fwhm * 2e-3
	        },
	        mu: {
	          init: peak => peak.shape && peak.shape.mu !== undefined ? peak.shape.mu : 0.5,
	          min: () => 0,
	          max: () => 1,
	          gradientDifference: () => 0.01
	        }
	      };
	      break;

	    default:
	      throw new Error('kind of shape is not supported');
	  }

	  let x = data.x;
	  let maxY = max$7(data.y);
	  let y = new Array(x.length);

	  for (let i = 0; i < x.length; i++) {
	    y[i] = data.y[i] / maxY;
	  }

	  for (let i = 0; i < peaks.length; i++) {
	    peaks[i].y /= maxY;
	    peaks[i].shape = {
	      kind: shape.kind,
	      ...peaks[i].shape
	    };
	  }

	  let parameters = assignDeep({}, defaultParameters, optimization.parameters);

	  for (let key in parameters) {
	    for (let par in parameters[key]) {
	      if (!Array.isArray(parameters[key][par])) {
	        parameters[key][par] = [parameters[key][par]];
	      }

	      if (parameters[key][par].length !== 1 && parameters[key][par].length !== peaks.length) {
	        throw new Error(`The length of ${key}-${par} is not correct`);
	      }

	      for (let index = 0; index < parameters[key][par].length; index++) {
	        if (typeof parameters[key][par][index] === 'number') {
	          let value = parameters[key][par][index];

	          parameters[key][par][index] = () => value;
	        }
	      }
	    }
	  }

	  optimization.parameters = parameters;
	  return {
	    y,
	    x,
	    maxY,
	    peaks,
	    paramsFunc,
	    optimization
	  };
	}

	function checkOptions$1(data, parameterizedFunction, options) {
	  let {
	    timeout,
	    minValues,
	    maxValues,
	    initialValues,
	    weights = 1,
	    damping = 1e-2,
	    dampingStepUp = 11,
	    dampingStepDown = 9,
	    maxIterations = 100,
	    errorTolerance = 1e-7,
	    centralDifference = false,
	    gradientDifference = 10e-2,
	    improvementThreshold = 1e-3
	  } = options;

	  if (damping <= 0) {
	    throw new Error('The damping option must be a positive number');
	  } else if (!data.x || !data.y) {
	    throw new Error('The data parameter must have x and y elements');
	  } else if (!isAnyArray$9(data.x) || data.x.length < 2 || !isAnyArray$9(data.y) || data.y.length < 2) {
	    throw new Error('The data parameter elements must be an array with more than 2 points');
	  } else if (data.x.length !== data.y.length) {
	    throw new Error('The data parameter elements must have the same size');
	  }

	  let parameters = initialValues || new Array(parameterizedFunction.length).fill(1);
	  let nbPoints = data.y.length;
	  let parLen = parameters.length;
	  maxValues = maxValues || new Array(parLen).fill(Number.MAX_SAFE_INTEGER);
	  minValues = minValues || new Array(parLen).fill(Number.MIN_SAFE_INTEGER);

	  if (maxValues.length !== minValues.length) {
	    throw new Error('minValues and maxValues must be the same size');
	  }

	  if (!isAnyArray$9(parameters)) {
	    throw new Error('initialValues must be an array');
	  }

	  if (typeof gradientDifference === 'number') {
	    gradientDifference = new Array(parameters.length).fill(gradientDifference);
	  } else if (isAnyArray$9(gradientDifference)) {
	    if (gradientDifference.length !== parLen) {
	      gradientDifference = new Array(parLen).fill(gradientDifference[0]);
	    }
	  } else {
	    throw new Error('gradientDifference should be a number or array with length equal to the number of parameters');
	  }

	  let filler;

	  if (typeof weights === 'number') {
	    let value = 1 / weights ** 2;

	    filler = () => value;
	  } else if (isAnyArray$9(weights)) {
	    if (weights.length < data.x.length) {
	      let value = 1 / weights[0] ** 2;

	      filler = () => value;
	    } else {
	      filler = i => 1 / weights[i] ** 2;
	    }
	  } else {
	    throw new Error('weights should be a number or array with length equal to the number of data points');
	  }

	  let checkTimeout;

	  if (timeout !== undefined) {
	    if (typeof timeout !== 'number') {
	      throw new Error('timeout should be a number');
	    }

	    let endTime = Date.now() + timeout * 1000;

	    checkTimeout = () => Date.now() > endTime;
	  } else {
	    checkTimeout = () => false;
	  }

	  let weightSquare = new Array(data.x.length);

	  for (let i = 0; i < nbPoints; i++) {
	    weightSquare[i] = filler(i);
	  }

	  return {
	    checkTimeout,
	    minValues,
	    maxValues,
	    parameters,
	    weightSquare,
	    damping,
	    dampingStepUp,
	    dampingStepDown,
	    maxIterations,
	    errorTolerance,
	    centralDifference,
	    gradientDifference,
	    improvementThreshold
	  };
	}

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

	  for (let i = 0; i < data.x.length; i++) {
	    error += Math.pow(data.y[i] - func(data.x[i]), 2) / weightSquare[i];
	  }

	  return error;
	}

	/**
	 * Difference of the matrix function over the parameters
	 * @ignore
	 * @param {{x:ArrayLike<number>, y:ArrayLike<number>}} data - Array of points to fit in the format [x1, x2, ... ], [y1, y2, ... ]
	 * @param {ArrayLike<number>} evaluatedData - Array of previous evaluated function values
	 * @param {Array<number>} params - Array of previous parameter values
	 * @param {number|array} gradientDifference - The step size to approximate the jacobian matrix
	 * @param {boolean} centralDifference - If true the jacobian matrix is approximated by central differences otherwise by forward differences
	 * @param {function} paramFunction - The parameters and returns a function with the independent variable as a parameter
	 * @return {Matrix}
	 */

	function gradientFunction(data, evaluatedData, params, gradientDifference, paramFunction, centralDifference) {
	  const nbParams = params.length;
	  const nbPoints = data.x.length;
	  let ans = Matrix$2.zeros(nbParams, nbPoints);
	  let rowIndex = 0;

	  for (let param = 0; param < nbParams; param++) {
	    if (gradientDifference[param] === 0) continue;
	    let delta = gradientDifference[param];
	    let auxParams = params.slice();
	    auxParams[param] += delta;
	    let funcParam = paramFunction(auxParams);

	    if (!centralDifference) {
	      for (let point = 0; point < nbPoints; point++) {
	        ans.set(rowIndex, point, (evaluatedData[point] - funcParam(data.x[point])) / delta);
	      }
	    } else {
	      auxParams = params.slice();
	      auxParams[param] -= delta;
	      delta *= 2;
	      let funcParam2 = paramFunction(auxParams);

	      for (let point = 0; point < nbPoints; point++) {
	        ans.set(rowIndex, point, (funcParam2(data.x[point]) - funcParam(data.x[point])) / delta);
	      }
	    }

	    rowIndex++;
	  }

	  return ans;
	}

	/**
	 * Matrix function over the samples
	 * @ignore
	 * @param {{x:ArrayLike<number>, y:ArrayLike<number>}} data - Array of points to fit in the format [x1, x2, ... ], [y1, y2, ... ]
	 * @param {ArrayLike<number>} evaluatedData - Array of previous evaluated function values
	 * @return {Matrix}
	 */

	function matrixFunction(data, evaluatedData) {
	  const m = data.x.length;
	  let ans = new Matrix$2(m, 1);

	  for (let point = 0; point < m; point++) {
	    ans.set(point, 0, data.y[point] - evaluatedData[point]);
	  }

	  return ans;
	}
	/**
	 * Iteration for Levenberg-Marquardt
	 * @ignore
	 * @param {{x:ArrayLike<number>, y:ArrayLike<number>}} data - Array of points to fit in the format [x1, x2, ... ], [y1, y2, ... ]
	 * @param {Array<number>} params - Array of previous parameter values
	 * @param {number} damping - Levenberg-Marquardt parameter
	 * @param {number|array} gradientDifference - The step size to approximate the jacobian matrix
	 * @param {boolean} centralDifference - If true the jacobian matrix is approximated by central differences otherwise by forward differences
	 * @param {function} parameterizedFunction - The parameters and returns a function with the independent variable as a parameter
	 */


	function step(data, params, damping, gradientDifference, parameterizedFunction, centralDifference, weights) {
	  let value = damping;
	  let identity = Matrix$2.eye(params.length, params.length, value);
	  const func = parameterizedFunction(params);
	  let evaluatedData = new Float64Array(data.x.length);

	  for (let i = 0; i < data.x.length; i++) {
	    evaluatedData[i] = func(data.x[i]);
	  }

	  let gradientFunc = gradientFunction(data, evaluatedData, params, gradientDifference, parameterizedFunction, centralDifference);
	  let residualError = matrixFunction(data, evaluatedData);
	  let inverseMatrix = inverse(identity.add(gradientFunc.mmul(gradientFunc.transpose().scale('row', {
	    scale: weights
	  }))));
	  let jacobianWeightResidualError = gradientFunc.mmul(residualError.scale('row', {
	    scale: weights
	  }));
	  let perturbations = inverseMatrix.mmul(jacobianWeightResidualError);
	  return {
	    perturbations,
	    jacobianWeightResidualError
	  };
	}

	/**
	 * Curve fitting algorithm
	 * @param {{x:ArrayLike<number>, y:ArrayLike<number>}} data - Array of points to fit in the format [x1, x2, ... ], [y1, y2, ... ]
	 * @param {function} parameterizedFunction - The parameters and returns a function with the independent variable as a parameter
	 * @param {object} [options] - Options object
	 * @param {number|ArrayLike<number>} [options.weights = 1] - weighting vector, if the length does not match with the number of data points, the vector is reconstructed with first value.
	 * @param {number} [options.damping = 1e-2] - Levenberg-Marquardt parameter, small values of the damping parameter λ result in a Gauss-Newton update and large
	values of λ result in a gradient descent update
	 * @param {number} [options.dampingStepDown = 9] - factor to reduce the damping (Levenberg-Marquardt parameter) when there is not an improvement when updating parameters.
	 * @param {number} [options.dampingStepUp = 11] - factor to increase the damping (Levenberg-Marquardt parameter) when there is an improvement when updating parameters.
	 * @param {number} [options.improvementThreshold = 1e-3] - the threshold to define an improvement through an update of parameters
	 * @param {number|ArrayLike<number>} [options.gradientDifference = 10e-2] - The step size to approximate the jacobian matrix
	 * @param {boolean} [options.centralDifference = false] - If true the jacobian matrix is approximated by central differences otherwise by forward differences
	 * @param {ArrayLike<number>} [options.minValues] - Minimum allowed values for parameters
	 * @param {ArrayLike<number>} [options.maxValues] - Maximum allowed values for parameters
	 * @param {ArrayLike<number>} [options.initialValues] - Array of initial parameter values
	 * @param {number} [options.maxIterations = 100] - Maximum of allowed iterations
	 * @param {number} [options.errorTolerance = 10e-3] - Minimum uncertainty allowed for each point.
	 * @param {number} [options.timeout] - maximum time running before throw in seconds.
	 * @return {{parameterValues: Array<number>, parameterError: number, iterations: number}}
	 */

	function levenbergMarquardt(data, parameterizedFunction) {
	  let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
	  let {
	    checkTimeout,
	    minValues,
	    maxValues,
	    parameters,
	    weightSquare,
	    damping,
	    dampingStepUp,
	    dampingStepDown,
	    maxIterations,
	    errorTolerance,
	    centralDifference,
	    gradientDifference,
	    improvementThreshold
	  } = checkOptions$1(data, parameterizedFunction, options);
	  let error = errorCalculation(data, parameters, parameterizedFunction, weightSquare);
	  let optimalError = error;
	  let optimalParameters = parameters.slice();
	  let converged = error <= errorTolerance;
	  let iteration = 0;

	  for (; iteration < maxIterations && !converged; iteration++) {
	    let previousError = error;
	    let {
	      perturbations,
	      jacobianWeightResidualError
	    } = step(data, parameters, damping, gradientDifference, parameterizedFunction, centralDifference, weightSquare);

	    for (let k = 0; k < parameters.length; k++) {
	      parameters[k] = Math.min(Math.max(minValues[k], parameters[k] - perturbations.get(k, 0)), maxValues[k]);
	    }

	    error = errorCalculation(data, parameters, parameterizedFunction, weightSquare);
	    if (isNaN(error)) break;

	    if (error < optimalError - errorTolerance) {
	      optimalError = error;
	      optimalParameters = parameters.slice();
	    }

	    let improvementMetric = (previousError - error) / perturbations.transpose().mmul(perturbations.mul(damping).add(jacobianWeightResidualError)).get(0, 0);

	    if (improvementMetric > improvementThreshold) {
	      damping = Math.max(damping / dampingStepDown, 1e-7);
	    } else {
	      damping = Math.min(damping * dampingStepUp, 1e7);
	    }

	    if (checkTimeout()) {
	      throw new Error(`The execution time is over to ${options.timeout} seconds`);
	    }

	    converged = error <= errorTolerance;
	  }

	  return {
	    parameterValues: optimalParameters,
	    parameterError: optimalError,
	    iterations: iteration
	  };
	}

	const LEVENBERG_MARQUARDT = 1;
	function selectMethod() {
	  let optimizationOptions = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
	  let {
	    kind,
	    options
	  } = optimizationOptions;
	  kind = getKind(kind);

	  switch (kind) {
	    case LEVENBERG_MARQUARDT:
	      return {
	        algorithm: levenbergMarquardt,
	        optimizationOptions: checkOptions(kind, options)
	      };

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

	function checkOptions(kind) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};

	  // eslint-disable-next-line default-case
	  switch (kind) {
	    case LEVENBERG_MARQUARDT:
	      return Object.assign({}, lmOptions, options);
	  }
	}

	function getKind(kind) {
	  if (typeof kind !== 'string') return kind;

	  switch (kind.toLowerCase().replace(/[^a-z]/g, '')) {
	    case 'lm':
	    case 'levenbergmarquardt':
	      return LEVENBERG_MARQUARDT;

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

	const lmOptions = {
	  damping: 1.5,
	  maxIterations: 100,
	  errorTolerance: 1e-8
	};

	/**
	 * Fits a set of points to the sum of a set of bell functions.
	 * @param {object} data - An object containing the x and y data to be fitted.
	 * @param {array} peaks - A list of initial parameters to be optimized. e.g. coming from a peak picking [{x, y, width}].
	 * @param {object} [options = {}]
	 * @param {object} [options.shape={}] - it's specify the kind of shape used to fitting.
	 * @param {string} [options.shape.kind = 'gaussian'] - kind of shape; lorentzian, gaussian and pseudovoigt are supported.
	 * @param {object} [options.optimization = {}] - it's specify the kind and options of the algorithm use to optimize parameters.
	 * @param {object} [options.optimization.kind = 'lm'] - kind of algorithm. By default it's levenberg-marquardt.
	 * @param {object} [options.optimization.parameters] - options of each parameter to be optimized e.g. For a gaussian shape
	 *  it could have x, y and with properties, each of which could contain init, min, max and gradientDifference, those options will define the guess,
	 *  the min and max value of the parameter (search space) and the step size to approximate the jacobian matrix respectively. Those options could be a number,
	 *  array of numbers, callback, or array of callbacks. Each kind of shape has default parameters so it could be undefined.
	 * @param {object} [options.optimization.parameters.x] - options for x parameter.
	 * @param {number|callback|array<number|callback>} [options.optimization.parameters.x.init] - definition of the starting point of the parameter (the guess),
	 *  if it is a callback the method pass the peak as the unique input, if it is an array the first element define the guess of the first peak and so on.
	 * @param {number|callback|array<number|callback>} [options.optimization.parameters.x.min] - definition of the lower limit of the parameter,
	 *  if it is a callback the method pass the peak as the unique input, if it is an array the first element define the min of the first peak and so on.
	 * @param {number|callback|array<number|callback>} [options.optimization.parameters.x.max] - definition of the upper limit of the parameter,
	 *  if it is a callback the method pass the peak as the unique input, if it is an array the first element define the max of the first peak and so on.
	 * @param {number|callback|array<number|callback>} [options.optimization.parameters.x.gradientDifference] - definition of  the step size to approximate the jacobian matrix of the parameter,
	 *  if it is a callback the method pass the peak as the unique input, if it is an array the first element define the gradientDifference of the first peak and so on.
	 * @param {object} [options.optimization.options = {}] - options for the specific kind of algorithm.
	 * @param {number} [options.optimization.options.timeout] - maximum time running before break in seconds.
	 * @param {number} [options.optimization.options.damping=1.5]
	 * @param {number} [options.optimization.options.maxIterations=100]
	 * @param {number} [options.optimization.options.errorTolerance=1e-8]
	 * @returns {object} - A object with fitting error and the list of optimized parameters { parameters: [ {x, y, width} ], error } if the kind of shape is pseudoVoigt mu parameter is optimized.
	 */

	function optimize(data, peakList) {
	  let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
	  const {
	    y,
	    x,
	    maxY,
	    peaks,
	    paramsFunc,
	    optimization
	  } = checkInput(data, peakList, options);
	  let parameters = optimization.parameters;
	  let nbShapes = peaks.length;
	  let parameterKey = Object.keys(parameters);
	  let nbParams = nbShapes * parameterKey.length;
	  let pMin = new Float64Array(nbParams);
	  let pMax = new Float64Array(nbParams);
	  let pInit = new Float64Array(nbParams);
	  let gradientDifference = new Float64Array(nbParams);

	  for (let i = 0; i < nbShapes; i++) {
	    let peak = peaks[i];

	    for (let k = 0; k < parameterKey.length; k++) {
	      let key = parameterKey[k];
	      let init = parameters[key].init;
	      let min = parameters[key].min;
	      let max = parameters[key].max;
	      let gradientDifferenceValue = parameters[key].gradientDifference;
	      pInit[i + k * nbShapes] = init[i % init.length](peak);
	      pMin[i + k * nbShapes] = min[i % min.length](peak);
	      pMax[i + k * nbShapes] = max[i % max.length](peak);
	      gradientDifference[i + k * nbShapes] = gradientDifferenceValue[i % gradientDifferenceValue.length](peak);
	    }
	  }

	  let {
	    algorithm,
	    optimizationOptions
	  } = selectMethod(optimization);
	  optimizationOptions.minValues = pMin;
	  optimizationOptions.maxValues = pMax;
	  optimizationOptions.initialValues = pInit;
	  optimizationOptions.gradientDifference = gradientDifference;
	  let pFit = algorithm({
	    x,
	    y
	  }, paramsFunc, optimizationOptions);
	  let {
	    parameterError: error,
	    iterations
	  } = pFit;
	  let result = {
	    error,
	    iterations,
	    peaks
	  };

	  for (let i = 0; i < nbShapes; i++) {
	    for (let k = 0; k < parameterKey.length; k++) {
	      const key = parameterKey[k];
	      const value = pFit.parameterValues[i + k * nbShapes]; // we modify the optimized parameters

	      if (key === 'x' || key === 'fwhm') {
	        peaks[i][parameterKey[k]] = value;
	      } else if (key === 'y') {
	        peaks[i][parameterKey[k]] = value * maxY;
	      } else {
	        peaks[i].shape[parameterKey[k]] = value;
	      }
	    }
	  }

	  return result;
	}

	/**
	 * Append 2 properties to the peaks, shape and fwhm
	 */

	function appendShapeAndFWHM(peaks) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  let {
	    shape = {
	      kind: 'gaussian'
	    }
	  } = options;
	  let widthToFWHM = getShape1D$1(shape).widthToFWHM;
	  return peaks.map(peak => ({ ...peak,
	    fwhm: widthToFWHM(peak.width),
	    shape
	  }));
	}

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

	  for (let i = 1; i < peaks.length; i++) {
	    const peak = peaks[i];

	    if ((peak.x - previousPeak.x) / ((peak.width + previousPeak.width) / 2) <= factor) {
	      currentGroup.push(peak);
	    } else {
	      currentGroup = [peak];
	      groups.push(currentGroup);
	    }

	    previousPeak = peak;
	  }

	  return groups;
	}

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

	function optimizePeaks(data, peakList) {
	  let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
	  const {
	    shape = {
	      kind: 'gaussian'
	    },
	    groupingFactor = 1,
	    factorLimits = 2,
	    optimization = {
	      kind: 'lm',
	      options: {
	        timeout: 10
	      }
	    }
	  } = options;
	  /*
	  The optimization algorithm will take some group of peaks.
	  We can not simply optimize everything because there would be too many variables to optimize
	  and it would be too time consuming.
	  */

	  let groups = groupPeaks(peakList, {
	    factor: groupingFactor
	  });
	  let results = [];
	  groups.forEach(peakGroup => {
	    // In order to make optimization we will add fwhm and shape on all the peaks
	    const peaks = appendShapeAndFWHM(peakGroup, {
	      shape
	    });
	    const firstPeak = peaks[0];
	    const lastPeak = peaks[peaks.length - 1];
	    const from = firstPeak.x - firstPeak.width * factorLimits;
	    const to = lastPeak.x + lastPeak.width * factorLimits;
	    const {
	      fromIndex,
	      toIndex
	    } = xGetFromToIndex$2(data.x, {
	      from,
	      to
	    });
	    const x = data.x instanceof Float64Array ? data.x.subarray(fromIndex, toIndex) : data.x.slice(fromIndex, toIndex);
	    const y = data.y instanceof Float64Array ? data.y.subarray(fromIndex, toIndex) : data.y.slice(fromIndex, toIndex);

	    if (x.length > 5) {
	      let {
	        peaks: optimizedPeaks
	      } = optimize({
	        x,
	        y
	      }, peaks, {
	        shape,
	        optimization
	      });

	      for (let i = 0; i < peaks.length; i++) {
	        results.push({
	          x: optimizedPeaks[i].x,
	          y: optimizedPeaks[i].y,
	          shape: peaks[i].shape,
	          fwhm: optimizedPeaks[i].fwhm || 0,
	          width: getShape1D$1(peaks[i].shape).fwhmToWidth(optimizedPeaks[i].fwhm)
	        });
	      }
	    } else {
	      results = results.concat(peaks);
	    }
	  });
	  return results;
	}

	/**
	 * This function tries to join the peaks that seems to belong to a broad signal in a single broad peak.
	 */

	function joinBroadPeaks(peakList) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  let {
	    shape = {
	      kind: 'gaussian'
	    },
	    optimization = {
	      kind: 'lm',
	      options: {
	        timeout: 10
	      }
	    },
	    broadWidth = 0.25,
	    broadRatio = 0.0025
	  } = options;
	  let max = 0;
	  let maxI = 0;
	  let count = 1;
	  const broadLines = [];
	  const peaks = appendShapeAndFWHM(peakList, {
	    shape
	  });
	  if (peaks.length < 2) return peaks;
	  let maxDdy = peakList[0].ddY;

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

	  for (let i = peaks.length - 1; i >= 0; i--) {
	    if (Math.abs(peaks[i].ddY) <= broadRatio * maxDdy) {
	      broadLines.push(peaks.splice(i, 1)[0]);
	    }
	  } //@ts-expect-error Push a feke peak


	  broadLines.push({
	    x: Number.MAX_VALUE,
	    y: 0
	  });
	  let candidates = {
	    x: [broadLines[0].x],
	    y: [broadLines[0].y]
	  };
	  let indexes = [0];

	  for (let i = 1; i < broadLines.length; i++) {
	    if (Math.abs(broadLines[i - 1].x - broadLines[i].x) < broadWidth) {
	      candidates.x.push(broadLines[i].x);
	      candidates.y.push(broadLines[i].y);

	      if (broadLines[i].y > max) {
	        max = broadLines[i].y;
	        maxI = i;
	      }

	      indexes.push(i);
	      count++;
	    } else {
	      if (count > 2) {
	        let fitted = optimizePeaks(candidates, [{
	          x: broadLines[maxI].x,
	          y: max,
	          width: candidates.x[0] - candidates.x[candidates.x.length - 1]
	        }], {
	          shape,
	          optimization
	        }); //@ts-expect-error type is equal as expected

	        peaks.push(fitted[0]);
	      } else {
	        // Put back the candidates to the peak list
	        indexes.forEach(index => {
	          // @ts-expect-error todo 2
	          peaks.push(broadLines[index]);
	        });
	      }

	      candidates = {
	        x: [broadLines[i].x],
	        y: [broadLines[i].y]
	      };
	      indexes = [i];
	      max = broadLines[i].y;
	      maxI = i;
	      count = 1;
	    }
	  }

	  peaks.sort((a, b) => {
	    return a.x - b.x;
	  });
	  return peaks;
	}

	/**
	 * This method will allow to enlarge peaks while preventing overlap between peaks
	 * A typical application in chromatography peak picking.
	 * We should not make the hypothesis that x is equidistant
	 * Because peaks may not be symmetric after we add 2 properties, from and to.
	 * @return {Array} peakList
	 */
	function broadenPeaks(peakList) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const {
	    factor = 2,
	    overlap = false
	  } = options;
	  const peaks = peakList.map(peak => {
	    const xFrom = peak.x - (peak.x - peak.inflectionPoints.from.x) * factor;
	    const xTo = peak.x + (peak.inflectionPoints.to.x - peak.x) * factor;
	    return {
	      x: peak.x,
	      y: peak.y,
	      index: peak.index,
	      width: xTo - xFrom,
	      from: {
	        x: xFrom
	      },
	      to: {
	        x: xTo
	      }
	    };
	  });

	  if (!overlap) {
	    for (let i = 0; i < peaks.length - 1; i++) {
	      let peak = peaks[i];
	      let nextPeak = peaks[i + 1];

	      if (peak.to.x > nextPeak.from.x) {
	        // we do it proportional to the width of the peaks
	        peak.to.x = nextPeak.from.x = peak.width / (nextPeak.width + peak.width) * (nextPeak.x - peak.x) + peak.x;
	      }
	    }
	  }

	  for (let peak of peaks) {
	    peak.width = peak.to.x - peak.from.x;
	  }

	  return peaks;
	}

	var libEsm$3 = /*#__PURE__*/Object.freeze({
		__proto__: null,
		gsd: gsd$1,
		optimizePeaks: optimizePeaks,
		joinBroadPeaks: joinBroadPeaks,
		broadenPeaks: broadenPeaks,
		appendShapeAndFWHM: appendShapeAndFWHM
	});

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

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


	const NEUTRON_MASS = 1;

	function appendPeaksCharge$1(peaks) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  let {
	    precision = 100,
	    low = -1,
	    high = 1,
	    min: minCharge = 1,
	    max: maxCharge = 10
	  } = options;
	  let fromCharge = minCharge * maxCharge > 0 ? Math.round(Math.min(Math.abs(minCharge), Math.abs(maxCharge))) : 1;
	  let toCharge = Math.round(Math.max(Math.abs(minCharge), Math.abs(maxCharge)));
	  let fromIsotope = Math.ceil(low);
	  let toIsotope = Math.floor(high);
	  let numberIsotopes = toIsotope - fromIsotope + 1;
	  let isotopeIntensity = 1 / numberIsotopes;
	  let fromIndex = 0;
	  let localFromIndex = 0;
	  let localToIndex = 0;

	  for (let peakIndex = 0; peakIndex < peaks.length; peakIndex++) {
	    let peak = peaks[peakIndex];
	    let targetMass = peak.x;
	    localFromIndex = fromIndex;
	    let bestCharge = fromCharge;
	    let bestChargeMatch = 0;

	    for (let charge = fromCharge; charge < toCharge + 1; charge++) {
	      let theoreticalPositions = {
	        x: [],
	        y: new Array(numberIsotopes).fill(isotopeIntensity)
	      };
	      let massRange = precision * 1e-6 * targetMass;

	      for (let isotopePosition = fromIsotope; isotopePosition < toIsotope + 1; isotopePosition++) {
	        theoreticalPositions.x.push(targetMass + isotopePosition * NEUTRON_MASS / charge);
	      }

	      let fromMass = targetMass + low / Math.abs(charge) - massRange;
	      let toMass = targetMass + high / Math.abs(charge) + massRange;

	      if (charge === 1) {
	        // we may move the fromIndex
	        while (peaks[fromIndex].x < fromMass) {
	          fromIndex++;
	        }
	      }
	      /*
	       * Find the from / to index for the specific peak and specific charge
	       */


	      while (peaks[localFromIndex].x < fromMass) {
	        localFromIndex++;
	      }

	      localToIndex = localFromIndex;
	      let localHeightSum = 0;

	      while (localToIndex < peaks.length && peaks[localToIndex].x < toMass) {
	        localHeightSum += peaks[localToIndex].y;
	        localToIndex++;
	      }

	      localToIndex--; //  console.log({ localFromIndex, localToIndex });

	      /*
	        Calculate the overlap for a specific peak and specific charge
	      */

	      let currentTheoreticalPosition = 0;
	      let theoreticalMaxValue = 1 / numberIsotopes;
	      let totalMatch = 0;

	      for (let index = localFromIndex; index <= localToIndex; index++) {
	        let minMass = theoreticalPositions.x[currentTheoreticalPosition] - massRange / charge;
	        let maxMass = theoreticalPositions.x[currentTheoreticalPosition] + massRange / charge;

	        while (maxMass < peaks[index].x) {
	          currentTheoreticalPosition++;
	          theoreticalMaxValue = 1 / numberIsotopes;
	          minMass = theoreticalPositions.x[currentTheoreticalPosition] - massRange / charge;
	          maxMass = theoreticalPositions.x[currentTheoreticalPosition] + massRange / charge;
	        }

	        while (index < peaks.length && peaks[index].x < minMass) {
	          index++;
	        } //    console.log({ index, minMass, maxMass, massRange, localHeightSum });


	        if (index < peaks.length && peaks[index].x <= maxMass) {
	          while (index < peaks.length && peaks[index].x <= maxMass) {
	            if (peaks[index].x >= minMass && peaks[index].x <= maxMass) {
	              let value = peaks[index].y / localHeightSum; //      console.log({ theoreticalMaxValue, value });

	              value = Math.min(theoreticalMaxValue, value);
	              theoreticalMaxValue -= value;
	              totalMatch += value;
	            }

	            index++;
	          }

	          index--;
	        }

	        if (totalMatch > bestChargeMatch) {
	          bestCharge = charge;
	          bestChargeMatch = totalMatch;
	        }
	      }
	    }

	    peak.charge = bestCharge;
	  }

	  return peaks;
	}

	var appendPeaksCharge_1 = appendPeaksCharge$1;

	const gsd = require$$0$4.gsd;
	const {
	  xyEnsureGrowingX: xyEnsureGrowingX$1
	} = require$$1$3;
	const appendPeaksCharge = appendPeaksCharge_1;
	/**
	 * Filter the array of peaks
	 * @param {Spectrum} spectrum - array of all the peaks
	 * @param {object} [options={}]
	 * @param {object} [options.charge={}]
	 * @param {number} [options.charge.min=1]
	 * @param {number} [options.charge.max=10]
	 * @param {number} [options.charge.low=-1]
	 * @param {number} [options.charge.high=1]
	 * @param {number} [options.charge.precision=30]
	 * @returns {array} - copy of peaks with 'close' annotation
	 */

	function peakPicking$1(spectrum) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const {
	    charge: chargeOptions = {}
	  } = options;

	  if (!spectrum.peaks || spectrum.peaks.length === 0) {
	    spectrum.peaks = [];
	    const keys = Object.keys(spectrum.data).filter(key => key !== 'x' && key !== 'y');

	    if (spectrum.isContinuous()) {
	      // some experimental data are really problematic and we need to add this line
	      const data = xyEnsureGrowingX$1(spectrum.data);
	      const gsdPeaks = gsd(data, {
	        minMaxRatio: 0.00025,
	        // Threshold to determine if a given peak should be considered as a noise
	        realTopDetection: true,
	        smoothY: false,
	        sgOptions: {
	          windowSize: 7,
	          polynomial: 3
	        }
	      });

	      for (let gsdPeak of gsdPeaks) {
	        const peak = {
	          x: gsdPeak.x,
	          y: gsdPeak.y,
	          width: gsdPeak.width
	        };

	        for (let key of keys) {
	          peak[key] = spectrum.data[key][gsdPeak.index];
	        }

	        spectrum.peaks.push(peak);
	      }
	    } else {
	      spectrum.peaks = [];
	      let data = spectrum.data;

	      for (let i = 0; i < data.x.length; i++) {
	        const peak = {
	          x: data.x[i],
	          y: data.y[i],
	          width: 0
	        };

	        for (let key of keys) {
	          peak[key] = spectrum.data[key][i];
	        }

	        spectrum.peaks.push(peak);
	      }
	    } // required and linked to https://github.com/mljs/global-spectral-deconvolution/issues/17


	    spectrum.peaks = spectrum.peaks.filter(peak => !isNaN(peak.x) && !isNaN(peak.y));
	    appendPeaksCharge(spectrum.peaks, chargeOptions);
	  }

	  return spectrum.peaks;
	}

	var peakPicking_1 = peakPicking$1;

	var isAnyArray$6 = require$$0$6;

	function min$4(input) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};

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

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

	  const {
	    fromIndex = 0,
	    toIndex = input.length
	  } = options;

	  if (fromIndex < 0 || fromIndex >= input.length || !Number.isInteger(fromIndex)) {
	    throw new Error('fromIndex must be a positive integer smaller than length');
	  }

	  if (toIndex <= fromIndex || toIndex > input.length || !Number.isInteger(toIndex)) {
	    throw new Error('toIndex must be an integer greater than fromIndex and at most equal to length');
	  }

	  let minValue = input[fromIndex];

	  for (let i = fromIndex + 1; i < toIndex; i++) {
	    if (input[i] < minValue) minValue = input[i];
	  }

	  return minValue;
	}

	var lib$5 = min$4;

	function maybeToPrecision$1(value, digits) {
	  if (value < 0) {
	    value = 0 - value;

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

	function checkArraySize$1(x, y) {
	  if (!isAnyArray$9(x) || !isAnyArray$9(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$3 {
	  constructor() {
	    if (new.target === BaseRegression$3) {
	      throw new Error('BaseRegression must be subclassed');
	    }
	  }

	  predict(x) {
	    if (typeof x === 'number') {
	      return this._predict(x);
	    } else if (isAnyArray$9(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$9(x) || !isAnyArray$9(y) || x.length !== y.length) {
	      throw new Error('x and y must be arrays of the same length');
	    }

	    const n = x.length;
	    const y2 = new Array(n);

	    for (let i = 0; i < n; i++) {
	      y2[i] = this._predict(x[i]);
	    }

	    let xSum = 0;
	    let ySum = 0;
	    let chi2 = 0;
	    let rmsd = 0;
	    let xSquared = 0;
	    let ySquared = 0;
	    let xY = 0;

	    for (let i = 0; i < n; i++) {
	      xSum += y2[i];
	      ySum += y[i];
	      xSquared += y2[i] * y2[i];
	      ySquared += y[i] * y[i];
	      xY += y2[i] * y[i];

	      if (y[i] !== 0) {
	        chi2 += (y[i] - y2[i]) * (y[i] - y2[i]) / y[i];
	      }

	      rmsd += (y[i] - y2[i]) * (y[i] - y2[i]);
	    }

	    const r = (n * xY - xSum * ySum) / Math.sqrt((n * xSquared - xSum * xSum) * (n * ySquared - ySum * ySum));
	    return {
	      r: r,
	      r2: r * r,
	      chi2: chi2,
	      rmsd: Math.sqrt(rmsd / n)
	    };
	  }

	}

	var src$f = /*#__PURE__*/Object.freeze({
		__proto__: null,
		'default': BaseRegression$3,
		maybeToPrecision: maybeToPrecision$1,
		checkArrayLength: checkArraySize$1
	});

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

	class SimpleLinearRegression$1 extends BaseRegression$3 {
	  constructor(x, y) {
	    super();

	    if (x === true) {
	      this.slope = y.slope;
	      this.intercept = y.intercept;
	      this.coefficients = [y.intercept, y.slope];
	    } else {
	      checkArraySize$1(x, y);
	      regress$1(this, x, y);
	    }
	  }

	  toJSON() {
	    return {
	      name: 'simpleLinearRegression',
	      slope: this.slope,
	      intercept: this.intercept
	    };
	  }

	  _predict(x) {
	    return this.slope * x + this.intercept;
	  }

	  computeX(y) {
	    return (y - this.intercept) / this.slope;
	  }

	  toString(precision) {
	    let result = 'f(x) = ';

	    if (this.slope !== 0) {
	      const xFactor = maybeToPrecision$1(this.slope, precision);
	      result += `${xFactor === '1' ? '' : `${xFactor} * `}x`;

	      if (this.intercept !== 0) {
	        const absIntercept = Math.abs(this.intercept);
	        const operator = absIntercept === this.intercept ? '+' : '-';
	        result += ` ${operator} ${maybeToPrecision$1(absIntercept, precision)}`;
	      }
	    } else {
	      result += maybeToPrecision$1(this.intercept, precision);
	    }

	    return result;
	  }

	  toLaTeX(precision) {
	    return this.toString(precision);
	  }

	  static load(json) {
	    if (json.name !== 'simpleLinearRegression') {
	      throw new TypeError('not a SLR model');
	    }

	    return new SimpleLinearRegression$1(true, json);
	  }

	}

	function regress$1(slr, x, y) {
	  const n = x.length;
	  let xSum = 0;
	  let ySum = 0;
	  let xSquared = 0;
	  let xY = 0;

	  for (let i = 0; i < n; i++) {
	    xSum += x[i];
	    ySum += y[i];
	    xSquared += x[i] * x[i];
	    xY += x[i] * y[i];
	  }

	  const numerator = n * xY - xSum * ySum;
	  slr.slope = numerator / (n * xSquared - xSum * xSum);
	  slr.intercept = 1 / n * ySum - slr.slope * (1 / n) * xSum;
	  slr.coefficients = [slr.intercept, slr.slope];
	}

	var src$e = /*#__PURE__*/Object.freeze({
		__proto__: null,
		'default': SimpleLinearRegression$1
	});

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

	function _interopDefault$2(ex) {
	  return ex && typeof ex === 'object' && 'default' in ex ? ex['default'] : ex;
	}

	var BaseRegression$2 = require$$0$3;

	var BaseRegression__default$1 = _interopDefault$2(BaseRegression$2);

	var SimpleLinearRegression = _interopDefault$2(require$$1$2);

	class PowerRegression extends BaseRegression__default$1 {
	  constructor(x, y) {
	    super();

	    if (x === true) {
	      // reloading model
	      this.A = y.A;
	      this.B = y.B;
	    } else {
	      BaseRegression$2.checkArrayLength(x, y);
	      regress(this, x, y);
	    }
	  }

	  _predict(newInputs) {
	    return this.A * Math.pow(newInputs, this.B);
	  }

	  toJSON() {
	    return {
	      name: 'powerRegression',
	      A: this.A,
	      B: this.B
	    };
	  }

	  toString(precision) {
	    return `f(x) = ${BaseRegression$2.maybeToPrecision(this.A, precision)} * x^${BaseRegression$2.maybeToPrecision(this.B, precision)}`;
	  }

	  toLaTeX(precision) {
	    let latex = '';

	    if (this.B >= 0) {
	      latex = `f(x) = ${BaseRegression$2.maybeToPrecision(this.A, precision)}x^{${BaseRegression$2.maybeToPrecision(this.B, precision)}}`;
	    } else {
	      latex = `f(x) = \\frac{${BaseRegression$2.maybeToPrecision(this.A, precision)}}{x^{${BaseRegression$2.maybeToPrecision(-this.B, precision)}}}`;
	    }

	    latex = latex.replace(/e([+-]?[0-9]+)/g, 'e^{$1}');
	    return latex;
	  }

	  static load(json) {
	    if (json.name !== 'powerRegression') {
	      throw new TypeError('not a power regression model');
	    }

	    return new PowerRegression(true, json);
	  }

	}

	function regress(pr, x, y) {
	  const n = x.length;
	  const xl = new Array(n);
	  const yl = new Array(n);

	  for (let i = 0; i < n; i++) {
	    xl[i] = Math.log(x[i]);
	    yl[i] = Math.log(y[i]);
	  }

	  const linear = new SimpleLinearRegression(xl, yl);
	  pr.A = Math.exp(linear.intercept);
	  pr.B = linear.slope;
	}

	var lib$4 = PowerRegression;

	const max$4 = lib$7;
	const min$3 = lib$5;
	const Regression$1 = lib$4;

	function peaksWidth$1(peaks) {
	  let xs = peaks.map(peak => peak.x);
	  let widths = peaks.map(peak => peak.width);

	  if (xs.length < 2) {
	    throw new Error(`peaksWidth: not enough peaks (less than 2) for automatic width calculation: ${xs.length}`);
	  }

	  let regression = new Regression$1(xs, widths, {
	    computeQuality: true,
	    computeCoefficient: true
	  });

	  if (isNaN(regression.A) || isNaN(regression.B)) {
	    throw new Error('peaksWidth: can not calculate regression');
	  }

	  let from = min$3(xs);
	  let to = max$4(xs);
	  let regressionChart = {
	    x: [],
	    y: []
	  };

	  for (let x = from; x <= to; x += (to - from) / 1000) {
	    regressionChart.x.push(x);
	    regressionChart.y.push(regression.predict(x));
	  }

	  return {
	    widths: {
	      x: xs,
	      y: widths
	    },
	    fit: regressionChart,
	    score: regression.score(xs, widths),
	    predictFct: regression.predict.bind(regression),
	    tex: regression.toLaTeX(3),
	    A: regression.A,
	    B: regression.B,
	    predictFctString: `${regression.A} * mass ** ${regression.B}`
	  };
	}

	var peaksWidth_1 = peaksWidth$1;

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


	async function getPeaksAnnotation(bestPeaks) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const emdb = new src$3();
	  options = Object.assign({
	    limit: 5,
	    precision: 100
	  }, options);
	  let {
	    numberDigits = 5,
	    shift = 0,
	    showMF = false,
	    numberMFs = 0,
	    charge = 1,
	    mfPrefs = {},
	    displayCharge = true,
	    displayProperties = [],
	    mfColors = [{
	      limit: 3,
	      color: 'green'
	    }, {
	      limit: 20,
	      color: 'lightgreen'
	    }, {
	      limit: 50,
	      color: 'lightorange'
	    }]
	  } = options;
	  if (showMF && !numberMFs) numberMFs = 1;
	  let annotations = [];
	  bestPeaks.sort((a, b) => a.close ? -1 : b.close ? 1 : 0);

	  for (let peak of bestPeaks) {
	    let textLine = 0;
	    let annotation;

	    if (peak.close) {
	      annotation = {
	        type: 'line',
	        _highlight: peak._highlight,
	        info: peak,
	        position: [{
	          y: peak.y,
	          dy: '-5px',
	          x: peak.x
	        }, {
	          y: peak.y,
	          dy: '-15px',
	          x: peak.x
	        }]
	      };
	      annotations.push(annotation);
	      annotation = {
	        type: 'ellipse',
	        _highlight: peak._highlight,
	        info: peak,
	        position: [{
	          y: peak.y,
	          dy: '-15px',
	          x: peak.x
	        }],
	        props: {
	          rx: '3px',
	          ry: '3px',
	          fillOpacity: 0.3
	        }
	      };
	    } else {
	      annotation = {
	        type: 'line',
	        _highlight: peak._highlight,
	        position: [{
	          y: peak.y,
	          dy: '-5px',
	          x: peak.x
	        }, {
	          y: peak.y,
	          dy: '-25px',
	          x: peak.x
	        }],
	        labels: [{
	          text: (peak.x + shift).toFixed(numberDigits),
	          color: 'red',
	          position: {
	            x: peak.x,
	            y: peak.y,
	            dy: `${textLine++ * -13 - 17}px`,
	            dx: '2px'
	          }
	        }]
	      };

	      if (displayCharge) {
	        annotation.labels.push({
	          text: `Z:${peak.charge}`,
	          color: 'grey',
	          position: {
	            x: peak.x,
	            y: peak.y,
	            dy: '-4px',
	            dx: '2px'
	          }
	        });
	      }

	      if (numberMFs) {
	        // we have 2 cases. Either there is a shift and we deal with differences
	        // otherwise it is absolute
	        // if there is a shift we consider only a neutral loss and the parameter charge is important
	        if (shift) {
	          // neutral loss
	          let currentMfPrefs = Object.assign({}, mfPrefs, {
	            allowNeutral: true,
	            ionizations: ''
	          }); // we need to deal with the precision and increase it

	          currentMfPrefs.precision = currentMfPrefs.precision / Math.max(Math.abs(peak.x + shift), 1) * peak.x;
	          await emdb.fromMonoisotopicMass(Math.abs((peak.x + shift) * charge), currentMfPrefs);
	        } else {
	          await emdb.fromMonoisotopicMass(Math.abs(peak.x * charge), mfPrefs);
	        }

	        let mfs = emdb.get('monoisotopic');
	        let numberOfMFS = Math.min(mfs.length, numberMFs);

	        for (let i = 0; i < numberOfMFS; i++) {
	          let mf = mfs[i];
	          let ppm = shift ? mf.ms.ppm / shift * mfs[0].ms.em : mf.ms.ppm;
	          annotation.labels.push({
	            text: mf.mf,
	            color: getColor$1(mfColors, Math.abs(ppm)),
	            position: {
	              x: peak.x,
	              y: peak.y,
	              dy: `${textLine++ * -13 - 17}px`,
	              dx: '2px'
	            }
	          });
	        }
	      }

	      if (displayProperties.length > 0) {
	        for (let property of displayProperties) {
	          annotation.labels.push({
	            text: peak[property],
	            color: 'red',
	            position: {
	              x: peak.x,
	              y: peak.y,
	              dy: `${textLine++ * -13 - 17}px`,
	              dx: '2px'
	            }
	          });
	        }
	      }
	    }

	    annotations.push(annotation);
	  }

	  return annotations;
	}

	function getColor$1(colors, value) {
	  for (let color of colors) {
	    if (value < color.limit) return color.color;
	  }

	  return 'lightgrey';
	}

	var getPeaksAnnotation_1 = getPeaksAnnotation;

	var jsgraph = {
	  getPeaksAnnotation: getPeaksAnnotation_1
	};

	const {
	  isAnyArray: isAnyArray$5
	} = require$$0$6;
	const max$3 = lib$7;
	const normed = lib$6;
	const {
	  parseXY
	} = require$$3;
	const getBestPeaks = getBestPeaks_1;
	const getFragmentPeaks = getFragmentPeaks_1;
	const getMassRemainder = getMassRemainder_1;
	const getPeakChargeBySimilarity = getPeakChargeBySimilarity_1;
	const getPeaks = getPeaks_1;
	const isContinuous = isContinuous_1;
	const peakPicking = peakPicking_1;
	const peaksWidth = peaksWidth_1;

	function Spectrum$1() {
	  let data = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {
	    x: [],
	    y: []
	  };

	  if (typeof data !== 'object' || !isAnyArray$5(data.x) || !isAnyArray$5(data.y)) {
	    throw new TypeError('Spectrum data must be an object with x:[], y:[]');
	  }

	  this.data = {}; // we make a copy so that we can add new properties

	  for (let key in data) {
	    this.data[key] = data[key];
	  }

	  Object.defineProperty(this.data, 'xOriginal', {
	    enumerable: false,
	    writable: true
	  });
	  this.cache = {};
	  this.peaks = [];
	}

	Spectrum$1.fromPeaks = function fromPeaks(peaks) {
	  if (peaks.length === 0) return new Spectrum$1();
	  const data = {};

	  for (let key of Object.keys(peaks[0])) {
	    data[key] = peaks.map(peak => peak[key]);
	  }

	  return new Spectrum$1(data);
	};

	Spectrum$1.fromText = function fromText(text) {
	  const data = parseXY(text);
	  return new Spectrum$1(data);
	};

	Spectrum$1.prototype.maxY = function maxY() {
	  return max$3(this.data.y);
	};

	Spectrum$1.prototype.sumY = function sumY() {
	  if (!this.cache.sumY) {
	    this.cache.sumY = this.data.y.reduce((previous, current) => previous + current, 0);
	  }

	  return this.cache.sumY;
	};

	Spectrum$1.prototype.scaleY = function scaleY() {
	  let intensity = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1;
	  let basePeak = this.maxY() / intensity;
	  this.data.y = this.data.y.map(y => y / basePeak);
	  return this;
	};

	Spectrum$1.prototype.rescaleX = function rescaleX(callback) {
	  this.ensureOriginalX();

	  for (let i = 0; i < this.data.x.length; i++) {
	    this.data.x[i] = callback(this.data.xOriginal[i]);
	  }

	  return this;
	};

	Spectrum$1.prototype.ensureOriginalX = function ensureOriginalX() {
	  if (!this.data.xOriginal) {
	    this.data.xOriginal = this.data.x.slice(0);
	  }
	};

	Spectrum$1.prototype.normedY = function normedY() {
	  let total = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1;
	  this.data.y = normed(this.data.y);

	  if (total !== 1) {
	    this.data.y = this.data.y.map(y => y * total);
	  }

	  return this;
	};

	Spectrum$1.prototype.peakPicking = function peakPickingFct() {
	  peakPicking(this);
	  return this.peaks;
	};

	Spectrum$1.prototype.peaksWidth = function peaksWidthFct() {
	  peakPicking(this);
	  return peaksWidth(this.peaks);
	};

	Spectrum$1.prototype.getBestPeaks = function getBestPeaksFct(options) {
	  peakPicking(this);
	  return getBestPeaks(this.peaks, options);
	};

	Spectrum$1.prototype.getPeakChargeBySimilarity = function getPeakChargeBySimilarityFct(targetMass, options) {
	  return getPeakChargeBySimilarity(this, targetMass, options);
	};

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


	Spectrum$1.prototype.getFragmentPeaks = function getFragmentPeaksFct(mf, options) {
	  peakPicking(this);
	  return getFragmentPeaks(this.peaks, mf, options);
	};

	Spectrum$1.prototype.isContinuous = function isContinuousFct() {
	  return isContinuous(this);
	};
	/**
	 * Remove an integer number of time the specifiedd monoisotopic mass
	 * Mass remainder analysis (MARA): https://doi.org/10.1021/acs.analchem.7b04730
	 */


	Spectrum$1.prototype.getMassRemainder = function getMassRemainderFct(mass) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  return getMassRemainder(this.data, mass, options);
	};

	Spectrum$1.JsGraph = Spectrum$1.prototype.JsGraph = jsgraph;
	var Spectrum_1 = Spectrum$1;

	src$k.Spectrum = Spectrum_1;
	src$k.getPeaks = getPeaks_1;
	src$k.getBestPeaks = getBestPeaks_1;

	var jszip = {exports: {}};

	/*!

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

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

	JSZip uses the library pako released under the MIT license :
	https://github.com/nodeca/pako/blob/master/LICENSE
	*/

	(function (module, exports) {
	  (function (f) {
	    {
	      module.exports = f();
	    }
	  })(function () {
	    return function e(t, n, r) {
	      function s(o, u) {
	        if (!n[o]) {
	          if (!t[o]) {
	            var a = typeof commonjsRequire == "function" && commonjsRequire;
	            if (!u && a) return a(o, !0);
	            if (i) return i(o, !0);
	            var f = new Error("Cannot find module '" + o + "'");
	            throw f.code = "MODULE_NOT_FOUND", f;
	          }

	          var l = n[o] = {
	            exports: {}
	          };
	          t[o][0].call(l.exports, function (e) {
	            var n = t[o][1][e];
	            return s(n ? n : e);
	          }, l, l.exports, e, t, n, r);
	        }

	        return n[o].exports;
	      }

	      var i = typeof commonjsRequire == "function" && commonjsRequire;

	      for (var o = 0; o < r.length; o++) s(r[o]);

	      return s;
	    }({
	      1: [function (require, module, exports) {

	        var utils = require('./utils');

	        var support = require('./support'); // private property


	        var _keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; // public method for encoding

	        exports.encode = function (input) {
	          var output = [];
	          var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
	          var i = 0,
	              len = input.length,
	              remainingBytes = len;
	          var isArray = utils.getTypeOf(input) !== "string";

	          while (i < input.length) {
	            remainingBytes = len - i;

	            if (!isArray) {
	              chr1 = input.charCodeAt(i++);
	              chr2 = i < len ? input.charCodeAt(i++) : 0;
	              chr3 = i < len ? input.charCodeAt(i++) : 0;
	            } else {
	              chr1 = input[i++];
	              chr2 = i < len ? input[i++] : 0;
	              chr3 = i < len ? input[i++] : 0;
	            }

	            enc1 = chr1 >> 2;
	            enc2 = (chr1 & 3) << 4 | chr2 >> 4;
	            enc3 = remainingBytes > 1 ? (chr2 & 15) << 2 | chr3 >> 6 : 64;
	            enc4 = remainingBytes > 2 ? chr3 & 63 : 64;
	            output.push(_keyStr.charAt(enc1) + _keyStr.charAt(enc2) + _keyStr.charAt(enc3) + _keyStr.charAt(enc4));
	          }

	          return output.join("");
	        }; // public method for decoding


	        exports.decode = function (input) {
	          var chr1, chr2, chr3;
	          var enc1, enc2, enc3, enc4;
	          var i = 0,
	              resultIndex = 0;
	          var dataUrlPrefix = "data:";

	          if (input.substr(0, dataUrlPrefix.length) === dataUrlPrefix) {
	            // This is a common error: people give a data url
	            // (data:image/png;base64,iVBOR...) with a {base64: true} and
	            // wonders why things don't work.
	            // We can detect that the string input looks like a data url but we
	            // *can't* be sure it is one: removing everything up to the comma would
	            // be too dangerous.
	            throw new Error("Invalid base64 input, it looks like a data url.");
	          }

	          input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
	          var totalLength = input.length * 3 / 4;

	          if (input.charAt(input.length - 1) === _keyStr.charAt(64)) {
	            totalLength--;
	          }

	          if (input.charAt(input.length - 2) === _keyStr.charAt(64)) {
	            totalLength--;
	          }

	          if (totalLength % 1 !== 0) {
	            // totalLength is not an integer, the length does not match a valid
	            // base64 content. That can happen if:
	            // - the input is not a base64 content
	            // - the input is *almost* a base64 content, with a extra chars at the
	            //   beginning or at the end
	            // - the input uses a base64 variant (base64url for example)
	            throw new Error("Invalid base64 input, bad content length.");
	          }

	          var output;

	          if (support.uint8array) {
	            output = new Uint8Array(totalLength | 0);
	          } else {
	            output = new Array(totalLength | 0);
	          }

	          while (i < input.length) {
	            enc1 = _keyStr.indexOf(input.charAt(i++));
	            enc2 = _keyStr.indexOf(input.charAt(i++));
	            enc3 = _keyStr.indexOf(input.charAt(i++));
	            enc4 = _keyStr.indexOf(input.charAt(i++));
	            chr1 = enc1 << 2 | enc2 >> 4;
	            chr2 = (enc2 & 15) << 4 | enc3 >> 2;
	            chr3 = (enc3 & 3) << 6 | enc4;
	            output[resultIndex++] = chr1;

	            if (enc3 !== 64) {
	              output[resultIndex++] = chr2;
	            }

	            if (enc4 !== 64) {
	              output[resultIndex++] = chr3;
	            }
	          }

	          return output;
	        };
	      }, {
	        "./support": 30,
	        "./utils": 32
	      }],
	      2: [function (require, module, exports) {

	        var external = require("./external");

	        var DataWorker = require('./stream/DataWorker');

	        var Crc32Probe = require('./stream/Crc32Probe');

	        var DataLengthProbe = require('./stream/DataLengthProbe');
	        /**
	         * Represent a compressed object, with everything needed to decompress it.
	         * @constructor
	         * @param {number} compressedSize the size of the data compressed.
	         * @param {number} uncompressedSize the size of the data after decompression.
	         * @param {number} crc32 the crc32 of the decompressed file.
	         * @param {object} compression the type of compression, see lib/compressions.js.
	         * @param {String|ArrayBuffer|Uint8Array|Buffer} data the compressed data.
	         */


	        function CompressedObject(compressedSize, uncompressedSize, crc32, compression, data) {
	          this.compressedSize = compressedSize;
	          this.uncompressedSize = uncompressedSize;
	          this.crc32 = crc32;
	          this.compression = compression;
	          this.compressedContent = data;
	        }

	        CompressedObject.prototype = {
	          /**
	           * Create a worker to get the uncompressed content.
	           * @return {GenericWorker} the worker.
	           */
	          getContentWorker: function () {
	            var worker = new DataWorker(external.Promise.resolve(this.compressedContent)).pipe(this.compression.uncompressWorker()).pipe(new DataLengthProbe("data_length"));
	            var that = this;
	            worker.on("end", function () {
	              if (this.streamInfo['data_length'] !== that.uncompressedSize) {
	                throw new Error("Bug : uncompressed data size mismatch");
	              }
	            });
	            return worker;
	          },

	          /**
	           * Create a worker to get the compressed content.
	           * @return {GenericWorker} the worker.
	           */
	          getCompressedWorker: function () {
	            return new DataWorker(external.Promise.resolve(this.compressedContent)).withStreamInfo("compressedSize", this.compressedSize).withStreamInfo("uncompressedSize", this.uncompressedSize).withStreamInfo("crc32", this.crc32).withStreamInfo("compression", this.compression);
	          }
	        };
	        /**
	         * Chain the given worker with other workers to compress the content with the
	         * given compression.
	         * @param {GenericWorker} uncompressedWorker the worker to pipe.
	         * @param {Object} compression the compression object.
	         * @param {Object} compressionOptions the options to use when compressing.
	         * @return {GenericWorker} the new worker compressing the content.
	         */

	        CompressedObject.createWorkerFrom = function (uncompressedWorker, compression, compressionOptions) {
	          return uncompressedWorker.pipe(new Crc32Probe()).pipe(new DataLengthProbe("uncompressedSize")).pipe(compression.compressWorker(compressionOptions)).pipe(new DataLengthProbe("compressedSize")).withStreamInfo("compression", compression);
	        };

	        module.exports = CompressedObject;
	      }, {
	        "./external": 6,
	        "./stream/Crc32Probe": 25,
	        "./stream/DataLengthProbe": 26,
	        "./stream/DataWorker": 27
	      }],
	      3: [function (require, module, exports) {

	        var GenericWorker = require("./stream/GenericWorker");

	        exports.STORE = {
	          magic: "\x00\x00",
	          compressWorker: function (compressionOptions) {
	            return new GenericWorker("STORE compression");
	          },
	          uncompressWorker: function () {
	            return new GenericWorker("STORE decompression");
	          }
	        };
	        exports.DEFLATE = require('./flate');
	      }, {
	        "./flate": 7,
	        "./stream/GenericWorker": 28
	      }],
	      4: [function (require, module, exports) {

	        var utils = require('./utils');
	        /**
	         * The following functions come from pako, from pako/lib/zlib/crc32.js
	         * released under the MIT license, see pako https://github.com/nodeca/pako/
	         */
	        // Use ordinary array, since untyped makes no boost here


	        function makeTable() {
	          var 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.


	        var crcTable = makeTable();

	        function crc32(crc, buf, len, pos) {
	          var t = crcTable,
	              end = pos + len;
	          crc = crc ^ -1;

	          for (var i = pos; i < end; i++) {
	            crc = crc >>> 8 ^ t[(crc ^ buf[i]) & 0xFF];
	          }

	          return crc ^ -1; // >>> 0;
	        } // That's all for the pako functions.

	        /**
	         * Compute the crc32 of a string.
	         * This is almost the same as the function crc32, but for strings. Using the
	         * same function for the two use cases leads to horrible performances.
	         * @param {Number} crc the starting value of the crc.
	         * @param {String} str the string to use.
	         * @param {Number} len the length of the string.
	         * @param {Number} pos the starting position for the crc32 computation.
	         * @return {Number} the computed crc32.
	         */


	        function crc32str(crc, str, len, pos) {
	          var t = crcTable,
	              end = pos + len;
	          crc = crc ^ -1;

	          for (var i = pos; i < end; i++) {
	            crc = crc >>> 8 ^ t[(crc ^ str.charCodeAt(i)) & 0xFF];
	          }

	          return crc ^ -1; // >>> 0;
	        }

	        module.exports = function crc32wrapper(input, crc) {
	          if (typeof input === "undefined" || !input.length) {
	            return 0;
	          }

	          var isArray = utils.getTypeOf(input) !== "string";

	          if (isArray) {
	            return crc32(crc | 0, input, input.length, 0);
	          } else {
	            return crc32str(crc | 0, input, input.length, 0);
	          }
	        };
	      }, {
	        "./utils": 32
	      }],
	      5: [function (require, module, exports) {

	        exports.base64 = false;
	        exports.binary = false;
	        exports.dir = false;
	        exports.createFolders = true;
	        exports.date = null;
	        exports.compression = null;
	        exports.compressionOptions = null;
	        exports.comment = null;
	        exports.unixPermissions = null;
	        exports.dosPermissions = null;
	      }, {}],
	      6: [function (require, module, exports) {
	        // - it should be better integrated in the system (unhandledRejection in node)
	        // - the environment may have a custom Promise implementation (see zone.js)

	        var ES6Promise = null;

	        if (typeof Promise !== "undefined") {
	          ES6Promise = Promise;
	        } else {
	          ES6Promise = require("lie");
	        }
	        /**
	         * Let the user use/change some implementations.
	         */


	        module.exports = {
	          Promise: ES6Promise
	        };
	      }, {
	        "lie": 37
	      }],
	      7: [function (require, module, exports) {

	        var USE_TYPEDARRAY = typeof Uint8Array !== 'undefined' && typeof Uint16Array !== 'undefined' && typeof Uint32Array !== 'undefined';

	        var pako = require("pako");

	        var utils = require("./utils");

	        var GenericWorker = require("./stream/GenericWorker");

	        var ARRAY_TYPE = USE_TYPEDARRAY ? "uint8array" : "array";
	        exports.magic = "\x08\x00";
	        /**
	         * Create a worker that uses pako to inflate/deflate.
	         * @constructor
	         * @param {String} action the name of the pako function to call : either "Deflate" or "Inflate".
	         * @param {Object} options the options to use when (de)compressing.
	         */

	        function FlateWorker(action, options) {
	          GenericWorker.call(this, "FlateWorker/" + action);
	          this._pako = null;
	          this._pakoAction = action;
	          this._pakoOptions = options; // the `meta` object from the last chunk received
	          // this allow this worker to pass around metadata

	          this.meta = {};
	        }

	        utils.inherits(FlateWorker, GenericWorker);
	        /**
	         * @see GenericWorker.processChunk
	         */

	        FlateWorker.prototype.processChunk = function (chunk) {
	          this.meta = chunk.meta;

	          if (this._pako === null) {
	            this._createPako();
	          }

	          this._pako.push(utils.transformTo(ARRAY_TYPE, chunk.data), false);
	        };
	        /**
	         * @see GenericWorker.flush
	         */


	        FlateWorker.prototype.flush = function () {
	          GenericWorker.prototype.flush.call(this);

	          if (this._pako === null) {
	            this._createPako();
	          }

	          this._pako.push([], true);
	        };
	        /**
	         * @see GenericWorker.cleanUp
	         */


	        FlateWorker.prototype.cleanUp = function () {
	          GenericWorker.prototype.cleanUp.call(this);
	          this._pako = null;
	        };
	        /**
	         * Create the _pako object.
	         * TODO: lazy-loading this object isn't the best solution but it's the
	         * quickest. The best solution is to lazy-load the worker list. See also the
	         * issue #446.
	         */


	        FlateWorker.prototype._createPako = function () {
	          this._pako = new pako[this._pakoAction]({
	            raw: true,
	            level: this._pakoOptions.level || -1 // default compression

	          });
	          var self = this;

	          this._pako.onData = function (data) {
	            self.push({
	              data: data,
	              meta: self.meta
	            });
	          };
	        };

	        exports.compressWorker = function (compressionOptions) {
	          return new FlateWorker("Deflate", compressionOptions);
	        };

	        exports.uncompressWorker = function () {
	          return new FlateWorker("Inflate", {});
	        };
	      }, {
	        "./stream/GenericWorker": 28,
	        "./utils": 32,
	        "pako": 38
	      }],
	      8: [function (require, module, exports) {

	        var utils = require('../utils');

	        var GenericWorker = require('../stream/GenericWorker');

	        var utf8 = require('../utf8');

	        var crc32 = require('../crc32');

	        var signature = require('../signature');
	        /**
	         * Transform an integer into a string in hexadecimal.
	         * @private
	         * @param {number} dec the number to convert.
	         * @param {number} bytes the number of bytes to generate.
	         * @returns {string} the result.
	         */


	        var decToHex = function (dec, bytes) {
	          var hex = "",
	              i;

	          for (i = 0; i < bytes; i++) {
	            hex += String.fromCharCode(dec & 0xff);
	            dec = dec >>> 8;
	          }

	          return hex;
	        };
	        /**
	         * Generate the UNIX part of the external file attributes.
	         * @param {Object} unixPermissions the unix permissions or null.
	         * @param {Boolean} isDir true if the entry is a directory, false otherwise.
	         * @return {Number} a 32 bit integer.
	         *
	         * adapted from http://unix.stackexchange.com/questions/14705/the-zip-formats-external-file-attribute :
	         *
	         * TTTTsstrwxrwxrwx0000000000ADVSHR
	         * ^^^^____________________________ file type, see zipinfo.c (UNX_*)
	         *     ^^^_________________________ setuid, setgid, sticky
	         *        ^^^^^^^^^________________ permissions
	         *                 ^^^^^^^^^^______ not used ?
	         *                           ^^^^^^ DOS attribute bits : Archive, Directory, Volume label, System file, Hidden, Read only
	         */


	        var generateUnixExternalFileAttr = function (unixPermissions, isDir) {
	          var result = unixPermissions;

	          if (!unixPermissions) {
	            // I can't use octal values in strict mode, hence the hexa.
	            //  040775 => 0x41fd
	            // 0100664 => 0x81b4
	            result = isDir ? 0x41fd : 0x81b4;
	          }

	          return (result & 0xFFFF) << 16;
	        };
	        /**
	         * Generate the DOS part of the external file attributes.
	         * @param {Object} dosPermissions the dos permissions or null.
	         * @param {Boolean} isDir true if the entry is a directory, false otherwise.
	         * @return {Number} a 32 bit integer.
	         *
	         * Bit 0     Read-Only
	         * Bit 1     Hidden
	         * Bit 2     System
	         * Bit 3     Volume Label
	         * Bit 4     Directory
	         * Bit 5     Archive
	         */


	        var generateDosExternalFileAttr = function (dosPermissions, isDir) {
	          // the dir flag is already set for compatibility
	          return (dosPermissions || 0) & 0x3F;
	        };
	        /**
	         * Generate the various parts used in the construction of the final zip file.
	         * @param {Object} streamInfo the hash with information about the compressed file.
	         * @param {Boolean} streamedContent is the content streamed ?
	         * @param {Boolean} streamingEnded is the stream finished ?
	         * @param {number} offset the current offset from the start of the zip file.
	         * @param {String} platform let's pretend we are this platform (change platform dependents fields)
	         * @param {Function} encodeFileName the function to encode the file name / comment.
	         * @return {Object} the zip parts.
	         */


	        var generateZipParts = function (streamInfo, streamedContent, streamingEnded, offset, platform, encodeFileName) {
	          var file = streamInfo['file'],
	              compression = streamInfo['compression'],
	              useCustomEncoding = encodeFileName !== utf8.utf8encode,
	              encodedFileName = utils.transformTo("string", encodeFileName(file.name)),
	              utfEncodedFileName = utils.transformTo("string", utf8.utf8encode(file.name)),
	              comment = file.comment,
	              encodedComment = utils.transformTo("string", encodeFileName(comment)),
	              utfEncodedComment = utils.transformTo("string", utf8.utf8encode(comment)),
	              useUTF8ForFileName = utfEncodedFileName.length !== file.name.length,
	              useUTF8ForComment = utfEncodedComment.length !== comment.length,
	              dosTime,
	              dosDate,
	              extraFields = "",
	              unicodePathExtraField = "",
	              unicodeCommentExtraField = "",
	              dir = file.dir,
	              date = file.date;
	          var dataInfo = {
	            crc32: 0,
	            compressedSize: 0,
	            uncompressedSize: 0
	          }; // if the content is streamed, the sizes/crc32 are only available AFTER
	          // the end of the stream.

	          if (!streamedContent || streamingEnded) {
	            dataInfo.crc32 = streamInfo['crc32'];
	            dataInfo.compressedSize = streamInfo['compressedSize'];
	            dataInfo.uncompressedSize = streamInfo['uncompressedSize'];
	          }

	          var bitflag = 0;

	          if (streamedContent) {
	            // Bit 3: the sizes/crc32 are set to zero in the local header.
	            // The correct values are put in the data descriptor immediately
	            // following the compressed data.
	            bitflag |= 0x0008;
	          }

	          if (!useCustomEncoding && (useUTF8ForFileName || useUTF8ForComment)) {
	            // Bit 11: Language encoding flag (EFS).
	            bitflag |= 0x0800;
	          }

	          var extFileAttr = 0;
	          var versionMadeBy = 0;

	          if (dir) {
	            // dos or unix, we set the dos dir flag
	            extFileAttr |= 0x00010;
	          }

	          if (platform === "UNIX") {
	            versionMadeBy = 0x031E; // UNIX, version 3.0

	            extFileAttr |= generateUnixExternalFileAttr(file.unixPermissions, dir);
	          } else {
	            // DOS or other, fallback to DOS
	            versionMadeBy = 0x0014; // DOS, version 2.0

	            extFileAttr |= generateDosExternalFileAttr(file.dosPermissions);
	          } // date
	          // @see http://www.delorie.com/djgpp/doc/rbinter/it/52/13.html
	          // @see http://www.delorie.com/djgpp/doc/rbinter/it/65/16.html
	          // @see http://www.delorie.com/djgpp/doc/rbinter/it/66/16.html


	          dosTime = date.getUTCHours();
	          dosTime = dosTime << 6;
	          dosTime = dosTime | date.getUTCMinutes();
	          dosTime = dosTime << 5;
	          dosTime = dosTime | date.getUTCSeconds() / 2;
	          dosDate = date.getUTCFullYear() - 1980;
	          dosDate = dosDate << 4;
	          dosDate = dosDate | date.getUTCMonth() + 1;
	          dosDate = dosDate << 5;
	          dosDate = dosDate | date.getUTCDate();

	          if (useUTF8ForFileName) {
	            // set the unicode path extra field. unzip needs at least one extra
	            // field to correctly handle unicode path, so using the path is as good
	            // as any other information. This could improve the situation with
	            // other archive managers too.
	            // This field is usually used without the utf8 flag, with a non
	            // unicode path in the header (winrar, winzip). This helps (a bit)
	            // with the messy Windows' default compressed folders feature but
	            // breaks on p7zip which doesn't seek the unicode path extra field.
	            // So for now, UTF-8 everywhere !
	            unicodePathExtraField = // Version
	            decToHex(1, 1) + // NameCRC32
	            decToHex(crc32(encodedFileName), 4) + // UnicodeName
	            utfEncodedFileName;
	            extraFields += // Info-ZIP Unicode Path Extra Field
	            "\x75\x70" + // size
	            decToHex(unicodePathExtraField.length, 2) + // content
	            unicodePathExtraField;
	          }

	          if (useUTF8ForComment) {
	            unicodeCommentExtraField = // Version
	            decToHex(1, 1) + // CommentCRC32
	            decToHex(crc32(encodedComment), 4) + // UnicodeName
	            utfEncodedComment;
	            extraFields += // Info-ZIP Unicode Path Extra Field
	            "\x75\x63" + // size
	            decToHex(unicodeCommentExtraField.length, 2) + // content
	            unicodeCommentExtraField;
	          }

	          var header = ""; // version needed to extract

	          header += "\x0A\x00"; // general purpose bit flag

	          header += decToHex(bitflag, 2); // compression method

	          header += compression.magic; // last mod file time

	          header += decToHex(dosTime, 2); // last mod file date

	          header += decToHex(dosDate, 2); // crc-32

	          header += decToHex(dataInfo.crc32, 4); // compressed size

	          header += decToHex(dataInfo.compressedSize, 4); // uncompressed size

	          header += decToHex(dataInfo.uncompressedSize, 4); // file name length

	          header += decToHex(encodedFileName.length, 2); // extra field length

	          header += decToHex(extraFields.length, 2);
	          var fileRecord = signature.LOCAL_FILE_HEADER + header + encodedFileName + extraFields;
	          var dirRecord = signature.CENTRAL_FILE_HEADER + // version made by (00: DOS)
	          decToHex(versionMadeBy, 2) + // file header (common to file and central directory)
	          header + // file comment length
	          decToHex(encodedComment.length, 2) + // disk number start
	          "\x00\x00" + // internal file attributes TODO
	          "\x00\x00" + // external file attributes
	          decToHex(extFileAttr, 4) + // relative offset of local header
	          decToHex(offset, 4) + // file name
	          encodedFileName + // extra field
	          extraFields + // file comment
	          encodedComment;
	          return {
	            fileRecord: fileRecord,
	            dirRecord: dirRecord
	          };
	        };
	        /**
	         * Generate the EOCD record.
	         * @param {Number} entriesCount the number of entries in the zip file.
	         * @param {Number} centralDirLength the length (in bytes) of the central dir.
	         * @param {Number} localDirLength the length (in bytes) of the local dir.
	         * @param {String} comment the zip file comment as a binary string.
	         * @param {Function} encodeFileName the function to encode the comment.
	         * @return {String} the EOCD record.
	         */


	        var generateCentralDirectoryEnd = function (entriesCount, centralDirLength, localDirLength, comment, encodeFileName) {
	          var dirEnd = "";
	          var encodedComment = utils.transformTo("string", encodeFileName(comment)); // end of central dir signature

	          dirEnd = signature.CENTRAL_DIRECTORY_END + // number of this disk
	          "\x00\x00" + // number of the disk with the start of the central directory
	          "\x00\x00" + // total number of entries in the central directory on this disk
	          decToHex(entriesCount, 2) + // total number of entries in the central directory
	          decToHex(entriesCount, 2) + // size of the central directory   4 bytes
	          decToHex(centralDirLength, 4) + // offset of start of central directory with respect to the starting disk number
	          decToHex(localDirLength, 4) + // .ZIP file comment length
	          decToHex(encodedComment.length, 2) + // .ZIP file comment
	          encodedComment;
	          return dirEnd;
	        };
	        /**
	         * Generate data descriptors for a file entry.
	         * @param {Object} streamInfo the hash generated by a worker, containing information
	         * on the file entry.
	         * @return {String} the data descriptors.
	         */


	        var generateDataDescriptors = function (streamInfo) {
	          var descriptor = "";
	          descriptor = signature.DATA_DESCRIPTOR + // crc-32                          4 bytes
	          decToHex(streamInfo['crc32'], 4) + // compressed size                 4 bytes
	          decToHex(streamInfo['compressedSize'], 4) + // uncompressed size               4 bytes
	          decToHex(streamInfo['uncompressedSize'], 4);
	          return descriptor;
	        };
	        /**
	         * A worker to concatenate other workers to create a zip file.
	         * @param {Boolean} streamFiles `true` to stream the content of the files,
	         * `false` to accumulate it.
	         * @param {String} comment the comment to use.
	         * @param {String} platform the platform to use, "UNIX" or "DOS".
	         * @param {Function} encodeFileName the function to encode file names and comments.
	         */


	        function ZipFileWorker(streamFiles, comment, platform, encodeFileName) {
	          GenericWorker.call(this, "ZipFileWorker"); // The number of bytes written so far. This doesn't count accumulated chunks.

	          this.bytesWritten = 0; // The comment of the zip file

	          this.zipComment = comment; // The platform "generating" the zip file.

	          this.zipPlatform = platform; // the function to encode file names and comments.

	          this.encodeFileName = encodeFileName; // Should we stream the content of the files ?

	          this.streamFiles = streamFiles; // If `streamFiles` is false, we will need to accumulate the content of the
	          // files to calculate sizes / crc32 (and write them *before* the content).
	          // This boolean indicates if we are accumulating chunks (it will change a lot
	          // during the lifetime of this worker).

	          this.accumulate = false; // The buffer receiving chunks when accumulating content.

	          this.contentBuffer = []; // The list of generated directory records.

	          this.dirRecords = []; // The offset (in bytes) from the beginning of the zip file for the current source.

	          this.currentSourceOffset = 0; // The total number of entries in this zip file.

	          this.entriesCount = 0; // the name of the file currently being added, null when handling the end of the zip file.
	          // Used for the emitted metadata.

	          this.currentFile = null;
	          this._sources = [];
	        }

	        utils.inherits(ZipFileWorker, GenericWorker);
	        /**
	         * @see GenericWorker.push
	         */

	        ZipFileWorker.prototype.push = function (chunk) {
	          var currentFilePercent = chunk.meta.percent || 0;
	          var entriesCount = this.entriesCount;
	          var remainingFiles = this._sources.length;

	          if (this.accumulate) {
	            this.contentBuffer.push(chunk);
	          } else {
	            this.bytesWritten += chunk.data.length;
	            GenericWorker.prototype.push.call(this, {
	              data: chunk.data,
	              meta: {
	                currentFile: this.currentFile,
	                percent: entriesCount ? (currentFilePercent + 100 * (entriesCount - remainingFiles - 1)) / entriesCount : 100
	              }
	            });
	          }
	        };
	        /**
	         * The worker started a new source (an other worker).
	         * @param {Object} streamInfo the streamInfo object from the new source.
	         */


	        ZipFileWorker.prototype.openedSource = function (streamInfo) {
	          this.currentSourceOffset = this.bytesWritten;
	          this.currentFile = streamInfo['file'].name;
	          var streamedContent = this.streamFiles && !streamInfo['file'].dir; // don't stream folders (because they don't have any content)

	          if (streamedContent) {
	            var record = generateZipParts(streamInfo, streamedContent, false, this.currentSourceOffset, this.zipPlatform, this.encodeFileName);
	            this.push({
	              data: record.fileRecord,
	              meta: {
	                percent: 0
	              }
	            });
	          } else {
	            // we need to wait for the whole file before pushing anything
	            this.accumulate = true;
	          }
	        };
	        /**
	         * The worker finished a source (an other worker).
	         * @param {Object} streamInfo the streamInfo object from the finished source.
	         */


	        ZipFileWorker.prototype.closedSource = function (streamInfo) {
	          this.accumulate = false;
	          var streamedContent = this.streamFiles && !streamInfo['file'].dir;
	          var record = generateZipParts(streamInfo, streamedContent, true, this.currentSourceOffset, this.zipPlatform, this.encodeFileName);
	          this.dirRecords.push(record.dirRecord);

	          if (streamedContent) {
	            // after the streamed file, we put data descriptors
	            this.push({
	              data: generateDataDescriptors(streamInfo),
	              meta: {
	                percent: 100
	              }
	            });
	          } else {
	            // the content wasn't streamed, we need to push everything now
	            // first the file record, then the content
	            this.push({
	              data: record.fileRecord,
	              meta: {
	                percent: 0
	              }
	            });

	            while (this.contentBuffer.length) {
	              this.push(this.contentBuffer.shift());
	            }
	          }

	          this.currentFile = null;
	        };
	        /**
	         * @see GenericWorker.flush
	         */


	        ZipFileWorker.prototype.flush = function () {
	          var localDirLength = this.bytesWritten;

	          for (var i = 0; i < this.dirRecords.length; i++) {
	            this.push({
	              data: this.dirRecords[i],
	              meta: {
	                percent: 100
	              }
	            });
	          }

	          var centralDirLength = this.bytesWritten - localDirLength;
	          var dirEnd = generateCentralDirectoryEnd(this.dirRecords.length, centralDirLength, localDirLength, this.zipComment, this.encodeFileName);
	          this.push({
	            data: dirEnd,
	            meta: {
	              percent: 100
	            }
	          });
	        };
	        /**
	         * Prepare the next source to be read.
	         */


	        ZipFileWorker.prototype.prepareNextSource = function () {
	          this.previous = this._sources.shift();
	          this.openedSource(this.previous.streamInfo);

	          if (this.isPaused) {
	            this.previous.pause();
	          } else {
	            this.previous.resume();
	          }
	        };
	        /**
	         * @see GenericWorker.registerPrevious
	         */


	        ZipFileWorker.prototype.registerPrevious = function (previous) {
	          this._sources.push(previous);

	          var self = this;
	          previous.on('data', function (chunk) {
	            self.processChunk(chunk);
	          });
	          previous.on('end', function () {
	            self.closedSource(self.previous.streamInfo);

	            if (self._sources.length) {
	              self.prepareNextSource();
	            } else {
	              self.end();
	            }
	          });
	          previous.on('error', function (e) {
	            self.error(e);
	          });
	          return this;
	        };
	        /**
	         * @see GenericWorker.resume
	         */


	        ZipFileWorker.prototype.resume = function () {
	          if (!GenericWorker.prototype.resume.call(this)) {
	            return false;
	          }

	          if (!this.previous && this._sources.length) {
	            this.prepareNextSource();
	            return true;
	          }

	          if (!this.previous && !this._sources.length && !this.generatedError) {
	            this.end();
	            return true;
	          }
	        };
	        /**
	         * @see GenericWorker.error
	         */


	        ZipFileWorker.prototype.error = function (e) {
	          var sources = this._sources;

	          if (!GenericWorker.prototype.error.call(this, e)) {
	            return false;
	          }

	          for (var i = 0; i < sources.length; i++) {
	            try {
	              sources[i].error(e);
	            } catch (e) {// the `error` exploded, nothing to do
	            }
	          }

	          return true;
	        };
	        /**
	         * @see GenericWorker.lock
	         */


	        ZipFileWorker.prototype.lock = function () {
	          GenericWorker.prototype.lock.call(this);
	          var sources = this._sources;

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

	        module.exports = ZipFileWorker;
	      }, {
	        "../crc32": 4,
	        "../signature": 23,
	        "../stream/GenericWorker": 28,
	        "../utf8": 31,
	        "../utils": 32
	      }],
	      9: [function (require, module, exports) {

	        var compressions = require('../compressions');

	        var ZipFileWorker = require('./ZipFileWorker');
	        /**
	         * Find the compression to use.
	         * @param {String} fileCompression the compression defined at the file level, if any.
	         * @param {String} zipCompression the compression defined at the load() level.
	         * @return {Object} the compression object to use.
	         */


	        var getCompression = function (fileCompression, zipCompression) {
	          var compressionName = fileCompression || zipCompression;
	          var compression = compressions[compressionName];

	          if (!compression) {
	            throw new Error(compressionName + " is not a valid compression method !");
	          }

	          return compression;
	        };
	        /**
	         * Create a worker to generate a zip file.
	         * @param {JSZip} zip the JSZip instance at the right root level.
	         * @param {Object} options to generate the zip file.
	         * @param {String} comment the comment to use.
	         */


	        exports.generateWorker = function (zip, options, comment) {
	          var zipFileWorker = new ZipFileWorker(options.streamFiles, comment, options.platform, options.encodeFileName);
	          var entriesCount = 0;

	          try {
	            zip.forEach(function (relativePath, file) {
	              entriesCount++;
	              var compression = getCompression(file.options.compression, options.compression);
	              var compressionOptions = file.options.compressionOptions || options.compressionOptions || {};
	              var dir = file.dir,
	                  date = file.date;

	              file._compressWorker(compression, compressionOptions).withStreamInfo("file", {
	                name: relativePath,
	                dir: dir,
	                date: date,
	                comment: file.comment || "",
	                unixPermissions: file.unixPermissions,
	                dosPermissions: file.dosPermissions
	              }).pipe(zipFileWorker);
	            });
	            zipFileWorker.entriesCount = entriesCount;
	          } catch (e) {
	            zipFileWorker.error(e);
	          }

	          return zipFileWorker;
	        };
	      }, {
	        "../compressions": 3,
	        "./ZipFileWorker": 8
	      }],
	      10: [function (require, module, exports) {
	        /**
	         * Representation a of zip file in js
	         * @constructor
	         */

	        function JSZip() {
	          // if this constructor is used without `new`, it adds `new` before itself:
	          if (!(this instanceof JSZip)) {
	            return new JSZip();
	          }

	          if (arguments.length) {
	            throw new Error("The constructor with parameters has been removed in JSZip 3.0, please check the upgrade guide.");
	          } // object containing the files :
	          // {
	          //   "folder/" : {...},
	          //   "folder/data.txt" : {...}
	          // }
	          // NOTE: we use a null prototype because we do not
	          // want filenames like "toString" coming from a zip file
	          // to overwrite methods and attributes in a normal Object.


	          this.files = Object.create(null);
	          this.comment = null; // Where we are in the hierarchy

	          this.root = "";

	          this.clone = function () {
	            var newObj = new JSZip();

	            for (var i in this) {
	              if (typeof this[i] !== "function") {
	                newObj[i] = this[i];
	              }
	            }

	            return newObj;
	          };
	        }

	        JSZip.prototype = require('./object');
	        JSZip.prototype.loadAsync = require('./load');
	        JSZip.support = require('./support');
	        JSZip.defaults = require('./defaults'); // TODO find a better way to handle this version,
	        // a require('package.json').version doesn't work with webpack, see #327

	        JSZip.version = "3.9.1";

	        JSZip.loadAsync = function (content, options) {
	          return new JSZip().loadAsync(content, options);
	        };

	        JSZip.external = require("./external");
	        module.exports = JSZip;
	      }, {
	        "./defaults": 5,
	        "./external": 6,
	        "./load": 11,
	        "./object": 15,
	        "./support": 30
	      }],
	      11: [function (require, module, exports) {

	        var utils = require('./utils');

	        var external = require("./external");

	        var utf8 = require('./utf8');

	        var ZipEntries = require('./zipEntries');

	        var Crc32Probe = require('./stream/Crc32Probe');

	        var nodejsUtils = require("./nodejsUtils");
	        /**
	         * Check the CRC32 of an entry.
	         * @param {ZipEntry} zipEntry the zip entry to check.
	         * @return {Promise} the result.
	         */


	        function checkEntryCRC32(zipEntry) {
	          return new external.Promise(function (resolve, reject) {
	            var worker = zipEntry.decompressed.getContentWorker().pipe(new Crc32Probe());
	            worker.on("error", function (e) {
	              reject(e);
	            }).on("end", function () {
	              if (worker.streamInfo.crc32 !== zipEntry.decompressed.crc32) {
	                reject(new Error("Corrupted zip : CRC32 mismatch"));
	              } else {
	                resolve();
	              }
	            }).resume();
	          });
	        }

	        module.exports = function (data, options) {
	          var zip = this;
	          options = utils.extend(options || {}, {
	            base64: false,
	            checkCRC32: false,
	            optimizedBinaryString: false,
	            createFolders: false,
	            decodeFileName: utf8.utf8decode
	          });

	          if (nodejsUtils.isNode && nodejsUtils.isStream(data)) {
	            return external.Promise.reject(new Error("JSZip can't accept a stream when loading a zip file."));
	          }

	          return utils.prepareContent("the loaded zip file", data, true, options.optimizedBinaryString, options.base64).then(function (data) {
	            var zipEntries = new ZipEntries(options);
	            zipEntries.load(data);
	            return zipEntries;
	          }).then(function checkCRC32(zipEntries) {
	            var promises = [external.Promise.resolve(zipEntries)];
	            var files = zipEntries.files;

	            if (options.checkCRC32) {
	              for (var i = 0; i < files.length; i++) {
	                promises.push(checkEntryCRC32(files[i]));
	              }
	            }

	            return external.Promise.all(promises);
	          }).then(function addFiles(results) {
	            var zipEntries = results.shift();
	            var files = zipEntries.files;

	            for (var i = 0; i < files.length; i++) {
	              var input = files[i];
	              var unsafeName = input.fileNameStr;
	              var safeName = utils.resolve(input.fileNameStr);
	              zip.file(safeName, input.decompressed, {
	                binary: true,
	                optimizedBinaryString: true,
	                date: input.date,
	                dir: input.dir,
	                comment: input.fileCommentStr.length ? input.fileCommentStr : null,
	                unixPermissions: input.unixPermissions,
	                dosPermissions: input.dosPermissions,
	                createFolders: options.createFolders
	              });

	              if (!input.dir) {
	                zip.file(safeName).unsafeOriginalName = unsafeName;
	              }
	            }

	            if (zipEntries.zipComment.length) {
	              zip.comment = zipEntries.zipComment;
	            }

	            return zip;
	          });
	        };
	      }, {
	        "./external": 6,
	        "./nodejsUtils": 14,
	        "./stream/Crc32Probe": 25,
	        "./utf8": 31,
	        "./utils": 32,
	        "./zipEntries": 33
	      }],
	      12: [function (require, module, exports) {

	        var utils = require('../utils');

	        var GenericWorker = require('../stream/GenericWorker');
	        /**
	         * A worker that use a nodejs stream as source.
	         * @constructor
	         * @param {String} filename the name of the file entry for this stream.
	         * @param {Readable} stream the nodejs stream.
	         */


	        function NodejsStreamInputAdapter(filename, stream) {
	          GenericWorker.call(this, "Nodejs stream input adapter for " + filename);
	          this._upstreamEnded = false;

	          this._bindStream(stream);
	        }

	        utils.inherits(NodejsStreamInputAdapter, GenericWorker);
	        /**
	         * Prepare the stream and bind the callbacks on it.
	         * Do this ASAP on node 0.10 ! A lazy binding doesn't always work.
	         * @param {Stream} stream the nodejs stream to use.
	         */

	        NodejsStreamInputAdapter.prototype._bindStream = function (stream) {
	          var self = this;
	          this._stream = stream;
	          stream.pause();
	          stream.on("data", function (chunk) {
	            self.push({
	              data: chunk,
	              meta: {
	                percent: 0
	              }
	            });
	          }).on("error", function (e) {
	            if (self.isPaused) {
	              this.generatedError = e;
	            } else {
	              self.error(e);
	            }
	          }).on("end", function () {
	            if (self.isPaused) {
	              self._upstreamEnded = true;
	            } else {
	              self.end();
	            }
	          });
	        };

	        NodejsStreamInputAdapter.prototype.pause = function () {
	          if (!GenericWorker.prototype.pause.call(this)) {
	            return false;
	          }

	          this._stream.pause();

	          return true;
	        };

	        NodejsStreamInputAdapter.prototype.resume = function () {
	          if (!GenericWorker.prototype.resume.call(this)) {
	            return false;
	          }

	          if (this._upstreamEnded) {
	            this.end();
	          } else {
	            this._stream.resume();
	          }

	          return true;
	        };

	        module.exports = NodejsStreamInputAdapter;
	      }, {
	        "../stream/GenericWorker": 28,
	        "../utils": 32
	      }],
	      13: [function (require, module, exports) {

	        var Readable = require('readable-stream').Readable;

	        var utils = require('../utils');

	        utils.inherits(NodejsStreamOutputAdapter, Readable);
	        /**
	        * A nodejs stream using a worker as source.
	        * @see the SourceWrapper in http://nodejs.org/api/stream.html
	        * @constructor
	        * @param {StreamHelper} helper the helper wrapping the worker
	        * @param {Object} options the nodejs stream options
	        * @param {Function} updateCb the update callback.
	        */

	        function NodejsStreamOutputAdapter(helper, options, updateCb) {
	          Readable.call(this, options);
	          this._helper = helper;
	          var self = this;
	          helper.on("data", function (data, meta) {
	            if (!self.push(data)) {
	              self._helper.pause();
	            }

	            if (updateCb) {
	              updateCb(meta);
	            }
	          }).on("error", function (e) {
	            self.emit('error', e);
	          }).on("end", function () {
	            self.push(null);
	          });
	        }

	        NodejsStreamOutputAdapter.prototype._read = function () {
	          this._helper.resume();
	        };

	        module.exports = NodejsStreamOutputAdapter;
	      }, {
	        "../utils": 32,
	        "readable-stream": 16
	      }],
	      14: [function (require, module, exports) {

	        module.exports = {
	          /**
	           * True if this is running in Nodejs, will be undefined in a browser.
	           * In a browser, browserify won't include this file and the whole module
	           * will be resolved an empty object.
	           */
	          isNode: typeof Buffer !== "undefined",

	          /**
	           * Create a new nodejs Buffer from an existing content.
	           * @param {Object} data the data to pass to the constructor.
	           * @param {String} encoding the encoding to use.
	           * @return {Buffer} a new Buffer.
	           */
	          newBufferFrom: function (data, encoding) {
	            if (Buffer.from && Buffer.from !== Uint8Array.from) {
	              return Buffer.from(data, encoding);
	            } else {
	              if (typeof data === "number") {
	                // Safeguard for old Node.js versions. On newer versions,
	                // Buffer.from(number) / Buffer(number, encoding) already throw.
	                throw new Error("The \"data\" argument must not be a number");
	              }

	              return new Buffer(data, encoding);
	            }
	          },

	          /**
	           * Create a new nodejs Buffer with the specified size.
	           * @param {Integer} size the size of the buffer.
	           * @return {Buffer} a new Buffer.
	           */
	          allocBuffer: function (size) {
	            if (Buffer.alloc) {
	              return Buffer.alloc(size);
	            } else {
	              var buf = new Buffer(size);
	              buf.fill(0);
	              return buf;
	            }
	          },

	          /**
	           * Find out if an object is a Buffer.
	           * @param {Object} b the object to test.
	           * @return {Boolean} true if the object is a Buffer, false otherwise.
	           */
	          isBuffer: function (b) {
	            return Buffer.isBuffer(b);
	          },
	          isStream: function (obj) {
	            return obj && typeof obj.on === "function" && typeof obj.pause === "function" && typeof obj.resume === "function";
	          }
	        };
	      }, {}],
	      15: [function (require, module, exports) {

	        var utf8 = require('./utf8');

	        var utils = require('./utils');

	        var GenericWorker = require('./stream/GenericWorker');

	        var StreamHelper = require('./stream/StreamHelper');

	        var defaults = require('./defaults');

	        var CompressedObject = require('./compressedObject');

	        var ZipObject = require('./zipObject');

	        var generate = require("./generate");

	        var nodejsUtils = require("./nodejsUtils");

	        var NodejsStreamInputAdapter = require("./nodejs/NodejsStreamInputAdapter");
	        /**
	         * Add a file in the current folder.
	         * @private
	         * @param {string} name the name of the file
	         * @param {String|ArrayBuffer|Uint8Array|Buffer} data the data of the file
	         * @param {Object} originalOptions the options of the file
	         * @return {Object} the new file.
	         */


	        var fileAdd = function (name, data, originalOptions) {
	          // be sure sub folders exist
	          var dataType = utils.getTypeOf(data),
	              parent;
	          /*
	           * Correct options.
	           */

	          var o = utils.extend(originalOptions || {}, defaults);
	          o.date = o.date || new Date();

	          if (o.compression !== null) {
	            o.compression = o.compression.toUpperCase();
	          }

	          if (typeof o.unixPermissions === "string") {
	            o.unixPermissions = parseInt(o.unixPermissions, 8);
	          } // UNX_IFDIR  0040000 see zipinfo.c


	          if (o.unixPermissions && o.unixPermissions & 0x4000) {
	            o.dir = true;
	          } // Bit 4    Directory


	          if (o.dosPermissions && o.dosPermissions & 0x0010) {
	            o.dir = true;
	          }

	          if (o.dir) {
	            name = forceTrailingSlash(name);
	          }

	          if (o.createFolders && (parent = parentFolder(name))) {
	            folderAdd.call(this, parent, true);
	          }

	          var isUnicodeString = dataType === "string" && o.binary === false && o.base64 === false;

	          if (!originalOptions || typeof originalOptions.binary === "undefined") {
	            o.binary = !isUnicodeString;
	          }

	          var isCompressedEmpty = data instanceof CompressedObject && data.uncompressedSize === 0;

	          if (isCompressedEmpty || o.dir || !data || data.length === 0) {
	            o.base64 = false;
	            o.binary = true;
	            data = "";
	            o.compression = "STORE";
	            dataType = "string";
	          }
	          /*
	           * Convert content to fit.
	           */


	          var zipObjectContent = null;

	          if (data instanceof CompressedObject || data instanceof GenericWorker) {
	            zipObjectContent = data;
	          } else if (nodejsUtils.isNode && nodejsUtils.isStream(data)) {
	            zipObjectContent = new NodejsStreamInputAdapter(name, data);
	          } else {
	            zipObjectContent = utils.prepareContent(name, data, o.binary, o.optimizedBinaryString, o.base64);
	          }

	          var object = new ZipObject(name, zipObjectContent, o);
	          this.files[name] = object;
	          /*
	          TODO: we can't throw an exception because we have async promises
	          (we can have a promise of a Date() for example) but returning a
	          promise is useless because file(name, data) returns the JSZip
	          object for chaining. Should we break that to allow the user
	          to catch the error ?
	           return external.Promise.resolve(zipObjectContent)
	          .then(function () {
	              return object;
	          });
	          */
	        };
	        /**
	         * Find the parent folder of the path.
	         * @private
	         * @param {string} path the path to use
	         * @return {string} the parent folder, or ""
	         */


	        var parentFolder = function (path) {
	          if (path.slice(-1) === '/') {
	            path = path.substring(0, path.length - 1);
	          }

	          var lastSlash = path.lastIndexOf('/');
	          return lastSlash > 0 ? path.substring(0, lastSlash) : "";
	        };
	        /**
	         * Returns the path with a slash at the end.
	         * @private
	         * @param {String} path the path to check.
	         * @return {String} the path with a trailing slash.
	         */


	        var forceTrailingSlash = function (path) {
	          // Check the name ends with a /
	          if (path.slice(-1) !== "/") {
	            path += "/"; // IE doesn't like substr(-1)
	          }

	          return path;
	        };
	        /**
	         * Add a (sub) folder in the current folder.
	         * @private
	         * @param {string} name the folder's name
	         * @param {boolean=} [createFolders] If true, automatically create sub
	         *  folders. Defaults to false.
	         * @return {Object} the new folder.
	         */


	        var folderAdd = function (name, createFolders) {
	          createFolders = typeof createFolders !== 'undefined' ? createFolders : defaults.createFolders;
	          name = forceTrailingSlash(name); // Does this folder already exist?

	          if (!this.files[name]) {
	            fileAdd.call(this, name, null, {
	              dir: true,
	              createFolders: createFolders
	            });
	          }

	          return this.files[name];
	        };
	        /**
	        * Cross-window, cross-Node-context regular expression detection
	        * @param  {Object}  object Anything
	        * @return {Boolean}        true if the object is a regular expression,
	        * false otherwise
	        */


	        function isRegExp(object) {
	          return Object.prototype.toString.call(object) === "[object RegExp]";
	        } // return the actual prototype of JSZip


	        var out = {
	          /**
	           * @see loadAsync
	           */
	          load: function () {
	            throw new Error("This method has been removed in JSZip 3.0, please check the upgrade guide.");
	          },

	          /**
	           * Call a callback function for each entry at this folder level.
	           * @param {Function} cb the callback function:
	           * function (relativePath, file) {...}
	           * It takes 2 arguments : the relative path and the file.
	           */
	          forEach: function (cb) {
	            var filename, relativePath, file;
	            /* jshint ignore:start */
	            // ignore warning about unwanted properties because this.files is a null prototype object

	            for (filename in this.files) {
	              file = this.files[filename];
	              relativePath = filename.slice(this.root.length, filename.length);

	              if (relativePath && filename.slice(0, this.root.length) === this.root) {
	                // the file is in the current root
	                cb(relativePath, file); // TODO reverse the parameters ? need to be clean AND consistent with the filter search fn...
	              }
	            }
	            /* jshint ignore:end */

	          },

	          /**
	           * Filter nested files/folders with the specified function.
	           * @param {Function} search the predicate to use :
	           * function (relativePath, file) {...}
	           * It takes 2 arguments : the relative path and the file.
	           * @return {Array} An array of matching elements.
	           */
	          filter: function (search) {
	            var result = [];
	            this.forEach(function (relativePath, entry) {
	              if (search(relativePath, entry)) {
	                // the file matches the function
	                result.push(entry);
	              }
	            });
	            return result;
	          },

	          /**
	           * Add a file to the zip file, or search a file.
	           * @param   {string|RegExp} name The name of the file to add (if data is defined),
	           * the name of the file to find (if no data) or a regex to match files.
	           * @param   {String|ArrayBuffer|Uint8Array|Buffer} data  The file data, either raw or base64 encoded
	           * @param   {Object} o     File options
	           * @return  {JSZip|Object|Array} this JSZip object (when adding a file),
	           * a file (when searching by string) or an array of files (when searching by regex).
	           */
	          file: function (name, data, o) {
	            if (arguments.length === 1) {
	              if (isRegExp(name)) {
	                var regexp = name;
	                return this.filter(function (relativePath, file) {
	                  return !file.dir && regexp.test(relativePath);
	                });
	              } else {
	                // text
	                var obj = this.files[this.root + name];

	                if (obj && !obj.dir) {
	                  return obj;
	                } else {
	                  return null;
	                }
	              }
	            } else {
	              // more than one argument : we have data !
	              name = this.root + name;
	              fileAdd.call(this, name, data, o);
	            }

	            return this;
	          },

	          /**
	           * Add a directory to the zip file, or search.
	           * @param   {String|RegExp} arg The name of the directory to add, or a regex to search folders.
	           * @return  {JSZip} an object with the new directory as the root, or an array containing matching folders.
	           */
	          folder: function (arg) {
	            if (!arg) {
	              return this;
	            }

	            if (isRegExp(arg)) {
	              return this.filter(function (relativePath, file) {
	                return file.dir && arg.test(relativePath);
	              });
	            } // else, name is a new folder


	            var name = this.root + arg;
	            var newFolder = folderAdd.call(this, name); // Allow chaining by returning a new object with this folder as the root

	            var ret = this.clone();
	            ret.root = newFolder.name;
	            return ret;
	          },

	          /**
	           * Delete a file, or a directory and all sub-files, from the zip
	           * @param {string} name the name of the file to delete
	           * @return {JSZip} this JSZip object
	           */
	          remove: function (name) {
	            name = this.root + name;
	            var file = this.files[name];

	            if (!file) {
	              // Look for any folders
	              if (name.slice(-1) !== "/") {
	                name += "/";
	              }

	              file = this.files[name];
	            }

	            if (file && !file.dir) {
	              // file
	              delete this.files[name];
	            } else {
	              // maybe a folder, delete recursively
	              var kids = this.filter(function (relativePath, file) {
	                return file.name.slice(0, name.length) === name;
	              });

	              for (var i = 0; i < kids.length; i++) {
	                delete this.files[kids[i].name];
	              }
	            }

	            return this;
	          },

	          /**
	           * Generate the complete zip file
	           * @param {Object} options the options to generate the zip file :
	           * - compression, "STORE" by default.
	           * - type, "base64" by default. Values are : string, base64, uint8array, arraybuffer, blob.
	           * @return {String|Uint8Array|ArrayBuffer|Buffer|Blob} the zip file
	           */
	          generate: function (options) {
	            throw new Error("This method has been removed in JSZip 3.0, please check the upgrade guide.");
	          },

	          /**
	           * Generate the complete zip file as an internal stream.
	           * @param {Object} options the options to generate the zip file :
	           * - compression, "STORE" by default.
	           * - type, "base64" by default. Values are : string, base64, uint8array, arraybuffer, blob.
	           * @return {StreamHelper} the streamed zip file.
	           */
	          generateInternalStream: function (options) {
	            var worker,
	                opts = {};

	            try {
	              opts = utils.extend(options || {}, {
	                streamFiles: false,
	                compression: "STORE",
	                compressionOptions: null,
	                type: "",
	                platform: "DOS",
	                comment: null,
	                mimeType: 'application/zip',
	                encodeFileName: utf8.utf8encode
	              });
	              opts.type = opts.type.toLowerCase();
	              opts.compression = opts.compression.toUpperCase(); // "binarystring" is preferred but the internals use "string".

	              if (opts.type === "binarystring") {
	                opts.type = "string";
	              }

	              if (!opts.type) {
	                throw new Error("No output type specified.");
	              }

	              utils.checkSupport(opts.type); // accept nodejs `process.platform`

	              if (opts.platform === 'darwin' || opts.platform === 'freebsd' || opts.platform === 'linux' || opts.platform === 'sunos') {
	                opts.platform = "UNIX";
	              }

	              if (opts.platform === 'win32') {
	                opts.platform = "DOS";
	              }

	              var comment = opts.comment || this.comment || "";
	              worker = generate.generateWorker(this, opts, comment);
	            } catch (e) {
	              worker = new GenericWorker("error");
	              worker.error(e);
	            }

	            return new StreamHelper(worker, opts.type || "string", opts.mimeType);
	          },

	          /**
	           * Generate the complete zip file asynchronously.
	           * @see generateInternalStream
	           */
	          generateAsync: function (options, onUpdate) {
	            return this.generateInternalStream(options).accumulate(onUpdate);
	          },

	          /**
	           * Generate the complete zip file asynchronously.
	           * @see generateInternalStream
	           */
	          generateNodeStream: function (options, onUpdate) {
	            options = options || {};

	            if (!options.type) {
	              options.type = "nodebuffer";
	            }

	            return this.generateInternalStream(options).toNodejsStream(onUpdate);
	          }
	        };
	        module.exports = out;
	      }, {
	        "./compressedObject": 2,
	        "./defaults": 5,
	        "./generate": 9,
	        "./nodejs/NodejsStreamInputAdapter": 12,
	        "./nodejsUtils": 14,
	        "./stream/GenericWorker": 28,
	        "./stream/StreamHelper": 29,
	        "./utf8": 31,
	        "./utils": 32,
	        "./zipObject": 35
	      }],
	      16: [function (require, module, exports) {
	        /*
	         * This file is used by module bundlers (browserify/webpack/etc) when
	         * including a stream implementation. We use "readable-stream" to get a
	         * consistent behavior between nodejs versions but bundlers often have a shim
	         * for "stream". Using this shim greatly improve the compatibility and greatly
	         * reduce the final size of the bundle (only one stream implementation, not
	         * two).
	         */
	        module.exports = require("stream");
	      }, {
	        "stream": undefined
	      }],
	      17: [function (require, module, exports) {

	        var DataReader = require('./DataReader');

	        var utils = require('../utils');

	        function ArrayReader(data) {
	          DataReader.call(this, data);

	          for (var i = 0; i < this.data.length; i++) {
	            data[i] = data[i] & 0xFF;
	          }
	        }

	        utils.inherits(ArrayReader, DataReader);
	        /**
	         * @see DataReader.byteAt
	         */

	        ArrayReader.prototype.byteAt = function (i) {
	          return this.data[this.zero + i];
	        };
	        /**
	         * @see DataReader.lastIndexOfSignature
	         */


	        ArrayReader.prototype.lastIndexOfSignature = function (sig) {
	          var sig0 = sig.charCodeAt(0),
	              sig1 = sig.charCodeAt(1),
	              sig2 = sig.charCodeAt(2),
	              sig3 = sig.charCodeAt(3);

	          for (var i = this.length - 4; i >= 0; --i) {
	            if (this.data[i] === sig0 && this.data[i + 1] === sig1 && this.data[i + 2] === sig2 && this.data[i + 3] === sig3) {
	              return i - this.zero;
	            }
	          }

	          return -1;
	        };
	        /**
	         * @see DataReader.readAndCheckSignature
	         */


	        ArrayReader.prototype.readAndCheckSignature = function (sig) {
	          var sig0 = sig.charCodeAt(0),
	              sig1 = sig.charCodeAt(1),
	              sig2 = sig.charCodeAt(2),
	              sig3 = sig.charCodeAt(3),
	              data = this.readData(4);
	          return sig0 === data[0] && sig1 === data[1] && sig2 === data[2] && sig3 === data[3];
	        };
	        /**
	         * @see DataReader.readData
	         */


	        ArrayReader.prototype.readData = function (size) {
	          this.checkOffset(size);

	          if (size === 0) {
	            return [];
	          }

	          var result = this.data.slice(this.zero + this.index, this.zero + this.index + size);
	          this.index += size;
	          return result;
	        };

	        module.exports = ArrayReader;
	      }, {
	        "../utils": 32,
	        "./DataReader": 18
	      }],
	      18: [function (require, module, exports) {

	        var utils = require('../utils');

	        function DataReader(data) {
	          this.data = data; // type : see implementation

	          this.length = data.length;
	          this.index = 0;
	          this.zero = 0;
	        }

	        DataReader.prototype = {
	          /**
	           * Check that the offset will not go too far.
	           * @param {string} offset the additional offset to check.
	           * @throws {Error} an Error if the offset is out of bounds.
	           */
	          checkOffset: function (offset) {
	            this.checkIndex(this.index + offset);
	          },

	          /**
	           * Check that the specified index will not be too far.
	           * @param {string} newIndex the index to check.
	           * @throws {Error} an Error if the index is out of bounds.
	           */
	          checkIndex: function (newIndex) {
	            if (this.length < this.zero + newIndex || newIndex < 0) {
	              throw new Error("End of data reached (data length = " + this.length + ", asked index = " + newIndex + "). Corrupted zip ?");
	            }
	          },

	          /**
	           * Change the index.
	           * @param {number} newIndex The new index.
	           * @throws {Error} if the new index is out of the data.
	           */
	          setIndex: function (newIndex) {
	            this.checkIndex(newIndex);
	            this.index = newIndex;
	          },

	          /**
	           * Skip the next n bytes.
	           * @param {number} n the number of bytes to skip.
	           * @throws {Error} if the new index is out of the data.
	           */
	          skip: function (n) {
	            this.setIndex(this.index + n);
	          },

	          /**
	           * Get the byte at the specified index.
	           * @param {number} i the index to use.
	           * @return {number} a byte.
	           */
	          byteAt: function (i) {// see implementations
	          },

	          /**
	           * Get the next number with a given byte size.
	           * @param {number} size the number of bytes to read.
	           * @return {number} the corresponding number.
	           */
	          readInt: function (size) {
	            var result = 0,
	                i;
	            this.checkOffset(size);

	            for (i = this.index + size - 1; i >= this.index; i--) {
	              result = (result << 8) + this.byteAt(i);
	            }

	            this.index += size;
	            return result;
	          },

	          /**
	           * Get the next string with a given byte size.
	           * @param {number} size the number of bytes to read.
	           * @return {string} the corresponding string.
	           */
	          readString: function (size) {
	            return utils.transformTo("string", this.readData(size));
	          },

	          /**
	           * Get raw data without conversion, <size> bytes.
	           * @param {number} size the number of bytes to read.
	           * @return {Object} the raw data, implementation specific.
	           */
	          readData: function (size) {// see implementations
	          },

	          /**
	           * Find the last occurrence of a zip signature (4 bytes).
	           * @param {string} sig the signature to find.
	           * @return {number} the index of the last occurrence, -1 if not found.
	           */
	          lastIndexOfSignature: function (sig) {// see implementations
	          },

	          /**
	           * Read the signature (4 bytes) at the current position and compare it with sig.
	           * @param {string} sig the expected signature
	           * @return {boolean} true if the signature matches, false otherwise.
	           */
	          readAndCheckSignature: function (sig) {// see implementations
	          },

	          /**
	           * Get the next date.
	           * @return {Date} the date.
	           */
	          readDate: function () {
	            var dostime = this.readInt(4);
	            return new Date(Date.UTC((dostime >> 25 & 0x7f) + 1980, // year
	            (dostime >> 21 & 0x0f) - 1, // month
	            dostime >> 16 & 0x1f, // day
	            dostime >> 11 & 0x1f, // hour
	            dostime >> 5 & 0x3f, // minute
	            (dostime & 0x1f) << 1)); // second
	          }
	        };
	        module.exports = DataReader;
	      }, {
	        "../utils": 32
	      }],
	      19: [function (require, module, exports) {

	        var Uint8ArrayReader = require('./Uint8ArrayReader');

	        var utils = require('../utils');

	        function NodeBufferReader(data) {
	          Uint8ArrayReader.call(this, data);
	        }

	        utils.inherits(NodeBufferReader, Uint8ArrayReader);
	        /**
	         * @see DataReader.readData
	         */

	        NodeBufferReader.prototype.readData = function (size) {
	          this.checkOffset(size);
	          var result = this.data.slice(this.zero + this.index, this.zero + this.index + size);
	          this.index += size;
	          return result;
	        };

	        module.exports = NodeBufferReader;
	      }, {
	        "../utils": 32,
	        "./Uint8ArrayReader": 21
	      }],
	      20: [function (require, module, exports) {

	        var DataReader = require('./DataReader');

	        var utils = require('../utils');

	        function StringReader(data) {
	          DataReader.call(this, data);
	        }

	        utils.inherits(StringReader, DataReader);
	        /**
	         * @see DataReader.byteAt
	         */

	        StringReader.prototype.byteAt = function (i) {
	          return this.data.charCodeAt(this.zero + i);
	        };
	        /**
	         * @see DataReader.lastIndexOfSignature
	         */


	        StringReader.prototype.lastIndexOfSignature = function (sig) {
	          return this.data.lastIndexOf(sig) - this.zero;
	        };
	        /**
	         * @see DataReader.readAndCheckSignature
	         */


	        StringReader.prototype.readAndCheckSignature = function (sig) {
	          var data = this.readData(4);
	          return sig === data;
	        };
	        /**
	         * @see DataReader.readData
	         */


	        StringReader.prototype.readData = function (size) {
	          this.checkOffset(size); // this will work because the constructor applied the "& 0xff" mask.

	          var result = this.data.slice(this.zero + this.index, this.zero + this.index + size);
	          this.index += size;
	          return result;
	        };

	        module.exports = StringReader;
	      }, {
	        "../utils": 32,
	        "./DataReader": 18
	      }],
	      21: [function (require, module, exports) {

	        var ArrayReader = require('./ArrayReader');

	        var utils = require('../utils');

	        function Uint8ArrayReader(data) {
	          ArrayReader.call(this, data);
	        }

	        utils.inherits(Uint8ArrayReader, ArrayReader);
	        /**
	         * @see DataReader.readData
	         */

	        Uint8ArrayReader.prototype.readData = function (size) {
	          this.checkOffset(size);

	          if (size === 0) {
	            // in IE10, when using subarray(idx, idx), we get the array [0x00] instead of [].
	            return new Uint8Array(0);
	          }

	          var result = this.data.subarray(this.zero + this.index, this.zero + this.index + size);
	          this.index += size;
	          return result;
	        };

	        module.exports = Uint8ArrayReader;
	      }, {
	        "../utils": 32,
	        "./ArrayReader": 17
	      }],
	      22: [function (require, module, exports) {

	        var utils = require('../utils');

	        var support = require('../support');

	        var ArrayReader = require('./ArrayReader');

	        var StringReader = require('./StringReader');

	        var NodeBufferReader = require('./NodeBufferReader');

	        var Uint8ArrayReader = require('./Uint8ArrayReader');
	        /**
	         * Create a reader adapted to the data.
	         * @param {String|ArrayBuffer|Uint8Array|Buffer} data the data to read.
	         * @return {DataReader} the data reader.
	         */


	        module.exports = function (data) {
	          var type = utils.getTypeOf(data);
	          utils.checkSupport(type);

	          if (type === "string" && !support.uint8array) {
	            return new StringReader(data);
	          }

	          if (type === "nodebuffer") {
	            return new NodeBufferReader(data);
	          }

	          if (support.uint8array) {
	            return new Uint8ArrayReader(utils.transformTo("uint8array", data));
	          }

	          return new ArrayReader(utils.transformTo("array", data));
	        };
	      }, {
	        "../support": 30,
	        "../utils": 32,
	        "./ArrayReader": 17,
	        "./NodeBufferReader": 19,
	        "./StringReader": 20,
	        "./Uint8ArrayReader": 21
	      }],
	      23: [function (require, module, exports) {

	        exports.LOCAL_FILE_HEADER = "PK\x03\x04";
	        exports.CENTRAL_FILE_HEADER = "PK\x01\x02";
	        exports.CENTRAL_DIRECTORY_END = "PK\x05\x06";
	        exports.ZIP64_CENTRAL_DIRECTORY_LOCATOR = "PK\x06\x07";
	        exports.ZIP64_CENTRAL_DIRECTORY_END = "PK\x06\x06";
	        exports.DATA_DESCRIPTOR = "PK\x07\x08";
	      }, {}],
	      24: [function (require, module, exports) {

	        var GenericWorker = require('./GenericWorker');

	        var utils = require('../utils');
	        /**
	         * A worker which convert chunks to a specified type.
	         * @constructor
	         * @param {String} destType the destination type.
	         */


	        function ConvertWorker(destType) {
	          GenericWorker.call(this, "ConvertWorker to " + destType);
	          this.destType = destType;
	        }

	        utils.inherits(ConvertWorker, GenericWorker);
	        /**
	         * @see GenericWorker.processChunk
	         */

	        ConvertWorker.prototype.processChunk = function (chunk) {
	          this.push({
	            data: utils.transformTo(this.destType, chunk.data),
	            meta: chunk.meta
	          });
	        };

	        module.exports = ConvertWorker;
	      }, {
	        "../utils": 32,
	        "./GenericWorker": 28
	      }],
	      25: [function (require, module, exports) {

	        var GenericWorker = require('./GenericWorker');

	        var crc32 = require('../crc32');

	        var utils = require('../utils');
	        /**
	         * A worker which calculate the crc32 of the data flowing through.
	         * @constructor
	         */


	        function Crc32Probe() {
	          GenericWorker.call(this, "Crc32Probe");
	          this.withStreamInfo("crc32", 0);
	        }

	        utils.inherits(Crc32Probe, GenericWorker);
	        /**
	         * @see GenericWorker.processChunk
	         */

	        Crc32Probe.prototype.processChunk = function (chunk) {
	          this.streamInfo.crc32 = crc32(chunk.data, this.streamInfo.crc32 || 0);
	          this.push(chunk);
	        };

	        module.exports = Crc32Probe;
	      }, {
	        "../crc32": 4,
	        "../utils": 32,
	        "./GenericWorker": 28
	      }],
	      26: [function (require, module, exports) {

	        var utils = require('../utils');

	        var GenericWorker = require('./GenericWorker');
	        /**
	         * A worker which calculate the total length of the data flowing through.
	         * @constructor
	         * @param {String} propName the name used to expose the length
	         */


	        function DataLengthProbe(propName) {
	          GenericWorker.call(this, "DataLengthProbe for " + propName);
	          this.propName = propName;
	          this.withStreamInfo(propName, 0);
	        }

	        utils.inherits(DataLengthProbe, GenericWorker);
	        /**
	         * @see GenericWorker.processChunk
	         */

	        DataLengthProbe.prototype.processChunk = function (chunk) {
	          if (chunk) {
	            var length = this.streamInfo[this.propName] || 0;
	            this.streamInfo[this.propName] = length + chunk.data.length;
	          }

	          GenericWorker.prototype.processChunk.call(this, chunk);
	        };

	        module.exports = DataLengthProbe;
	      }, {
	        "../utils": 32,
	        "./GenericWorker": 28
	      }],
	      27: [function (require, module, exports) {

	        var utils = require('../utils');

	        var GenericWorker = require('./GenericWorker'); // the size of the generated chunks
	        // TODO expose this as a public variable


	        var DEFAULT_BLOCK_SIZE = 16 * 1024;
	        /**
	         * A worker that reads a content and emits chunks.
	         * @constructor
	         * @param {Promise} dataP the promise of the data to split
	         */

	        function DataWorker(dataP) {
	          GenericWorker.call(this, "DataWorker");
	          var self = this;
	          this.dataIsReady = false;
	          this.index = 0;
	          this.max = 0;
	          this.data = null;
	          this.type = "";
	          this._tickScheduled = false;
	          dataP.then(function (data) {
	            self.dataIsReady = true;
	            self.data = data;
	            self.max = data && data.length || 0;
	            self.type = utils.getTypeOf(data);

	            if (!self.isPaused) {
	              self._tickAndRepeat();
	            }
	          }, function (e) {
	            self.error(e);
	          });
	        }

	        utils.inherits(DataWorker, GenericWorker);
	        /**
	         * @see GenericWorker.cleanUp
	         */

	        DataWorker.prototype.cleanUp = function () {
	          GenericWorker.prototype.cleanUp.call(this);
	          this.data = null;
	        };
	        /**
	         * @see GenericWorker.resume
	         */


	        DataWorker.prototype.resume = function () {
	          if (!GenericWorker.prototype.resume.call(this)) {
	            return false;
	          }

	          if (!this._tickScheduled && this.dataIsReady) {
	            this._tickScheduled = true;
	            utils.delay(this._tickAndRepeat, [], this);
	          }

	          return true;
	        };
	        /**
	         * Trigger a tick a schedule an other call to this function.
	         */


	        DataWorker.prototype._tickAndRepeat = function () {
	          this._tickScheduled = false;

	          if (this.isPaused || this.isFinished) {
	            return;
	          }

	          this._tick();

	          if (!this.isFinished) {
	            utils.delay(this._tickAndRepeat, [], this);
	            this._tickScheduled = true;
	          }
	        };
	        /**
	         * Read and push a chunk.
	         */


	        DataWorker.prototype._tick = function () {
	          if (this.isPaused || this.isFinished) {
	            return false;
	          }

	          var size = DEFAULT_BLOCK_SIZE;
	          var data = null,
	              nextIndex = Math.min(this.max, this.index + size);

	          if (this.index >= this.max) {
	            // EOF
	            return this.end();
	          } else {
	            switch (this.type) {
	              case "string":
	                data = this.data.substring(this.index, nextIndex);
	                break;

	              case "uint8array":
	                data = this.data.subarray(this.index, nextIndex);
	                break;

	              case "array":
	              case "nodebuffer":
	                data = this.data.slice(this.index, nextIndex);
	                break;
	            }

	            this.index = nextIndex;
	            return this.push({
	              data: data,
	              meta: {
	                percent: this.max ? this.index / this.max * 100 : 0
	              }
	            });
	          }
	        };

	        module.exports = DataWorker;
	      }, {
	        "../utils": 32,
	        "./GenericWorker": 28
	      }],
	      28: [function (require, module, exports) {
	        /**
	         * A worker that does nothing but passing chunks to the next one. This is like
	         * a nodejs stream but with some differences. On the good side :
	         * - it works on IE 6-9 without any issue / polyfill
	         * - it weights less than the full dependencies bundled with browserify
	         * - it forwards errors (no need to declare an error handler EVERYWHERE)
	         *
	         * A chunk is an object with 2 attributes : `meta` and `data`. The former is an
	         * object containing anything (`percent` for example), see each worker for more
	         * details. The latter is the real data (String, Uint8Array, etc).
	         *
	         * @constructor
	         * @param {String} name the name of the stream (mainly used for debugging purposes)
	         */

	        function GenericWorker(name) {
	          // the name of the worker
	          this.name = name || "default"; // an object containing metadata about the workers chain

	          this.streamInfo = {}; // an error which happened when the worker was paused

	          this.generatedError = null; // an object containing metadata to be merged by this worker into the general metadata

	          this.extraStreamInfo = {}; // true if the stream is paused (and should not do anything), false otherwise

	          this.isPaused = true; // true if the stream is finished (and should not do anything), false otherwise

	          this.isFinished = false; // true if the stream is locked to prevent further structure updates (pipe), false otherwise

	          this.isLocked = false; // the event listeners

	          this._listeners = {
	            'data': [],
	            'end': [],
	            'error': []
	          }; // the previous worker, if any

	          this.previous = null;
	        }

	        GenericWorker.prototype = {
	          /**
	           * Push a chunk to the next workers.
	           * @param {Object} chunk the chunk to push
	           */
	          push: function (chunk) {
	            this.emit("data", chunk);
	          },

	          /**
	           * End the stream.
	           * @return {Boolean} true if this call ended the worker, false otherwise.
	           */
	          end: function () {
	            if (this.isFinished) {
	              return false;
	            }

	            this.flush();

	            try {
	              this.emit("end");
	              this.cleanUp();
	              this.isFinished = true;
	            } catch (e) {
	              this.emit("error", e);
	            }

	            return true;
	          },

	          /**
	           * End the stream with an error.
	           * @param {Error} e the error which caused the premature end.
	           * @return {Boolean} true if this call ended the worker with an error, false otherwise.
	           */
	          error: function (e) {
	            if (this.isFinished) {
	              return false;
	            }

	            if (this.isPaused) {
	              this.generatedError = e;
	            } else {
	              this.isFinished = true;
	              this.emit("error", e); // in the workers chain exploded in the middle of the chain,
	              // the error event will go downward but we also need to notify
	              // workers upward that there has been an error.

	              if (this.previous) {
	                this.previous.error(e);
	              }

	              this.cleanUp();
	            }

	            return true;
	          },

	          /**
	           * Add a callback on an event.
	           * @param {String} name the name of the event (data, end, error)
	           * @param {Function} listener the function to call when the event is triggered
	           * @return {GenericWorker} the current object for chainability
	           */
	          on: function (name, listener) {
	            this._listeners[name].push(listener);

	            return this;
	          },

	          /**
	           * Clean any references when a worker is ending.
	           */
	          cleanUp: function () {
	            this.streamInfo = this.generatedError = this.extraStreamInfo = null;
	            this._listeners = [];
	          },

	          /**
	           * Trigger an event. This will call registered callback with the provided arg.
	           * @param {String} name the name of the event (data, end, error)
	           * @param {Object} arg the argument to call the callback with.
	           */
	          emit: function (name, arg) {
	            if (this._listeners[name]) {
	              for (var i = 0; i < this._listeners[name].length; i++) {
	                this._listeners[name][i].call(this, arg);
	              }
	            }
	          },

	          /**
	           * Chain a worker with an other.
	           * @param {Worker} next the worker receiving events from the current one.
	           * @return {worker} the next worker for chainability
	           */
	          pipe: function (next) {
	            return next.registerPrevious(this);
	          },

	          /**
	           * Same as `pipe` in the other direction.
	           * Using an API with `pipe(next)` is very easy.
	           * Implementing the API with the point of view of the next one registering
	           * a source is easier, see the ZipFileWorker.
	           * @param {Worker} previous the previous worker, sending events to this one
	           * @return {Worker} the current worker for chainability
	           */
	          registerPrevious: function (previous) {
	            if (this.isLocked) {
	              throw new Error("The stream '" + this + "' has already been used.");
	            } // sharing the streamInfo...


	            this.streamInfo = previous.streamInfo; // ... and adding our own bits

	            this.mergeStreamInfo();
	            this.previous = previous;
	            var self = this;
	            previous.on('data', function (chunk) {
	              self.processChunk(chunk);
	            });
	            previous.on('end', function () {
	              self.end();
	            });
	            previous.on('error', function (e) {
	              self.error(e);
	            });
	            return this;
	          },

	          /**
	           * Pause the stream so it doesn't send events anymore.
	           * @return {Boolean} true if this call paused the worker, false otherwise.
	           */
	          pause: function () {
	            if (this.isPaused || this.isFinished) {
	              return false;
	            }

	            this.isPaused = true;

	            if (this.previous) {
	              this.previous.pause();
	            }

	            return true;
	          },

	          /**
	           * Resume a paused stream.
	           * @return {Boolean} true if this call resumed the worker, false otherwise.
	           */
	          resume: function () {
	            if (!this.isPaused || this.isFinished) {
	              return false;
	            }

	            this.isPaused = false; // if true, the worker tried to resume but failed

	            var withError = false;

	            if (this.generatedError) {
	              this.error(this.generatedError);
	              withError = true;
	            }

	            if (this.previous) {
	              this.previous.resume();
	            }

	            return !withError;
	          },

	          /**
	           * Flush any remaining bytes as the stream is ending.
	           */
	          flush: function () {},

	          /**
	           * Process a chunk. This is usually the method overridden.
	           * @param {Object} chunk the chunk to process.
	           */
	          processChunk: function (chunk) {
	            this.push(chunk);
	          },

	          /**
	           * Add a key/value to be added in the workers chain streamInfo once activated.
	           * @param {String} key the key to use
	           * @param {Object} value the associated value
	           * @return {Worker} the current worker for chainability
	           */
	          withStreamInfo: function (key, value) {
	            this.extraStreamInfo[key] = value;
	            this.mergeStreamInfo();
	            return this;
	          },

	          /**
	           * Merge this worker's streamInfo into the chain's streamInfo.
	           */
	          mergeStreamInfo: function () {
	            for (var key in this.extraStreamInfo) {
	              if (!this.extraStreamInfo.hasOwnProperty(key)) {
	                continue;
	              }

	              this.streamInfo[key] = this.extraStreamInfo[key];
	            }
	          },

	          /**
	           * Lock the stream to prevent further updates on the workers chain.
	           * After calling this method, all calls to pipe will fail.
	           */
	          lock: function () {
	            if (this.isLocked) {
	              throw new Error("The stream '" + this + "' has already been used.");
	            }

	            this.isLocked = true;

	            if (this.previous) {
	              this.previous.lock();
	            }
	          },

	          /**
	           *
	           * Pretty print the workers chain.
	           */
	          toString: function () {
	            var me = "Worker " + this.name;

	            if (this.previous) {
	              return this.previous + " -> " + me;
	            } else {
	              return me;
	            }
	          }
	        };
	        module.exports = GenericWorker;
	      }, {}],
	      29: [function (require, module, exports) {

	        var utils = require('../utils');

	        var ConvertWorker = require('./ConvertWorker');

	        var GenericWorker = require('./GenericWorker');

	        var base64 = require('../base64');

	        var support = require("../support");

	        var external = require("../external");

	        var NodejsStreamOutputAdapter = null;

	        if (support.nodestream) {
	          try {
	            NodejsStreamOutputAdapter = require('../nodejs/NodejsStreamOutputAdapter');
	          } catch (e) {}
	        }
	        /**
	         * Apply the final transformation of the data. If the user wants a Blob for
	         * example, it's easier to work with an U8intArray and finally do the
	         * ArrayBuffer/Blob conversion.
	         * @param {String} type the name of the final type
	         * @param {String|Uint8Array|Buffer} content the content to transform
	         * @param {String} mimeType the mime type of the content, if applicable.
	         * @return {String|Uint8Array|ArrayBuffer|Buffer|Blob} the content in the right format.
	         */


	        function transformZipOutput(type, content, mimeType) {
	          switch (type) {
	            case "blob":
	              return utils.newBlob(utils.transformTo("arraybuffer", content), mimeType);

	            case "base64":
	              return base64.encode(content);

	            default:
	              return utils.transformTo(type, content);
	          }
	        }
	        /**
	         * Concatenate an array of data of the given type.
	         * @param {String} type the type of the data in the given array.
	         * @param {Array} dataArray the array containing the data chunks to concatenate
	         * @return {String|Uint8Array|Buffer} the concatenated data
	         * @throws Error if the asked type is unsupported
	         */


	        function concat(type, dataArray) {
	          var i,
	              index = 0,
	              res = null,
	              totalLength = 0;

	          for (i = 0; i < dataArray.length; i++) {
	            totalLength += dataArray[i].length;
	          }

	          switch (type) {
	            case "string":
	              return dataArray.join("");

	            case "array":
	              return Array.prototype.concat.apply([], dataArray);

	            case "uint8array":
	              res = new Uint8Array(totalLength);

	              for (i = 0; i < dataArray.length; i++) {
	                res.set(dataArray[i], index);
	                index += dataArray[i].length;
	              }

	              return res;

	            case "nodebuffer":
	              return Buffer.concat(dataArray);

	            default:
	              throw new Error("concat : unsupported type '" + type + "'");
	          }
	        }
	        /**
	         * Listen a StreamHelper, accumulate its content and concatenate it into a
	         * complete block.
	         * @param {StreamHelper} helper the helper to use.
	         * @param {Function} updateCallback a callback called on each update. Called
	         * with one arg :
	         * - the metadata linked to the update received.
	         * @return Promise the promise for the accumulation.
	         */


	        function accumulate(helper, updateCallback) {
	          return new external.Promise(function (resolve, reject) {
	            var dataArray = [];
	            var chunkType = helper._internalType,
	                resultType = helper._outputType,
	                mimeType = helper._mimeType;
	            helper.on('data', function (data, meta) {
	              dataArray.push(data);

	              if (updateCallback) {
	                updateCallback(meta);
	              }
	            }).on('error', function (err) {
	              dataArray = [];
	              reject(err);
	            }).on('end', function () {
	              try {
	                var result = transformZipOutput(resultType, concat(chunkType, dataArray), mimeType);
	                resolve(result);
	              } catch (e) {
	                reject(e);
	              }

	              dataArray = [];
	            }).resume();
	          });
	        }
	        /**
	         * An helper to easily use workers outside of JSZip.
	         * @constructor
	         * @param {Worker} worker the worker to wrap
	         * @param {String} outputType the type of data expected by the use
	         * @param {String} mimeType the mime type of the content, if applicable.
	         */


	        function StreamHelper(worker, outputType, mimeType) {
	          var internalType = outputType;

	          switch (outputType) {
	            case "blob":
	            case "arraybuffer":
	              internalType = "uint8array";
	              break;

	            case "base64":
	              internalType = "string";
	              break;
	          }

	          try {
	            // the type used internally
	            this._internalType = internalType; // the type used to output results

	            this._outputType = outputType; // the mime type

	            this._mimeType = mimeType;
	            utils.checkSupport(internalType);
	            this._worker = worker.pipe(new ConvertWorker(internalType)); // the last workers can be rewired without issues but we need to
	            // prevent any updates on previous workers.

	            worker.lock();
	          } catch (e) {
	            this._worker = new GenericWorker("error");

	            this._worker.error(e);
	          }
	        }

	        StreamHelper.prototype = {
	          /**
	           * Listen a StreamHelper, accumulate its content and concatenate it into a
	           * complete block.
	           * @param {Function} updateCb the update callback.
	           * @return Promise the promise for the accumulation.
	           */
	          accumulate: function (updateCb) {
	            return accumulate(this, updateCb);
	          },

	          /**
	           * Add a listener on an event triggered on a stream.
	           * @param {String} evt the name of the event
	           * @param {Function} fn the listener
	           * @return {StreamHelper} the current helper.
	           */
	          on: function (evt, fn) {
	            var self = this;

	            if (evt === "data") {
	              this._worker.on(evt, function (chunk) {
	                fn.call(self, chunk.data, chunk.meta);
	              });
	            } else {
	              this._worker.on(evt, function () {
	                utils.delay(fn, arguments, self);
	              });
	            }

	            return this;
	          },

	          /**
	           * Resume the flow of chunks.
	           * @return {StreamHelper} the current helper.
	           */
	          resume: function () {
	            utils.delay(this._worker.resume, [], this._worker);
	            return this;
	          },

	          /**
	           * Pause the flow of chunks.
	           * @return {StreamHelper} the current helper.
	           */
	          pause: function () {
	            this._worker.pause();

	            return this;
	          },

	          /**
	           * Return a nodejs stream for this helper.
	           * @param {Function} updateCb the update callback.
	           * @return {NodejsStreamOutputAdapter} the nodejs stream.
	           */
	          toNodejsStream: function (updateCb) {
	            utils.checkSupport("nodestream");

	            if (this._outputType !== "nodebuffer") {
	              // an object stream containing blob/arraybuffer/uint8array/string
	              // is strange and I don't know if it would be useful.
	              // I you find this comment and have a good usecase, please open a
	              // bug report !
	              throw new Error(this._outputType + " is not supported by this method");
	            }

	            return new NodejsStreamOutputAdapter(this, {
	              objectMode: this._outputType !== "nodebuffer"
	            }, updateCb);
	          }
	        };
	        module.exports = StreamHelper;
	      }, {
	        "../base64": 1,
	        "../external": 6,
	        "../nodejs/NodejsStreamOutputAdapter": 13,
	        "../support": 30,
	        "../utils": 32,
	        "./ConvertWorker": 24,
	        "./GenericWorker": 28
	      }],
	      30: [function (require, module, exports) {

	        exports.base64 = true;
	        exports.array = true;
	        exports.string = true;
	        exports.arraybuffer = typeof ArrayBuffer !== "undefined" && typeof Uint8Array !== "undefined";
	        exports.nodebuffer = typeof Buffer !== "undefined"; // contains true if JSZip can read/generate Uint8Array, false otherwise.

	        exports.uint8array = typeof Uint8Array !== "undefined";

	        if (typeof ArrayBuffer === "undefined") {
	          exports.blob = false;
	        } else {
	          var buffer = new ArrayBuffer(0);

	          try {
	            exports.blob = new Blob([buffer], {
	              type: "application/zip"
	            }).size === 0;
	          } catch (e) {
	            try {
	              var Builder = self.BlobBuilder || self.WebKitBlobBuilder || self.MozBlobBuilder || self.MSBlobBuilder;
	              var builder = new Builder();
	              builder.append(buffer);
	              exports.blob = builder.getBlob('application/zip').size === 0;
	            } catch (e) {
	              exports.blob = false;
	            }
	          }
	        }

	        try {
	          exports.nodestream = !!require('readable-stream').Readable;
	        } catch (e) {
	          exports.nodestream = false;
	        }
	      }, {
	        "readable-stream": 16
	      }],
	      31: [function (require, module, exports) {

	        var utils = require('./utils');

	        var support = require('./support');

	        var nodejsUtils = require('./nodejsUtils');

	        var GenericWorker = require('./stream/GenericWorker');
	        /**
	         * The following functions come from pako, from pako/lib/utils/strings
	         * released under the MIT license, see pako https://github.com/nodeca/pako/
	         */
	        // 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


	        var _utf8len = new Array(256);

	        for (var i = 0; i < 256; i++) {
	          _utf8len[i] = i >= 252 ? 6 : i >= 248 ? 5 : i >= 240 ? 4 : i >= 224 ? 3 : i >= 192 ? 2 : 1;
	        }

	        _utf8len[254] = _utf8len[254] = 1; // Invalid sequence start
	        // convert string to array (typed, when possible)

	        var string2buf = function (str) {
	          var 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


	          if (support.uint8array) {
	            buf = new Uint8Array(buf_len);
	          } else {
	            buf = new Array(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;
	        }; // 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 = function (buf, max) {
	          var pos;
	          max = max || buf.length;

	          if (max > buf.length) {
	            max = buf.length;
	          } // go back from last position, until start of sequence found


	          pos = max - 1;

	          while (pos >= 0 && (buf[pos] & 0xC0) === 0x80) {
	            pos--;
	          } // Fuckup - 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 vuffer is too small,
	          // return max too.


	          if (pos === 0) {
	            return max;
	          }

	          return pos + _utf8len[buf[pos]] > max ? pos : max;
	        }; // convert array to string


	        var buf2string = function (buf) {
	          var i, out, c, c_len;
	          var len = buf.length; // Reserve max possible length (2 words per char)
	          // NB: by unknown reasons, Array is significantly faster for
	          //     String.fromCharCode.apply than Uint16Array.

	          var utf16buf = new Array(len * 2);

	          for (out = 0, i = 0; i < len;) {
	            c = buf[i++]; // quick process ascii

	            if (c < 0x80) {
	              utf16buf[out++] = c;
	              continue;
	            }

	            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;
	            }
	          } // shrinkBuf(utf16buf, out)


	          if (utf16buf.length !== out) {
	            if (utf16buf.subarray) {
	              utf16buf = utf16buf.subarray(0, out);
	            } else {
	              utf16buf.length = out;
	            }
	          } // return String.fromCharCode.apply(null, utf16buf);


	          return utils.applyFromCharCode(utf16buf);
	        }; // That's all for the pako functions.

	        /**
	         * Transform a javascript string into an array (typed if possible) of bytes,
	         * UTF-8 encoded.
	         * @param {String} str the string to encode
	         * @return {Array|Uint8Array|Buffer} the UTF-8 encoded string.
	         */


	        exports.utf8encode = function utf8encode(str) {
	          if (support.nodebuffer) {
	            return nodejsUtils.newBufferFrom(str, "utf-8");
	          }

	          return string2buf(str);
	        };
	        /**
	         * Transform a bytes array (or a representation) representing an UTF-8 encoded
	         * string into a javascript string.
	         * @param {Array|Uint8Array|Buffer} buf the data de decode
	         * @return {String} the decoded string.
	         */


	        exports.utf8decode = function utf8decode(buf) {
	          if (support.nodebuffer) {
	            return utils.transformTo("nodebuffer", buf).toString("utf-8");
	          }

	          buf = utils.transformTo(support.uint8array ? "uint8array" : "array", buf);
	          return buf2string(buf);
	        };
	        /**
	         * A worker to decode utf8 encoded binary chunks into string chunks.
	         * @constructor
	         */


	        function Utf8DecodeWorker() {
	          GenericWorker.call(this, "utf-8 decode"); // the last bytes if a chunk didn't end with a complete codepoint.

	          this.leftOver = null;
	        }

	        utils.inherits(Utf8DecodeWorker, GenericWorker);
	        /**
	         * @see GenericWorker.processChunk
	         */

	        Utf8DecodeWorker.prototype.processChunk = function (chunk) {
	          var data = utils.transformTo(support.uint8array ? "uint8array" : "array", chunk.data); // 1st step, re-use what's left of the previous chunk

	          if (this.leftOver && this.leftOver.length) {
	            if (support.uint8array) {
	              var previousData = data;
	              data = new Uint8Array(previousData.length + this.leftOver.length);
	              data.set(this.leftOver, 0);
	              data.set(previousData, this.leftOver.length);
	            } else {
	              data = this.leftOver.concat(data);
	            }

	            this.leftOver = null;
	          }

	          var nextBoundary = utf8border(data);
	          var usableData = data;

	          if (nextBoundary !== data.length) {
	            if (support.uint8array) {
	              usableData = data.subarray(0, nextBoundary);
	              this.leftOver = data.subarray(nextBoundary, data.length);
	            } else {
	              usableData = data.slice(0, nextBoundary);
	              this.leftOver = data.slice(nextBoundary, data.length);
	            }
	          }

	          this.push({
	            data: exports.utf8decode(usableData),
	            meta: chunk.meta
	          });
	        };
	        /**
	         * @see GenericWorker.flush
	         */


	        Utf8DecodeWorker.prototype.flush = function () {
	          if (this.leftOver && this.leftOver.length) {
	            this.push({
	              data: exports.utf8decode(this.leftOver),
	              meta: {}
	            });
	            this.leftOver = null;
	          }
	        };

	        exports.Utf8DecodeWorker = Utf8DecodeWorker;
	        /**
	         * A worker to endcode string chunks into utf8 encoded binary chunks.
	         * @constructor
	         */

	        function Utf8EncodeWorker() {
	          GenericWorker.call(this, "utf-8 encode");
	        }

	        utils.inherits(Utf8EncodeWorker, GenericWorker);
	        /**
	         * @see GenericWorker.processChunk
	         */

	        Utf8EncodeWorker.prototype.processChunk = function (chunk) {
	          this.push({
	            data: exports.utf8encode(chunk.data),
	            meta: chunk.meta
	          });
	        };

	        exports.Utf8EncodeWorker = Utf8EncodeWorker;
	      }, {
	        "./nodejsUtils": 14,
	        "./stream/GenericWorker": 28,
	        "./support": 30,
	        "./utils": 32
	      }],
	      32: [function (require, module, exports) {

	        var support = require('./support');

	        var base64 = require('./base64');

	        var nodejsUtils = require('./nodejsUtils');

	        var setImmediate = require('set-immediate-shim');

	        var external = require("./external");
	        /**
	         * Convert a string that pass as a "binary string": it should represent a byte
	         * array but may have > 255 char codes. Be sure to take only the first byte
	         * and returns the byte array.
	         * @param {String} str the string to transform.
	         * @return {Array|Uint8Array} the string in a binary format.
	         */


	        function string2binary(str) {
	          var result = null;

	          if (support.uint8array) {
	            result = new Uint8Array(str.length);
	          } else {
	            result = new Array(str.length);
	          }

	          return stringToArrayLike(str, result);
	        }
	        /**
	         * Create a new blob with the given content and the given type.
	         * @param {String|ArrayBuffer} part the content to put in the blob. DO NOT use
	         * an Uint8Array because the stock browser of android 4 won't accept it (it
	         * will be silently converted to a string, "[object Uint8Array]").
	         *
	         * Use only ONE part to build the blob to avoid a memory leak in IE11 / Edge:
	         * when a large amount of Array is used to create the Blob, the amount of
	         * memory consumed is nearly 100 times the original data amount.
	         *
	         * @param {String} type the mime type of the blob.
	         * @return {Blob} the created blob.
	         */


	        exports.newBlob = function (part, type) {
	          exports.checkSupport("blob");

	          try {
	            // Blob constructor
	            return new Blob([part], {
	              type: type
	            });
	          } catch (e) {
	            try {
	              // deprecated, browser only, old way
	              var Builder = self.BlobBuilder || self.WebKitBlobBuilder || self.MozBlobBuilder || self.MSBlobBuilder;
	              var builder = new Builder();
	              builder.append(part);
	              return builder.getBlob(type);
	            } catch (e) {
	              // well, fuck ?!
	              throw new Error("Bug : can't construct the Blob.");
	            }
	          }
	        };
	        /**
	         * The identity function.
	         * @param {Object} input the input.
	         * @return {Object} the same input.
	         */


	        function identity(input) {
	          return input;
	        }
	        /**
	         * Fill in an array with a string.
	         * @param {String} str the string to use.
	         * @param {Array|ArrayBuffer|Uint8Array|Buffer} array the array to fill in (will be mutated).
	         * @return {Array|ArrayBuffer|Uint8Array|Buffer} the updated array.
	         */


	        function stringToArrayLike(str, array) {
	          for (var i = 0; i < str.length; ++i) {
	            array[i] = str.charCodeAt(i) & 0xFF;
	          }

	          return array;
	        }
	        /**
	         * An helper for the function arrayLikeToString.
	         * This contains static information and functions that
	         * can be optimized by the browser JIT compiler.
	         */


	        var arrayToStringHelper = {
	          /**
	           * Transform an array of int into a string, chunk by chunk.
	           * See the performances notes on arrayLikeToString.
	           * @param {Array|ArrayBuffer|Uint8Array|Buffer} array the array to transform.
	           * @param {String} type the type of the array.
	           * @param {Integer} chunk the chunk size.
	           * @return {String} the resulting string.
	           * @throws Error if the chunk is too big for the stack.
	           */
	          stringifyByChunk: function (array, type, chunk) {
	            var result = [],
	                k = 0,
	                len = array.length; // shortcut

	            if (len <= chunk) {
	              return String.fromCharCode.apply(null, array);
	            }

	            while (k < len) {
	              if (type === "array" || type === "nodebuffer") {
	                result.push(String.fromCharCode.apply(null, array.slice(k, Math.min(k + chunk, len))));
	              } else {
	                result.push(String.fromCharCode.apply(null, array.subarray(k, Math.min(k + chunk, len))));
	              }

	              k += chunk;
	            }

	            return result.join("");
	          },

	          /**
	           * Call String.fromCharCode on every item in the array.
	           * This is the naive implementation, which generate A LOT of intermediate string.
	           * This should be used when everything else fail.
	           * @param {Array|ArrayBuffer|Uint8Array|Buffer} array the array to transform.
	           * @return {String} the result.
	           */
	          stringifyByChar: function (array) {
	            var resultStr = "";

	            for (var i = 0; i < array.length; i++) {
	              resultStr += String.fromCharCode(array[i]);
	            }

	            return resultStr;
	          },
	          applyCanBeUsed: {
	            /**
	             * true if the browser accepts to use String.fromCharCode on Uint8Array
	             */
	            uint8array: function () {
	              try {
	                return support.uint8array && String.fromCharCode.apply(null, new Uint8Array(1)).length === 1;
	              } catch (e) {
	                return false;
	              }
	            }(),

	            /**
	             * true if the browser accepts to use String.fromCharCode on nodejs Buffer.
	             */
	            nodebuffer: function () {
	              try {
	                return support.nodebuffer && String.fromCharCode.apply(null, nodejsUtils.allocBuffer(1)).length === 1;
	              } catch (e) {
	                return false;
	              }
	            }()
	          }
	        };
	        /**
	         * Transform an array-like object to a string.
	         * @param {Array|ArrayBuffer|Uint8Array|Buffer} array the array to transform.
	         * @return {String} the result.
	         */

	        function arrayLikeToString(array) {
	          // Performances notes :
	          // --------------------
	          // String.fromCharCode.apply(null, array) is the fastest, see
	          // see http://jsperf.com/converting-a-uint8array-to-a-string/2
	          // but the stack is limited (and we can get huge arrays !).
	          //
	          // result += String.fromCharCode(array[i]); generate too many strings !
	          //
	          // This code is inspired by http://jsperf.com/arraybuffer-to-string-apply-performance/2
	          // TODO : we now have workers that split the work. Do we still need that ?
	          var chunk = 65536,
	              type = exports.getTypeOf(array),
	              canUseApply = true;

	          if (type === "uint8array") {
	            canUseApply = arrayToStringHelper.applyCanBeUsed.uint8array;
	          } else if (type === "nodebuffer") {
	            canUseApply = arrayToStringHelper.applyCanBeUsed.nodebuffer;
	          }

	          if (canUseApply) {
	            while (chunk > 1) {
	              try {
	                return arrayToStringHelper.stringifyByChunk(array, type, chunk);
	              } catch (e) {
	                chunk = Math.floor(chunk / 2);
	              }
	            }
	          } // no apply or chunk error : slow and painful algorithm
	          // default browser on android 4.*


	          return arrayToStringHelper.stringifyByChar(array);
	        }

	        exports.applyFromCharCode = arrayLikeToString;
	        /**
	         * Copy the data from an array-like to an other array-like.
	         * @param {Array|ArrayBuffer|Uint8Array|Buffer} arrayFrom the origin array.
	         * @param {Array|ArrayBuffer|Uint8Array|Buffer} arrayTo the destination array which will be mutated.
	         * @return {Array|ArrayBuffer|Uint8Array|Buffer} the updated destination array.
	         */

	        function arrayLikeToArrayLike(arrayFrom, arrayTo) {
	          for (var i = 0; i < arrayFrom.length; i++) {
	            arrayTo[i] = arrayFrom[i];
	          }

	          return arrayTo;
	        } // a matrix containing functions to transform everything into everything.


	        var transform = {}; // string to ?

	        transform["string"] = {
	          "string": identity,
	          "array": function (input) {
	            return stringToArrayLike(input, new Array(input.length));
	          },
	          "arraybuffer": function (input) {
	            return transform["string"]["uint8array"](input).buffer;
	          },
	          "uint8array": function (input) {
	            return stringToArrayLike(input, new Uint8Array(input.length));
	          },
	          "nodebuffer": function (input) {
	            return stringToArrayLike(input, nodejsUtils.allocBuffer(input.length));
	          }
	        }; // array to ?

	        transform["array"] = {
	          "string": arrayLikeToString,
	          "array": identity,
	          "arraybuffer": function (input) {
	            return new Uint8Array(input).buffer;
	          },
	          "uint8array": function (input) {
	            return new Uint8Array(input);
	          },
	          "nodebuffer": function (input) {
	            return nodejsUtils.newBufferFrom(input);
	          }
	        }; // arraybuffer to ?

	        transform["arraybuffer"] = {
	          "string": function (input) {
	            return arrayLikeToString(new Uint8Array(input));
	          },
	          "array": function (input) {
	            return arrayLikeToArrayLike(new Uint8Array(input), new Array(input.byteLength));
	          },
	          "arraybuffer": identity,
	          "uint8array": function (input) {
	            return new Uint8Array(input);
	          },
	          "nodebuffer": function (input) {
	            return nodejsUtils.newBufferFrom(new Uint8Array(input));
	          }
	        }; // uint8array to ?

	        transform["uint8array"] = {
	          "string": arrayLikeToString,
	          "array": function (input) {
	            return arrayLikeToArrayLike(input, new Array(input.length));
	          },
	          "arraybuffer": function (input) {
	            return input.buffer;
	          },
	          "uint8array": identity,
	          "nodebuffer": function (input) {
	            return nodejsUtils.newBufferFrom(input);
	          }
	        }; // nodebuffer to ?

	        transform["nodebuffer"] = {
	          "string": arrayLikeToString,
	          "array": function (input) {
	            return arrayLikeToArrayLike(input, new Array(input.length));
	          },
	          "arraybuffer": function (input) {
	            return transform["nodebuffer"]["uint8array"](input).buffer;
	          },
	          "uint8array": function (input) {
	            return arrayLikeToArrayLike(input, new Uint8Array(input.length));
	          },
	          "nodebuffer": identity
	        };
	        /**
	         * Transform an input into any type.
	         * The supported output type are : string, array, uint8array, arraybuffer, nodebuffer.
	         * If no output type is specified, the unmodified input will be returned.
	         * @param {String} outputType the output type.
	         * @param {String|Array|ArrayBuffer|Uint8Array|Buffer} input the input to convert.
	         * @throws {Error} an Error if the browser doesn't support the requested output type.
	         */

	        exports.transformTo = function (outputType, input) {
	          if (!input) {
	            // undefined, null, etc
	            // an empty string won't harm.
	            input = "";
	          }

	          if (!outputType) {
	            return input;
	          }

	          exports.checkSupport(outputType);
	          var inputType = exports.getTypeOf(input);
	          var result = transform[inputType][outputType](input);
	          return result;
	        };
	        /**
	         * Resolve all relative path components, "." and "..", in a path. If these relative components
	         * traverse above the root then the resulting path will only contain the final path component.
	         *
	         * All empty components, e.g. "//", are removed.
	         * @param {string} path A path with / or \ separators
	         * @returns {string} The path with all relative path components resolved.
	         */


	        exports.resolve = function (path) {
	          var parts = path.split("/");
	          var result = [];

	          for (var index = 0; index < parts.length; index++) {
	            var part = parts[index]; // Allow the first and last component to be empty for trailing slashes.

	            if (part === "." || part === "" && index !== 0 && index !== parts.length - 1) {
	              continue;
	            } else if (part === "..") {
	              result.pop();
	            } else {
	              result.push(part);
	            }
	          }

	          return result.join("/");
	        };
	        /**
	         * Return the type of the input.
	         * The type will be in a format valid for JSZip.utils.transformTo : string, array, uint8array, arraybuffer.
	         * @param {Object} input the input to identify.
	         * @return {String} the (lowercase) type of the input.
	         */


	        exports.getTypeOf = function (input) {
	          if (typeof input === "string") {
	            return "string";
	          }

	          if (Object.prototype.toString.call(input) === "[object Array]") {
	            return "array";
	          }

	          if (support.nodebuffer && nodejsUtils.isBuffer(input)) {
	            return "nodebuffer";
	          }

	          if (support.uint8array && input instanceof Uint8Array) {
	            return "uint8array";
	          }

	          if (support.arraybuffer && input instanceof ArrayBuffer) {
	            return "arraybuffer";
	          }
	        };
	        /**
	         * Throw an exception if the type is not supported.
	         * @param {String} type the type to check.
	         * @throws {Error} an Error if the browser doesn't support the requested type.
	         */


	        exports.checkSupport = function (type) {
	          var supported = support[type.toLowerCase()];

	          if (!supported) {
	            throw new Error(type + " is not supported by this platform");
	          }
	        };

	        exports.MAX_VALUE_16BITS = 65535;
	        exports.MAX_VALUE_32BITS = -1; // well, "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF" is parsed as -1

	        /**
	         * Prettify a string read as binary.
	         * @param {string} str the string to prettify.
	         * @return {string} a pretty string.
	         */

	        exports.pretty = function (str) {
	          var res = '',
	              code,
	              i;

	          for (i = 0; i < (str || "").length; i++) {
	            code = str.charCodeAt(i);
	            res += '\\x' + (code < 16 ? "0" : "") + code.toString(16).toUpperCase();
	          }

	          return res;
	        };
	        /**
	         * Defer the call of a function.
	         * @param {Function} callback the function to call asynchronously.
	         * @param {Array} args the arguments to give to the callback.
	         */


	        exports.delay = function (callback, args, self) {
	          setImmediate(function () {
	            callback.apply(self || null, args || []);
	          });
	        };
	        /**
	         * Extends a prototype with an other, without calling a constructor with
	         * side effects. Inspired by nodejs' `utils.inherits`
	         * @param {Function} ctor the constructor to augment
	         * @param {Function} superCtor the parent constructor to use
	         */


	        exports.inherits = function (ctor, superCtor) {
	          var Obj = function () {};

	          Obj.prototype = superCtor.prototype;
	          ctor.prototype = new Obj();
	        };
	        /**
	         * Merge the objects passed as parameters into a new one.
	         * @private
	         * @param {...Object} var_args All objects to merge.
	         * @return {Object} a new object with the data of the others.
	         */


	        exports.extend = function () {
	          var result = {},
	              i,
	              attr;

	          for (i = 0; i < arguments.length; i++) {
	            // arguments is not enumerable in some browsers
	            for (attr in arguments[i]) {
	              if (arguments[i].hasOwnProperty(attr) && typeof result[attr] === "undefined") {
	                result[attr] = arguments[i][attr];
	              }
	            }
	          }

	          return result;
	        };
	        /**
	         * Transform arbitrary content into a Promise.
	         * @param {String} name a name for the content being processed.
	         * @param {Object} inputData the content to process.
	         * @param {Boolean} isBinary true if the content is not an unicode string
	         * @param {Boolean} isOptimizedBinaryString true if the string content only has one byte per character.
	         * @param {Boolean} isBase64 true if the string content is encoded with base64.
	         * @return {Promise} a promise in a format usable by JSZip.
	         */


	        exports.prepareContent = function (name, inputData, isBinary, isOptimizedBinaryString, isBase64) {
	          // if inputData is already a promise, this flatten it.
	          var promise = external.Promise.resolve(inputData).then(function (data) {
	            var isBlob = support.blob && (data instanceof Blob || ['[object File]', '[object Blob]'].indexOf(Object.prototype.toString.call(data)) !== -1);

	            if (isBlob && typeof FileReader !== "undefined") {
	              return new external.Promise(function (resolve, reject) {
	                var reader = new FileReader();

	                reader.onload = function (e) {
	                  resolve(e.target.result);
	                };

	                reader.onerror = function (e) {
	                  reject(e.target.error);
	                };

	                reader.readAsArrayBuffer(data);
	              });
	            } else {
	              return data;
	            }
	          });
	          return promise.then(function (data) {
	            var dataType = exports.getTypeOf(data);

	            if (!dataType) {
	              return external.Promise.reject(new Error("Can't read the data of '" + name + "'. Is it " + "in a supported JavaScript type (String, Blob, ArrayBuffer, etc) ?"));
	            } // special case : it's way easier to work with Uint8Array than with ArrayBuffer


	            if (dataType === "arraybuffer") {
	              data = exports.transformTo("uint8array", data);
	            } else if (dataType === "string") {
	              if (isBase64) {
	                data = base64.decode(data);
	              } else if (isBinary) {
	                // optimizedBinaryString === true means that the file has already been filtered with a 0xFF mask
	                if (isOptimizedBinaryString !== true) {
	                  // this is a string, not in a base64 format.
	                  // Be sure that this is a correct "binary string"
	                  data = string2binary(data);
	                }
	              }
	            }

	            return data;
	          });
	        };
	      }, {
	        "./base64": 1,
	        "./external": 6,
	        "./nodejsUtils": 14,
	        "./support": 30,
	        "set-immediate-shim": 54
	      }],
	      33: [function (require, module, exports) {

	        var readerFor = require('./reader/readerFor');

	        var utils = require('./utils');

	        var sig = require('./signature');

	        var ZipEntry = require('./zipEntry');

	        require('./utf8');

	        var support = require('./support'); //  class ZipEntries {{{

	        /**
	         * All the entries in the zip file.
	         * @constructor
	         * @param {Object} loadOptions Options for loading the stream.
	         */


	        function ZipEntries(loadOptions) {
	          this.files = [];
	          this.loadOptions = loadOptions;
	        }

	        ZipEntries.prototype = {
	          /**
	           * Check that the reader is on the specified signature.
	           * @param {string} expectedSignature the expected signature.
	           * @throws {Error} if it is an other signature.
	           */
	          checkSignature: function (expectedSignature) {
	            if (!this.reader.readAndCheckSignature(expectedSignature)) {
	              this.reader.index -= 4;
	              var signature = this.reader.readString(4);
	              throw new Error("Corrupted zip or bug: unexpected signature " + "(" + utils.pretty(signature) + ", expected " + utils.pretty(expectedSignature) + ")");
	            }
	          },

	          /**
	           * Check if the given signature is at the given index.
	           * @param {number} askedIndex the index to check.
	           * @param {string} expectedSignature the signature to expect.
	           * @return {boolean} true if the signature is here, false otherwise.
	           */
	          isSignature: function (askedIndex, expectedSignature) {
	            var currentIndex = this.reader.index;
	            this.reader.setIndex(askedIndex);
	            var signature = this.reader.readString(4);
	            var result = signature === expectedSignature;
	            this.reader.setIndex(currentIndex);
	            return result;
	          },

	          /**
	           * Read the end of the central directory.
	           */
	          readBlockEndOfCentral: function () {
	            this.diskNumber = this.reader.readInt(2);
	            this.diskWithCentralDirStart = this.reader.readInt(2);
	            this.centralDirRecordsOnThisDisk = this.reader.readInt(2);
	            this.centralDirRecords = this.reader.readInt(2);
	            this.centralDirSize = this.reader.readInt(4);
	            this.centralDirOffset = this.reader.readInt(4);
	            this.zipCommentLength = this.reader.readInt(2); // warning : the encoding depends of the system locale
	            // On a linux machine with LANG=en_US.utf8, this field is utf8 encoded.
	            // On a windows machine, this field is encoded with the localized windows code page.

	            var zipComment = this.reader.readData(this.zipCommentLength);
	            var decodeParamType = support.uint8array ? "uint8array" : "array"; // To get consistent behavior with the generation part, we will assume that
	            // this is utf8 encoded unless specified otherwise.

	            var decodeContent = utils.transformTo(decodeParamType, zipComment);
	            this.zipComment = this.loadOptions.decodeFileName(decodeContent);
	          },

	          /**
	           * Read the end of the Zip 64 central directory.
	           * Not merged with the method readEndOfCentral :
	           * The end of central can coexist with its Zip64 brother,
	           * I don't want to read the wrong number of bytes !
	           */
	          readBlockZip64EndOfCentral: function () {
	            this.zip64EndOfCentralSize = this.reader.readInt(8);
	            this.reader.skip(4); // this.versionMadeBy = this.reader.readString(2);
	            // this.versionNeeded = this.reader.readInt(2);

	            this.diskNumber = this.reader.readInt(4);
	            this.diskWithCentralDirStart = this.reader.readInt(4);
	            this.centralDirRecordsOnThisDisk = this.reader.readInt(8);
	            this.centralDirRecords = this.reader.readInt(8);
	            this.centralDirSize = this.reader.readInt(8);
	            this.centralDirOffset = this.reader.readInt(8);
	            this.zip64ExtensibleData = {};
	            var extraDataSize = this.zip64EndOfCentralSize - 44,
	                index = 0,
	                extraFieldId,
	                extraFieldLength,
	                extraFieldValue;

	            while (index < extraDataSize) {
	              extraFieldId = this.reader.readInt(2);
	              extraFieldLength = this.reader.readInt(4);
	              extraFieldValue = this.reader.readData(extraFieldLength);
	              this.zip64ExtensibleData[extraFieldId] = {
	                id: extraFieldId,
	                length: extraFieldLength,
	                value: extraFieldValue
	              };
	            }
	          },

	          /**
	           * Read the end of the Zip 64 central directory locator.
	           */
	          readBlockZip64EndOfCentralLocator: function () {
	            this.diskWithZip64CentralDirStart = this.reader.readInt(4);
	            this.relativeOffsetEndOfZip64CentralDir = this.reader.readInt(8);
	            this.disksCount = this.reader.readInt(4);

	            if (this.disksCount > 1) {
	              throw new Error("Multi-volumes zip are not supported");
	            }
	          },

	          /**
	           * Read the local files, based on the offset read in the central part.
	           */
	          readLocalFiles: function () {
	            var i, file;

	            for (i = 0; i < this.files.length; i++) {
	              file = this.files[i];
	              this.reader.setIndex(file.localHeaderOffset);
	              this.checkSignature(sig.LOCAL_FILE_HEADER);
	              file.readLocalPart(this.reader);
	              file.handleUTF8();
	              file.processAttributes();
	            }
	          },

	          /**
	           * Read the central directory.
	           */
	          readCentralDir: function () {
	            var file;
	            this.reader.setIndex(this.centralDirOffset);

	            while (this.reader.readAndCheckSignature(sig.CENTRAL_FILE_HEADER)) {
	              file = new ZipEntry({
	                zip64: this.zip64
	              }, this.loadOptions);
	              file.readCentralPart(this.reader);
	              this.files.push(file);
	            }

	            if (this.centralDirRecords !== this.files.length) {
	              if (this.centralDirRecords !== 0 && this.files.length === 0) {
	                // We expected some records but couldn't find ANY.
	                // This is really suspicious, as if something went wrong.
	                throw new Error("Corrupted zip or bug: expected " + this.centralDirRecords + " records in central dir, got " + this.files.length);
	              }
	            }
	          },

	          /**
	           * Read the end of central directory.
	           */
	          readEndOfCentral: function () {
	            var offset = this.reader.lastIndexOfSignature(sig.CENTRAL_DIRECTORY_END);

	            if (offset < 0) {
	              // Check if the content is a truncated zip or complete garbage.
	              // A "LOCAL_FILE_HEADER" is not required at the beginning (auto
	              // extractible zip for example) but it can give a good hint.
	              // If an ajax request was used without responseType, we will also
	              // get unreadable data.
	              var isGarbage = !this.isSignature(0, sig.LOCAL_FILE_HEADER);

	              if (isGarbage) {
	                throw new Error("Can't find end of central directory : is this a zip file ? " + "If it is, see https://stuk.github.io/jszip/documentation/howto/read_zip.html");
	              } else {
	                throw new Error("Corrupted zip: can't find end of central directory");
	              }
	            }

	            this.reader.setIndex(offset);
	            var endOfCentralDirOffset = offset;
	            this.checkSignature(sig.CENTRAL_DIRECTORY_END);
	            this.readBlockEndOfCentral();
	            /* extract from the zip spec :
	                4)  If one of the fields in the end of central directory
	                    record is too small to hold required data, the field
	                    should be set to -1 (0xFFFF or 0xFFFFFFFF) and the
	                    ZIP64 format record should be created.
	                5)  The end of central directory record and the
	                    Zip64 end of central directory locator record must
	                    reside on the same disk when splitting or spanning
	                    an archive.
	             */

	            if (this.diskNumber === utils.MAX_VALUE_16BITS || this.diskWithCentralDirStart === utils.MAX_VALUE_16BITS || this.centralDirRecordsOnThisDisk === utils.MAX_VALUE_16BITS || this.centralDirRecords === utils.MAX_VALUE_16BITS || this.centralDirSize === utils.MAX_VALUE_32BITS || this.centralDirOffset === utils.MAX_VALUE_32BITS) {
	              this.zip64 = true;
	              /*
	              Warning : the zip64 extension is supported, but ONLY if the 64bits integer read from
	              the zip file can fit into a 32bits integer. This cannot be solved : JavaScript represents
	              all numbers as 64-bit double precision IEEE 754 floating point numbers.
	              So, we have 53bits for integers and bitwise operations treat everything as 32bits.
	              see https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Operators/Bitwise_Operators
	              and http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-262.pdf section 8.5
	              */
	              // should look for a zip64 EOCD locator

	              offset = this.reader.lastIndexOfSignature(sig.ZIP64_CENTRAL_DIRECTORY_LOCATOR);

	              if (offset < 0) {
	                throw new Error("Corrupted zip: can't find the ZIP64 end of central directory locator");
	              }

	              this.reader.setIndex(offset);
	              this.checkSignature(sig.ZIP64_CENTRAL_DIRECTORY_LOCATOR);
	              this.readBlockZip64EndOfCentralLocator(); // now the zip64 EOCD record

	              if (!this.isSignature(this.relativeOffsetEndOfZip64CentralDir, sig.ZIP64_CENTRAL_DIRECTORY_END)) {
	                // console.warn("ZIP64 end of central directory not where expected.");
	                this.relativeOffsetEndOfZip64CentralDir = this.reader.lastIndexOfSignature(sig.ZIP64_CENTRAL_DIRECTORY_END);

	                if (this.relativeOffsetEndOfZip64CentralDir < 0) {
	                  throw new Error("Corrupted zip: can't find the ZIP64 end of central directory");
	                }
	              }

	              this.reader.setIndex(this.relativeOffsetEndOfZip64CentralDir);
	              this.checkSignature(sig.ZIP64_CENTRAL_DIRECTORY_END);
	              this.readBlockZip64EndOfCentral();
	            }

	            var expectedEndOfCentralDirOffset = this.centralDirOffset + this.centralDirSize;

	            if (this.zip64) {
	              expectedEndOfCentralDirOffset += 20; // end of central dir 64 locator

	              expectedEndOfCentralDirOffset += 12
	              /* should not include the leading 12 bytes */
	              + this.zip64EndOfCentralSize;
	            }

	            var extraBytes = endOfCentralDirOffset - expectedEndOfCentralDirOffset;

	            if (extraBytes > 0) {
	              // console.warn(extraBytes, "extra bytes at beginning or within zipfile");
	              if (this.isSignature(endOfCentralDirOffset, sig.CENTRAL_FILE_HEADER)) ; else {
	                // the offset is wrong, update the "zero" of the reader
	                // this happens if data has been prepended (crx files for example)
	                this.reader.zero = extraBytes;
	              }
	            } else if (extraBytes < 0) {
	              throw new Error("Corrupted zip: missing " + Math.abs(extraBytes) + " bytes.");
	            }
	          },
	          prepareReader: function (data) {
	            this.reader = readerFor(data);
	          },

	          /**
	           * Read a zip file and create ZipEntries.
	           * @param {String|ArrayBuffer|Uint8Array|Buffer} data the binary string representing a zip file.
	           */
	          load: function (data) {
	            this.prepareReader(data);
	            this.readEndOfCentral();
	            this.readCentralDir();
	            this.readLocalFiles();
	          }
	        }; // }}} end of ZipEntries

	        module.exports = ZipEntries;
	      }, {
	        "./reader/readerFor": 22,
	        "./signature": 23,
	        "./support": 30,
	        "./utf8": 31,
	        "./utils": 32,
	        "./zipEntry": 34
	      }],
	      34: [function (require, module, exports) {

	        var readerFor = require('./reader/readerFor');

	        var utils = require('./utils');

	        var CompressedObject = require('./compressedObject');

	        var crc32fn = require('./crc32');

	        var utf8 = require('./utf8');

	        var compressions = require('./compressions');

	        var support = require('./support');

	        var MADE_BY_DOS = 0x00;
	        var MADE_BY_UNIX = 0x03;
	        /**
	         * Find a compression registered in JSZip.
	         * @param {string} compressionMethod the method magic to find.
	         * @return {Object|null} the JSZip compression object, null if none found.
	         */

	        var findCompression = function (compressionMethod) {
	          for (var method in compressions) {
	            if (!compressions.hasOwnProperty(method)) {
	              continue;
	            }

	            if (compressions[method].magic === compressionMethod) {
	              return compressions[method];
	            }
	          }

	          return null;
	        }; // class ZipEntry {{{

	        /**
	         * An entry in the zip file.
	         * @constructor
	         * @param {Object} options Options of the current file.
	         * @param {Object} loadOptions Options for loading the stream.
	         */


	        function ZipEntry(options, loadOptions) {
	          this.options = options;
	          this.loadOptions = loadOptions;
	        }

	        ZipEntry.prototype = {
	          /**
	           * say if the file is encrypted.
	           * @return {boolean} true if the file is encrypted, false otherwise.
	           */
	          isEncrypted: function () {
	            // bit 1 is set
	            return (this.bitFlag & 0x0001) === 0x0001;
	          },

	          /**
	           * say if the file has utf-8 filename/comment.
	           * @return {boolean} true if the filename/comment is in utf-8, false otherwise.
	           */
	          useUTF8: function () {
	            // bit 11 is set
	            return (this.bitFlag & 0x0800) === 0x0800;
	          },

	          /**
	           * Read the local part of a zip file and add the info in this object.
	           * @param {DataReader} reader the reader to use.
	           */
	          readLocalPart: function (reader) {
	            var compression, localExtraFieldsLength; // we already know everything from the central dir !
	            // If the central dir data are false, we are doomed.
	            // On the bright side, the local part is scary  : zip64, data descriptors, both, etc.
	            // The less data we get here, the more reliable this should be.
	            // Let's skip the whole header and dash to the data !

	            reader.skip(22); // in some zip created on windows, the filename stored in the central dir contains \ instead of /.
	            // Strangely, the filename here is OK.
	            // I would love to treat these zip files as corrupted (see http://www.info-zip.org/FAQ.html#backslashes
	            // or APPNOTE#4.4.17.1, "All slashes MUST be forward slashes '/'") but there are a lot of bad zip generators...
	            // Search "unzip mismatching "local" filename continuing with "central" filename version" on
	            // the internet.
	            //
	            // I think I see the logic here : the central directory is used to display
	            // content and the local directory is used to extract the files. Mixing / and \
	            // may be used to display \ to windows users and use / when extracting the files.
	            // Unfortunately, this lead also to some issues : http://seclists.org/fulldisclosure/2009/Sep/394

	            this.fileNameLength = reader.readInt(2);
	            localExtraFieldsLength = reader.readInt(2); // can't be sure this will be the same as the central dir
	            // the fileName is stored as binary data, the handleUTF8 method will take care of the encoding.

	            this.fileName = reader.readData(this.fileNameLength);
	            reader.skip(localExtraFieldsLength);

	            if (this.compressedSize === -1 || this.uncompressedSize === -1) {
	              throw new Error("Bug or corrupted zip : didn't get enough information from the central directory " + "(compressedSize === -1 || uncompressedSize === -1)");
	            }

	            compression = findCompression(this.compressionMethod);

	            if (compression === null) {
	              // no compression found
	              throw new Error("Corrupted zip : compression " + utils.pretty(this.compressionMethod) + " unknown (inner file : " + utils.transformTo("string", this.fileName) + ")");
	            }

	            this.decompressed = new CompressedObject(this.compressedSize, this.uncompressedSize, this.crc32, compression, reader.readData(this.compressedSize));
	          },

	          /**
	           * Read the central part of a zip file and add the info in this object.
	           * @param {DataReader} reader the reader to use.
	           */
	          readCentralPart: function (reader) {
	            this.versionMadeBy = reader.readInt(2);
	            reader.skip(2); // this.versionNeeded = reader.readInt(2);

	            this.bitFlag = reader.readInt(2);
	            this.compressionMethod = reader.readString(2);
	            this.date = reader.readDate();
	            this.crc32 = reader.readInt(4);
	            this.compressedSize = reader.readInt(4);
	            this.uncompressedSize = reader.readInt(4);
	            var fileNameLength = reader.readInt(2);
	            this.extraFieldsLength = reader.readInt(2);
	            this.fileCommentLength = reader.readInt(2);
	            this.diskNumberStart = reader.readInt(2);
	            this.internalFileAttributes = reader.readInt(2);
	            this.externalFileAttributes = reader.readInt(4);
	            this.localHeaderOffset = reader.readInt(4);

	            if (this.isEncrypted()) {
	              throw new Error("Encrypted zip are not supported");
	            } // will be read in the local part, see the comments there


	            reader.skip(fileNameLength);
	            this.readExtraFields(reader);
	            this.parseZIP64ExtraField(reader);
	            this.fileComment = reader.readData(this.fileCommentLength);
	          },

	          /**
	           * Parse the external file attributes and get the unix/dos permissions.
	           */
	          processAttributes: function () {
	            this.unixPermissions = null;
	            this.dosPermissions = null;
	            var madeBy = this.versionMadeBy >> 8; // Check if we have the DOS directory flag set.
	            // We look for it in the DOS and UNIX permissions
	            // but some unknown platform could set it as a compatibility flag.

	            this.dir = this.externalFileAttributes & 0x0010 ? true : false;

	            if (madeBy === MADE_BY_DOS) {
	              // first 6 bits (0 to 5)
	              this.dosPermissions = this.externalFileAttributes & 0x3F;
	            }

	            if (madeBy === MADE_BY_UNIX) {
	              this.unixPermissions = this.externalFileAttributes >> 16 & 0xFFFF; // the octal permissions are in (this.unixPermissions & 0x01FF).toString(8);
	            } // fail safe : if the name ends with a / it probably means a folder


	            if (!this.dir && this.fileNameStr.slice(-1) === '/') {
	              this.dir = true;
	            }
	          },

	          /**
	           * Parse the ZIP64 extra field and merge the info in the current ZipEntry.
	           * @param {DataReader} reader the reader to use.
	           */
	          parseZIP64ExtraField: function (reader) {
	            if (!this.extraFields[0x0001]) {
	              return;
	            } // should be something, preparing the extra reader


	            var extraReader = readerFor(this.extraFields[0x0001].value); // I really hope that these 64bits integer can fit in 32 bits integer, because js
	            // won't let us have more.

	            if (this.uncompressedSize === utils.MAX_VALUE_32BITS) {
	              this.uncompressedSize = extraReader.readInt(8);
	            }

	            if (this.compressedSize === utils.MAX_VALUE_32BITS) {
	              this.compressedSize = extraReader.readInt(8);
	            }

	            if (this.localHeaderOffset === utils.MAX_VALUE_32BITS) {
	              this.localHeaderOffset = extraReader.readInt(8);
	            }

	            if (this.diskNumberStart === utils.MAX_VALUE_32BITS) {
	              this.diskNumberStart = extraReader.readInt(4);
	            }
	          },

	          /**
	           * Read the central part of a zip file and add the info in this object.
	           * @param {DataReader} reader the reader to use.
	           */
	          readExtraFields: function (reader) {
	            var end = reader.index + this.extraFieldsLength,
	                extraFieldId,
	                extraFieldLength,
	                extraFieldValue;

	            if (!this.extraFields) {
	              this.extraFields = {};
	            }

	            while (reader.index + 4 < end) {
	              extraFieldId = reader.readInt(2);
	              extraFieldLength = reader.readInt(2);
	              extraFieldValue = reader.readData(extraFieldLength);
	              this.extraFields[extraFieldId] = {
	                id: extraFieldId,
	                length: extraFieldLength,
	                value: extraFieldValue
	              };
	            }

	            reader.setIndex(end);
	          },

	          /**
	           * Apply an UTF8 transformation if needed.
	           */
	          handleUTF8: function () {
	            var decodeParamType = support.uint8array ? "uint8array" : "array";

	            if (this.useUTF8()) {
	              this.fileNameStr = utf8.utf8decode(this.fileName);
	              this.fileCommentStr = utf8.utf8decode(this.fileComment);
	            } else {
	              var upath = this.findExtraFieldUnicodePath();

	              if (upath !== null) {
	                this.fileNameStr = upath;
	              } else {
	                // ASCII text or unsupported code page
	                var fileNameByteArray = utils.transformTo(decodeParamType, this.fileName);
	                this.fileNameStr = this.loadOptions.decodeFileName(fileNameByteArray);
	              }

	              var ucomment = this.findExtraFieldUnicodeComment();

	              if (ucomment !== null) {
	                this.fileCommentStr = ucomment;
	              } else {
	                // ASCII text or unsupported code page
	                var commentByteArray = utils.transformTo(decodeParamType, this.fileComment);
	                this.fileCommentStr = this.loadOptions.decodeFileName(commentByteArray);
	              }
	            }
	          },

	          /**
	           * Find the unicode path declared in the extra field, if any.
	           * @return {String} the unicode path, null otherwise.
	           */
	          findExtraFieldUnicodePath: function () {
	            var upathField = this.extraFields[0x7075];

	            if (upathField) {
	              var extraReader = readerFor(upathField.value); // wrong version

	              if (extraReader.readInt(1) !== 1) {
	                return null;
	              } // the crc of the filename changed, this field is out of date.


	              if (crc32fn(this.fileName) !== extraReader.readInt(4)) {
	                return null;
	              }

	              return utf8.utf8decode(extraReader.readData(upathField.length - 5));
	            }

	            return null;
	          },

	          /**
	           * Find the unicode comment declared in the extra field, if any.
	           * @return {String} the unicode comment, null otherwise.
	           */
	          findExtraFieldUnicodeComment: function () {
	            var ucommentField = this.extraFields[0x6375];

	            if (ucommentField) {
	              var extraReader = readerFor(ucommentField.value); // wrong version

	              if (extraReader.readInt(1) !== 1) {
	                return null;
	              } // the crc of the comment changed, this field is out of date.


	              if (crc32fn(this.fileComment) !== extraReader.readInt(4)) {
	                return null;
	              }

	              return utf8.utf8decode(extraReader.readData(ucommentField.length - 5));
	            }

	            return null;
	          }
	        };
	        module.exports = ZipEntry;
	      }, {
	        "./compressedObject": 2,
	        "./compressions": 3,
	        "./crc32": 4,
	        "./reader/readerFor": 22,
	        "./support": 30,
	        "./utf8": 31,
	        "./utils": 32
	      }],
	      35: [function (require, module, exports) {

	        var StreamHelper = require('./stream/StreamHelper');

	        var DataWorker = require('./stream/DataWorker');

	        var utf8 = require('./utf8');

	        var CompressedObject = require('./compressedObject');

	        var GenericWorker = require('./stream/GenericWorker');
	        /**
	         * A simple object representing a file in the zip file.
	         * @constructor
	         * @param {string} name the name of the file
	         * @param {String|ArrayBuffer|Uint8Array|Buffer} data the data
	         * @param {Object} options the options of the file
	         */


	        var ZipObject = function (name, data, options) {
	          this.name = name;
	          this.dir = options.dir;
	          this.date = options.date;
	          this.comment = options.comment;
	          this.unixPermissions = options.unixPermissions;
	          this.dosPermissions = options.dosPermissions;
	          this._data = data;
	          this._dataBinary = options.binary; // keep only the compression

	          this.options = {
	            compression: options.compression,
	            compressionOptions: options.compressionOptions
	          };
	        };

	        ZipObject.prototype = {
	          /**
	           * Create an internal stream for the content of this object.
	           * @param {String} type the type of each chunk.
	           * @return StreamHelper the stream.
	           */
	          internalStream: function (type) {
	            var result = null,
	                outputType = "string";

	            try {
	              if (!type) {
	                throw new Error("No output type specified.");
	              }

	              outputType = type.toLowerCase();
	              var askUnicodeString = outputType === "string" || outputType === "text";

	              if (outputType === "binarystring" || outputType === "text") {
	                outputType = "string";
	              }

	              result = this._decompressWorker();
	              var isUnicodeString = !this._dataBinary;

	              if (isUnicodeString && !askUnicodeString) {
	                result = result.pipe(new utf8.Utf8EncodeWorker());
	              }

	              if (!isUnicodeString && askUnicodeString) {
	                result = result.pipe(new utf8.Utf8DecodeWorker());
	              }
	            } catch (e) {
	              result = new GenericWorker("error");
	              result.error(e);
	            }

	            return new StreamHelper(result, outputType, "");
	          },

	          /**
	           * Prepare the content in the asked type.
	           * @param {String} type the type of the result.
	           * @param {Function} onUpdate a function to call on each internal update.
	           * @return Promise the promise of the result.
	           */
	          async: function (type, onUpdate) {
	            return this.internalStream(type).accumulate(onUpdate);
	          },

	          /**
	           * Prepare the content as a nodejs stream.
	           * @param {String} type the type of each chunk.
	           * @param {Function} onUpdate a function to call on each internal update.
	           * @return Stream the stream.
	           */
	          nodeStream: function (type, onUpdate) {
	            return this.internalStream(type || "nodebuffer").toNodejsStream(onUpdate);
	          },

	          /**
	           * Return a worker for the compressed content.
	           * @private
	           * @param {Object} compression the compression object to use.
	           * @param {Object} compressionOptions the options to use when compressing.
	           * @return Worker the worker.
	           */
	          _compressWorker: function (compression, compressionOptions) {
	            if (this._data instanceof CompressedObject && this._data.compression.magic === compression.magic) {
	              return this._data.getCompressedWorker();
	            } else {
	              var result = this._decompressWorker();

	              if (!this._dataBinary) {
	                result = result.pipe(new utf8.Utf8EncodeWorker());
	              }

	              return CompressedObject.createWorkerFrom(result, compression, compressionOptions);
	            }
	          },

	          /**
	           * Return a worker for the decompressed content.
	           * @private
	           * @return Worker the worker.
	           */
	          _decompressWorker: function () {
	            if (this._data instanceof CompressedObject) {
	              return this._data.getContentWorker();
	            } else if (this._data instanceof GenericWorker) {
	              return this._data;
	            } else {
	              return new DataWorker(this._data);
	            }
	          }
	        };
	        var removedMethods = ["asText", "asBinary", "asNodeBuffer", "asUint8Array", "asArrayBuffer"];

	        var removedFn = function () {
	          throw new Error("This method has been removed in JSZip 3.0, please check the upgrade guide.");
	        };

	        for (var i = 0; i < removedMethods.length; i++) {
	          ZipObject.prototype[removedMethods[i]] = removedFn;
	        }

	        module.exports = ZipObject;
	      }, {
	        "./compressedObject": 2,
	        "./stream/DataWorker": 27,
	        "./stream/GenericWorker": 28,
	        "./stream/StreamHelper": 29,
	        "./utf8": 31
	      }],
	      36: [function (require, module, exports) {
	        (function (global) {

	          var Mutation = global.MutationObserver || global.WebKitMutationObserver;
	          var scheduleDrain;
	          {
	            if (Mutation) {
	              var called = 0;
	              var observer = new Mutation(nextTick);
	              var element = global.document.createTextNode('');
	              observer.observe(element, {
	                characterData: true
	              });

	              scheduleDrain = function () {
	                element.data = called = ++called % 2;
	              };
	            } else if (!global.setImmediate && typeof global.MessageChannel !== 'undefined') {
	              var channel = new global.MessageChannel();
	              channel.port1.onmessage = nextTick;

	              scheduleDrain = function () {
	                channel.port2.postMessage(0);
	              };
	            } else if ('document' in global && 'onreadystatechange' in global.document.createElement('script')) {
	              scheduleDrain = function () {
	                // Create a <script> element; its readystatechange event will be fired asynchronously once it is inserted
	                // into the document. Do so, thus queuing up the task. Remember to clean up once it's been called.
	                var scriptEl = global.document.createElement('script');

	                scriptEl.onreadystatechange = function () {
	                  nextTick();
	                  scriptEl.onreadystatechange = null;
	                  scriptEl.parentNode.removeChild(scriptEl);
	                  scriptEl = null;
	                };

	                global.document.documentElement.appendChild(scriptEl);
	              };
	            } else {
	              scheduleDrain = function () {
	                setTimeout(nextTick, 0);
	              };
	            }
	          }
	          var draining;
	          var queue = []; //named nextTick for less confusing stack traces

	          function nextTick() {
	            draining = true;
	            var i, oldQueue;
	            var len = queue.length;

	            while (len) {
	              oldQueue = queue;
	              queue = [];
	              i = -1;

	              while (++i < len) {
	                oldQueue[i]();
	              }

	              len = queue.length;
	            }

	            draining = false;
	          }

	          module.exports = immediate;

	          function immediate(task) {
	            if (queue.push(task) === 1 && !draining) {
	              scheduleDrain();
	            }
	          }
	        }).call(this, typeof commonjsGlobal !== "undefined" ? commonjsGlobal : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {});
	      }, {}],
	      37: [function (require, module, exports) {

	        var immediate = require('immediate');
	        /* istanbul ignore next */


	        function INTERNAL() {}

	        var handlers = {};
	        var REJECTED = ['REJECTED'];
	        var FULFILLED = ['FULFILLED'];
	        var PENDING = ['PENDING'];
	        module.exports = Promise;

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

	          this.state = PENDING;
	          this.queue = [];
	          this.outcome = void 0;

	          if (resolver !== INTERNAL) {
	            safelyResolveThenable(this, resolver);
	          }
	        }

	        Promise.prototype["finally"] = function (callback) {
	          if (typeof callback !== 'function') {
	            return this;
	          }

	          var p = this.constructor;
	          return this.then(resolve, reject);

	          function resolve(value) {
	            function yes() {
	              return value;
	            }

	            return p.resolve(callback()).then(yes);
	          }

	          function reject(reason) {
	            function no() {
	              throw reason;
	            }

	            return p.resolve(callback()).then(no);
	          }
	        };

	        Promise.prototype["catch"] = function (onRejected) {
	          return this.then(null, onRejected);
	        };

	        Promise.prototype.then = function (onFulfilled, onRejected) {
	          if (typeof onFulfilled !== 'function' && this.state === FULFILLED || typeof onRejected !== 'function' && this.state === REJECTED) {
	            return this;
	          }

	          var promise = new this.constructor(INTERNAL);

	          if (this.state !== PENDING) {
	            var resolver = this.state === FULFILLED ? onFulfilled : onRejected;
	            unwrap(promise, resolver, this.outcome);
	          } else {
	            this.queue.push(new QueueItem(promise, onFulfilled, onRejected));
	          }

	          return promise;
	        };

	        function QueueItem(promise, onFulfilled, onRejected) {
	          this.promise = promise;

	          if (typeof onFulfilled === 'function') {
	            this.onFulfilled = onFulfilled;
	            this.callFulfilled = this.otherCallFulfilled;
	          }

	          if (typeof onRejected === 'function') {
	            this.onRejected = onRejected;
	            this.callRejected = this.otherCallRejected;
	          }
	        }

	        QueueItem.prototype.callFulfilled = function (value) {
	          handlers.resolve(this.promise, value);
	        };

	        QueueItem.prototype.otherCallFulfilled = function (value) {
	          unwrap(this.promise, this.onFulfilled, value);
	        };

	        QueueItem.prototype.callRejected = function (value) {
	          handlers.reject(this.promise, value);
	        };

	        QueueItem.prototype.otherCallRejected = function (value) {
	          unwrap(this.promise, this.onRejected, value);
	        };

	        function unwrap(promise, func, value) {
	          immediate(function () {
	            var returnValue;

	            try {
	              returnValue = func(value);
	            } catch (e) {
	              return handlers.reject(promise, e);
	            }

	            if (returnValue === promise) {
	              handlers.reject(promise, new TypeError('Cannot resolve promise with itself'));
	            } else {
	              handlers.resolve(promise, returnValue);
	            }
	          });
	        }

	        handlers.resolve = function (self, value) {
	          var result = tryCatch(getThen, value);

	          if (result.status === 'error') {
	            return handlers.reject(self, result.value);
	          }

	          var thenable = result.value;

	          if (thenable) {
	            safelyResolveThenable(self, thenable);
	          } else {
	            self.state = FULFILLED;
	            self.outcome = value;
	            var i = -1;
	            var len = self.queue.length;

	            while (++i < len) {
	              self.queue[i].callFulfilled(value);
	            }
	          }

	          return self;
	        };

	        handlers.reject = function (self, error) {
	          self.state = REJECTED;
	          self.outcome = error;
	          var i = -1;
	          var len = self.queue.length;

	          while (++i < len) {
	            self.queue[i].callRejected(error);
	          }

	          return self;
	        };

	        function getThen(obj) {
	          // Make sure we only access the accessor once as required by the spec
	          var then = obj && obj.then;

	          if (obj && (typeof obj === 'object' || typeof obj === 'function') && typeof then === 'function') {
	            return function appyThen() {
	              then.apply(obj, arguments);
	            };
	          }
	        }

	        function safelyResolveThenable(self, thenable) {
	          // Either fulfill, reject or reject with error
	          var called = false;

	          function onError(value) {
	            if (called) {
	              return;
	            }

	            called = true;
	            handlers.reject(self, value);
	          }

	          function onSuccess(value) {
	            if (called) {
	              return;
	            }

	            called = true;
	            handlers.resolve(self, value);
	          }

	          function tryToUnwrap() {
	            thenable(onSuccess, onError);
	          }

	          var result = tryCatch(tryToUnwrap);

	          if (result.status === 'error') {
	            onError(result.value);
	          }
	        }

	        function tryCatch(func, value) {
	          var out = {};

	          try {
	            out.value = func(value);
	            out.status = 'success';
	          } catch (e) {
	            out.status = 'error';
	            out.value = e;
	          }

	          return out;
	        }

	        Promise.resolve = resolve;

	        function resolve(value) {
	          if (value instanceof this) {
	            return value;
	          }

	          return handlers.resolve(new this(INTERNAL), value);
	        }

	        Promise.reject = reject;

	        function reject(reason) {
	          var promise = new this(INTERNAL);
	          return handlers.reject(promise, reason);
	        }

	        Promise.all = all;

	        function all(iterable) {
	          var self = this;

	          if (Object.prototype.toString.call(iterable) !== '[object Array]') {
	            return this.reject(new TypeError('must be an array'));
	          }

	          var len = iterable.length;
	          var called = false;

	          if (!len) {
	            return this.resolve([]);
	          }

	          var values = new Array(len);
	          var resolved = 0;
	          var i = -1;
	          var promise = new this(INTERNAL);

	          while (++i < len) {
	            allResolver(iterable[i], i);
	          }

	          return promise;

	          function allResolver(value, i) {
	            self.resolve(value).then(resolveFromAll, function (error) {
	              if (!called) {
	                called = true;
	                handlers.reject(promise, error);
	              }
	            });

	            function resolveFromAll(outValue) {
	              values[i] = outValue;

	              if (++resolved === len && !called) {
	                called = true;
	                handlers.resolve(promise, values);
	              }
	            }
	          }
	        }

	        Promise.race = race;

	        function race(iterable) {
	          var self = this;

	          if (Object.prototype.toString.call(iterable) !== '[object Array]') {
	            return this.reject(new TypeError('must be an array'));
	          }

	          var len = iterable.length;
	          var called = false;

	          if (!len) {
	            return this.resolve([]);
	          }

	          var i = -1;
	          var promise = new this(INTERNAL);

	          while (++i < len) {
	            resolver(iterable[i]);
	          }

	          return promise;

	          function resolver(value) {
	            self.resolve(value).then(function (response) {
	              if (!called) {
	                called = true;
	                handlers.resolve(promise, response);
	              }
	            }, function (error) {
	              if (!called) {
	                called = true;
	                handlers.reject(promise, error);
	              }
	            });
	          }
	        }
	      }, {
	        "immediate": 36
	      }],
	      38: [function (require, module, exports) {

	        var assign = require('./lib/utils/common').assign;

	        var deflate = require('./lib/deflate');

	        var inflate = require('./lib/inflate');

	        var constants = require('./lib/zlib/constants');

	        var pako = {};
	        assign(pako, deflate, inflate, constants);
	        module.exports = pako;
	      }, {
	        "./lib/deflate": 39,
	        "./lib/inflate": 40,
	        "./lib/utils/common": 41,
	        "./lib/zlib/constants": 44
	      }],
	      39: [function (require, module, exports) {

	        var zlib_deflate = require('./zlib/deflate');

	        var utils = require('./utils/common');

	        var strings = require('./utils/strings');

	        var msg = require('./zlib/messages');

	        var ZStream = require('./zlib/zstream');

	        var toString = Object.prototype.toString;
	        /* Public constants ==========================================================*/

	        /* ===========================================================================*/

	        var Z_NO_FLUSH = 0;
	        var Z_FINISH = 4;
	        var Z_OK = 0;
	        var Z_STREAM_END = 1;
	        var Z_SYNC_FLUSH = 2;
	        var Z_DEFAULT_COMPRESSION = -1;
	        var Z_DEFAULT_STRATEGY = 0;
	        var Z_DEFLATED = 8;
	        /* ===========================================================================*/

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

	        /**
	         * Deflate.result -> Uint8Array|Array
	         *
	         * 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)  or if you
	         * push a chunk with explicit flush (call [[Deflate#push]] with
	         * `Z_SYNC_FLUSH` 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
	         * - `to` (String) - if equal to 'string', then result will be "binary string"
	         *    (each char code [0..255])
	         * - `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
	         * var pako = require('pako')
	         *   , chunk1 = Uint8Array([1,2,3,4,5,6,7,8,9])
	         *   , chunk2 = Uint8Array([10,11,12,13,14,15,16,17,18,19]);
	         *
	         * var 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(options) {
	          if (!(this instanceof Deflate)) return new Deflate(options);
	          this.options = utils.assign({
	            level: Z_DEFAULT_COMPRESSION,
	            method: Z_DEFLATED,
	            chunkSize: 16384,
	            windowBits: 15,
	            memLevel: 8,
	            strategy: Z_DEFAULT_STRATEGY,
	            to: ''
	          }, options || {});
	          var 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;
	          var status = zlib_deflate.deflateInit2(this.strm, opt.level, opt.method, opt.windowBits, opt.memLevel, opt.strategy);

	          if (status !== Z_OK) {
	            throw new Error(msg[status]);
	          }

	          if (opt.header) {
	            zlib_deflate.deflateSetHeader(this.strm, opt.header);
	          }

	          if (opt.dictionary) {
	            var 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.call(opt.dictionary) === '[object ArrayBuffer]') {
	              dict = new Uint8Array(opt.dictionary);
	            } else {
	              dict = opt.dictionary;
	            }

	            status = zlib_deflate.deflateSetDictionary(this.strm, dict);

	            if (status !== Z_OK) {
	              throw new Error(msg[status]);
	            }

	            this._dict_set = true;
	          }
	        }
	        /**
	         * Deflate#push(data[, mode]) -> Boolean
	         * - data (Uint8Array|Array|ArrayBuffer|String): input data. Strings will be
	         *   converted to utf8 byte sequence.
	         * - mode (Number|Boolean): 0..6 for corresponding Z_NO_FLUSH..Z_TREE modes.
	         *   See constants. Skipped or `false` means Z_NO_FLUSH, `true` meansh 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
	         * mode Z_FINISH (or `true`). That will flush internal pending buffers and call
	         * [[Deflate#onEnd]]. For interim explicit flushes (without ending the stream) you
	         * can use mode Z_SYNC_FLUSH, keeping the compression context.
	         *
	         * On fail call [[Deflate#onEnd]] with error code and return false.
	         *
	         * We strongly recommend to use `Uint8Array` on input for best speed (output
	         * array format is detected automatically). Also, don't skip last param and always
	         * use the same type in your code (boolean or number). That will improve JS speed.
	         *
	         * For regular `Array`-s make sure all elements are [0..255].
	         *
	         * ##### Example
	         *
	         * ```javascript
	         * push(chunk, false); // push one of data chunks
	         * ...
	         * push(chunk, true);  // push last chunk
	         * ```
	         **/


	        Deflate.prototype.push = function (data, mode) {
	          var strm = this.strm;
	          var chunkSize = this.options.chunkSize;

	          var status, _mode;

	          if (this.ended) {
	            return false;
	          }

	          _mode = mode === ~~mode ? mode : mode === true ? Z_FINISH : Z_NO_FLUSH; // 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.call(data) === '[object ArrayBuffer]') {
	            strm.input = new Uint8Array(data);
	          } else {
	            strm.input = data;
	          }

	          strm.next_in = 0;
	          strm.avail_in = strm.input.length;

	          do {
	            if (strm.avail_out === 0) {
	              strm.output = new utils.Buf8(chunkSize);
	              strm.next_out = 0;
	              strm.avail_out = chunkSize;
	            }

	            status = zlib_deflate.deflate(strm, _mode);
	            /* no bad return value */

	            if (status !== Z_STREAM_END && status !== Z_OK) {
	              this.onEnd(status);
	              this.ended = true;
	              return false;
	            }

	            if (strm.avail_out === 0 || strm.avail_in === 0 && (_mode === Z_FINISH || _mode === Z_SYNC_FLUSH)) {
	              if (this.options.to === 'string') {
	                this.onData(strings.buf2binstring(utils.shrinkBuf(strm.output, strm.next_out)));
	              } else {
	                this.onData(utils.shrinkBuf(strm.output, strm.next_out));
	              }
	            }
	          } while ((strm.avail_in > 0 || strm.avail_out === 0) && status !== Z_STREAM_END); // Finalize on the last chunk.


	          if (_mode === Z_FINISH) {
	            status = zlib_deflate.deflateEnd(this.strm);
	            this.onEnd(status);
	            this.ended = true;
	            return status === Z_OK;
	          } // callback interim results if Z_SYNC_FLUSH.


	          if (_mode === Z_SYNC_FLUSH) {
	            this.onEnd(Z_OK);
	            strm.avail_out = 0;
	            return true;
	          }

	          return true;
	        };
	        /**
	         * Deflate#onData(chunk) -> Void
	         * - chunk (Uint8Array|Array|String): ouput data. Type of array depends
	         *   on js engine support. 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.
	         **/


	        Deflate.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) or should be flushed (Z_SYNC_FLUSH)
	         * or if an error happened. By default - join collected chunks,
	         * free memory and fill `results` / `err` properties.
	         **/


	        Deflate.prototype.onEnd = function (status) {
	          // On success - join
	          if (status === Z_OK) {
	            if (this.options.to === 'string') {
	              this.result = this.chunks.join('');
	            } else {
	              this.result = utils.flattenChunks(this.chunks);
	            }
	          }

	          this.chunks = [];
	          this.err = status;
	          this.msg = this.strm.msg;
	        };
	        /**
	         * deflate(data[, options]) -> Uint8Array|Array|String
	         * - data (Uint8Array|Array|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.
	         * - `to` (String) - if equal to 'string', then result will be "binary string"
	         *    (each char code [0..255])
	         *
	         * ##### Example:
	         *
	         * ```javascript
	         * var pako = require('pako')
	         *   , data = Uint8Array([1,2,3,4,5,6,7,8,9]);
	         *
	         * console.log(pako.deflate(data));
	         * ```
	         **/


	        function deflate(input, options) {
	          var deflator = new Deflate(options);
	          deflator.push(input, true); // That will never happens, if you don't cheat with options :)

	          if (deflator.err) {
	            throw deflator.msg || msg[deflator.err];
	          }

	          return deflator.result;
	        }
	        /**
	         * deflateRaw(data[, options]) -> Uint8Array|Array|String
	         * - data (Uint8Array|Array|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(input, options) {
	          options = options || {};
	          options.raw = true;
	          return deflate(input, options);
	        }
	        /**
	         * gzip(data[, options]) -> Uint8Array|Array|String
	         * - data (Uint8Array|Array|String): input data to compress.
	         * - options (Object): zlib deflate options.
	         *
	         * The same as [[deflate]], but create gzip wrapper instead of
	         * deflate one.
	         **/


	        function gzip(input, options) {
	          options = options || {};
	          options.gzip = true;
	          return deflate(input, options);
	        }

	        exports.Deflate = Deflate;
	        exports.deflate = deflate;
	        exports.deflateRaw = deflateRaw;
	        exports.gzip = gzip;
	      }, {
	        "./utils/common": 41,
	        "./utils/strings": 42,
	        "./zlib/deflate": 46,
	        "./zlib/messages": 51,
	        "./zlib/zstream": 53
	      }],
	      40: [function (require, module, exports) {

	        var zlib_inflate = require('./zlib/inflate');

	        var utils = require('./utils/common');

	        var strings = require('./utils/strings');

	        var c = require('./zlib/constants');

	        var msg = require('./zlib/messages');

	        var ZStream = require('./zlib/zstream');

	        var GZheader = require('./zlib/gzheader');

	        var toString = Object.prototype.toString;
	        /**
	         * 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 overriden.
	         **/

	        /**
	         * Inflate.result -> Uint8Array|Array|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) or if you
	         * push a chunk with explicit flush (call [[Inflate#push]] with
	         * `Z_SYNC_FLUSH` 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
	         * var pako = require('pako')
	         *   , chunk1 = Uint8Array([1,2,3,4,5,6,7,8,9])
	         *   , chunk2 = Uint8Array([10,11,12,13,14,15,16,17,18,19]);
	         *
	         * var 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(options) {
	          if (!(this instanceof Inflate)) return new Inflate(options);
	          this.options = utils.assign({
	            chunkSize: 16384,
	            windowBits: 0,
	            to: ''
	          }, options || {});
	          var 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;
	          var status = zlib_inflate.inflateInit2(this.strm, opt.windowBits);

	          if (status !== c.Z_OK) {
	            throw new Error(msg[status]);
	          }

	          this.header = new GZheader();
	          zlib_inflate.inflateGetHeader(this.strm, this.header);
	        }
	        /**
	         * Inflate#push(data[, mode]) -> Boolean
	         * - data (Uint8Array|Array|ArrayBuffer|String): input data
	         * - mode (Number|Boolean): 0..6 for corresponding Z_NO_FLUSH..Z_TREE modes.
	         *   See constants. Skipped or `false` means Z_NO_FLUSH, `true` meansh Z_FINISH.
	         *
	         * Sends input data to inflate pipe, generating [[Inflate#onData]] calls with
	         * new output chunks. Returns `true` on success. The last data block must have
	         * mode Z_FINISH (or `true`). That will flush internal pending buffers and call
	         * [[Inflate#onEnd]]. For interim explicit flushes (without ending the stream) you
	         * can use mode Z_SYNC_FLUSH, keeping the decompression context.
	         *
	         * On fail call [[Inflate#onEnd]] with error code and return false.
	         *
	         * We strongly recommend to use `Uint8Array` on input for best speed (output
	         * format is detected automatically). Also, don't skip last param and always
	         * use the same type in your code (boolean or number). That will improve JS speed.
	         *
	         * For regular `Array`-s make sure all elements are [0..255].
	         *
	         * ##### Example
	         *
	         * ```javascript
	         * push(chunk, false); // push one of data chunks
	         * ...
	         * push(chunk, true);  // push last chunk
	         * ```
	         **/


	        Inflate.prototype.push = function (data, mode) {
	          var strm = this.strm;
	          var chunkSize = this.options.chunkSize;
	          var dictionary = this.options.dictionary;

	          var status, _mode;

	          var next_out_utf8, tail, utf8str;
	          var dict; // Flag to properly process Z_BUF_ERROR on testing inflate call
	          // when we check that all output data was flushed.

	          var allowBufError = false;

	          if (this.ended) {
	            return false;
	          }

	          _mode = mode === ~~mode ? mode : mode === true ? c.Z_FINISH : c.Z_NO_FLUSH; // Convert data if needed

	          if (typeof data === 'string') {
	            // Only binary strings can be decompressed on practice
	            strm.input = strings.binstring2buf(data);
	          } else 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;

	          do {
	            if (strm.avail_out === 0) {
	              strm.output = new utils.Buf8(chunkSize);
	              strm.next_out = 0;
	              strm.avail_out = chunkSize;
	            }

	            status = zlib_inflate.inflate(strm, c.Z_NO_FLUSH);
	            /* no bad return value */

	            if (status === c.Z_NEED_DICT && dictionary) {
	              // Convert data if needed
	              if (typeof dictionary === 'string') {
	                dict = strings.string2buf(dictionary);
	              } else if (toString.call(dictionary) === '[object ArrayBuffer]') {
	                dict = new Uint8Array(dictionary);
	              } else {
	                dict = dictionary;
	              }

	              status = zlib_inflate.inflateSetDictionary(this.strm, dict);
	            }

	            if (status === c.Z_BUF_ERROR && allowBufError === true) {
	              status = c.Z_OK;
	              allowBufError = false;
	            }

	            if (status !== c.Z_STREAM_END && status !== c.Z_OK) {
	              this.onEnd(status);
	              this.ended = true;
	              return false;
	            }

	            if (strm.next_out) {
	              if (strm.avail_out === 0 || status === c.Z_STREAM_END || strm.avail_in === 0 && (_mode === c.Z_FINISH || _mode === c.Z_SYNC_FLUSH)) {
	                if (this.options.to === 'string') {
	                  next_out_utf8 = strings.utf8border(strm.output, strm.next_out);
	                  tail = strm.next_out - next_out_utf8;
	                  utf8str = strings.buf2string(strm.output, next_out_utf8); // move tail

	                  strm.next_out = tail;
	                  strm.avail_out = chunkSize - tail;

	                  if (tail) {
	                    utils.arraySet(strm.output, strm.output, next_out_utf8, tail, 0);
	                  }

	                  this.onData(utf8str);
	                } else {
	                  this.onData(utils.shrinkBuf(strm.output, strm.next_out));
	                }
	              }
	            } // When no more input data, we should check that internal inflate buffers
	            // are flushed. The only way to do it when avail_out = 0 - run one more
	            // inflate pass. But if output data not exists, inflate return Z_BUF_ERROR.
	            // Here we set flag to process this error properly.
	            //
	            // NOTE. Deflate does not return error in this case and does not needs such
	            // logic.


	            if (strm.avail_in === 0 && strm.avail_out === 0) {
	              allowBufError = true;
	            }
	          } while ((strm.avail_in > 0 || strm.avail_out === 0) && status !== c.Z_STREAM_END);

	          if (status === c.Z_STREAM_END) {
	            _mode = c.Z_FINISH;
	          } // Finalize on the last chunk.


	          if (_mode === c.Z_FINISH) {
	            status = zlib_inflate.inflateEnd(this.strm);
	            this.onEnd(status);
	            this.ended = true;
	            return status === c.Z_OK;
	          } // callback interim results if Z_SYNC_FLUSH.


	          if (_mode === c.Z_SYNC_FLUSH) {
	            this.onEnd(c.Z_OK);
	            strm.avail_out = 0;
	            return true;
	          }

	          return true;
	        };
	        /**
	         * Inflate#onData(chunk) -> Void
	         * - chunk (Uint8Array|Array|String): ouput data. Type of array depends
	         *   on js engine support. 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.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) or should be flushed (Z_SYNC_FLUSH)
	         * or if an error happened. By default - join collected chunks,
	         * free memory and fill `results` / `err` properties.
	         **/


	        Inflate.prototype.onEnd = function (status) {
	          // On success - join
	          if (status === c.Z_OK) {
	            if (this.options.to === 'string') {
	              // Glue & convert here, until we teach pako to send
	              // utf8 alligned strings to onData
	              this.result = this.chunks.join('');
	            } else {
	              this.result = utils.flattenChunks(this.chunks);
	            }
	          }

	          this.chunks = [];
	          this.err = status;
	          this.msg = this.strm.msg;
	        };
	        /**
	         * inflate(data[, options]) -> Uint8Array|Array|String
	         * - data (Uint8Array|Array|String): 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
	         * var pako = require('pako')
	         *   , input = pako.deflate([1,2,3,4,5,6,7,8,9])
	         *   , output;
	         *
	         * try {
	         *   output = pako.inflate(input);
	         * } catch (err)
	         *   console.log(err);
	         * }
	         * ```
	         **/


	        function inflate(input, options) {
	          var inflator = new Inflate(options);
	          inflator.push(input, true); // That will never happens, if you don't cheat with options :)

	          if (inflator.err) {
	            throw inflator.msg || msg[inflator.err];
	          }

	          return inflator.result;
	        }
	        /**
	         * inflateRaw(data[, options]) -> Uint8Array|Array|String
	         * - data (Uint8Array|Array|String): 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(input, options) {
	          options = options || {};
	          options.raw = true;
	          return inflate(input, options);
	        }
	        /**
	         * ungzip(data[, options]) -> Uint8Array|Array|String
	         * - data (Uint8Array|Array|String): input data to decompress.
	         * - options (Object): zlib inflate options.
	         *
	         * Just shortcut to [[inflate]], because it autodetects format
	         * by header.content. Done for convenience.
	         **/


	        exports.Inflate = Inflate;
	        exports.inflate = inflate;
	        exports.inflateRaw = inflateRaw;
	        exports.ungzip = inflate;
	      }, {
	        "./utils/common": 41,
	        "./utils/strings": 42,
	        "./zlib/constants": 44,
	        "./zlib/gzheader": 47,
	        "./zlib/inflate": 49,
	        "./zlib/messages": 51,
	        "./zlib/zstream": 53
	      }],
	      41: [function (require, module, exports) {

	        var TYPED_OK = typeof Uint8Array !== 'undefined' && typeof Uint16Array !== 'undefined' && typeof Int32Array !== 'undefined';

	        exports.assign = function (obj
	        /*from1, from2, from3, ...*/
	        ) {
	          var sources = Array.prototype.slice.call(arguments, 1);

	          while (sources.length) {
	            var source = sources.shift();

	            if (!source) {
	              continue;
	            }

	            if (typeof source !== 'object') {
	              throw new TypeError(source + 'must be non-object');
	            }

	            for (var p in source) {
	              if (source.hasOwnProperty(p)) {
	                obj[p] = source[p];
	              }
	            }
	          }

	          return obj;
	        }; // reduce buffer size, avoiding mem copy


	        exports.shrinkBuf = function (buf, size) {
	          if (buf.length === size) {
	            return buf;
	          }

	          if (buf.subarray) {
	            return buf.subarray(0, size);
	          }

	          buf.length = size;
	          return buf;
	        };

	        var fnTyped = {
	          arraySet: function (dest, src, src_offs, len, dest_offs) {
	            if (src.subarray && dest.subarray) {
	              dest.set(src.subarray(src_offs, src_offs + len), dest_offs);
	              return;
	            } // Fallback to ordinary array


	            for (var i = 0; i < len; i++) {
	              dest[dest_offs + i] = src[src_offs + i];
	            }
	          },
	          // Join array of chunks to single array.
	          flattenChunks: function (chunks) {
	            var i, l, len, pos, chunk, result; // calculate data length

	            len = 0;

	            for (i = 0, l = chunks.length; i < l; i++) {
	              len += chunks[i].length;
	            } // join chunks


	            result = new Uint8Array(len);
	            pos = 0;

	            for (i = 0, l = chunks.length; i < l; i++) {
	              chunk = chunks[i];
	              result.set(chunk, pos);
	              pos += chunk.length;
	            }

	            return result;
	          }
	        };
	        var fnUntyped = {
	          arraySet: function (dest, src, src_offs, len, dest_offs) {
	            for (var i = 0; i < len; i++) {
	              dest[dest_offs + i] = src[src_offs + i];
	            }
	          },
	          // Join array of chunks to single array.
	          flattenChunks: function (chunks) {
	            return [].concat.apply([], chunks);
	          }
	        }; // Enable/Disable typed arrays use, for testing
	        //

	        exports.setTyped = function (on) {
	          if (on) {
	            exports.Buf8 = Uint8Array;
	            exports.Buf16 = Uint16Array;
	            exports.Buf32 = Int32Array;
	            exports.assign(exports, fnTyped);
	          } else {
	            exports.Buf8 = Array;
	            exports.Buf16 = Array;
	            exports.Buf32 = Array;
	            exports.assign(exports, fnUntyped);
	          }
	        };

	        exports.setTyped(TYPED_OK);
	      }, {}],
	      42: [function (require, module, exports) {

	        var utils = require('./common'); // 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 Safary
	        //


	        var STR_APPLY_OK = true;
	        var STR_APPLY_UIA_OK = true;

	        try {
	          String.fromCharCode.apply(null, [0]);
	        } catch (__) {
	          STR_APPLY_OK = false;
	        }

	        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


	        var _utf8len = new utils.Buf8(256);

	        for (var 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)

	        exports.string2buf = function (str) {
	          var 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 utils.Buf8(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 (used in 2 places)


	        function buf2binstring(buf, len) {
	          // use fallback for big arrays to avoid stack overflow
	          if (len < 65537) {
	            if (buf.subarray && STR_APPLY_UIA_OK || !buf.subarray && STR_APPLY_OK) {
	              return String.fromCharCode.apply(null, utils.shrinkBuf(buf, len));
	            }
	          }

	          var result = '';

	          for (var i = 0; i < len; i++) {
	            result += String.fromCharCode(buf[i]);
	          }

	          return result;
	        } // Convert byte array to binary string


	        exports.buf2binstring = function (buf) {
	          return buf2binstring(buf, buf.length);
	        }; // Convert binary string (typed, when possible)


	        exports.binstring2buf = function (str) {
	          var buf = new utils.Buf8(str.length);

	          for (var i = 0, len = buf.length; i < len; i++) {
	            buf[i] = str.charCodeAt(i);
	          }

	          return buf;
	        }; // convert array to string


	        exports.buf2string = function (buf, max) {
	          var i, out, c, c_len;
	          var len = max || buf.length; // Reserve max possible length (2 words per char)
	          // NB: by unknown reasons, Array is significantly faster for
	          //     String.fromCharCode.apply than Uint16Array.

	          var utf16buf = new Array(len * 2);

	          for (out = 0, i = 0; i < len;) {
	            c = buf[i++]; // quick process ascii

	            if (c < 0x80) {
	              utf16buf[out++] = c;
	              continue;
	            }

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


	        exports.utf8border = function (buf, max) {
	          var pos;
	          max = max || buf.length;

	          if (max > buf.length) {
	            max = buf.length;
	          } // go back from last position, until start of sequence found


	          pos = max - 1;

	          while (pos >= 0 && (buf[pos] & 0xC0) === 0x80) {
	            pos--;
	          } // Fuckup - 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 vuffer is too small,
	          // return max too.


	          if (pos === 0) {
	            return max;
	          }

	          return pos + _utf8len[buf[pos]] > max ? pos : max;
	        };
	      }, {
	        "./common": 41
	      }],
	      43: [function (require, module, exports) {
	        // It doesn't worth to make additional optimizationa 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.

	        function adler32(adler, buf, len, pos) {
	          var 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;
	        }

	        module.exports = adler32;
	      }, {}],
	      44: [function (require, module, exports) {
	        // (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.

	        module.exports = {
	          /* 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

	        };
	      }, {}],
	      45: [function (require, module, exports) {
	        // 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

	        function makeTable() {
	          var 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.


	        var crcTable = makeTable();

	        function crc32(crc, buf, len, pos) {
	          var t = crcTable,
	              end = pos + len;
	          crc ^= -1;

	          for (var i = pos; i < end; i++) {
	            crc = crc >>> 8 ^ t[(crc ^ buf[i]) & 0xFF];
	          }

	          return crc ^ -1; // >>> 0;
	        }

	        module.exports = crc32;
	      }, {}],
	      46: [function (require, module, exports) {
	        // (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 utils = require('../utils/common');

	        var trees = require('./trees');

	        var adler32 = require('./adler32');

	        var crc32 = require('./crc32');

	        var msg = require('./messages');
	        /* Public constants ==========================================================*/

	        /* ===========================================================================*/

	        /* Allowed flush values; see deflate() and inflate() below for details */


	        var Z_NO_FLUSH = 0;
	        var Z_PARTIAL_FLUSH = 1; //var Z_SYNC_FLUSH    = 2;

	        var Z_FULL_FLUSH = 3;
	        var Z_FINISH = 4;
	        var Z_BLOCK = 5; //var Z_TREES         = 6;

	        /* Return codes for the compression/decompression functions. Negative values
	         * are errors, positive values are used for special but normal events.
	         */

	        var Z_OK = 0;
	        var Z_STREAM_END = 1; //var Z_NEED_DICT     = 2;
	        //var Z_ERRNO         = -1;

	        var Z_STREAM_ERROR = -2;
	        var Z_DATA_ERROR = -3; //var Z_MEM_ERROR     = -4;

	        var Z_BUF_ERROR = -5; //var Z_VERSION_ERROR = -6;

	        /* compression levels */
	        //var Z_NO_COMPRESSION      = 0;
	        //var Z_BEST_SPEED          = 1;
	        //var Z_BEST_COMPRESSION    = 9;

	        var Z_DEFAULT_COMPRESSION = -1;
	        var Z_FILTERED = 1;
	        var Z_HUFFMAN_ONLY = 2;
	        var Z_RLE = 3;
	        var Z_FIXED = 4;
	        var Z_DEFAULT_STRATEGY = 0;
	        /* Possible values of the data_type field (though see inflate()) */
	        //var Z_BINARY              = 0;
	        //var Z_TEXT                = 1;
	        //var Z_ASCII               = 1; // = Z_TEXT

	        var Z_UNKNOWN = 2;
	        /* The deflate compression method */

	        var Z_DEFLATED = 8;
	        /*============================================================================*/

	        var MAX_MEM_LEVEL = 9;
	        /* Maximum value for memLevel in deflateInit2 */

	        var MAX_WBITS = 15;
	        /* 32K LZ77 window */

	        var DEF_MEM_LEVEL = 8;
	        var LENGTH_CODES = 29;
	        /* number of length codes, not counting the special END_BLOCK code */

	        var LITERALS = 256;
	        /* number of literal bytes 0..255 */

	        var L_CODES = LITERALS + 1 + LENGTH_CODES;
	        /* number of Literal or Length codes, including the END_BLOCK code */

	        var D_CODES = 30;
	        /* number of distance codes */

	        var BL_CODES = 19;
	        /* number of codes used to transfer the bit lengths */

	        var HEAP_SIZE = 2 * L_CODES + 1;
	        /* maximum heap size */

	        var MAX_BITS = 15;
	        /* All codes must not exceed MAX_BITS bits */

	        var MIN_MATCH = 3;
	        var MAX_MATCH = 258;
	        var MIN_LOOKAHEAD = MAX_MATCH + MIN_MATCH + 1;
	        var PRESET_DICT = 0x20;
	        var INIT_STATE = 42;
	        var EXTRA_STATE = 69;
	        var NAME_STATE = 73;
	        var COMMENT_STATE = 91;
	        var HCRC_STATE = 103;
	        var BUSY_STATE = 113;
	        var FINISH_STATE = 666;
	        var BS_NEED_MORE = 1;
	        /* block not completed, need more input or more output */

	        var BS_BLOCK_DONE = 2;
	        /* block flush performed */

	        var BS_FINISH_STARTED = 3;
	        /* finish started, need only more output at next deflate */

	        var BS_FINISH_DONE = 4;
	        /* finish done, accept no more input or output */

	        var OS_CODE = 0x03; // Unix :) . Don't detect, use this default.

	        function err(strm, errorCode) {
	          strm.msg = msg[errorCode];
	          return errorCode;
	        }

	        function rank(f) {
	          return (f << 1) - (f > 4 ? 9 : 0);
	        }

	        function zero(buf) {
	          var len = buf.length;

	          while (--len >= 0) {
	            buf[len] = 0;
	          }
	        }
	        /* =========================================================================
	         * 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()).
	         */


	        function flush_pending(strm) {
	          var s = strm.state; //_tr_flush_bits(s);

	          var len = s.pending;

	          if (len > strm.avail_out) {
	            len = strm.avail_out;
	          }

	          if (len === 0) {
	            return;
	          }

	          utils.arraySet(strm.output, s.pending_buf, 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;
	          }
	        }

	        function flush_block_only(s, last) {
	          trees._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);
	        }

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


	        function 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()).
	         */


	        function read_buf(strm, buf, start, size) {
	          var len = strm.avail_in;

	          if (len > size) {
	            len = size;
	          }

	          if (len === 0) {
	            return 0;
	          }

	          strm.avail_in -= len; // zmemcpy(buf, strm->next_in, len);

	          utils.arraySet(buf, strm.input, strm.next_in, len, start);

	          if (strm.state.wrap === 1) {
	            strm.adler = adler32(strm.adler, buf, len, start);
	          } else if (strm.state.wrap === 2) {
	            strm.adler = crc32(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.
	         */


	        function longest_match(s, cur_match) {
	          var chain_length = s.max_chain_length;
	          /* max hash chain length */

	          var scan = s.strstart;
	          /* current string */

	          var match;
	          /* matched string */

	          var len;
	          /* length of current match */

	          var best_len = s.prev_length;
	          /* best match length so far */

	          var nice_match = s.nice_match;
	          /* stop if match long enough */

	          var limit = s.strstart > s.w_size - MIN_LOOKAHEAD ? s.strstart - (s.w_size - MIN_LOOKAHEAD) : 0
	          /*NIL*/
	          ;
	          var _win = s.window; // shortcut

	          var wmask = s.w_mask;
	          var prev = s.prev;
	          /* Stop when cur_match becomes <= limit. To simplify the code,
	           * we prevent matches with the string of window index 0.
	           */

	          var strend = s.strstart + MAX_MATCH;
	          var scan_end1 = _win[scan + best_len - 1];
	          var 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).
	         */


	        function fill_window(s) {
	          var _w_size = s.w_size;
	          var 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)) {
	              utils.arraySet(s.window, s.window, _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 = (s.ins_h << s.hash_shift ^ s.window[str + 1]) & s.hash_mask; //#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 = (s.ins_h << s.hash_shift ^ s.window[str + MIN_MATCH - 1]) & s.hash_mask;
	                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) {
	          //    var curr = s.strstart + s.lookahead;
	          //    var 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.
	         */


	        function 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:
	           */
	          var 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) {
	                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: */

	            var 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) {
	            /*** 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.
	         */


	        function deflate_fast(s, flush) {
	          var hash_head;
	          /* head of the hash chain */

	          var 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) {
	                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 = (s.ins_h << s.hash_shift ^ s.window[s.strstart + MIN_MATCH - 1]) & s.hash_mask;
	              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 = trees._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 = (s.ins_h << s.hash_shift ^ s.window[s.strstart + MIN_MATCH - 1]) & s.hash_mask;
	                  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 = (s.ins_h << s.hash_shift ^ s.window[s.strstart + 1]) & s.hash_mask; //#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 = trees._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) {
	            /*** 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.
	         */


	        function deflate_slow(s, flush) {
	          var hash_head;
	          /* head of hash chain */

	          var bflush;
	          /* set if current block must be flushed */

	          var 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) {
	                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 = (s.ins_h << s.hash_shift ^ s.window[s.strstart + MIN_MATCH - 1]) & s.hash_mask;
	              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 = trees._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 = (s.ins_h << s.hash_shift ^ s.window[s.strstart + MIN_MATCH - 1]) & s.hash_mask;
	                  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 = trees._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 = trees._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) {
	            /*** 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.)
	         */


	        function deflate_rle(s, flush) {
	          var bflush;
	          /* set if current block must be flushed */

	          var prev;
	          /* byte at distance one to match */

	          var scan, strend;
	          /* scan goes up to strend for length of run */

	          var _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) {
	                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 = trees._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 = trees._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) {
	            /*** 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.)
	         */


	        function deflate_huff(s, flush) {
	          var 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) {
	                  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 = trees._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) {
	            /*** 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;
	        }

	        var configuration_table;
	        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
	         */

	        function 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;
	          /* 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 utils.Buf16(HEAP_SIZE * 2);
	          this.dyn_dtree = new utils.Buf16((2 * D_CODES + 1) * 2);
	          this.bl_tree = new utils.Buf16((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 utils.Buf16(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 utils.Buf16(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 utils.Buf16(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.
	           */
	        }

	        function deflateResetKeep(strm) {
	          var s;

	          if (!strm || !strm.state) {
	            return err(strm, Z_STREAM_ERROR);
	          }

	          strm.total_in = strm.total_out = 0;
	          strm.data_type = Z_UNKNOWN;
	          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;

	          trees._tr_init(s);

	          return Z_OK;
	        }

	        function deflateReset(strm) {
	          var ret = deflateResetKeep(strm);

	          if (ret === Z_OK) {
	            lm_init(strm.state);
	          }

	          return ret;
	        }

	        function deflateSetHeader(strm, head) {
	          if (!strm || !strm.state) {
	            return Z_STREAM_ERROR;
	          }

	          if (strm.state.wrap !== 2) {
	            return Z_STREAM_ERROR;
	          }

	          strm.state.gzhead = head;
	          return Z_OK;
	        }

	        function deflateInit2(strm, level, method, windowBits, memLevel, strategy) {
	          if (!strm) {
	            // === Z_NULL
	            return Z_STREAM_ERROR;
	          }

	          var wrap = 1;

	          if (level === Z_DEFAULT_COMPRESSION) {
	            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 || windowBits < 8 || windowBits > 15 || level < 0 || level > 9 || strategy < 0 || strategy > Z_FIXED) {
	            return err(strm, Z_STREAM_ERROR);
	          }

	          if (windowBits === 8) {
	            windowBits = 9;
	          }
	          /* until 256-byte window bug fixed */


	          var 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 utils.Buf8(s.w_size * 2);
	          s.head = new utils.Buf16(s.hash_size);
	          s.prev = new utils.Buf16(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 utils.Buf8(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);
	        }

	        function deflateInit(strm, level) {
	          return deflateInit2(strm, level, Z_DEFLATED, MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY);
	        }

	        function deflate(strm, flush) {
	          var old_flush, s;
	          var beg, val; // for gzip header write only

	          if (!strm || !strm.state || flush > Z_BLOCK || flush < 0) {
	            return strm ? err(strm, Z_STREAM_ERROR) : Z_STREAM_ERROR;
	          }

	          s = strm.state;

	          if (!strm.output || !strm.input && strm.avail_in !== 0 || s.status === FINISH_STATE && flush !== Z_FINISH) {
	            return err(strm, strm.avail_out === 0 ? Z_BUF_ERROR : Z_STREAM_ERROR);
	          }

	          s.strm = strm;
	          /* just in case */

	          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(strm.adler, s.pending_buf, s.pending, 0);
	                }

	                s.gzindex = 0;
	                s.status = EXTRA_STATE;
	              }
	            } else // DEFLATE header
	              {
	                var header = Z_DEFLATED + (s.w_bits - 8 << 4) << 8;
	                var 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(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(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(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(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(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(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;
	            }
	            /* 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) {
	            return err(strm, Z_BUF_ERROR);
	          }
	          /* 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);
	          }
	          /* Start a new block or continue the current one.
	           */


	          if (strm.avail_in !== 0 || s.lookahead !== 0 || flush !== Z_NO_FLUSH && s.status !== FINISH_STATE) {
	            var 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;
	              /* 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) {
	                trees._tr_align(s);
	              } else if (flush !== Z_BLOCK) {
	                /* FULL_FLUSH or SYNC_FLUSH */
	                trees._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) {
	                  /*** 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;
	              }
	            }
	          } //Assert(strm->avail_out > 0, "bug2");
	          //if (strm.avail_out <= 0) { throw new Error("bug2");}


	          if (flush !== Z_FINISH) {
	            return Z_OK;
	          }

	          if (s.wrap <= 0) {
	            return Z_STREAM_END;
	          }
	          /* 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 : Z_STREAM_END;
	        }

	        function deflateEnd(strm) {
	          var status;

	          if (!strm
	          /*== Z_NULL*/
	          || !strm.state
	          /*== Z_NULL*/
	          ) {
	            return Z_STREAM_ERROR;
	          }

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

	          strm.state = null;
	          return status === BUSY_STATE ? err(strm, Z_DATA_ERROR) : Z_OK;
	        }
	        /* =========================================================================
	         * Initializes the compression dictionary from the given byte
	         * sequence without producing any compressed output.
	         */


	        function deflateSetDictionary(strm, dictionary) {
	          var dictLength = dictionary.length;
	          var s;
	          var str, n;
	          var wrap;
	          var avail;
	          var next;
	          var input;
	          var tmpDict;

	          if (!strm
	          /*== Z_NULL*/
	          || !strm.state
	          /*== Z_NULL*/
	          ) {
	            return Z_STREAM_ERROR;
	          }

	          s = strm.state;
	          wrap = s.wrap;

	          if (wrap === 2 || wrap === 1 && s.status !== INIT_STATE || s.lookahead) {
	            return Z_STREAM_ERROR;
	          }
	          /* when using zlib wrappers, compute Adler-32 for provided dictionary */


	          if (wrap === 1) {
	            /* adler32(strm->adler, dictionary, dictLength); */
	            strm.adler = adler32(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);


	            tmpDict = new utils.Buf8(s.w_size);
	            utils.arraySet(tmpDict, dictionary, dictLength - s.w_size, s.w_size, 0);
	            dictionary = tmpDict;
	            dictLength = s.w_size;
	          }
	          /* insert dictionary into window and hash */


	          avail = strm.avail_in;
	          next = strm.next_in;
	          input = strm.input;
	          strm.avail_in = dictLength;
	          strm.next_in = 0;
	          strm.input = dictionary;
	          fill_window(s);

	          while (s.lookahead >= MIN_MATCH) {
	            str = s.strstart;
	            n = s.lookahead - (MIN_MATCH - 1);

	            do {
	              /* UPDATE_HASH(s, s->ins_h, s->window[str + MIN_MATCH-1]); */
	              s.ins_h = (s.ins_h << s.hash_shift ^ s.window[str + MIN_MATCH - 1]) & s.hash_mask;
	              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;
	        }

	        exports.deflateInit = deflateInit;
	        exports.deflateInit2 = deflateInit2;
	        exports.deflateReset = deflateReset;
	        exports.deflateResetKeep = deflateResetKeep;
	        exports.deflateSetHeader = deflateSetHeader;
	        exports.deflate = deflate;
	        exports.deflateEnd = deflateEnd;
	        exports.deflateSetDictionary = deflateSetDictionary;
	        exports.deflateInfo = 'pako deflate (from Nodeca project)';
	        /* Not implemented
	        exports.deflateBound = deflateBound;
	        exports.deflateCopy = deflateCopy;
	        exports.deflateParams = deflateParams;
	        exports.deflatePending = deflatePending;
	        exports.deflatePrime = deflatePrime;
	        exports.deflateTune = deflateTune;
	        */
	      }, {
	        "../utils/common": 41,
	        "./adler32": 43,
	        "./crc32": 45,
	        "./messages": 51,
	        "./trees": 52
	      }],
	      47: [function (require, module, exports) {
	        // (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;
	        }

	        module.exports = GZheader;
	      }, {}],
	      48: [function (require, module, exports) {
	        // (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

	        var BAD = 30;
	        /* got a data error -- remain here until reset */

	        var TYPE = 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.
	         */

	        module.exports = function inflate_fast(strm, start) {
	          var state;

	          var _in;
	          /* local strm.input */


	          var last;
	          /* have enough input while in < last */

	          var _out;
	          /* local strm.output */


	          var beg;
	          /* inflate()'s initial strm.output */

	          var end;
	          /* while out < end, enough space available */
	          //#ifdef INFLATE_STRICT

	          var dmax;
	          /* maximum distance from zlib header */
	          //#endif

	          var wsize;
	          /* window size or zero if not using window */

	          var whave;
	          /* valid bytes in the window */

	          var wnext;
	          /* window write index */
	          // Use `s_window` instead `window`, avoid conflict with instrumentation tools

	          var s_window;
	          /* allocated sliding window, if wsize != 0 */

	          var hold;
	          /* local strm.hold */

	          var bits;
	          /* local strm.bits */

	          var lcode;
	          /* local strm.lencode */

	          var dcode;
	          /* local strm.distcode */

	          var lmask;
	          /* mask for first level of length codes */

	          var dmask;
	          /* mask for first level of distance codes */

	          var here;
	          /* retrieved table entry */

	          var op;
	          /* code bits, operation, extra bits, or */

	          /*  window position, window bytes to copy */

	          var len;
	          /* match length, unused bytes */

	          var dist;
	          /* match distance */

	          var from;
	          /* where to copy match from */

	          var from_source;
	          var input, output; // JS specific, because we have no pointers

	          /* copy state to local variables */

	          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;
	                      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;
	                          break top;
	                        } // (!) This block is disabled in zlib defailts,
	                        // 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
	                    /*here.val*/
	                    ) + (hold & (1 << op) - 1)];
	                    continue dodist;
	                  } else {
	                    strm.msg = 'invalid distance code';
	                    state.mode = BAD;
	                    break top;
	                  }

	                  break; // need to emulate goto via "continue"
	                }
	              } else if ((op & 64) === 0) {
	                /* 2nd level length code */
	                here = lcode[(here & 0xffff
	                /*here.val*/
	                ) + (hold & (1 << op) - 1)];
	                continue dolen;
	              } else if (op & 32) {
	                /* end-of-block */
	                //Tracevv((stderr, "inflate:         end of block\n"));
	                state.mode = TYPE;
	                break top;
	              } else {
	                strm.msg = 'invalid literal/length code';
	                state.mode = BAD;
	                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;
	        };
	      }, {}],
	      49: [function (require, module, exports) {
	        // (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 utils = require('../utils/common');

	        var adler32 = require('./adler32');

	        var crc32 = require('./crc32');

	        var inflate_fast = require('./inffast');

	        var inflate_table = require('./inftrees');

	        var CODES = 0;
	        var LENS = 1;
	        var DISTS = 2;
	        /* Public constants ==========================================================*/

	        /* ===========================================================================*/

	        /* Allowed flush values; see deflate() and inflate() below for details */
	        //var Z_NO_FLUSH      = 0;
	        //var Z_PARTIAL_FLUSH = 1;
	        //var Z_SYNC_FLUSH    = 2;
	        //var Z_FULL_FLUSH    = 3;

	        var Z_FINISH = 4;
	        var Z_BLOCK = 5;
	        var Z_TREES = 6;
	        /* Return codes for the compression/decompression functions. Negative values
	         * are errors, positive values are used for special but normal events.
	         */

	        var Z_OK = 0;
	        var Z_STREAM_END = 1;
	        var Z_NEED_DICT = 2; //var Z_ERRNO         = -1;

	        var Z_STREAM_ERROR = -2;
	        var Z_DATA_ERROR = -3;
	        var Z_MEM_ERROR = -4;
	        var Z_BUF_ERROR = -5; //var Z_VERSION_ERROR = -6;

	        /* The deflate compression method */

	        var Z_DEFLATED = 8;
	        /* STATES ====================================================================*/

	        /* ===========================================================================*/

	        var HEAD = 1;
	        /* i: waiting for magic header */

	        var FLAGS = 2;
	        /* i: waiting for method and flags (gzip) */

	        var TIME = 3;
	        /* i: waiting for modification time (gzip) */

	        var OS = 4;
	        /* i: waiting for extra flags and operating system (gzip) */

	        var EXLEN = 5;
	        /* i: waiting for extra length (gzip) */

	        var EXTRA = 6;
	        /* i: waiting for extra bytes (gzip) */

	        var NAME = 7;
	        /* i: waiting for end of file name (gzip) */

	        var COMMENT = 8;
	        /* i: waiting for end of comment (gzip) */

	        var HCRC = 9;
	        /* i: waiting for header crc (gzip) */

	        var DICTID = 10;
	        /* i: waiting for dictionary check value */

	        var DICT = 11;
	        /* waiting for inflateSetDictionary() call */

	        var TYPE = 12;
	        /* i: waiting for type bits, including last-flag bit */

	        var TYPEDO = 13;
	        /* i: same, but skip check to exit inflate on new block */

	        var STORED = 14;
	        /* i: waiting for stored size (length and complement) */

	        var COPY_ = 15;
	        /* i/o: same as COPY below, but only first time in */

	        var COPY = 16;
	        /* i/o: waiting for input or output to copy stored block */

	        var TABLE = 17;
	        /* i: waiting for dynamic block table lengths */

	        var LENLENS = 18;
	        /* i: waiting for code length code lengths */

	        var CODELENS = 19;
	        /* i: waiting for length/lit and distance code lengths */

	        var LEN_ = 20;
	        /* i: same as LEN below, but only first time in */

	        var LEN = 21;
	        /* i: waiting for length/lit/eob code */

	        var LENEXT = 22;
	        /* i: waiting for length extra bits */

	        var DIST = 23;
	        /* i: waiting for distance code */

	        var DISTEXT = 24;
	        /* i: waiting for distance extra bits */

	        var MATCH = 25;
	        /* o: waiting for output space to copy string */

	        var LIT = 26;
	        /* o: waiting for output space to write literal */

	        var CHECK = 27;
	        /* i: waiting for 32-bit check value */

	        var LENGTH = 28;
	        /* i: waiting for 32-bit length (gzip) */

	        var DONE = 29;
	        /* finished check, done -- remain here until reset */

	        var BAD = 30;
	        /* got a data error -- remain here until reset */

	        var MEM = 31;
	        /* got an inflate() memory error -- remain here until reset */

	        var SYNC = 32;
	        /* looking for synchronization bytes to restart inflate() */

	        /* ===========================================================================*/

	        var ENOUGH_LENS = 852;
	        var ENOUGH_DISTS = 592; //var ENOUGH =  (ENOUGH_LENS+ENOUGH_DISTS);

	        var MAX_WBITS = 15;
	        /* 32K LZ77 window */

	        var DEF_WBITS = MAX_WBITS;

	        function 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 utils.Buf16(320);
	          /* temporary storage for code lengths */

	          this.work = new utils.Buf16(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 utils.Buf32(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 */
	        }

	        function inflateResetKeep(strm) {
	          var state;

	          if (!strm || !strm.state) {
	            return Z_STREAM_ERROR;
	          }

	          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 utils.Buf32(ENOUGH_LENS);
	          state.distcode = state.distdyn = new utils.Buf32(ENOUGH_DISTS);
	          state.sane = 1;
	          state.back = -1; //Tracev((stderr, "inflate: reset\n"));

	          return Z_OK;
	        }

	        function inflateReset(strm) {
	          var state;

	          if (!strm || !strm.state) {
	            return Z_STREAM_ERROR;
	          }

	          state = strm.state;
	          state.wsize = 0;
	          state.whave = 0;
	          state.wnext = 0;
	          return inflateResetKeep(strm);
	        }

	        function inflateReset2(strm, windowBits) {
	          var wrap;
	          var state;
	          /* get the state */

	          if (!strm || !strm.state) {
	            return Z_STREAM_ERROR;
	          }

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

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

	        function inflateInit2(strm, windowBits) {
	          var ret;
	          var state;

	          if (!strm) {
	            return Z_STREAM_ERROR;
	          } //strm.msg = Z_NULL;                 /* in case we return an error */


	          state = new InflateState(); //if (state === Z_NULL) return Z_MEM_ERROR;
	          //Tracev((stderr, "inflate: allocated\n"));

	          strm.state = state;
	          state.window = null
	          /*Z_NULL*/
	          ;
	          ret = inflateReset2(strm, windowBits);

	          if (ret !== Z_OK) {
	            strm.state = null
	            /*Z_NULL*/
	            ;
	          }

	          return ret;
	        }

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


	        var virgin = true;
	        var lenfix, distfix; // We have no pointers in JS, so keep tables separate

	        function fixedtables(state) {
	          /* build fixed huffman tables if first call (may not be thread safe) */
	          if (virgin) {
	            var sym;
	            lenfix = new utils.Buf32(512);
	            distfix = new utils.Buf32(32);
	            /* literal/length table */

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

	            inflate_table(LENS, state.lens, 0, 288, lenfix, 0, state.work, {
	              bits: 9
	            });
	            /* distance table */

	            sym = 0;

	            while (sym < 32) {
	              state.lens[sym++] = 5;
	            }

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


	        function updatewindow(strm, src, end, copy) {
	          var dist;
	          var 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 utils.Buf8(state.wsize);
	          }
	          /* copy state->wsize or less output bytes into the circular window */


	          if (copy >= state.wsize) {
	            utils.arraySet(state.window, src, end - state.wsize, state.wsize, 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);


	            utils.arraySet(state.window, src, end - copy, dist, state.wnext);
	            copy -= dist;

	            if (copy) {
	              //zmemcpy(state->window, end - copy, copy);
	              utils.arraySet(state.window, src, end - copy, copy, 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;
	        }

	        function inflate(strm, flush) {
	          var state;
	          var input, output; // input/output buffers

	          var next;
	          /* next input INDEX */

	          var put;
	          /* next output INDEX */

	          var have, left;
	          /* available input and output */

	          var hold;
	          /* bit buffer */

	          var bits;
	          /* bits in bit buffer */

	          var _in, _out;
	          /* save starting available input and output */


	          var copy;
	          /* number of stored or match bytes to copy */

	          var from;
	          /* where to copy match bytes from */

	          var from_source;
	          var here = 0;
	          /* current decoding table entry */

	          var here_bits, here_op, here_val; // paked "here" denormalized (JS specific)
	          //var last;                   /* parent table entry */

	          var last_bits, last_op, last_val; // paked "last" denormalized (JS specific)

	          var len;
	          /* length to copy for repeats, bits to drop */

	          var ret;
	          /* return code */

	          var hbuf = new utils.Buf8(4);
	          /* buffer for gzip header crc calculation */

	          var opts;
	          var n; // temporary var for NEED_BITS

	          var order =
	          /* permutation of code lengths */
	          [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;
	          }

	          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;

	          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(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
	                /*BITS(8)*/
	                ) << 8) + (hold >> 8)) % 31) {
	                  strm.msg = 'incorrect header check';
	                  state.mode = BAD;
	                  break;
	                }

	                if ((hold & 0x0f
	                /*BITS(4)*/
	                ) !== Z_DEFLATED) {
	                  strm.msg = 'unknown compression method';
	                  state.mode = BAD;
	                  break;
	                } //--- DROPBITS(4) ---//


	                hold >>>= 4;
	                bits -= 4; //---//

	                len = (hold & 0x0f
	                /*BITS(4)*/
	                ) + 8;

	                if (state.wbits === 0) {
	                  state.wbits = len;
	                } else if (len > state.wbits) {
	                  strm.msg = 'invalid window size';
	                  state.mode = BAD;
	                  break;
	                }

	                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(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(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(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(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 conveniend processing later
	                        state.head.extra = new Array(state.head.extra_len);
	                      }

	                      utils.arraySet(state.head.extra, input, next, // extra field is limited to 65536 bytes
	                      // - no need for additional size check
	                      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(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(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(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;
	                }

	                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
	                /*BITS(2)*/
	                ) {
	                  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); ---


	                  utils.arraySet(output, input, 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
	                /*BITS(5)*/
	                ) + 257; //--- DROPBITS(5) ---//

	                hold >>>= 5;
	                bits -= 5; //---//

	                state.ndist = (hold & 0x1f
	                /*BITS(5)*/
	                ) + 1; //--- DROPBITS(5) ---//

	                hold >>>= 5;
	                bits -= 5; //---//

	                state.ncode = (hold & 0x0f
	                /*BITS(4)*/
	                ) + 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 = inflate_table(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 = inflate_table(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 = inflate_table(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; //---

	                  inflate_fast(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
	                    /*BITS(last.bits + last.op)*/
	                    ) >> 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
	                    /*BITS(last.bits + last.op)*/
	                    ) >> 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 defailts,
	                    // 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 '|' insdead 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 =
	                    /*UPDATE(state.check, put - _out, _out);*/
	                    state.flags ? crc32(state.check, output, _out, put - _out) : adler32(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;
	                break inf_leave;

	              case BAD:
	                ret = Z_DATA_ERROR;
	                break inf_leave;

	              case MEM:
	                return Z_MEM_ERROR;

	              case SYNC:
	              /* falls through */

	              default:
	                return Z_STREAM_ERROR;
	            }
	          } // 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)) {
	            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 =
	            /*UPDATE(state.check, strm.next_out - _out, _out);*/
	            state.flags ? crc32(state.check, output, _out, strm.next_out - _out) : adler32(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) && ret === Z_OK) {
	            ret = Z_BUF_ERROR;
	          }

	          return ret;
	        }

	        function inflateEnd(strm) {
	          if (!strm || !strm.state
	          /*|| strm->zfree == (free_func)0*/
	          ) {
	            return Z_STREAM_ERROR;
	          }

	          var state = strm.state;

	          if (state.window) {
	            state.window = null;
	          }

	          strm.state = null;
	          return Z_OK;
	        }

	        function inflateGetHeader(strm, head) {
	          var state;
	          /* check state */

	          if (!strm || !strm.state) {
	            return Z_STREAM_ERROR;
	          }

	          state = strm.state;

	          if ((state.wrap & 2) === 0) {
	            return Z_STREAM_ERROR;
	          }
	          /* save header structure */


	          state.head = head;
	          head.done = false;
	          return Z_OK;
	        }

	        function inflateSetDictionary(strm, dictionary) {
	          var dictLength = dictionary.length;
	          var state;
	          var dictid;
	          var ret;
	          /* check state */

	          if (!strm
	          /* == Z_NULL */
	          || !strm.state
	          /* == Z_NULL */
	          ) {
	            return Z_STREAM_ERROR;
	          }

	          state = strm.state;

	          if (state.wrap !== 0 && state.mode !== DICT) {
	            return Z_STREAM_ERROR;
	          }
	          /* check for correct dictionary identifier */


	          if (state.mode === DICT) {
	            dictid = 1;
	            /* adler32(0, null, 0)*/

	            /* dictid = adler32(dictid, dictionary, dictLength); */

	            dictid = adler32(dictid, dictionary, dictLength, 0);

	            if (dictid !== state.check) {
	              return Z_DATA_ERROR;
	            }
	          }
	          /* 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;
	          }

	          state.havedict = 1; // Tracev((stderr, "inflate:   dictionary set\n"));

	          return Z_OK;
	        }

	        exports.inflateReset = inflateReset;
	        exports.inflateReset2 = inflateReset2;
	        exports.inflateResetKeep = inflateResetKeep;
	        exports.inflateInit = inflateInit;
	        exports.inflateInit2 = inflateInit2;
	        exports.inflate = inflate;
	        exports.inflateEnd = inflateEnd;
	        exports.inflateGetHeader = inflateGetHeader;
	        exports.inflateSetDictionary = inflateSetDictionary;
	        exports.inflateInfo = 'pako inflate (from Nodeca project)';
	        /* Not implemented
	        exports.inflateCopy = inflateCopy;
	        exports.inflateGetDictionary = inflateGetDictionary;
	        exports.inflateMark = inflateMark;
	        exports.inflatePrime = inflatePrime;
	        exports.inflateSync = inflateSync;
	        exports.inflateSyncPoint = inflateSyncPoint;
	        exports.inflateUndermine = inflateUndermine;
	        */
	      }, {
	        "../utils/common": 41,
	        "./adler32": 43,
	        "./crc32": 45,
	        "./inffast": 48,
	        "./inftrees": 50
	      }],
	      50: [function (require, module, exports) {
	        // (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 utils = require('../utils/common');

	        var MAXBITS = 15;
	        var ENOUGH_LENS = 852;
	        var ENOUGH_DISTS = 592; //var ENOUGH = (ENOUGH_LENS+ENOUGH_DISTS);

	        var CODES = 0;
	        var LENS = 1;
	        var DISTS = 2;
	        var lbase = [
	        /* 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];
	        var lext = [
	        /* 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];
	        var dbase = [
	        /* 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];
	        var dext = [
	        /* 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];

	        module.exports = function inflate_table(type, lens, lens_index, codes, table, table_index, work, opts) {
	          var bits = opts.bits; //here = opts.here; /* table entry for duplication */

	          var len = 0;
	          /* a code's length in bits */

	          var sym = 0;
	          /* index of code symbols */

	          var min = 0,
	              max = 0;
	          /* minimum and maximum code lengths */

	          var root = 0;
	          /* number of index bits for root table */

	          var curr = 0;
	          /* number of index bits for current table */

	          var drop = 0;
	          /* code bits to drop for sub-table */

	          var left = 0;
	          /* number of prefix codes available */

	          var used = 0;
	          /* code entries in table used */

	          var huff = 0;
	          /* Huffman code */

	          var incr;
	          /* for incrementing code, index */

	          var fill;
	          /* index for replicating entries */

	          var low;
	          /* low bits for current root entry */

	          var mask;
	          /* mask for low root bits */

	          var next;
	          /* next available space in table */

	          var base = null;
	          /* base value table to use */

	          var base_index = 0; //  var shoextra;    /* extra bits table to use */

	          var end;
	          /* use base and extra for symbol > end */

	          var count = new utils.Buf16(MAXBITS + 1); //[MAXBITS+1];    /* number of codes of each length */

	          var offs = new utils.Buf16(MAXBITS + 1); //[MAXBITS+1];     /* offsets in table for each length */

	          var extra = null;
	          var extra_index = 0;
	          var 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 || 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) {
	            base = extra = work;
	            /* dummy value--not used */

	            end = 19;
	          } else if (type === LENS) {
	            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 && used > ENOUGH_LENS || type === DISTS && used > ENOUGH_DISTS) {
	            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 && used > ENOUGH_LENS || type === DISTS && used > ENOUGH_DISTS) {
	                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;
	        };
	      }, {
	        "../utils/common": 41
	      }],
	      51: [function (require, module, exports) {
	        // (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.

	        module.exports = {
	          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) */

	        };
	      }, {}],
	      52: [function (require, module, exports) {
	        // (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 utils = require('../utils/common');
	        /* Public constants ==========================================================*/

	        /* ===========================================================================*/
	        //var Z_FILTERED          = 1;
	        //var Z_HUFFMAN_ONLY      = 2;
	        //var Z_RLE               = 3;


	        var Z_FIXED = 4; //var Z_DEFAULT_STRATEGY  = 0;

	        /* Possible values of the data_type field (though see inflate()) */

	        var Z_BINARY = 0;
	        var Z_TEXT = 1; //var Z_ASCII             = 1; // = Z_TEXT

	        var Z_UNKNOWN = 2;
	        /*============================================================================*/

	        function zero(buf) {
	          var len = buf.length;

	          while (--len >= 0) {
	            buf[len] = 0;
	          }
	        } // From zutil.h


	        var STORED_BLOCK = 0;
	        var STATIC_TREES = 1;
	        var DYN_TREES = 2;
	        /* The three kinds of block type */

	        var MIN_MATCH = 3;
	        var MAX_MATCH = 258;
	        /* The minimum and maximum match lengths */
	        // From deflate.h

	        /* ===========================================================================
	         * Internal compression state.
	         */

	        var LENGTH_CODES = 29;
	        /* number of length codes, not counting the special END_BLOCK code */

	        var LITERALS = 256;
	        /* number of literal bytes 0..255 */

	        var L_CODES = LITERALS + 1 + LENGTH_CODES;
	        /* number of Literal or Length codes, including the END_BLOCK code */

	        var D_CODES = 30;
	        /* number of distance codes */

	        var BL_CODES = 19;
	        /* number of codes used to transfer the bit lengths */

	        var HEAP_SIZE = 2 * L_CODES + 1;
	        /* maximum heap size */

	        var MAX_BITS = 15;
	        /* All codes must not exceed MAX_BITS bits */

	        var Buf_size = 16;
	        /* size of bit buffer in bi_buf */

	        /* ===========================================================================
	         * Constants
	         */

	        var MAX_BL_BITS = 7;
	        /* Bit length codes must not exceed MAX_BL_BITS bits */

	        var END_BLOCK = 256;
	        /* end of block literal code */

	        var REP_3_6 = 16;
	        /* repeat previous bit length 3-6 times (2 bits of repeat count) */

	        var REPZ_3_10 = 17;
	        /* repeat a zero length 3-10 times  (3 bits of repeat count) */

	        var REPZ_11_138 = 18;
	        /* repeat a zero length 11-138 times  (7 bits of repeat count) */

	        /* eslint-disable comma-spacing,array-bracket-spacing */

	        var extra_lbits =
	        /* extra bits for each length code */
	        [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];
	        var extra_dbits =
	        /* extra bits for each distance code */
	        [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];
	        var extra_blbits =
	        /* extra bits for each bit length code */
	        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 7];
	        var bl_order = [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

	        var DIST_CODE_LEN = 512;
	        /* see definition of array dist_code below */
	        // !!!! Use flat array insdead of structure, Freq = i*2, Len = i*2+1

	        var static_ltree = new Array((L_CODES + 2) * 2);
	        zero(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).
	         */

	        var static_dtree = new Array(D_CODES * 2);
	        zero(static_dtree);
	        /* The static distance tree. (Actually a trivial tree since all codes use
	         * 5 bits.)
	         */

	        var _dist_code = new Array(DIST_CODE_LEN);

	        zero(_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.
	         */

	        var _length_code = new Array(MAX_MATCH - MIN_MATCH + 1);

	        zero(_length_code);
	        /* length code for each normalized match length (0 == MIN_MATCH) */

	        var base_length = new Array(LENGTH_CODES);
	        zero(base_length);
	        /* First normalized length for each code (0 = MIN_MATCH) */

	        var base_dist = new Array(D_CODES);
	        zero(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;
	        }

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

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


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


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

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


	        function bi_reverse(code, len) {
	          var 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.
	         */


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


	        function gen_bitlen(s, desc) //    deflate_state *s;
	        //    tree_desc *desc;    /* the tree descriptor */
	        {
	          var tree = desc.dyn_tree;
	          var max_code = desc.max_code;
	          var stree = desc.stat_desc.static_tree;
	          var has_stree = desc.stat_desc.has_stree;
	          var extra = desc.stat_desc.extra_bits;
	          var base = desc.stat_desc.extra_base;
	          var max_length = desc.stat_desc.max_length;
	          var h;
	          /* heap index */

	          var n, m;
	          /* iterate over the tree elements */

	          var bits;
	          /* bit length */

	          var xbits;
	          /* extra bits */

	          var f;
	          /* frequency */

	          var overflow = 0;
	          /* number of elements with bit length too large */

	          for (bits = 0; bits <= MAX_BITS; 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; 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.
	         */


	        function 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 */
	        {
	          var next_code = new Array(MAX_BITS + 1);
	          /* next code value for each bit length */

	          var code = 0;
	          /* running code value */

	          var bits;
	          /* bit index */

	          var n;
	          /* code index */

	          /* The distribution counts are first used to generate the code values
	           * without bit reversal.
	           */

	          for (bits = 1; bits <= MAX_BITS; 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++) {
	            var 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.
	         */


	        function tr_static_init() {
	          var n;
	          /* iterates over tree elements */

	          var bits;
	          /* bit counter */

	          var length;
	          /* length value */

	          var code;
	          /* code value */

	          var dist;
	          /* distance index */

	          var bl_count = new Array(MAX_BITS + 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; 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; 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; 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, bl_count);
	          /* The static distance tree is trivial: */

	          for (n = 0; n < D_CODES; 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, L_CODES, MAX_BITS);
	          static_d_desc = new StaticTreeDesc(static_dtree, extra_dbits, 0, D_CODES, MAX_BITS);
	          static_bl_desc = new StaticTreeDesc(new Array(0), extra_blbits, 0, BL_CODES, MAX_BL_BITS); //static_init_done = true;
	        }
	        /* ===========================================================================
	         * Initialize a new block.
	         */


	        function init_block(s) {
	          var n;
	          /* iterates over tree elements */

	          /* Initialize the trees. */

	          for (n = 0; n < L_CODES; n++) {
	            s.dyn_ltree[n * 2]
	            /*.Freq*/
	            = 0;
	          }

	          for (n = 0; n < D_CODES; n++) {
	            s.dyn_dtree[n * 2]
	            /*.Freq*/
	            = 0;
	          }

	          for (n = 0; n < BL_CODES; 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
	         */


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


	        function 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++);
	          //  }


	          utils.arraySet(s.pending_buf, s.window, 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.
	         */


	        function smaller(tree, n, m, depth) {
	          var _n2 = n * 2;

	          var _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).
	         */


	        function pqdownheap(s, tree, k) //    deflate_state *s;
	        //    ct_data *tree;  /* the tree to restore */
	        //    int k;               /* node to move down */
	        {
	          var v = s.heap[k];
	          var 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
	        // var SMALLEST = 1;

	        /* ===========================================================================
	         * Send the block data compressed using the given Huffman trees
	         */


	        function compress_block(s, ltree, dtree) //    deflate_state *s;
	        //    const ct_data *ltree; /* literal tree */
	        //    const ct_data *dtree; /* distance tree */
	        {
	          var dist;
	          /* distance of matched string */

	          var lc;
	          /* match length or unmatched char (if dist == 0) */

	          var lx = 0;
	          /* running index in l_buf */

	          var code;
	          /* the code to send */

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


	        function build_tree(s, desc) //    deflate_state *s;
	        //    tree_desc *desc; /* the tree descriptor */
	        {
	          var tree = desc.dyn_tree;
	          var stree = desc.stat_desc.static_tree;
	          var has_stree = desc.stat_desc.has_stree;
	          var elems = desc.stat_desc.elems;
	          var n, m;
	          /* iterate over heap elements */

	          var max_code = -1;
	          /* largest code with non zero frequency */

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

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


	        function 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 */
	        {
	          var n;
	          /* iterates over all tree elements */

	          var prevlen = -1;
	          /* last emitted length */

	          var curlen;
	          /* length of current code */

	          var nextlen = tree[0 * 2 + 1]
	          /*.Len*/
	          ;
	          /* length of next code */

	          var count = 0;
	          /* repeat count of the current code */

	          var max_count = 7;
	          /* max repeat count */

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


	        function 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 */
	        {
	          var n;
	          /* iterates over all tree elements */

	          var prevlen = -1;
	          /* last emitted length */

	          var curlen;
	          /* length of current code */

	          var nextlen = tree[0 * 2 + 1]
	          /*.Len*/
	          ;
	          /* length of next code */

	          var count = 0;
	          /* repeat count of the current code */

	          var max_count = 7;
	          /* max repeat count */

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


	        function build_bl_tree(s) {
	          var 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; 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.
	         */


	        function send_all_trees(s, lcodes, dcodes, blcodes) //    deflate_state *s;
	        //    int lcodes, dcodes, blcodes; /* number of codes for each tree */
	        {
	          var 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.
	         */


	        function 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
	           */
	          var black_mask = 0xf3ffc07f;
	          var 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; 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;
	        }

	        var static_init_done = false;
	        /* ===========================================================================
	         * Initialize the tree data structures for a new zlib stream.
	         */

	        function _tr_init(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
	         */


	        function _tr_stored_block(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.
	         */


	        function _tr_align(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.
	         */


	        function _tr_flush_block(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 */
	        {
	          var opt_lenb, static_lenb;
	          /* opt_len and static_len in bytes */

	          var 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) {
	              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(s, buf, stored_len, last);
	          } else if (s.strategy === Z_FIXED || 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.
	         */


	        function _tr_tally(s, dist, lc) //    deflate_state *s;
	        //    unsigned dist;  /* distance of matched string */
	        //    unsigned lc;    /* match length-MIN_MATCH or unmatched char (if dist==0) */
	        {
	          //var 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) * 2] /*.Freq*/++;
	            s.dyn_dtree[d_code(dist) * 2] /*.Freq*/++;
	          } // (!) This block is disabled in zlib defailts,
	          // 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.
	           */
	        }

	        exports._tr_init = _tr_init;
	        exports._tr_stored_block = _tr_stored_block;
	        exports._tr_flush_block = _tr_flush_block;
	        exports._tr_tally = _tr_tally;
	        exports._tr_align = _tr_align;
	      }, {
	        "../utils/common": 41
	      }],
	      53: [function (require, module, exports) {
	        // (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;
	        }

	        module.exports = ZStream;
	      }, {}],
	      54: [function (require, module, exports) {

	        module.exports = typeof setImmediate === 'function' ? setImmediate : function setImmediate() {
	          var args = [].slice.apply(arguments);
	          args.splice(1, 0, 0);
	          setTimeout.apply(null, args);
	        };
	      }, {}]
	    }, {}, [10])(10);
	  });
	})(jszip);

	var browserPonyfill = {exports: {}};

	(function (module, exports) {
	  var global = typeof self !== 'undefined' ? self : commonjsGlobal;

	  var __self__ = function () {
	    function F() {
	      this.fetch = false;
	      this.DOMException = global.DOMException;
	    }

	    F.prototype = global;
	    return new F();
	  }();

	  (function (self) {
	    (function (exports) {
	      var support = {
	        searchParams: 'URLSearchParams' in self,
	        iterable: 'Symbol' in self && 'iterator' in Symbol,
	        blob: 'FileReader' in self && 'Blob' in self && function () {
	          try {
	            new Blob();
	            return true;
	          } catch (e) {
	            return false;
	          }
	        }(),
	        formData: 'FormData' in self,
	        arrayBuffer: 'ArrayBuffer' in self
	      };

	      function isDataView(obj) {
	        return obj && DataView.prototype.isPrototypeOf(obj);
	      }

	      if (support.arrayBuffer) {
	        var viewClasses = ['[object Int8Array]', '[object Uint8Array]', '[object Uint8ClampedArray]', '[object Int16Array]', '[object Uint16Array]', '[object Int32Array]', '[object Uint32Array]', '[object Float32Array]', '[object Float64Array]'];

	        var isArrayBufferView = ArrayBuffer.isView || function (obj) {
	          return obj && viewClasses.indexOf(Object.prototype.toString.call(obj)) > -1;
	        };
	      }

	      function normalizeName(name) {
	        if (typeof name !== 'string') {
	          name = String(name);
	        }

	        if (/[^a-z0-9\-#$%&'*+.^_`|~]/i.test(name)) {
	          throw new TypeError('Invalid character in header field name');
	        }

	        return name.toLowerCase();
	      }

	      function normalizeValue(value) {
	        if (typeof value !== 'string') {
	          value = String(value);
	        }

	        return value;
	      } // Build a destructive iterator for the value list


	      function iteratorFor(items) {
	        var iterator = {
	          next: function () {
	            var value = items.shift();
	            return {
	              done: value === undefined,
	              value: value
	            };
	          }
	        };

	        if (support.iterable) {
	          iterator[Symbol.iterator] = function () {
	            return iterator;
	          };
	        }

	        return iterator;
	      }

	      function Headers(headers) {
	        this.map = {};

	        if (headers instanceof Headers) {
	          headers.forEach(function (value, name) {
	            this.append(name, value);
	          }, this);
	        } else if (Array.isArray(headers)) {
	          headers.forEach(function (header) {
	            this.append(header[0], header[1]);
	          }, this);
	        } else if (headers) {
	          Object.getOwnPropertyNames(headers).forEach(function (name) {
	            this.append(name, headers[name]);
	          }, this);
	        }
	      }

	      Headers.prototype.append = function (name, value) {
	        name = normalizeName(name);
	        value = normalizeValue(value);
	        var oldValue = this.map[name];
	        this.map[name] = oldValue ? oldValue + ', ' + value : value;
	      };

	      Headers.prototype['delete'] = function (name) {
	        delete this.map[normalizeName(name)];
	      };

	      Headers.prototype.get = function (name) {
	        name = normalizeName(name);
	        return this.has(name) ? this.map[name] : null;
	      };

	      Headers.prototype.has = function (name) {
	        return this.map.hasOwnProperty(normalizeName(name));
	      };

	      Headers.prototype.set = function (name, value) {
	        this.map[normalizeName(name)] = normalizeValue(value);
	      };

	      Headers.prototype.forEach = function (callback, thisArg) {
	        for (var name in this.map) {
	          if (this.map.hasOwnProperty(name)) {
	            callback.call(thisArg, this.map[name], name, this);
	          }
	        }
	      };

	      Headers.prototype.keys = function () {
	        var items = [];
	        this.forEach(function (value, name) {
	          items.push(name);
	        });
	        return iteratorFor(items);
	      };

	      Headers.prototype.values = function () {
	        var items = [];
	        this.forEach(function (value) {
	          items.push(value);
	        });
	        return iteratorFor(items);
	      };

	      Headers.prototype.entries = function () {
	        var items = [];
	        this.forEach(function (value, name) {
	          items.push([name, value]);
	        });
	        return iteratorFor(items);
	      };

	      if (support.iterable) {
	        Headers.prototype[Symbol.iterator] = Headers.prototype.entries;
	      }

	      function consumed(body) {
	        if (body.bodyUsed) {
	          return Promise.reject(new TypeError('Already read'));
	        }

	        body.bodyUsed = true;
	      }

	      function fileReaderReady(reader) {
	        return new Promise(function (resolve, reject) {
	          reader.onload = function () {
	            resolve(reader.result);
	          };

	          reader.onerror = function () {
	            reject(reader.error);
	          };
	        });
	      }

	      function readBlobAsArrayBuffer(blob) {
	        var reader = new FileReader();
	        var promise = fileReaderReady(reader);
	        reader.readAsArrayBuffer(blob);
	        return promise;
	      }

	      function readBlobAsText(blob) {
	        var reader = new FileReader();
	        var promise = fileReaderReady(reader);
	        reader.readAsText(blob);
	        return promise;
	      }

	      function readArrayBufferAsText(buf) {
	        var view = new Uint8Array(buf);
	        var chars = new Array(view.length);

	        for (var i = 0; i < view.length; i++) {
	          chars[i] = String.fromCharCode(view[i]);
	        }

	        return chars.join('');
	      }

	      function bufferClone(buf) {
	        if (buf.slice) {
	          return buf.slice(0);
	        } else {
	          var view = new Uint8Array(buf.byteLength);
	          view.set(new Uint8Array(buf));
	          return view.buffer;
	        }
	      }

	      function Body() {
	        this.bodyUsed = false;

	        this._initBody = function (body) {
	          this._bodyInit = body;

	          if (!body) {
	            this._bodyText = '';
	          } else if (typeof body === 'string') {
	            this._bodyText = body;
	          } else if (support.blob && Blob.prototype.isPrototypeOf(body)) {
	            this._bodyBlob = body;
	          } else if (support.formData && FormData.prototype.isPrototypeOf(body)) {
	            this._bodyFormData = body;
	          } else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) {
	            this._bodyText = body.toString();
	          } else if (support.arrayBuffer && support.blob && isDataView(body)) {
	            this._bodyArrayBuffer = bufferClone(body.buffer); // IE 10-11 can't handle a DataView body.

	            this._bodyInit = new Blob([this._bodyArrayBuffer]);
	          } else if (support.arrayBuffer && (ArrayBuffer.prototype.isPrototypeOf(body) || isArrayBufferView(body))) {
	            this._bodyArrayBuffer = bufferClone(body);
	          } else {
	            this._bodyText = body = Object.prototype.toString.call(body);
	          }

	          if (!this.headers.get('content-type')) {
	            if (typeof body === 'string') {
	              this.headers.set('content-type', 'text/plain;charset=UTF-8');
	            } else if (this._bodyBlob && this._bodyBlob.type) {
	              this.headers.set('content-type', this._bodyBlob.type);
	            } else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) {
	              this.headers.set('content-type', 'application/x-www-form-urlencoded;charset=UTF-8');
	            }
	          }
	        };

	        if (support.blob) {
	          this.blob = function () {
	            var rejected = consumed(this);

	            if (rejected) {
	              return rejected;
	            }

	            if (this._bodyBlob) {
	              return Promise.resolve(this._bodyBlob);
	            } else if (this._bodyArrayBuffer) {
	              return Promise.resolve(new Blob([this._bodyArrayBuffer]));
	            } else if (this._bodyFormData) {
	              throw new Error('could not read FormData body as blob');
	            } else {
	              return Promise.resolve(new Blob([this._bodyText]));
	            }
	          };

	          this.arrayBuffer = function () {
	            if (this._bodyArrayBuffer) {
	              return consumed(this) || Promise.resolve(this._bodyArrayBuffer);
	            } else {
	              return this.blob().then(readBlobAsArrayBuffer);
	            }
	          };
	        }

	        this.text = function () {
	          var rejected = consumed(this);

	          if (rejected) {
	            return rejected;
	          }

	          if (this._bodyBlob) {
	            return readBlobAsText(this._bodyBlob);
	          } else if (this._bodyArrayBuffer) {
	            return Promise.resolve(readArrayBufferAsText(this._bodyArrayBuffer));
	          } else if (this._bodyFormData) {
	            throw new Error('could not read FormData body as text');
	          } else {
	            return Promise.resolve(this._bodyText);
	          }
	        };

	        if (support.formData) {
	          this.formData = function () {
	            return this.text().then(decode);
	          };
	        }

	        this.json = function () {
	          return this.text().then(JSON.parse);
	        };

	        return this;
	      } // HTTP methods whose capitalization should be normalized


	      var methods = ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST', 'PUT'];

	      function normalizeMethod(method) {
	        var upcased = method.toUpperCase();
	        return methods.indexOf(upcased) > -1 ? upcased : method;
	      }

	      function Request(input, options) {
	        options = options || {};
	        var body = options.body;

	        if (input instanceof Request) {
	          if (input.bodyUsed) {
	            throw new TypeError('Already read');
	          }

	          this.url = input.url;
	          this.credentials = input.credentials;

	          if (!options.headers) {
	            this.headers = new Headers(input.headers);
	          }

	          this.method = input.method;
	          this.mode = input.mode;
	          this.signal = input.signal;

	          if (!body && input._bodyInit != null) {
	            body = input._bodyInit;
	            input.bodyUsed = true;
	          }
	        } else {
	          this.url = String(input);
	        }

	        this.credentials = options.credentials || this.credentials || 'same-origin';

	        if (options.headers || !this.headers) {
	          this.headers = new Headers(options.headers);
	        }

	        this.method = normalizeMethod(options.method || this.method || 'GET');
	        this.mode = options.mode || this.mode || null;
	        this.signal = options.signal || this.signal;
	        this.referrer = null;

	        if ((this.method === 'GET' || this.method === 'HEAD') && body) {
	          throw new TypeError('Body not allowed for GET or HEAD requests');
	        }

	        this._initBody(body);
	      }

	      Request.prototype.clone = function () {
	        return new Request(this, {
	          body: this._bodyInit
	        });
	      };

	      function decode(body) {
	        var form = new FormData();
	        body.trim().split('&').forEach(function (bytes) {
	          if (bytes) {
	            var split = bytes.split('=');
	            var name = split.shift().replace(/\+/g, ' ');
	            var value = split.join('=').replace(/\+/g, ' ');
	            form.append(decodeURIComponent(name), decodeURIComponent(value));
	          }
	        });
	        return form;
	      }

	      function parseHeaders(rawHeaders) {
	        var headers = new Headers(); // Replace instances of \r\n and \n followed by at least one space or horizontal tab with a space
	        // https://tools.ietf.org/html/rfc7230#section-3.2

	        var preProcessedHeaders = rawHeaders.replace(/\r?\n[\t ]+/g, ' ');
	        preProcessedHeaders.split(/\r?\n/).forEach(function (line) {
	          var parts = line.split(':');
	          var key = parts.shift().trim();

	          if (key) {
	            var value = parts.join(':').trim();
	            headers.append(key, value);
	          }
	        });
	        return headers;
	      }

	      Body.call(Request.prototype);

	      function Response(bodyInit, options) {
	        if (!options) {
	          options = {};
	        }

	        this.type = 'default';
	        this.status = options.status === undefined ? 200 : options.status;
	        this.ok = this.status >= 200 && this.status < 300;
	        this.statusText = 'statusText' in options ? options.statusText : 'OK';
	        this.headers = new Headers(options.headers);
	        this.url = options.url || '';

	        this._initBody(bodyInit);
	      }

	      Body.call(Response.prototype);

	      Response.prototype.clone = function () {
	        return new Response(this._bodyInit, {
	          status: this.status,
	          statusText: this.statusText,
	          headers: new Headers(this.headers),
	          url: this.url
	        });
	      };

	      Response.error = function () {
	        var response = new Response(null, {
	          status: 0,
	          statusText: ''
	        });
	        response.type = 'error';
	        return response;
	      };

	      var redirectStatuses = [301, 302, 303, 307, 308];

	      Response.redirect = function (url, status) {
	        if (redirectStatuses.indexOf(status) === -1) {
	          throw new RangeError('Invalid status code');
	        }

	        return new Response(null, {
	          status: status,
	          headers: {
	            location: url
	          }
	        });
	      };

	      exports.DOMException = self.DOMException;

	      try {
	        new exports.DOMException();
	      } catch (err) {
	        exports.DOMException = function (message, name) {
	          this.message = message;
	          this.name = name;
	          var error = Error(message);
	          this.stack = error.stack;
	        };

	        exports.DOMException.prototype = Object.create(Error.prototype);
	        exports.DOMException.prototype.constructor = exports.DOMException;
	      }

	      function fetch(input, init) {
	        return new Promise(function (resolve, reject) {
	          var request = new Request(input, init);

	          if (request.signal && request.signal.aborted) {
	            return reject(new exports.DOMException('Aborted', 'AbortError'));
	          }

	          var xhr = new XMLHttpRequest();

	          function abortXhr() {
	            xhr.abort();
	          }

	          xhr.onload = function () {
	            var options = {
	              status: xhr.status,
	              statusText: xhr.statusText,
	              headers: parseHeaders(xhr.getAllResponseHeaders() || '')
	            };
	            options.url = 'responseURL' in xhr ? xhr.responseURL : options.headers.get('X-Request-URL');
	            var body = 'response' in xhr ? xhr.response : xhr.responseText;
	            resolve(new Response(body, options));
	          };

	          xhr.onerror = function () {
	            reject(new TypeError('Network request failed'));
	          };

	          xhr.ontimeout = function () {
	            reject(new TypeError('Network request failed'));
	          };

	          xhr.onabort = function () {
	            reject(new exports.DOMException('Aborted', 'AbortError'));
	          };

	          xhr.open(request.method, request.url, true);

	          if (request.credentials === 'include') {
	            xhr.withCredentials = true;
	          } else if (request.credentials === 'omit') {
	            xhr.withCredentials = false;
	          }

	          if ('responseType' in xhr && support.blob) {
	            xhr.responseType = 'blob';
	          }

	          request.headers.forEach(function (value, name) {
	            xhr.setRequestHeader(name, value);
	          });

	          if (request.signal) {
	            request.signal.addEventListener('abort', abortXhr);

	            xhr.onreadystatechange = function () {
	              // DONE (success or failure)
	              if (xhr.readyState === 4) {
	                request.signal.removeEventListener('abort', abortXhr);
	              }
	            };
	          }

	          xhr.send(typeof request._bodyInit === 'undefined' ? null : request._bodyInit);
	        });
	      }

	      fetch.polyfill = true;

	      if (!self.fetch) {
	        self.fetch = fetch;
	        self.Headers = Headers;
	        self.Request = Request;
	        self.Response = Response;
	      }

	      exports.Headers = Headers;
	      exports.Request = Request;
	      exports.Response = Response;
	      exports.fetch = fetch;
	      Object.defineProperty(exports, '__esModule', {
	        value: true
	      });
	      return exports;
	    })({});
	  })(__self__);

	  __self__.fetch.ponyfill = true; // Remove "polyfill" property added by whatwg-fetch

	  delete __self__.fetch.polyfill; // Choose between native implementation (global) or custom implementation (__self__)
	  // var ctx = global.fetch ? global : __self__;

	  var ctx = __self__; // this line disable service worker support temporarily

	  exports = ctx.fetch; // To enable: import fetch from 'cross-fetch'

	  exports.default = ctx.fetch; // For TypeScript consumers without esModuleInterop.

	  exports.fetch = ctx.fetch; // To enable: import {fetch} from 'cross-fetch'

	  exports.Headers = ctx.Headers;
	  exports.Request = ctx.Request;
	  exports.Response = ctx.Response;
	  module.exports = exports;
	})(browserPonyfill, browserPonyfill.exports);

	const crossFetch$1 = browserPonyfill.exports;

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

	const JSZip$1 = jszip.exports;
	const fetchArrayBuffer$1 = fetchArrayBuffer$2;
	const loadingPromises$1 = {};

	var loadCommercials = async function loadCommercials() {
	  let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
	  const {
	    url = 'https://couch.cheminfo.org/cheminfo-public/d2eb480198c80275a1d05dd3609414f9/upload/commercials.zip'
	  } = options;

	  if (!loadingPromises$1[url]) {
	    loadingPromises$1[url] = fetchArrayBuffer$1(url);
	  }

	  const buffer = await loadingPromises$1[url];
	  const jsZip = new JSZip$1();
	  let zip = await jsZip.loadAsync(buffer);
	  let fileData = await zip.files['commercials.json'].async('string');
	  let data = JSON.parse(fileData);
	  data.sort((a, b) => a.em - b.em);
	  return data;
	};

	/**
	 * @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) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const {
	    min = Number.MIN_SAFE_INTEGER,
	    max = Number.MAX_SAFE_INTEGER,
	    onlyInteger,
	    onlyNonInteger
	  } = options;

	  if (entry.unsaturation !== undefined) {
	    if (entry.unsaturation < min || entry.unsaturation > max) return false;
	    if (onlyInteger && !Number.isInteger(entry.unsaturation)) return false;
	    if (onlyNonInteger && Number.isInteger(entry.unsaturation)) return false;
	  }

	  return true;
	};

	/**
	 * Returns true if the entry containing MF information match
	 * @param {object}   [entry={}}] - object containing mw, ...
	 * @param {object}   [options={}}]
	 * @param {number}   [options.minMW=0] - Minimal molecular weight
	 * @param {number}   [options.maxMW=+Infinity] - Maximal molecular weight
	 * @param {number}   [options.minEM=0] - Minimal monoisotopic mass
	 * @param {number}   [options.maxEM=+Infinity] - Maximal monoisotopic mass
	 * @param {number}   [options.minCharge=-Infinity] - Minimal charge
	 * @param {number}   [options.maxCharge=+Infinity] - Maximal charge
	 * @param {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) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const {
	    minMW = 0,
	    maxMW = +Infinity,
	    minEM = 0,
	    maxEM = +Infinity,
	    minCharge = Number.MIN_SAFE_INTEGER,
	    maxCharge = Number.MAX_SAFE_INTEGER,
	    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;
	};

	const {
	  ELECTRON_MASS: ELECTRON_MASS$4
	} = constants;

	var getMsem$1 = function getMsem(em, charge) {
	  if (charge > 0) {
	    return em / charge - ELECTRON_MASS$4;
	  } else if (charge < 0) {
	    return em / (charge * -1) + ELECTRON_MASS$4;
	  } 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$4 = function getMsInfo(entry) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const {
	    allowNeutralMolecules,
	    ionization = {
	      mf: '',
	      em: 0,
	      charge: 0
	    },
	    forceIonization = false,
	    targetMass
	  } = options;
	  let realIonization = ionization;

	  if (!forceIonization && entry.ionization && entry.ionization.mf !== '') {
	    realIonization = entry.ionization;
	  }

	  let ms = {
	    ionization: realIonization.mf,
	    em: 0,
	    charge: entry.charge + realIonization.charge
	  };

	  if (ms.charge !== 0) {
	    ms.em = getMsem(entry.em + realIonization.em, ms.charge);
	  } else if (allowNeutralMolecules) {
	    ms.em = entry.em + realIonization.em;
	  }

	  if (targetMass) {
	    ms.delta = targetMass - ms.em;
	    ms.ppm = (targetMass - ms.em) / ms.em * 1e6;
	  }

	  return {
	    ms,
	    ionization: realIonization
	  };
	};

	/**
	 * Calculates reimAbsolute value of a complex spectrum
	 *
	 * @param data - complex spectrum
	 * @returns - reimAbsolute value
	 */
	function reimAbsolute(data) {
	  const length = data.re.length;
	  const re = data.re;
	  const im = data.im;
	  const newArray = new Float64Array(length);

	  for (let i = 0; i < length; i++) {
	    newArray[i] = Math.sqrt(re[i] ** 2 + im[i] ** 2);
	  }

	  return newArray;
	}

	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) {
	    let lambda = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 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() {
	    let step = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 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() {
	    let step = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 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) {
	    let domain = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 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;

	/**
	 * Create an array with numbers between "from" and "to" of length "length"
	 *
	 * @param options - options
	 * @return - array of distributed numbers between "from" and "to"
	 */
	function createFromToArray() {
	  let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
	  let {
	    from = 0,
	    to = 1,
	    length = 1000,
	    includeFrom = true,
	    includeTo = true,
	    distribution = 'uniform'
	  } = options;
	  const array = new Float64Array(length);
	  let div = length;

	  if (includeFrom === true && includeTo === true) {
	    div = length - 1;
	  } else if (includeFrom === false && includeTo === true || includeFrom === true && includeTo === false) {
	    div = length;
	  } else if (includeFrom === false && includeTo === false) {
	    div = length + 1;
	  }

	  let delta = (to - from) / div;

	  if (distribution === 'uniform') {
	    if (includeFrom === true) {
	      let index = 0;

	      while (index < length) {
	        array[index] = from + delta * index;
	        index++;
	      }
	    } else {
	      let index = 0;

	      while (index < length) {
	        array[index] = from + delta * (index + 1);
	        index++;
	      }
	    }
	  } else if (distribution === 'log') {
	    let base = (to / from) ** (1 / div);
	    let firstExponent = Math.log(from) / Math.log(base);

	    if (includeFrom === true) {
	      let index = 0;

	      while (index < length) {
	        array[index] = base ** (firstExponent + index);
	        index++;
	      }
	    } else {
	      let index = 0;

	      while (index < length) {
	        array[index] = base ** (firstExponent + index + 1);
	        index++;
	      }
	    }
	  } else {
	    throw new Error('Please choose for the distribution either uniform or log. By default the distribution chosen is uniform.');
	  }

	  return array;
	}

	/* eslint-disable @typescript-eslint/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];
	/**
	 * Polyval.
	 *
	 * @param c - Array of Number.
	 * @param x - Number.
	 * @returns Number.
	 */

	function polyval(c, x) {
	  let p = 0;

	  for (const coef of c) {
	    p = p * x + coef;
	  }

	  return p;
	}
	/**
	 * Calculates a rational approximation.
	 *
	 * @private
	 * @param x - Number.
	 * @param v - Number.
	 * @param P - Array of polynomial coefficients.
	 * @param Q - Array of polynomial coefficients.
	 * @param Y - Number.
	 * @returns Rational approximation.
	 */


	function calc(x, v, P, Q, Y) {
	  const s = x - v;
	  const r = polyval(P, s) / polyval(Q, s);
	  return Y * x + r * x;
	}
	/**
	 * Evaluates the complementary inverse error function for an input value.
	 *
	 * @private
	 * @param x - Input value.
	 * @returns Evaluated complementary inverse error function.
	 */


	function erfcinv(x) {
	  let sign = false;
	  let val;
	  let q;
	  let g;
	  let r; // [1] Special cases...
	  // NaN:

	  if (Number.isNaN(x)) {
	    return 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.
	}

	/**
	 * RayleighCdf.
	 *
	 * @param x - data
	 * @param sigma - standard deviation
	 * @returns - rayleigh cdf
	 */
	function rayleighCdf(x) {
	  let sigma = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 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 - real or magnitude spectra data.
	 * @param options - options
	 * @returns noise level
	 */

	function xNoiseSanPlot(array) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const {
	    mask,
	    cutOff,
	    refine = true,
	    magnitudeMode = false,
	    scaleFactor = 1,
	    factorStd = 5,
	    fixOffset = true
	  } = options;
	  let input;

	  if (Array.isArray(mask) && mask.length === array.length) {
	    input = new Float64Array(array.filter((_e, i) => !mask[i]));
	  } else {
	    input = new Float64Array(array);
	  }

	  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
	        }
	      }
	    })
	  };
	}
	/**
	 * DetermineCutOff.
	 *
	 * @param signPositive - Array of numbers.
	 * @param [options = {}] - Options.
	 * @param [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 [options.scaleFactor=1] - Factor to scale the data input[i]*=scaleFactor.
	 * @param [options.cutOff] - Percent of positive signal distribution where the noise level will be determined, if it is not defined the program calculate it.
	 * @param [options.factorStd=5] - Factor times std to determine what will be marked as signals.
	 * @param [options.refine=true] - If true the noise level will be recalculated get out the signals using factorStd.
	 * @param [options.fixOffset=true] - If the baseline is correct, the midpoint of distribution should be zero. If true, the distribution will be centered.
	 * @param [options.logBaseY=2] - Log scale to apply in the intensity axis in order to avoid big numbers.
	 * @param options.magnitudeMode -
	 * @param options.considerList -
	 * @param options.considerList.from -
	 * @param options.considerList.step -
	 * @param options.considerList.to -
	 * @param options.fromTo -
	 * @returns Result.
	 */

	function determineCutOff(signPositive) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  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;
	    elementsOfCutOff.forEach(element => {
	      kiSqrt += Math.pow(element[1] - averageValue, 2);
	    });

	    if (kiSqrt < minKi) {
	      minKi = kiSqrt;
	      whereToCutStat = i;
	    }
	  }

	  return whereToCutStat;
	}
	/**
	 * SimpleNormInvs.
	 *
	 * @param data - Data array.
	 * @param [options = {}] - Options.
	 * @param [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 [options.scaleFactor=1] - Factor to scale the data input[i]*=scaleFactor.
	 * @param [options.cutOff] - Percent of positive signal distribution where the noise level will be determined, if it is not defined the program calculate it.
	 * @param [options.factorStd=5] - Factor times std to determine what will be marked as signals.
	 * @param [options.refine=true] - If true the noise level will be recalculated get out the signals using factorStd.
	 * @param [options.fixOffset=true] - If the baseline is correct, the midpoint of distribution should be zero. If true, the distribution will be centered.
	 * @param [options.logBaseY=2] - Log scale to apply in the intensity axis in order to avoid big numbers.
	 * @param options.magnitudeMode -
	 * @param options.considerList -
	 * @param options.considerList.from -
	 * @param options.considerList.step -
	 * @param options.considerList.to -
	 * @param options.fromTo -
	 * @returns Result.
	 */


	function simpleNormInv(data) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const {
	    magnitudeMode = false
	  } = options;
	  if (!Array.isArray(data)) data = [data];
	  let from = 0;
	  let to = 2;
	  let step = 0.01;
	  let xTraining = Array.from(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;
	}
	/**
	 * CreateArray.
	 *
	 * @param from - From.
	 * @param to - To.
	 * @param step - Step.
	 * @returns Array of results.
	 */


	function createArray(from, to, step) {
	  // Changed Array to Float64Array
	  let result = new Float64Array(Math.abs((from - to) / step + 1));

	  for (let i = 0; i < result.length; i++) {
	    result[i] = from + i * step;
	  }

	  return result;
	}
	/**
	 * GenerateSanPlot.
	 *
	 * @param array - Array.
	 * @param [options = {}] - Options.
	 * @param [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 [options.scaleFactor=1] - Factor to scale the data input[i]*=scaleFactor.
	 * @param [options.cutOff] - Percent of positive signal distribution where the noise level will be determined, if it is not defined the program calculate it.
	 * @param [options.factorStd=5] - Factor times std to determine what will be marked as signals.
	 * @param [options.refine=true] - If true the noise level will be recalculated get out the signals using factorStd.
	 * @param [options.fixOffset=true] - If the baseline is correct, the midpoint of distribution should be zero. If true, the distribution will be centered.
	 * @param [options.logBaseY=2] - Log scale to apply in the intensity axis in order to avoid big numbers.
	 * @param options.magnitudeMode -
	 * @param options.considerList -
	 * @param options.considerList.from -
	 * @param options.considerList.step -
	 * @param options.considerList.to -
	 * @param options.fromTo -
	 * @returns Results.
	 */


	function generateSanPlot(array) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  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;
	}
	/**
	 * Scale.
	 *
	 * @param array - Array.
	 * @param [options = {}] - Options.
	 * @param [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 [options.scaleFactor=1] - Factor to scale the data input[i]*=scaleFactor.
	 * @param [options.cutOff] - Percent of positive signal distribution where the noise level will be determined, if it is not defined the program calculate it.
	 * @param [options.factorStd=5] - Factor times std to determine what will be marked as signals.
	 * @param [options.refine=true] - If true the noise level will be recalculated get out the signals using factorStd.
	 * @param [options.fixOffset=true] - If the baseline is correct, the midpoint of distribution should be zero. If true, the distribution will be centered.
	 * @param [options.logBaseY=2] - Log scale to apply in the intensity axis in order to avoid big numbers.
	 * @param options.magnitudeMode -
	 * @param options.considerList -
	 * @param options.considerList.from -
	 * @param options.considerList.step -
	 * @param options.considerList.to -
	 * @param options.fromTo -
	 * @returns Results.
	 */


	function scale(array) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  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 = createFromToArray({
	    from: 0,
	    to: array.length - 1,
	    length: array.length
	  });
	  return {
	    x: xAxis,
	    y: array
	  };
	}

	/**
	 * Phase correction filter
	 *
	 * @param data - complex spectrum
	 * @param phi0 - Angle in radians for zero order phase correction
	 * @param phi1 - Angle in radians for first order phase correction
	 * @returns - returns a new object {re:[], im:[]}
	 */
	function reimPhaseCorrection(data) {
	  let phi0 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
	  let phi1 = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
	  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 data - complex spectrum
	 * @param options - options
	 */

	function reimAutoPhaseCorrection(data) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const {
	    re,
	    im
	  } = data;
	  const length = re.length;
	  const {
	    magnitudeMode = true,
	    minRegSize = 30,
	    factorNoise = 3,
	    maxDistanceToJoin = 256
	  } = options;
	  let magnitudeData = magnitudeMode ? reimAbsolute(data) : re;
	  let ds = holoborodko(magnitudeData);
	  let peaksDs = robustBaseLineRegionsDetection(ds, {
	    maxDistanceToJoin,
	    magnitudeMode,
	    factorNoise
	  });
	  let peaksSp = robustBaseLineRegionsDetection(magnitudeData, {
	    maxDistanceToJoin,
	    magnitudeMode,
	    factorNoise
	  });
	  let finalPeaks = new Uint8Array(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.
	  // 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) {
	      //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));
	    }
	  } // 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
	  };
	}
	/**
	 * AutoPhaseRegion.
	 *
	 * @param re - Array of Number.
	 * @param im - Array of Number.
	 * @param x0 - Number.
	 * @returns Region.
	 */

	function autoPhaseRegion(re, im, x0) {
	  let start = -180;
	  let stop = 180;
	  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
	  };
	}
	/**
	 * Holoborodko.
	 *
	 * @param s - Array of float.
	 * @returns Array of float.
	 */


	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;
	}
	/**
	 * RobustBaseLineRegionsDetection.
	 *
	 * @param s
	 * @param options
	 * @param options.magnitudeMode
	 * @param options.maxDistanceToJoin
	 * @param options.factorNoise
	 */


	function robustBaseLineRegionsDetection(s, options) {
	  const {
	    maxDistanceToJoin,
	    magnitudeMode,
	    factorNoise
	  } = options;
	  let mask = new Uint8Array(s.length);

	  for (let i = 0; i < s.length; i++) {
	    mask[i] = 0;
	  }

	  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] = 1;
	      }
	    }
	  } // Clean up mask by merging peaks blocks, separated by just a few points(4??).


	  let count = 0;
	  let prev = 0;

	  for (let i = 0; i < s.length; i++) {
	    if (!mask[i]) {
	      count++;
	    } else {
	      if (count < maxDistanceToJoin) {
	        for (let j = 0; j <= count; j++) {
	          mask[prev + j] = 1;
	        }
	      }

	      while (mask[++i] && i < s.length);

	      prev = i;
	      count = 0;
	    }
	  }

	  return mask;
	}
	/**
	 * WeightedLinearRegression.
	 *
	 * @param x
	 * @param y
	 * @param w
	 */


	function weightedLinearRegression(x, y, w) {
	  let sxtw = 0;
	  let swx = 0;
	  let sw = 0;
	  let sxtwy = 0;
	  let swy = 0;

	  for (let i = 0; i < x.length; i++) {
	    sxtw += x[i] * x[i] * w[i];
	    swx += x[i] * w[i];
	    sw += w[i];
	    sxtwy += x[i] * w[i] * y[i];
	    swy += w[i] * y[i];
	  }
	  /* Just to know what is the matrix system that we solve
	   let Mx=[[sxtw, swx], [swx, sw]];
	   let My=[[sxtwy], [swy]];
	  */
	  //Mx inverse


	  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 element of data) {
	    if (element < 0) area -= element;
	  }

	  return area;
	};

	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;

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

	/**
	 * ReimFFT.
	 *
	 * @param data - complex spectrum
	 * @param options - options.
	 * @returns FFT of complex spectrum.
	 */

	function reimFFT(data) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  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 returns an array with absolute values
	 *
	 * @param array - array of data
	 * @returns - array with absolute values
	 */
	function xAbsolute(array) {
	  let tmpArray = array.slice();

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

	  return tmpArray;
	}

	const toString$2 = Object.prototype.toString;
	/**
	 * Checks if an object is an instance of an Array (array or typed array).
	 *
	 * @param {any} value - Object to check.
	 * @returns {boolean} True if the object is an array.
	 */

	function isAnyArray$4(value) {
	  return toString$2.call(value).endsWith('Array]');
	}

	var medianQuickselect_min$1 = {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$1);

	var quickSelectMedian$1 = medianQuickselect_min$1.exports;

	/**
	 * Calculates the median of an array
	 *
	 * @param input - Array containing values
	 * @returns - median
	 */

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

	  if (input.length === 0) {
	    throw new TypeError('input must not be empty');
	  } // we need to slice because the order of elements is changed in the quickselect
	  // https://github.com/mad-gooze/median-quickselect


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

	/**
	 * This function calculates the median after taking the reimAbsolute values of the points
	 *
	 * @param array - the array for which we want to calculate the absolute value
	 * @returns - median
	 */

	function xAbsoluteMedian(array) {
	  return xMedian(xAbsolute(array));
	}

	/**
	 * This function xAdd the first array by the second array or a constant value to each element of the first array
	 *
	 * @param array1 - the first array
	 * @param array2 - the second array or number
	 */

	function xAdd$1(array1, array2) {
	  let isConstant = false;
	  let constant = 0;

	  if (isAnyArray$4(array2)) {
	    if (array1.length !== array2.length) {
	      throw new Error('xAdd: size of array1 and array2 must be identical');
	    }
	  } else {
	    isConstant = true;
	    constant = array2;
	  }

	  let array3 = new Float64Array(array1.length);

	  if (isConstant) {
	    for (let i = 0; i < array1.length; i++) {
	      array3[i] = array1[i] + constant;
	    }
	  } else {
	    for (let i = 0; i < array1.length; i++) {
	      array3[i] = array1[i] + array2[i];
	    }
	  }

	  return array3;
	}

	/**
	 * This function
	 * @param output - undefined or a new array
	 * @param length - length of the output array
	 * @returns
	 */

	function getOutputArray(output, length) {
	  if (output !== undefined) {
	    if (!isAnyArray$4(output)) {
	      throw new TypeError('output option must be an array if specified');
	    }

	    if (output.length !== length) {
	      throw new TypeError('the output array does not have the correct length');
	    }

	    return output;
	  } else {
	    return new Float64Array(length);
	  }
	}

	/**
	 * This function xMultiply the first array by the second array or a constant value to each element of the first array
	 *
	 * @param array1 - first array
	 * @param array2 - second array
	 * @param options - options
	 */

	function xMultiply(array1, array2) {
	  let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
	  let isConstant = false;
	  let constant = 0;

	  if (isAnyArray$4(array2)) {
	    if (array1.length !== array2.length) {
	      throw new Error('xMultiply: size of array1 and array2 must be identical');
	    }
	  } else {
	    isConstant = true;
	    constant = Number(array2);
	  }

	  let array3 = getOutputArray(options.output, array1.length);

	  if (isConstant) {
	    for (let i = 0; i < array1.length; i++) {
	      array3[i] = array1[i] * constant;
	    }
	  } else {
	    for (let i = 0; i < array1.length; i++) {
	      array3[i] = array1[i] * array2[i];
	    }
	  }

	  return array3;
	}

	/**
	 * Dot product between two arrays
	 *
	 * @param A - First array.
	 * @param B - Second array.
	 */

	function xDotProduct(A, B) {
	  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 arrays
	 *
	 * @param A - fixed array
	 * @param B - sweeping array
	 * @param options - Options
	 */

	function xCrossCorrelation(A, B) {
	  let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
	  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 an array
	 *
	 * @param A - the array for which to calculate the auto-correlation
	 * @param options - Options
	 */

	function xAutoCorrelation(A) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  return xCrossCorrelation(A, A, options);
	}

	/**
	 * Calculating the box plot of the array
	 *
	 * @param array - data
	 */
	function xBoxPlot(array) {
	  array = Float64Array.from(array).sort();

	  if (array.length < 5) {
	    throw Error('xBoxPlot: can not calculate info if array contains less than 3 elements');
	  }

	  let info = {
	    q1: 0.0,
	    median: 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.median = array[middle];
	    q1max = middle - 1;
	    q3min = middle + 1;
	  } else {
	    // even
	    q3min = array.length / 2;
	    q1max = q3min - 1;
	    info.median = (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;
	}

	/**
	 * Checks if input is of type array
	 *
	 * @param input - input
	 */

	function xCheck$1(input) {
	  if (!isAnyArray$4(input)) {
	    throw new TypeError('input must be an array');
	  }

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

	/**
	 * Calculates the correlation between 2 vectors
	 * https://en.wikipedia.org/wiki/Correlation_and_dependence
	 *
	 * @param A - first array
	 * @param B - sencond array
	 */
	function xCorrelation(A, B) {
	  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 an array of the same size that is the cumulative values
	 *
	 * @param array - initial array
	 */

	function xCumulative(array) {
	  if (!isAnyArray$4(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 array1 - first array
	 * @param array2 - second array or number
	 * @param options - options
	 */

	function xDivide(array1, array2) {
	  let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
	  let isConstant = false;
	  let constant = 0;

	  if (isAnyArray$4(array2)) {
	    if (array1.length !== array2.length) {
	      throw new Error('xDivide: size of array1 and array2 must be identical');
	    }
	  } else {
	    isConstant = true;
	    constant = Number(array2);
	  }

	  let array3 = getOutputArray(options.output, array1.length);

	  if (isConstant) {
	    for (let i = 0; i < array1.length; i++) {
	      array3[i] = array1[i] / constant;
	    }
	  } else {
	    for (let i = 0; i < array1.length; i++) {
	      array3[i] = array1[i] / array2[i];
	    }
	  }

	  return array3;
	}

	/**
	 * Returns a copy of the data as Float64
	 *
	 * @param array - array of numbers
	 */

	function xEnsureFloat64(array) {
	  xCheck$1(array);

	  if (array instanceof Float64Array) {
	    return array.slice(0);
	  }

	  return Float64Array.from(array);
	}

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

	  if (sorted) {
	    let low = 0;
	    let high = array.length - 1;
	    let middle = 0;

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

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

	    if (low < array.length - 1) {
	      if (Math.abs(target - array[low]) < Math.abs(array[low + 1] - target)) {
	        return low;
	      } else {
	        return low + 1;
	      }
	    } else {
	      return low;
	    }
	  } else {
	    let index = 0;
	    let diff = Number.POSITIVE_INFINITY;

	    for (let i = 0; i < array.length; i++) {
	      const currentDiff = Math.abs(array[i] - target);

	      if (currentDiff < diff) {
	        diff = currentDiff;
	        index = i;
	      }
	    }

	    return index;
	  }
	}

	/**
	 * Returns an object with {fromIndex, toIndex} for a specific from / to
	 *
	 * @param x - array of numbers
	 * @param options - Options
	 */

	function xGetFromToIndex$1(x) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  let {
	    fromIndex,
	    toIndex,
	    from,
	    to
	  } = options;

	  if (fromIndex === undefined) {
	    if (from !== undefined) {
	      fromIndex = xFindClosestIndex$2(x, from);
	    } else {
	      fromIndex = 0;
	    }
	  }

	  if (toIndex === undefined) {
	    if (to !== undefined) {
	      toIndex = xFindClosestIndex$2(x, to);
	    } else {
	      toIndex = x.length - 1;
	    }
	  }

	  if (fromIndex < 0) fromIndex = 0;
	  if (toIndex < 0) toIndex = 0;
	  if (fromIndex >= x.length) fromIndex = x.length - 1;
	  if (toIndex >= x.length) toIndex = x.length - 1;
	  if (fromIndex > toIndex) [fromIndex, toIndex] = [toIndex, fromIndex];
	  return {
	    fromIndex,
	    toIndex
	  };
	}

	/**
	 *  Returns the targetIndex
	 *
	 * @param x - array of numbers
	 * @param options - options
	 */

	function xGetTargetIndex(x) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  let {
	    target,
	    targetIndex
	  } = options;

	  if (targetIndex === undefined) {
	    if (target !== undefined) {
	      return xFindClosestIndex$2(x, target);
	    } else {
	      return 0;
	    }
	  }

	  return targetIndex;
	}

	/**
	 * Computes the maximal value of an array of values
	 *
	 * @param array - array of numbers
	 * @param options - options
	 */

	function xMaxValue$1(array) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  xCheck$1(array);
	  const {
	    fromIndex,
	    toIndex
	  } = xGetFromToIndex$1(array, options);
	  let maxValue = array[fromIndex];

	  for (let i = fromIndex + 1; i <= toIndex; i++) {
	    if (array[i] > maxValue) {
	      maxValue = array[i];
	    }
	  }

	  return maxValue;
	}

	/**
	 * Computes the minimal value of an array of values
	 *
	 * @param array - array of numbers
	 * @param options - options
	 */

	function xMinValue(array) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  xCheck$1(array);
	  const {
	    fromIndex,
	    toIndex
	  } = xGetFromToIndex$1(array, options);
	  let minValue = array[fromIndex];

	  for (let i = fromIndex + 1; i <= toIndex; i++) {
	    if (array[i] < minValue) {
	      minValue = array[i];
	    }
	  }

	  return minValue;
	}

	/**
	 * Calculates a histogram of defined number of slots
	 *
	 * @param array - Array containing values
	 * @param options - options
	 * @returns - result of the histogram
	 */

	function xHistogram(array) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  xCheck$1(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$1(array)
	  } = options;
	  const slotSize = (max - min) / (nbSlots + Number.EPSILON);
	  const y = histogram === undefined ? new Float64Array(nbSlots) : histogram.y;
	  const x = histogram === undefined ? Array.from(createFromToArray({
	    from: min + (centerX ? slotSize / 2 : 0),
	    to: max - (centerX ? slotSize / 2 : 0),
	    length: nbSlots
	  })) : histogram.x;
	  array.forEach(element => {
	    const index = Math.max(Math.min((element - 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
	  };
	}

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

	  for (let i = 0; i < array.length - 1; ++i) {
	    let absoluteDifference = array[i + 1] - array[i];

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

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

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

	/**
	 * Returns true if x is monotone
	 *
	 * @param array - array of numbers
	 */
	function 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;
	}

	/**
	 * Returns true if x is monotone
	 *
	 * @param array - array of numbers
	 */
	function xIsMonotoneIncreasing(array) {
	  if (array.length < 2) {
	    return true;
	  }

	  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 - array of numbers
	 * @param  options - options
	 * @returns - index
	 */

	function xMaxIndex(array) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  xCheck$1(array);
	  const {
	    fromIndex,
	    toIndex
	  } = xGetFromToIndex$1(array, options);
	  let maxIndex = fromIndex;

	  for (let i = fromIndex + 1; i <= toIndex; i++) {
	    if (array[i] > array[maxIndex]) {
	      maxIndex = i;
	    }
	  }

	  return maxIndex;
	}

	/**
	 * Computes the mean value of an array of values
	 *
	 * @param array - array of numbers
	 * @param options - options
	 */

	function xMean(array) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  xCheck$1(array);
	  const {
	    fromIndex,
	    toIndex
	  } = xGetFromToIndex$1(array, options);
	  let sumValue = array[fromIndex];

	  for (let i = fromIndex + 1; i <= toIndex; i++) {
	    sumValue += array[i];
	  }

	  return sumValue / (toIndex - fromIndex + 1);
	}

	/**
	 * XCheckLengths.
	 *
	 * @param array1 - First array.
	 * @param array2 - Second array.
	 */
	function xCheckLengths(array1, array2) {
	  if (array1.length !== array2.length) {
	    throw new TypeError('Length of array1 and array2 must be identical');
	  }
	}

	/**
	 * This function calculates the mean absolute error
	 *
	 * @param array1 - first array
	 * @param array2 - second array
	 */

	function xMeanAbsoluteError(array1, array2) {
	  xCheckLengths(array1, array2);
	  let sum = 0;

	  for (let i = 0; i < array1.length; i++) {
	    sum += Math.abs(array1[i] - array2[i]);
	  }

	  return sum / array1.length;
	}

	/**
	 * This function calculates the mean squared error
	 *
	 * @param array1 -first array
	 * @param array2 - second array
	 */

	function xMeanSquaredError(array1, array2) {
	  xCheckLengths(array1, array2);
	  let sum = 0;

	  for (let i = 0; i < array1.length; i++) {
	    sum += Math.pow(array1[i] - array2[i], 2);
	  }

	  return sum / array1.length;
	}

	/**
	 * This function calculates the median absolute deviation (MAD)
	 * https://en.wikipedia.org/wiki/Median_absolute_deviation
	 * @param array
	 */

	function xMedianAbsoluteDeviation(array) {
	  const median = xMedian(array);
	  const averageDeviations = new Float64Array(array.length);

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

	  return {
	    median,
	    mad: xMedian(averageDeviations)
	  };
	}

	/**
	 * Computes the index of the minimum of the given values
	 *
	 * @param array - array of numbers
	 * @param options - options
	 * @returns - index
	 */

	function xMinIndex(array) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  xCheck$1(array);
	  const {
	    fromIndex,
	    toIndex
	  } = xGetFromToIndex$1(array, options);
	  let minIndex = fromIndex;

	  for (let i = fromIndex + 1; i <= toIndex; i++) {
	    if (array[i] < array[minIndex]) {
	      minIndex = i;
	    }
	  }

	  return minIndex;
	}

	/**
	 * Return min and max values of an array
	 *
	 * @param array - array of number
	 * @returns - Object with 2 properties, min and max
	 */

	function xMinMaxValues(array) {
	  xCheck$1(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
	  };
	}

	/**
	 * Calculates the mode of an array
	 *
	 * @param input - Array containing values
	 * @returns - mode
	 */
	function xMode(input) {
	  if (input.length === 0) {
	    throw new TypeError('input must not be empty');
	  }

	  let maxValue = 0;
	  let maxCount = 0;
	  let count = 0;
	  let counts = {};

	  for (let i = 0; i < input.length; ++i) {
	    let element = input[i];
	    count = counts[element];

	    if (count) {
	      counts[element]++;
	      count++;
	    } else {
	      counts[element] = count = 1;
	    }

	    if (count > maxCount) {
	      maxCount = count;
	      maxValue = input[i];
	    }
	  }

	  return maxValue;
	}

	/**
	 * Determine noise level using MAD https://en.wikipedia.org/wiki/Median_absolute_deviation
	 * Constant to convert mad to sd calculated using https://www.wolframalpha.com/input?i=sqrt%282%29+inverse+erf%280.5%29
	 * This assumes a gaussian distribution of the noise
	 * @param array
	 * @returns noise level corresponding to one standard deviation
	 */

	function xNoiseStandardDeviation(array) {
	  const {
	    mad,
	    median
	  } = xMedianAbsoluteDeviation(array);
	  return {
	    sd: mad / 0.6744897501960817,
	    mad,
	    median
	  };
	}

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

	/**
	 * Calculate the sum of the values
	 *
	 * @param array - Object that contains property x (an ordered increasing array) and y (an array).
	 * @param options - Options.
	 * @returns XSum value on the specified range.
	 */

	function xSum(array) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  xCheck$1(array);
	  const {
	    fromIndex,
	    toIndex
	  } = xGetFromToIndex$1(array, options);
	  let sumValue = array[fromIndex];

	  for (let i = fromIndex + 1; i <= toIndex; i++) {
	    sumValue += array[i];
	  }

	  return sumValue;
	}

	/**
	 * Divides the data with either the sum, the absolute sum or the maximum of the data
	 * @param array - Array containing values
	 * @param options - options
	 * @returns - normalized data
	 */

	function xNormed(input) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const {
	    algorithm = 'absolute',
	    value = 1
	  } = options;
	  xCheck$1(input);
	  const output = getOutputArray(options.output, input.length);

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

	  switch (algorithm.toLowerCase()) {
	    case 'absolute':
	      {
	        let absoluteSumValue = absoluteSum(input) / value;

	        if (absoluteSumValue === 0) {
	          throw new Error('xNormed: trying to divide by 0');
	        }

	        for (let i = 0; i < input.length; i++) {
	          output[i] = input[i] / absoluteSumValue;
	        }

	        return output;
	      }

	    case 'max':
	      {
	        let currentMaxValue = xMaxValue$1(input);

	        if (currentMaxValue === 0) {
	          throw new Error('xNormed: trying to divide by 0');
	        }

	        const factor = value / currentMaxValue;

	        for (let i = 0; i < input.length; i++) {
	          output[i] = input[i] * factor;
	        }

	        return output;
	      }

	    case 'sum':
	      {
	        let sumFactor = xSum(input) / value;

	        if (sumFactor === 0) {
	          throw new Error('xNormed: trying to divide by 0');
	        }

	        for (let i = 0; i < input.length; i++) {
	          output[i] = input[i] / sumFactor;
	        }

	        return output;
	      }

	    default:
	      throw new Error(`norm: unknown algorithm: ${algorithm}`);
	  }
	}

	function absoluteSum(input) {
	  let sumValue = 0;

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

	  return sumValue;
	}

	/**
	 * This function pads an array
	 *s
	 * @param array - the array that will be padded
	 * @param options - options
	 */

	function xPadding(array) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const {
	    size = 0,
	    value = 0,
	    algorithm = ''
	  } = options;
	  xCheck$1(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;
	}

	/** Finds the variance of the data
	 *
	 * @param values - the values of the array
	 * @param options - options
	 * @returns variance
	 */

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

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

	  const {
	    unbiased = true,
	    mean = xMean(values)
	  } = options;
	  let sqrError = 0;

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

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

	/** Finds the standard deviation for the data at hand
	 *
	 * @param values - values in the data
	 * @param options - options
	 * @returns standard deviation
	 */

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

	/**
	 * Pareto scaling, which uses the square root of standard deviation as the scaling factor, circumvents the amplification of noise by retaining a small portion of magnitude information.
	 * Noda, I. (2008). Scaling techniques to enhance two-dimensional correlation spectra. Journal of Molecular Structure, 883, 216-227.
	 * DOI: 10.1016/j.molstruc.2007.12.026
	 *
	 * @param array - array of number
	 */

	function xParetoNormalization(array) {
	  xCheck$1(array);
	  let result = [];
	  const sqrtSD = Math.sqrt(xStandardDeviation(array));

	  for (let item of array) {
	    result.push(item / sqrtSD);
	  }

	  return result;
	}

	/** Function used to rescale data
	 *
	 * @param input - input for the rescale
	 * @param options - options
	 * @returns rescaled data
	 */

	function xRescale(input) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  xCheck$1(input);
	  const output = getOutputArray(options.output, input.length);
	  const currentMin = xMinValue(input);
	  const currentMax = xMaxValue$1(input);

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

	  const {
	    min = 0,
	    max = 1
	  } = options;

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

	  const factor = (max - min) / (currentMax - currentMin);

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

	  return output;
	}

	/**
	 * This function calculates a rolling average
	 *
	 * @param array - array
	 * @param fct - callback function that from an array returns a value
	 * @param options - options
	 */

	function xRolling(array, fct) {
	  let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
	  xCheck$1(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++) {
	    // we will send a view to the original buffer
	    newArray.push(fct(array.subarray(i, i + window)));
	  }

	  return newArray;
	}

	/**
	 * This function calculates a rolling average
	 *
	 * @param array - array
	 * @param options - option
	 */

	function xRollingAverage(array) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  return xRolling(array, xMean, options);
	}

	/**
	 * This function calculates a maximum within a rolling window
	 *
	 * @param array - array
	 * @param options - options
	 */

	function xRollingMax(array) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  return xRolling(array, xMaxValue$1, options);
	}

	/**
	 * This function calculates a rolling average
	 *
	 * @param array - array
	 * @param options - options
	 */

	function xRollingMedian(array) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  return xRolling(array, xMedian, options);
	}

	/**
	 * This function calculates a minimum within a rolling window
	 *
	 * @param array - array
	 * @param options - options
	 */

	function xRollingMin(array) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  return xRolling(array, xMinValue, options);
	}

	/**
	 * Sample within the array
	 *
	 * @param array - array from which to sample
	 * @param options - options
	 * @return - array with evenly spaced elements
	 */
	function xSampling(array) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const {
	    length = 10
	  } = options;
	  let returnArray = [];

	  if (length > array.length) {
	    throw new Error('Choose sample number smaller than the number of elements in the array');
	  }

	  let clonedArray = array.slice();
	  returnArray.push(clonedArray[0]);
	  clonedArray.shift();
	  let delta = Math.floor(clonedArray.length / (length - 1));

	  for (let i = delta - 1, j = 0; i < clonedArray.length && j < length - 1; i = i + delta, j++) {
	    returnArray.push(clonedArray[i]);
	  }

	  return returnArray;
	}

	/** Fill an array with sequential numbers
	 *
	 * @param input - optional destination array (if not provided a new array will be created)
	 * @param options - options
	 * @return array with sequential numbers
	 */

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

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

	  if (!isAnyArray$4(input)) {
	    throw new TypeError('input must be an array');
	  } // maybe should not specify default step and size


	  let {
	    from = 0,
	    to = 10,
	    size = Array.isArray(input) || input.constructor === Float64Array || input.constructor === Uint16Array ? input.length : 0,
	    step = null
	  } = options;

	  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 (let i = 0; i < size; i++) {
	      input.push(from);
	      if (step) from += step;
	    }
	  } else {
	    if (Array.isArray(input) || input.constructor === Float64Array || input.constructor === Uint16Array && input.length !== size) {
	      throw new Error('sequentialFill typed array must have the correct length');
	    }

	    for (let i = 0; i < size; i++) {
	      if (Array.isArray(input) || input.constructor === Float64Array || input.constructor === Uint16Array) {
	        input[i] = from;
	      }

	      if (step) {
	        from += step;
	      }
	    }
	  }

	  return Array.isArray(input) || input.constructor === Float64Array || input.constructor === Uint16Array ? Array.from(input) : [];
	}

	/**
	 * Will apply a function on each element of the array described as a string
	 * By default we will use as variable 'x'
	 * In front of sequence of lowercase we will add 'Math.'. This allows to write
	 * `sin(x) + cos(x)` and it will be replace internally by (x) => (Math.sin(x) + Math.cos(x))
	 * @param array
	 * @param options
	 * @returns
	 */
	function xApplyFunctionStr(array) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const {
	    variableLabel = 'x',
	    fctString = variableLabel
	  } = options;
	  const fct = new Function(variableLabel, `return Number(${fctString.replace(/(^|\W)([a-z]{2,})/g, '$1Math.$2').replace(/Math\.Math/g, 'Math')})`);
	  const toReturn = Float64Array.from(array);

	  for (let i = 0; i < array.length; i++) {
	    toReturn[i] = fct(array[i]);

	    if (isNaN(toReturn[i])) {
	      throw Error(`The callback ${fctString} does not return a number: ${array[i]}`);
	    }
	  }

	  return toReturn;
	}

	/**
	 * This function xSubtract the first array by the second array or a constant value from each element of the first array
	 *
	 * @param array1 - the array that will be rotated
	 * @param array2 - second array or number
	 * @returns array after subtraction
	 */

	function xSubtract(array1, array2) {
	  let isConstant = false;
	  let constant = 0;

	  if (isAnyArray$4(array2)) {
	    if (array1.length !== array2.length) {
	      throw new Error('xSubtract: 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;
	}

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

	/**
	 * 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 data1 - First spectrum data
	 * @param data2 - Second spectrum data
	 * @param options - Options
	 */
	function xyAlign(data1, data2) {
	  let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
	  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;
	}

	/**
	 * Throw an error in no an object of x,y arrays
	 *
	 * @param data - array of points {x,y,z}
	 */

	function xyCheck(data) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const {
	    minLength
	  } = options;

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

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

	  if (minLength) {
	    if (data.x.length < minLength) {
	      throw new Error(`data.x must have a length of at least ${minLength}`);
	    }
	  }
	}

	/**
	 * Finds the closest point
	 *
	 * @param data - x array should be sorted and ascending
	 * @param target - target to search
	 * @returns - closest point
	 */

	function xyFindClosestPoint(
	/** points */
	data, target) {
	  const {
	    x,
	    y
	  } = data;
	  const index = xFindClosestIndex$2(x, target);
	  return {
	    x: x[index],
	    y: y[index]
	  };
	}

	/**Finds the covariance of the points
	 *
	 * @param data
	 * @param options
	 * @return the covariance
	 */

	function xyCovariance(data) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const {
	    x,
	    y
	  } = data;
	  const {
	    unbiased = true
	  } = options;
	  const meanX = xMean(x);
	  const meanY = xMean(y);
	  let error = 0;

	  for (let i = 0; i < x.length; i++) {
	    error += (x[i] - meanX) * (y[i] - meanY);
	  }

	  if (unbiased) {
	    return error / (x.length - 1);
	  } else {
	    return error / x.length;
	  }
	}

	/**
	 * Finds the max y value in a range and return a {x,y} point
	 *
	 * @param data - Object that contains property x (an ordered increasing array) and y (an array)
	 * @param options Options
	 */

	function xyMaxYPoint(data) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  xyCheck(data, {
	    minLength: 1
	  });
	  const {
	    x,
	    y
	  } = data;

	  if (x.length === 1) {
	    return {
	      x: x[0],
	      y: y[0],
	      index: 0
	    };
	  }

	  const {
	    fromIndex,
	    toIndex
	  } = xGetFromToIndex$1(x, options);
	  let current = {
	    x: x[fromIndex],
	    y: y[fromIndex],
	    index: fromIndex
	  };

	  for (let i = fromIndex; i <= toIndex; i++) {
	    if (y[i] > current.y) current = {
	      x: x[i],
	      y: y[i],
	      index: i
	    };
	  }

	  return current;
	}

	const STEPS = [0.25, 0.5, 0.75];
	/** Cumulative Distribution Statistics
	 *
	 * @param data - array of points {x,y}
	 * @returns x0, x25, x50, x75, x100, mode (x for maxY)
	 */

	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 = xMaxValue$1(cumulativeSum);

	  for (let i = 0; i < cumulativeSum.length; i++) {
	    cumulativeSum[i] /= maxY;
	  } // eslint-disable-next-line @typescript-eslint/no-explicit-any


	  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 data - Object that contains property x (an ordered increasing array) and y (an array).
	 */

	function xyEnsureGrowingX(data) {
	  xyCheck(data);
	  if (xIsMonotoneIncreasing(data.x)) return data;
	  const x = Array.from(data.x);
	  const y = Array.from(data.y);
	  let prevX = Number.NEGATIVE_INFINITY;
	  let currentIndex = 0;

	  for (let index = 0; index < x.length; index++) {
	    if (prevX < x[index]) {
	      if (currentIndex < index) {
	        x[currentIndex] = x[index];
	        y[currentIndex] = y[index];
	      }

	      currentIndex++;
	      prevX = x[index];
	    }
	  }

	  x.length = currentIndex;
	  y.length = currentIndex;
	  return {
	    x,
	    y
	  };
	}

	/**
	 * Normalize an array of zones:
	 * - ensure than from < to
	 * - merge overlapping zones
	 * - deal with exclusions zones
	 * - if no zones is specified add one between -Infinity and +Infinity
	 * @param zones - array of zones
	 * @param options - options
	 * @returns array of zones
	 */
	function zonesNormalize() {
	  let zones = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  let {
	    from = Number.NEGATIVE_INFINITY,
	    to = Number.POSITIVE_INFINITY,
	    exclusions = []
	  } = options;
	  if (from > to) [from, to] = [to, from];
	  zones = JSON.parse(JSON.stringify(zones)).map(zone => zone.from > zone.to ? {
	    from: zone.to,
	    to: zone.from
	  } : zone);
	  zones = zones.sort((a, b) => {
	    if (a.from !== b.from) return a.from - b.from;
	    return a.to - b.to;
	  });

	  if (zones.length === 0) {
	    zones.push({
	      from,
	      to
	    });
	  }

	  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 beforeExclusionsZones = [currentZone];

	  for (let i = 1; i < zones.length; i++) {
	    let zone = zones[i];

	    if (zone.from <= currentZone.to) {
	      if (currentZone.to < zone.to) {
	        currentZone.to = zone.to;
	      }
	    } else {
	      currentZone = zone;
	      beforeExclusionsZones.push(currentZone);
	    }
	  }

	  if (exclusions.length === 0) return beforeExclusionsZones;
	  const normalizedExclusions = zonesNormalize(exclusions);
	  let currentExclusionIndex = 0;
	  const results = [];
	  let counter = 0;

	  for (let zoneIndex = 0; zoneIndex < beforeExclusionsZones.length; zoneIndex++) {
	    if (counter++ > 5) break;
	    const zone = beforeExclusionsZones[zoneIndex];

	    if (currentExclusionIndex === normalizedExclusions.length) {
	      // we analysed all the exclusion zones
	      results.push(zone);
	      continue;
	    }

	    while (currentExclusionIndex < normalizedExclusions.length && normalizedExclusions[currentExclusionIndex].to <= zone.from) {
	      currentExclusionIndex++;
	    }

	    if (currentExclusionIndex === normalizedExclusions.length) {
	      // we analysed all the exclusion zones
	      results.push(zone);
	      continue;
	    }

	    if (zone.to < normalizedExclusions[currentExclusionIndex].from) {
	      // no problems, not yet in exclusion
	      results.push(zone);
	      continue;
	    }

	    if (normalizedExclusions[currentExclusionIndex].to >= zone.to) {
	      // could be totally excluded
	      if (normalizedExclusions[currentExclusionIndex].from <= zone.from) {
	        continue;
	      }

	      results.push({
	        from: normalizedExclusions[currentExclusionIndex].to,
	        to: zone.to
	      });
	    } // we cut in the middle, we need to create more zones, annoying !


	    if (normalizedExclusions[currentExclusionIndex].from > zone.from) {
	      results.push({
	        from: zone.from,
	        to: normalizedExclusions[currentExclusionIndex].from
	      });
	    }

	    zone.from = normalizedExclusions[currentExclusionIndex].to;
	    zoneIndex--;
	  }

	  return results;
	}

	/**
	 * Add the number of points per zone to reach a specified total
	 *
	 * @param zones - array of zones
	 * @param numberOfPoints - total number of points to distribute between zones
	 * @param options - options
	 * @returns array of zones with points
	 */

	function zonesWithPoints() {
	  let zones = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
	  let numberOfPoints = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 10;
	  let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
	  if (zones.length === 0) return zones;
	  let returnZones = zonesNormalize(zones, options);
	  const totalSize = returnZones.reduce((previous, current) => {
	    return previous + (current.to - current.from);
	  }, 0);
	  let unitsPerPoint = totalSize / numberOfPoints;
	  let currentTotal = 0;

	  for (let i = 0; i < returnZones.length - 1; i++) {
	    let zone = returnZones[i];
	    zone.numberOfPoints = Math.min(Math.round((zone.to - zone.from) / unitsPerPoint), numberOfPoints - currentTotal);
	    currentTotal += zone.numberOfPoints;
	  }

	  let zone = returnZones[returnZones.length - 1];
	  zone.numberOfPoints = numberOfPoints - currentTotal;
	  return returnZones;
	}

	/**
	 * function that retrieves the getEquallySpacedData with the variant "slot"
	 *
	 * @param x
	 * @param y
	 * @param from
	 * @param to
	 * @param numberOfPoints
	 * @return Array of y's equally spaced with the variant "slot"
	 */
	function equallySpacedSlot(
	/** x coordinates */
	x,
	/** y coordinates */
	y,
	/** from value */
	from,
	/** to value */
	to,
	/** number of points */
	numberOfPoints) {
	  let xLength = x.length;
	  let step = (to - from) / (numberOfPoints > 1 ? numberOfPoints - 1 : 1);
	  let halfStep = step / 2;
	  let lastStep = x[x.length - 1] - x[x.length - 2];
	  let start = from - halfStep; // Changed Array to Float64Array

	  let output = new Float64Array(numberOfPoints); // Init main variables

	  let min = start;
	  let max = start + step;
	  let previousX = -Number.MAX_VALUE;
	  let previousY = 0;
	  let nextX = x[0];
	  let nextY = y[0];
	  let frontOutsideSpectra = 0;
	  let backOutsideSpectra = true;
	  let currentValue = 0; // for slot algorithm

	  let currentPoints = 0;
	  let i = 1; // index of input

	  let j = 0; // index of output

	  main: while (true) {
	    if (previousX >= nextX) throw new Error('x must be a growing series');

	    while (previousX - max > 0) {
	      // no overlap with original point, just consume current value
	      if (backOutsideSpectra) {
	        currentPoints++;
	        backOutsideSpectra = false;
	      }

	      output[j] = currentPoints <= 0 ? 0 : currentValue / currentPoints;
	      j++;

	      if (j === numberOfPoints) {
	        break main;
	      }

	      min = max;
	      max += step;
	      currentValue = 0;
	      currentPoints = 0;
	    }

	    if (previousX > min) {
	      currentValue += previousY;
	      currentPoints++;
	    }

	    if (previousX === -Number.MAX_VALUE || frontOutsideSpectra > 1) {
	      currentPoints--;
	    }

	    previousX = nextX;
	    previousY = nextY;

	    if (i < xLength) {
	      nextX = x[i];
	      nextY = y[i];
	      i++;
	    } else {
	      nextX += lastStep;
	      nextY = 0;
	      frontOutsideSpectra++;
	    }
	  }

	  return output;
	}

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

	/**
	 * function that retrieves the getEquallySpacedData with the variant "smooth"
	 *
	 * @param x
	 * @param y
	 * @param from
	 * @param to
	 * @param numberOfPoints
	 * @return - Array of y's equally spaced with the variant "smooth"
	 */

	function equallySpacedSmooth(
	/** x coordinates */
	x,
	/** y coordinates */
	y,
	/** from value */
	from,
	/** to value */
	to,
	/** number of points */
	numberOfPoints) {
	  let xLength = x.length;
	  let step = (to - from) / (numberOfPoints > 1 ? numberOfPoints - 1 : 1);
	  let halfStep = step / 2; // Changed Array to Float64Array

	  let output = new Float64Array(numberOfPoints);
	  let initialOriginalStep = x[1] - x[0];
	  let lastOriginalStep = x[xLength - 1] - x[xLength - 2]; // Init main variables

	  let min = from - halfStep;
	  let max = from + halfStep;
	  let previousX = Number.MIN_SAFE_INTEGER;
	  let previousY = 0;
	  let nextX = x[0] - initialOriginalStep;
	  let nextY = 0;
	  let currentValue = 0;
	  let slope = 0;
	  let intercept = 0;
	  let sumAtMin = 0;
	  let sumAtMax = 0;
	  let i = 0; // index of input

	  let j = 0; // index of output

	  function getSlope(x0, y0, x1, y1) {
	    return (y1 - y0) / (x1 - x0);
	  }

	  let add = 0;

	  main: while (true) {
	    if (previousX >= nextX) throw new Error('x must be a growing series');

	    if (previousX <= min && min <= nextX) {
	      add = integral(0, min - previousX, slope, previousY);
	      sumAtMin = currentValue + add;
	    }

	    while (nextX - max >= 0) {
	      // no overlap with original point, just consume current value
	      add = integral(0, max - previousX, slope, previousY);
	      sumAtMax = currentValue + add;
	      output[j++] = (sumAtMax - sumAtMin) / step;

	      if (j === numberOfPoints) {
	        break main;
	      }

	      min = max;
	      max += step;
	      sumAtMin = sumAtMax;
	    }

	    currentValue += integral(previousX, nextX, slope, intercept);
	    previousX = nextX;
	    previousY = nextY;

	    if (i < xLength) {
	      nextX = x[i];
	      nextY = y[i];
	      i++;
	    } else if (i === xLength) {
	      nextX += lastOriginalStep;
	      nextY = 0;
	    }

	    slope = getSlope(previousX, previousY, nextX, nextY);
	    intercept = -slope * previousX + previousY;
	  }

	  return output;
	}

	/**
	 * Function that returns a Number array of equally spaced numberOfPoints
	 * containing a representation of intensities of the spectra arguments x
	 * and y.
	 *
	 * The options parameter contains an object in the following form:
	 * from: starting point
	 * to: last point
	 * numberOfPoints: number of points between from and to
	 * variant: "slot" or "smooth" - smooth is the default option
	 *
	 * The slot variant consist that each point in an array is calculated
	 * averaging the existing points between the slot that belongs to the current
	 * value. The smooth variant is the same but takes the integral of the range
	 * of the slot and divide by the step size between two points in an array.
	 *
	 * If exclusions zone are present, zones are ignored !
	 *
	 * @param data - object containing 2 properties x and y
	 * @param options - options
	 * @return new object with x / y array with the equally spaced data.
	 */

	function xyEquallySpaced(data) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  let {
	    x,
	    y
	  } = data;
	  let xLength = x.length;
	  const {
	    from = x[0],
	    to = x[xLength - 1],
	    variant = 'smooth',
	    numberOfPoints = 100,
	    exclusions = [],
	    zones = [{
	      from,
	      to
	    }]
	  } = options;

	  if (from > to) {
	    throw new RangeError('from should be larger than to');
	  }

	  xyCheck(data);

	  if (numberOfPoints < 2) {
	    throw new RangeError("'numberOfPoints' option must be greater than 1");
	  }

	  const normalizedZones = zonesNormalize(zones, {
	    from,
	    to,
	    exclusions
	  });
	  const zonesWithPointsRes = zonesWithPoints(normalizedZones, numberOfPoints, {
	    from,
	    to
	  });
	  let xResult = [];
	  let yResult = [];

	  for (let zone of zonesWithPointsRes) {
	    if (!zone.numberOfPoints) {
	      zone.numberOfPoints = 0;
	    }

	    let zoneResult = processZone(Array.from(x), Array.from(y), zone.from, zone.to, zone.numberOfPoints, variant);
	    xResult = xResult.concat(zoneResult.x);
	    yResult = yResult.concat(zoneResult.y);
	  }

	  return {
	    x: xResult,
	    y: yResult
	  };
	}

	function processZone(x, y, from, to, numberOfPoints, variant) {
	  if (numberOfPoints < 1) {
	    throw new RangeError('the number of points must be at least 1');
	  }

	  let output = variant === 'slot' ? Array.from(equallySpacedSlot(x, y, from, to, numberOfPoints)) : Array.from(equallySpacedSmooth(x, y, from, to, numberOfPoints));
	  return {
	    x: Array.from(createFromToArray({
	      from,
	      to,
	      length: numberOfPoints
	    })),
	    y: output
	  };
	}

	/**
	 * XyExtract zones from a XY data
	 *
	 * @param data - Object that contains property x (an ordered increasing array) and y (an array)
	 * @param options - options
	 * @returns - Array of points
	 */

	function xyExtract(data) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  xyCheck(data);
	  const {
	    x,
	    y
	  } = data;
	  let {
	    zones
	  } = options;
	  zones = zonesNormalize(zones);

	  if (x === undefined || y === undefined || !Array.isArray(zones) || zones.length === 0) {
	    return data;
	  }

	  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 an array x/y based on various criteria x points are expected to be sorted
	 *
	 * @param data - points
	 * @param options - options
	 * @return filtered array
	 */

	function xyFilterX(data) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const {
	    x,
	    y
	  } = data;
	  const {
	    from = x[0],
	    to = x[x.length - 1],
	    zones = [{
	      from,
	      to
	    }],
	    exclusions = []
	  } = options;
	  let normalizedZones = zonesNormalize(zones, {
	    from,
	    to,
	    exclusions
	  });
	  let currentZoneIndex = 0;
	  let newX = [];
	  let newY = [];
	  let position = 0;

	  while (position < x.length) {
	    if (x[position] <= normalizedZones[currentZoneIndex].to && x[position] >= normalizedZones[currentZoneIndex].from) {
	      newX.push(x[position]);
	      newY.push(y[position]);
	    } else {
	      if (x[position] > normalizedZones[currentZoneIndex].to) {
	        currentZoneIndex++;
	        if (!normalizedZones[currentZoneIndex]) break;
	      }
	    }

	    position++;
	  }

	  return {
	    x: newX,
	    y: newY
	  };
	}

	/**
	 * Filter out all the points for which x <= 0. Useful to display log scale data
	 *
	 * @param data - data
	 * @returns - An object with the filtered data
	 */

	function xyFilterXPositive(data) {
	  xyCheck(data);
	  const {
	    x,
	    y
	  } = data;
	  const newX = [];
	  const newY = [];
	  if (x === undefined || y === undefined) return {
	    x: newX,
	    y: newY
	  };

	  for (let i = 0; i < x.length; i++) {
	    if (x[i] > 0) {
	      newX.push(x[i]);
	      newY.push(y[i]);
	    }
	  }

	  return {
	    x: newX,
	    y: newY
	  };
	}

	/**
	 * Returns the numberMaxPoints points with the bigger y.
	 *
	 * @param data - Object that contains property x (an ordered increasing array) and y (an array)
	 * @param numberMaxPoints - Number of points to keep
	 * @returns - The points filtered to keep the `numberMaxPoints` most intense points of the input.
	 */

	function xyGetNMaxY(data, numberMaxPoints) {
	  xyCheck(data);

	  if (data.x.length <= numberMaxPoints) {
	    return data;
	  } else {
	    let newX = new Float64Array(numberMaxPoints);
	    let newY = new Float64Array(numberMaxPoints); // slice() is used to make a copy of the array, because sort() is IPM

	    let threshold = Float64Array.from(data.y).sort().reverse()[numberMaxPoints - 1];
	    let index = 0;

	    for (let i = 0; i < data.x.length; i++) {
	      if (data.y[i] >= threshold) {
	        newX[index] = data.x[i];
	        newY[index] = data.y[i];
	        index++;
	      }

	      if (index === numberMaxPoints) {
	        return {
	          x: newX,
	          y: newY
	        };
	      }
	    }

	    return data;
	  }
	}

	/**
	 * Order object of array, x has to be monotone. Ensure x is growing
	 *
	 * @param data - Object of kind {x:[], y:[]}.
	 */
	function xyGrowingX(data) {
	  const {
	    x,
	    y
	  } = data;

	  if (x.length !== y.length) {
	    throw 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 data - Object that contains property x (an ordered increasing array) and y (an array)
	 * @param options - Options
	 * @returns - An object with the xyIntegration function
	 */

	function xyIntegral(data) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const {
	    reverse = false
	  } = options;
	  xyCheck(data, {
	    minLength: 1
	  });
	  const {
	    x,
	    y
	  } = data;
	  const {
	    fromIndex,
	    toIndex
	  } = xGetFromToIndex$1(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 data - Object that contains property x (an ordered increasing array) and y (an array)
	 * @param options - Options
	 * @returns - xyIntegration value on the specified range
	 */

	function xyIntegration(data) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  xyCheck(data, {
	    minLength: 1
	  });
	  const {
	    x,
	    y
	  } = data;
	  if (x.length === 1) 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;
	}

	/**
	 * Join x / y values when difference in X is closer than delta.
	 * When joining, y values are summed and x values are weighted average
	 *
	 * @param data - Object that contains property x (an ordered increasing array) and y (an array)
	 * @param options - Options
	 * @returns - An object with the xyIntegration function
	 */

	function xyJoinX(data) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  xyCheck(data);
	  const {
	    delta = 1
	  } = options;
	  const deltaIsFunction = typeof delta === 'function';
	  const x = Array.from(data.x);
	  const y = Array.from(data.y);

	  if (x.length < 2) {
	    return {
	      x,
	      y
	    };
	  }

	  let position = 0;

	  for (let i = 1; i < x.length; i++) {
	    let difference = x[i] - x[i - 1];
	    let currentDelta = deltaIsFunction ? delta((x[i] + x[i - 1]) / 2) : delta;

	    if (difference <= currentDelta) {
	      // we join
	      if (y[position] !== 0 || y[i] !== 0) {
	        x[position] = (x[position] * y[position] + x[i] * y[i]) / (y[position] + y[i]);
	        y[position] += y[i];
	      }
	    } else {
	      position++;
	      x[position] = x[i];
	      y[position] = y[i];
	    }
	  }

	  x.length = position + 1;
	  y.length = position + 1;
	  return {
	    x,
	    y
	  };
	}

	/**
	 * Find the closest maximum going up hill
	 *
	 * @param data - Object that contains property x (an ordered increasing array) and y (an array)
	 * @param options - options
	 * @returns - An object with the x/y value
	 */

	function xyMaxClosestYPoint(data) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  xyCheck(data);
	  const {
	    x,
	    y
	  } = data;
	  let {
	    target,
	    targetIndex
	  } = options;

	  if (targetIndex === undefined) {
	    if (target !== undefined) {
	      targetIndex = xFindClosestIndex$2(x, target);
	    } else {
	      targetIndex = 0;
	    }
	  }

	  let previousIndex = Number.MIN_SAFE_INTEGER;
	  let currentIndex = targetIndex;
	  let xyMaxY = y[targetIndex];

	  while (currentIndex !== previousIndex) {
	    previousIndex = currentIndex;

	    if (currentIndex > 0 && y[currentIndex - 1] > xyMaxY) {
	      currentIndex--;
	    } else if (currentIndex < x.length - 1 && y[currentIndex + 1] > xyMaxY) {
	      currentIndex++;
	    }

	    xyMaxY = y[currentIndex];
	  }

	  return {
	    x: x[currentIndex],
	    y: y[currentIndex],
	    index: currentIndex
	  };
	}

	/**
	 * Finds all the max values
	 * If the values are equal the middle
	 * of the equal part will be the position of the signal!
	 *
	 * @param data - Object that contains property x (an ordered increasing array) and y (an array)
	 * @returns - Array of points
	 */

	function xyMaximaY(data) {
	  xyCheck(data, {
	    minLength: 2
	  });
	  const {
	    x,
	    y
	  } = data;
	  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;
	}

	/**
	 * Merge abscissas values on similar ordinates and weight the group of abscissas
	 *
	 * @param data - points
	 * @param options - options
	 * @return array of merged and weighted points
	 */
	function xyMaxMerge(data) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const {
	    x,
	    y
	  } = data;
	  const {
	    groupWidth = 0.001
	  } = options;
	  let merged = {
	    x: [],
	    y: []
	  };
	  let maxAbscissa = {
	    x: [],
	    y: []
	  };
	  let size = 0;
	  let index = 0;

	  while (index < x.length) {
	    if (size === 0 || x[index] - merged.x[size - 1] > groupWidth) {
	      maxAbscissa.x.push(x[index]);
	      maxAbscissa.y.push(y[index]);
	      merged.x.push(x[index]);
	      merged.y.push(y[index]);
	      index++;
	      size++;
	    } else {
	      if (y[index] > maxAbscissa.y[size - 1]) {
	        maxAbscissa.x[size - 1] = x[index];
	        maxAbscissa.y[size - 1] = y[index];
	      }

	      merged.x[size - 1] = x[index];
	      merged.y[size - 1] += y[index];
	      index++;
	    }
	  }

	  merged.x = maxAbscissa.x.slice();
	  return merged;
	}

	/**
	 * Finds the max value in a zone
	 *
	 * @param data - Object that contains property x (an ordered increasing array) and y (an array)
	 * @param options - Options
	 * @returns - Max y on the specified range
	 */

	function xyMaxY(data) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  xyCheck(data);
	  const {
	    x,
	    y
	  } = data;
	  const {
	    fromIndex,
	    toIndex
	  } = xGetFromToIndex$1(x, options);
	  let currentxyMaxY = y[fromIndex];

	  for (let i = fromIndex; i <= toIndex; i++) {
	    if (y[i] > currentxyMaxY) currentxyMaxY = y[i];
	  }

	  return currentxyMaxY;
	}

	/**
	 * Finds the median x value for an object with properties x and y (arrays of the same length)
	 *
	 * @param data - x should be sorted in increasing order
	 * @returns - the median of x values
	 */
	function xyMedian(data) {
	  const {
	    x,
	    y
	  } = data;
	  let sumY = 0;
	  let cumSumY = 0;
	  let i;

	  if (x.length === 0) {
	    return NaN;
	  }

	  if (x.length === 1) {
	    return x[0];
	  }

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

	  for (i = 0; i < y.length; i++) {
	    cumSumY += y[i];

	    if (cumSumY > sumY / 2) {
	      return x[i];
	    } else if (cumSumY === sumY / 2) {
	      return 0.5 * (x[i] + x[i + 1]);
	    }
	  }

	  return NaN;
	}

	/**
	 * Merge abscissa values if the ordinate value is in a list of centroids
	 *
	 * @param data - points
	 * @param centroids - centroids
	 * @param options - options
	 * @return merged points
	 */
	function xyMergeByCentroids(data, centroids) {
	  let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
	  const {
	    window = 0.01
	  } = options;
	  let mergedPoints = {
	    x: centroids.slice(),
	    y: new Float64Array(centroids.length).fill(0)
	  };
	  let originalIndex = 0;
	  let mergedIndex = 0;

	  while (originalIndex < data.x.length && mergedIndex < centroids.length) {
	    let diff = data.x[originalIndex] - centroids[mergedIndex];

	    if (Math.abs(diff) < window) {
	      mergedPoints.y[mergedIndex] += data.y[originalIndex++];
	    } else if (diff < 0) {
	      originalIndex++;
	    } else {
	      mergedIndex++;
	    }
	  }

	  return mergedPoints;
	}

	/**
	 * Find the closest minimum going down hill
	 *
	 * @param data - Object that contains property x (an ordered increasing array) and y (an array)
	 * @param options - Options
	 * @returns - An object with the x/y value
	 */

	function xyMinClosestYPoint(data) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  xyCheck(data);
	  const {
	    x,
	    y
	  } = data;
	  let {
	    target,
	    targetIndex
	  } = options;

	  if (targetIndex === undefined) {
	    if (target !== undefined) {
	      targetIndex = xFindClosestIndex$2(x, target);
	    } else {
	      targetIndex = 0;
	    }
	  }

	  let previousIndex = Number.MIN_SAFE_INTEGER;
	  let currentIndex = targetIndex;
	  let minY = y[targetIndex];

	  while (currentIndex !== previousIndex) {
	    previousIndex = currentIndex;

	    if (currentIndex > 0 && y[currentIndex - 1] < minY) {
	      currentIndex--;
	    } else if (currentIndex < x.length - 1 && y[currentIndex + 1] < minY) {
	      currentIndex++;
	    }

	    minY = y[currentIndex];
	  }

	  return {
	    x: x[currentIndex],
	    y: y[currentIndex],
	    index: currentIndex
	  };
	}

	/**
	 * Finds all the min values
	 * If the values are equal the middle
	 * of the equal part will be the position of the signal!
	 *
	 * @param data - Object that contains property X (an ordered increasing array) and y (an arraY)
	 * @returns - Array of points.
	 */

	function xyMinimaY(data) {
	  xyCheck(data, {
	    minLength: 2
	  });
	  const {
	    x,
	    y
	  } = data;
	  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 min y value in a range and return a {x,y} point
	 *
	 * @param data - Object that contains property x (an ordered increasing array) and y (an array)
	 * @param options - Options
	 */

	function xyMinYPoint(data) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  xyCheck(data, {
	    minLength: 1
	  });
	  const {
	    x,
	    y
	  } = data;
	  if (x.length === 1) return {
	    x: x[0],
	    y: y[0],
	    index: 0
	  };
	  const {
	    fromIndex,
	    toIndex
	  } = xGetFromToIndex$1(x, options);
	  let current = {
	    x: x[fromIndex],
	    y: y[fromIndex],
	    index: fromIndex
	  };

	  for (let i = fromIndex; i <= toIndex; i++) {
	    if (y[i] < current.y) current = {
	      x: x[i],
	      y: y[i],
	      index: i
	    };
	  }

	  return current;
	}

	/**
	 * Returns an information about a signal.
	 *
	 *
	 * We expect ordered data and equidistant X axis
	 * You can use the method helper if required:
	 * ML.ArrayPoints.uniqueX
	 * ML.ArrayPoints.sortX
	 * ML.ArrayPoints.equallySpaced
	 *
	 * @param data - Object that contains property x (an ordered increasing array) and y (an array)
	 * @param options - options
	 * @returns - Information about signal
	 */

	function xyPeakInfo(data) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  xyCheck(data);
	  const {
	    x,
	    y
	  } = data;
	  if (x === undefined || y === undefined || x.length < 3) return undefined;
	  let {
	    targetIndex,
	    target
	  } = options;

	  if (targetIndex === undefined) {
	    if (target !== undefined) {
	      targetIndex = xFindClosestIndex$2(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 data - Object that contains property x (an ordered increasing array) and y (an array)
	 * @param options - options
	 * @returns - An object with the x/y value
	 */

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

	/**
	 * XyRealMinYPoint.
	 *
	 * @param data - Data.
	 * @param options - Options.
	 */

	function xyRealMinYPoint(data) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  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. If you want a similar looking spectrum you should still however generate 4x the nbPoints that is being displayed.
	 * SHOULD NOT BE USED FOR DATA PROCESSING !!!
	 * You should rather use ml-xy-equally-spaced to make further processing
	 *
	 * @param data - Object that contains property x (an ordered increasing array) and y (an array)
	 * @param options - options
	 */

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

	  const internalZones = zones;
	  let totalPoints = 0;

	  for (let zone of internalZones) {
	    zone.fromIndex = xFindClosestIndex$2(x, zone.from);
	    zone.toIndex = xFindClosestIndex$2(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 < internalZones.length - 1; i++) {
	      const zone = internalZones[i];
	      zone.nbPoints = Math.round(zone.nbPoints * ratio);
	      currentTotal += zone.nbPoints;
	    }

	    internalZones[internalZones.length - 1].nbPoints = nbPoints - currentTotal;
	  } else {
	    let newX = new Float64Array(totalPoints);
	    let newY = new Float64Array(totalPoints);
	    let index = 0;

	    for (let zone of internalZones) {
	      for (let i = zone.fromIndex; i < zone.toIndex + 1; i++) {
	        newX[index] = x[i];
	        newY[index] = y[i];
	        index++;
	      }
	    }

	    return {
	      x: newX,
	      y: newY
	    };
	  }

	  let newX = [];
	  let newY = [];

	  for (let zone of internalZones) {
	    if (!zone.nbPoints) continue;
	    appendFromTo(zone.fromIndex, zone.toIndex, zone.nbPoints);
	  }

	  return {
	    x: newX,
	    y: newY
	  };
	  /**
	   * AppendFromTo.
	   *
	   * @param fromIndex - From.
	   * @param  toIndex - To.
	   * @param zoneNbPoints - NbPoints.
	   */

	  function appendFromTo(fromIndex, toIndex, zoneNbPoints) {
	    if (zoneNbPoints === 1) {
	      newX.push(x[Math.round((toIndex - fromIndex) / 2)]);
	      newY.push(y[Math.round((toIndex - fromIndex) / 2)]);
	      return;
	    }

	    if (zoneNbPoints === 2) {
	      newX.push(x[fromIndex], x[toIndex]);
	      newY.push(y[fromIndex], y[toIndex]);
	      return;
	    }

	    newX.push(x[fromIndex]);
	    newY.push(y[fromIndex]);

	    if (zoneNbPoints % 2 === 0) {
	      zoneNbPoints = zoneNbPoints / 2 + 1;
	    } else {
	      zoneNbPoints = (zoneNbPoints - 1) / 2 + 1;
	    } // we will need to make some kind of min / max because there are too many points
	    // we will always keep the first point and the last point


	    let slot = (x[toIndex] - x[fromIndex]) / (zoneNbPoints - 1);
	    let currentX = x[fromIndex] + slot;
	    let first = true;
	    let minY = Number.POSITIVE_INFINITY;
	    let xyMaxY = Number.NEGATIVE_INFINITY;

	    for (let i = fromIndex + 1; i <= toIndex; i++) {
	      if (first) {
	        minY = y[i];
	        xyMaxY = y[i];
	        first = false;
	      } else {
	        if (y[i] < minY) minY = y[i];
	        if (y[i] > xyMaxY) xyMaxY = y[i];
	      }

	      if (x[i] >= currentX || i === toIndex) {
	        if (optimize) {
	          if (minY > newY[newX.length - 1]) ; else if (xyMaxY < newY[newX.length - 1]) {
	            // we can skip the intermediate value
	            xyMaxY = minY;
	          } else {
	            newX.push(currentX - slot / 2);
	            newY.push(minY);
	          }
	        } else {
	          newX.push(currentX - slot / 2);
	          newY.push(minY);
	        }

	        newX.push(currentX);
	        newY.push(xyMaxY);
	        currentX += slot;
	        first = true;
	      }
	    }
	  }
	}

	/**
	 * This function calculates a rolling average.
	 * This methods will recalculate the x values by using xRollingAverage
	 *
	 * @param data - array of points {x,y}
	 * @param fct - callback function that from an array returns a value.
	 * @param options - options
	 */

	function xyRolling(data, fct) {
	  let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
	  let {
	    x,
	    y
	  } = data;
	  y = xRolling(y, fct, options);

	  if (x.length !== y.length) {
	    x = xRollingAverage(x, options);
	  }

	  return {
	    x,
	    y
	  };
	}

	/**
	 * Set a value (default 0) to specific zones.
	 *
	 * @param data - Object that contains property x (an ordered increasing array) and y (an array)
	 * @param options - options
	 * @returns - Array of points
	 */

	function xySetYValue(data) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  xyCheck(data);
	  const {
	    x,
	    y
	  } = data;
	  let {
	    zones,
	    value = 0
	  } = options;

	  if (!Array.isArray(zones) || zones.length === 0) {
	    return data;
	  }

	  zones = zonesNormalize(zones);
	  const newX = x.slice();
	  const newY = y.slice();
	  let currentZone = zones[0];
	  let position = 0;

	  loop: for (let i = 0; i < x.length; i++) {
	    while (currentZone.to < x[i]) {
	      position++;
	      currentZone = zones[position];

	      if (!currentZone) {
	        i = x.length;
	        break loop;
	      }
	    }

	    if (x[i] >= currentZone.from) {
	      newY[i] = value;
	    }
	  }

	  return {
	    x: newX,
	    y: newY
	  };
	}

	/**
	 * This function performs a quick sort of the x array while transforming the y array to preserve the coordinates.
	 *
	 * @param data - Object that contains property x (Array) and y (Array)
	 */

	function xySortX(data) {
	  const {
	    x,
	    y
	  } = data;

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

	/**
	 * Convert a DataXY to an array of array containing x,y
	 *
	 * @param data - array of points {x,y}
	 */

	function xyToXYArray(data) {
	  xyCheck(data);
	  const {
	    x,
	    y
	  } = data;
	  let objectArray = [];

	  for (let i = 0; i < x.length; i++) {
	    objectArray.push([x[i], y[i]]);
	  }

	  return objectArray;
	}

	/**
	 * XyToXYObject.
	 *
	 * @param data - Array of points {x,y}.
	 */

	function xyToXYObject(data) {
	  xyCheck(data);
	  const {
	    x,
	    y
	  } = data;
	  let objectArray = [];

	  for (let i = 0; i < x.length; i++) {
	    objectArray.push({
	      x: x[i],
	      y: y[i]
	    });
	  }

	  return objectArray;
	}

	/**
	 * Ensure x values are unique
	 *
	 * @param data - Object that contains property x (Array) and y (Array)
	 * @param options - Object containing a property algorithm (can be 'sum' or 'average', the latter being the default value), and a property isSorted (boolean indicating if the x-array is sorted).
	 */

	function xyUniqueX(data) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  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}`);
	  }
	}
	/**
	 * Average.
	 *
	 * @param data - Input.
	 * @returns Result.
	 */

	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
	  };
	}
	/**
	 * Sum.
	 *
	 * @param data - Input.
	 * @returns Result.
	 */


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

	/**
	 * Merge abscissas values on similar ordinates and weight the group of abscissas
	 *
	 * @param data - points
	 * @param options - options
	 * @return array of merged and weighted points
	 */
	function xyWeightedMerge(data) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const {
	    x,
	    y
	  } = data;
	  const {
	    groupWidth = 0.001
	  } = options;
	  let merged = {
	    x: [],
	    y: []
	  };
	  let weightedAbscissa = {
	    x: [],
	    y: []
	  };
	  let size = 0;
	  let index = 0;

	  while (index < x.length) {
	    if (size === 0 || x[index] - merged.x[size - 1] > groupWidth) {
	      weightedAbscissa.x.push(x[index] * y[index]);
	      weightedAbscissa.y.push(y[index]);
	      merged.x.push(x[index]);
	      merged.y.push(y[index]);
	      index++;
	      size++;
	    } else {
	      weightedAbscissa.x[size - 1] += x[index] * y[index];
	      weightedAbscissa.y[size - 1] += y[index];
	      merged.x[size - 1] = x[index];
	      merged.y[size - 1] += y[index];
	      index++;
	    }
	  }

	  for (let i = 0; i < merged.x.length; i++) {
	    merged.x[i] = weightedAbscissa.x[i] / weightedAbscissa.y[i];
	  }

	  return merged;
	}

	/**
	 * This function make a zero filling to re and im part.
	 *
	 * @param data - object of kind {x:[], re:[], im:[]}
	 * @param totalLength - final number of points
	 * @returns - data.
	 */
	function xreimZeroFilling(data, totalLength) {
	  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 data - object of kind {x:[], re:[], im:[]}
	 * @returns - sorted array
	 */
	function xreimSortX(data) {
	  const {
	    x,
	    re,
	    im
	  } = data;

	  if (x.length !== re.length || x.length !== im.length) {
	    throw 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()
	  };
	}

	/**
	 * GetSlots.
	 *
	 * @param data - data.
	 * @param options - Options.
	 */
	function getSlots(data) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const {
	    delta = 1
	  } = options;
	  const deltaIsFunction = typeof delta === 'function';
	  let possibleXs = Float64Array.from([].concat(...data.map(spectrum => spectrum.x))).sort();

	  if (possibleXs.length < 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 data, can be used for spectra
	 *
	 * @param data - data
	 * @param options - Options
	 */

	function xyArrayAlign(data) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const {
	    delta = 1
	  } = options;
	  data = data.map(spectrum => xyJoinX(spectrum, {
	    delta
	  }));
	  const slots = getSlots(data, options);
	  let x = Float64Array.from(slots.map(slot => slot.average));
	  let ys = new Array(data.length).fill(0).map(() => new Float64Array(x.length));
	  let positions = new Uint32Array(data.length);

	  for (let i = 0; i < slots.length; i++) {
	    let slot = slots[i];

	    for (let j = 0; j < data.length; j++) {
	      let spectrum = data[j];

	      while (positions[j] < spectrum.x.length && spectrum.x[positions[j]] <= slot.to) {
	        ys[j][i] += spectrum.y[positions[j]];
	        positions[j]++;
	      }
	    }
	  }

	  return {
	    x,
	    ys
	  };
	}

	/**
	 * Merge DataXY
	 * We have an array of DataXY and the goal is to merge all the values that are the closest possible
	 *
	 * @param data - data
	 * @param options - Options
	 */

	function xyArrayMerge(data) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const {
	    delta = 1
	  } = options; // we start by checking that the data/spectra don't have peaks too close and we simplify them

	  data = data.map(spectrum => xyJoinX(spectrum, {
	    delta
	  })); // at first we will calculate the X values (simple mean)

	  let slots = getSlots(data, options);
	  let x = Float64Array.from(slots.map(slot => slot.average));
	  let y = new Float64Array(x.length);
	  let positions = new Uint32Array(data.length);

	  for (let i = 0; i < slots.length; i++) {
	    let slot = slots[i];

	    for (let j = 0; j < data.length; j++) {
	      let spectrum = data[j];

	      while (positions[j] < spectrum.x.length && spectrum.x[positions[j]] <= slot.to) {
	        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 data - data
	 * @param options - Options
	 */
	function xyArrayWeightedMerge(data) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  let {
	    delta = 1
	  } = options;

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

	    delta = () => deltaNumber;
	  }

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

	  while (data.length !== 0) {
	    nextValue(data, positions, point);
	    let sameSlot = point.x <= slot.maxX;

	    if (!sameSlot) {
	      if (slot.sumY > 0) {
	        x.push(slot.sumXY / slot.sumY);
	        y.push(slot.sumY);
	      }

	      slot.sumY = 0;
	      slot.sumXY = 0;
	    }

	    slot.sumY += point.y;
	    slot.sumXY += point.x * point.y;
	    slot.maxX = point.x + delta(point.x);

	    if (data.length === 0) {
	      if (slot.sumY > 0) {
	        x.push(slot.sumXY / slot.sumY);
	        y.push(slot.sumY);
	      }
	    }
	  }

	  return {
	    x,
	    y
	  };
	}
	/**
	 * NextValue.
	 *
	 * @param data - data.
	 * @param positions - Positions array.
	 * @param point - Point.
	 */

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

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

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

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

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

	/**
	 * GetSlotsToFirst.
	 *
	 * @param data - data.
	 * @param options - Options.
	 */

	function getSlotsToFirst(data) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const {
	    delta = 1
	  } = options;
	  const deltaIsFunction = typeof delta === 'function';
	  let firstXs = data[0].x;
	  let slots = []; // we first create the slots based on the first spectrum

	  firstXs.forEach(element => {
	    let currentDelta = deltaIsFunction ? delta(element) : delta;
	    slots.push({
	      from: element - currentDelta,
	      to: element + currentDelta,
	      value: element
	    });
	  });
	  let otherXs = xyArrayWeightedMerge(data.slice(1), options).x;
	  let currentPosition = 0;

	  for (let slot of slots) {
	    while (otherXs[currentPosition] < slot.to && currentPosition < otherXs.length) {
	      if (otherXs[currentPosition] < slot.from) {
	        let currentDelta = deltaIsFunction ? delta(otherXs[currentPosition]) : delta;
	        slots.push({
	          from: otherXs[currentPosition] - currentDelta,
	          to: otherXs[currentPosition] + currentDelta,
	          value: otherXs[currentPosition]
	        });
	      }

	      currentPosition++;
	    }
	  }

	  for (let i = currentPosition; i < otherXs.length; i++) {
	    let currentDelta = deltaIsFunction ? delta(otherXs[i]) : delta;
	    slots.push({
	      from: otherXs[i] - currentDelta,
	      to: otherXs[i] + currentDelta,
	      value: otherXs[i]
	    });
	  }

	  slots.sort((a, b) => a.value - b.value); // we prevent slots overlap in the first spectrum

	  for (let i = 0; i < slots.length - 1; i++) {
	    if (slots[i].to > slots[i + 1].from) {
	      let middle = (slots[i].value + slots[i + 1].value) / 2;
	      slots[i].to = middle;
	      slots[i + 1].from = middle;
	    }
	  }

	  return slots;
	}

	/**
	 * We align all the data/spectra to the first array of X.
	 * The alignment is based on the X values of the first spectrum and the `delta` error allowed. If some x values are missing in the first specdtrum we will add them
	 *
	 * @param data data
	 * @param options options
	 */

	function xyArrayAlignToFirst(data) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const slots = getSlotsToFirst(data, options);
	  let x = Float64Array.from(slots.map(slot => slot.value));
	  let ys = new Array(data.length).fill(0).map(() => new Float64Array(x.length));
	  let positions = new Uint32Array(data.length);

	  for (let i = 0; i < slots.length; i++) {
	    let slot = slots[i];

	    for (let j = 0; j < data.length; j++) {
	      let spectrum = data[j];

	      while (positions[j] < spectrum.x.length && spectrum.x[positions[j]] < slot.to) {
	        ys[j][i] += spectrum.y[positions[j]];
	        positions[j]++;
	      }
	    }
	  }

	  return {
	    x,
	    ys
	  };
	}

	/**
	 * Throw an error in no an object of x,y arrays
	 *
	 * @param points - list of points
	 */
	function xyObjectCheck() {
	  let points = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];

	  if (!Array.isArray(points)) {
	    throw new Error('ArrayPoints must be an array of {x,y} object');
	  }

	  if (points.length > 0 && (points[0].x === undefined || points[0].y === undefined)) {
	    throw new Error('ArrayPoints must be an array of {x,y} object');
	  }
	}

	/**
	 * Finds the max x value and return a {x,y,index} point
	 *
	 * @param points - Object that contains property x (an ordered increasing array) and y (an array)
	 */

	function xyObjectMaxXPoint() {
	  let points = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
	  xyObjectCheck(points);
	  if (points.length < 1) return {
	    x: 0,
	    y: 0
	  };
	  let current = {
	    x: points[0].x,
	    y: points[0].y,
	    index: 0
	  };

	  for (let i = 1; i < points.length; i++) {
	    if (points[i].x > current.x) {
	      current = {
	        x: points[i].x,
	        y: points[i].y,
	        index: i
	      };
	    }
	  }

	  return current;
	}

	/**
	 * Finds the min x value and return a {x,y,index} point
	 *
	 * @param points - Object that contains property x (an ordered increasing array) and y (an array)
	 */

	function xyObjectMinXPoint() {
	  let points = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
	  xyObjectCheck(points);
	  if (points.length < 1) return {
	    x: 0,
	    y: 0
	  };
	  let current = {
	    x: points[0].x,
	    y: points[0].y,
	    index: 0
	  };

	  for (let i = 1; i < points.length; i++) {
	    if (points[i].x < current.x) {
	      current = {
	        x: points[i].x,
	        y: points[i].y,
	        index: i
	      };
	    }
	  }

	  return current;
	}

	/**
	 * Filter the array by taking the higher points (max y value) and only.
	 * Keep one per slot. There are 2 different slots, the smallest one will have the
	 * new property `close` to true
	 *
	 * @param points - array of all the points
	 * @param options - Options
	 * @returns - copy of points with 'close' property
	 */

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

	/**
	 * xyObjectJoinX.
	 *
	 * @param points - Array of growing points {x,y}.
	 * @param options - Options.
	 */
	function xyObjectJoinX(points) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  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 points - Object that contains property x (an ordered increasing array) and y (an array)
	 */

	function xyObjectMaxYPoint() {
	  let points = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
	  xyObjectCheck(points);
	  if (points.length < 1) return {
	    x: 0,
	    y: 0
	  };
	  let current = {
	    x: points[0].x,
	    y: points[0].y,
	    index: 0
	  };

	  for (let i = 1; i < points.length; i++) {
	    if (points[i].y > current.y) {
	      current = {
	        x: points[i].x,
	        y: points[i].y,
	        index: i
	      };
	    }
	  }

	  return current;
	}

	/**
	 * Finds the min y value and return a {x,y,index} point
	 *
	 * @param points - Object that contains property x (an ordered increasing array) and y (an array)
	 */

	function xyObjectMinYPoint() {
	  let points = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
	  xyObjectCheck(points);
	  if (points.length < 1) return {
	    x: 0,
	    y: 0
	  };
	  let current = {
	    x: points[0].x,
	    y: points[0].y,
	    index: 0
	  };

	  for (let i = 1; i < points.length; i++) {
	    if (points[i].y < current.y) {
	      current = {
	        x: points[i].x,
	        y: points[i].y,
	        index: i
	      };
	    }
	  }

	  return current;
	}

	/**
	 * XyObjectSlotX
	 *
	 * @param points - Array of growing points {x,y}.
	 * @param options - Options.
	 */
	function xyObjectSlotX(points) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const {
	    slotWidth = 1
	  } = options;
	  const halfSlot = slotWidth / 2; // when we join we will use the center of mass

	  let result = [];
	  let current = {
	    x: Number.NEGATIVE_INFINITY,
	    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 points - array of points {x,y}
	 * @returns - sorted array of points {x,y}
	 */
	function xyObjectSortX(points) {
	  return points.sort((a, b) => a.x - b.x);
	}

	/**
	 * Calculate the sum of Y values
	 *
	 * @param points - Object that contains property x and y (an array)
	 */

	function xyObjectSumY() {
	  let points = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
	  xyObjectCheck(points);
	  let sum = 0;

	  for (let point of points) {
	    sum += point.y;
	  }

	  return sum;
	}

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

	/**
	 * Deprecated !!! Should use utils/createFromToArray
	 *
	 * @param zone.
	 * @param size - Size.
	 * @returns - Array of float.
	 */
	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 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 matrix - matrix [rows][cols].
	 */
	function matrixMinMaxAbsoluteZ(matrix) {
	  if (matrix.length === 0 || matrix[0].length === 0) {
	    throw new Error('matrixMinMaxAbsoluteZ requires at least 1 row and 1 column');
	  }

	  const nbRows = matrix.length;
	  const nbColumns = matrix[0].length;
	  let min = Number.POSITIVE_INFINITY;
	  let max = Number.NEGATIVE_INFINITY;

	  for (let column = 0; column < nbColumns; column++) {
	    for (let row = 0; row < nbRows; row++) {
	      let value = matrix[row][column];
	      if (value < 0) value *= -1;
	      if (value < min) min = value;
	      if (value > max) max = value;
	    }
	  }

	  return {
	    min,
	    max
	  };
	}

	function matrixCheck$1(data) {
	  if (data.length === 0 || data[0].length === 0) {
	    throw RangeError('matrix should contain data');
	  }

	  const firstLength = data[0].length;

	  for (let i = 1; i < data.length; i++) {
	    if (data[i].length !== firstLength) {
	      throw new RangeError('All rows should has the same length');
	    }
	  }
	}

	/**
	 * Get min and max Z
	 *
	 * @param matrix - matrix [rows][cols].
	 */

	function matrixMinMaxZ$1(matrix) {
	  matrixCheck$1(matrix);
	  const nbRows = matrix.length;
	  const nbColumns = matrix[0].length;
	  let min = matrix[0][0];
	  let max = matrix[0][0];

	  for (let column = 0; column < nbColumns; column++) {
	    for (let row = 0; row < nbRows; row++) {
	      if (matrix[row][column] < min) min = matrix[row][column];
	      if (matrix[row][column] > max) max = matrix[row][column];
	    }
	  }

	  return {
	    min,
	    max
	  };
	}

	/**
	 * Calculates an histogram of defined number of slots
	 *
	 * @param matrix - matrix [rows][cols].
	 * @param options - options
	 * @returns - result of the histogram
	 */

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

	    if (options.min === undefined) {
	      options.min = logBaseX && minMax.min ? Math.log(minMax.min) / Math.log(logBaseX) : minMax.min;
	    }

	    if (options.max === undefined) {
	      options.max = logBaseX && minMax.max ? 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;
	}

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

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

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

	  var _options$fromIndex = options.fromIndex,
	      fromIndex = _options$fromIndex === void 0 ? 0 : _options$fromIndex,
	      _options$toIndex = options.toIndex,
	      toIndex = _options$toIndex === void 0 ? input.length : _options$toIndex;

	  if (fromIndex < 0 || fromIndex >= input.length || !Number.isInteger(fromIndex)) {
	    throw new Error('fromIndex must be a positive integer smaller than length');
	  }

	  if (toIndex <= fromIndex || toIndex > input.length || !Number.isInteger(toIndex)) {
	    throw new Error('toIndex must be an integer greater than fromIndex and at most equal to length');
	  }

	  var maxValue = input[fromIndex];

	  for (var i = fromIndex + 1; i < toIndex; i++) {
	    if (input[i] > maxValue) maxValue = input[i];
	  }

	  return maxValue;
	}

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

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

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

	  var _options$fromIndex = options.fromIndex,
	      fromIndex = _options$fromIndex === void 0 ? 0 : _options$fromIndex,
	      _options$toIndex = options.toIndex,
	      toIndex = _options$toIndex === void 0 ? input.length : _options$toIndex;

	  if (fromIndex < 0 || fromIndex >= input.length || !Number.isInteger(fromIndex)) {
	    throw new Error('fromIndex must be a positive integer smaller than length');
	  }

	  if (toIndex <= fromIndex || toIndex > input.length || !Number.isInteger(toIndex)) {
	    throw new Error('toIndex must be an integer greater than fromIndex and at most equal to length');
	  }

	  var minValue = input[fromIndex];

	  for (var i = fromIndex + 1; i < toIndex; i++) {
	    if (input[i] < minValue) minValue = input[i];
	  }

	  return minValue;
	}

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

	  if (!isAnyArray$4(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$4(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$2(input);
	  var currentMax = max$2(input);

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

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

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

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

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

	  return output;
	}

	const indent = ' '.repeat(2);
	const indentData = ' '.repeat(4);
	function inspectMatrix() {
	  return inspectMatrixWithOptions(this);
	}
	function inspectMatrixWithOptions(matrix) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const {
	    maxRows = 15,
	    maxColumns = 10,
	    maxNumSize = 8
	  } = 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 checkRowIndices(matrix, rowIndices) {
	  if (!isAnyArray$4(rowIndices)) {
	    throw new TypeError('row indices must be an array');
	  }

	  for (let i = 0; i < rowIndices.length; i++) {
	    if (rowIndices[i] < 0 || rowIndices[i] >= matrix.rows) {
	      throw new RangeError('row indices are out of range');
	    }
	  }
	}
	function checkColumnIndices(matrix, columnIndices) {
	  if (!isAnyArray$4(columnIndices)) {
	    throw new TypeError('column indices must be an array');
	  }

	  for (let i = 0; i < columnIndices.length; i++) {
	    if (columnIndices[i] < 0 || columnIndices[i] >= matrix.columns) {
	      throw new RangeError('column indices are out of range');
	    }
	  }
	}
	function checkRange(matrix, startRow, endRow, startColumn, endColumn) {
	  if (arguments.length !== 5) {
	    throw new RangeError('expected 4 arguments');
	  }

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

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

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

	  return array;
	}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

	    let newMatrix = new Matrix$1(newRows, newColumns);

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

	    return newMatrix;
	  }

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

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

	    return vector;
	  }

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

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

	    return vector;
	  }

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

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

	  static rand(rows, columns) {
	    let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};

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

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

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

	    return matrix;
	  }

	  static randInt(rows, columns) {
	    let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};

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

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

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

	    return matrix;
	  }

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

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

	    return matrix;
	  }

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

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

	    return matrix;
	  }

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

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

	    return result;
	  }

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

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

	    return result;
	  }

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

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

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

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

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

	    return this;
	  }

	  to1DArray() {
	    let array = [];

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

	    return array;
	  }

	  to2DArray() {
	    let copy = [];

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

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

	    return copy;
	  }

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

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

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

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

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

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

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

	      return true;
	    }

	    return false;
	  }

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

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

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

	      i++;
	    }

	    return isEchelonForm;
	  }

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

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

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

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

	      i++;
	    }

	    return isReducedEchelonForm;
	  }

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

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

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

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

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

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

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

	        h++;
	        k++;
	      }
	    }

	    return result;
	  }

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

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

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

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

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

	        h--;
	      }
	    }

	    return result;
	  }

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

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

	  repeat() {
	    let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};

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

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

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

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

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

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

	    return matrix;
	  }

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

	    return this;
	  }

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

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

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

	    return row;
	  }

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

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

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

	    return this;
	  }

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

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

	    return this;
	  }

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

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

	    return column;
	  }

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

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

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

	    return this;
	  }

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

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

	    return this;
	  }

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

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

	    return this;
	  }

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

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

	    return this;
	  }

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

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

	    return this;
	  }

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

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

	    return this;
	  }

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

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

	    return this;
	  }

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

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

	    return this;
	  }

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

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

	    return this;
	  }

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

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

	    return this;
	  }

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

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

	    return this;
	  }

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

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

	    return this;
	  }

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

	    switch (by) {
	      case 'row':
	        {
	          const max = new Array(this.rows).fill(Number.NEGATIVE_INFINITY);

	          for (let row = 0; row < this.rows; row++) {
	            for (let column = 0; column < this.columns; column++) {
	              if (this.get(row, column) > max[row]) {
	                max[row] = this.get(row, column);
	              }
	            }
	          }

	          return max;
	        }

	      case 'column':
	        {
	          const max = new Array(this.columns).fill(Number.NEGATIVE_INFINITY);

	          for (let row = 0; row < this.rows; row++) {
	            for (let column = 0; column < this.columns; column++) {
	              if (this.get(row, column) > max[column]) {
	                max[column] = this.get(row, column);
	              }
	            }
	          }

	          return max;
	        }

	      case undefined:
	        {
	          let max = this.get(0, 0);

	          for (let row = 0; row < this.rows; row++) {
	            for (let column = 0; column < this.columns; column++) {
	              if (this.get(row, column) > max) {
	                max = this.get(row, column);
	              }
	            }
	          }

	          return max;
	        }

	      default:
	        throw new Error(`invalid option: ${by}`);
	    }
	  }

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

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

	    return idx;
	  }

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

	    switch (by) {
	      case 'row':
	        {
	          const min = new Array(this.rows).fill(Number.POSITIVE_INFINITY);

	          for (let row = 0; row < this.rows; row++) {
	            for (let column = 0; column < this.columns; column++) {
	              if (this.get(row, column) < min[row]) {
	                min[row] = this.get(row, column);
	              }
	            }
	          }

	          return min;
	        }

	      case 'column':
	        {
	          const min = new Array(this.columns).fill(Number.POSITIVE_INFINITY);

	          for (let row = 0; row < this.rows; row++) {
	            for (let column = 0; column < this.columns; column++) {
	              if (this.get(row, column) < min[column]) {
	                min[column] = this.get(row, column);
	              }
	            }
	          }

	          return min;
	        }

	      case undefined:
	        {
	          let min = this.get(0, 0);

	          for (let row = 0; row < this.rows; row++) {
	            for (let column = 0; column < this.columns; column++) {
	              if (this.get(row, column) < min) {
	                min = this.get(row, column);
	              }
	            }
	          }

	          return min;
	        }

	      default:
	        throw new Error(`invalid option: ${by}`);
	    }
	  }

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

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

	    return idx;
	  }

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

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

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

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

	    return v;
	  }

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

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

	    return idx;
	  }

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

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

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

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

	    return v;
	  }

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

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

	    return idx;
	  }

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

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

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

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

	    return v;
	  }

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

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

	    return idx;
	  }

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

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

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

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

	    return v;
	  }

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

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

	    return idx;
	  }

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

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

	    return diag;
	  }

	  norm() {
	    let type = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'frobenius';
	    let result = 0;

	    if (type === 'max') {
	      return this.max();
	    } else if (type === 'frobenius') {
	      for (let i = 0; i < this.rows; i++) {
	        for (let j = 0; j < this.columns; j++) {
	          result = result + this.get(i, j) * this.get(i, j);
	        }
	      }

	      return Math.sqrt(result);
	    } else {
	      throw new RangeError(`unknown norm type: ${type}`);
	    }
	  }

	  cumulativeSum() {
	    let sum = 0;

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

	    return this;
	  }

	  dot(vector2) {
	    if (AbstractMatrix.isMatrix(vector2)) vector2 = vector2.to1DArray();
	    let vector1 = this.to1DArray();

	    if (vector1.length !== vector2.length) {
	      throw new RangeError('vectors do not have the same size');
	    }

	    let dot = 0;

	    for (let i = 0; i < vector1.length; i++) {
	      dot += vector1[i] * vector2[i];
	    }

	    return dot;
	  }

	  mmul(other) {
	    other = Matrix$1.checkMatrix(other);
	    let m = this.rows;
	    let n = this.columns;
	    let p = other.columns;
	    let result = new Matrix$1(m, p);
	    let Bcolj = new Float64Array(n);

	    for (let j = 0; j < p; j++) {
	      for (let k = 0; k < n; k++) {
	        Bcolj[k] = other.get(k, j);
	      }

	      for (let i = 0; i < m; i++) {
	        let s = 0;

	        for (let k = 0; k < n; k++) {
	          s += this.get(i, k) * Bcolj[k];
	        }

	        result.set(i, j, s);
	      }
	    }

	    return result;
	  }

	  strassen2x2(other) {
	    other = Matrix$1.checkMatrix(other);
	    let result = new Matrix$1(2, 2);
	    const a11 = this.get(0, 0);
	    const b11 = other.get(0, 0);
	    const a12 = this.get(0, 1);
	    const b12 = other.get(0, 1);
	    const a21 = this.get(1, 0);
	    const b21 = other.get(1, 0);
	    const a22 = this.get(1, 1);
	    const b22 = other.get(1, 1); // Compute intermediate values.

	    const m1 = (a11 + a22) * (b11 + b22);
	    const m2 = (a21 + a22) * b11;
	    const m3 = a11 * (b12 - b22);
	    const m4 = a22 * (b21 - b11);
	    const m5 = (a11 + a12) * b22;
	    const m6 = (a21 - a11) * (b11 + b12);
	    const m7 = (a12 - a22) * (b21 + b22); // Combine intermediate values into the output.

	    const c00 = m1 + m4 - m5 + m7;
	    const c01 = m3 + m5;
	    const c10 = m2 + m4;
	    const c11 = m1 - m2 + m3 + m6;
	    result.set(0, 0, c00);
	    result.set(0, 1, c01);
	    result.set(1, 0, c10);
	    result.set(1, 1, c11);
	    return result;
	  }

	  strassen3x3(other) {
	    other = Matrix$1.checkMatrix(other);
	    let result = new Matrix$1(3, 3);
	    const a00 = this.get(0, 0);
	    const a01 = this.get(0, 1);
	    const a02 = this.get(0, 2);
	    const a10 = this.get(1, 0);
	    const a11 = this.get(1, 1);
	    const a12 = this.get(1, 2);
	    const a20 = this.get(2, 0);
	    const a21 = this.get(2, 1);
	    const a22 = this.get(2, 2);
	    const b00 = other.get(0, 0);
	    const b01 = other.get(0, 1);
	    const b02 = other.get(0, 2);
	    const b10 = other.get(1, 0);
	    const b11 = other.get(1, 1);
	    const b12 = other.get(1, 2);
	    const b20 = other.get(2, 0);
	    const b21 = other.get(2, 1);
	    const b22 = other.get(2, 2);
	    const m1 = (a00 + a01 + a02 - a10 - a11 - a21 - a22) * b11;
	    const m2 = (a00 - a10) * (-b01 + b11);
	    const m3 = a11 * (-b00 + b01 + b10 - b11 - b12 - b20 + b22);
	    const m4 = (-a00 + a10 + a11) * (b00 - b01 + b11);
	    const m5 = (a10 + a11) * (-b00 + b01);
	    const m6 = a00 * b00;
	    const m7 = (-a00 + a20 + a21) * (b00 - b02 + b12);
	    const m8 = (-a00 + a20) * (b02 - b12);
	    const m9 = (a20 + a21) * (-b00 + b02);
	    const m10 = (a00 + a01 + a02 - a11 - a12 - a20 - a21) * b12;
	    const m11 = a21 * (-b00 + b02 + b10 - b11 - b12 - b20 + b21);
	    const m12 = (-a02 + a21 + a22) * (b11 + b20 - b21);
	    const m13 = (a02 - a22) * (b11 - b21);
	    const m14 = a02 * b20;
	    const m15 = (a21 + a22) * (-b20 + b21);
	    const m16 = (-a02 + a11 + a12) * (b12 + b20 - b22);
	    const m17 = (a02 - a12) * (b12 - b22);
	    const m18 = (a11 + a12) * (-b20 + b22);
	    const m19 = a01 * b10;
	    const m20 = a12 * b21;
	    const m21 = a10 * b02;
	    const m22 = a20 * b01;
	    const m23 = a22 * b22;
	    const c00 = m6 + m14 + m19;
	    const c01 = m1 + m4 + m5 + m6 + m12 + m14 + m15;
	    const c02 = m6 + m7 + m9 + m10 + m14 + m16 + m18;
	    const c10 = m2 + m3 + m4 + m6 + m14 + m16 + m17;
	    const c11 = m2 + m4 + m5 + m6 + m20;
	    const c12 = m14 + m16 + m17 + m18 + m21;
	    const c20 = m6 + m7 + m8 + m11 + m12 + m13 + m14;
	    const c21 = m12 + m13 + m14 + m15 + m22;
	    const c22 = m6 + m7 + m8 + m9 + m23;
	    result.set(0, 0, c00);
	    result.set(0, 1, c01);
	    result.set(0, 2, c02);
	    result.set(1, 0, c10);
	    result.set(1, 1, c11);
	    result.set(1, 2, c12);
	    result.set(2, 0, c20);
	    result.set(2, 1, c21);
	    result.set(2, 2, c22);
	    return result;
	  }

	  mmulStrassen(y) {
	    y = Matrix$1.checkMatrix(y);
	    let x = this.clone();
	    let r1 = x.rows;
	    let c1 = x.columns;
	    let r2 = y.rows;
	    let c2 = y.columns;

	    if (c1 !== r2) {
	      // eslint-disable-next-line no-console
	      console.warn(`Multiplying ${r1} x ${c1} and ${r2} x ${c2} matrix: dimensions do not match.`);
	    } // Put a matrix into the top left of a matrix of zeros.
	    // `rows` and `cols` are the dimensions of the output matrix.


	    function embed(mat, rows, cols) {
	      let r = mat.rows;
	      let c = mat.columns;

	      if (r === rows && c === cols) {
	        return mat;
	      } else {
	        let resultat = AbstractMatrix.zeros(rows, cols);
	        resultat = resultat.setSubMatrix(mat, 0, 0);
	        return resultat;
	      }
	    } // Make sure both matrices are the same size.
	    // This is exclusively for simplicity:
	    // this algorithm can be implemented with matrices of different sizes.


	    let r = Math.max(r1, r2);
	    let c = Math.max(c1, c2);
	    x = embed(x, r, c);
	    y = embed(y, r, c); // Our recursive multiplication function.

	    function blockMult(a, b, rows, cols) {
	      // For small matrices, resort to naive multiplication.
	      if (rows <= 512 || cols <= 512) {
	        return a.mmul(b); // a is equivalent to this
	      } // Apply dynamic padding.


	      if (rows % 2 === 1 && cols % 2 === 1) {
	        a = embed(a, rows + 1, cols + 1);
	        b = embed(b, rows + 1, cols + 1);
	      } else if (rows % 2 === 1) {
	        a = embed(a, rows + 1, cols);
	        b = embed(b, rows + 1, cols);
	      } else if (cols % 2 === 1) {
	        a = embed(a, rows, cols + 1);
	        b = embed(b, rows, cols + 1);
	      }

	      let halfRows = parseInt(a.rows / 2, 10);
	      let halfCols = parseInt(a.columns / 2, 10); // Subdivide input matrices.

	      let a11 = a.subMatrix(0, halfRows - 1, 0, halfCols - 1);
	      let b11 = b.subMatrix(0, halfRows - 1, 0, halfCols - 1);
	      let a12 = a.subMatrix(0, halfRows - 1, halfCols, a.columns - 1);
	      let b12 = b.subMatrix(0, halfRows - 1, halfCols, b.columns - 1);
	      let a21 = a.subMatrix(halfRows, a.rows - 1, 0, halfCols - 1);
	      let b21 = b.subMatrix(halfRows, b.rows - 1, 0, halfCols - 1);
	      let a22 = a.subMatrix(halfRows, a.rows - 1, halfCols, a.columns - 1);
	      let b22 = b.subMatrix(halfRows, b.rows - 1, halfCols, b.columns - 1); // Compute intermediate values.

	      let m1 = blockMult(AbstractMatrix.add(a11, a22), AbstractMatrix.add(b11, b22), halfRows, halfCols);
	      let m2 = blockMult(AbstractMatrix.add(a21, a22), b11, halfRows, halfCols);
	      let m3 = blockMult(a11, AbstractMatrix.sub(b12, b22), halfRows, halfCols);
	      let m4 = blockMult(a22, AbstractMatrix.sub(b21, b11), halfRows, halfCols);
	      let m5 = blockMult(AbstractMatrix.add(a11, a12), b22, halfRows, halfCols);
	      let m6 = blockMult(AbstractMatrix.sub(a21, a11), AbstractMatrix.add(b11, b12), halfRows, halfCols);
	      let m7 = blockMult(AbstractMatrix.sub(a12, a22), AbstractMatrix.add(b21, b22), halfRows, halfCols); // Combine intermediate values into the output.

	      let c11 = AbstractMatrix.add(m1, m4);
	      c11.sub(m5);
	      c11.add(m7);
	      let c12 = AbstractMatrix.add(m3, m5);
	      let c21 = AbstractMatrix.add(m2, m4);
	      let c22 = AbstractMatrix.sub(m1, m2);
	      c22.add(m3);
	      c22.add(m6); // Crop output to the desired size (undo dynamic padding).

	      let resultat = AbstractMatrix.zeros(2 * c11.rows, 2 * c11.columns);
	      resultat = resultat.setSubMatrix(c11, 0, 0);
	      resultat = resultat.setSubMatrix(c12, c11.rows, 0);
	      resultat = resultat.setSubMatrix(c21, 0, c11.columns);
	      resultat = resultat.setSubMatrix(c22, c11.rows, c11.columns);
	      return resultat.subMatrix(0, rows - 1, 0, cols - 1);
	    }

	    return blockMult(x, y, r, c);
	  }

	  scaleRows() {
	    let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};

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

	    const {
	      min = 0,
	      max = 1
	    } = options;
	    if (!Number.isFinite(min)) throw new TypeError('min must be a number');
	    if (!Number.isFinite(max)) throw new TypeError('max must be a number');
	    if (min >= max) throw new RangeError('min must be smaller than max');
	    let newMatrix = new Matrix$1(this.rows, this.columns);

	    for (let i = 0; i < this.rows; i++) {
	      const row = this.getRow(i);

	      if (row.length > 0) {
	        rescale(row, {
	          min,
	          max,
	          output: row
	        });
	      }

	      newMatrix.setRow(i, row);
	    }

	    return newMatrix;
	  }

	  scaleColumns() {
	    let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};

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

	    const {
	      min = 0,
	      max = 1
	    } = options;
	    if (!Number.isFinite(min)) throw new TypeError('min must be a number');
	    if (!Number.isFinite(max)) throw new TypeError('max must be a number');
	    if (min >= max) throw new RangeError('min must be smaller than max');
	    let newMatrix = new Matrix$1(this.rows, this.columns);

	    for (let i = 0; i < this.columns; i++) {
	      const column = this.getColumn(i);

	      if (column.length) {
	        rescale(column, {
	          min: min,
	          max: max,
	          output: column
	        });
	      }

	      newMatrix.setColumn(i, column);
	    }

	    return newMatrix;
	  }

	  flipRows() {
	    const middle = Math.ceil(this.columns / 2);

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

	    return this;
	  }

	  flipColumns() {
	    const middle = Math.ceil(this.rows / 2);

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

	    return this;
	  }

	  kroneckerProduct(other) {
	    other = Matrix$1.checkMatrix(other);
	    let m = this.rows;
	    let n = this.columns;
	    let p = other.rows;
	    let q = other.columns;
	    let result = new Matrix$1(m * p, n * q);

	    for (let i = 0; i < m; i++) {
	      for (let j = 0; j < n; j++) {
	        for (let k = 0; k < p; k++) {
	          for (let l = 0; l < q; l++) {
	            result.set(p * i + k, q * j + l, this.get(i, j) * other.get(k, l));
	          }
	        }
	      }
	    }

	    return result;
	  }

	  kroneckerSum(other) {
	    other = Matrix$1.checkMatrix(other);

	    if (!this.isSquare() || !other.isSquare()) {
	      throw new Error('Kronecker Sum needs two Square Matrices');
	    }

	    let m = this.rows;
	    let n = other.rows;
	    let AxI = this.kroneckerProduct(Matrix$1.eye(n, n));
	    let IxB = Matrix$1.eye(m, m).kroneckerProduct(other);
	    return AxI.add(IxB);
	  }

	  transpose() {
	    let result = new Matrix$1(this.columns, this.rows);

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

	    return result;
	  }

	  sortRows() {
	    let compareFunction = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : compareNumbers;

	    for (let i = 0; i < this.rows; i++) {
	      this.setRow(i, this.getRow(i).sort(compareFunction));
	    }

	    return this;
	  }

	  sortColumns() {
	    let compareFunction = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : compareNumbers;

	    for (let i = 0; i < this.columns; i++) {
	      this.setColumn(i, this.getColumn(i).sort(compareFunction));
	    }

	    return this;
	  }

	  subMatrix(startRow, endRow, startColumn, endColumn) {
	    checkRange(this, startRow, endRow, startColumn, endColumn);
	    let newMatrix = new Matrix$1(endRow - startRow + 1, endColumn - startColumn + 1);

	    for (let i = startRow; i <= endRow; i++) {
	      for (let j = startColumn; j <= endColumn; j++) {
	        newMatrix.set(i - startRow, j - startColumn, this.get(i, j));
	      }
	    }

	    return newMatrix;
	  }

	  subMatrixRow(indices, startColumn, endColumn) {
	    if (startColumn === undefined) startColumn = 0;
	    if (endColumn === undefined) endColumn = this.columns - 1;

	    if (startColumn > endColumn || startColumn < 0 || startColumn >= this.columns || endColumn < 0 || endColumn >= this.columns) {
	      throw new RangeError('Argument out of range');
	    }

	    let newMatrix = new Matrix$1(indices.length, endColumn - startColumn + 1);

	    for (let i = 0; i < indices.length; i++) {
	      for (let j = startColumn; j <= endColumn; j++) {
	        if (indices[i] < 0 || indices[i] >= this.rows) {
	          throw new RangeError(`Row index out of range: ${indices[i]}`);
	        }

	        newMatrix.set(i, j - startColumn, this.get(indices[i], j));
	      }
	    }

	    return newMatrix;
	  }

	  subMatrixColumn(indices, startRow, endRow) {
	    if (startRow === undefined) startRow = 0;
	    if (endRow === undefined) endRow = this.rows - 1;

	    if (startRow > endRow || startRow < 0 || startRow >= this.rows || endRow < 0 || endRow >= this.rows) {
	      throw new RangeError('Argument out of range');
	    }

	    let newMatrix = new Matrix$1(endRow - startRow + 1, indices.length);

	    for (let i = 0; i < indices.length; i++) {
	      for (let j = startRow; j <= endRow; j++) {
	        if (indices[i] < 0 || indices[i] >= this.columns) {
	          throw new RangeError(`Column index out of range: ${indices[i]}`);
	        }

	        newMatrix.set(j - startRow, i, this.get(j, indices[i]));
	      }
	    }

	    return newMatrix;
	  }

	  setSubMatrix(matrix, startRow, startColumn) {
	    matrix = Matrix$1.checkMatrix(matrix);

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

	    let endRow = startRow + matrix.rows - 1;
	    let endColumn = startColumn + matrix.columns - 1;
	    checkRange(this, startRow, endRow, startColumn, endColumn);

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

	    return this;
	  }

	  selection(rowIndices, columnIndices) {
	    checkRowIndices(this, rowIndices);
	    checkColumnIndices(this, columnIndices);
	    let newMatrix = new Matrix$1(rowIndices.length, columnIndices.length);

	    for (let i = 0; i < rowIndices.length; i++) {
	      let rowIndex = rowIndices[i];

	      for (let j = 0; j < columnIndices.length; j++) {
	        let columnIndex = columnIndices[j];
	        newMatrix.set(i, j, this.get(rowIndex, columnIndex));
	      }
	    }

	    return newMatrix;
	  }

	  trace() {
	    let min = Math.min(this.rows, this.columns);
	    let trace = 0;

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

	    return trace;
	  }

	  clone() {
	    let newMatrix = new Matrix$1(this.rows, this.columns);

	    for (let row = 0; row < this.rows; row++) {
	      for (let column = 0; column < this.columns; column++) {
	        newMatrix.set(row, column, this.get(row, column));
	      }
	    }

	    return newMatrix;
	  }

	  sum(by) {
	    switch (by) {
	      case 'row':
	        return sumByRow(this);

	      case 'column':
	        return sumByColumn(this);

	      case undefined:
	        return sumAll(this);

	      default:
	        throw new Error(`invalid option: ${by}`);
	    }
	  }

	  product(by) {
	    switch (by) {
	      case 'row':
	        return productByRow(this);

	      case 'column':
	        return productByColumn(this);

	      case undefined:
	        return productAll(this);

	      default:
	        throw new Error(`invalid option: ${by}`);
	    }
	  }

	  mean(by) {
	    const sum = this.sum(by);

	    switch (by) {
	      case 'row':
	        {
	          for (let i = 0; i < this.rows; i++) {
	            sum[i] /= this.columns;
	          }

	          return sum;
	        }

	      case 'column':
	        {
	          for (let i = 0; i < this.columns; i++) {
	            sum[i] /= this.rows;
	          }

	          return sum;
	        }

	      case undefined:
	        return sum / this.size;

	      default:
	        throw new Error(`invalid option: ${by}`);
	    }
	  }

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

	    if (typeof by === 'object') {
	      options = by;
	      by = undefined;
	    }

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

	    const {
	      unbiased = true,
	      mean = this.mean(by)
	    } = options;

	    if (typeof unbiased !== 'boolean') {
	      throw new TypeError('unbiased must be a boolean');
	    }

	    switch (by) {
	      case 'row':
	        {
	          if (!isAnyArray$4(mean)) {
	            throw new TypeError('mean must be an array');
	          }

	          return varianceByRow(this, unbiased, mean);
	        }

	      case 'column':
	        {
	          if (!isAnyArray$4(mean)) {
	            throw new TypeError('mean must be an array');
	          }

	          return varianceByColumn(this, unbiased, mean);
	        }

	      case undefined:
	        {
	          if (typeof mean !== 'number') {
	            throw new TypeError('mean must be a number');
	          }

	          return varianceAll(this, unbiased, mean);
	        }

	      default:
	        throw new Error(`invalid option: ${by}`);
	    }
	  }

	  standardDeviation(by, options) {
	    if (typeof by === 'object') {
	      options = by;
	      by = undefined;
	    }

	    const variance = this.variance(by, options);

	    if (by === undefined) {
	      return Math.sqrt(variance);
	    } else {
	      for (let i = 0; i < variance.length; i++) {
	        variance[i] = Math.sqrt(variance[i]);
	      }

	      return variance;
	    }
	  }

	  center(by) {
	    let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};

	    if (typeof by === 'object') {
	      options = by;
	      by = undefined;
	    }

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

	    const {
	      center = this.mean(by)
	    } = options;

	    switch (by) {
	      case 'row':
	        {
	          if (!isAnyArray$4(center)) {
	            throw new TypeError('center must be an array');
	          }

	          centerByRow(this, center);
	          return this;
	        }

	      case 'column':
	        {
	          if (!isAnyArray$4(center)) {
	            throw new TypeError('center must be an array');
	          }

	          centerByColumn(this, center);
	          return this;
	        }

	      case undefined:
	        {
	          if (typeof center !== 'number') {
	            throw new TypeError('center must be a number');
	          }

	          centerAll(this, center);
	          return this;
	        }

	      default:
	        throw new Error(`invalid option: ${by}`);
	    }
	  }

	  scale(by) {
	    let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};

	    if (typeof by === 'object') {
	      options = by;
	      by = undefined;
	    }

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

	    let scale = options.scale;

	    switch (by) {
	      case 'row':
	        {
	          if (scale === undefined) {
	            scale = getScaleByRow(this);
	          } else if (!isAnyArray$4(scale)) {
	            throw new TypeError('scale must be an array');
	          }

	          scaleByRow(this, scale);
	          return this;
	        }

	      case 'column':
	        {
	          if (scale === undefined) {
	            scale = getScaleByColumn(this);
	          } else if (!isAnyArray$4(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$1 extends AbstractMatrix {
	  constructor(nRows, nColumns) {
	    super();

	    if (Matrix$1.isMatrix(nRows)) {
	      // eslint-disable-next-line no-constructor-return
	      return nRows.clone();
	    } else if (Number.isInteger(nRows) && nRows >= 0) {
	      // Create an empty matrix
	      this.data = [];

	      if (Number.isInteger(nColumns) && nColumns >= 0) {
	        for (let i = 0; i < nRows; i++) {
	          this.data.push(new Float64Array(nColumns));
	        }
	      } else {
	        throw new TypeError('nColumns must be a positive integer');
	      }
	    } else if (isAnyArray$4(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$1);

	/**
	 * Performs a Probabilistic quotient normalization (PQN) over the dataset to account dilution based in median spectrum.
	 * Dieterle, F., Ross, A., Schlotterbeck, G., & Senn, H. (2006). Probabilistic quotient normalization as robust method to account for dilution of complex biological mixtures. Application in 1H NMR metabonomics. Analytical chemistry, 78(13), 4281-4290.
	 * DOI: 10.1021/ac051632c
	 *
	 * @param matrix - matrix [rows][cols].
	 * @param options - options
	 * @returns - {data: Normalized dataset, medianOfQuotients: The median of quotients of each variables}
	 */

	function matrixPQN(matrix) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const {
	    max = 100
	  } = options;
	  let matrixB = new Matrix$1(matrix);

	  for (let i = 0; i < matrixB.rows; i++) {
	    const normalizationFactor = matrixB.getRowVector(i).norm('frobenius') / max;
	    const row = matrixB.getRowVector(i).div(normalizationFactor);
	    matrixB.setRow(i, row);
	  }

	  let referenceSpectrum = [];

	  for (let i = 0; i < matrixB.columns; i++) {
	    const currentVariable = matrixB.getColumn(i);
	    referenceSpectrum.push(xMedian(currentVariable));
	  }

	  let medianOfQuotients = [];

	  for (let i = 0; i < matrixB.columns; i++) {
	    let quotients = matrixB.getColumnVector(i).div(referenceSpectrum[i]);
	    medianOfQuotients.push(xMedian(quotients.getColumn(0)));
	  }

	  for (let i = 0; i < matrixB.rows; i++) {
	    matrixB.mulRow(i, 1 / medianOfQuotients[i]);
	  }

	  return {
	    data: matrixB.to2DArray(),
	    medianOfQuotients: medianOfQuotients
	  };
	}

	/**
	 * Rescale columns
	 *
	 * @param matrix - matrix [rows][cols].
	 * @param options - Options
	 */
	function matrixZRescale(matrix) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  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;
	}

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

	/**
	 * Numerically encodes the strings in the matrix and returns an encoding dictionnary which can be used to encode other matrices
	 * @param matrix - original matrix before encoding
	 * @param dictCategoricalToNumerical - dictionnary for encoding
	 * @returns - dictionnary from string to number
	 */

	function matrixNumericalEncoding(matrixInitial) {
	  let matrix = matrixClone(matrixInitial);
	  let nRows = matrix.length;
	  let nColumns = matrix[0].length;
	  let dictCategoricalToNumerical = {};
	  let k = 0;

	  for (let i = 0; i < nRows; i++) {
	    for (let j = 0; j < nColumns; j++) {
	      if (typeof matrix[i][j] === 'number' && matrix[i][j] > k) {
	        k = matrix[i][j];
	      }
	    }
	  }

	  for (let i = 0; i < nRows; i++) {
	    for (let j = 0; j < nColumns; j++) {
	      if (typeof matrix[i][j] === 'string') {
	        if (matrix[i][j] in dictCategoricalToNumerical) {
	          matrix[i][j] = dictCategoricalToNumerical[matrix[i][j]];
	        } else {
	          k++;
	          dictCategoricalToNumerical[matrix[i][j]] = k;
	          matrix[i][j] = k;
	        }
	      }
	    }
	  }

	  return {
	    matrix,
	    dictCategoricalToNumerical
	  };
	}

	/**
	 * Numerically decodes the matrix using the dictionnary
	 * @param matrixInitial - original matrix before encoding
	 * @param dictionnary - dictionary against which to do the encoding
	 * @returns - decoded matrix
	 */

	function swap(dictionnary) {
	  let ret = {};

	  for (let key in dictionnary) {
	    ret[Number(dictionnary[key])] = key;
	  }

	  return ret;
	}

	function matrixNumericalDecoding(matrixInitial, dictionnary) {
	  let matrix = matrixClone(matrixInitial);
	  let invertedDictionnary = swap(dictionnary);

	  for (let i = 0; i < matrix.length; i++) {
	    for (let j = 0; j < matrix[0].length; j++) {
	      if (matrix[i][j] in invertedDictionnary) {
	        matrix[i][j] = invertedDictionnary[matrix[i][j]];
	      }
	    }
	  }

	  return matrix;
	}

	/**
	 * Numerically encodes the strings in the matrix with an encoding dictionary
	 * @param matrixInitial - original matrix before encoding
	 * @param dictionnary - dictionary against which to do the encoding
	 * @returns - encoded matrix
	 */

	function matrixApplyNumericalEncoding(matrixInitial, dictionnary) {
	  let matrix = matrixClone(matrixInitial);
	  let arrayOfValues = [];

	  for (let key in dictionnary) {
	    arrayOfValues.push(dictionnary[key]);
	  }

	  let k = xMaxValue$1(arrayOfValues);

	  for (let i = 0; i < matrix.length; i++) {
	    for (let j = 0; j < matrix[0].length; j++) {
	      if (typeof matrix[i][j] === 'string') {
	        if (matrix[i][j] in dictionnary) {
	          matrix[i][j] = dictionnary[matrix[i][j]];
	        } else {
	          k++;
	          dictionnary[matrix[i][j]] = k;
	          matrix[i][j] = k;
	        }
	      }
	    }
	  }

	  return matrix;
	}

	const LOOP$1 = 8;
	const FLOAT_MUL$1 = 1 / 16777216;
	const sh1$1 = 15;
	const sh2$1 = 18;
	const sh3$1 = 11;

	function multiply_uint32$1(n, m) {
	  n >>>= 0;
	  m >>>= 0;
	  const nlo = n & 0xffff;
	  const nhi = n - nlo;
	  return (nhi * m >>> 0) + nlo * m >>> 0;
	}

	class XSadd$1 {
	  constructor() {
	    let seed = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : Date.now();
	    this.state = new Uint32Array(4);
	    this.init(seed);
	    this.random = this.getFloat.bind(this);
	  }
	  /**
	   * Returns a 32-bit integer r (0 <= r < 2^32)
	   */


	  getUint32() {
	    this.nextState();
	    return this.state[3] + this.state[2] >>> 0;
	  }
	  /**
	   * Returns a floating point number r (0.0 <= r < 1.0)
	   */


	  getFloat() {
	    return (this.getUint32() >>> 8) * FLOAT_MUL$1;
	  }

	  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$1; i++) {
	      this.state[i & 3] ^= i + multiply_uint32$1(1812433253, this.state[i - 1 & 3] ^ this.state[i - 1 & 3] >>> 30 >>> 0) >>> 0;
	    }

	    this.periodCertification();

	    for (let i = 0; i < LOOP$1; 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$1;
	    t ^= t >>> sh2$1;
	    t ^= this.state[3] << sh3$1;
	    this.state[0] = this.state[1];
	    this.state[1] = this.state[2];
	    this.state[2] = this.state[3];
	    this.state[3] = t;
	  }

	}

	/**
	 * Create a random array of numbers of a specific length
	 *
	 * @return - array of random floats normally distributed
	 */

	let spare$1;
	let hasSpare$1 = false;
	function createRandomArray$1() {
	  let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
	  let {
	    mean = 0,
	    standardDeviation = 1,
	    length = 1000,
	    range = 1,
	    seed,
	    distribution = 'normal'
	  } = options;
	  const generator = new XSadd$1(seed);
	  let returnArray = new Float64Array(length);

	  switch (distribution) {
	    case 'normal':
	      for (let i = 0; i < length; i++) {
	        returnArray[i] = generateGaussian$1(mean, standardDeviation, generator);
	      }

	      break;

	    case 'uniform':
	      for (let i = 0; i < length; i++) {
	        returnArray[i] = (generator.random() - 0.5) * range + mean;
	      }

	      break;

	    default:
	      // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
	      throw new Error(`unknown distribution: ${distribution}`);
	  }

	  return returnArray;
	}

	function generateGaussian$1(mean, standardDeviation, generator) {
	  let val, u, v, s;

	  if (hasSpare$1) {
	    hasSpare$1 = false;
	    val = spare$1 * standardDeviation + mean;
	  } else {
	    do {
	      u = generator.random() * 2 - 1;
	      v = generator.random() * 2 - 1;
	      s = u * u + v * v;
	    } while (s >= 1 || s === 0);

	    s = Math.sqrt(-2.0 * Math.log(s) / s);
	    spare$1 = v * s;
	    hasSpare$1 = true;
	    val = mean + standardDeviation * u * s;
	  }

	  return val;
	}

	/**
	 * Create an array with numbers starting from "from" with step "step" of length "length"
	 *
	 * @param options - options
	 * @return - array of distributed numbers with step "step" from "from"
	 */
	function createStepArray() {
	  let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
	  let {
	    from = 0,
	    step = 1,
	    length = 1000
	  } = options;
	  const array = new Float64Array(length);
	  let index = 0;

	  while (index < length) {
	    array[index] = from + step * index;
	    index++;
	  }

	  return array;
	}

	var libEsm$2 = /*#__PURE__*/Object.freeze({
		__proto__: null,
		reimAbsolute: reimAbsolute,
		reimAutoPhaseCorrection: reimAutoPhaseCorrection,
		reimPhaseCorrection: reimPhaseCorrection,
		reimFFT: reimFFT,
		xAbsolute: xAbsolute,
		xAbsoluteMedian: xAbsoluteMedian,
		xAdd: xAdd$1,
		xAutoCorrelation: xAutoCorrelation,
		xBoxPlot: xBoxPlot,
		xCheck: xCheck$1,
		xCorrelation: xCorrelation,
		xCrossCorrelation: xCrossCorrelation,
		xCumulative: xCumulative,
		xDivide: xDivide,
		xDotProduct: xDotProduct,
		xEnsureFloat64: xEnsureFloat64,
		xFindClosestIndex: xFindClosestIndex$2,
		xGetFromToIndex: xGetFromToIndex$1,
		xGetTargetIndex: xGetTargetIndex,
		xHistogram: xHistogram,
		xIsEquallySpaced: xIsEquallySpaced,
		xIsMonotone: xIsMonotone,
		xIsMonotoneIncreasing: xIsMonotoneIncreasing,
		xMaxIndex: xMaxIndex,
		xMaxValue: xMaxValue$1,
		xMean: xMean,
		xMeanAbsoluteError: xMeanAbsoluteError,
		xMeanSquaredError: xMeanSquaredError,
		xMedian: xMedian,
		xMedianAbsoluteDeviation: xMedianAbsoluteDeviation,
		xMinIndex: xMinIndex,
		xMinMaxValues: xMinMaxValues,
		xMinValue: xMinValue,
		xMode: xMode,
		xMultiply: xMultiply,
		xNoiseSanPlot: xNoiseSanPlot,
		xNoiseStandardDeviation: xNoiseStandardDeviation,
		xNorm: xNorm,
		xNormed: xNormed,
		xPadding: xPadding,
		xParetoNormalization: xParetoNormalization,
		xRescale: xRescale,
		xRolling: xRolling,
		xRollingAverage: xRollingAverage,
		xRollingMax: xRollingMax,
		xRollingMedian: xRollingMedian,
		xRollingMin: xRollingMin,
		xRotate: xRotate,
		xSampling: xSampling,
		xSequentialFill: xSequentialFill,
		xStandardDeviation: xStandardDeviation,
		xApplyFunctionStr: xApplyFunctionStr,
		xSubtract: xSubtract,
		xSum: xSum,
		xUniqueSorted: xUniqueSorted,
		getOutputArray: getOutputArray,
		xyAlign: xyAlign,
		xyCheck: xyCheck,
		xyFindClosestPoint: xyFindClosestPoint,
		xyCovariance: xyCovariance,
		xyCumulativeDistributionStatistics: xyCumulativeDistributionStatistics,
		xyEnsureGrowingX: xyEnsureGrowingX,
		xyEquallySpaced: xyEquallySpaced,
		xyExtract: xyExtract,
		xyFilterX: xyFilterX,
		xyFilterXPositive: xyFilterXPositive,
		xyGetNMaxY: xyGetNMaxY,
		xyGrowingX: xyGrowingX,
		xyIntegral: xyIntegral,
		xyIntegration: xyIntegration,
		xyJoinX: xyJoinX,
		xyMaxClosestYPoint: xyMaxClosestYPoint,
		xyMaximaY: xyMaximaY,
		xyMaxMerge: xyMaxMerge,
		xyMaxY: xyMaxY,
		xyMaxYPoint: xyMaxYPoint,
		xyMedian: xyMedian,
		xyMergeByCentroids: xyMergeByCentroids,
		xyMinClosestYPoint: xyMinClosestYPoint,
		xyMinimaY: xyMinimaY,
		xyMinYPoint: xyMinYPoint,
		xyPeakInfo: xyPeakInfo,
		xyRealMaxYPoint: xyRealMaxYPoint,
		xyRealMinYPoint: xyRealMinYPoint,
		xyReduce: xyReduce,
		xyRolling: xyRolling,
		xySetYValue: xySetYValue,
		xySortX: xySortX,
		xyToXYArray: xyToXYArray,
		xyToXYObject: xyToXYObject,
		xyUniqueX: xyUniqueX,
		xyWeightedMerge: xyWeightedMerge,
		xreimZeroFilling: xreimZeroFilling,
		xreimSortX: xreimSortX,
		xyArrayAlign: xyArrayAlign,
		xyArrayMerge: xyArrayMerge,
		xyArrayWeightedMerge: xyArrayWeightedMerge,
		xyArrayAlignToFirst: xyArrayAlignToFirst,
		xyObjectBestPoints: xyObjectBestPoints,
		xyObjectJoinX: xyObjectJoinX,
		xyObjectMaxXPoint: xyObjectMaxXPoint,
		xyObjectMinXPoint: xyObjectMinXPoint,
		xyObjectMaxYPoint: xyObjectMaxYPoint,
		xyObjectMinYPoint: xyObjectMinYPoint,
		xyObjectSlotX: xyObjectSlotX,
		xyObjectSortX: xyObjectSortX,
		xyObjectSumY: xyObjectSumY,
		xyObjectToXY: xyObjectToXY,
		zoneToX: zoneToX,
		zonesNormalize: zonesNormalize,
		matrixCenterZMean: matrixCenterZMean,
		matrixHistogram: matrixHistogram,
		matrixMinMaxZ: matrixMinMaxZ$1,
		matrixMinMaxAbsoluteZ: matrixMinMaxAbsoluteZ,
		matrixPQN: matrixPQN,
		matrixZRescale: matrixZRescale,
		matrixClone: matrixClone,
		matrixNumericalEncoding: matrixNumericalEncoding,
		matrixNumericalDecoding: matrixNumericalDecoding,
		matrixApplyNumericalEncoding: matrixApplyNumericalEncoding,
		createFromToArray: createFromToArray,
		createRandomArray: createRandomArray$1,
		createStepArray: createStepArray
	});

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

	const getMsInfo$3 = getMsInfo$4;
	const xFindClosestIndex$1 = require$$1$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 {boolean}        [options.allowNegativeAtoms=false] - Allow to have negative number of atoms
	 * @param {object}         [options.unsaturation={}]
	 * @param {number}         [options.unsaturation.min=-Infinity] - Minimal unsaturation
	 * @param {number}         [options.unsaturation.max=+Infinity] - Maximal unsaturation
	 * @param {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) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const {
	    ionization = {
	      mf: '',
	      em: 0,
	      charge: 0,
	      atoms: {}
	    },
	    forceIonization = false,
	    precision = 1000,
	    minCharge = Number.MIN_SAFE_INTEGER,
	    maxCharge = Number.MAX_SAFE_INTEGER,
	    unsaturation = {},
	    targetMass,
	    // if present we will calculate the errors
	    targetMasses,
	    // if present we will calculate the smallest error
	    targetIntensities,
	    // if present it will be added in the report
	    minEM = -Infinity,
	    maxEM = +Infinity,
	    minMSEM = -Infinity,
	    maxMSEM = +Infinity,
	    allowNegativeAtoms = false,
	    atoms,
	    callback
	  } = options;
	  let msInfo = getMsInfo$3(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 in entry.atoms) {
	      if (!atoms[atom]) return false;
	      if (entry.atoms[atom] < atoms[atom].min) return false;
	      if (entry.atoms[atom] > atoms[atom].max) return false;
	    }
	  }

	  if (entry.atoms !== undefined && !allowNegativeAtoms) {
	    const ionizationAtoms = msInfo.ionization && msInfo.ionization.atoms || {};
	    const atomKeys = new Set(Object.keys(ionizationAtoms).concat(Object.keys(entry.atoms)));

	    for (let atom of atomKeys) {
	      if ((entry.atoms[atom] || 0) + (ionizationAtoms[atom] || 0) < 0) {
	        return false;
	      }
	    }
	  }

	  if (targetMasses && targetMasses.length > 0) {
	    let index = xFindClosestIndex$1(targetMasses, ms.em);
	    let closestMass = targetMasses[index];
	    msInfo = getMsInfo$3(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$d = {
	  general: generalMatcher,
	  msem: msemMatcher
	};

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

	  let position = -1;
	  let mfs = [];

	  for (let i = 0; i < parts.length; i++) {
	    let part = parts[i];

	    if (!~part.search(/-?[0-9]--?[0-9]/)) {
	      position++;
	      mfs[position] = {
	        mf: part,
	        min: 1,
	        max: 1
	      };
	    } else {
	      let min = part.replace(/^(-?[0-9]*)-(-?[0-9]*)/, '$1') >> 0;
	      let max = part.replace(/^(-?[0-9]*)-(-?[0-9]*)/, '$2') >> 0;
	      mfs[position].min = Math.min(min, max);
	      mfs[position].max = Math.max(min, max);
	    }
	  }

	  let currents = new Array(mfs.length);

	  for (let i = 0; i < currents.length; i++) {
	    currents[i] = mfs[i].min;
	  }

	  position = 0;

	  while (position < currents.length) {
	    if (currents[position] < mfs[position].max) {
	      results.push(getMF(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$6 = MF_1;
	const processRange$1 = processRange_1;

	var preprocessIonizations$5 = function preprocessIonizations() {
	  let ionizationsString = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
	  if (Array.isArray(ionizationsString)) return ionizationsString;
	  let ionizations = ionizationsString.split(/ *[.,;\t\r\n]+ */); // it is allowed to have ranges in Ionizations. We need to explode them.

	  let results = [];

	  for (let ionization of ionizations) {
	    let parts = processRange$1(ionization);

	    for (let part of parts) {
	      let info = new MF$6(part).getInfo();
	      results.push({
	        mf: part,
	        em: info.monoisotopicMass,
	        charge: info.charge,
	        atoms: info.atoms
	      });
	    }
	  }

	  return results;
	};

	/**
	 * 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$3 = sum$1;

	const {
	  ELECTRON_MASS: ELECTRON_MASS$3
	} = constants;
	const MF$5 = src$h.MF;
	const matcher$3 = src$d.msem;
	const preprocessIonizations$4 = preprocessIonizations$5;
	const processRange = processRange_1;
	const sum = lib$3;
	/**
	 * 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}       [options.canonizeMF=true] - Canonize molecular formula
	 * @param {boolean}       [options.uniqueMFs=true] - Force canonization and make MF unique
	 * @param {string}        [options.ionizations=''] - Comma separated list of ionizations (to charge the molecule)
	 * @param {function}      [options.onStep] - Callback to do after each step
	 * @param {object}        [options.filter={}] - Minimal monoisotopic mass
	 * @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 {boolean}       [options.filter.allowNegativeAtoms=false] - Allow to have negative number of atoms
	 * @param {object}        [options.filter.unsaturation={}]
	 * @param {number}        [options.filter.unsaturation.min=-Infinity] - Minimal unsaturation
	 * @param {number}        [options.filter.unsaturation.max=+Infinity] - Maximal unsaturation
	 * @param {boolean}       [options.filter.unsaturation.onlyInteger=false] - Integer unsaturation
	 * @param {boolean}       [options.filter.unsaturation.onlyNonInteger=false] - Non integer unsaturation
	 * @param {object}        [options.filter.atoms] - object of atom:{min, max}
	 * @param {function}      [options.filter.callback] - a function to filter the MF
	 * @param {string}        [options.filterFct]
	 * @param {object}        [options.links]
	 * @param {boolean}       [options.links.filter] We filter all the MF that do not match the '*X'
	 * @returns {Promise}
	 */

	var src$c = async function generateMFs(keys) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  options = { ...options
	  };
	  let {
	    limit = 100000,
	    uniqueMFs = true,
	    estimate = false,
	    onStep
	  } = 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$4(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]) {
	      if (onStep) await onStep(evolution);
	      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$5(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) {
	  let options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
	  const {
	    canonizeMF,
	    filter,
	    ionizations,
	    links = {}
	  } = options; // this script is designed to combine molecular formula
	  // that may contain comments after a "$" sign
	  // therefore we should put all the comments at the ned

	  if (links.filter) {
	    let 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$3) / Math.abs(ionization.charge), result.charge + result.ionization.charge, result.unsaturation, result.atoms);
	      if (!options.filterFct.apply(null, variables)) continue;
	    }

	    result.parts = [];
	    result.mf = '';
	    let comments = [];

	    for (let i = 0; i < keys.length; i++) {
	      let key = keys[i][currents[i]];

	      if (key) {
	        if (key.indexOf('$') > -1) {
	          comments.push(key.replace(/^[^$]*\$/, ''));
	          key = key.replace(/\$.*/, '');
	        }

	        result.parts[i] = key;
	        result.mf += key;
	      }
	    }

	    if (comments.length > 0) {
	      result.comment = comments.join(' ');
	    }

	    let match = matcher$3(result, filter);
	    if (!match) continue;
	    result.ms = match.ms;
	    result.ionization = match.ionization;

	    if (canonizeMF) {
	      result.mf = new MF$5(result.mf).toMF();
	    }

	    results.push(result);
	  }
	}

	var papaparse_min = {exports: {}};

	/* @license
	Papa Parse
	v5.3.2
	https://github.com/mholt/PapaParse
	License: MIT
	*/

	(function (module, exports) {
	  !function (e, t) {
	    module.exports = t() ;
	  }(commonjsGlobal, function s() {

	    var f = "undefined" != typeof self ? self : "undefined" != typeof window ? window : void 0 !== f ? f : {};
	    var n = !f.document && !!f.postMessage,
	        o = n && /blob:/i.test((f.location || {}).protocol),
	        a = {},
	        h = 0,
	        b = {
	      parse: function (e, t) {
	        var i = (t = t || {}).dynamicTyping || !1;
	        M(i) && (t.dynamicTypingFunction = i, i = {});

	        if (t.dynamicTyping = i, t.transform = !!M(t.transform) && t.transform, t.worker && b.WORKERS_SUPPORTED) {
	          var r = function () {
	            if (!b.WORKERS_SUPPORTED) return !1;
	            var e = (i = f.URL || f.webkitURL || null, r = s.toString(), b.BLOB_URL || (b.BLOB_URL = i.createObjectURL(new Blob(["(", r, ")();"], {
	              type: "text/javascript"
	            })))),
	                t = new f.Worker(e);
	            var i, r;
	            return t.onmessage = _, t.id = h++, a[t.id] = t;
	          }();

	          return r.userStep = t.step, r.userChunk = t.chunk, r.userComplete = t.complete, r.userError = t.error, t.step = M(t.step), t.chunk = M(t.chunk), t.complete = M(t.complete), t.error = M(t.error), delete t.worker, void r.postMessage({
	            input: e,
	            config: t,
	            workerId: r.id
	          });
	        }

	        var n = null;
	        b.NODE_STREAM_INPUT, "string" == typeof e ? n = t.download ? new l(t) : new p(t) : !0 === e.readable && M(e.read) && M(e.on) ? n = new g(t) : (f.File && e instanceof File || e instanceof Object) && (n = new c(t));
	        return n.stream(e);
	      },
	      unparse: function (e, t) {
	        var n = !1,
	            _ = !0,
	            m = ",",
	            y = "\r\n",
	            s = '"',
	            a = s + s,
	            i = !1,
	            r = null,
	            o = !1;

	        !function () {
	          if ("object" != typeof t) return;
	          "string" != typeof t.delimiter || b.BAD_DELIMITERS.filter(function (e) {
	            return -1 !== t.delimiter.indexOf(e);
	          }).length || (m = t.delimiter);
	          ("boolean" == typeof t.quotes || "function" == typeof t.quotes || Array.isArray(t.quotes)) && (n = t.quotes);
	          "boolean" != typeof t.skipEmptyLines && "string" != typeof t.skipEmptyLines || (i = t.skipEmptyLines);
	          "string" == typeof t.newline && (y = t.newline);
	          "string" == typeof t.quoteChar && (s = t.quoteChar);
	          "boolean" == typeof t.header && (_ = t.header);

	          if (Array.isArray(t.columns)) {
	            if (0 === t.columns.length) throw new Error("Option columns is empty");
	            r = t.columns;
	          }

	          void 0 !== t.escapeChar && (a = t.escapeChar + s);
	          ("boolean" == typeof t.escapeFormulae || t.escapeFormulae instanceof RegExp) && (o = t.escapeFormulae instanceof RegExp ? t.escapeFormulae : /^[=+\-@\t\r].*$/);
	        }();
	        var h = new RegExp(j(s), "g");
	        "string" == typeof e && (e = JSON.parse(e));

	        if (Array.isArray(e)) {
	          if (!e.length || Array.isArray(e[0])) return u(null, e, i);
	          if ("object" == typeof e[0]) return u(r || Object.keys(e[0]), e, i);
	        } else if ("object" == typeof e) return "string" == typeof e.data && (e.data = JSON.parse(e.data)), Array.isArray(e.data) && (e.fields || (e.fields = e.meta && e.meta.fields || r), e.fields || (e.fields = Array.isArray(e.data[0]) ? e.fields : "object" == typeof e.data[0] ? Object.keys(e.data[0]) : []), Array.isArray(e.data[0]) || "object" == typeof e.data[0] || (e.data = [e.data])), u(e.fields || [], e.data || [], i);

	        throw new Error("Unable to serialize unrecognized input");

	        function u(e, t, i) {
	          var r = "";
	          "string" == typeof e && (e = JSON.parse(e)), "string" == typeof t && (t = JSON.parse(t));
	          var n = Array.isArray(e) && 0 < e.length,
	              s = !Array.isArray(t[0]);

	          if (n && _) {
	            for (var a = 0; a < e.length; a++) 0 < a && (r += m), r += v(e[a], a);

	            0 < t.length && (r += y);
	          }

	          for (var o = 0; o < t.length; o++) {
	            var h = n ? e.length : t[o].length,
	                u = !1,
	                f = n ? 0 === Object.keys(t[o]).length : 0 === t[o].length;

	            if (i && !n && (u = "greedy" === i ? "" === t[o].join("").trim() : 1 === t[o].length && 0 === t[o][0].length), "greedy" === i && n) {
	              for (var d = [], l = 0; l < h; l++) {
	                var c = s ? e[l] : l;
	                d.push(t[o][c]);
	              }

	              u = "" === d.join("").trim();
	            }

	            if (!u) {
	              for (var p = 0; p < h; p++) {
	                0 < p && !f && (r += m);
	                var g = n && s ? e[p] : p;
	                r += v(t[o][g], p);
	              }

	              o < t.length - 1 && (!i || 0 < h && !f) && (r += y);
	            }
	          }

	          return r;
	        }

	        function v(e, t) {
	          if (null == e) return "";
	          if (e.constructor === Date) return JSON.stringify(e).slice(1, 25);
	          var i = !1;
	          o && "string" == typeof e && o.test(e) && (e = "'" + e, i = !0);
	          var r = e.toString().replace(h, a);
	          return (i = i || !0 === n || "function" == typeof n && n(e, t) || Array.isArray(n) && n[t] || function (e, t) {
	            for (var i = 0; i < t.length; i++) if (-1 < e.indexOf(t[i])) return !0;

	            return !1;
	          }(r, b.BAD_DELIMITERS) || -1 < r.indexOf(m) || " " === r.charAt(0) || " " === r.charAt(r.length - 1)) ? s + r + s : r;
	        }
	      }
	    };

	    if (b.RECORD_SEP = String.fromCharCode(30), b.UNIT_SEP = String.fromCharCode(31), b.BYTE_ORDER_MARK = "\ufeff", b.BAD_DELIMITERS = ["\r", "\n", '"', b.BYTE_ORDER_MARK], b.WORKERS_SUPPORTED = !n && !!f.Worker, b.NODE_STREAM_INPUT = 1, b.LocalChunkSize = 10485760, b.RemoteChunkSize = 5242880, b.DefaultDelimiter = ",", b.Parser = E, b.ParserHandle = i, b.NetworkStreamer = l, b.FileStreamer = c, b.StringStreamer = p, b.ReadableStreamStreamer = g, f.jQuery) {
	      var d = f.jQuery;

	      d.fn.parse = function (o) {
	        var i = o.config || {},
	            h = [];
	        return this.each(function (e) {
	          if (!("INPUT" === d(this).prop("tagName").toUpperCase() && "file" === d(this).attr("type").toLowerCase() && f.FileReader) || !this.files || 0 === this.files.length) return !0;

	          for (var t = 0; t < this.files.length; t++) h.push({
	            file: this.files[t],
	            inputElem: this,
	            instanceConfig: d.extend({}, i)
	          });
	        }), e(), this;

	        function e() {
	          if (0 !== h.length) {
	            var e,
	                t,
	                i,
	                r,
	                n = h[0];

	            if (M(o.before)) {
	              var s = o.before(n.file, n.inputElem);

	              if ("object" == typeof s) {
	                if ("abort" === s.action) return e = "AbortError", t = n.file, i = n.inputElem, r = s.reason, void (M(o.error) && o.error({
	                  name: e
	                }, t, i, r));
	                if ("skip" === s.action) return void u();
	                "object" == typeof s.config && (n.instanceConfig = d.extend(n.instanceConfig, s.config));
	              } else if ("skip" === s) return void u();
	            }

	            var a = n.instanceConfig.complete;
	            n.instanceConfig.complete = function (e) {
	              M(a) && a(e, n.file, n.inputElem), u();
	            }, b.parse(n.file, n.instanceConfig);
	          } else M(o.complete) && o.complete();
	        }

	        function u() {
	          h.splice(0, 1), e();
	        }
	      };
	    }

	    function u(e) {
	      this._handle = null, this._finished = !1, this._completed = !1, this._halted = !1, this._input = null, this._baseIndex = 0, this._partialLine = "", this._rowCount = 0, this._start = 0, this._nextChunk = null, this.isFirstChunk = !0, this._completeResults = {
	        data: [],
	        errors: [],
	        meta: {}
	      }, function (e) {
	        var t = w(e);
	        t.chunkSize = parseInt(t.chunkSize), e.step || e.chunk || (t.chunkSize = null);
	        this._handle = new i(t), (this._handle.streamer = this)._config = t;
	      }.call(this, e), this.parseChunk = function (e, t) {
	        if (this.isFirstChunk && M(this._config.beforeFirstChunk)) {
	          var i = this._config.beforeFirstChunk(e);

	          void 0 !== i && (e = i);
	        }

	        this.isFirstChunk = !1, this._halted = !1;
	        var r = this._partialLine + e;
	        this._partialLine = "";

	        var n = this._handle.parse(r, this._baseIndex, !this._finished);

	        if (!this._handle.paused() && !this._handle.aborted()) {
	          var s = n.meta.cursor;
	          this._finished || (this._partialLine = r.substring(s - this._baseIndex), this._baseIndex = s), n && n.data && (this._rowCount += n.data.length);
	          var a = this._finished || this._config.preview && this._rowCount >= this._config.preview;
	          if (o) f.postMessage({
	            results: n,
	            workerId: b.WORKER_ID,
	            finished: a
	          });else if (M(this._config.chunk) && !t) {
	            if (this._config.chunk(n, this._handle), this._handle.paused() || this._handle.aborted()) return void (this._halted = !0);
	            n = void 0, this._completeResults = void 0;
	          }
	          return this._config.step || this._config.chunk || (this._completeResults.data = this._completeResults.data.concat(n.data), this._completeResults.errors = this._completeResults.errors.concat(n.errors), this._completeResults.meta = n.meta), this._completed || !a || !M(this._config.complete) || n && n.meta.aborted || (this._config.complete(this._completeResults, this._input), this._completed = !0), a || n && n.meta.paused || this._nextChunk(), n;
	        }

	        this._halted = !0;
	      }, this._sendError = function (e) {
	        M(this._config.error) ? this._config.error(e) : o && this._config.error && f.postMessage({
	          workerId: b.WORKER_ID,
	          error: e,
	          finished: !1
	        });
	      };
	    }

	    function l(e) {
	      var r;
	      (e = e || {}).chunkSize || (e.chunkSize = b.RemoteChunkSize), u.call(this, e), this._nextChunk = n ? function () {
	        this._readChunk(), this._chunkLoaded();
	      } : function () {
	        this._readChunk();
	      }, this.stream = function (e) {
	        this._input = e, this._nextChunk();
	      }, this._readChunk = function () {
	        if (this._finished) this._chunkLoaded();else {
	          if (r = new XMLHttpRequest(), this._config.withCredentials && (r.withCredentials = this._config.withCredentials), n || (r.onload = v(this._chunkLoaded, this), r.onerror = v(this._chunkError, this)), r.open(this._config.downloadRequestBody ? "POST" : "GET", this._input, !n), this._config.downloadRequestHeaders) {
	            var e = this._config.downloadRequestHeaders;

	            for (var t in e) r.setRequestHeader(t, e[t]);
	          }

	          if (this._config.chunkSize) {
	            var i = this._start + this._config.chunkSize - 1;
	            r.setRequestHeader("Range", "bytes=" + this._start + "-" + i);
	          }

	          try {
	            r.send(this._config.downloadRequestBody);
	          } catch (e) {
	            this._chunkError(e.message);
	          }

	          n && 0 === r.status && this._chunkError();
	        }
	      }, this._chunkLoaded = function () {
	        4 === r.readyState && (r.status < 200 || 400 <= r.status ? this._chunkError() : (this._start += this._config.chunkSize ? this._config.chunkSize : r.responseText.length, this._finished = !this._config.chunkSize || this._start >= function (e) {
	          var t = e.getResponseHeader("Content-Range");
	          if (null === t) return -1;
	          return parseInt(t.substring(t.lastIndexOf("/") + 1));
	        }(r), this.parseChunk(r.responseText)));
	      }, this._chunkError = function (e) {
	        var t = r.statusText || e;

	        this._sendError(new Error(t));
	      };
	    }

	    function c(e) {
	      var r, n;
	      (e = e || {}).chunkSize || (e.chunkSize = b.LocalChunkSize), u.call(this, e);
	      var s = "undefined" != typeof FileReader;
	      this.stream = function (e) {
	        this._input = e, n = e.slice || e.webkitSlice || e.mozSlice, s ? ((r = new FileReader()).onload = v(this._chunkLoaded, this), r.onerror = v(this._chunkError, this)) : r = new FileReaderSync(), this._nextChunk();
	      }, this._nextChunk = function () {
	        this._finished || this._config.preview && !(this._rowCount < this._config.preview) || this._readChunk();
	      }, this._readChunk = function () {
	        var e = this._input;

	        if (this._config.chunkSize) {
	          var t = Math.min(this._start + this._config.chunkSize, this._input.size);
	          e = n.call(e, this._start, t);
	        }

	        var i = r.readAsText(e, this._config.encoding);
	        s || this._chunkLoaded({
	          target: {
	            result: i
	          }
	        });
	      }, this._chunkLoaded = function (e) {
	        this._start += this._config.chunkSize, this._finished = !this._config.chunkSize || this._start >= this._input.size, this.parseChunk(e.target.result);
	      }, this._chunkError = function () {
	        this._sendError(r.error);
	      };
	    }

	    function p(e) {
	      var i;
	      u.call(this, e = e || {}), this.stream = function (e) {
	        return i = e, this._nextChunk();
	      }, this._nextChunk = function () {
	        if (!this._finished) {
	          var e,
	              t = this._config.chunkSize;
	          return t ? (e = i.substring(0, t), i = i.substring(t)) : (e = i, i = ""), this._finished = !i, this.parseChunk(e);
	        }
	      };
	    }

	    function g(e) {
	      u.call(this, e = e || {});
	      var t = [],
	          i = !0,
	          r = !1;
	      this.pause = function () {
	        u.prototype.pause.apply(this, arguments), this._input.pause();
	      }, this.resume = function () {
	        u.prototype.resume.apply(this, arguments), this._input.resume();
	      }, this.stream = function (e) {
	        this._input = e, this._input.on("data", this._streamData), this._input.on("end", this._streamEnd), this._input.on("error", this._streamError);
	      }, this._checkIsFinished = function () {
	        r && 1 === t.length && (this._finished = !0);
	      }, this._nextChunk = function () {
	        this._checkIsFinished(), t.length ? this.parseChunk(t.shift()) : i = !0;
	      }, this._streamData = v(function (e) {
	        try {
	          t.push("string" == typeof e ? e : e.toString(this._config.encoding)), i && (i = !1, this._checkIsFinished(), this.parseChunk(t.shift()));
	        } catch (e) {
	          this._streamError(e);
	        }
	      }, this), this._streamError = v(function (e) {
	        this._streamCleanUp(), this._sendError(e);
	      }, this), this._streamEnd = v(function () {
	        this._streamCleanUp(), r = !0, this._streamData("");
	      }, this), this._streamCleanUp = v(function () {
	        this._input.removeListener("data", this._streamData), this._input.removeListener("end", this._streamEnd), this._input.removeListener("error", this._streamError);
	      }, this);
	    }

	    function i(m) {
	      var a,
	          o,
	          h,
	          r = Math.pow(2, 53),
	          n = -r,
	          s = /^\s*-?(\d+\.?|\.\d+|\d+\.\d+)([eE][-+]?\d+)?\s*$/,
	          u = /^(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))$/,
	          t = this,
	          i = 0,
	          f = 0,
	          d = !1,
	          e = !1,
	          l = [],
	          c = {
	        data: [],
	        errors: [],
	        meta: {}
	      };

	      if (M(m.step)) {
	        var p = m.step;

	        m.step = function (e) {
	          if (c = e, _()) g();else {
	            if (g(), 0 === c.data.length) return;
	            i += e.data.length, m.preview && i > m.preview ? o.abort() : (c.data = c.data[0], p(c, t));
	          }
	        };
	      }

	      function y(e) {
	        return "greedy" === m.skipEmptyLines ? "" === e.join("").trim() : 1 === e.length && 0 === e[0].length;
	      }

	      function g() {
	        return c && h && (k("Delimiter", "UndetectableDelimiter", "Unable to auto-detect delimiting character; defaulted to '" + b.DefaultDelimiter + "'"), h = !1), m.skipEmptyLines && (c.data = c.data.filter(function (e) {
	          return !y(e);
	        })), _() && function () {
	          if (!c) return;

	          function e(e, t) {
	            M(m.transformHeader) && (e = m.transformHeader(e, t)), l.push(e);
	          }

	          if (Array.isArray(c.data[0])) {
	            for (var t = 0; _() && t < c.data.length; t++) c.data[t].forEach(e);

	            c.data.splice(0, 1);
	          } else c.data.forEach(e);
	        }(), function () {
	          if (!c || !m.header && !m.dynamicTyping && !m.transform) return c;

	          function e(e, t) {
	            var i,
	                r = m.header ? {} : [];

	            for (i = 0; i < e.length; i++) {
	              var n = i,
	                  s = e[i];
	              m.header && (n = i >= l.length ? "__parsed_extra" : l[i]), m.transform && (s = m.transform(s, n)), s = v(n, s), "__parsed_extra" === n ? (r[n] = r[n] || [], r[n].push(s)) : r[n] = s;
	            }

	            return m.header && (i > l.length ? k("FieldMismatch", "TooManyFields", "Too many fields: expected " + l.length + " fields but parsed " + i, f + t) : i < l.length && k("FieldMismatch", "TooFewFields", "Too few fields: expected " + l.length + " fields but parsed " + i, f + t)), r;
	          }

	          var t = 1;
	          !c.data.length || Array.isArray(c.data[0]) ? (c.data = c.data.map(e), t = c.data.length) : c.data = e(c.data, 0);
	          m.header && c.meta && (c.meta.fields = l);
	          return f += t, c;
	        }();
	      }

	      function _() {
	        return m.header && 0 === l.length;
	      }

	      function v(e, t) {
	        return i = e, m.dynamicTypingFunction && void 0 === m.dynamicTyping[i] && (m.dynamicTyping[i] = m.dynamicTypingFunction(i)), !0 === (m.dynamicTyping[i] || m.dynamicTyping) ? "true" === t || "TRUE" === t || "false" !== t && "FALSE" !== t && (function (e) {
	          if (s.test(e)) {
	            var t = parseFloat(e);
	            if (n < t && t < r) return !0;
	          }

	          return !1;
	        }(t) ? parseFloat(t) : u.test(t) ? new Date(t) : "" === t ? null : t) : t;
	        var i;
	      }

	      function k(e, t, i, r) {
	        var n = {
	          type: e,
	          code: t,
	          message: i
	        };
	        void 0 !== r && (n.row = r), c.errors.push(n);
	      }

	      this.parse = function (e, t, i) {
	        var r = m.quoteChar || '"';
	        if (m.newline || (m.newline = function (e, t) {
	          e = e.substring(0, 1048576);
	          var i = new RegExp(j(t) + "([^]*?)" + j(t), "gm"),
	              r = (e = e.replace(i, "")).split("\r"),
	              n = e.split("\n"),
	              s = 1 < n.length && n[0].length < r[0].length;
	          if (1 === r.length || s) return "\n";

	          for (var a = 0, o = 0; o < r.length; o++) "\n" === r[o][0] && a++;

	          return a >= r.length / 2 ? "\r\n" : "\r";
	        }(e, r)), h = !1, m.delimiter) M(m.delimiter) && (m.delimiter = m.delimiter(e), c.meta.delimiter = m.delimiter);else {
	          var n = function (e, t, i, r, n) {
	            var s, a, o, h;
	            n = n || [",", "\t", "|", ";", b.RECORD_SEP, b.UNIT_SEP];

	            for (var u = 0; u < n.length; u++) {
	              var f = n[u],
	                  d = 0,
	                  l = 0,
	                  c = 0;
	              o = void 0;

	              for (var p = new E({
	                comments: r,
	                delimiter: f,
	                newline: t,
	                preview: 10
	              }).parse(e), g = 0; g < p.data.length; g++) if (i && y(p.data[g])) c++;else {
	                var _ = p.data[g].length;
	                l += _, void 0 !== o ? 0 < _ && (d += Math.abs(_ - o), o = _) : o = _;
	              }

	              0 < p.data.length && (l /= p.data.length - c), (void 0 === a || d <= a) && (void 0 === h || h < l) && 1.99 < l && (a = d, s = f, h = l);
	            }

	            return {
	              successful: !!(m.delimiter = s),
	              bestDelimiter: s
	            };
	          }(e, m.newline, m.skipEmptyLines, m.comments, m.delimitersToGuess);

	          n.successful ? m.delimiter = n.bestDelimiter : (h = !0, m.delimiter = b.DefaultDelimiter), c.meta.delimiter = m.delimiter;
	        }
	        var s = w(m);
	        return m.preview && m.header && s.preview++, a = e, o = new E(s), c = o.parse(a, t, i), g(), d ? {
	          meta: {
	            paused: !0
	          }
	        } : c || {
	          meta: {
	            paused: !1
	          }
	        };
	      }, this.paused = function () {
	        return d;
	      }, this.pause = function () {
	        d = !0, o.abort(), a = M(m.chunk) ? "" : a.substring(o.getCharIndex());
	      }, this.resume = function () {
	        t.streamer._halted ? (d = !1, t.streamer.parseChunk(a, !0)) : setTimeout(t.resume, 3);
	      }, this.aborted = function () {
	        return e;
	      }, this.abort = function () {
	        e = !0, o.abort(), c.meta.aborted = !0, M(m.complete) && m.complete(c), a = "";
	      };
	    }

	    function j(e) {
	      return e.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
	    }

	    function E(e) {
	      var S,
	          O = (e = e || {}).delimiter,
	          x = e.newline,
	          I = e.comments,
	          T = e.step,
	          D = e.preview,
	          A = e.fastMode,
	          L = S = void 0 === e.quoteChar || null === e.quoteChar ? '"' : e.quoteChar;
	      if (void 0 !== e.escapeChar && (L = e.escapeChar), ("string" != typeof O || -1 < b.BAD_DELIMITERS.indexOf(O)) && (O = ","), I === O) throw new Error("Comment character same as delimiter");
	      !0 === I ? I = "#" : ("string" != typeof I || -1 < b.BAD_DELIMITERS.indexOf(I)) && (I = !1), "\n" !== x && "\r" !== x && "\r\n" !== x && (x = "\n");
	      var F = 0,
	          z = !1;
	      this.parse = function (r, t, i) {
	        if ("string" != typeof r) throw new Error("Input must be a string");
	        var n = r.length,
	            e = O.length,
	            s = x.length,
	            a = I.length,
	            o = M(T),
	            h = [],
	            u = [],
	            f = [],
	            d = F = 0;
	        if (!r) return C();

	        if (A || !1 !== A && -1 === r.indexOf(S)) {
	          for (var l = r.split(x), c = 0; c < l.length; c++) {
	            if (f = l[c], F += f.length, c !== l.length - 1) F += x.length;else if (i) return C();

	            if (!I || f.substring(0, a) !== I) {
	              if (o) {
	                if (h = [], k(f.split(O)), R(), z) return C();
	              } else k(f.split(O));

	              if (D && D <= c) return h = h.slice(0, D), C(!0);
	            }
	          }

	          return C();
	        }

	        for (var p = r.indexOf(O, F), g = r.indexOf(x, F), _ = new RegExp(j(L) + j(S), "g"), m = r.indexOf(S, F);;) if (r[F] !== S) {
	          if (I && 0 === f.length && r.substring(F, F + a) === I) {
	            if (-1 === g) return C();
	            F = g + s, g = r.indexOf(x, F), p = r.indexOf(O, F);
	          } else if (-1 !== p && (p < g || -1 === g)) f.push(r.substring(F, p)), F = p + e, p = r.indexOf(O, F);else {
	            if (-1 === g) break;
	            if (f.push(r.substring(F, g)), w(g + s), o && (R(), z)) return C();
	            if (D && h.length >= D) return C(!0);
	          }
	        } else for (m = F, F++;;) {
	          if (-1 === (m = r.indexOf(S, m + 1))) return i || u.push({
	            type: "Quotes",
	            code: "MissingQuotes",
	            message: "Quoted field unterminated",
	            row: h.length,
	            index: F
	          }), E();
	          if (m === n - 1) return E(r.substring(F, m).replace(_, S));

	          if (S !== L || r[m + 1] !== L) {
	            if (S === L || 0 === m || r[m - 1] !== L) {
	              -1 !== p && p < m + 1 && (p = r.indexOf(O, m + 1)), -1 !== g && g < m + 1 && (g = r.indexOf(x, m + 1));
	              var y = b(-1 === g ? p : Math.min(p, g));

	              if (r.substr(m + 1 + y, e) === O) {
	                f.push(r.substring(F, m).replace(_, S)), r[F = m + 1 + y + e] !== S && (m = r.indexOf(S, F)), p = r.indexOf(O, F), g = r.indexOf(x, F);
	                break;
	              }

	              var v = b(g);

	              if (r.substring(m + 1 + v, m + 1 + v + s) === x) {
	                if (f.push(r.substring(F, m).replace(_, S)), w(m + 1 + v + s), p = r.indexOf(O, F), m = r.indexOf(S, F), o && (R(), z)) return C();
	                if (D && h.length >= D) return C(!0);
	                break;
	              }

	              u.push({
	                type: "Quotes",
	                code: "InvalidQuotes",
	                message: "Trailing quote on quoted field is malformed",
	                row: h.length,
	                index: F
	              }), m++;
	            }
	          } else m++;
	        }

	        return E();

	        function k(e) {
	          h.push(e), d = F;
	        }

	        function b(e) {
	          var t = 0;

	          if (-1 !== e) {
	            var i = r.substring(m + 1, e);
	            i && "" === i.trim() && (t = i.length);
	          }

	          return t;
	        }

	        function E(e) {
	          return i || (void 0 === e && (e = r.substring(F)), f.push(e), F = n, k(f), o && R()), C();
	        }

	        function w(e) {
	          F = e, k(f), f = [], g = r.indexOf(x, F);
	        }

	        function C(e) {
	          return {
	            data: h,
	            errors: u,
	            meta: {
	              delimiter: O,
	              linebreak: x,
	              aborted: z,
	              truncated: !!e,
	              cursor: d + (t || 0)
	            }
	          };
	        }

	        function R() {
	          T(C()), h = [], u = [];
	        }
	      }, this.abort = function () {
	        z = !0;
	      }, this.getCharIndex = function () {
	        return F;
	      };
	    }

	    function _(e) {
	      var t = e.data,
	          i = a[t.workerId],
	          r = !1;
	      if (t.error) i.userError(t.error, t.file);else if (t.results && t.results.data) {
	        var n = {
	          abort: function () {
	            r = !0, m(t.workerId, {
	              data: [],
	              errors: [],
	              meta: {
	                aborted: !0
	              }
	            });
	          },
	          pause: y,
	          resume: y
	        };

	        if (M(i.userStep)) {
	          for (var s = 0; s < t.results.data.length && (i.userStep({
	            data: t.results.data[s],
	            errors: t.results.errors,
	            meta: t.results.meta
	          }, n), !r); s++);

	          delete t.results;
	        } else M(i.userChunk) && (i.userChunk(t.results, n, t.file), delete t.results);
	      }
	      t.finished && !r && m(t.workerId, t.results);
	    }

	    function m(e, t) {
	      var i = a[e];
	      M(i.userComplete) && i.userComplete(t), i.terminate(), delete a[e];
	    }

	    function y() {
	      throw new Error("Not implemented.");
	    }

	    function w(e) {
	      if ("object" != typeof e || null === e) return e;
	      var t = Array.isArray(e) ? [] : {};

	      for (var i in e) t[i] = w(e[i]);

	      return t;
	    }

	    function v(e, t) {
	      return function () {
	        e.apply(t, arguments);
	      };
	    }

	    function M(e) {
	      return "function" == typeof e;
	    }

	    return o && (f.onmessage = function (e) {
	      var t = e.data;
	      void 0 === b.WORKER_ID && t && (b.WORKER_ID = t.workerId);
	      if ("string" == typeof t.input) f.postMessage({
	        workerId: b.WORKER_ID,
	        results: b.parse(t.input, t.config),
	        finished: !0
	      });else if (f.File && t.input instanceof File || t.input instanceof Object) {
	        var i = b.parse(t.input, t.config);
	        i && f.postMessage({
	          workerId: b.WORKER_ID,
	          results: i,
	          finished: !0
	        });
	      }
	    }), (l.prototype = Object.create(u.prototype)).constructor = l, (c.prototype = Object.create(u.prototype)).constructor = c, (p.prototype = Object.create(p.prototype)).constructor = p, (g.prototype = Object.create(u.prototype)).constructor = g, b;
	  });
	})(papaparse_min);

	/* eslint-env browser */


	var fetchTextBrowser = async function fetchTextBrowser(url) {
	  const result = await fetch(url);
	  return result.text();
	};

	let generateMFs$2 = src$c;
	let MF$4 = MF_1;
	let Papa = papaparse_min.exports;
	const fetchText = fetchTextBrowser;

	async function mfFromGoogleSheet$1(url) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  let {
	    urlReferences
	  } = options;

	  if (urlReferences) {
	    let results = await Promise.all([fetchText(url), fetchText(urlReferences)]);
	    return parse(results[0], results[1]);
	  } else {
	    let result = await fetchText(url);
	    return parse(result);
	  }

	  async function parse(tsv, tsvReferences) {
	    let parsed = Papa.parse(tsv, {
	      delimiter: '\t',
	      header: true
	    });
	    let fields = parsed.meta.fields;
	    let infoFields = fields.filter(a => !['mf', 'modif', 'ESI', 'MALDI', 'positive', 'negative'].includes(a));
	    let formulas = parsed.data;
	    let references = {};

	    if (tsvReferences) {
	      let referencesArray = Papa.parse(tsvReferences, {
	        delimiter: '\t',
	        header: true
	      }).data;
	      referencesArray.forEach(r => {
	        references[r.label] = r;
	      });
	    }

	    let results = [];

	    for (let formula of formulas) {
	      if (tsvReferences) {
	        // we add references
	        let refs = formula.references.split(/[ ,]+/);
	        formula.references = [];

	        for (let ref of refs) {
	          formula.references.push(references[ref]);
	        }
	      } // we need to calculate all the possibilities


	      try {
	        let mfs = await generateMFs$2([formula.mf], {
	          ionizations: formula.modif
	        });

	        for (let mf of mfs) {
	          mf.info = {};

	          for (let infoField of infoFields) {
	            mf.info[infoField] = formula[infoField];
	          }

	          if (!formula.ESI && !formula.MALDI && !formula.positive && !formula.negative) {
	            mf.filter = {
	              ESI: true,
	              MALDI: true,
	              positive: true,
	              negative: true
	            };
	          } else {
	            mf.filter = {
	              ESI: formula.ESI === 'X' ? true : false,
	              MALDI: formula.MALDI === 'X' ? true : false,
	              positive: formula.positive === 'X' ? true : false,
	              negative: formula.negative === 'X' ? true : false
	            };
	          }

	          mf.mf = new MF$4(mf.mf).toMF();
	          results.push(mf);
	        }
	      } catch (e) {
	        // eslint-disable-next-line no-console
	        console.warn('Non parsable molecular formula: ', formula.mf, formula.modif, e.toString());
	      }
	    }

	    results = results.filter(a => {
	      return a.ms.em !== 0;
	    });
	    results.sort((a, b) => {
	      return a.ms.em - b.ms.em;
	    });
	    let uniqueResults = [results[0]];

	    for (let i = 1; i < results.length; i++) {
	      if (results[i - 1].ms.em !== results[i].ms.em) {
	        uniqueResults.push(results[i]);
	      }
	    }

	    return uniqueResults;
	  }
	}

	var src$b = mfFromGoogleSheet$1;

	const mfFromGoogleSheet = src$b;

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

	const JSZip = jszip.exports;
	const fetchArrayBuffer = fetchArrayBuffer$2;
	const loadingPromises = {};

	var loadKnapSack = async function loadKnapSack() {
	  let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
	  const {
	    url = 'https://couch.cheminfo.org/cheminfo-public/d2eb480198c80275a1d05dd3609414f9/upload/ms.zip'
	  } = options;

	  if (!loadingPromises[url]) {
	    loadingPromises[url] = fetchArrayBuffer(url);
	  }

	  const buffer = await loadingPromises[url];
	  const jsZip = new JSZip();
	  let zip = await jsZip.loadAsync(buffer);
	  let fileData = await zip.files['ms.json'].async('string');
	  let data = JSON.parse(fileData);
	  data.forEach(d => {
	    d.url = `http://kanaya.naist.jp/knapsack_jsp/information.jsp?word=${d.id}`;
	  });
	  data.sort((a, b) => a.em - b.em);
	  return data;
	};

	const ELECTRON_MASS$2 = constants.ELECTRON_MASS;
	/**
	 * returns all the possible neutral mass for a defined experimental (targetMass) mass
	 */

	let TargetMassCache$1 = function TargetMassCache(targetMass, possibilities) {
	  let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
	  const {
	    allowNeutral = false,
	    // msem because em in this case !
	    minCharge = Number.MIN_SAFE_INTEGER,
	    maxCharge = Number.MAX_SAFE_INTEGER,
	    charge = 0,
	    precision = 100
	  } = options;
	  if (!possibilities || possibilities.length === 0) return {};
	  let firstPossibility = possibilities[0];
	  let currentMinCharge = Math.max(minCharge, firstPossibility.minCharge + charge);
	  let currentMaxCharge = Math.min(maxCharge, firstPossibility.maxCharge + charge);
	  this.minCharge = currentMinCharge;
	  this.maxCharge = currentMaxCharge;
	  let size = this.maxCharge - this.minCharge + 1;
	  this.data = [];
	  let minMass = 0;
	  let maxMass = 0;
	  let range = targetMass * precision / 1e6;

	  for (let i = 0; i < size; i++) {
	    let currentCharge = i + this.minCharge;

	    if (currentCharge === 0) {
	      if (allowNeutral) {
	        minMass = targetMass - range;
	        maxMass = targetMass + range;
	      } else {
	        minMass = Number.MAX_SAFE_INTEGER;
	        maxMass = Number.MIN_SAFE_INTEGER;
	      }
	    } else {
	      minMass = (targetMass - range) * Math.abs(currentCharge) + ELECTRON_MASS$2 * currentCharge;
	      maxMass = (targetMass + range) * Math.abs(currentCharge) + ELECTRON_MASS$2 * currentCharge;
	    }

	    this.data.push({
	      charge: currentCharge,
	      minMass,
	      maxMass
	    });
	  }
	};

	var TargetMassCache_1 = TargetMassCache$1;

	TargetMassCache$1.prototype.getMinMass = function getMinMass(charge) {
	  return this.data[charge - this.minCharge] ? this.data[charge - this.minCharge].minMass : Number.MAX_SAFE_INTEGER;
	};

	TargetMassCache$1.prototype.getMaxMass = function getMaxMass(charge) {
	  return this.data[charge - this.minCharge] ? this.data[charge - this.minCharge].maxMass : Number.MIN_SAFE_INTEGER;
	};

	const Kind$1 = Kind$c;
	const MF$3 = MF_1;
	const parse$1 = parse$4;

	var preprocessRanges$1 = function preprocessRanges(ranges) {
	  ranges = JSON.parse(JSON.stringify(ranges));

	  if (typeof ranges === 'string') {
	    // need to convert to ranges
	    let parsed = parse$1(ranges.replace(/[\r\n\t ]/g, ''));
	    let newRanges = [];
	    let current = {
	      mf: '',
	      min: 1,
	      max: 1
	    }; // example ClBr2(CH2)0-2NO
	    // the idea is that has long as we don't have a range we don't really care
	    // there is a limitation is that the range has to be first level of parenthesis

	    let parenthesisLevel = 0;
	    let currentMF = ''; // start at an atom first level or a parenthesis

	    for (let item of parsed) {
	      switch (item.kind) {
	        case Kind$1.ATOM:
	          if (parenthesisLevel === 0 && currentMF) {
	            current.mf += currentMF;
	            currentMF = '';
	          }

	          currentMF += item.value;
	          break;

	        case Kind$1.ISOTOPE:
	          if (parenthesisLevel === 0 && currentMF) {
	            current.mf += currentMF;
	            currentMF = '';
	          }

	          currentMF += `[${item.value.isotope}${item.value.atom}]`;
	          break;

	        case Kind$1.MULTIPLIER:
	          if (parenthesisLevel === 0 && currentMF) {
	            current.mf += currentMF + item.value;
	            currentMF = '';
	          } else {
	            currentMF += item.value;
	          }

	          break;

	        case Kind$1.MULTIPLIER_RANGE:
	          if (parenthesisLevel !== 0) {
	            throw new Error('Range multiplier can only be at the first level');
	          }

	          newRanges.push({
	            mf: currentMF,
	            min: item.value.from,
	            max: item.value.to
	          });
	          currentMF = '';
	          break;

	        case Kind$1.OPENING_PARENTHESIS:
	          parenthesisLevel++;
	          currentMF += '(';
	          break;

	        case Kind$1.CLOSING_PARENTHESIS:
	          parenthesisLevel--;
	          currentMF += ')';
	          break;

	        default:
	          throw Error(`can not preprocess ${ranges}`);
	      }
	    }

	    if (currentMF) {
	      current.mf += currentMF;
	    }

	    if (current.mf) {
	      newRanges.push(current);
	    }

	    ranges = newRanges;
	  }

	  let possibilities = [];

	  for (let i = 0; i < ranges.length; i++) {
	    let range = ranges[i];
	    let min = range.min === undefined ? 0 : range.min;
	    let max = range.max === undefined ? 1 : range.max;
	    let possibility = {
	      mf: range.mf,
	      originalMinCount: min,
	      // value defined by the user
	      originalMaxCount: max,
	      // value defined by the user
	      currentMinCount: min,
	      currentMaxCount: max,
	      currentCount: min,
	      currentMonoisotopicMass: 0,
	      currentCharge: 0,
	      currentUnsaturation: 0,
	      initialOrder: i,
	      minInnerMass: 0,
	      maxInnerMass: 0,
	      minInnerCharge: 0,
	      maxInnerCharge: 0,
	      minCharge: 0,
	      maxCharge: 0,
	      minMass: 0,
	      maxMass: 0,
	      innerCharge: false
	    };
	    possibilities.push(possibility);
	    let info = new MF$3(range.mf).getInfo();
	    possibility.em = range.em || info.monoisotopicMass;
	    possibility.charge = range.charge || info.charge;
	    possibility.unsaturation = range.unsaturation === undefined ? (info.unsaturation - 1) * 2 : range.unsaturation;
	    possibility.atoms = info.atoms;

	    if (possibility.mf !== info.mf) {
	      possibility.isGroup = true;
	    }
	  }

	  possibilities = possibilities.filter(r => r.originalMinCount !== 0 || r.originalMaxCount !== 0); // we will sort the way we analyse the data
	  // 1. The one possibility parameter
	  // 2. The charged part
	  // 3. Decreasing em

	  possibilities.sort((a, b) => {
	    if (a.originalMinCount === a.originalMaxCount) return -1; // should be in front, they are 'static'

	    if (b.originalMinCount === b.originalMaxCount) return 1;

	    if (a.charge && b.charge) {
	      if (Math.abs(a.charge) > Math.abs(b.charge)) return -1;
	      if (Math.abs(a.charge) < Math.abs(b.charge)) return 1;
	      return b.em - a.em;
	    }

	    if (a.charge) return -1;
	    if (b.charge) return 1;
	    return b.em - a.em;
	  }); // we calculate couple of fixed values

	  for (let i = 0; i < possibilities.length; i++) {
	    for (let j = i; j < possibilities.length; j++) {
	      let possibility = possibilities[j];

	      if (possibility.em > 0) {
	        possibilities[i].minMass += possibility.em * possibility.originalMinCount;
	        possibilities[i].maxMass += possibility.em * possibility.originalMaxCount;
	      } else {
	        possibilities[i].minMass += possibility.em * possibility.originalMaxCount;
	        possibilities[i].maxMass += possibility.em * possibility.originalMinCount;
	      }

	      if (possibility.charge > 0) {
	        possibilities[i].minCharge += possibility.charge * possibility.originalMinCount;
	        possibilities[i].maxCharge += possibility.charge * possibility.originalMaxCount;
	      } else {
	        possibilities[i].minCharge += possibility.charge * possibility.originalMaxCount;
	        possibilities[i].maxCharge += possibility.charge * possibility.originalMinCount;
	      }
	    }
	  }

	  for (let i = 0; i < possibilities.length; i++) {
	    if (i < possibilities.length - 1) {
	      let possibility = possibilities[i];
	      let innerPossibility = possibilities[i + 1];
	      possibility.minInnerMass = innerPossibility.minMass;
	      possibility.maxInnerMass = innerPossibility.maxMass;
	      possibility.minInnerCharge = innerPossibility.minCharge;
	      possibility.maxInnerCharge = innerPossibility.maxCharge;

	      if (possibility.minInnerCharge || possibility.maxInnerCharge) {
	        possibility.innerCharge = true;
	      }
	    }
	  }

	  return possibilities;
	};

	const matcher$2 = src$d.msem;
	const atomSorter$1 = src$i;
	const getMsInfo$2 = getMsInfo$4;
	const preprocessIonizations$3 = preprocessIonizations$5;
	const TargetMassCache = TargetMassCache_1;
	const preprocessRanges = preprocessRanges$1;
	let targetMassCache;
	/**
	 * @param {number}        targetMass - Monoisotopic mass
	 * @param {object}        [options={}]
	 * @param {number}        [options.maxIterations=10000000] - Maximum number of iterations
	 * @param {boolean}       [options.allowNeutral=true]
	 * @param {number}        [options.limit=1000] - Maximum number of results
	 * @param {string}        [options.ionizations=''] - string containing a comma separated list of modifications
	 * @param {string}        [options.ranges='C0-100 H0-100 O0-100 N0-100'] - range of mfs to search
	 * @param {number}        [options.precision=100] - Allowed mass range based on precision
	 * @param {object}        [options.filter={}]
	 * @param {number}        [options.filter.minCharge=-Infinity] - Minimal charge
	 * @param {number}        [options.filter.maxCharge=+Infinity] - Maximal charge
	 * @param {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
	 * @returns {Promise}
	 */

	var src$a = async function mfFinder(targetMass) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const {
	    filter = {},
	    maxIterations = 1e8,
	    limit = 1000,
	    allowNeutral = true,
	    // if there is no msem we use em !
	    ranges = [{
	      mf: 'C',
	      min: 0,
	      max: 100
	    }, {
	      mf: 'H',
	      min: 0,
	      max: 100
	    }, {
	      mf: 'O',
	      min: 0,
	      max: 100
	    }, {
	      mf: 'N',
	      min: 0,
	      max: 100
	    }]
	  } = options;
	  const {
	    minCharge = Number.MIN_SAFE_INTEGER,
	    maxCharge = Number.MAX_SAFE_INTEGER,
	    unsaturation = {}
	  } = filter;
	  let filterUnsaturation = unsaturation ? true : false; // we calculate not the real unsaturation but the one before dividing by 2 + 1

	  let fakeMinUnsaturation = unsaturation.min === undefined ? Number.MIN_SAFE_INTEGER : (unsaturation.min - 1) * 2;
	  let fakeMaxUnsaturation = unsaturation.max === undefined ? Number.MAX_SAFE_INTEGER : (unsaturation.max - 1) * 2;
	  let filterCharge = minCharge !== Number.MIN_SAFE_INTEGER || maxCharge !== Number.MAX_SAFE_INTEGER;
	  let advancedFilter;

	  if (filter.atoms || filter.callback) {
	    advancedFilter = {
	      atoms: filter.atoms,
	      callback: filter.callback
	    };
	  }

	  let result = {
	    mfs: [],
	    info: {
	      numberMFEvaluated: 0,
	      numberResults: 0
	    }
	  };
	  let orderMapping = []; // used to sort the atoms
	  // we need to make the processing for all the ionizations

	  let ionizations = preprocessIonizations$3(options.ionizations);

	  for (let ionization of ionizations) {
	    let currentIonization = {
	      currentMonoisotopicMass: ionization.em || 0,
	      currentCharge: ionization.charge,
	      currentUnsaturation: 0 // we don't take into account the unsaturation of the ionization agent

	    }; // if (DEBUG) console.log('new ionization', ionization.mf, ionization.em, ionization.charge);
	    // ionization em and charge will be used to set the first atom value

	    let possibilities = preprocessRanges(ranges);
	    orderMapping = getOrderMapping$1(possibilities);
	    if (possibilities.length === 0) return {
	      mfs: []
	    };
	    targetMassCache = new TargetMassCache(targetMass, possibilities, Object.assign({}, options, {
	      charge: ionization.charge
	    }));
	    let theEnd = false;
	    let maxPosition = possibilities.length;
	    let lastPosition = possibilities.length - 1;
	    let currentPosition = 0;
	    let currentAtom;
	    let previousAtom;
	    let lastPossibility = possibilities[lastPosition];
	    initializePossibilities(possibilities, currentIonization); //  if (DEBUG) console.log('possibilities', possibilities.map((a) => `${a.mf + a.originalMinCount}-${a.originalMaxCount}`));

	    let isValid = false; // designed so that the first time it is not a valid solution

	    while (!theEnd) {
	      if (result.info.numberMFEvaluated++ > maxIterations) {
	        throw Error(`Iteration number is over the current maximum of: ${maxIterations}`);
	      }

	      if (filterUnsaturation) {
	        let unsaturationValue = lastPossibility.currentUnsaturation;
	        let isOdd = Math.abs(unsaturationValue % 2);

	        if (unsaturation.onlyInteger && isOdd === 1 || unsaturation.onlyNonInteger && isOdd === 0 || fakeMinUnsaturation > unsaturationValue || fakeMaxUnsaturation < unsaturationValue) {
	          isValid = false;
	        }
	      }

	      if (filterCharge && (lastPossibility.currentCharge < minCharge || lastPossibility.currentCharge > maxCharge)) {
	        isValid = false;
	      }

	      if (isValid) {
	        let minMass = targetMassCache.getMinMass(lastPossibility.currentCharge);
	        let maxMass = targetMassCache.getMaxMass(lastPossibility.currentCharge);

	        if (lastPossibility.currentMonoisotopicMass < minMass || lastPossibility.currentMonoisotopicMass > maxMass) {
	          isValid = false;
	        }
	      }

	      if (isValid) {
	        result.info.numberResults++;
	        let newResult = getResult$1(possibilities, targetMass, allowNeutral, ionization, orderMapping);

	        if (advancedFilter) {
	          isValid = matcher$2(newResult, advancedFilter) !== false;
	        }

	        if (isValid) {
	          result.mfs.push(newResult);

	          if (result.mfs.length > 2 * limit) {
	            result.mfs.sort((a, b) => Math.abs(a.ms.ppm) - Math.abs(b.ms.ppm));
	            result.mfs.length = limit;
	          }
	        }
	      }

	      isValid = true; // we need to setup all the arrays if possible

	      while (currentPosition < maxPosition && currentPosition >= 0) {
	        currentAtom = possibilities[currentPosition];
	        previousAtom = currentPosition === 0 ? currentIonization : possibilities[currentPosition - 1];

	        if (currentAtom.currentCount < currentAtom.currentMaxCount) {
	          currentAtom.currentCount++;
	          updateCurrentAtom(currentAtom, previousAtom);

	          if (currentPosition < lastPosition) {
	            currentPosition++;
	            setCurrentMinMax(possibilities[currentPosition], possibilities[currentPosition - 1]);
	          } else {
	            break;
	          }
	        } else {
	          currentPosition--;
	        }
	      }

	      if (currentPosition < 0) {
	        theEnd = true;
	      }
	    }
	  }

	  result.mfs.sort((a, b) => Math.abs(a.ms.ppm) - Math.abs(b.ms.ppm));

	  if (result.mfs.length > limit) {
	    result.mfs.length = limit;
	  }

	  return result;
	};

	function updateCurrentAtom(currentAtom, previousAtom) {
	  currentAtom.currentMonoisotopicMass = previousAtom.currentMonoisotopicMass + currentAtom.em * currentAtom.currentCount;
	  currentAtom.currentCharge = previousAtom.currentCharge + currentAtom.charge * currentAtom.currentCount;
	  currentAtom.currentUnsaturation = previousAtom.currentUnsaturation + currentAtom.unsaturation * currentAtom.currentCount;
	}

	function getResult$1(possibilities, targetMass, allowNeutralMolecules, ionization, orderMapping) {
	  let lastPossibility = possibilities[possibilities.length - 1];
	  let result = {
	    em: lastPossibility.currentMonoisotopicMass - ionization.em,
	    unsaturation: lastPossibility.currentUnsaturation,
	    mf: '',
	    charge: lastPossibility.currentCharge - ionization.charge,
	    ionization,
	    atoms: {},
	    groups: {}
	  }; // we check that the first time we meet the ionization group it does not end
	  // in the final result

	  for (let i = 0; i < possibilities.length; i++) {
	    let possibility = possibilities[orderMapping[i]];

	    if (possibility.currentCount !== 0) {
	      if (possibility.isGroup) {
	        result.mf += `(${possibility.mf})`;

	        if (possibility.currentCount !== 1) {
	          result.mf += possibility.currentCount;
	        }

	        if (result.groups[possibility.mf]) {
	          result.groups[possibility.mf] += possibility.currentCount;
	        } else {
	          result.groups[possibility.mf] = possibility.currentCount;
	        }
	      } else {
	        result.mf += possibility.mf;

	        if (possibility.currentCount !== 1) {
	          result.mf += possibility.currentCount;
	        }
	      }

	      for (let atom in possibility.atoms) {
	        if (result.atoms[atom]) {
	          result.atoms[atom] += possibility.atoms[atom] * possibility.currentCount;
	        } else {
	          result.atoms[atom] = possibility.atoms[atom] * possibility.currentCount;
	        }
	      }
	    }
	  }

	  result.unsaturation = (result.unsaturation + Math.abs(result.charge)) / 2 + 1;
	  result.ms = getMsInfo$2(result, {
	    targetMass,
	    allowNeutralMolecules
	  }).ms;
	  return result;
	}

	function setCurrentMinMax(currentAtom, previousAtom) {
	  // the current min max can only be optimize if the charge will not change anymore
	  if (currentAtom.innerCharge === true || currentAtom.charge !== 0) {
	    currentAtom.currentMinCount = currentAtom.originalMinCount;
	    currentAtom.currentMaxCount = currentAtom.originalMaxCount;
	    currentAtom.currentCount = currentAtom.currentMinCount - 1;
	  } else {
	    // no more change of charge, we can optimize
	    let currentMass = previousAtom !== undefined ? previousAtom.currentMonoisotopicMass : 0;
	    let currentCharge = previousAtom !== undefined ? previousAtom.currentCharge : 0;
	    currentAtom.currentMinCount = Math.max(Math.floor((targetMassCache.getMinMass(currentCharge) - currentMass - currentAtom.maxInnerMass) / currentAtom.em), currentAtom.originalMinCount);
	    currentAtom.currentMaxCount = Math.min(Math.floor((targetMassCache.getMaxMass(currentCharge) - currentMass - currentAtom.minInnerMass) / currentAtom.em), currentAtom.originalMaxCount);
	    currentAtom.currentCount = currentAtom.currentMinCount - 1;
	  }
	}

	function initializePossibilities(possibilities, currentIonization) {
	  for (let i = 0; i < possibilities.length; i++) {
	    if (i === 0) {
	      updateCurrentAtom(possibilities[i], currentIonization);
	      setCurrentMinMax(possibilities[i], currentIonization);
	    } else {
	      updateCurrentAtom(possibilities[i], possibilities[i - 1]);
	    }
	  }
	} // eslint-disable-next-line no-unused-vars

	function getOrderMapping$1(possibilities) {
	  let mapping = possibilities.map((p, i) => ({
	    atom: p.mf,
	    index: i
	  }));
	  mapping.sort((a, b) => {
	    return atomSorter$1(a.atom, b.atom);
	  });
	  return mapping.map(a => a.index);
	}

	const findMFs$1 = src$a;
	/**
	 * Generates a database 'monoisotopic' from a monoisotopic mass and various options
	 * @param {number|string|array}    masses - Monoisotopic mass
	 * @param {object}    [options={}]
	 * @param {number}    [options.maxIterations=10000000] - Maximum number of iterations
	 * @param {function} [options.onStep] - Callback to do after each step
	 * @param {number}    [options.limit=1000] - Maximum number of results
	 * @param {string}    [options.ionizations=''] - string containing a comma separated list of modifications
	 * @param {string}    [options.ranges='C0-100 H0-100 O0-100 N0-100'] - range of mfs to search
	 * @param {number}    [options.precision=100] - Allowed mass range based on precision
	 * @param {object}    [options.filter={}]
	 * @param {number}    [options.filter.minCharge=-Infinity] - Minimal charge
	 * @param {number}    [options.filter.maxCharge=+Infinity] - Maximal charge
	 * @param {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
	 * @returns {Promise}
	 */

	var fromMonoisotopicMass = async function fromMonoisotopicMass(masses) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};

	  if (typeof masses === 'string') {
	    masses = masses.split(/[ ,;\r\n\t]/).map(Number);
	  }

	  if (typeof masses === 'number') {
	    masses = [masses];
	  }

	  let results = [];

	  for (let mass of masses) {
	    results.push(await findMFs$1(mass, options));
	  }

	  return {
	    mfs: results.map(entry => entry.mfs).flat(),
	    info: {
	      numberMFEvaluated: results.reduce((sum, current) => sum += current.info.numberMFEvaluated, 0),
	      numberResults: results.reduce((sum, current) => sum += current.info.numberResults, 0)
	    }
	  };
	};

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

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

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

	var fromRange = async function fromRange(rangesString) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  let ranges = rangesString.split(/ *[;\r\n] */);

	  for (let i = 0; i < ranges.length; i++) {
	    let range = ranges[i];

	    if (range.includes(',')) {
	      ranges[i] = range.split(/ *, */);
	    }
	  }

	  return combineMFs$1(ranges, options);
	};

	const groupsObject = groupsObject_1;
	/**
	 * Recreate a one letter sequence
	 * @param {} mf
	 */

	function groupsToSequence$3(mf) {
	  mf = mf.replace(/\([^(]*\)/g, '');
	  let parts = mf.split(/(?=[A-Z ])/);
	  let usefulParts = [];

	  for (let part of parts) {
	    if (part === ' ') {
	      usefulParts.push(' ');
	      continue;
	    }

	    if (!part.match(/^[A-Z][a-z]{2,6}/)) continue;

	    if (groupsObject[part] && groupsObject[part].oneLetter) {
	      usefulParts.push(groupsObject[part].oneLetter);
	    } else {
	      usefulParts.push('?');
	    }
	  }

	  return usefulParts.join('').replace(/ +/g, ' ').trim();
	}

	var groupsToSequence_1 = groupsToSequence$3;

	const groups$2 = groups$7;
	const groupsToSequence$2 = groupsToSequence_1;

	function getGroupsObject() {
	  let object = {};
	  groups$2.forEach(e => {
	    object[e.symbol] = e;
	  });
	  return object;
	}

	var src$9 = {
	  groups: groups$2,
	  getGroupsObject,
	  groupsToSequence: groupsToSequence$2
	};

	var src$8 = {};

	function allowNeutralLoss$1(mf, options) {
	  if (Array.isArray(mf)) {
	    for (let i = 0; i < mf.length; i++) {
	      mf[i] = allowOneNeutralLoss(mf[i]);
	    }

	    return mf;
	  } else {
	    return allowOneNeutralLoss(mf);
	  }
	}

	function allowOneNeutralLoss(mf) {
	  mf = mf.replace(/(Ser|Thr|Asp|Glu)(?!\()/g, '$1(H-2O-1)0-1');
	  mf = mf.replace(/(Arg|Lys|Asn|Gln)(?!\()/g, '$1(N-1H-3)0-1');
	  return mf;
	}

	var allowNeutralLoss_1 = allowNeutralLoss$1;

	// Link for UTF8 code for modified: https://codepoints.net/search?sc=Grek


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

	let aa$3 = aminoAcids;

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

	  if (code.length === 3) {
	    for (let i = 0; i < aa$3.length; i++) {
	      if (aa$3[i].aa3 === code) {
	        return aa$3[i];
	      }
	    }
	  }
	}

	var getAA_1 = getAA$1;

	let getAA = getAA_1; // SOURCE: https://en.wikipedia.org/wiki/Amino_acid

	function chargePeptide$1(mf) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  if (options.pH === undefined) options.pH = 0;

	  if (Array.isArray(mf)) {
	    for (let i = 0; i < mf.length; i++) {
	      mf[i] = chargeOnePeptide(mf[i], options);
	    }

	    return mf;
	  } else {
	    return chargeOnePeptide(mf, options);
	  }
	}

	function chargeOnePeptide(mf, options) {
	  let pH = options.pH; // we will allow to charge the peptide at a specific pH
	  // first amino acids (N-terminal)

	  if (mf.match(/^H[A-Z][a-z]{2}/)) {
	    let firstAA = mf.replace(/^H([A-Z][a-z]{2}).*/, '$1');

	    if (getAA(firstAA) && pH < getAA(firstAA).pKaN) {
	      mf = mf.replace(/^H([^+])/, 'H+H$1');
	    }
	  } // last amino acids (C-terminal)


	  if (mf.match(/[A-Z][a-z]{2}OH$/)) {
	    let lastAA = mf.replace(/.*([A-Z][a-z]{2})OH$/, '$1');

	    if (getAA(lastAA) && pH > getAA(lastAA).pKaC) {
	      mf = mf.replace(/OH$/, 'O-');
	    }
	  } // basic AA


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

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

	var chargePeptide_1 = chargePeptide$1;

	const aa$2 = aminoAcids;

	function convertAASequence$1(mf) {
	  // this function will check if it is a sequence of aa in 1 letter or 3 letters and convert them if it is the case
	  // it could be a multiline mf !
	  // if it is a multiline we could make some "tricks" ...
	  let newMF = mf; // SEQRES   1 B  256  MET PRO VAL GLU ILE THR VAL LYS GLU LEU LEU GLU ALA
	  // SEQRES   2 B  256  GLY VAL HIS PHE GLY HIS GLU ARG LYS ARG TRP ASN PRO
	  // or
	  // MET PRO VAL GLU ILE THR VAL LYS GLU LEU LEU GLU ALA
	  // GLY VAL HIS PHE GLY HIS GLU ARG LYS ARG TRP ASN PRO

	  if (mf.search(/[A-Z]{3} [A-Z]{3} [A-Z]{3}/) > -1) {
	    // this is a PDB !
	    let tmpmf = mf.replace(/[\r\n]+/g, ' ');
	    tmpmf = tmpmf.replace(/(SEQRES|[0-9]+| [A-Z] | [0-9A-Z]{4-50})/g, ''); // we need to correct the uppercase / lowercase

	    let parts = tmpmf.split(' ');
	    newMF = 'H';

	    for (let i = 0; i < parts.length; i++) {
	      newMF += parts[i].substr(0, 1) + parts[i].substr(1).toLowerCase();
	    }

	    newMF += 'OH';
	  } else if (mf.includes('(') && isOneLetterCode(mf)) {
	    // we expect one letter code with modification
	    newMF = '';
	    let nTerminal = 'H';
	    let cTerminal = 'OH';
	    let parenthesisCounter = 0;

	    for (let i = 0; i < mf.length; i++) {
	      let currentSymbol = mf[i];

	      if (currentSymbol === '(' || currentSymbol === ')' || parenthesisCounter > 0) {
	        if (currentSymbol === '(') {
	          parenthesisCounter++;
	          if (i === 0) nTerminal = '';
	        }

	        if (currentSymbol === ')') {
	          parenthesisCounter--;
	          if (i === mf.length - 1) cTerminal = '';
	        }

	        newMF += currentSymbol;
	        continue;
	      }

	      newMF += convertAA1To3(currentSymbol);
	    }

	    newMF = nTerminal + newMF + cTerminal;
	  } else if (mf.search(/[A-Z]{3}/) > -1 && mf.search(/[a-zA-Z][a-z0-9]/) === -1) {
	    // UNIPROT
	    //   370        380        390        400        410        420
	    //GFKPNLRKTF VSGLFRESCG AHFYRGVDVK PFYIKKPVDN LFALMLILNR LRGWGVVGGM
	    //
	    //    430        440        450        460        470        480
	    //SDPRLYKVWV RLSSQVPSMF FGGTDLAADY YVVSPPTAVS VYTKTPYGRL LADTRTSGFR
	    // We remove all the number, all the spaces, etc
	    newMF = `H${convertAA1To3(newMF.replace(/[^A-Z]/g, ''))}OH`;
	  }

	  return newMF;
	}

	function convertAA1To3(mf) {
	  let newmf = '';

	  for (let i = 0; i < mf.length; i++) {
	    newmf += aa1To3(mf.charAt(i));
	  }

	  return newmf;
	}

	function aa1To3(code) {
	  for (let i = 0; i < aa$2.length; i++) {
	    if (aa$2[i].aa1 === code) {
	      return aa$2[i].aa3;
	    }
	  }

	  throw new Error(`Invalid 1 letter code: ${code}`);
	}

	var convertAASequence_1 = convertAASequence$1; // mf can contain as well parenthesis. We need to check if it is not yet a correct molecular formula

	function isOneLetterCode(mf) {
	  let parenthesisLevel = 0;

	  for (let char of mf) {
	    if (parenthesisLevel === 0 && char.match(/[a-z]/)) return false;
	    if (char === '(') parenthesisLevel++;
	    if (char === ')') parenthesisLevel--;
	  }

	  return true;
	}

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


	function digestSequence(sequence) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  sequence = sequence.replace(/^H([^a-z])/, '$1').replace(/OH$/, '');
	  options.enzyme = options.enzyme || 'trypsin';
	  if (options.minMissed === undefined) options.minMissed = 0;
	  if (options.maxMissed === undefined) options.maxMissed = 0;
	  if (options.minResidue === undefined) options.minResidue = 0;
	  if (options.maxResidue === undefined) options.maxResidue = Number.MAX_VALUE;
	  let regexp = getRegexp(options.enzyme);
	  let fragments = sequence.replace(regexp, '$1 ').split(/ /).filter(entry => entry);
	  {
	    let from = 0;

	    for (let i = 0; i < fragments.length; i++) {
	      let nbResidue = fragments[i].replace(/([A-Z][a-z][a-z])/g, ' $1').split(/ /).filter(entry => entry).length;
	      fragments[i] = {
	        sequence: fragments[i],
	        nbResidue: nbResidue,
	        from: from,
	        to: from + nbResidue - 1
	      };
	      from += nbResidue;
	    }
	  }
	  let results = [];

	  for (let i = 0; i < fragments.length - options.minMissed; i++) {
	    for (let j = options.minMissed; j <= Math.min(options.maxMissed, fragments.length - i - 1); j++) {
	      let fragment = '';
	      let nbResidue = 0;

	      for (let k = i; k <= i + j; k++) {
	        fragment += fragments[k].sequence;
	        nbResidue += fragments[k].nbResidue;
	      }

	      let from = fragments[i].from + 1;
	      let to = fragments[i + j].to + 1;

	      if (fragment && nbResidue >= options.minResidue && nbResidue <= options.maxResidue) {
	        results.push(`H${fragment}OH$D${from}>${to}`);
	      }
	    }
	  }

	  return results;
	}

	function getRegexp(enzyme) {
	  switch (enzyme.toLowerCase().replace(/[^a-z0-9]/g, '')) {
	    case 'chymotrypsin':
	      return /(Phe|Tyr|Trp)(?!Pro)/g;

	    case 'trypsin':
	      return /(Lys|Arg)(?!Pro)/g;

	    case 'lysc':
	      return /(Lys)(?!Pro)/g;

	    case 'glucph4':
	      return /(Glu)(?!Pro|Glu)/g;

	    case 'glucph8':
	      return /(Asp|Glu)(?!Pro|Glu)/g;

	    case 'thermolysin':
	      // N-term of  Leu, Phe, Val, Ile, Ala, Met
	      return /()(?=Ile|Leu|Val|Ala|Met|Phe)/g;

	    case 'cyanogenbromide':
	      return /(Met)/g;

	    case 'any':
	      return /()(?=[A-Z][a-z][a-z])/g;

	    default:
	      throw new Error(`Digestion enzyme: ${enzyme} is unknown`);
	  }
	}

	var digestPeptide$1 = digestSequence;

	var generatePeptideFragments$1 = function generatePeptideFragments(mf, options) {
	  if (options === undefined) {
	    options = {
	      a: false,
	      b: true,
	      c: false,
	      x: false,
	      y: true,
	      z: false,
	      i: false,
	      ya: false,
	      yb: false,
	      yc: false,
	      zc: false
	    };
	  }

	  options.maxInternal = options.maxInternal || Number.MAX_VALUE;
	  options.minInternal = options.minInternal || 0;
	  let mfs = []; // need to allow 0-9 to deal with neutral loss

	  let mfparts = mf.replace(/([a-z)0-9])([A-Z][a-z](?=[a-z]))/g, '$1 $2').split(/ /);
	  let nTerm = '';
	  let cTerm = '';

	  if (mfparts[0].startsWith('(')) {
	    nTerm += mfparts[0];
	    mfparts = mfparts.splice(1);
	  }

	  if (mfparts[mfparts.length - 1].includes('(')) {
	    cTerm += mfparts[mfparts.length - 1].replace(/^[^()]*/, '');
	    mfparts[mfparts.length - 1] = mfparts[mfparts.length - 1].replace(/\(.*/, '');
	  }

	  for (let i = 1; i < mfparts.length; i++) {
	    nTerm += mfparts[i - 1];
	    cTerm = mfparts[mfparts.length - i] + cTerm;
	    addNTerm(mfs, nTerm, i, options);
	    addCTerm(mfs, cTerm, i, options);
	    if (options.i) mfs.push(`${mfparts[i]}HC-1O-1(+1)$i:${mfparts[i]}`);

	    if (options.ya || options.yb || options.yc || options.zc) {
	      // we have double fragmentations
	      for (let j = i + 1; j < Math.min(mfparts.length, options.maxInternal + i + 1); j++) {
	        let iTerm = '';

	        if (j - i >= options.minInternal) {
	          for (let k = i; k < j; k++) {
	            iTerm += mfparts[k];
	          }

	          addITerm(mfs, iTerm, mfparts.length - i, j, options);
	        }
	      }
	    }
	  } // todo does this make sense ??? I think we should remote those 3 lines


	  if (mfs.length === 0) {
	    mfs = mfs.concat([mf]);
	  }

	  return mfs;
	};

	function addNTerm(mfs, nTerm, i, options) {
	  if (options.a) mfs.push(`${nTerm}C-1O-1(+1)$a${i}`);
	  if (options.b) mfs.push(`${nTerm}(+1)$b${i}`);
	  if (options.c) mfs.push(`${nTerm}NH3(+1)$c${i}`);
	}

	function addITerm(mfs, iTerm, i, j, options) {
	  if (options.ya) mfs.push(`H${iTerm}C-1O-1(+1)$a${j}y${i}`);
	  if (options.yb) mfs.push(`H${iTerm}(+1)$b${j}y${i}`);
	  if (options.yc) mfs.push(`H${iTerm}NH3(+1)$c${j}y${i}`);
	  if (options.zc) mfs.push(`N-1${iTerm}NH3(+1)$c${j}z${i}`);
	}

	function addCTerm(mfs, cTerm, i, options) {
	  if (options.x) mfs.push(`CO(+1)${cTerm}$x${i}`);
	  if (options.y) mfs.push(`H2(+1)${cTerm}$y${i}`);
	  if (options.z) mfs.push(`N-1H-1(+1)${cTerm}$z${i}`);
	}

	let aa$1 = aminoAcids; // we will convert the data to an object to be much faster

	let aaObject = {};

	for (let i = 0; i < aa$1.length; i++) {
	  aaObject[aa$1[i].aa3] = aa$1[i];
	}

	function calculateCharge(aas, pH) {
	  if (!pH) pH = 7.0;
	  let combined = combine(aas);
	  if (!combined) return;
	  let charge = calculateForPh(combined, pH);
	  return Math.round(charge * 1000) / 1000;
	} // this methods required an array of aas


	function calculateIEP(aas) {
	  let combined = combine(aas);
	  if (!combined) return;
	  let first = 0;
	  let last = 14;
	  let current = 14;
	  let previous = 0;
	  let currentCharge;

	  while (Math.abs(current - previous) > 0.0001) {
	    previous = current;
	    current = (last + first) / 2;
	    currentCharge = calculateForPh(combined, current);

	    if (currentCharge > 0) {
	      first = current;
	    } else if (currentCharge < 0) {
	      last = current;
	    } else {
	      previous = current;
	    }
	  }

	  return Math.round(current * 1000) / 1000;
	}

	function calculateChart(aas) {
	  let combined = combine(aas);
	  if (!combined) return;
	  let y = [];
	  let x = [];
	  let yAbs = [];

	  for (let i = 0; i <= 14; i = i + 0.01) {
	    let charge = calculateForPh(combined, i);
	    x.push(i);
	    y.push(charge);
	    yAbs.push(Math.abs(charge));
	  }

	  combined.x = x;
	  combined.y = y;
	  combined.yAbs = yAbs;
	  return combined;
	}

	function calculateForPh(combined, pH) {
	  let total = 0;
	  total += 1 / (1 + Math.pow(10, pH - combined.first));
	  total += -1 / (1 + Math.pow(10, combined.last - pH));

	  for (let key in combined.acid) {
	    total += -combined.acid[key] / (1 + Math.pow(10, aaObject[key].sc.pKa - pH));
	  }

	  for (let key in combined.basic) {
	    total += combined.basic[key] / (1 + Math.pow(10, pH - aaObject[key].sc.pKa));
	  }

	  return total;
	} // we will combine the amino acids


	function combine(aas) {
	  let combined = {};

	  if (aaObject[aas[0]]) {
	    combined.first = aaObject[aas[0]].pKaN;
	  } else {
	    return;
	  }

	  if (aaObject[aas[aas.length - 1]]) {
	    combined.last = aaObject[aas[aas.length - 1]].pKaC;
	  } else {
	    return;
	  }

	  combined.basic = {};
	  combined.acid = {};

	  for (let i = 0; i < aas.length; i++) {
	    let currentAA = aas[i];
	    if (!aaObject[currentAA]) return;

	    if (aaObject[currentAA].sc && aaObject[currentAA].sc.type) {
	      if (aaObject[currentAA].sc.type === 'positive') {
	        if (!combined.basic[currentAA]) {
	          combined.basic[currentAA] = 0;
	        }

	        combined.basic[currentAA]++;
	      } else if (aaObject[currentAA].sc.type === 'negative') {
	        if (!combined.acid[currentAA]) {
	          combined.acid[currentAA] = 0;
	        }

	        combined.acid[currentAA]++;
	      }
	    }
	  }

	  return combined;
	}
	/*
	 We can generate a color based on iep
	 0 -> 7 means that at pH 7 it is charged negatively (blue)
	 7 -> 14 means that at pH7 it is charged positively (red)
	 */


	function getColor(iep) {
	  if (iep < 7) {
	    if (iep < 3) iep = 3;
	    let white = Math.round(255 - (7 - iep) * (200 / 4));
	    return `rgb(${white},${white},255)`;
	  } else if (iep > 7) {
	    if (iep > 11) iep = 11;
	    let white = Math.round(255 - (iep - 7) * (200 / 4));
	    return `rgb(255,${white},${white})`;
	  }

	  return 'rgb(255,255,255)';
	}

	var isoElectricPoint = {
	  calculateIEP: calculateIEP,
	  calculateCharge: calculateCharge,
	  calculateChart: calculateChart,
	  getColor: getColor
	};

	function splitSequence(sequence) {
	  let aas = sequence.replace(/([A-Z])/g, ' $1').split(/ /);
	  let begin = 0;

	  while (aas[begin] === '' || aas[begin] === 'H') {
	    begin++;
	  }

	  let end = aas.length - 1;

	  while (aas[end] === 'O' || aas[end] === 'H') {
	    end--;
	  }

	  aas = aas.slice(begin, end + 1);
	  return aas;
	}

	var splitPeptide$1 = splitSequence;

	const allowNeutralLoss = allowNeutralLoss_1;
	const aa = aminoAcids;
	const chargePeptide = chargePeptide_1;
	const convertAASequence = convertAASequence_1;
	const digestPeptide = digestPeptide$1;
	const generatePeptideFragments = generatePeptideFragments$1;
	const IEP = isoElectricPoint;
	const splitPeptide = splitPeptide$1;

	src$8.getInfo = () => {
	  return aa;
	}; // sequence should be in the "right" format like HAlaGlyProOH


	src$8.splitPeptide = splitPeptide;
	src$8.digestPeptide = digestPeptide;

	src$8.calculateIEP = function calculateIEP(sequence) {
	  let aas = splitPeptide(sequence);
	  let result = IEP.calculateIEP(aas);
	  return result;
	};

	src$8.calculateIEPChart = function calculateIEPChart(sequence) {
	  let aas = splitPeptide(sequence);
	  let result = IEP.calculateChart(aas);
	  return result;
	};

	src$8.getColorForIEP = function getColorForIEP(iep) {
	  return IEP.getColor(iep);
	};

	src$8.calculateCharge = function calculateCharge(sequence, ph) {
	  let aas = splitPeptide(sequence);
	  return IEP.calculateCharge(aas, ph);
	};

	src$8.generatePeptideFragments = generatePeptideFragments;
	src$8.chargePeptide = chargePeptide;
	src$8.allowNeutralLoss = allowNeutralLoss;
	src$8.convertAASequence = convertAASequence;
	src$8.sequenceToMF = convertAASequence;

	const peptide = src$8;

	function fragmentArray(sequence) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const {
	    digestion = {},
	    protonation,
	    fragmentation,
	    protonationPH,
	    allowNeutralLoss
	  } = options;
	  sequence = peptide.convertAASequence(sequence);
	  let fragmentsArray = [sequence]; // do we also have some digest fragments ?

	  if (digestion.enzyme) {
	    let digests = peptide.digestPeptide(sequence, digestion);

	    if (options.protonation) {
	      digests = peptide.chargePeptide(digests, {
	        pH: options.protonationPH
	      });
	    }

	    fragmentsArray = fragmentsArray.concat(digests);
	  } // allow neutral loss


	  if (allowNeutralLoss) {
	    sequence = peptide.allowNeutralLoss(sequence);
	  } // apply protonation


	  if (protonation) {
	    sequence = peptide.chargePeptide(sequence, {
	      pH: protonationPH
	    });
	  } // calculate fragmentation


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

	var fragmentPeptide$1 = fragmentArray;

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

	var fromPeptidicSequence = async function fromPeptidicSequence(sequences) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const {
	    digestion = {},
	    mfsArray: originalMFsArray = [],
	    allowNeutralLoss = false,
	    protonation = false,
	    protonationPH = 7,
	    fragmentation = {},
	    filter = {},
	    ionizations = '',
	    limit = 100000,
	    estimate = false,
	    onStep,
	    links = {}
	  } = options;
	  const hasLinked = sequences.includes('#');
	  const mfsArrayLinked = JSON.parse(JSON.stringify(originalMFsArray));
	  const mfsArrayUnlinked = JSON.parse(JSON.stringify(originalMFsArray));
	  const unlinked = [];
	  mfsArrayUnlinked.push(unlinked);

	  for (const sequence of sequences.split(/[,:]/)) {
	    let fragmentsArray = fragmentPeptide(sequence, {
	      digestion,
	      protonation,
	      fragmentation,
	      protonationPH,
	      allowNeutralLoss
	    });
	    mfsArrayLinked.push(fragmentsArray.filter(fragment => fragment.includes('#')));
	    unlinked.push(...fragmentsArray.filter(fragment => !fragment.includes('#')));
	  }

	  let combined = await generateMFs(mfsArrayUnlinked, {
	    ionizations,
	    filter,
	    estimate,
	    limit,
	    onStep,
	    links
	  });

	  if (hasLinked) {
	    combined.push(...(await generateMFs(mfsArrayLinked, {
	      ionizations,
	      filter,
	      estimate,
	      limit,
	      onStep,
	      links
	    })));
	  }

	  if (!estimate) {
	    combined.forEach(result => {
	      result.sequence = groupsToSequence$1(result.parts.filter(part => part).join(' '));
	    });
	  }

	  return combined;
	};

	const findMFs = src$a;
	/**
	 *
	 * @param {object}   experimentalSpectrum
	 * @param {object}   database
	 * @param {object}   [options={}]
	 * @param {function} [options.onStep] - Callback to do after each step
	 * @param {string}   [options.ionizations=''] - string containing a comma separated list of modifications
	 * @param {number}   [options.precision=100] - Allowed mass range based on precision
	 */

	var appendFragmentsInfo = async function appendFragmentsInfo(experimentalSpectrum, database) {
	  let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
	  const {
	    ionizations,
	    onStep,
	    precision
	  } = options;

	  if (!experimentalSpectrum) {
	    throw new Error('Experimental spectrum is not defined');
	  }

	  if (!database) {
	    throw new Error('Database is not defined');
	  }

	  const peaks = experimentalSpectrum.getPeaks({
	    sumValue: 1
	  });

	  for (let entry of database) {
	    const ranges = Object.keys(entry.atoms).map(atom => `${atom}0-${entry.atoms[atom]}`).join(' ');
	    entry.fragments = {
	      nbFound: 0,
	      intensityFound: 0,
	      assignments: []
	    };

	    for (let i = 0; i < peaks.length; i++) {
	      if (onStep) await onStep(i);
	      const peak = peaks[i];
	      const possibleMFs = await findMFs(peak.x, {
	        ionizations,
	        precision,
	        ranges
	      });

	      if (possibleMFs.mfs.length > 0) {
	        entry.fragments.nbFound++;
	        entry.fragments.intensityFound += peak.y;
	        entry.fragments.assignments.push({
	          peak,
	          bestMF: possibleMFs.mfs[0]
	        });
	      }
	    }
	  }
	};

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


	function ensureUppercaseSequence$1(sequence) {
	  let parenthesisCounter = 0;
	  let parts = [];
	  let part = '';

	  for (let i = 0; i < sequence.length; i++) {
	    let currentSymbol = sequence[i];

	    if (currentSymbol === '(' && parenthesisCounter === 0 && part) {
	      parts.push(part);
	      part = currentSymbol;
	    } else if (currentSymbol === ')' && parenthesisCounter === 0) {
	      part += currentSymbol;
	      parts.push(part);
	      part = '';
	    } else {
	      part += currentSymbol;
	    }
	  }

	  if (part) parts.push(part);

	  for (let i = 0; i < parts.length; i++) {
	    if (!parts[i].startsWith('(') && parts[i].match(/^[a-z]+$/)) {
	      parts[i] = parts[i].toUpperCase();
	    }
	  }

	  return parts.join('');
	}

	var ensureUppercaseSequence_1 = ensureUppercaseSequence$1;

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

	function sequenceToMF(sequence) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  let fivePrimeTerminal = 'HO';
	  let threePrimeTerminal = 'H';
	  sequence = sequence.replace(/^HO/, '');
	  sequence = sequence.replace(/H$/, '');
	  sequence = sequence.trim();
	  if (sequence === '') return '';
	  sequence = ensureUppercaseSequence(sequence); // if the sequence is in lowercase but the parenthesis we should convert it to uppercase

	  if (sequence.match(/^[a-z]+$/)) {
	    sequence = sequence.toUpperCase();
	  }

	  let {
	    kind,
	    circular,
	    fivePrime = 'monophosphate'
	  } = options;
	  fivePrime = fivePrime.replace(/[^a-zA-Z]/g, '').toLowerCase();

	  if (!kind) {
	    if (sequence.includes('U')) {
	      kind = 'rna';
	    } else {
	      kind = 'ds-dna';
	    }
	  }

	  kind = kind.replace(/[^A-Za-z]/g, '').toLowerCase();

	  if (sequence.includes('(') && kind === 'dsdna') {
	    throw new Error('Nucleotide sequenceToMF: modifications not allowed for ds-DNA');
	  }

	  let results = [[]];
	  if (kind === 'dsdna') results.push([]);
	  let parenthesisCounter = 0;

	  for (let i = 0; i < sequence.length; i++) {
	    let currentSymbol = sequence[i];

	    while (sequence[i + 1] && sequence[i + 1].match(/[a-z]/)) {
	      i++;
	      currentSymbol += sequence[i];
	    }

	    if (currentSymbol.length > 1) {
	      results[0].push(currentSymbol);
	      continue;
	    }

	    if (currentSymbol === '(' || currentSymbol === ')' || parenthesisCounter > 0) {
	      if (currentSymbol === '(') {
	        parenthesisCounter++;
	        if (i === 0) fivePrimeTerminal = '';
	      }

	      if (currentSymbol === ')') {
	        parenthesisCounter--;
	        if (i === sequence.length - 1) threePrimeTerminal = '';
	      }

	      switch (kind) {
	        case 'dna':
	        case 'rna':
	          results[0].push(currentSymbol);
	          break;

	        default:
	          // eslint-disable-next-line no-console
	          console.warn(`Nucleotide sequenceToMF with modification: unknown kind: ${kind}`);
	      }

	      continue;
	    }

	    let nucleotideType = i === 0 ? fivePrime : 'monophosphate';
	    currentSymbol = currentSymbol.replace(/[ \t\r\n]/, '');
	    if (!currentSymbol) continue;

	    switch (kind) {
	      case 'dna':
	        results[0].push(desoxyNucleotides[nucleotideType][currentSymbol]);
	        break;

	      case 'rna':
	        results[0].push(oxyNucleotides[nucleotideType][currentSymbol]);
	        break;

	      case 'dsdna':
	        results[0].push(desoxyNucleotides[nucleotideType][currentSymbol]);
	        results[1].unshift(desoxyNucleotides[nucleotideType][complementary[currentSymbol]]);
	        break;

	      default:
	        // eslint-disable-next-line no-console
	        console.warn(`Nucleotide sequenceToMF: unknown kind: ${kind}`);
	    }
	  }

	  if (!circular) {
	    results.forEach(result => result.unshift(fivePrimeTerminal));
	    results.forEach(result => result.push(threePrimeTerminal));
	  }

	  return results.map(result => result.join('')).join('.');
	}

	var sequenceToMF_1 = sequenceToMF;
	const complementary = {
	  A: 'T',
	  T: 'A',
	  C: 'G',
	  G: 'C'
	};
	const desoxyNucleotides = {
	  alcohol: {},
	  monophosphate: {},
	  diphosphate: {},
	  triphosphate: {}
	};
	groups$1.filter(group => group.kind === 'DNA').forEach(group => {
	  if (group.oneLetter) {
	    desoxyNucleotides.alcohol[group.oneLetter] = group.symbol;
	  }
	});
	groups$1.filter(group => group.kind === 'DNAp').forEach(group => {
	  if (group.oneLetter) {
	    desoxyNucleotides.monophosphate[group.oneLetter] = group.symbol;
	  }
	});
	groups$1.filter(group => group.kind === 'NucleotideP').forEach(group => {
	  if (group.oneLetter) {
	    desoxyNucleotides.monophosphate[group.oneLetter] = group.symbol;
	  }
	});
	groups$1.filter(group => group.kind === 'DNApp').forEach(group => {
	  if (group.oneLetter) {
	    desoxyNucleotides.diphosphate[group.oneLetter] = group.symbol;
	  }
	});
	groups$1.filter(group => group.kind === 'DNAppp').forEach(group => {
	  if (group.oneLetter) {
	    desoxyNucleotides.triphosphate[group.oneLetter] = group.symbol;
	  }
	});
	const oxyNucleotides = {
	  alcohol: {},
	  monophosphate: {},
	  diphosphate: {},
	  triphosphate: {}
	};
	groups$1.filter(group => group.kind === 'RNA').forEach(group => {
	  if (group.oneLetter) {
	    oxyNucleotides.alcohol[group.oneLetter] = group.symbol;
	  }
	});
	groups$1.filter(group => group.kind === 'RNAp').forEach(group => {
	  if (group.oneLetter) {
	    oxyNucleotides.monophosphate[group.oneLetter] = group.symbol;
	  }
	});
	groups$1.filter(group => group.kind === 'NucleotideP').forEach(group => {
	  if (group.oneLetter) {
	    oxyNucleotides.monophosphate[group.oneLetter] = group.symbol;
	  }
	});
	groups$1.filter(group => group.kind === 'RNApp').forEach(group => {
	  if (group.oneLetter) {
	    oxyNucleotides.diphosphate[group.oneLetter] = group.symbol;
	  }
	});
	groups$1.filter(group => group.kind === 'RNAppp').forEach(group => {
	  if (group.oneLetter) {
	    oxyNucleotides.triphosphate[group.oneLetter] = group.symbol;
	  }
	});

	function furanThreeTerm$2(nucleotide) {
	  // last residue should become a furan
	  let parts = nucleotide.replace(/ /g, '').replace(/([a-z)0-9])([A-Z][a-z](?=[a-z]))/g, '$1 $2').split(/ /);
	  let last = parts.pop();

	  if (!last.match(/D[atcg]mp(.*)$/)) {
	    // eslint-disable-next-line no-console
	    console.warn(`furanThreeTerm can not remove a non monophosphate nucleic acid: ${last}`);
	    return parts.join('') + last;
	  }

	  return parts.join('') + last.replace(/D[atcg]mp(.*)$/, 'Furp');
	}

	var furanThreeTerm_1 = furanThreeTerm$2;

	const furanThreeTerm$1 = furanThreeTerm_1;

	function addFiveTerm$1(mfs, fiveTerm, i, options) {
	  if (options.a) mfs.push(`${fiveTerm}O-1H-1$a${i}`); // neutral ok

	  if (options.ab && i > 1) mfs.push(`${furanThreeTerm$1(fiveTerm)}$a${i}-B`); // A minus base

	  if (options.b) mfs.push(`${fiveTerm}H$b${i}`); // need to add an hydrogen, see: https://books.google.ch/books?id=B57e37bJjqAC&pg=PA172&lpg=PA172&dq=oligonucleotide+b+fragmentation&source=bl&ots=mRr29Pexx2&sig=1NUQcWV-wuj6o9q81my86AVoRto&hl=fr&sa=X&ved=2ahUKEwjI5M3yn-7fAhUJMewKHQR6Bcs4ChDoATADegQIBhAB#v=onepage&q&f=true

	  if (options.c) mfs.push(`${fiveTerm}PO2$c${i}`); // neutral ok

	  if (options.d) mfs.push(`${fiveTerm}PO3H2$d${i}`);
	  if (options.dh2o) mfs.push(`${fiveTerm}PO2$d${i}-H2O`);
	}

	var addFiveTerm_1 = addFiveTerm$1;

	const MF$2 = src$h.MF;

	function mfDiff$1(mfString1, mfString2) {
	  let mf1 = new MF$2(mfString1).getInfo().atoms;
	  let mf2 = new MF$2(mfString2).getInfo().atoms;
	  let atoms = Object.keys(mf1);
	  Object.keys(mf2).forEach(atom => {
	    if (!atoms.includes(atom)) atoms.push(atom);
	  });
	  let mf = '';

	  for (let atom of atoms) {
	    let diff = (mf1[atom] || 0) - (mf2[atom] || 0);
	    if (diff) mf += atom + diff;
	  }

	  return new MF$2(mf).toMF();
	}

	var mfDiff_1 = mfDiff$1;

	const mfDiff = mfDiff_1;
	const mfLosses = {};
	['Amp', 'Tmp', 'Cmp', 'Gmp', 'Ump'].forEach(nucleotide => {
	  mfLosses[nucleotide] = {
	    code: nucleotide.charAt(0),
	    diff: mfDiff('Rmp', nucleotide)
	  };
	});
	['Damp', 'Dtmp', 'Dcmp', 'Dgmp', 'Dump'].forEach(nucleotide => {
	  mfLosses[nucleotide] = {
	    code: nucleotide.charAt(1).toUpperCase(),
	    diff: mfDiff('Drmp', nucleotide)
	  };
	});

	function baseLoss$2(nucleotide) {
	  // any residue can loose a base
	  let results = [];

	  for (let key in mfLosses) {
	    const base = mfLosses[key];

	    if (nucleotide.includes(key)) {
	      results.push(`${nucleotide}(${base.diff})$${base.code}*`);
	    }
	  }

	  return results;
	}

	var baseLoss_1 = baseLoss$2;

	const baseLoss$1 = baseLoss_1;

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

	    if (options.b) {
	      mfs.push(`${mf}`.replace('$', `H-1$b${i} `));
	    }

	    if (options.c) {
	      mfs.push(`${mf}`.replace('$', `PO2$c${i} `));
	    }

	    if (options.d) {
	      mfs.push(`${mf}`.replace('$', `PO3H2$d${i} `));
	    }
	  });
	}

	var addFiveTermBaseLoss_1 = addFiveTermBaseLoss$1;

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

	function addInternalTerm$1(mfs, internal, ter3, ter5) {
	  let options = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : {};

	  if (options.aw) {
	    // without base loss
	    mfs.push(`HO${internal}O-1H-1$w${ter3}:a${ter5}`); // A W
	  }

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

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

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

	var addInternalTerm_1 = addInternalTerm$1;

	function addThreeTerm$1(mfs, threeTerm, i, options) {
	  if (options.w) mfs.push(`HO${threeTerm}$w${i}`); // neutral ok

	  if (options.x) mfs.push(`H-1${threeTerm}$x${i}`); // neutral ok

	  if (options.y) mfs.push(`O-2P-1${threeTerm}$y${i}`); // neutral ok

	  if (options.z) mfs.push(`O-3H-2P-1${threeTerm}$z${i}`); // neutral ok

	  if (options.zch2) mfs.push(`O-3H-4C-1P-1${threeTerm}$z${i}-CH2`); // TODO to confirm
	}

	var addThreeTerm_1 = addThreeTerm$1;

	const baseLoss = baseLoss_1;

	function addThreeTermBaseLoss$1(mfs, threeTerm, i, options) {
	  if (!options.wxyzBaseLoss) return;
	  let loss = baseLoss(threeTerm);
	  loss.forEach(mf => {
	    if (options.w) {
	      mfs.push(`HO${mf}`.replace('$', `$w${i} `));
	    }

	    if (options.x) {
	      mfs.push(`H-1${mf}`.replace('$', `$x${i} `));
	    }

	    if (options.y) {
	      mfs.push(`O-2P-1${mf}`.replace('$', `$y${i} `));
	    }

	    if (options.z) {
	      mfs.push(`O-3H-1P-1(+)${mf}`.replace('$', `$z${i} `));
	    }
	  });
	}

	var addThreeTermBaseLoss_1 = addThreeTermBaseLoss$1;

	const addFiveTerm = addFiveTerm_1;
	const addFiveTermBaseLoss = addFiveTermBaseLoss_1;
	const addInternalTerm = addInternalTerm_1;
	const addThreeTerm = addThreeTerm_1;
	const addThreeTermBaseLoss = addThreeTermBaseLoss_1;

	var generateFragments = function generateFragments(mf, options) {
	  if (options === undefined) {
	    options = {
	      a: false,
	      ab: false,
	      b: false,
	      c: false,
	      d: false,
	      dh2o: false,
	      w: false,
	      x: false,
	      y: false,
	      z: false,
	      zch2: false,
	      aw: false,
	      bw: false,
	      abw: false,
	      aby: false,
	      abcdBaseLoss: false,
	      wxyzBaseLoss: false
	    };
	  }

	  let mfs = []; // need to allow 0-9 to deal with neutral loss

	  let mfparts = mf.replace(/([a-z)0-9])([A-Z][a-z](?=[a-z]))/g, '$1 $2').split(/ /);
	  let fiveTerm = '';
	  let threeTerm = '';

	  if (mfparts[0].startsWith('(')) {
	    fiveTerm += mfparts[0];
	    mfparts = mfparts.splice(1);
	  }

	  if (mfparts[mfparts.length - 1].includes('(')) {
	    threeTerm += mfparts[mfparts.length - 1].replace(/^[^()]*/, '');
	    mfparts[mfparts.length - 1] = mfparts[mfparts.length - 1].replace(/\(.*/, '');
	  }

	  for (let ter5 = 1; ter5 < mfparts.length; ter5++) {
	    fiveTerm += mfparts[ter5 - 1];
	    threeTerm = mfparts[mfparts.length - ter5] + threeTerm;
	    addFiveTerm(mfs, fiveTerm, ter5, options);
	    addFiveTermBaseLoss(mfs, fiveTerm, ter5, options);
	    addThreeTerm(mfs, threeTerm, ter5, options);
	    addThreeTermBaseLoss(mfs, threeTerm, ter5, options);
	  }

	  for (let i = 1; i < mfparts.length - 1; i++) {
	    let internal = '';

	    for (let j = i; j < mfparts.length - 1; j++) {
	      internal += mfparts[j];

	      if (j > i) {
	        addInternalTerm(mfs, internal, mfparts.length - i, j + 1, options);
	      }
	    }
	  }

	  return mfs;
	};

	var src$7 = {
	  sequenceToMF: sequenceToMF_1,
	  generateFragments: generateFragments,
	  furanThreeTerm: furanThreeTerm_1,
	  baseLoss: baseLoss_1
	};

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

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

	  for (let i = 0; i < sequences.length; i++) {
	    let sequence = sequences[i];
	    let fragments = nucleotide.generateFragments(sequence, fragmentation);

	    if (i === 1) {
	      // complementary sequence
	      fragments = fragments.map(fragment => fragment.replace(/\$/g, '$cmp-'));
	    }

	    fragmentsArray = fragmentsArray.concat(fragments);

	    if (fragmentation.baseLoss) {
	      fragmentsArray = fragmentsArray.concat(nucleotide.baseLoss(sequence));
	    }
	  }

	  mfsArray.push(fragmentsArray);
	  let combined = await combineMFs(mfsArray, {
	    ionizations,
	    filter,
	    uniqueMFs: false,
	    estimate,
	    onStep,
	    limit
	  });

	  if (Array.isArray(combined)) {
	    // not an estimation
	    combined.forEach(result => {
	      result.sequence = groupsToSequence(result.parts.filter(part => part).join(' '));
	    });
	  }

	  return combined;
	};

	const toString$1 = Object.prototype.toString;
	/**
	 * Checks if an object is an instance of an Array (array or typed array).
	 *
	 * @param {any} value - Object to check.
	 * @returns {boolean} True if the object is an array.
	 */

	function isAnyArray$3(value) {
	  return toString$1.call(value).endsWith('Array]');
	}

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

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

	var isAnyArray$2 = require$$0$2;

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

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

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

	  const {
	    fromIndex = 0,
	    toIndex = input.length
	  } = options;

	  if (fromIndex < 0 || fromIndex >= input.length || !Number.isInteger(fromIndex)) {
	    throw new Error('fromIndex must be a positive integer smaller than length');
	  }

	  if (toIndex <= fromIndex || toIndex > input.length || !Number.isInteger(toIndex)) {
	    throw new Error('toIndex must be an integer greater than fromIndex and at most equal to length');
	  }

	  let maxValue = input[fromIndex];

	  for (let i = fromIndex + 1; i < toIndex; i++) {
	    if (input[i] > maxValue) maxValue = input[i];
	  }

	  return maxValue;
	}

	var lib$2 = max$1;

	var isAnyArray$1 = require$$0$2;

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

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

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

	  const {
	    fromIndex = 0,
	    toIndex = input.length
	  } = options;

	  if (fromIndex < 0 || fromIndex >= input.length || !Number.isInteger(fromIndex)) {
	    throw new Error('fromIndex must be a positive integer smaller than length');
	  }

	  if (toIndex <= fromIndex || toIndex > input.length || !Number.isInteger(toIndex)) {
	    throw new Error('toIndex must be an integer greater than fromIndex and at most equal to length');
	  }

	  let minValue = input[fromIndex];

	  for (let i = fromIndex + 1; i < toIndex; i++) {
	    if (input[i] < minValue) minValue = input[i];
	  }

	  return minValue;
	}

	var lib$1 = min$1;

	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$3(x) || !isAnyArray$3(y)) {
	    throw new TypeError('x and y must be arrays');
	  }

	  if (x.length !== y.length) {
	    throw new RangeError('x and y arrays must have the same length');
	  }
	}

	class BaseRegression$1 {
	  constructor() {
	    if (new.target === BaseRegression$1) {
	      throw new Error('BaseRegression must be subclassed');
	    }
	  }

	  predict(x) {
	    if (typeof x === 'number') {
	      return this._predict(x);
	    } else if (isAnyArray$3(x)) {
	      const y = [];

	      for (let i = 0; i < x.length; i++) {
	        y.push(this._predict(x[i]));
	      }

	      return y;
	    } else {
	      throw new TypeError('x must be a number or array');
	    }
	  }

	  _predict() {
	    throw new Error('_predict must be implemented');
	  }

	  train() {// Do nothing for this package
	  }

	  toString() {
	    return '';
	  }

	  toLaTeX() {
	    return '';
	  }
	  /**
	   * Return the correlation coefficient of determination (r) and chi-square.
	   * @param {Array<number>} x
	   * @param {Array<number>} y
	   * @return {object}
	   */


	  score(x, y) {
	    if (!isAnyArray$3(x) || !isAnyArray$3(y) || x.length !== y.length) {
	      throw new Error('x and y must be arrays of the same length');
	    }

	    const n = x.length;
	    const y2 = new Array(n);

	    for (let i = 0; i < n; i++) {
	      y2[i] = this._predict(x[i]);
	    }

	    let xSum = 0;
	    let ySum = 0;
	    let chi2 = 0;
	    let rmsd = 0;
	    let xSquared = 0;
	    let ySquared = 0;
	    let xY = 0;

	    for (let i = 0; i < n; i++) {
	      xSum += y2[i];
	      ySum += y[i];
	      xSquared += y2[i] * y2[i];
	      ySquared += y[i] * y[i];
	      xY += y2[i] * y[i];

	      if (y[i] !== 0) {
	        chi2 += (y[i] - y2[i]) * (y[i] - y2[i]) / y[i];
	      }

	      rmsd += (y[i] - y2[i]) * (y[i] - y2[i]);
	    }

	    const r = (n * xY - xSum * ySum) / Math.sqrt((n * xSquared - xSum * xSum) * (n * ySquared - ySum * ySum));
	    return {
	      r: r,
	      r2: r * r,
	      chi2: chi2,
	      rmsd: Math.sqrt(rmsd / n)
	    };
	  }

	}

	var src$6 = /*#__PURE__*/Object.freeze({
		__proto__: null,
		'default': BaseRegression$1,
		maybeToPrecision: maybeToPrecision,
		checkArrayLength: checkArraySize
	});

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

	var medianQuickselect_min = {exports: {}};

	(function (module) {
	  (function () {
	    function a(d) {
	      for (var e = 0, f = d.length - 1, g = void 0, h = void 0, i = void 0, j = c(e, f); !0;) {
	        if (f <= e) return d[j];
	        if (f == e + 1) return d[e] > d[f] && b(d, e, f), d[j];

	        for (g = c(e, f), d[g] > d[f] && b(d, g, f), d[e] > d[f] && b(d, e, f), d[g] > d[e] && b(d, g, e), b(d, g, e + 1), h = e + 1, i = f; !0;) {
	          do h++; while (d[e] > d[h]);

	          do i--; while (d[i] > d[e]);

	          if (i < h) break;
	          b(d, h, i);
	        }

	        b(d, e, i), i <= j && (e = h), i >= j && (f = i - 1);
	      }
	    }

	    var b = function b(d, e, f) {
	      var _ref;

	      return _ref = [d[f], d[e]], d[e] = _ref[0], d[f] = _ref[1], _ref;
	    },
	        c = function c(d, e) {
	      return ~~((d + e) / 2);
	    };

	    module.exports ? module.exports = a : window.median = a;
	  })();
	})(medianQuickselect_min);

	var quickSelectMedian = medianQuickselect_min.exports;

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

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

	  return quickSelectMedian(input.slice());
	}

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

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

	function _interopDefault$1(ex) {
	  return ex && typeof ex === 'object' && 'default' in ex ? ex['default'] : ex;
	}

	var BaseRegression = require$$0$1;

	var BaseRegression__default = _interopDefault$1(BaseRegression);

	var median = _interopDefault$1(require$$1);

	class TheilSenRegression extends BaseRegression__default {
	  /**
	   * Theil–Sen estimator
	   * https://en.wikipedia.org/wiki/Theil%E2%80%93Sen_estimator
	   * @param {Array<number>|boolean} x
	   * @param {Array<number>|object} y
	   * @constructor
	   */
	  constructor(x, y) {
	    super();

	    if (x === true) {
	      // loads the model
	      this.slope = y.slope;
	      this.intercept = y.intercept;
	      this.coefficients = y.coefficients;
	    } else {
	      // creates the model
	      BaseRegression.checkArrayLength(x, y);
	      theilSen(this, x, y);
	    }
	  }

	  toJSON() {
	    return {
	      name: 'TheilSenRegression',
	      slope: this.slope,
	      intercept: this.intercept
	    };
	  }

	  _predict(input) {
	    return this.slope * input + this.intercept;
	  }

	  computeX(input) {
	    return (input - this.intercept) / this.slope;
	  }

	  toString(precision) {
	    var result = 'f(x) = ';

	    if (this.slope) {
	      var xFactor = BaseRegression.maybeToPrecision(this.slope, precision);
	      result += `${Math.abs(xFactor - 1) < 1e-5 ? '' : `${xFactor} * `}x`;

	      if (this.intercept) {
	        var absIntercept = Math.abs(this.intercept);
	        var operator = absIntercept === this.intercept ? '+' : '-';
	        result += ` ${operator} ${BaseRegression.maybeToPrecision(absIntercept, precision)}`;
	      }
	    } else {
	      result += BaseRegression.maybeToPrecision(this.intercept, precision);
	    }

	    return result;
	  }

	  toLaTeX(precision) {
	    return this.toString(precision);
	  }

	  static load(json) {
	    if (json.name !== 'TheilSenRegression') {
	      throw new TypeError('not a Theil-Sen model');
	    }

	    return new TheilSenRegression(true, json);
	  }

	}

	function theilSen(regression, x, y) {
	  let len = x.length;
	  let slopes = new Array(len * len);
	  let count = 0;

	  for (let i = 0; i < len; ++i) {
	    for (let j = i + 1; j < len; ++j) {
	      if (x[i] !== x[j]) {
	        slopes[count++] = (y[j] - y[i]) / (x[j] - x[i]);
	      }
	    }
	  }

	  slopes.length = count;
	  let medianSlope = median(slopes);
	  let cuts = new Array(len);

	  for (let i = 0; i < len; ++i) {
	    cuts[i] = y[i] - medianSlope * x[i];
	  }

	  regression.slope = medianSlope;
	  regression.intercept = median(cuts);
	  regression.coefficients = [regression.intercept, regression.slope];
	}

	var lib = TheilSenRegression;

	const max = lib$2;
	const min = lib$1;
	const Regression = lib;

	function massShifts(similarities) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const {
	    minSimilarity = 0.95,
	    minLength = 10
	  } = options;
	  let results = [];

	  if (!Array.isArray(similarities)) {
	    for (let key of results) {
	      for (let entry of results[key]) {
	        results.push(entry);
	      }
	    }
	  } else {
	    results = similarities;
	  }

	  results = results.filter(result => result.ms && result.ms.similarity && result.ms.similarity.value > minSimilarity);

	  if (results.length < minLength) {
	    throw new Error(`X rescale can not be applied. We need at least ${minLength} peaks with over ${Math.round(minSimilarity * 100)}% similarity`);
	  }

	  const data = results.map(result => {
	    return {
	      em: result.ms.em,
	      delta: result.ms.delta
	    };
	  }).sort((a, b) => a.em - b.em);
	  let shifts = {
	    x: [],
	    y: []
	  };
	  data.forEach(datum => {
	    shifts.x.push(Number(datum.em));
	    shifts.y.push(Number(datum.delta));
	  });
	  const regression = new Regression(shifts.x, shifts.y);
	  let minX = min(shifts.x);
	  let maxX = max(shifts.x);
	  let shiftsPPM = {
	    x: shifts.x,
	    y: []
	  };
	  data.forEach(datum => {
	    shiftsPPM.y.push(Number(datum.delta / datum.em * 1e6));
	  });
	  let regressionChart = {
	    x: [],
	    y: []
	  };

	  for (let i = minX; i < maxX; i += (maxX - minX) / 1000) {
	    regressionChart.x.push(i);
	    regressionChart.y.push(regression.predict(i));
	  }

	  return {
	    shifts,
	    shiftsPPM,
	    fit: regressionChart,
	    score: regression.score(shifts.x, shifts.y),
	    predictFct: regression.predict.bind(regression),
	    tex: regression.toLaTeX(3),
	    slope: regression.slope,
	    intercept: regression.intercept,
	    predictFctString: `${regression.slope} * mass + ${regression.intercept}`
	  };
	}

	var massShifts_1 = massShifts;

	const matcher$1 = src$d.general;
	/**
	    Searching by various criteria. This mass will not take into account the mass
	    of the mass of the electron
	* @param {object}   [filter={}}]
	* @param {number}   [filter.minMW=0] - Minimal molecular weight
	* @param {number}   [filter.maxMW=+Infinity] - Maximal molecular weight
	* @param {number}   [filter.minEM=0] - Minimal monoisotopic mass
	* @param {number}   [filter.maxEM=+Infinity] - Maximal monoisotopic mass
	* @param {number}   [filter.minCharge=-Infinity] - Minimal charge
	* @param {number}   [filter.maxCharge=+Infinity] - Maximal charge
	* @param {object}   [filter.unsaturation={}}]
	* @param {number}   [filter.unsaturation.min=-Infinity] - Minimal unsaturation
	* @param {number}   [filter.unsaturation.max=+Infinity] - Maximal unsaturation
	* @param {number}   [filter.unsaturation.onlyInteger=false] - Integer unsaturation
	* @param {number}   [filter.unsaturation.onlyNonInteger=false] - Non integer unsaturation
	* @param {object}   [filter.atoms] - object of atom:{min, max}

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

	 *
	*/

	var search = function search(filter) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  let {
	    databases = Object.keys(this.databases),
	    flatten = false
	  } = options;
	  let results = {};

	  for (let database of databases) {
	    results[database] = this.databases[database].filter(entry => matcher$1(entry, filter));
	  }

	  if (flatten) {
	    let flattenResults = [];

	    for (let database of databases) {
	      for (let entry of results[database]) {
	        entry.database = database;
	        flattenResults.push(entry);
	      }
	    }

	    return flattenResults;
	  } else {
	    return results;
	  }
	};

	const matcher = msemMatcher;
	const preprocessIonizations$2 = preprocessIonizations$5;
	/**
	Search for an experimental monoisotopic mass
	* @param {number}   msem - The observed monoisotopic mass
	* @param {object}   [options={}]
	* @param {array}    [options.databases] - an array containing the name of the databases so search, by default all
	* @param {boolean}  [options.flatten=false] - should we return the array as a flat result
	* @param {string}   [options.ionizations] - list the allowed ionizations possibilities
	* @param {object}   [options.filter={}]
	* @param {number}         [options.filter.targetMass] - Target mass, allows to calculate error and filter results
	* @param {Array<number>}  [options.filter.targetMasses] - Target masses: SORTED array of numbers
	* @param {Array<number>}  [options.filter.targetIntensities] - Target intensities: SORTED array of numbers
	* @param {number}         [options.filter.minEM=0] - Minimal monoisotopic mass
	* @param {number}         [options.filter.maxEM=+Infinity] - Maximal monoisotopic mass
	* @param {number}         [options.filter.minMSEM=0] - Minimal monoisotopic mass observed by mass
	* @param {number}         [options.filter.maxMSEM=+Infinity] - Maximal monoisotopic mass observed by mass
	* @param {number}         [options.filter.minCharge=-Infinity] - Minimal charge
	* @param {number}         [options.filter.maxCharge=+Infinity] - Maximal charge
	* @param {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.callback] - a function to filter the MF
	* @param {object}         [options.filter.atoms] - object of atom:{min, max}
	*/

	var searchMSEM = function searchMSEM(msem) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  let filter = Object.assign({}, options.filter || {}, {
	    targetMass: msem
	  });
	  let {
	    databases = Object.keys(this.databases),
	    flatten = false
	  } = options;
	  let ionizations = preprocessIonizations$2(options.ionizations);
	  let results = {};

	  for (let database of databases) {
	    results[database] = [];
	  }

	  for (let ionization of ionizations) {
	    filter.ionization = ionization;

	    for (let database of databases) {
	      for (let entry of this.databases[database]) {
	        let match = matcher(entry, filter);

	        if (match) {
	          results[database].push(Object.assign({}, entry, {
	            ms: match.ms,
	            ionization: match.ionization
	          }));
	        }
	      }
	    }
	  }

	  if (flatten) {
	    let flattenResults = [];

	    for (let database of databases) {
	      for (let entry of results[database]) {
	        entry.database = database;
	        flattenResults.push(entry);
	      }
	    }

	    flattenResults.sort((a, b) => Math.abs(a.ms.ppm) - Math.abs(b.ms.ppm));
	    return flattenResults;
	  } else {
	    Object.keys(results).forEach(k => results[k].sort((a, b) => Math.abs(a.ms.ppm) - Math.abs(b.ms.ppm)));
	    return results;
	  }
	};

	const crossFetch = browserPonyfill.exports;

	var fetchJSON$1 = async function fetchJSONBrowser(url) {
	  const result = await crossFetch(url);
	  return result.json();
	};

	const {
	  ELECTRON_MASS: ELECTRON_MASS$1
	} = constants;
	const mfParser = src$h;
	const getMsInfo$1 = getMsInfo$4;
	const preprocessIonizations$1 = preprocessIonizations$5;
	const fetchJSON = fetchJSON$1;
	/**
	 * Generates a database 'pubchem' based on all molecular formula available
	 * in the database and a monoisotopic mass.
	 * @param {number|string|number[]} masses - Observed monoisotopic mass
	 * @param {object} [options={}]
	 * @param {string} [options.databaseName='pubchem']
	 * @param {string} [options.ionizations=''] - string containing a comma separated list of modifications
	 * @param {number} [options.precision=1000] - Precision of the monoisotopic mass in ppm
	 * @param {number} [options.limit=1000] - Maximal number of entries to return
	 * @param {number} [options.minPubchemEntries=5] - Minimal number of molecules having a specific MF
	 * @param {number} [options.url='https://pubchem.cheminfo.org/mfs/em'] - URL of the webservice
	 */

	var searchPubchem = async function searchPubchem(masses) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const {
	    url = 'https://pubchem.cheminfo.org/mfs/em',
	    precision = 1000,
	    limit = 1000,
	    minPubchemEntries = 5
	  } = options;

	  if (typeof masses === 'number') {
	    masses = [masses];
	  }

	  if (typeof masses === 'string') {
	    masses = masses.split(/[\r\n\t,; ]+/).map(Number);
	  }

	  let promises = [];
	  let ionizations = preprocessIonizations$1(options.ionizations);

	  for (let mass of masses) {
	    for (let ionization of ionizations) {
	      let realMass = mass * Math.abs(ionization.charge || 1) - ionization.em + ELECTRON_MASS$1 * ionization.charge;
	      const pubchemURL = `${url}?em=${realMass}&precision=${precision}&limit=${limit}&minPubchemEntries=${minPubchemEntries}`;
	      promises.push(fetchJSON(pubchemURL));
	    }
	  }

	  let results = await Promise.all(promises);
	  let mfs = [];

	  for (let i = 0; i < results.length; i++) {
	    for (let mf of results[i].result) {
	      try {
	        let mfInfo = new mfParser.MF(mf.mf).getInfo();
	        mfInfo.ionization = ionizations[i];
	        mfInfo.em = mfInfo.monoisotopicMass;
	        mfInfo.ms = getMsInfo$1(mfInfo, {
	          targetMass: masses
	        }).ms;
	        mfInfo.info = {
	          nbPubchemEntries: mf.total
	        };
	        mfs.push(mfInfo);
	      } catch (e) {
	        // eslint-disable-next-line no-console
	        console.warn(`${e}`);
	      }
	    }
	  } // because we can combine many ionizations we should resort the data


	  mfs.sort((a, b) => Math.abs(a.ms.ppm) - Math.abs(b.ms.ppm));
	  return mfs;
	};

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

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

	class Gaussian {
	  constructor() {
	    let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
	    const {
	      fwhm = 500,
	      sd
	    } = options;
	    this.fwhm = sd ? gaussianWidthToFWHM(2 * sd) : fwhm;
	  }

	  fwhmToWidth() {
	    let fwhm = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.fwhm;
	    return gaussianFwhmToWidth(fwhm);
	  }

	  widthToFWHM(width) {
	    return gaussianWidthToFWHM(width);
	  }

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

	  getArea() {
	    let height = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : calculateGaussianHeight({
	      fwhm: this.fwhm
	    });
	    return getGaussianArea({
	      fwhm: this.fwhm,
	      height
	    });
	  }

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

	  getData() {
	    let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
	    return getGaussianData(this, options);
	  }

	  calculateHeight() {
	    let area = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1;
	    return calculateGaussianHeight({
	      fwhm: this.fwhm,
	      area
	    });
	  }

	  getParameters() {
	    return ['fwhm'];
	  }

	}
	function calculateGaussianHeight(options) {
	  let {
	    fwhm = 500,
	    area = 1,
	    sd
	  } = options;
	  if (sd) fwhm = gaussianWidthToFWHM(2 * sd);
	  return 2 * area / ROOT_PI_OVER_LN2 / fwhm;
	}
	function gaussianFct(x, fwhm) {
	  return Math.exp(GAUSSIAN_EXP_FACTOR * Math.pow(x / fwhm, 2));
	}
	function gaussianWidthToFWHM(width) {
	  return width * ROOT_2LN2;
	}
	function gaussianFwhmToWidth(fwhm) {
	  return fwhm / ROOT_2LN2;
	}
	function getGaussianArea(options) {
	  let {
	    fwhm = 500,
	    sd,
	    height = 1
	  } = options;
	  if (sd) fwhm = gaussianWidthToFWHM(2 * sd);
	  return height * ROOT_PI_OVER_LN2 * fwhm / 2;
	}
	function getGaussianFactor() {
	  let area = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0.9999;
	  return Math.sqrt(2) * erfinv(area);
	}
	function getGaussianData() {
	  let shape = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  let {
	    fwhm = 500,
	    sd
	  } = shape;
	  if (sd) fwhm = gaussianWidthToFWHM(2 * sd);
	  let {
	    length,
	    factor = getGaussianFactor(),
	    height = calculateGaussianHeight({
	      fwhm
	    })
	  } = options;

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

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

	  for (let i = 0; i <= center; i++) {
	    data[i] = gaussianFct(i - center, fwhm) * height;
	    data[length - 1 - i] = data[i];
	  }

	  return data;
	}

	class Lorentzian {
	  constructor() {
	    let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
	    const {
	      fwhm = 500
	    } = options;
	    this.fwhm = fwhm;
	  }

	  fwhmToWidth() {
	    let fwhm = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.fwhm;
	    return lorentzianFwhmToWidth(fwhm);
	  }

	  widthToFWHM(width) {
	    return lorentzianWidthToFWHM(width);
	  }

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

	  getArea() {
	    let height = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1;
	    return getLorentzianArea({
	      fwhm: this.fwhm,
	      height
	    });
	  }

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

	  getData() {
	    let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
	    return getLorentzianData(this, options);
	  }

	  calculateHeight() {
	    let area = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1;
	    return calculateLorentzianHeight({
	      fwhm: this.fwhm,
	      area
	    });
	  }

	  getParameters() {
	    return ['fwhm'];
	  }

	}
	const calculateLorentzianHeight = _ref => {
	  let {
	    fwhm = 1,
	    area = 1
	  } = _ref;
	  return 2 * area / Math.PI / fwhm;
	};
	const lorentzianFct = (x, fwhm) => {
	  return Math.pow(fwhm, 2) / (4 * Math.pow(x, 2) + Math.pow(fwhm, 2));
	};
	const lorentzianWidthToFWHM = width => {
	  return width * ROOT_THREE;
	};
	const lorentzianFwhmToWidth = fwhm => {
	  return fwhm / ROOT_THREE;
	};
	const getLorentzianArea = options => {
	  const {
	    fwhm = 500,
	    height = 1
	  } = options;
	  return height * Math.PI * fwhm / 2;
	};
	const getLorentzianFactor = function () {
	  let area = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0.9999;
	  return 2 * Math.tan(Math.PI * (area - 0.5));
	};
	const getLorentzianData = function () {
	  let shape = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  let {
	    fwhm = 500
	  } = shape;
	  let {
	    length,
	    factor = getLorentzianFactor(),
	    height = calculateLorentzianHeight({
	      fwhm,
	      area: 1
	    })
	  } = options;

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

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

	  for (let i = 0; i <= center; i++) {
	    data[i] = lorentzianFct(i - center, fwhm) * height;
	    data[length - 1 - i] = data[i];
	  }

	  return data;
	};

	class PseudoVoigt {
	  constructor() {
	    let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
	    const {
	      fwhm = 500,
	      mu = 0.5
	    } = options;
	    this.mu = mu;
	    this.fwhm = fwhm;
	  }

	  fwhmToWidth() {
	    let fwhm = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.fwhm;
	    let mu = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.mu;
	    return pseudoVoigtFwhmToWidth(fwhm, mu);
	  }

	  widthToFWHM(width) {
	    let mu = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.mu;
	    return pseudoVoigtWidthToFWHM(width, mu);
	  }

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

	  getArea() {
	    let height = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1;
	    return getPseudoVoigtArea({
	      fwhm: this.fwhm,
	      height,
	      mu: this.mu
	    });
	  }

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

	  getData() {
	    let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
	    const {
	      length,
	      factor,
	      height = calculatePseudoVoigtHeight({
	        fwhm: this.fwhm,
	        mu: this.mu,
	        area: 1
	      })
	    } = options;
	    return getPseudoVoigtData(this, {
	      factor,
	      length,
	      height
	    });
	  }

	  calculateHeight() {
	    let area = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1;
	    return calculatePseudoVoigtHeight({
	      fwhm: this.fwhm,
	      mu: this.mu,
	      area
	    });
	  }

	  getParameters() {
	    return ['fwhm', 'mu'];
	  }

	}
	const calculatePseudoVoigtHeight = function () {
	  let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
	  let {
	    fwhm = 1,
	    mu = 0.5,
	    area = 1
	  } = options;
	  return 2 * area / (fwhm * (mu * ROOT_PI_OVER_LN2 + (1 - mu) * Math.PI));
	};
	const pseudoVoigtFct = (x, fwhm, mu) => {
	  return (1 - mu) * lorentzianFct(x, fwhm) + mu * gaussianFct(x, fwhm);
	};
	const pseudoVoigtWidthToFWHM = function (width) {
	  let mu = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0.5;
	  return width * (mu * ROOT_2LN2_MINUS_ONE + 1);
	};
	const pseudoVoigtFwhmToWidth = function (fwhm) {
	  let mu = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0.5;
	  return fwhm / (mu * ROOT_2LN2_MINUS_ONE + 1);
	};
	const getPseudoVoigtArea = options => {
	  const {
	    fwhm = 500,
	    height = 1,
	    mu = 0.5
	  } = options;
	  return fwhm * height * (mu * ROOT_PI_OVER_LN2 + (1 - mu) * Math.PI) / 2;
	};
	const getPseudoVoigtFactor = function () {
	  let area = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0.9999;
	  let mu = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0.5;
	  return mu < 1 ? getLorentzianFactor(area) : getGaussianFactor(area);
	};
	const getPseudoVoigtData = function () {
	  let shape = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  let {
	    fwhm = 500,
	    mu = 0.5
	  } = shape;
	  let {
	    length,
	    factor = getPseudoVoigtFactor(0.999, mu),
	    height = calculatePseudoVoigtHeight({
	      fwhm,
	      mu,
	      area: 1
	    })
	  } = options;

	  if (!height) {
	    height = 1 / (mu / Math.sqrt(-GAUSSIAN_EXP_FACTOR / Math.PI) * fwhm + (1 - mu) * fwhm * Math.PI / 2);
	  }

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

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

	  for (let i = 0; i <= center; i++) {
	    data[i] = pseudoVoigtFct(i - center, fwhm, mu) * height;
	    data[length - 1 - i] = data[i];
	  }

	  return data;
	};

	class Gaussian2D {
	  constructor() {
	    let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
	    let {
	      fwhm = 50,
	      sd
	    } = options;
	    fwhm = ensureFWHM2D(fwhm, sd);
	    this.fwhmX = fwhm.x;
	    this.fwhmY = fwhm.y;
	  }

	  fct(x, y) {
	    return gaussian2DFct(x, y, this.fwhmX, this.fwhmY);
	  }

	  getData() {
	    let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
	    return getGaussian2DData({
	      fwhm: {
	        x: this.fwhmX,
	        y: this.fwhmY
	      }
	    }, options);
	  }

	  getFactor() {
	    let volume = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1;
	    return getGaussianFactor(volume);
	  }

	  getVolume() {
	    let height = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : calculateGaussian2DHeight({
	      fwhm: {
	        x: this.fwhmX,
	        y: this.fwhmY
	      },
	      volume: 1
	    });
	    return getGaussian2DVolume({
	      fwhm: {
	        x: this.fwhmX,
	        y: this.fwhmY
	      },
	      height
	    });
	  }

	  widthToFWHM(width) {
	    return gaussianWidthToFWHM(width);
	  }

	  fwhmToWidth(fwhm) {
	    return gaussianFwhmToWidth(fwhm);
	  }

	  calculateHeight() {
	    let volume = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1;
	    return calculateGaussian2DHeight({
	      volume,
	      fwhm: {
	        x: this.fwhmX,
	        y: this.fwhmY
	      }
	    });
	  }

	  set fwhm(fwhm) {
	    fwhm = ensureXYNumber$1(fwhm);
	    this.fwhmX = fwhm.x;
	    this.fwhmY = fwhm.y;
	  }

	}
	const gaussian2DFct = (x, y, xFWHM, yFWHM) => {
	  return Math.exp(GAUSSIAN_EXP_FACTOR * (Math.pow(x / xFWHM, 2) + Math.pow(y / yFWHM, 2)));
	};
	const getGaussian2DData = function (shape) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  let {
	    fwhm = 50,
	    sd
	  } = shape;
	  fwhm = ensureFWHM2D(fwhm, sd);
	  let {
	    factor = getGaussianFactor(),
	    length = {
	      x: 0,
	      y: 0
	    },
	    height = calculateGaussian2DHeight({
	      fwhm,
	      volume: 1
	    })
	  } = options;
	  factor = ensureXYNumber$1(factor);
	  length = ensureXYNumber$1(length);

	  for (const axis of ['x', 'y']) {
	    if (!length[axis]) {
	      length[axis] = Math.min(Math.ceil(fwhm[axis] * factor[axis]), Math.pow(2, 25) - 1);
	      if (length[axis] % 2 === 0) length[axis]++;
	    }
	  }

	  const xCenter = (length.x - 1) / 2;
	  const yCenter = (length.y - 1) / 2;
	  const data = new Array(length.x);

	  for (let i = 0; i < length.x; i++) {
	    data[i] = new Float64Array(length.y);
	  }

	  for (let i = 0; i < length.x; i++) {
	    for (let j = 0; j < length.y; j++) {
	      data[i][j] = gaussian2DFct(i - xCenter, j - yCenter, fwhm.x, fwhm.y) * height;
	    }
	  }

	  return data;
	};
	const calculateGaussian2DHeight = function () {
	  let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
	  let {
	    volume = 1,
	    fwhm = 50,
	    sd
	  } = options;
	  fwhm = ensureFWHM2D(fwhm, sd);
	  return volume * Math.LN2 * 4 / (Math.PI * fwhm.y * fwhm.x);
	};
	const getGaussian2DVolume = function () {
	  let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
	  let {
	    fwhm = 50,
	    height = 1,
	    sd
	  } = options;
	  fwhm = ensureFWHM2D(fwhm, sd);
	  return height * Math.PI * fwhm.y * fwhm.x / Math.LN2 / 4;
	};

	function ensureXYNumber$1(input) {
	  return typeof input !== 'object' ? {
	    x: input,
	    y: input
	  } : { ...input
	  };
	}

	function ensureFWHM2D(fwhm, sd) {
	  if (sd !== undefined) {
	    let sdObject = ensureXYNumber$1(sd);
	    return {
	      x: gaussianWidthToFWHM(2 * sdObject.x),
	      y: gaussianWidthToFWHM(2 * sdObject.y)
	    };
	  } else if (fwhm !== undefined) {
	    return ensureXYNumber$1(fwhm);
	  } else {
	    throw new Error('ensureFWHM2D must have either fwhm or sd defined');
	  }
	}

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

	function getShape1D(shape) {
	  const {
	    kind
	  } = shape;

	  switch (kind) {
	    case 'gaussian':
	      return new Gaussian(shape);

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

	    case 'pseudoVoigt':
	      return new PseudoVoigt(shape);

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

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

	function getShape2D(shape) {
	  const {
	    kind
	  } = shape;

	  switch (kind) {
	    case 'gaussian':
	      return new Gaussian2D(shape);

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

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

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

	const toString = Object.prototype.toString;
	/**
	 * Checks if an object is an instance of an Array (array or typed array).
	 *
	 * @param {any} value - Object to check.
	 * @returns {boolean} True if the object is an array.
	 */

	function isAnyArray(value) {
	  return toString.call(value).endsWith('Array]');
	}

	/**
	 * This function xAdd the first array by the second array or a constant value to each element of the first array
	 *
	 * @param array1 - the first array
	 * @param array2 - the second array or number
	 */

	function xAdd(array1, array2) {
	  let isConstant = false;
	  let constant = 0;

	  if (isAnyArray(array2)) {
	    if (array1.length !== array2.length) {
	      throw new Error('xAdd: size of array1 and array2 must be identical');
	    }
	  } else {
	    isConstant = true;
	    constant = array2;
	  }

	  let array3 = new Float64Array(array1.length);

	  if (isConstant) {
	    for (let i = 0; i < array1.length; i++) {
	      array3[i] = array1[i] + constant;
	    }
	  } else {
	    for (let i = 0; i < array1.length; i++) {
	      array3[i] = array1[i] + array2[i];
	    }
	  }

	  return array3;
	}

	/**
	 * Checks if input is of type array
	 *
	 * @param input - input
	 */

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

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

	/**
	 * Returns the closest index of a `target`
	 *
	 * @param array - array of numbers
	 * @param target - target
	 * @returns - closest index
	 */
	function xFindClosestIndex(array, target) {
	  let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
	  const {
	    sorted = true
	  } = options;

	  if (sorted) {
	    let low = 0;
	    let high = array.length - 1;
	    let middle = 0;

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

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

	    if (low < array.length - 1) {
	      if (Math.abs(target - array[low]) < Math.abs(array[low + 1] - target)) {
	        return low;
	      } else {
	        return low + 1;
	      }
	    } else {
	      return low;
	    }
	  } else {
	    let index = 0;
	    let diff = Number.POSITIVE_INFINITY;

	    for (let i = 0; i < array.length; i++) {
	      const currentDiff = Math.abs(array[i] - target);

	      if (currentDiff < diff) {
	        diff = currentDiff;
	        index = i;
	      }
	    }

	    return index;
	  }
	}

	/**
	 * Returns an object with {fromIndex, toIndex} for a specific from / to
	 *
	 * @param x - array of numbers
	 * @param options - Options
	 */

	function xGetFromToIndex(x) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  let {
	    fromIndex,
	    toIndex,
	    from,
	    to
	  } = options;

	  if (fromIndex === undefined) {
	    if (from !== undefined) {
	      fromIndex = xFindClosestIndex(x, from);
	    } else {
	      fromIndex = 0;
	    }
	  }

	  if (toIndex === undefined) {
	    if (to !== undefined) {
	      toIndex = xFindClosestIndex(x, to);
	    } else {
	      toIndex = x.length - 1;
	    }
	  }

	  if (fromIndex < 0) fromIndex = 0;
	  if (toIndex < 0) toIndex = 0;
	  if (fromIndex >= x.length) fromIndex = x.length - 1;
	  if (toIndex >= x.length) toIndex = x.length - 1;
	  if (fromIndex > toIndex) [fromIndex, toIndex] = [toIndex, fromIndex];
	  return {
	    fromIndex,
	    toIndex
	  };
	}

	/**
	 * Computes the maximal value of an array of values
	 *
	 * @param array - array of numbers
	 * @param options - options
	 */

	function xMaxValue(array) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  xCheck(array);
	  const {
	    fromIndex,
	    toIndex
	  } = xGetFromToIndex(array, options);
	  let maxValue = array[fromIndex];

	  for (let i = fromIndex + 1; i <= toIndex; i++) {
	    if (array[i] > maxValue) {
	      maxValue = array[i];
	    }
	  }

	  return maxValue;
	}

	function matrixCheck(data) {
	  if (data.length === 0 || data[0].length === 0) {
	    throw RangeError('matrix should contain data');
	  }

	  const firstLength = data[0].length;

	  for (let i = 1; i < data.length; i++) {
	    if (data[i].length !== firstLength) {
	      throw new RangeError('All rows should has the same length');
	    }
	  }
	}

	/**
	 * Get min and max Z
	 *
	 * @param matrix - matrix [rows][cols].
	 */

	function matrixMinMaxZ(matrix) {
	  matrixCheck(matrix);
	  const nbRows = matrix.length;
	  const nbColumns = matrix[0].length;
	  let min = matrix[0][0];
	  let max = matrix[0][0];

	  for (let column = 0; column < nbColumns; column++) {
	    for (let row = 0; row < nbRows; row++) {
	      if (matrix[row][column] < min) min = matrix[row][column];
	      if (matrix[row][column] > max) max = matrix[row][column];
	    }
	  }

	  return {
	    min,
	    max
	  };
	}

	const LOOP = 8;
	const FLOAT_MUL = 1 / 16777216;
	const sh1 = 15;
	const sh2 = 18;
	const sh3 = 11;

	function multiply_uint32(n, m) {
	  n >>>= 0;
	  m >>>= 0;
	  const nlo = n & 0xffff;
	  const nhi = n - nlo;
	  return (nhi * m >>> 0) + nlo * m >>> 0;
	}

	class XSadd {
	  constructor() {
	    let seed = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : Date.now();
	    this.state = new Uint32Array(4);
	    this.init(seed);
	    this.random = this.getFloat.bind(this);
	  }
	  /**
	   * Returns a 32-bit integer r (0 <= r < 2^32)
	   */


	  getUint32() {
	    this.nextState();
	    return this.state[3] + this.state[2] >>> 0;
	  }
	  /**
	   * Returns a floating point number r (0.0 <= r < 1.0)
	   */


	  getFloat() {
	    return (this.getUint32() >>> 8) * FLOAT_MUL;
	  }

	  init(seed) {
	    if (!Number.isInteger(seed)) {
	      throw new TypeError('seed must be an integer');
	    }

	    this.state[0] = seed;
	    this.state[1] = 0;
	    this.state[2] = 0;
	    this.state[3] = 0;

	    for (let i = 1; i < LOOP; i++) {
	      this.state[i & 3] ^= i + multiply_uint32(1812433253, this.state[i - 1 & 3] ^ this.state[i - 1 & 3] >>> 30 >>> 0) >>> 0;
	    }

	    this.periodCertification();

	    for (let i = 0; i < LOOP; i++) {
	      this.nextState();
	    }
	  }

	  periodCertification() {
	    if (this.state[0] === 0 && this.state[1] === 0 && this.state[2] === 0 && this.state[3] === 0) {
	      this.state[0] = 88; // X

	      this.state[1] = 83; // S

	      this.state[2] = 65; // A

	      this.state[3] = 68; // D
	    }
	  }

	  nextState() {
	    let t = this.state[0];
	    t ^= t << sh1;
	    t ^= t >>> sh2;
	    t ^= this.state[3] << sh3;
	    this.state[0] = this.state[1];
	    this.state[1] = this.state[2];
	    this.state[2] = this.state[3];
	    this.state[3] = t;
	  }

	}

	/**
	 * Create a random array of numbers of a specific length
	 *
	 * @return - array of random floats normally distributed
	 */

	let spare;
	let hasSpare = false;
	function createRandomArray() {
	  let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
	  let {
	    mean = 0,
	    standardDeviation = 1,
	    length = 1000,
	    range = 1,
	    seed,
	    distribution = 'normal'
	  } = options;
	  const generator = new XSadd(seed);
	  let returnArray = new Float64Array(length);

	  switch (distribution) {
	    case 'normal':
	      for (let i = 0; i < length; i++) {
	        returnArray[i] = generateGaussian(mean, standardDeviation, generator);
	      }

	      break;

	    case 'uniform':
	      for (let i = 0; i < length; i++) {
	        returnArray[i] = (generator.random() - 0.5) * range + mean;
	      }

	      break;

	    default:
	      // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
	      throw new Error(`unknown distribution: ${distribution}`);
	  }

	  return returnArray;
	}

	function generateGaussian(mean, standardDeviation, generator) {
	  let val, u, v, s;

	  if (hasSpare) {
	    hasSpare = false;
	    val = spare * standardDeviation + mean;
	  } else {
	    do {
	      u = generator.random() * 2 - 1;
	      v = generator.random() * 2 - 1;
	      s = u * u + v * v;
	    } while (s >= 1 || s === 0);

	    s = Math.sqrt(-2.0 * Math.log(s) / s);
	    spare = v * s;
	    hasSpare = true;
	    val = mean + standardDeviation * u * s;
	  }

	  return val;
	}

	function addNoise(data) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const {
	    seed = 0,
	    distribution = 'normal',
	    percent = 1
	  } = options;
	  const range = xMaxValue(data.y) * percent / 100;
	  const noise = createRandomArray({
	    distribution,
	    seed,
	    mean: 0,
	    standardDeviation: range,
	    range,
	    length: data.x.length
	  });
	  data.y = xAdd(data.y, noise);
	  return data;
	}

	class SpectrumGenerator$1 {
	  constructor() {
	    let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
	    const {
	      from = 0,
	      to = 1000,
	      nbPoints = 10001,
	      peakWidthFct,
	      shape = {
	        kind: 'gaussian',
	        fwhm: 5
	      }
	    } = options;
	    this.from = from;
	    this.to = to;
	    this.nbPoints = nbPoints;
	    this.interval = (this.to - this.from) / (this.nbPoints - 1);
	    this.peakWidthFct = peakWidthFct;
	    this.maxPeakHeight = Number.MIN_SAFE_INTEGER;
	    this.data = {
	      x: new Float64Array(this.nbPoints),
	      y: new Float64Array(this.nbPoints)
	    };
	    let shapeGenerator = getShape1D(shape);
	    this.shape = shapeGenerator;
	    assertNumber$1(this.from, 'from');
	    assertNumber$1(this.to, 'to');
	    assertInteger$1(this.nbPoints, 'nbPoints');

	    if (this.to <= this.from) {
	      throw new RangeError('to option must be larger than from');
	    }

	    if (this.peakWidthFct && typeof this.peakWidthFct !== 'function') {
	      throw new TypeError('peakWidthFct option must be a function');
	    }

	    this.reset();
	  }
	  /**
	   * Add a series of peaks to the spectrum.
	   * @param peaks - Peaks to add.
	   */


	  addPeaks(peaks, options) {
	    if (!Array.isArray(peaks) && (typeof peaks !== 'object' || peaks.x === undefined || peaks.y === undefined || !Array.isArray(peaks.x) || !Array.isArray(peaks.y) || peaks.x.length !== peaks.y.length)) {
	      throw new TypeError('peaks must be an array or an object containing x[] and y[]');
	    }

	    if (Array.isArray(peaks)) {
	      for (const peak of peaks) {
	        this.addPeak(peak, options);
	      }
	    } else {
	      for (let i = 0; i < peaks.x.length; i++) {
	        this.addPeak([peaks.x[i], peaks.y[i]], options);
	      }
	    }
	  }
	  /**
	   * Add a single peak to the spectrum.
	   * A peak may be either defined as [x,y,fwhm,...] or as {x, y, shape}
	   * @param peak
	   * @param options
	   */


	  addPeak(peak) {
	    let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};

	    if (Array.isArray(peak) && peak.length < 2) {
	      throw new Error('peak must be an array with two (or three) values or an object with {x,y,width?}');
	    }

	    if (!Array.isArray(peak) && (peak.x === undefined || peak.y === undefined)) {
	      throw new Error('peak must be an array with two (or three) values or an object with {x,y,width?}');
	    }

	    let xPosition;
	    let intensity;
	    let peakFWHM;
	    let peakWidth;
	    let peakShapeOptions;

	    if (Array.isArray(peak)) {
	      [xPosition, intensity, peakFWHM, peakShapeOptions] = peak;
	    } else {
	      xPosition = peak.x;
	      intensity = peak.y;
	      peakWidth = peak.width;
	      peakShapeOptions = peak.shape;
	    }

	    if (intensity > this.maxPeakHeight) this.maxPeakHeight = intensity;
	    let {
	      shape: shapeOptions
	    } = options;

	    if (peakShapeOptions) {
	      shapeOptions = shapeOptions ? { ...shapeOptions,
	        ...peakShapeOptions
	      } : peakShapeOptions;
	    }

	    if (shapeOptions) {
	      this.shape = getShape1D(shapeOptions);
	    }

	    let {
	      widthLeft,
	      widthRight
	    } = options;
	    /*
	     if we don't force the fwhm we just take the one from the shape
	     however we have many way to force it:
	     - use [x,y,fwhm]
	     - define `width` that will be converted to fwhm
	     - define `widthLeft` and `widthRight` to define asymmetric peaks
	     - have a callback `peakWidthFct`
	     This should evolve in the future because we will not always have `fwhm`
	     */

	    const fwhm = peakFWHM !== undefined ? peakFWHM : peakWidth ? this.shape.widthToFWHM(peakWidth) : this.peakWidthFct ? this.peakWidthFct(xPosition) : this.shape.fwhm;
	    if (!widthLeft) widthLeft = fwhm;
	    if (!widthRight) widthRight = fwhm;

	    if (!widthLeft || !widthRight) {
	      throw new Error('Width left or right is undefined or zero');
	    }

	    let factor = options.factor === undefined ? this.shape.getFactor() : options.factor;
	    const firstValue = xPosition - widthLeft / 2 * factor;
	    const lastValue = xPosition + widthRight / 2 * factor;
	    const firstPoint = Math.max(0, Math.floor((firstValue - this.from) / this.interval));
	    const lastPoint = Math.min(this.nbPoints - 1, Math.ceil((lastValue - this.from) / this.interval));
	    const middlePoint = Math.round((xPosition - this.from) / this.interval); // PEAK SHAPE MAY BE ASYMMETRC (widthLeft and widthRight) !
	    // we calculate the left part of the shape

	    this.shape.fwhm = widthLeft;

	    for (let index = firstPoint; index < Math.max(middlePoint, 0); index++) {
	      this.data.y[index] += intensity * this.shape.fct(this.data.x[index] - xPosition);
	    } // we calculate the right part of the gaussian


	    this.shape.fwhm = widthRight;

	    for (let index = Math.min(middlePoint, lastPoint); index <= lastPoint; index++) {
	      this.data.y[index] += intensity * this.shape.fct(this.data.x[index] - xPosition);
	    }
	  }
	  /**
	   * Add a baseline to the spectrum.
	   * @param baselineFct - Mathematical function producing the baseline you want.
	   */


	  addBaseline(baselineFct) {
	    addBaseline(this.data, baselineFct);
	    return this;
	  }
	  /**
	   * Add noise to the spectrum.
	   *
	   * @param percent - Noise's amplitude in percents of the spectrum max value. Default: 1.
	   */


	  addNoise(options) {
	    addNoise(this.data, options);
	    return this;
	  }
	  /**
	   * Get the generated spectrum.
	   */


	  getSpectrum() {
	    let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};

	    if (typeof options === 'boolean') {
	      options = {
	        copy: options
	      };
	    }

	    const {
	      copy = true,
	      threshold = 0
	    } = options;

	    if (threshold) {
	      let minPeakHeight = this.maxPeakHeight * threshold;
	      let x = [];
	      let y = [];

	      for (let i = 0; i < this.data.x.length; i++) {
	        if (this.data.y[i] >= minPeakHeight) {
	          x.push(this.data.x[i]);
	          y.push(this.data.y[i]);
	        }
	      }

	      return {
	        x,
	        y
	      };
	    }

	    if (copy) {
	      return {
	        x: this.data.x.slice(),
	        y: this.data.y.slice()
	      };
	    } else {
	      return this.data;
	    }
	  }
	  /**
	   * Resets the generator with an empty spectrum.
	   */


	  reset() {
	    const spectrum = this.data;

	    for (let i = 0; i < this.nbPoints; i++) {
	      spectrum.x[i] = this.from + i * this.interval;
	    }

	    return this;
	  }

	}

	function assertInteger$1(value, name) {
	  if (!Number.isInteger(value)) {
	    throw new TypeError(`${name} option must be an integer`);
	  }
	}

	function assertNumber$1(value, name) {
	  if (!Number.isFinite(value)) {
	    throw new TypeError(`${name} option must be a number`);
	  }
	}
	/**
	 * Generates a spectrum and returns it.
	 * @param peaks - List of peaks to put in the spectrum.
	 * @param options
	 */


	function generateSpectrum(peaks) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const {
	    generator: generatorOptions,
	    noise,
	    baseline,
	    threshold,
	    peakOptions
	  } = options;
	  const generator = new SpectrumGenerator$1(generatorOptions);
	  generator.addPeaks(peaks, peakOptions);
	  if (baseline) generator.addBaseline(baseline);

	  if (noise) {
	    generator.addNoise(noise);
	  }

	  return generator.getSpectrum({
	    threshold
	  });
	}

	const axis2D = ['x', 'y'];
	const peakCoordinates = ['x', 'y', 'z'];

	const convertWidthToFWHM = (shape, width) => {
	  const widthData = ensureXYNumber(width);

	  for (let key of axis2D) {
	    widthData[key] = shape.widthToFWHM(widthData[key]);
	  }

	  return widthData;
	};

	class Spectrum2DGenerator {
	  constructor() {
	    let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
	    let {
	      from = 0,
	      to = 100,
	      nbPoints = 1001,
	      peakWidthFct = () => 5,
	      shape = {
	        kind: 'gaussian'
	      }
	    } = options;
	    from = ensureXYNumber(from);
	    to = ensureXYNumber(to);
	    nbPoints = ensureXYNumber(nbPoints);

	    for (const axis of axis2D) {
	      assertNumber(from[axis], `from-${axis}`);
	      assertNumber(to[axis], `to-${axis}`);
	      assertInteger(nbPoints[axis], `nbPoints-${axis}`);
	    }

	    this.from = from;
	    this.to = to;
	    this.nbPoints = nbPoints;
	    this.interval = calculeIntervals(from, to, nbPoints);
	    this.peakWidthFct = peakWidthFct;
	    this.maxPeakHeight = Number.MIN_SAFE_INTEGER;
	    let shapeGenerator = getShape2D(shape);
	    this.shape = shapeGenerator;
	    this.data = {
	      x: new Float64Array(nbPoints.x),
	      y: new Float64Array(nbPoints.y),
	      z: createMatrix(this.nbPoints)
	    };

	    for (const axis of axis2D) {
	      if (this.to[axis] <= this.from[axis]) {
	        throw new RangeError('to option must be larger than from');
	      }
	    }

	    if (typeof this.peakWidthFct !== 'function') {
	      throw new TypeError('peakWidthFct option must be a function');
	    }

	    this.reset();
	  }

	  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 {
	      let nbPeaks = peaks.x.length;

	      for (const c of peakCoordinates) {
	        if (peaks[c] && Array.isArray(peaks[c])) {
	          if (nbPeaks !== peaks[c].length) {
	            throw new Error('x, y, z should have the same length');
	          }
	        }
	      }

	      for (let i = 0; i < peaks.x.length; i++) {
	        this.addPeak([peaks.x[i], peaks.y[i], peaks.z[i]], options);
	      }
	    }

	    return this;
	  }

	  addPeak(peak) {
	    let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};

	    if (Array.isArray(peak) && peak.length < 3) {
	      throw new Error('peak must be an array with three (or four) values or an object with {x,y,z,width?}');
	    }

	    if (!Array.isArray(peak) && peakCoordinates.some(e => peak[e] === undefined)) {
	      throw new Error('peak must be an array with three (or four) values or an object with {x,y,z,width?}');
	    }

	    let xPosition;
	    let yPosition;
	    let intensity;
	    let peakFWHM;
	    let peakWidth;
	    let peakShapeOptions;

	    if (Array.isArray(peak)) {
	      [xPosition, yPosition, intensity, peakFWHM, peakShapeOptions] = peak;
	    } else {
	      xPosition = peak.x;
	      yPosition = peak.y;
	      intensity = peak.z;
	      peakFWHM = peak.fwhm;
	      peakWidth = peak.width;
	      peakShapeOptions = peak.shape;
	    }

	    const position = {
	      x: xPosition,
	      y: yPosition
	    };
	    if (intensity > this.maxPeakHeight) this.maxPeakHeight = intensity;
	    let {
	      shape: shapeOptions,
	      width
	    } = options;

	    if (peakShapeOptions) {
	      shapeOptions = shapeOptions ? { ...shapeOptions,
	        ...peakShapeOptions
	      } : peakShapeOptions;
	    }

	    if (shapeOptions) {
	      this.shape = getShape2D(shapeOptions);
	    }

	    let {
	      fwhm = peakFWHM !== undefined ? peakFWHM : peakWidth ? convertWidthToFWHM(this.shape, peakWidth) : width ? convertWidthToFWHM(this.shape, width) : this.peakWidthFct(xPosition, yPosition)
	    } = options;
	    fwhm = ensureXYNumber(fwhm);
	    let factor = options.factor === undefined ? this.shape.getFactor() : options.factor;
	    factor = ensureXYNumber(factor);
	    const firstPoint = {
	      x: 0,
	      y: 0
	    };
	    const lastPoint = {
	      x: 0,
	      y: 0
	    };

	    for (const axis of axis2D) {
	      const first = position[axis] - fwhm[axis] / 2 * factor[axis];
	      const last = position[axis] + fwhm[axis] / 2 * factor[axis];
	      firstPoint[axis] = Math.max(0, Math.floor((first - this.from[axis]) / this.interval[axis]));
	      lastPoint[axis] = Math.min(this.nbPoints[axis], Math.ceil((last - this.from[axis]) / this.interval[axis]));
	    }

	    this.shape.fwhm = fwhm;

	    for (let xIndex = firstPoint.x; xIndex < lastPoint.x; xIndex++) {
	      for (let yIndex = firstPoint.y; yIndex < lastPoint.y; yIndex++) {
	        const value = intensity * this.shape.fct(this.data.x[xIndex] - position.x, this.data.y[yIndex] - position.y);

	        if (value > 1e-6) {
	          this.data.z[yIndex][xIndex] += value;
	        }
	      }
	    }

	    return this;
	  }

	  getSpectrum() {
	    let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};

	    if (typeof options === 'boolean') {
	      options = {
	        copy: options
	      };
	    }

	    const {
	      copy = true
	    } = options;
	    let minMaxZ = matrixMinMaxZ(this.data.z);
	    return {
	      minX: this.from.x,
	      maxX: this.to.x,
	      maxY: this.to.y,
	      minY: this.from.y,
	      minZ: minMaxZ.min,
	      maxZ: minMaxZ.max,
	      z: copy ? this.data.z.slice() : this.data.z
	    };
	  }

	  reset() {
	    const spectrum = this.data;

	    for (const axis of axis2D) {
	      for (let i = 0; i < this.nbPoints[axis]; i++) {
	        spectrum[axis][i] = this.from[axis] + i * this.interval[axis];
	      }
	    }

	    for (let row of spectrum.z) {
	      for (let j = 0; j < row.length; j++) {
	        row[j] = 0;
	      }
	    }

	    return this;
	  }

	}
	function generateSpectrum2D(peaks) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const {
	    generator: generatorOptions,
	    peaks: addPeaksOptions
	  } = options;
	  const generator = new Spectrum2DGenerator(generatorOptions);
	  generator.addPeaks(peaks, addPeaksOptions);
	  return generator.getSpectrum();
	}

	function ensureXYNumber(input) {
	  return typeof input !== 'object' ? {
	    x: input,
	    y: input
	  } : { ...input
	  };
	}

	function calculeIntervals(from, to, nbPoints) {
	  return {
	    x: (to.x - from.x) / (nbPoints.x - 1),
	    y: (to.y - from.y) / (nbPoints.y - 1)
	  };
	}

	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 createMatrix(nbPoints) {
	  const zMatrix = new Array(nbPoints.y);

	  for (let i = 0; i < nbPoints.y; i++) {
	    zMatrix[i] = new Float64Array(nbPoints.x);
	  }

	  return zMatrix;
	}

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

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

	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 joinX = function joinX() {
	  let threshold = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : Number.EPSILON;
	  // when we join we will use the center of mass
	  if (this.array.length === 0) return [];
	  this.sortX();
	  let current = this.array[0];
	  let result = [current];

	  for (let i = 1; i < this.array.length; i++) {
	    const item = this.array[i];

	    if (item.x - current.x <= threshold) {
	      // weighted sum
	      current.x = item.y / (current.y + item.y) * (item.x - current.x) + current.x;
	      current.y += item.y;
	    } else {
	      current = {
	        x: item.x,
	        y: item.y
	      };
	      if (item.composition) current.composition = item.composition;
	      result.push(current);
	    }
	  }

	  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) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  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) {
	        const composition = calculateComposition(entryA, entryB);

	        if (composition) {
	          result.push({
	            x: entryA.x + entryB.x,
	            y,
	            composition
	          });
	        } else {
	          result.push({
	            x: entryA.x + entryB.x,
	            y
	          });
	        }
	      }

	      if (result.length > maxLines * 2) {
	        result.joinX(deltaX);
	        result.topY(maxLines);
	      }
	    }
	  }

	  result.joinX(deltaX);
	  result.topY(maxLines);
	  this.move(result);
	  return this;
	};

	function calculateComposition(entryA, entryB) {
	  if (!entryA.composition || !entryB.composition) return;
	  let toReturn = {};
	  const keys = [...new Set(Object.keys(entryA.composition).concat(Object.keys(entryB.composition)))];

	  for (let key of keys) {
	    toReturn[key] = (entryA.composition[key] || 0) + (entryB.composition[key] || 0);
	  }

	  return toReturn;
	}

	var square = function square() {
	  let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
	  return this.multiply(this, options);
	};

	var power = function power(p) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  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(point) {
	  this.array.push(point);
	  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.joinX = joinX;
	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 {
	  stableIsotopesObject
	} = src$j;
	const {
	  subscript,
	  superscript
	} = src$h;

	function getDerivedCompositionInfo$1(composition) {
	  const shortComposition = {};
	  let label = '';
	  let shortLabel = '';

	  for (let key in composition) {
	    let isotopeLabel = '';

	    for (let i = 0; i < key.length; i++) {
	      if (superscript[key[i]]) {
	        isotopeLabel += superscript[key[i]];
	      } else {
	        isotopeLabel += key[i];
	      }
	    }

	    if (composition[key] > 1) {
	      const number = String(composition[key]);

	      for (let i = 0; i < number.length; i++) {
	        isotopeLabel += subscript[number[i]];
	      }
	    }

	    label += isotopeLabel;
	    if (stableIsotopesObject[key].mostAbundant) continue;
	    shortLabel += isotopeLabel;
	    shortComposition[key] = composition[key];
	  }

	  return {
	    label,
	    shortComposition,
	    shortLabel
	  };
	}

	var getDerivedCompositionInfo_1 = getDerivedCompositionInfo$1;

	const ELECTRON_MASS = src$j.ELECTRON_MASS;
	const MF$1 = src$h.MF;
	const getMsInfo = getMsInfo$4;
	const preprocessIonizations = preprocessIonizations$5;
	const SpectrumGenerator = require$$4.SpectrumGenerator;
	const Distribution = Distribution_1;
	const getDerivedCompositionInfo = getDerivedCompositionInfo_1;
	const MINIMAL_FWHM = 1e-8;
	/**
	 * 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) {
	    let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};

	    if (Array.isArray(value)) {
	      this.parts = JSON.parse(JSON.stringify(value));

	      for (let part of this.parts) {
	        part.confidence = 0;
	        part.isotopesInfo = new MF$1(`${part.mf}(${part.ionization.mf})`).getIsotopesInfo();
	      }
	    } else {
	      let mf = new MF$1(value, {
	        ensureCase: options.ensureCase
	      });
	      let mfInfo = mf.getInfo();
	      let ionizations = preprocessIonizations(options.ionizations);
	      let parts = mfInfo.parts || [mfInfo];
	      this.parts = [];

	      for (let partOriginal of parts) {
	        // we calculate information for each part
	        for (const ionization of ionizations) {
	          let part = JSON.parse(JSON.stringify(partOriginal));
	          part.em = part.monoisotopicMass; // TODO: To remove !!! we change the name !?

	          part.isotopesInfo = new MF$1(`${part.mf}(${ionization.mf})`).getIsotopesInfo();
	          part.confidence = 0;
	          let msInfo = getMsInfo(part, {
	            ionization
	          });
	          part.ionization = msInfo.ionization;
	          part.ms = msInfo.ms;
	          this.parts.push(part);
	        }
	      }
	    }

	    this.cachedDistribution = undefined;
	    this.fwhm = options.fwhm === undefined ? 0.01 : options.fwhm; // if fwhm is under 1e-8 there are some artifacts in the spectra

	    if (this.fwhm < MINIMAL_FWHM) this.fwhm = MINIMAL_FWHM;
	    this.minY = options.minY === undefined ? MINIMAL_FWHM : options.minY;
	    this.maxLines = options.maxLines || 5000;
	    this.allowNeutral = options.allowNeutral === undefined ? true : options.allowNeutral;
	  }

	  getParts() {
	    return this.parts;
	  }
	  /**
	   * @return {Distribution} returns the total distribution (for all parts)
	   */


	  getDistribution() {
	    if (this.cachedDistribution) return this.cachedDistribution;
	    let options = {
	      maxLines: this.maxLines,
	      minY: this.minY,
	      deltaX: this.fwhm
	    };
	    let finalDistribution = new Distribution();
	    this.confidence = 0; // TODO need to cache each part without ionization
	    // in case of many ionization we don't need to recalculate everything !

	    for (let part of this.parts) {
	      let totalDistribution = new Distribution([{
	        x: 0,
	        y: 1,
	        composition: this.fwhm === MINIMAL_FWHM ? {} : undefined
	      }]);
	      let charge = part.ms.charge;
	      let absoluteCharge = Math.abs(charge);

	      if (charge || this.allowNeutral) {
	        for (let isotope of part.isotopesInfo.isotopes) {
	          if (isotope.number < 0) return {
	            array: []
	          };

	          if (isotope.number > 0) {
	            const newDistribution = JSON.parse(JSON.stringify(isotope.distribution));

	            if (this.fwhm === MINIMAL_FWHM) {
	              // add composition
	              for (const entry of newDistribution) {
	                entry.composition = {
	                  [Math.round(entry.x) + isotope.atom]: 1
	                };
	              }
	            }

	            let distribution = new Distribution(newDistribution);
	            distribution.power(isotope.number, options);
	            totalDistribution.multiply(distribution, options);
	          }
	        }

	        this.confidence += totalDistribution.array.reduce((sum, value) => sum + value.y, 0); // we finally deal with the charge

	        if (charge) {
	          totalDistribution.array.forEach(e => {
	            e.x = (e.x - ELECTRON_MASS * 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.joinX(this.fwhm);

	    for (let entry of finalDistribution.array) {
	      if (!entry.composition) continue;
	      Object.assign(entry, getDerivedCompositionInfo(entry.composition));
	    }

	    this.confidence /= this.parts.length;
	    this.cachedDistribution = finalDistribution;
	    return finalDistribution;
	  }

	  getCSV() {
	    return this.getText({
	      delimiter: ', '
	    });
	  }

	  getTSV() {
	    return this.getText({
	      delimiter: '\t'
	    });
	  }

	  getTable() {
	    let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
	    const {
	      maxValue,
	      xLabel = 'x',
	      yLabel = 'y'
	    } = options;
	    let points = this.getDistribution().array;
	    let factor = 1;

	    if (maxValue) {
	      let maxY = this.getMaxY(points);
	      factor = maxValue / maxY;
	    }

	    return points.map(point => {
	      let newPoint = {};
	      newPoint[xLabel] = point.x;
	      newPoint[yLabel] = point.y * factor;
	      return newPoint;
	    });
	  }

	  getText() {
	    let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
	    const {
	      delimiter = '\t',
	      numberDecimals = 3
	    } = options;
	    let points = this.getDistribution().array;
	    let csv = [];

	    for (let point of points) {
	      csv.push(`${point.x.toFixed(5)}${delimiter}${(point.y * 100).toFixed(numberDecimals)}`);
	    }

	    return csv.join('\n');
	  }

	  getMaxY(points) {
	    let maxY = points[0].y;

	    for (let point of points) {
	      if (point.y > maxY) maxY = point.y;
	    }

	    return maxY;
	  }

	  getSumY(points) {
	    let sumY = 0;

	    for (let point of points) {
	      sumY += point.y;
	    }

	    return sumY;
	  }
	  /**
	   * Returns the isotopic distribution as an array of peaks
	   * @param {object} [options={}]
	   * @param {number} [options.maxValue=100]
	   * @param {number} [options.sumValue] // if sumValue is defined, maxValue is ignored
	   * @return {Array<any>} an object containing at least the 2 properties: x:[] and y:[]
	   */


	  getPeaks() {
	    let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
	    const {
	      maxValue = 100,
	      sumValue
	    } = options;
	    let peaks = this.getDistribution().array;
	    if (peaks.length === 0) return [];
	    let factor = 1;

	    if (sumValue) {
	      let sumY = this.getSumY(peaks);
	      factor = sumY / sumValue;
	    } else if (maxValue) {
	      let maxY = this.getMaxY(peaks);
	      factor = maxY / maxValue;
	    }

	    if (factor !== 1) {
	      // we need to copy the array because we prefer no side effects
	      peaks = JSON.parse(JSON.stringify(peaks));

	      for (const peak of peaks) {
	        peak.y = peak.y / factor;
	      }
	    }

	    return peaks;
	  }
	  /**
	   * Returns the isotopic distirubtion
	   * @param {object} [options={}]
	   * @param {number} [options.maxValue=100]
	   * @param {number} [options.sumValue] // if sumValue is defined, maxValue is ignored
	   * @return {XY} an object containing at least the 2 properties: x:[] and y:[]
	   */


	  getXY() {
	    let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
	    let peaks = this.getPeaks(options);

	    if (peaks.length === 0) {
	      return {
	        x: [],
	        y: []
	      };
	    }

	    const result = {
	      x: peaks.map(a => a.x),
	      y: peaks.map(a => a.y)
	    };

	    for (let key of Object.keys(peaks[0]).filter(k => k !== 'x' && k !== 'y')) {
	      result[key] = peaks.map(a => a[key]);
	    }

	    return result;
	  }
	  /**
	   * Returns the isotopic distribution as the sum of gaussian
	   * @param {object} [options={}]
	   * @param {number} [options.gaussianWidth=10]
	   * @param {number} [options.threshold=0.00001] // minimal height to return point
	   * @param {number} [options.maxLength=1e6] // minimal height to return point
	   * @param {number} [options.maxValue] // rescale Y to reach maxValue
	   * @param {function} [options.peakWidthFct=(mz)=>(this.fwhm)]
	   * @return {XY} isotopic distribution as an object containing 2 properties: x:[] and y:[]
	   */


	  getGaussian() {
	    let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
	    const {
	      peakWidthFct = () => this.fwhm,
	      threshold = 0.00001,
	      gaussianWidth = 10,
	      maxValue,
	      maxLength = 1e6
	    } = options;
	    let points = this.getTable({
	      maxValue
	    });
	    if (points.length === 0) return {
	      x: [],
	      y: []
	    };
	    const from = Math.floor(options.from || points[0].x - 2);
	    const to = Math.ceil(options.to || points[points.length - 1].x + 2);
	    const nbPoints = Math.round((to - from) * gaussianWidth / this.fwhm + 1);

	    if (nbPoints > maxLength) {
	      throw Error(`Number of points is over the maxLength: ${nbPoints}>${maxLength}`);
	    }

	    let gaussianOptions = {
	      from,
	      to,
	      nbPoints,
	      peakWidthFct
	    };
	    let spectrumGenerator = new SpectrumGenerator(gaussianOptions);

	    for (let point of points) {
	      spectrumGenerator.addPeak([point.x, point.y]);
	    }

	    let spectrum = spectrumGenerator.getSpectrum({
	      threshold
	    });
	    return spectrum;
	  }

	}

	var IsotopicDistribution_1 = IsotopicDistribution$2;

	const IsotopicDistribution$1 = IsotopicDistribution_1;
	var src$5 = IsotopicDistribution$1;

	var mlStat = {};

	var array$1 = {};

	(function (exports) {

	  function compareNumbers(a, b) {
	    return a - b;
	  }
	  /**
	   * Computes the sum of the given values
	   * @param {Array} values
	   * @returns {number}
	   */


	  exports.sum = function sum(values) {
	    var sum = 0;

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

	    return sum;
	  };
	  /**
	   * Computes the maximum of the given values
	   * @param {Array} values
	   * @returns {number}
	   */


	  exports.max = function max(values) {
	    var max = values[0];
	    var l = values.length;

	    for (var i = 1; i < l; i++) {
	      if (values[i] > max) max = values[i];
	    }

	    return max;
	  };
	  /**
	   * Computes the minimum of the given values
	   * @param {Array} values
	   * @returns {number}
	   */


	  exports.min = function min(values) {
	    var min = values[0];
	    var l = values.length;

	    for (var i = 1; i < l; i++) {
	      if (values[i] < min) min = values[i];
	    }

	    return min;
	  };
	  /**
	   * Computes the min and max of the given values
	   * @param {Array} values
	   * @returns {{min: number, max: number}}
	   */


	  exports.minMax = function minMax(values) {
	    var min = values[0];
	    var max = values[0];
	    var l = values.length;

	    for (var i = 1; i < l; i++) {
	      if (values[i] < min) min = values[i];
	      if (values[i] > max) max = values[i];
	    }

	    return {
	      min: min,
	      max: max
	    };
	  };
	  /**
	   * Computes the arithmetic mean of the given values
	   * @param {Array} values
	   * @returns {number}
	   */


	  exports.arithmeticMean = function arithmeticMean(values) {
	    var sum = 0;
	    var l = values.length;

	    for (var i = 0; i < l; i++) {
	      sum += values[i];
	    }

	    return sum / l;
	  };
	  /**
	   * {@link arithmeticMean}
	   */


	  exports.mean = exports.arithmeticMean;
	  /**
	   * Computes the geometric mean of the given values
	   * @param {Array} values
	   * @returns {number}
	   */

	  exports.geometricMean = function geometricMean(values) {
	    var mul = 1;
	    var l = values.length;

	    for (var i = 0; i < l; i++) {
	      mul *= values[i];
	    }

	    return Math.pow(mul, 1 / l);
	  };
	  /**
	   * Computes the mean of the log of the given values
	   * If the return value is exponentiated, it gives the same result as the
	   * geometric mean.
	   * @param {Array} values
	   * @returns {number}
	   */


	  exports.logMean = function logMean(values) {
	    var lnsum = 0;
	    var l = values.length;

	    for (var i = 0; i < l; i++) {
	      lnsum += Math.log(values[i]);
	    }

	    return lnsum / l;
	  };
	  /**
	   * Computes the weighted grand mean for a list of means and sample sizes
	   * @param {Array} means - Mean values for each set of samples
	   * @param {Array} samples - Number of original values for each set of samples
	   * @returns {number}
	   */


	  exports.grandMean = function grandMean(means, samples) {
	    var sum = 0;
	    var n = 0;
	    var l = means.length;

	    for (var i = 0; i < l; i++) {
	      sum += samples[i] * means[i];
	      n += samples[i];
	    }

	    return sum / n;
	  };
	  /**
	   * Computes the truncated mean of the given values using a given percentage
	   * @param {Array} values
	   * @param {number} percent - The percentage of values to keep (range: [0,1])
	   * @param {boolean} [alreadySorted=false]
	   * @returns {number}
	   */


	  exports.truncatedMean = function truncatedMean(values, percent, alreadySorted) {
	    if (alreadySorted === undefined) alreadySorted = false;

	    if (!alreadySorted) {
	      values = [].concat(values).sort(compareNumbers);
	    }

	    var l = values.length;
	    var k = Math.floor(l * percent);
	    var sum = 0;

	    for (var i = k; i < l - k; i++) {
	      sum += values[i];
	    }

	    return sum / (l - 2 * k);
	  };
	  /**
	   * Computes the harmonic mean of the given values
	   * @param {Array} values
	   * @returns {number}
	   */


	  exports.harmonicMean = function harmonicMean(values) {
	    var sum = 0;
	    var l = values.length;

	    for (var i = 0; i < l; i++) {
	      if (values[i] === 0) {
	        throw new RangeError('value at index ' + i + 'is zero');
	      }

	      sum += 1 / values[i];
	    }

	    return l / sum;
	  };
	  /**
	   * Computes the contraharmonic mean of the given values
	   * @param {Array} values
	   * @returns {number}
	   */


	  exports.contraHarmonicMean = function contraHarmonicMean(values) {
	    var r1 = 0;
	    var r2 = 0;
	    var l = values.length;

	    for (var i = 0; i < l; i++) {
	      r1 += values[i] * values[i];
	      r2 += values[i];
	    }

	    if (r2 < 0) {
	      throw new RangeError('sum of values is negative');
	    }

	    return r1 / r2;
	  };
	  /**
	   * Computes the median of the given values
	   * @param {Array} values
	   * @param {boolean} [alreadySorted=false]
	   * @returns {number}
	   */


	  exports.median = function median(values, alreadySorted) {
	    if (alreadySorted === undefined) alreadySorted = false;

	    if (!alreadySorted) {
	      values = [].concat(values).sort(compareNumbers);
	    }

	    var l = values.length;
	    var half = Math.floor(l / 2);

	    if (l % 2 === 0) {
	      return (values[half - 1] + values[half]) * 0.5;
	    } else {
	      return values[half];
	    }
	  };
	  /**
	   * Computes the variance of the given values
	   * @param {Array} values
	   * @param {boolean} [unbiased=true] - if true, divide by (n-1); if false, divide by n.
	   * @returns {number}
	   */


	  exports.variance = function variance(values, unbiased) {
	    if (unbiased === undefined) unbiased = true;
	    var theMean = exports.mean(values);
	    var theVariance = 0;
	    var l = values.length;

	    for (var i = 0; i < l; i++) {
	      var x = values[i] - theMean;
	      theVariance += x * x;
	    }

	    if (unbiased) {
	      return theVariance / (l - 1);
	    } else {
	      return theVariance / l;
	    }
	  };
	  /**
	   * Computes the standard deviation of the given values
	   * @param {Array} values
	   * @param {boolean} [unbiased=true] - if true, divide by (n-1); if false, divide by n.
	   * @returns {number}
	   */


	  exports.standardDeviation = function standardDeviation(values, unbiased) {
	    return Math.sqrt(exports.variance(values, unbiased));
	  };

	  exports.standardError = function standardError(values) {
	    return exports.standardDeviation(values) / Math.sqrt(values.length);
	  };
	  /**
	   * IEEE Transactions on biomedical engineering, vol. 52, no. 1, january 2005, p. 76-
	   * Calculate the standard deviation via the Median of the absolute deviation
	   *  The formula for the standard deviation only holds for Gaussian random variables.
	   * @returns {{mean: number, stdev: number}}
	   */


	  exports.robustMeanAndStdev = function robustMeanAndStdev(y) {
	    var mean = 0,
	        stdev = 0;
	    var length = y.length,
	        i = 0;

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

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

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

	    averageDeviations.sort(compareNumbers);

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

	    return {
	      mean: mean,
	      stdev: stdev
	    };
	  };

	  exports.quartiles = function quartiles(values, alreadySorted) {
	    if (typeof alreadySorted === 'undefined') alreadySorted = false;

	    if (!alreadySorted) {
	      values = [].concat(values).sort(compareNumbers);
	    }

	    var quart = values.length / 4;
	    var q1 = values[Math.ceil(quart) - 1];
	    var q2 = exports.median(values, true);
	    var q3 = values[Math.ceil(quart * 3) - 1];
	    return {
	      q1: q1,
	      q2: q2,
	      q3: q3
	    };
	  };

	  exports.pooledStandardDeviation = function pooledStandardDeviation(samples, unbiased) {
	    return Math.sqrt(exports.pooledVariance(samples, unbiased));
	  };

	  exports.pooledVariance = function pooledVariance(samples, unbiased) {
	    if (typeof unbiased === 'undefined') unbiased = true;
	    var sum = 0;
	    var length = 0,
	        l = samples.length;

	    for (var i = 0; i < l; i++) {
	      var values = samples[i];
	      var vari = exports.variance(values);
	      sum += (values.length - 1) * vari;
	      if (unbiased) length += values.length - 1;else length += values.length;
	    }

	    return sum / length;
	  };

	  exports.mode = function mode(values) {
	    var l = values.length,
	        itemCount = new Array(l),
	        i;

	    for (i = 0; i < l; i++) {
	      itemCount[i] = 0;
	    }

	    var itemArray = new Array(l);
	    var count = 0;

	    for (i = 0; i < l; i++) {
	      var index = itemArray.indexOf(values[i]);
	      if (index >= 0) itemCount[index]++;else {
	        itemArray[count] = values[i];
	        itemCount[count] = 1;
	        count++;
	      }
	    }

	    var maxValue = 0,
	        maxIndex = 0;

	    for (i = 0; i < count; i++) {
	      if (itemCount[i] > maxValue) {
	        maxValue = itemCount[i];
	        maxIndex = i;
	      }
	    }

	    return itemArray[maxIndex];
	  };

	  exports.covariance = function covariance(vector1, vector2, unbiased) {
	    if (typeof unbiased === 'undefined') unbiased = true;
	    var mean1 = exports.mean(vector1);
	    var mean2 = exports.mean(vector2);
	    if (vector1.length !== vector2.length) throw 'Vectors do not have the same dimensions';
	    var cov = 0,
	        l = vector1.length;

	    for (var i = 0; i < l; i++) {
	      var x = vector1[i] - mean1;
	      var y = vector2[i] - mean2;
	      cov += x * y;
	    }

	    if (unbiased) return cov / (l - 1);else return cov / l;
	  };

	  exports.skewness = function skewness(values, unbiased) {
	    if (typeof unbiased === 'undefined') unbiased = true;
	    var theMean = exports.mean(values);
	    var s2 = 0,
	        s3 = 0,
	        l = values.length;

	    for (var i = 0; i < l; i++) {
	      var dev = values[i] - theMean;
	      s2 += dev * dev;
	      s3 += dev * dev * dev;
	    }

	    var m2 = s2 / l;
	    var m3 = s3 / l;
	    var g = m3 / Math.pow(m2, 3 / 2.0);

	    if (unbiased) {
	      var a = Math.sqrt(l * (l - 1));
	      var b = l - 2;
	      return a / b * g;
	    } else {
	      return g;
	    }
	  };

	  exports.kurtosis = function kurtosis(values, unbiased) {
	    if (typeof unbiased === 'undefined') unbiased = true;
	    var theMean = exports.mean(values);
	    var n = values.length,
	        s2 = 0,
	        s4 = 0;

	    for (var i = 0; i < n; i++) {
	      var dev = values[i] - theMean;
	      s2 += dev * dev;
	      s4 += dev * dev * dev * dev;
	    }

	    var m2 = s2 / n;
	    var m4 = s4 / n;

	    if (unbiased) {
	      var v = s2 / (n - 1);
	      var a = n * (n + 1) / ((n - 1) * (n - 2) * (n - 3));
	      var b = s4 / (v * v);
	      var c = (n - 1) * (n - 1) / ((n - 2) * (n - 3));
	      return a * b - 3 * c;
	    } else {
	      return m4 / (m2 * m2) - 3;
	    }
	  };

	  exports.entropy = function entropy(values, eps) {
	    if (typeof eps === 'undefined') eps = 0;
	    var sum = 0,
	        l = values.length;

	    for (var i = 0; i < l; i++) sum += values[i] * Math.log(values[i] + eps);

	    return -sum;
	  };

	  exports.weightedMean = function weightedMean(values, weights) {
	    var sum = 0,
	        l = values.length;

	    for (var i = 0; i < l; i++) sum += values[i] * weights[i];

	    return sum;
	  };

	  exports.weightedStandardDeviation = function weightedStandardDeviation(values, weights) {
	    return Math.sqrt(exports.weightedVariance(values, weights));
	  };

	  exports.weightedVariance = function weightedVariance(values, weights) {
	    var theMean = exports.weightedMean(values, weights);
	    var vari = 0,
	        l = values.length;
	    var a = 0,
	        b = 0;

	    for (var i = 0; i < l; i++) {
	      var z = values[i] - theMean;
	      var w = weights[i];
	      vari += w * (z * z);
	      b += w;
	      a += w * w;
	    }

	    return vari * (b / (b * b - a));
	  };

	  exports.center = function center(values, inPlace) {
	    if (typeof inPlace === 'undefined') inPlace = false;
	    var result = values;
	    if (!inPlace) result = [].concat(values);
	    var theMean = exports.mean(result),
	        l = result.length;

	    for (var i = 0; i < l; i++) result[i] -= theMean;
	  };

	  exports.standardize = function standardize(values, standardDev, inPlace) {
	    if (typeof standardDev === 'undefined') standardDev = exports.standardDeviation(values);
	    if (typeof inPlace === 'undefined') inPlace = false;
	    var l = values.length;
	    var result = inPlace ? values : new Array(l);

	    for (var i = 0; i < l; i++) result[i] = values[i] / standardDev;

	    return result;
	  };

	  exports.cumulativeSum = function cumulativeSum(array) {
	    var l = array.length;
	    var result = new Array(l);
	    result[0] = array[0];

	    for (var i = 1; i < l; i++) result[i] = result[i - 1] + array[i];

	    return result;
	  };
	})(array$1);

	var matrix = {};

	(function (exports) {

	  var arrayStat = array$1;

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

	  exports.max = function max(matrix) {
	    var max = -Infinity;

	    for (var i = 0; i < matrix.length; i++) {
	      for (var j = 0; j < matrix[i].length; j++) {
	        if (matrix[i][j] > max) max = matrix[i][j];
	      }
	    }

	    return max;
	  };

	  exports.min = function min(matrix) {
	    var min = Infinity;

	    for (var i = 0; i < matrix.length; i++) {
	      for (var j = 0; j < matrix[i].length; j++) {
	        if (matrix[i][j] < min) min = matrix[i][j];
	      }
	    }

	    return min;
	  };

	  exports.minMax = function minMax(matrix) {
	    var min = Infinity;
	    var max = -Infinity;

	    for (var i = 0; i < matrix.length; i++) {
	      for (var j = 0; j < matrix[i].length; j++) {
	        if (matrix[i][j] < min) min = matrix[i][j];
	        if (matrix[i][j] > max) max = matrix[i][j];
	      }
	    }

	    return {
	      min: min,
	      max: max
	    };
	  };

	  exports.entropy = function entropy(matrix, eps) {
	    if (typeof eps === 'undefined') {
	      eps = 0;
	    }

	    var sum = 0,
	        l1 = matrix.length,
	        l2 = matrix[0].length;

	    for (var i = 0; i < l1; i++) {
	      for (var j = 0; j < l2; j++) {
	        sum += matrix[i][j] * Math.log(matrix[i][j] + eps);
	      }
	    }

	    return -sum;
	  };

	  exports.mean = function mean(matrix, dimension) {
	    if (typeof dimension === 'undefined') {
	      dimension = 0;
	    }

	    var rows = matrix.length,
	        cols = matrix[0].length,
	        theMean,
	        N,
	        i,
	        j;

	    if (dimension === -1) {
	      theMean = [0];
	      N = rows * cols;

	      for (i = 0; i < rows; i++) {
	        for (j = 0; j < cols; j++) {
	          theMean[0] += matrix[i][j];
	        }
	      }

	      theMean[0] /= N;
	    } else if (dimension === 0) {
	      theMean = new Array(cols);
	      N = rows;

	      for (j = 0; j < cols; j++) {
	        theMean[j] = 0;

	        for (i = 0; i < rows; i++) {
	          theMean[j] += matrix[i][j];
	        }

	        theMean[j] /= N;
	      }
	    } else if (dimension === 1) {
	      theMean = new Array(rows);
	      N = cols;

	      for (j = 0; j < rows; j++) {
	        theMean[j] = 0;

	        for (i = 0; i < cols; i++) {
	          theMean[j] += matrix[j][i];
	        }

	        theMean[j] /= N;
	      }
	    } else {
	      throw new Error('Invalid dimension');
	    }

	    return theMean;
	  };

	  exports.sum = function sum(matrix, dimension) {
	    if (typeof dimension === 'undefined') {
	      dimension = 0;
	    }

	    var rows = matrix.length,
	        cols = matrix[0].length,
	        theSum,
	        i,
	        j;

	    if (dimension === -1) {
	      theSum = [0];

	      for (i = 0; i < rows; i++) {
	        for (j = 0; j < cols; j++) {
	          theSum[0] += matrix[i][j];
	        }
	      }
	    } else if (dimension === 0) {
	      theSum = new Array(cols);

	      for (j = 0; j < cols; j++) {
	        theSum[j] = 0;

	        for (i = 0; i < rows; i++) {
	          theSum[j] += matrix[i][j];
	        }
	      }
	    } else if (dimension === 1) {
	      theSum = new Array(rows);

	      for (j = 0; j < rows; j++) {
	        theSum[j] = 0;

	        for (i = 0; i < cols; i++) {
	          theSum[j] += matrix[j][i];
	        }
	      }
	    } else {
	      throw new Error('Invalid dimension');
	    }

	    return theSum;
	  };

	  exports.product = function product(matrix, dimension) {
	    if (typeof dimension === 'undefined') {
	      dimension = 0;
	    }

	    var rows = matrix.length,
	        cols = matrix[0].length,
	        theProduct,
	        i,
	        j;

	    if (dimension === -1) {
	      theProduct = [1];

	      for (i = 0; i < rows; i++) {
	        for (j = 0; j < cols; j++) {
	          theProduct[0] *= matrix[i][j];
	        }
	      }
	    } else if (dimension === 0) {
	      theProduct = new Array(cols);

	      for (j = 0; j < cols; j++) {
	        theProduct[j] = 1;

	        for (i = 0; i < rows; i++) {
	          theProduct[j] *= matrix[i][j];
	        }
	      }
	    } else if (dimension === 1) {
	      theProduct = new Array(rows);

	      for (j = 0; j < rows; j++) {
	        theProduct[j] = 1;

	        for (i = 0; i < cols; i++) {
	          theProduct[j] *= matrix[j][i];
	        }
	      }
	    } else {
	      throw new Error('Invalid dimension');
	    }

	    return theProduct;
	  };

	  exports.standardDeviation = function standardDeviation(matrix, means, unbiased) {
	    var vari = exports.variance(matrix, means, unbiased),
	        l = vari.length;

	    for (var i = 0; i < l; i++) {
	      vari[i] = Math.sqrt(vari[i]);
	    }

	    return vari;
	  };

	  exports.variance = function variance(matrix, means, unbiased) {
	    if (typeof unbiased === 'undefined') {
	      unbiased = true;
	    }

	    means = means || exports.mean(matrix);
	    var rows = matrix.length;
	    if (rows === 0) return [];
	    var cols = matrix[0].length;
	    var vari = new Array(cols);

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

	      for (var i = 0; i < rows; i++) {
	        x = matrix[i][j] - means[j];
	        sum1 += x;
	        sum2 += x * x;
	      }

	      if (unbiased) {
	        vari[j] = (sum2 - sum1 * sum1 / rows) / (rows - 1);
	      } else {
	        vari[j] = (sum2 - sum1 * sum1 / rows) / rows;
	      }
	    }

	    return vari;
	  };

	  exports.median = function median(matrix) {
	    var rows = matrix.length,
	        cols = matrix[0].length;
	    var medians = new Array(cols);

	    for (var i = 0; i < cols; i++) {
	      var data = new Array(rows);

	      for (var j = 0; j < rows; j++) {
	        data[j] = matrix[j][i];
	      }

	      data.sort(compareNumbers);
	      var N = data.length;

	      if (N % 2 === 0) {
	        medians[i] = (data[N / 2] + data[N / 2 - 1]) * 0.5;
	      } else {
	        medians[i] = data[Math.floor(N / 2)];
	      }
	    }

	    return medians;
	  };

	  exports.mode = function mode(matrix) {
	    var rows = matrix.length,
	        cols = matrix[0].length,
	        modes = new Array(cols),
	        i,
	        j;

	    for (i = 0; i < cols; i++) {
	      var itemCount = new Array(rows);

	      for (var k = 0; k < rows; k++) {
	        itemCount[k] = 0;
	      }

	      var itemArray = new Array(rows);
	      var count = 0;

	      for (j = 0; j < rows; j++) {
	        var index = itemArray.indexOf(matrix[j][i]);

	        if (index >= 0) {
	          itemCount[index]++;
	        } else {
	          itemArray[count] = matrix[j][i];
	          itemCount[count] = 1;
	          count++;
	        }
	      }

	      var maxValue = 0,
	          maxIndex = 0;

	      for (j = 0; j < count; j++) {
	        if (itemCount[j] > maxValue) {
	          maxValue = itemCount[j];
	          maxIndex = j;
	        }
	      }

	      modes[i] = itemArray[maxIndex];
	    }

	    return modes;
	  };

	  exports.skewness = function skewness(matrix, unbiased) {
	    if (typeof unbiased === 'undefined') unbiased = true;
	    var means = exports.mean(matrix);
	    var n = matrix.length,
	        l = means.length;
	    var skew = new Array(l);

	    for (var j = 0; j < l; j++) {
	      var s2 = 0,
	          s3 = 0;

	      for (var i = 0; i < n; i++) {
	        var dev = matrix[i][j] - means[j];
	        s2 += dev * dev;
	        s3 += dev * dev * dev;
	      }

	      var m2 = s2 / n;
	      var m3 = s3 / n;
	      var g = m3 / Math.pow(m2, 3 / 2);

	      if (unbiased) {
	        var a = Math.sqrt(n * (n - 1));
	        var b = n - 2;
	        skew[j] = a / b * g;
	      } else {
	        skew[j] = g;
	      }
	    }

	    return skew;
	  };

	  exports.kurtosis = function kurtosis(matrix, unbiased) {
	    if (typeof unbiased === 'undefined') unbiased = true;
	    var means = exports.mean(matrix);
	    var n = matrix.length,
	        m = matrix[0].length;
	    var kurt = new Array(m);

	    for (var j = 0; j < m; j++) {
	      var s2 = 0,
	          s4 = 0;

	      for (var i = 0; i < n; i++) {
	        var dev = matrix[i][j] - means[j];
	        s2 += dev * dev;
	        s4 += dev * dev * dev * dev;
	      }

	      var m2 = s2 / n;
	      var m4 = s4 / n;

	      if (unbiased) {
	        var v = s2 / (n - 1);
	        var a = n * (n + 1) / ((n - 1) * (n - 2) * (n - 3));
	        var b = s4 / (v * v);
	        var c = (n - 1) * (n - 1) / ((n - 2) * (n - 3));
	        kurt[j] = a * b - 3 * c;
	      } else {
	        kurt[j] = m4 / (m2 * m2) - 3;
	      }
	    }

	    return kurt;
	  };

	  exports.standardError = function standardError(matrix) {
	    var samples = matrix.length;
	    var standardDeviations = exports.standardDeviation(matrix);
	    var l = standardDeviations.length;
	    var standardErrors = new Array(l);
	    var sqrtN = Math.sqrt(samples);

	    for (var i = 0; i < l; i++) {
	      standardErrors[i] = standardDeviations[i] / sqrtN;
	    }

	    return standardErrors;
	  };

	  exports.covariance = function covariance(matrix, dimension) {
	    return exports.scatter(matrix, undefined, dimension);
	  };

	  exports.scatter = function scatter(matrix, divisor, dimension) {
	    if (typeof dimension === 'undefined') {
	      dimension = 0;
	    }

	    if (typeof divisor === 'undefined') {
	      if (dimension === 0) {
	        divisor = matrix.length - 1;
	      } else if (dimension === 1) {
	        divisor = matrix[0].length - 1;
	      }
	    }

	    var means = exports.mean(matrix, dimension);
	    var rows = matrix.length;

	    if (rows === 0) {
	      return [[]];
	    }

	    var cols = matrix[0].length,
	        cov,
	        i,
	        j,
	        s,
	        k;

	    if (dimension === 0) {
	      cov = new Array(cols);

	      for (i = 0; i < cols; i++) {
	        cov[i] = new Array(cols);
	      }

	      for (i = 0; i < cols; i++) {
	        for (j = i; j < cols; j++) {
	          s = 0;

	          for (k = 0; k < rows; k++) {
	            s += (matrix[k][j] - means[j]) * (matrix[k][i] - means[i]);
	          }

	          s /= divisor;
	          cov[i][j] = s;
	          cov[j][i] = s;
	        }
	      }
	    } else if (dimension === 1) {
	      cov = new Array(rows);

	      for (i = 0; i < rows; i++) {
	        cov[i] = new Array(rows);
	      }

	      for (i = 0; i < rows; i++) {
	        for (j = i; j < rows; j++) {
	          s = 0;

	          for (k = 0; k < cols; k++) {
	            s += (matrix[j][k] - means[j]) * (matrix[i][k] - means[i]);
	          }

	          s /= divisor;
	          cov[i][j] = s;
	          cov[j][i] = s;
	        }
	      }
	    } else {
	      throw new Error('Invalid dimension');
	    }

	    return cov;
	  };

	  exports.correlation = function correlation(matrix) {
	    var means = exports.mean(matrix),
	        standardDeviations = exports.standardDeviation(matrix, true, means),
	        scores = exports.zScores(matrix, means, standardDeviations),
	        rows = matrix.length,
	        cols = matrix[0].length,
	        i,
	        j;
	    var cor = new Array(cols);

	    for (i = 0; i < cols; i++) {
	      cor[i] = new Array(cols);
	    }

	    for (i = 0; i < cols; i++) {
	      for (j = i; j < cols; j++) {
	        var c = 0;

	        for (var k = 0, l = scores.length; k < l; k++) {
	          c += scores[k][j] * scores[k][i];
	        }

	        c /= rows - 1;
	        cor[i][j] = c;
	        cor[j][i] = c;
	      }
	    }

	    return cor;
	  };

	  exports.zScores = function zScores(matrix, means, standardDeviations) {
	    means = means || exports.mean(matrix);
	    if (typeof standardDeviations === 'undefined') standardDeviations = exports.standardDeviation(matrix, true, means);
	    return exports.standardize(exports.center(matrix, means, false), standardDeviations, true);
	  };

	  exports.center = function center(matrix, means, inPlace) {
	    means = means || exports.mean(matrix);
	    var result = matrix,
	        l = matrix.length,
	        i,
	        j,
	        jj;

	    if (!inPlace) {
	      result = new Array(l);

	      for (i = 0; i < l; i++) {
	        result[i] = new Array(matrix[i].length);
	      }
	    }

	    for (i = 0; i < l; i++) {
	      var row = result[i];

	      for (j = 0, jj = row.length; j < jj; j++) {
	        row[j] = matrix[i][j] - means[j];
	      }
	    }

	    return result;
	  };

	  exports.standardize = function standardize(matrix, standardDeviations, inPlace) {
	    if (typeof standardDeviations === 'undefined') standardDeviations = exports.standardDeviation(matrix);
	    var result = matrix,
	        l = matrix.length,
	        i,
	        j,
	        jj;

	    if (!inPlace) {
	      result = new Array(l);

	      for (i = 0; i < l; i++) {
	        result[i] = new Array(matrix[i].length);
	      }
	    }

	    for (i = 0; i < l; i++) {
	      var resultRow = result[i];
	      var sourceRow = matrix[i];

	      for (j = 0, jj = resultRow.length; j < jj; j++) {
	        if (standardDeviations[j] !== 0 && !isNaN(standardDeviations[j])) {
	          resultRow[j] = sourceRow[j] / standardDeviations[j];
	        }
	      }
	    }

	    return result;
	  };

	  exports.weightedVariance = function weightedVariance(matrix, weights) {
	    var means = exports.mean(matrix);
	    var rows = matrix.length;
	    if (rows === 0) return [];
	    var cols = matrix[0].length;
	    var vari = new Array(cols);

	    for (var j = 0; j < cols; j++) {
	      var sum = 0;
	      var a = 0,
	          b = 0;

	      for (var i = 0; i < rows; i++) {
	        var z = matrix[i][j] - means[j];
	        var w = weights[i];
	        sum += w * (z * z);
	        b += w;
	        a += w * w;
	      }

	      vari[j] = sum * (b / (b * b - a));
	    }

	    return vari;
	  };

	  exports.weightedMean = function weightedMean(matrix, weights, dimension) {
	    if (typeof dimension === 'undefined') {
	      dimension = 0;
	    }

	    var rows = matrix.length;
	    if (rows === 0) return [];
	    var cols = matrix[0].length,
	        means,
	        i,
	        ii,
	        j,
	        w,
	        row;

	    if (dimension === 0) {
	      means = new Array(cols);

	      for (i = 0; i < cols; i++) {
	        means[i] = 0;
	      }

	      for (i = 0; i < rows; i++) {
	        row = matrix[i];
	        w = weights[i];

	        for (j = 0; j < cols; j++) {
	          means[j] += row[j] * w;
	        }
	      }
	    } else if (dimension === 1) {
	      means = new Array(rows);

	      for (i = 0; i < rows; i++) {
	        means[i] = 0;
	      }

	      for (j = 0; j < rows; j++) {
	        row = matrix[j];
	        w = weights[j];

	        for (i = 0; i < cols; i++) {
	          means[j] += row[i] * w;
	        }
	      }
	    } else {
	      throw new Error('Invalid dimension');
	    }

	    var weightSum = arrayStat.sum(weights);

	    if (weightSum !== 0) {
	      for (i = 0, ii = means.length; i < ii; i++) {
	        means[i] /= weightSum;
	      }
	    }

	    return means;
	  };

	  exports.weightedCovariance = function weightedCovariance(matrix, weights, means, dimension) {
	    dimension = dimension || 0;
	    means = means || exports.weightedMean(matrix, weights, dimension);
	    var s1 = 0,
	        s2 = 0;

	    for (var i = 0, ii = weights.length; i < ii; i++) {
	      s1 += weights[i];
	      s2 += weights[i] * weights[i];
	    }

	    var factor = s1 / (s1 * s1 - s2);
	    return exports.weightedScatter(matrix, weights, means, factor, dimension);
	  };

	  exports.weightedScatter = function weightedScatter(matrix, weights, means, factor, dimension) {
	    dimension = dimension || 0;
	    means = means || exports.weightedMean(matrix, weights, dimension);

	    if (typeof factor === 'undefined') {
	      factor = 1;
	    }

	    var rows = matrix.length;

	    if (rows === 0) {
	      return [[]];
	    }

	    var cols = matrix[0].length,
	        cov,
	        i,
	        j,
	        k,
	        s;

	    if (dimension === 0) {
	      cov = new Array(cols);

	      for (i = 0; i < cols; i++) {
	        cov[i] = new Array(cols);
	      }

	      for (i = 0; i < cols; i++) {
	        for (j = i; j < cols; j++) {
	          s = 0;

	          for (k = 0; k < rows; k++) {
	            s += weights[k] * (matrix[k][j] - means[j]) * (matrix[k][i] - means[i]);
	          }

	          cov[i][j] = s * factor;
	          cov[j][i] = s * factor;
	        }
	      }
	    } else if (dimension === 1) {
	      cov = new Array(rows);

	      for (i = 0; i < rows; i++) {
	        cov[i] = new Array(rows);
	      }

	      for (i = 0; i < rows; i++) {
	        for (j = i; j < rows; j++) {
	          s = 0;

	          for (k = 0; k < cols; k++) {
	            s += weights[k] * (matrix[j][k] - means[j]) * (matrix[i][k] - means[i]);
	          }

	          cov[i][j] = s * factor;
	          cov[j][i] = s * factor;
	        }
	      }
	    } else {
	      throw new Error('Invalid dimension');
	    }

	    return cov;
	  };
	})(matrix);

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

	var COMMON_NO = 0;
	var COMMON_FIRST = 1;
	var COMMON_SECOND = 2;
	var COMMON_BOTH = 3; // should be a binary operation !

	var Stat = mlStat.array;
	/**
	 * Create a comparator class
	 * {object} [options={}]
	 * {string} [options.common=''] should we take only common peaks 'first', 'second', 'both', ''
	 * {number} [options.widthBottom=2] bottom trapezoid width for similarity evaluation
	 * {number} [options.widthTop=1] top trapezoid width for similarity evaluation
	 * {number} [options.from] from region used for similarity calculation
	 * {number} [options.to] to region used for similarity calculation
	 */

	var src$4 = function Comparator() {
	  let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
	  var widthTop, widthBottom, from, to;
	  var array1Extract, array2Extract, widthSlope, array1ExtractInfo, array2ExtractInfo;
	  var common, commonFactor;
	  setOptions(options);
	  var array1 = [];
	  var array2 = [];
	  /*
	     2 formats are allowed:
	     [[x1,x2,...],[y1,y2,...]] or [[x1,y1],[x2,y2], ...]
	    */

	  function setOptions(newOptions) {
	    options = newOptions || {};

	    if (typeof options.common === 'string') {
	      if (options.common.toLowerCase() === 'first') {
	        common = COMMON_FIRST;
	      } else if (options.common.toLowerCase() === 'second') {
	        common = COMMON_SECOND;
	      } else if (options.common.toLowerCase() === 'both') {
	        common = COMMON_BOTH;
	      } else {
	        common = COMMON_NO;
	      }
	    } else {
	      if (options.common === true) {
	        common = COMMON_BOTH;
	      } else {
	        common = COMMON_NO;
	      }
	    }

	    commonFactor = options.commonFactor || commonFactor || 4;

	    if (options.widthBottom === undefined) {
	      options.widthBottom = widthBottom || 2;
	    }

	    if (options.widthTop === undefined) {
	      options.widthTop = widthTop || 1;
	    }

	    setTrapezoid(options.widthBottom, options.widthTop);
	    setFromTo(options.from || from, options.to || to);
	  }

	  function setPeaks1(anArray) {
	    array1 = checkArray(anArray);

	    if (common) {
	      var extracts = commonExtractAndNormalize(array1, array2, widthBottom, from, to, common);
	      array1Extract = extracts.data1;
	      array1ExtractInfo = extracts.info1;
	      array2Extract = extracts.data2;
	      array2ExtractInfo = extracts.info2;
	    } else {
	      var extract = extractAndNormalize(array1, from, to);
	      array1Extract = extract.data;
	      array1ExtractInfo = extract.info;
	    }
	  }

	  function setPeaks2(anArray) {
	    array2 = checkArray(anArray);

	    if (common) {
	      var extracts = commonExtractAndNormalize(array1, array2, widthBottom, from, to, common);
	      array1Extract = extracts.data1;
	      array1ExtractInfo = extracts.info1;
	      array2Extract = extracts.data2;
	      array2ExtractInfo = extracts.info2;
	    } else {
	      var extract = extractAndNormalize(array2, from, to);
	      array2Extract = extract.data;
	      array2ExtractInfo = extract.info;
	    }
	  }

	  function getExtract1() {
	    return array1Extract;
	  }

	  function getExtract2() {
	    return array2Extract;
	  }

	  function getExtractInfo1() {
	    return array1ExtractInfo;
	  }

	  function getExtractInfo2() {
	    return array2ExtractInfo;
	  }

	  function setTrapezoid(newWidthBottom, newWidthTop) {
	    widthTop = newWidthTop;
	    widthBottom = newWidthBottom;
	    widthSlope = (widthBottom - widthTop) / 2;
	    if (widthBottom < widthTop) throw new Error('widthBottom has to be larger than widthTop');
	  }

	  function setFromTo(newFrom, newTo) {
	    if (newFrom === from && newTo === to) return;
	    from = newFrom;
	    to = newTo;

	    if (common) {
	      var extracts = commonExtractAndNormalize(array1, array2, widthBottom, from, to, common);
	      array1Extract = extracts.data1;
	      array1ExtractInfo = extracts.info1;
	      array2Extract = extracts.data2;
	      array2ExtractInfo = extracts.info2;
	    } else {
	      var extract = extractAndNormalize(array1, from, to);
	      array1Extract = extract.data;
	      array1ExtractInfo = extract.info;
	      extract = extractAndNormalize(array2, from, to);
	      array2Extract = extract.data;
	      array2ExtractInfo = extract.info;
	    }
	  }

	  function getOverlap(x1, y1, x2, y2) {
	    if (y1 === 0 || y2 === 0) return 0; // TAKE CARE !!! We multiply the diff by 2 !!!

	    var diff = Math.abs(x1 - x2) * 2;
	    if (diff > widthBottom) return 0;

	    if (diff <= widthTop) {
	      return Math.min(y1, y2);
	    }

	    var maxValue = Math.max(y1, y2) * (widthBottom - diff) / (widthBottom - widthTop);
	    return Math.min(y1, y2, maxValue);
	  } // This is the old trapezoid similarity


	  function getOverlapTrapezoid(x1, y1, x2, y2) {
	    var factor = 2 / (widthTop + widthBottom); // correction for surface=1

	    if (y1 === 0 || y2 === 0) return 0;

	    if (x1 === x2) {
	      // they have the same position
	      return Math.min(y1, y2);
	    }

	    var diff = Math.abs(x1 - x2);
	    if (diff >= widthBottom) return 0;

	    if (y1 === y2) {
	      // do they have the same height ???
	      // we need to find the common length
	      if (diff <= widthTop) {
	        return ((widthTop + widthBottom) / 2 - diff) * y1 * factor;
	      } else if (diff <= widthBottom) {
	        return (widthBottom - diff) * y1 / 2 * (diff - widthTop) / (widthBottom - widthTop) * factor;
	      }

	      return 0;
	    } else {
	      // the height are different and not the same position ...
	      // we need to consider only one segment to find its intersection
	      var small = Math.min(y1, y2);
	      var big = Math.max(y1, y2);
	      var targets = [[[0, 0], [widthSlope, small]], [[widthSlope, small], [widthSlope + widthTop, small]], [[widthTop + widthSlope, small], [widthBottom, 0]]];
	      var segment;

	      if (x1 > x2 && y1 > y2 || x1 < x2 && y1 < y2) {
	        segment = [[diff, 0], [diff + widthSlope, big]];
	      } else {
	        segment = [[diff + widthSlope, big], [diff, 0]];
	      }

	      for (var i = 0; i < 3; i++) {
	        var intersection = getIntersection(targets[i], segment);

	        if (intersection) {
	          switch (i) {
	            case 0:
	              return small - diff * intersection.y / 2 * factor;

	            case 1:
	              // to simplify ...
	              //     console.log("           ",widthSlope,small,big,intersection.x)
	              return (widthSlope * small / (2 * big) * small + (widthTop + widthSlope - intersection.x) * small + widthSlope * small / 2) * factor;

	            case 2:
	              return (widthBottom - diff) * intersection.y / 2 * factor;

	            default:
	              throw new Error(`unexpected intersection value: ${i}`);
	          }
	        }
	      }
	    }

	    return NaN;
	  } // this method calculates the total diff. The sum of positive value will yield to overlap


	  function calculateDiff() {
	    // we need to take 2 pointers
	    // and travel progressively between them ...
	    var newFirst = [[].concat(array1Extract[0]), [].concat(array1Extract[1])];
	    var newSecond = [[].concat(array2Extract[0]), [].concat(array2Extract[1])];
	    var array1Length = array1Extract[0] ? array1Extract[0].length : 0;
	    var array2Length = array2Extract[0] ? array2Extract[0].length : 0;
	    var pos1 = 0;
	    var pos2 = 0;
	    var previous2 = 0;

	    while (pos1 < array1Length) {
	      var diff = newFirst[0][pos1] - array2Extract[0][pos2];

	      if (Math.abs(diff) < widthBottom) {
	        // there is some overlap
	        var overlap;

	        if (options.trapezoid) {
	          overlap = getOverlapTrapezoid(newFirst[0][pos1], newFirst[1][pos1], newSecond[0][pos2], newSecond[1][pos2]);
	        } else {
	          overlap = getOverlap(newFirst[0][pos1], newFirst[1][pos1], newSecond[0][pos2], newSecond[1][pos2]);
	        }

	        newFirst[1][pos1] -= overlap;
	        newSecond[1][pos2] -= overlap;

	        if (pos2 < array2Length - 1) {
	          pos2++;
	        } else {
	          pos1++;
	          pos2 = previous2;
	        }
	      } else {
	        if (diff > 0 && pos2 < array2Length - 1) {
	          pos2++;
	          previous2 = pos2;
	        } else {
	          pos1++;
	          pos2 = previous2;
	        }
	      }
	    }

	    return newSecond;
	  }
	  /*
	        This code requires the use of an array like  [[x1,y1],[x2,y2], ...]
	        If it is not the right format, we will just convert it
	        Otherwise we return the correct format
	     */


	  function checkArray(points) {
	    // if it is already a 2D array of points, we just return them
	    if (Array.isArray(points) && Array.isArray(points[0]) && points.length === 2) return points;
	    var x = new Array(points.length);
	    var y = new Array(points.length);

	    for (var i = 0; i < points.length; i++) {
	      x[i] = points[i][0];
	      y[i] = points[i][1];
	    }

	    return [x, y];
	  }

	  function getSimilarity(newPeaks1, newPeaks2) {
	    if (newPeaks1) setPeaks1(newPeaks1);
	    if (newPeaks2) setPeaks2(newPeaks2);
	    var result = {};
	    result.diff = calculateDiff();
	    result.extract1 = getExtract1();
	    result.extract2 = getExtract2();
	    result.extractInfo1 = getExtractInfo1();
	    result.extractInfo2 = getExtractInfo2();
	    result.similarity = calculateOverlapFromDiff(result.diff);
	    result.widthBottom = widthBottom;
	    result.widthTop = widthTop;
	    return result;
	  }
	  /*
	    This works mainly when you have a array1 that is fixed
	    newPeaks2 have to be normalized ! (sum to 1)
	  */


	  function fastSimilarity(newPeaks2, from, to) {
	    array1Extract = extract(array1, from, to);
	    array2Extract = newPeaks2;
	    if (common & COMMON_SECOND) array1Extract = getCommonArray(array1Extract, array2Extract, widthBottom);
	    normalize(array1Extract);
	    var diff = calculateDiff();
	    return calculateOverlapFromDiff(diff);
	  }

	  this.setPeaks1 = setPeaks1;
	  this.setPeaks2 = setPeaks2;
	  this.getExtract1 = getExtract1;
	  this.getExtract2 = getExtract2;
	  this.getExtractInfo1 = getExtractInfo1;
	  this.getExtractInfo2 = getExtractInfo2;
	  this.setFromTo = setFromTo;
	  this.setOptions = setOptions;
	  this.setTrapezoid = setTrapezoid;
	  this.getSimilarity = getSimilarity;
	  this.getCommonArray = getCommonArray;
	  this.fastSimilarity = fastSimilarity;
	}; // returns an new array based on array1 where there is a peak of array2 at a distance under width/2


	function getCommonArray(array1, array2, width) {
	  var newArray = [[], []];
	  var pos2 = 0;
	  width /= 2;
	  var j = 0;
	  var array1Length = array1[0] ? array1[0].length : 0;
	  var array2Length = array2[0] ? array2[0].length : 0;

	  for (var i = 0; i < array1Length; i++) {
	    while (pos2 < array2Length && array1[0][i] > array2[0][pos2] + width) {
	      pos2++;
	    }

	    if (pos2 < array2Length && array1[0][i] > array2[0][pos2] - width) {
	      newArray[0][j] = array1[0][i];
	      newArray[1][j] = array1[1][i];
	      j++;
	    }
	  }

	  return newArray;
	} // Adapted from: http://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect/1968345#1968345


	function getIntersection(segment1, segment2) {
	  var p0X = segment1[0][0];
	  var p0Y = segment1[0][1];
	  var p1X = segment1[1][0];
	  var p1Y = segment1[1][1];
	  var p2X = segment2[0][0];
	  var p2Y = segment2[0][1];
	  var p3X = segment2[1][0];
	  var p3Y = segment2[1][1];
	  var s1X, s1Y, s2X, s2Y;
	  s1X = p1X - p0X;
	  s1Y = p1Y - p0Y;
	  s2X = p3X - p2X;
	  s2Y = p3Y - p2Y;
	  var s, t;
	  s = (-s1Y * (p0X - p2X) + s1X * (p0Y - p2Y)) / (-s2X * s1Y + s1X * s2Y);
	  t = (s2X * (p0Y - p2Y) - s2Y * (p0X - p2X)) / (-s2X * s1Y + s1X * s2Y);

	  if (s >= 0 && s <= 1 && t >= 0 && t <= 1) {
	    return {
	      x: p0X + t * s1X,
	      y: p0Y + t * s1Y
	    };
	  }

	  return null; // No collision
	}

	function normalize(array) {
	  var min = Stat.min(array[1]);
	  var max = Stat.max(array[1]);
	  var sum = Stat.sum(array[1]);
	  var length = array[1] ? array[1].length : 0;

	  if (sum !== 0) {
	    for (var i = 0; i < length; i++) {
	      array[1][i] /= sum;
	    }
	  }

	  return {
	    sum: sum,
	    min: min,
	    max: max
	  };
	} // this method will systematically take care of both array


	function commonExtractAndNormalize(array1, array2, width, from, to, common) {
	  if (!Array.isArray(array1) || !Array.isArray(array2)) {
	    return {
	      info: undefined,
	      data: undefined
	    };
	  }

	  var extract1 = extract(array1, from, to);
	  var extract2 = extract(array2, from, to);
	  var common1, common2, info1, info2;

	  if (common & COMMON_SECOND) {
	    common1 = getCommonArray(extract1, extract2, width);
	    info1 = normalize(common1);
	  } else {
	    common1 = extract1;
	    info1 = normalize(common1);
	  }

	  if (common & COMMON_FIRST) {
	    common2 = getCommonArray(extract2, extract1, width);
	    info2 = normalize(common2);
	  } else {
	    common2 = extract2;
	    info2 = normalize(common2);
	  }

	  return {
	    info1: info1,
	    info2: info2,
	    data1: common1,
	    data2: common2
	  };
	}

	function extract(array, from, to) {
	  var newArray = [[], []];
	  var j = 0;
	  var length = array[0] ? array[0].length : 0;

	  for (var i = 0; i < length; i++) {
	    if ((!from || array[0][i] >= from) && (!to || array[0][i] <= to)) {
	      newArray[0][j] = array[0][i];
	      newArray[1][j] = array[1][i];
	      j++;
	    }
	  }

	  return newArray;
	}

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

	  var newArray = extract(array, from, to);
	  var info = normalize(newArray);
	  return {
	    info: info,
	    data: newArray
	  };
	}

	function calculateOverlapFromDiff(diffs) {
	  if (diffs[1].length === 0) return 0;
	  var sumPos = 0;

	  for (var i = 0; i < diffs[1].length; i++) {
	    sumPos += Math.abs(diffs[1][i]);
	  }

	  return 1 - sumPos;
	}

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

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

	var searchSimilarity = async function searchSimilarity() {
	  let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
	  const {
	    similarity = {},
	    minSimilarity = 0.5,
	    filter = {},
	    onStep
	  } = options;
	  let width = {
	    bottom: similarity.widthBottom,
	    top: similarity.widthTop
	  };

	  if (!this.experimentalSpectrum || !this.experimentalSpectrum.data.x.length > 0) {
	    throw Error('You need to add an experimental spectrum first using setMassSpectrum');
	  }

	  let experimentalData = this.experimentalSpectrum.data;
	  let sumY = this.experimentalSpectrum.sumY(); // the result of this query will be stored in a property 'ms'

	  let results = this.searchMSEM(filter.msem, options);
	  let flatEntries = [];

	  if (!options.flatten) {
	    for (let database of Object.keys(results)) {
	      for (let entry of results[database]) {
	        flatEntries.push(entry);
	      }
	    }
	  } else {
	    flatEntries = results;
	  }

	  let {
	    widthFunction,
	    zone = {}
	  } = similarity;

	  if (widthFunction && typeof widthFunction === 'string') {
	    // eslint-disable-next-line no-new-func
	    widthFunction = new Function('mass', widthFunction);
	    let checkTopBottom = widthFunction(123);

	    if (!checkTopBottom.bottom || !checkTopBottom.top) {
	      throw Error('widthFunction should return an object with bottom and top properties');
	    }
	  }

	  const {
	    low = -0.5,
	    high = 2.5
	  } = zone; // we need to calculate the similarity of the isotopic distribution

	  let similarityProcessor = new Similarity(similarity);
	  similarityProcessor.setPeaks1([experimentalData.x, experimentalData.y]);

	  for (let i = 0; i < flatEntries.length; i++) {
	    const entry = flatEntries[i];
	    if (onStep) await onStep(i);
	    let isotopicDistribution = new IsotopicDistribution(entry.mf, {
	      allowNeutral: false,
	      ionizations: [entry.ionization]
	    });
	    let distribution = isotopicDistribution.getDistribution(); // we need to define the comparison zone that depends of the charge

	    let from = entry.ms.em + low / Math.abs(entry.ms.charge);
	    let to = entry.ms.em + high / Math.abs(entry.ms.charge);
	    similarityProcessor.setFromTo(from, to);

	    if (widthFunction) {
	      width = widthFunction(entry.ms.em);
	      similarityProcessor.setTrapezoid(width.bottom, width.top);
	    }

	    similarityProcessor.setPeaks2([distribution.xs, distribution.ys]);
	    let result = similarityProcessor.getSimilarity();
	    result.extractInfo1.from = from;
	    result.extractInfo1.to = to;

	    if (result.similarity > minSimilarity) {
	      entry.ms.similarity = {
	        value: result.similarity,
	        experimental: result.extract1,
	        theoretical: result.extract2,
	        difference: result.diff,
	        experimentalInfo: result.extractInfo1,
	        thereoticalInfo: result.extractInfo2,
	        quantity: result.extractInfo1.sum / sumY,
	        width
	      };
	    }
	  }

	  if (!options.flatten) {
	    for (let database of Object.keys(results)) {
	      results[database] = results[database].filter(entry => entry.ms.similarity).sort((a, b) => b.ms.similarity.value - a.ms.similarity.value);

	      for (let entry of results[database]) {
	        flatEntries.push(entry);
	      }
	    }
	  } else {
	    results = results.filter(entry => entry.ms.similarity).sort((a, b) => b.ms.similarity.value - a.ms.similarity.value);
	  }

	  return results;
	};

	const {
	  Spectrum
	} = src$k;
	const loadCommercialsPromise = loadCommercials;
	const loadGoogleSheetPromise = loadGoogleSheet;
	const loadKnapSackPromise = loadKnapSack;

	function DBManager() {
	  this.databases = {};
	  this.experimentalSpectrum = undefined;
	}
	/**
	 *
	 * @param {*} data
	 * @param {object} [options={}]
	 * @param {number} [options.normed=true] Should we normed (sum Y to 1) the experimental spectrum ?
	 */


	DBManager.prototype.setExperimentalSpectrum = function setExperimentalSpectrum(data) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const {
	    normed = true
	  } = options;

	  if (normed) {
	    this.experimentalSpectrum = new Spectrum(data).normedY();
	  } else {
	    this.experimentalSpectrum = new Spectrum(data);
	  }

	  return this.experimentalSpectrum;
	};
	/**
	 * Add a new database using the KnapSack content
	 * @param {*} options
	 */


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


	DBManager.prototype.loadCommercials = async function loadCommercials() {
	  let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
	  const {
	    databaseName = 'commercials',
	    forceReload = false
	  } = options;
	  if (this.databases[databaseName] && !forceReload) return;
	  this.databases[databaseName] = await loadCommercialsPromise();
	};

	DBManager.prototype.get = function get(databaseName) {
	  return this.databases[databaseName];
	};
	/**
	 * Load the contaminants databvase from a google sheet document
	 * @param {*} options
	 * @param {string} ['contaminants'] databaseName
	 */


	DBManager.prototype.loadContaminants = async function loadContaminants() {
	  let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
	  const {
	    databaseName = 'contaminants',
	    forceReload = false
	  } = options;
	  if (this.databases[databaseName] && !forceReload) return;
	  this.databases[databaseName] = await loadGoogleSheetPromise();
	};
	/**
	 * Load a google sheet containin
	 * @param {*} options
	 * @param {string} ['sheet'] databaseName
	 */


	DBManager.prototype.loadGoogleSheet = async function loadGoogleSheet() {
	  let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
	  const {
	    databaseName = 'sheet',
	    forceReload = false
	  } = options;
	  if (this.databases[databaseName] && !forceReload) return;
	  this.databases[databaseName] = await loadGoogleSheetPromise();
	};

	DBManager.prototype.loadTest = async function loadTest() {
	  await this.fromArray(['C1-100'], {
	    databaseName: 'test',
	    ionizations: '+'
	  });
	};

	DBManager.prototype.loadNeutralTest = async function loadNeutralTest() {
	  let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
	  const {
	    maxC = 100
	  } = options;
	  await this.fromArray([`C1-${maxC}`], {
	    databaseName: 'test'
	  });
	};

	DBManager.prototype.fromMonoisotopicMass = async function fromMonoisotopicMass$1(mass) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const {
	    databaseName = 'monoisotopic',
	    append = false
	  } = options;
	  let result = await fromMonoisotopicMass(mass, options);
	  replaceOrAppend(this, databaseName, result.mfs, append);
	  return result;
	};

	DBManager.prototype.fromArray = async function fromArray$1(sequence) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const {
	    databaseName = 'generated',
	    append = false,
	    estimate
	  } = options;
	  const results = await fromArray(sequence, options);
	  if (estimate) return results;
	  replaceOrAppend(this, databaseName, results, append);
	};

	DBManager.prototype.fromRange = async function fromRange$1(sequence) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const {
	    databaseName = 'generated',
	    append = false,
	    estimate
	  } = options;
	  const results = await fromRange(sequence, options);
	  if (estimate) return results;
	  replaceOrAppend(this, databaseName, results, append);
	};

	DBManager.prototype.fromPeptidicSequence = async function fromPeptidicSequence$1(sequence) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const {
	    databaseName = 'peptidic',
	    append = false,
	    estimate
	  } = options;
	  const results = await fromPeptidicSequence(sequence, options);
	  if (estimate) return results;
	  replaceOrAppend(this, databaseName, results, append);
	};
	/**
	 *
	 * @param {string} databaseName
	 * @param {object} [options={}]
	 * @param {number} [options.precision=100]
	 * @param {string} [options.ionizations='']
	 * @returns
	 */


	DBManager.prototype.appendFragmentsInfo = async function appendFragmentsInfo$1(databaseName) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const database = this.databases[databaseName];
	  await appendFragmentsInfo(this.experimentalSpectrum, database, options);
	  return database;
	};

	DBManager.prototype.fromNucleicSequence = async function fromNucleicSequence$1(sequence) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const {
	    databaseName = 'nucleic',
	    append = false,
	    estimate
	  } = options;
	  const results = await fromNucleicSequence(sequence, options);
	  if (estimate) return results;
	  replaceOrAppend(this, databaseName, results, append);
	};

	DBManager.prototype.listDatabases = function listDatabases() {
	  return Object.keys(this.databases).sort();
	};

	DBManager.prototype.getInfo = function getInfo() {
	  return {
	    databases: Object.keys(this.databases).sort().map(key => {
	      return {
	        name: key,
	        nbEntries: this.databases[key].length
	      };
	    })
	  };
	};

	DBManager.prototype.massShifts = massShifts_1;
	DBManager.prototype.search = search;
	DBManager.prototype.searchMSEM = searchMSEM;
	DBManager.prototype.searchPubchem = searchPubchem;
	DBManager.prototype.searchSimilarity = searchSimilarity;
	var src$3 = DBManager;

	function replaceOrAppend(emdb, databaseName, results) {
	  let append = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;

	  if (!emdb.databases[databaseName] || !append) {
	    emdb.databases[databaseName] = results;
	    return;
	  }

	  emdb.databases[databaseName] = emdb.databases[databaseName].concat(results);
	}

	DBManager.Peptide = src$8;
	DBManager.Nucleotide = src$7;
	DBManager.MFParser = src$h;
	DBManager.IsotopicDistribution = src$5;

	function appendInternals$1(data) {
	  // for each line (internal fragment) we calculate the vertical position
	  // where it should be drawn as well and the maximal number of lines
	  let maxNumberLines = 0;

	  for (let result of data.results) {
	    if (result.internal) {
	      result.slot = assignSlot(result.from, result.to, data.residues.residues);
	      if (result.slot > maxNumberLines) maxNumberLines = result.slot;
	    }
	  }

	  for (let row of data.rows) {
	    row.info.internals = maxNumberLines;
	  }
	} // we need to define the height of the line.
	// we need to find a height that is not yet used.


	function assignSlot(from, to, residues) {
	  let used = {};
	  if (from > 0) from--; // we ensure that we don't put on the same line to sequences that are consecutive

	  for (let i = from; i < to; i++) {
	    let residue = residues[i];
	    residue.paper.usedSlots.forEach((usedSlot, index) => {
	      used[index] = true;
	    });
	  }

	  let counter = 0;

	  while (true) {
	    if (!used[counter]) {
	      break;
	    }

	    counter++;
	  }

	  for (let i = from; i < to; i++) {
	    residues[i].paper.usedSlots[counter] = true;
	  }

	  return counter;
	}

	var appendInternals_1 = appendInternals$1;

	const Nucleotide = src$7;
	const Peptide = src$8;
	const groups = src$9.getGroupsObject();
	const ALTERNATIVES = ['', '¹', '²', '³', '⁴', '⁵', '⁶', '⁷', '⁸', '⁹'];
	const SYMBOLS = ['Θ', 'Δ', 'Λ', 'Φ', 'Ω', 'Γ', 'Χ'];
	let currentSymbol = 0;
	/**
	 * Code that allows to split a sequence of amino acids or nucleotides natural or non natural
	 * @param {string} [sequence]
	 * @param {object} [options={}]
	 * @param {string} [options.kind] - peptide, rna, ds-dna or dna. Default if contains U: rna, otherwise ds-dna
	 * @param {string} [options.fivePrime=monophosphate] - alcohol, monophosphate, diphosphate, triphosphate
	 * @param {string} [options.circular=false]
	 */

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

	  if (kind === 'peptide') {
	    sequence = Peptide.sequenceToMF(sequence);
	  } else {
	    sequence = Nucleotide.sequenceToMF(sequence, options);
	  }

	  const result = {
	    begin: '',
	    end: '',
	    residues: []
	  };
	  const STATE_BEGIN = 0;
	  const STATE_MIDDLE = 1;
	  const STATE_END = 2;
	  let parenthesisLevel = 0;
	  let state = STATE_BEGIN; // as long as we don't have an uppercase followed by 2 lowercases

	  for (let i = 0; i < sequence.length; i++) {
	    let currentChar = sequence.charAt(i);
	    let nextChar = i < sequence.length - 1 ? sequence.charAt(i + 1) : '';
	    let nextNextChar = i < sequence.length - 2 ? sequence.charAt(i + 2) : '';

	    if (state === STATE_BEGIN && currentChar.match(/[A-Z]/) && nextChar.match(/[a-z]/) && nextNextChar.match(/[a-z]/) && parenthesisLevel === 0) {
	      state = STATE_MIDDLE;
	    }

	    if (state === STATE_MIDDLE && !sequence.substring(i).match(/[A-Z][a-z][a-z]/) && !currentChar.match(/[a-z]/) && parenthesisLevel === 0) {
	      state = STATE_END;
	    } else {
	      if (currentChar.match(/[A-Z]/) && nextChar.match(/[a-z]/) && nextNextChar.match(/[a-z]/) && parenthesisLevel === 0) {
	        result.residues.push('');
	      }
	    }

	    switch (state) {
	      case STATE_BEGIN:
	        result.begin = result.begin + currentChar;
	        break;

	      case STATE_MIDDLE:
	        result.residues[result.residues.length - 1] = result.residues[result.residues.length - 1] + currentChar;
	        break;

	      case STATE_END:
	        result.end = result.end + currentChar;
	        break;
	    }

	    if (currentChar === '(') {
	      parenthesisLevel++;
	    } else if (currentChar === ')') {
	      parenthesisLevel--;
	    }
	  } // we process all the residues


	  let alternatives = {};
	  let replacements = {};

	  for (let i = 0; i < result.residues.length; i++) {
	    let label = result.residues[i];
	    let residue = {
	      value: label,
	      results: {
	        begin: [],
	        end: []
	      }
	    };
	    residue.fromBegin = i + 1;
	    residue.fromEnd = result.residues.length - i;
	    residue.kind = 'residue';

	    if (label.includes('(')) {
	      getModifiedReplacement(label, residue, alternatives, replacements);
	    } else {
	      if (groups[label] && groups[label].oneLetter) {
	        residue.label = groups[label].oneLetter;
	      } else {
	        getUnknownReplacement(label, residue, replacements);
	      }
	    }

	    result.residues[i] = residue;
	  }

	  result.begin = removeStartEndParenthesis(result.begin);
	  result.end = removeStartEndParenthesis(result.end);

	  if (result.begin.length > 2) {
	    let label = options.kind === 'peptide' ? 'Nter' : "5'";
	    replacements[result.begin] = {
	      label
	    };
	    result.begin = label;
	  }

	  if (result.end.length > 2) {
	    let label = options.kind === 'peptide' ? 'Cter' : "3'";
	    replacements[result.end] = {
	      label
	    };
	    result.end = label;
	  }

	  result.begin = {
	    label: result.begin,
	    kind: 'begin'
	  };
	  result.end = {
	    label: result.end,
	    kind: 'end'
	  };
	  result.alternatives = alternatives;
	  result.replacements = replacements;
	  result.all = [result.begin].concat(result.residues, [result.end]);
	  result.all.forEach(entry => {
	    entry.info = {
	      nbOver: 0,
	      nbUnder: 0
	    };
	  });
	  data.residues = result;
	};

	function getUnknownReplacement(unknownResidue, residue, replacements) {
	  if (!replacements[unknownResidue]) {
	    replacements[unknownResidue] = {
	      label: SYMBOLS[currentSymbol] || '?',
	      id: unknownResidue
	    };
	  }

	  currentSymbol++;
	  residue.replaced = true;
	  residue.label = replacements[unknownResidue].label;
	}

	function getModifiedReplacement(modifiedResidue, residue, alternatives, replacements) {
	  if (!replacements[modifiedResidue]) {
	    let position = modifiedResidue.indexOf('(');
	    let residueCode = modifiedResidue.substring(0, position);
	    let modification = removeStartEndParenthesis(modifiedResidue.substring(position));

	    if (groups[residueCode] && groups[residueCode].alternativeOneLetter) {
	      let alternativeOneLetter = groups[residueCode].alternativeOneLetter;

	      if (!alternatives[alternativeOneLetter]) {
	        alternatives[alternativeOneLetter] = {
	          count: 1
	        };
	      } else {
	        alternatives[alternativeOneLetter].count++;
	      }

	      replacements[modifiedResidue] = {
	        label: ALTERNATIVES[alternatives[alternativeOneLetter].count - 1] + alternativeOneLetter,
	        residue: residueCode,
	        modification
	      };
	    } else {
	      getUnknownReplacement(modifiedResidue, residue, replacements);
	    }
	  }

	  residue.replaced = true;
	  residue.label = replacements[modifiedResidue].label;
	}

	function removeStartEndParenthesis(mf) {
	  if (mf[0] === '(' && mf[mf.length - 1] === ')') {
	    return mf.substring(1, mf.length - 1);
	  }

	  return mf;
	}

	/*!
	* @svgdotjs/svg.js - A lightweight library for manipulating and animating SVG.
	* @version 3.1.1
	* https://svgjs.dev/
	*
	* @copyright Wout Fierens <wout@mick-wout.com>
	* @license MIT
	*
	* BUILT: Fri Jun 25 2021 15:37:54 GMT+0200 (Mitteleuropäische Sommerzeit)
	*/
	const methods$1 = {};
	const names = [];

	function registerMethods(name, m) {
	  if (Array.isArray(name)) {
	    for (const _name of name) {
	      registerMethods(_name, m);
	    }

	    return;
	  }

	  if (typeof name === 'object') {
	    for (const _name in name) {
	      registerMethods(_name, name[_name]);
	    }

	    return;
	  }

	  addMethodNames(Object.getOwnPropertyNames(m));
	  methods$1[name] = Object.assign(methods$1[name] || {}, m);
	}

	function getMethodsFor(name) {
	  return methods$1[name] || {};
	}

	function getMethodNames() {
	  return [...new Set(names)];
	}

	function addMethodNames(_names) {
	  names.push(..._names);
	} // Map function


	function map(array, block) {
	  let i;
	  const il = array.length;
	  const result = [];

	  for (i = 0; i < il; i++) {
	    result.push(block(array[i]));
	  }

	  return result;
	} // Filter function


	function filter(array, block) {
	  let i;
	  const il = array.length;
	  const result = [];

	  for (i = 0; i < il; i++) {
	    if (block(array[i])) {
	      result.push(array[i]);
	    }
	  }

	  return result;
	} // Degrees to radians


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


	function degrees(r) {
	  return r * 180 / Math.PI % 360;
	} // Convert dash-separated-string to camelCase


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


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


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


	function proportionalSize(element, width, height, box) {
	  if (width == null || height == null) {
	    box = box || element.bbox();

	    if (width == null) {
	      width = box.width / box.height * height;
	    } else if (height == null) {
	      height = box.height / box.width * width;
	    }
	  }

	  return {
	    width: width,
	    height: height
	  };
	}
	/**
	 * This function adds support for string origins.
	 * It searches for an origin in o.origin o.ox and o.originX.
	 * This way, origin: {x: 'center', y: 50} can be passed as well as ox: 'center', oy: 50
	**/


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

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

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


	  const condX = typeof ox === 'string';
	  const condY = typeof oy === 'string';

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

	    if (condX) {
	      ox = ox.includes('left') ? x : ox.includes('right') ? x + width : x + width / 2;
	    }

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


	  return [ox, oy];
	}

	var utils = {
	  __proto__: null,
	  map: map,
	  filter: filter,
	  radians: radians,
	  degrees: degrees,
	  camelCase: camelCase,
	  unCamelCase: unCamelCase,
	  capitalize: capitalize,
	  proportionalSize: proportionalSize,
	  getOrigin: getOrigin
	}; // Default namespaces

	const svg = 'http://www.w3.org/2000/svg';
	const html = 'http://www.w3.org/1999/xhtml';
	const xmlns = 'http://www.w3.org/2000/xmlns/';
	const xlink = 'http://www.w3.org/1999/xlink';
	const svgjs = 'http://svgjs.dev/svgjs';
	var namespaces = {
	  __proto__: null,
	  svg: svg,
	  html: html,
	  xmlns: xmlns,
	  xlink: xlink,
	  svgjs: svgjs
	};
	const globals = {
	  window: typeof window === 'undefined' ? null : window,
	  document: typeof document === 'undefined' ? null : document
	};

	function registerWindow() {
	  let win = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
	  let doc = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
	  globals.window = win;
	  globals.document = doc;
	}

	const save = {};

	function saveWindow() {
	  save.window = globals.window;
	  save.document = globals.document;
	}

	function restoreWindow() {
	  globals.window = save.window;
	  globals.document = save.document;
	}

	function withWindow(win, fn) {
	  saveWindow();
	  registerWindow(win, win.document);
	  fn(win, win.document);
	  restoreWindow();
	}

	function getWindow() {
	  return globals.window;
	}

	class Base {// constructor (node/*, {extensions = []} */) {
	  //   // this.tags = []
	  //   //
	  //   // for (let extension of extensions) {
	  //   //   extension.setup.call(this, node)
	  //   //   this.tags.push(extension.name)
	  //   // }
	  // }
	}

	const elements = {};
	const root = '___SYMBOL___ROOT___'; // Method for element creation

	function create(name) {
	  let ns = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : svg;
	  // create element
	  return globals.document.createElementNS(ns, name);
	}

	function makeInstance(element) {
	  let isHTML = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
	  if (element instanceof Base) return element;

	  if (typeof element === 'object') {
	    return adopter(element);
	  }

	  if (element == null) {
	    return new elements[root]();
	  }

	  if (typeof element === 'string' && element.charAt(0) !== '<') {
	    return adopter(globals.document.querySelector(element));
	  } // Make sure, that HTML elements are created with the correct namespace


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

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

	  wrapper.removeChild(wrapper.firstChild);
	  return element;
	}

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


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

	  if (node.instance instanceof Base) return node.instance;

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


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

	  if (className === 'LinearGradient' || className === 'RadialGradient') {
	    className = 'Gradient'; // Fallback to Dom if element is not known
	  } else if (!elements[className]) {
	    className = 'Dom';
	  }

	  return new elements[className](node);
	}

	let adopter = adopt;

	function mockAdopt() {
	  let mock = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : adopt;
	  adopter = mock;
	}

	function register(element) {
	  let name = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : element.name;
	  let asRoot = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
	  elements[name] = element;
	  if (asRoot) elements[root] = element;
	  addMethodNames(Object.getOwnPropertyNames(element.prototype));
	  return element;
	}

	function getClass(name) {
	  return elements[name];
	} // Element id sequence


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

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


	function assignNewId(node) {
	  // do the same for SVG child nodes as well
	  for (let i = node.children.length - 1; i >= 0; i--) {
	    assignNewId(node.children[i]);
	  }

	  if (node.id) {
	    node.id = eid(node.nodeName);
	    return node;
	  }

	  return node;
	} // Method for extending objects


	function extend(modules, methods) {
	  let key, i;
	  modules = Array.isArray(modules) ? modules : [modules];

	  for (i = modules.length - 1; i >= 0; i--) {
	    for (key in methods) {
	      modules[i].prototype[key] = methods[key];
	    }
	  }
	}

	function wrapWithAttrCheck(fn) {
	  return function () {
	    for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
	      args[_key] = arguments[_key];
	    }

	    const o = args[args.length - 1];

	    if (o && o.constructor === Object && !(o instanceof Array)) {
	      return fn.apply(this, args.slice(0, -1)).attr(o);
	    } else {
	      return fn.apply(this, args);
	    }
	  };
	}

	function siblings() {
	  return this.parent().children();
	} // Get the current position siblings


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


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


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


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

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


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


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

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


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

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


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


	function after(element) {
	  element = makeInstance(element);
	  element.remove();
	  const i = this.position();
	  this.parent().add(element, i + 1);
	  return this;
	}

	function insertBefore(element) {
	  element = makeInstance(element);
	  element.before(this);
	  return this;
	}

	function insertAfter(element) {
	  element = makeInstance(element);
	  element.after(this);
	  return this;
	}

	registerMethods('Dom', {
	  siblings,
	  position,
	  next,
	  prev,
	  forward,
	  backward,
	  front,
	  back,
	  before,
	  after,
	  insertBefore,
	  insertAfter
	}); // Parse unit value

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

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

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

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

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

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

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

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

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

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

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

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

	const isPathLetter = /[MLHVCSQTAZ]/i;
	var regex = {
	  __proto__: null,
	  numberAndUnit: numberAndUnit,
	  hex: hex,
	  rgb: rgb,
	  reference: reference,
	  transforms: transforms,
	  whitespace: whitespace,
	  isHex: isHex,
	  isRgb: isRgb,
	  isBlank: isBlank,
	  isNumber: isNumber,
	  isImage: isImage,
	  delimiter: delimiter,
	  isPathLetter: isPathLetter
	};

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


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


	function addClass(name) {
	  if (!this.hasClass(name)) {
	    const array = this.classes();
	    array.push(name);
	    this.attr('class', array.join(' '));
	  }

	  return this;
	} // Remove class from the node


	function removeClass(name) {
	  if (this.hasClass(name)) {
	    this.attr('class', this.classes().filter(function (c) {
	      return c !== name;
	    }).join(' '));
	  }

	  return this;
	} // Toggle the presence of a class on the node


	function toggleClass(name) {
	  return this.hasClass(name) ? this.removeClass(name) : this.addClass(name);
	}

	registerMethods('Dom', {
	  classes,
	  hasClass,
	  addClass,
	  removeClass,
	  toggleClass
	});

	function css(style, val) {
	  const ret = {};

	  if (arguments.length === 0) {
	    // get full style as object
	    this.node.style.cssText.split(/\s*;\s*/).filter(function (el) {
	      return !!el.length;
	    }).forEach(function (el) {
	      const t = el.split(/\s*:\s*/);
	      ret[t[0]] = t[1];
	    });
	    return ret;
	  }

	  if (arguments.length < 2) {
	    // get style properties as array
	    if (Array.isArray(style)) {
	      for (const name of style) {
	        const cased = camelCase(name);
	        ret[cased] = this.node.style[cased];
	      }

	      return ret;
	    } // get style for property


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


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


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

	  return this;
	} // Show element


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


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


	function visible() {
	  return this.css('display') !== 'none';
	}

	registerMethods('Dom', {
	  css,
	  show,
	  hide,
	  visible
	});

	function data(a, v, r) {
	  if (a == null) {
	    // get an object of attributes
	    return this.data(map(filter(this.node.attributes, el => el.nodeName.indexOf('data-') === 0), el => el.nodeName.slice(5)));
	  } else if (a instanceof Array) {
	    const data = {};

	    for (const key of a) {
	      data[key] = this.data(key);
	    }

	    return data;
	  } else if (typeof a === 'object') {
	    for (v in a) {
	      this.data(v, a[v]);
	    }
	  } else if (arguments.length < 2) {
	    try {
	      return JSON.parse(this.attr('data-' + a));
	    } catch (e) {
	      return this.attr('data-' + a);
	    }
	  } else {
	    this.attr('data-' + a, v === null ? null : r === true || typeof v === 'string' || typeof v === 'number' ? v : JSON.stringify(v));
	  }

	  return this;
	}

	registerMethods('Dom', {
	  data
	});

	function remember(k, v) {
	  // remember every item in an object individually
	  if (typeof arguments[0] === 'object') {
	    for (const key in k) {
	      this.remember(key, k[key]);
	    }
	  } else if (arguments.length === 1) {
	    // retrieve memory
	    return this.memory()[k];
	  } else {
	    // store memory
	    this.memory()[k] = v;
	  }

	  return this;
	} // Erase a given memory


	function forget() {
	  if (arguments.length === 0) {
	    this._memory = {};
	  } else {
	    for (let i = arguments.length - 1; i >= 0; i--) {
	      delete this.memory()[arguments[i]];
	    }
	  }

	  return this;
	} // This triggers creation of a new hidden class which is not performant
	// However, this function is not rarely used so it will not happen frequently
	// Return local memory object


	function memory() {
	  return this._memory = this._memory || {};
	}

	registerMethods('Dom', {
	  remember,
	  forget,
	  memory
	});

	function sixDigitHex(hex) {
	  return hex.length === 4 ? ['#', hex.substring(1, 2), hex.substring(1, 2), hex.substring(2, 3), hex.substring(2, 3), hex.substring(3, 4), hex.substring(3, 4)].join('') : hex;
	}

	function componentHex(component) {
	  const integer = Math.round(component);
	  const bounded = Math.max(0, Math.min(255, integer));
	  const hex = bounded.toString(16);
	  return hex.length === 1 ? '0' + hex : hex;
	}

	function is(object, space) {
	  for (let i = space.length; i--;) {
	    if (object[space[i]] == null) {
	      return false;
	    }
	  }

	  return true;
	}

	function getParameters(a, b) {
	  const params = is(a, 'rgb') ? {
	    _a: a.r,
	    _b: a.g,
	    _c: a.b,
	    _d: 0,
	    space: 'rgb'
	  } : is(a, 'xyz') ? {
	    _a: a.x,
	    _b: a.y,
	    _c: a.z,
	    _d: 0,
	    space: 'xyz'
	  } : is(a, 'hsl') ? {
	    _a: a.h,
	    _b: a.s,
	    _c: a.l,
	    _d: 0,
	    space: 'hsl'
	  } : is(a, 'lab') ? {
	    _a: a.l,
	    _b: a.a,
	    _c: a.b,
	    _d: 0,
	    space: 'lab'
	  } : is(a, 'lch') ? {
	    _a: a.l,
	    _b: a.c,
	    _c: a.h,
	    _d: 0,
	    space: 'lch'
	  } : is(a, 'cmyk') ? {
	    _a: a.c,
	    _b: a.m,
	    _c: a.y,
	    _d: a.k,
	    space: 'cmyk'
	  } : {
	    _a: 0,
	    _b: 0,
	    _c: 0,
	    space: 'rgb'
	  };
	  params.space = b || params.space;
	  return params;
	}

	function cieSpace(space) {
	  if (space === 'lab' || space === 'xyz' || space === 'lch') {
	    return true;
	  } else {
	    return false;
	  }
	}

	function hueToRgb(p, q, t) {
	  if (t < 0) t += 1;
	  if (t > 1) t -= 1;
	  if (t < 1 / 6) return p + (q - p) * 6 * t;
	  if (t < 1 / 2) return q;
	  if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
	  return p;
	}

	class Color {
	  constructor() {
	    this.init(...arguments);
	  } // Test if given value is a color


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


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


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

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


	  static test(color) {
	    return typeof color === 'string' && (isHex.test(color) || isRgb.test(color));
	  }

	  cmyk() {
	    // Get the rgb values for the current color
	    const {
	      _a,
	      _b,
	      _c
	    } = this.rgb();
	    const [r, g, b] = [_a, _b, _c].map(v => v / 255); // Get the cmyk values in an unbounded format

	    const k = Math.min(1 - r, 1 - g, 1 - b);

	    if (k === 1) {
	      // Catch the black case
	      return new Color(0, 0, 0, 1, 'cmyk');
	    }

	    const c = (1 - r - k) / (1 - k);
	    const m = (1 - g - k) / (1 - k);
	    const y = (1 - b - k) / (1 - k); // Construct the new color

	    const color = new Color(c, m, y, k, 'cmyk');
	    return color;
	  }

	  hsl() {
	    // Get the rgb values
	    const {
	      _a,
	      _b,
	      _c
	    } = this.rgb();
	    const [r, g, b] = [_a, _b, _c].map(v => v / 255); // Find the maximum and minimum values to get the lightness

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

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

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

	    const color = new Color(360 * h, 100 * s, 100 * l, 'hsl');
	    return color;
	  }

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

	    if (this.space) {
	      for (const component in this.space) {
	        delete this[this.space[component]];
	      }
	    }

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

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

	        const [, _a, _b, _c] = hex.exec(sixDigitHex(a)).map(hexParse);
	        Object.assign(this, {
	          _a,
	          _b,
	          _c,
	          _d: 0,
	          space: 'rgb'
	        });
	      } else throw Error('Unsupported string format, can\'t construct Color');
	    } // Now add the components as a convenience


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

	  lab() {
	    // Get the xyz color
	    const {
	      x,
	      y,
	      z
	    } = this.xyz(); // Get the lab components

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

	    const color = new Color(l, a, b, 'lab');
	    return color;
	  }

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

	    const c = Math.sqrt(a ** 2 + b ** 2);
	    let h = 180 * Math.atan2(b, a) / Math.PI;

	    if (h < 0) {
	      h *= -1;
	      h = 360 - h;
	    } // Make a new color and return it


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


	  rgb() {
	    if (this.space === 'rgb') {
	      return this;
	    } else if (cieSpace(this.space)) {
	      // Convert to the xyz color space
	      let {
	        x,
	        y,
	        z
	      } = this;

	      if (this.space === 'lab' || this.space === 'lch') {
	        // Get the values in the lab space
	        let {
	          l,
	          a,
	          b
	        } = this;

	        if (this.space === 'lch') {
	          const {
	            c,
	            h
	          } = this;
	          const dToR = Math.PI / 180;
	          a = c * Math.cos(dToR * h);
	          b = c * Math.sin(dToR * h);
	        } // Undo the nonlinear function


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

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


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

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

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

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


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

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

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

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

	      const color = new Color(r, g, b);
	      return color;
	    } else {
	      return this;
	    }
	  }

	  toArray() {
	    const {
	      _a,
	      _b,
	      _c,
	      _d,
	      space
	    } = this;
	    return [_a, _b, _c, _d, space];
	  }

	  toHex() {
	    const [r, g, b] = this._clamped().map(componentHex);

	    return `#${r}${g}${b}`;
	  }

	  toRgb() {
	    const [rV, gV, bV] = this._clamped();

	    const string = `rgb(${rV},${gV},${bV})`;
	    return string;
	  }

	  toString() {
	    return this.toHex();
	  }

	  xyz() {
	    // Normalise the red, green and blue values
	    const {
	      _a: r255,
	      _b: g255,
	      _c: b255
	    } = this.rgb();
	    const [r, g, b] = [r255, g255, b255].map(v => v / 255); // Convert to the lab rgb space

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

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

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

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


	  _clamped() {
	    const {
	      _a,
	      _b,
	      _c
	    } = this.rgb();
	    const {
	      max,
	      min,
	      round
	    } = Math;

	    const format = v => max(0, min(round(v), 255));

	    return [_a, _b, _c].map(format);
	  }
	  /*
	  Constructing colors
	  */


	}

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


	  clone() {
	    return new Point(this);
	  }

	  init(x, y) {
	    const base = {
	      x: 0,
	      y: 0
	    }; // ensure source as object

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

	    this.x = source.x == null ? base.x : source.x;
	    this.y = source.y == null ? base.y : source.y;
	    return this;
	  }

	  toArray() {
	    return [this.x, this.y];
	  }

	  transform(m) {
	    return this.clone().transformO(m);
	  } // Transform point with matrix


	  transformO(m) {
	    if (!Matrix.isMatrixLike(m)) {
	      m = new Matrix(m);
	    }

	    const {
	      x,
	      y
	    } = this; // Perform the matrix multiplication

	    this.x = m.a * x + m.c * y + m.e;
	    this.y = m.b * x + m.d * y + m.f;
	    return this;
	  }

	}

	function point(x, y) {
	  return new Point(x, y).transform(this.screenCTM().inverse());
	}

	function closeEnough(a, b, threshold) {
	  return Math.abs(b - a) < (threshold || 1e-6);
	}

	class Matrix {
	  constructor() {
	    this.init(...arguments);
	  }

	  static formatTransforms(o) {
	    // Get all of the parameters required to form the matrix
	    const flipBoth = o.flip === 'both' || o.flip === true;
	    const flipX = o.flip && (flipBoth || o.flip === 'x') ? -1 : 1;
	    const flipY = o.flip && (flipBoth || o.flip === 'y') ? -1 : 1;
	    const skewX = o.skew && o.skew.length ? o.skew[0] : isFinite(o.skew) ? o.skew : isFinite(o.skewX) ? o.skewX : 0;
	    const skewY = o.skew && o.skew.length ? o.skew[1] : isFinite(o.skew) ? o.skew : isFinite(o.skewY) ? o.skewY : 0;
	    const scaleX = o.scale && o.scale.length ? o.scale[0] * flipX : isFinite(o.scale) ? o.scale * flipX : isFinite(o.scaleX) ? o.scaleX * flipX : flipX;
	    const scaleY = o.scale && o.scale.length ? o.scale[1] * flipY : isFinite(o.scale) ? o.scale * flipY : isFinite(o.scaleY) ? o.scaleY * flipY : flipY;
	    const shear = o.shear || 0;
	    const theta = o.rotate || o.theta || 0;
	    const origin = new Point(o.origin || o.around || o.ox || o.originX, o.oy || o.originY);
	    const ox = origin.x;
	    const oy = origin.y; // We need Point to be invalid if nothing was passed because we cannot default to 0 here. Thats why NaN

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

	    return {
	      scaleX,
	      scaleY,
	      skewX,
	      skewY,
	      shear,
	      theta,
	      rx,
	      ry,
	      tx,
	      ty,
	      ox,
	      oy,
	      px,
	      py
	    };
	  }

	  static fromArray(a) {
	    return {
	      a: a[0],
	      b: a[1],
	      c: a[2],
	      d: a[3],
	      e: a[4],
	      f: a[5]
	    };
	  }

	  static isMatrixLike(o) {
	    return o.a != null || o.b != null || o.c != null || o.d != null || o.e != null || o.f != null;
	  } // left matrix, right matrix, target matrix which is overwritten


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

	    o.a = a;
	    o.b = b;
	    o.c = c;
	    o.d = d;
	    o.e = e;
	    o.f = f;
	    return o;
	  }

	  around(cx, cy, matrix) {
	    return this.clone().aroundO(cx, cy, matrix);
	  } // Transform around a center point


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


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


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

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

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

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

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

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


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


	  flip(axis, around) {
	    return this.clone().flipO(axis, around);
	  }

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


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

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

	    this.a = source.a != null ? source.a : base.a;
	    this.b = source.b != null ? source.b : base.b;
	    this.c = source.c != null ? source.c : base.c;
	    this.d = source.d != null ? source.d : base.d;
	    this.e = source.e != null ? source.e : base.e;
	    this.f = source.f != null ? source.f : base.f;
	    return this;
	  }

	  inverse() {
	    return this.clone().inverseO();
	  } // Inverses matrix


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

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

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

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

	    this.a = na;
	    this.b = nb;
	    this.c = nc;
	    this.d = nd;
	    this.e = ne;
	    this.f = nf;
	    return this;
	  }

	  lmultiply(matrix) {
	    return this.clone().lmultiplyO(matrix);
	  }

	  lmultiplyO(matrix) {
	    const r = this;
	    const l = matrix instanceof Matrix ? matrix : new Matrix(matrix);
	    return Matrix.matrixMultiply(l, r, this);
	  } // Left multiplies by the given matrix


	  multiply(matrix) {
	    return this.clone().multiplyO(matrix);
	  }

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


	  rotate(r, cx, cy) {
	    return this.clone().rotateO(r, cx, cy);
	  }

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


	  scale(x, y, cx, cy) {
	    return this.clone().scaleO(...arguments);
	  }

	  scaleO(x) {
	    let y = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : x;
	    let cx = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
	    let cy = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0;

	    // Support uniform scaling
	    if (arguments.length === 3) {
	      cy = cx;
	      cx = y;
	      y = x;
	    }

	    const {
	      a,
	      b,
	      c,
	      d,
	      e,
	      f
	    } = this;
	    this.a = a * x;
	    this.b = b * y;
	    this.c = c * x;
	    this.d = d * y;
	    this.e = e * x - cx * x + cx;
	    this.f = f * y - cy * y + cy;
	    return this;
	  } // Shear matrix


	  shear(a, cx, cy) {
	    return this.clone().shearO(a, cx, cy);
	  }

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


	  skew(x, y, cx, cy) {
	    return this.clone().skewO(...arguments);
	  }

	  skewO(x) {
	    let y = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : x;
	    let cx = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
	    let cy = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0;

	    // support uniformal skew
	    if (arguments.length === 3) {
	      cy = cx;
	      cx = y;
	      y = x;
	    } // Convert degrees to radians


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


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


	  skewY(y, cx, cy) {
	    return this.skew(0, y, cx, cy);
	  }

	  toArray() {
	    return [this.a, this.b, this.c, this.d, this.e, this.f];
	  } // Convert matrix to string


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


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


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

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

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

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


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


	  translate(x, y) {
	    return this.clone().translateO(x, y);
	  }

	  translateO(x, y) {
	    this.e += x || 0;
	    this.f += y || 0;
	    return this;
	  }

	  valueOf() {
	    return {
	      a: this.a,
	      b: this.b,
	      c: this.c,
	      d: this.d,
	      e: this.e,
	      f: this.f
	    };
	  }

	}

	function ctm() {
	  return new Matrix(this.node.getCTM());
	}

	function screenCTM() {
	  /* https://bugzilla.mozilla.org/show_bug.cgi?id=1344537
	     This is needed because FF does not return the transformation matrix
	     for the inner coordinate system when getScreenCTM() is called on nested svgs.
	     However all other Browsers do that */
	  if (typeof this.isRoot === 'function' && !this.isRoot()) {
	    const rect = this.rect(1, 1);
	    const m = rect.node.getScreenCTM();
	    rect.remove();
	    return new Matrix(m);
	  }

	  return new Matrix(this.node.getScreenCTM());
	}

	register(Matrix, 'Matrix');

	function parser() {
	  // Reuse cached element if possible
	  if (!parser.nodes) {
	    const svg = makeInstance().size(2, 0);
	    svg.node.style.cssText = ['opacity: 0', 'position: absolute', 'left: -100%', 'top: -100%', 'overflow: hidden'].join(';');
	    svg.attr('focusable', 'false');
	    svg.attr('aria-hidden', 'true');
	    const path = svg.path().node;
	    parser.nodes = {
	      svg,
	      path
	    };
	  }

	  if (!parser.nodes.svg.node.parentNode) {
	    const b = globals.document.body || globals.document.documentElement;
	    parser.nodes.svg.addTo(b);
	  }

	  return parser.nodes;
	}

	function isNulledBox(box) {
	  return !box.width && !box.height && !box.x && !box.y;
	}

	function domContains(node) {
	  return node === globals.document || (globals.document.documentElement.contains || function (node) {
	    // This is IE - it does not support contains() for top-level SVGs
	    while (node.parentNode) {
	      node = node.parentNode;
	    }

	    return node === globals.document;
	  }).call(globals.document.documentElement, node);
	}

	class Box {
	  constructor() {
	    this.init(...arguments);
	  }

	  addOffset() {
	    // offset by window scroll position, because getBoundingClientRect changes when window is scrolled
	    this.x += globals.window.pageXOffset;
	    this.y += globals.window.pageYOffset;
	    return new Box(this);
	  }

	  init(source) {
	    const base = [0, 0, 0, 0];
	    source = typeof source === 'string' ? source.split(delimiter).map(parseFloat) : Array.isArray(source) ? source : typeof source === 'object' ? [source.left != null ? source.left : source.x, source.top != null ? source.top : source.y, source.width, source.height] : arguments.length === 4 ? [].slice.call(arguments) : base;
	    this.x = source[0] || 0;
	    this.y = source[1] || 0;
	    this.width = this.w = source[2] || 0;
	    this.height = this.h = source[3] || 0; // Add more bounding box properties

	    this.x2 = this.x + this.w;
	    this.y2 = this.y + this.h;
	    this.cx = this.x + this.w / 2;
	    this.cy = this.y + this.h / 2;
	    return this;
	  }

	  isNulled() {
	    return isNulledBox(this);
	  } // Merge rect box with another, return a new instance


	  merge(box) {
	    const x = Math.min(this.x, box.x);
	    const y = Math.min(this.y, box.y);
	    const width = Math.max(this.x + this.width, box.x + box.width) - x;
	    const height = Math.max(this.y + this.height, box.y + box.height) - y;
	    return new Box(x, y, width, height);
	  }

	  toArray() {
	    return [this.x, this.y, this.width, this.height];
	  }

	  toString() {
	    return this.x + ' ' + this.y + ' ' + this.width + ' ' + this.height;
	  }

	  transform(m) {
	    if (!(m instanceof Matrix)) {
	      m = new Matrix(m);
	    }

	    let xMin = Infinity;
	    let xMax = -Infinity;
	    let yMin = Infinity;
	    let yMax = -Infinity;
	    const pts = [new Point(this.x, this.y), new Point(this.x2, this.y), new Point(this.x, this.y2), new Point(this.x2, this.y2)];
	    pts.forEach(function (p) {
	      p = p.transform(m);
	      xMin = Math.min(xMin, p.x);
	      xMax = Math.max(xMax, p.x);
	      yMin = Math.min(yMin, p.y);
	      yMax = Math.max(yMax, p.y);
	    });
	    return new Box(xMin, yMin, xMax - xMin, yMax - yMin);
	  }

	}

	function getBox(el, getBBoxFn, retry) {
	  let box;

	  try {
	    // Try to get the box with the provided function
	    box = getBBoxFn(el.node); // If the box is worthless and not even in the dom, retry
	    // by throwing an error here...

	    if (isNulledBox(box) && !domContains(el.node)) {
	      throw new Error('Element not in the dom');
	    }
	  } catch (e) {
	    // ... and calling the retry handler here
	    box = retry(el);
	  }

	  return box;
	}

	function bbox() {
	  // Function to get bbox is getBBox()
	  const getBBox = node => node.getBBox(); // Take all measures so that a stupid browser renders the element
	  // so we can get the bbox from it when we try again


	  const retry = el => {
	    try {
	      const clone = el.clone().addTo(parser().svg).show();
	      const box = clone.node.getBBox();
	      clone.remove();
	      return box;
	    } catch (e) {
	      // We give up...
	      throw new Error(`Getting bbox of element "${el.node.nodeName}" is not possible: ${e.toString()}`);
	    }
	  };

	  const box = getBox(this, getBBox, retry);
	  const bbox = new Box(box);
	  return bbox;
	}

	function rbox(el) {
	  const getRBox = node => node.getBoundingClientRect();

	  const retry = el => {
	    // There is no point in trying tricks here because if we insert the element into the dom ourselves
	    // it obviously will be at the wrong position
	    throw new Error(`Getting rbox of element "${el.node.nodeName}" is not possible`);
	  };

	  const box = getBox(this, getRBox, retry);
	  const rbox = new Box(box); // If an element was passed, we want the bbox in the coordinate system of that element

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


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


	function inside(x, y) {
	  const box = this.bbox();
	  return x > box.x && y > box.y && x < box.x + box.width && y < box.y + box.height;
	}

	registerMethods({
	  viewbox: {
	    viewbox(x, y, width, height) {
	      // act as getter
	      if (x == null) return new Box(this.attr('viewBox')); // act as setter

	      return this.attr('viewBox', new Box(x, y, width, height));
	    },

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

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


	      if (!width || !height) {
	        throw new Error('Impossible to get absolute width and height. Please provide an absolute width and height attribute on the zooming element');
	      }

	      const v = this.viewbox();
	      const zoomX = width / v.width;
	      const zoomY = height / v.height;
	      const zoom = Math.min(zoomX, zoomY);

	      if (level == null) {
	        return zoom;
	      }

	      let zoomAmount = zoom / level; // Set the zoomAmount to the highest value which is safe to process and recover from
	      // The * 100 is a bit of wiggle room for the matrix transformation

	      if (zoomAmount === Infinity) zoomAmount = Number.MAX_SAFE_INTEGER / 100;
	      point = point || new Point(width / 2 / zoomX + v.x, height / 2 / zoomY + v.y);
	      const box = new Box(v).transform(new Matrix({
	        scale: zoomAmount,
	        origin: point
	      }));
	      return this.viewbox(box);
	    }

	  }
	});
	register(Box, 'Box');

	class List extends Array {
	  constructor() {
	    let arr = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];

	    for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
	      args[_key2 - 1] = arguments[_key2];
	    }

	    super(arr, ...args);
	    if (typeof arr === 'number') return this;
	    this.length = 0;
	    this.push(...arr);
	  }

	}

	extend([List], {
	  each(fnOrMethodName) {
	    for (var _len3 = arguments.length, args = new Array(_len3 > 1 ? _len3 - 1 : 0), _key3 = 1; _key3 < _len3; _key3++) {
	      args[_key3 - 1] = arguments[_key3];
	    }

	    if (typeof fnOrMethodName === 'function') {
	      return this.map((el, i, arr) => {
	        return fnOrMethodName.call(el, el, i, arr);
	      });
	    } else {
	      return this.map(el => {
	        return el[fnOrMethodName](...args);
	      });
	    }
	  },

	  toArray() {
	    return Array.prototype.concat.apply([], this);
	  }

	});
	const reserved = ['toArray', 'constructor', 'each'];

	List.extend = function (methods) {
	  methods = methods.reduce((obj, name) => {
	    // Don't overwrite own methods
	    if (reserved.includes(name)) return obj; // Don't add private methods

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

	    obj[name] = function () {
	      for (var _len4 = arguments.length, attrs = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
	        attrs[_key4] = arguments[_key4];
	      }

	      return this.each(name, ...attrs);
	    };

	    return obj;
	  }, {});
	  extend([List], methods);
	};

	function baseFind(query, parent) {
	  return new List(map((parent || globals.document).querySelectorAll(query), function (node) {
	    return adopt(node);
	  }));
	} // Scoped find method


	function find(query) {
	  return baseFind(query, this.node);
	}

	function findOne(query) {
	  return adopt(this.node.querySelector(query));
	}

	let listenerId = 0;
	const windowEvents = {};

	function getEvents(instance) {
	  let n = instance.getEventHolder(); // We dont want to save events in global space

	  if (n === globals.window) n = windowEvents;
	  if (!n.events) n.events = {};
	  return n.events;
	}

	function getEventTarget(instance) {
	  return instance.getEventTarget();
	}

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


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

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

	  if (!listener._svgjsListenerId) {
	    listener._svgjsListenerId = ++listenerId;
	  }

	  events.forEach(function (event) {
	    const ev = event.split('.')[0];
	    const ns = event.split('.')[1] || '*'; // ensure valid object

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

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

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


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

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


	  events = Array.isArray(events) ? events : (events || '').split(delimiter);
	  events.forEach(function (event) {
	    const ev = event && event.split('.')[0];
	    const ns = event && event.split('.')[1];
	    let namespace, l;

	    if (listener) {
	      // remove listener reference
	      if (bag[ev] && bag[ev][ns || '*']) {
	        // removeListener
	        n.removeEventListener(ev, bag[ev][ns || '*'][listener], options || false);
	        delete bag[ev][ns || '*'][listener];
	      }
	    } else if (ev && ns) {
	      // remove all listeners for a namespaced event
	      if (bag[ev] && bag[ev][ns]) {
	        for (l in bag[ev][ns]) {
	          off(n, [ev, ns].join('.'), l);
	        }

	        delete bag[ev][ns];
	      }
	    } else if (ns) {
	      // remove all listeners for a specific namespace
	      for (event in bag) {
	        for (namespace in bag[event]) {
	          if (ns === namespace) {
	            off(n, [event, ns].join('.'));
	          }
	        }
	      }
	    } else if (ev) {
	      // remove all listeners for the event
	      if (bag[ev]) {
	        for (namespace in bag[ev]) {
	          off(n, [ev, namespace].join('.'));
	        }

	        delete bag[ev];
	      }
	    } else {
	      // remove all listeners on a given node
	      for (event in bag) {
	        off(n, event);
	      }

	      clearEvents(instance);
	    }
	  });
	}

	function dispatch(node, event, data, options) {
	  const n = getEventTarget(node); // Dispatch event

	  if (event instanceof globals.window.Event) {
	    n.dispatchEvent(event);
	  } else {
	    event = new globals.window.CustomEvent(event, {
	      detail: data,
	      cancelable: true,
	      ...options
	    });
	    n.dispatchEvent(event);
	  }

	  return event;
	}

	class EventTarget extends Base {
	  addEventListener() {}

	  dispatch(event, data, options) {
	    return dispatch(this, event, data, options);
	  }

	  dispatchEvent(event) {
	    const bag = this.getEventHolder().events;
	    if (!bag) return true;
	    const events = bag[event.type];

	    for (const i in events) {
	      for (const j in events[i]) {
	        events[i][j](event);
	      }
	    }

	    return !event.defaultPrevented;
	  } // Fire given event


	  fire(event, data, options) {
	    this.dispatch(event, data, options);
	    return this;
	  }

	  getEventHolder() {
	    return this;
	  }

	  getEventTarget() {
	    return this;
	  } // Unbind event from listener


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


	  on(event, listener, binding, options) {
	    on(this, event, listener, binding, options);
	    return this;
	  }

	  removeEventListener() {}

	}

	register(EventTarget, 'EventTarget');

	function noop() {} // Default animation values


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

	const attrs = {
	  // fill and stroke
	  'fill-opacity': 1,
	  'stroke-opacity': 1,
	  'stroke-width': 0,
	  'stroke-linejoin': 'miter',
	  'stroke-linecap': 'butt',
	  fill: '#000000',
	  stroke: '#000000',
	  opacity: 1,
	  // position
	  x: 0,
	  y: 0,
	  cx: 0,
	  cy: 0,
	  // size
	  width: 0,
	  height: 0,
	  // radius
	  r: 0,
	  rx: 0,
	  ry: 0,
	  // gradient
	  offset: 0,
	  'stop-opacity': 1,
	  'stop-color': '#000000',
	  // text
	  'text-anchor': 'start'
	};
	var defaults = {
	  __proto__: null,
	  noop: noop,
	  timeline: timeline,
	  attrs: attrs
	};

	class SVGArray extends Array {
	  constructor() {
	    super(...arguments);
	    this.init(...arguments);
	  }

	  clone() {
	    return new this.constructor(this);
	  }

	  init(arr) {
	    // This catches the case, that native map tries to create an array with new Array(1)
	    if (typeof arr === 'number') return this;
	    this.length = 0;
	    this.push(...this.parse(arr));
	    return this;
	  } // Parse whitespace separated string


	  parse() {
	    let array = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
	    // If already is an array, no need to parse it
	    if (array instanceof Array) return array;
	    return array.trim().split(delimiter).map(parseFloat);
	  }

	  toArray() {
	    return Array.prototype.concat.apply([], this);
	  }

	  toSet() {
	    return new Set(this);
	  }

	  toString() {
	    return this.join(' ');
	  } // Flattens the array if needed


	  valueOf() {
	    const ret = [];
	    ret.push(...this);
	    return ret;
	  }

	}

	class SVGNumber {
	  // Initialize
	  constructor() {
	    this.init(...arguments);
	  }

	  convert(unit) {
	    return new SVGNumber(this.value, unit);
	  } // Divide number


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

	  init(value, unit) {
	    unit = Array.isArray(value) ? value[1] : unit;
	    value = Array.isArray(value) ? value[0] : value; // initialize defaults

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

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

	      if (unit) {
	        // make value numeric
	        this.value = parseFloat(unit[1]); // normalize

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


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

	    return this;
	  } // Subtract number


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


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


	  times(number) {
	    number = new SVGNumber(number);
	    return new SVGNumber(this * number, this.unit || number.unit);
	  }

	  toArray() {
	    return [this.value, this.unit];
	  }

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

	  toString() {
	    return (this.unit === '%' ? ~~(this.value * 1e8) / 1e6 : this.unit === 's' ? this.value / 1e3 : this.value) + this.unit;
	  }

	  valueOf() {
	    return this.value;
	  }

	}

	const hooks = [];

	function registerAttrHook(fn) {
	  hooks.push(fn);
	} // Set svg element attribute


	function attr(attr, val, ns) {
	  // act as full getter
	  if (attr == null) {
	    // get an object of attributes
	    attr = {};
	    val = this.node.attributes;

	    for (const node of val) {
	      attr[node.nodeName] = isNumber.test(node.nodeValue) ? parseFloat(node.nodeValue) : node.nodeValue;
	    }

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

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


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


	    if (this.rebuild && (attr === 'font-size' || attr === 'x')) {
	      this.rebuild();
	    }
	  }

	  return this;
	}

	class Dom extends EventTarget {
	  constructor(node, attrs) {
	    super();
	    this.node = node;
	    this.type = node.nodeName;

	    if (attrs && node !== attrs) {
	      this.attr(attrs);
	    }
	  } // Add given element at a position


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

	    if (element.removeNamespace && this.node instanceof globals.window.SVGElement) {
	      element.removeNamespace();
	    }

	    if (i == null) {
	      this.node.appendChild(element.node);
	    } else if (element.node !== this.node.childNodes[i]) {
	      this.node.insertBefore(element.node, this.node.childNodes[i]);
	    }

	    return this;
	  } // Add element to given container and return self


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


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


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

	    return this;
	  } // Clone element


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

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


	  each(block, deep) {
	    const children = this.children();
	    let i, il;

	    for (i = 0, il = children.length; i < il; i++) {
	      block.apply(children[i], [i, children]);

	      if (deep) {
	        children[i].each(block, deep);
	      }
	    }

	    return this;
	  }

	  element(nodeName, attrs) {
	    return this.put(new Dom(create(nodeName), attrs));
	  } // Get first child


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


	  get(i) {
	    return adopt(this.node.childNodes[i]);
	  }

	  getEventHolder() {
	    return this.node;
	  }

	  getEventTarget() {
	    return this.node;
	  } // Checks if the given element is a child


	  has(element) {
	    return this.index(element) >= 0;
	  }

	  html(htmlOrFn, outerHTML) {
	    return this.xml(htmlOrFn, outerHTML, html);
	  } // Get / set id


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


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


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


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


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


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

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

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

	    do {
	      if (typeof type === 'string' ? parent.matches(type) : parent instanceof type) return parent;
	    } while (parent = adopt(parent.node.parentNode));

	    return parent;
	  } // Basically does the same as `add()` but returns the added element instead


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


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


	  remove() {
	    if (this.parent()) {
	      this.parent().removeElement(this);
	    }

	    return this;
	  } // Remove a given child


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


	  replace(element) {
	    element = makeInstance(element);

	    if (this.node.parentNode) {
	      this.node.parentNode.replaceChild(element.node, this.node);
	    }

	    return element;
	  }

	  round() {
	    let precision = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 2;
	    let map = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
	    const factor = 10 ** precision;
	    const attrs = this.attr(map);

	    for (const i in attrs) {
	      if (typeof attrs[i] === 'number') {
	        attrs[i] = Math.round(attrs[i] * factor) / factor;
	      }
	    }

	    this.attr(attrs);
	    return this;
	  } // Import / Export raw svg


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


	  toString() {
	    return this.id();
	  }

	  words(text) {
	    // This is faster than removing all children and adding a new one
	    this.node.textContent = text;
	    return this;
	  }

	  wrap(node) {
	    const parent = this.parent();

	    if (!parent) {
	      return this.addTo(node);
	    }

	    const position = parent.index(this);
	    return parent.put(node, position).put(this);
	  } // write svgjs data to the dom


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


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


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

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

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

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

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


	        current.each(function () {
	          const result = xmlOrFn(this);

	          const _this = result || this; // If modifier returns false, discard node


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


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


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

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

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

	    for (let len = well.children.length; len--;) {
	      fragment.appendChild(well.firstElementChild);
	    }

	    const parent = this.parent(); // Add the whole fragment at once

	    return outerXML ? this.replace(fragment) && parent : this.add(fragment);
	  }

	}

	extend(Dom, {
	  attr,
	  find,
	  findOne
	});
	register(Dom, 'Dom');

	class Element extends Dom {
	  constructor(node, attrs) {
	    super(node, attrs); // initialize data object

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

	    this.node.instance = this;

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


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


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


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


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


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


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


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

	  getEventHolder() {
	    return this;
	  } // Set height of element


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


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


	  parents() {
	    let until = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.root();
	    until = makeInstance(until);
	    const parents = new List();
	    let parent = this;

	    while ((parent = parent.parent()) && parent.node !== globals.document && parent.nodeName !== '#document-fragment') {
	      parents.push(parent);

	      if (parent.node === until.node) {
	        break;
	      }
	    }

	    return parents;
	  } // Get referenced element form attribute value


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


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


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


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


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


	  writeDataToDom() {
	    // remove previously set data
	    this.node.removeAttribute('svgjs:data');

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

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


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


	  y(y) {
	    return this.attr('y', y);
	  }

	}

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

	  extension[m] = function (o) {
	    if (typeof o === 'undefined') {
	      return this.attr(m);
	    }

	    if (typeof o === 'string' || o instanceof Color || Color.isRgb(o) || o instanceof Element) {
	      this.attr(m, o);
	    } else {
	      // set all attributes from sugar.fill and sugar.stroke list
	      for (i = sugar[m].length - 1; i >= 0; i--) {
	        if (o[sugar[m][i]] != null) {
	          this.attr(sugar.prefix(m, sugar[m][i]), o[sugar[m][i]]);
	        }
	      }
	    }

	    return this;
	  };

	  registerMethods(['Element', 'Runner'], extension);
	});
	registerMethods(['Element', 'Runner'], {
	  // Let the user set the matrix directly
	  matrix: function (mat, b, c, d, e, f) {
	    // Act as a getter
	    if (mat == null) {
	      return new Matrix(this);
	    } // Act as a setter, the user can pass a matrix or a set of numbers


	    return this.attr('transform', new Matrix(mat, b, c, d, e, f));
	  },
	  // Map rotation to transform
	  rotate: function (angle, cx, cy) {
	    return this.transform({
	      rotate: angle,
	      ox: cx,
	      oy: cy
	    }, true);
	  },
	  // Map skew to transform
	  skew: function (x, y, cx, cy) {
	    return arguments.length === 1 || arguments.length === 3 ? this.transform({
	      skew: x,
	      ox: y,
	      oy: cx
	    }, true) : this.transform({
	      skew: [x, y],
	      ox: cx,
	      oy: cy
	    }, true);
	  },
	  shear: function (lam, cx, cy) {
	    return this.transform({
	      shear: lam,
	      ox: cx,
	      oy: cy
	    }, true);
	  },
	  // Map scale to transform
	  scale: function (x, y, cx, cy) {
	    return arguments.length === 1 || arguments.length === 3 ? this.transform({
	      scale: x,
	      ox: y,
	      oy: cx
	    }, true) : this.transform({
	      scale: [x, y],
	      ox: cx,
	      oy: cy
	    }, true);
	  },
	  // Map translate to transform
	  translate: function (x, y) {
	    return this.transform({
	      translate: [x, y]
	    }, true);
	  },
	  // Map relative translations to transform
	  relative: function (x, y) {
	    return this.transform({
	      relative: [x, y]
	    }, true);
	  },
	  // Map flip to transform
	  flip: function () {
	    let direction = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'both';
	    let origin = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'center';

	    if ('xybothtrue'.indexOf(direction) === -1) {
	      origin = direction;
	      direction = 'both';
	    }

	    return this.transform({
	      flip: direction,
	      origin: origin
	    }, true);
	  },
	  // Opacity
	  opacity: function (value) {
	    return this.attr('opacity', value);
	  }
	});
	registerMethods('radius', {
	  // Add x and y radius
	  radius: function (x) {
	    let y = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : x;
	    const type = (this._element || this).type;
	    return type === 'radialGradient' ? this.attr('r', new SVGNumber(x)) : this.rx(x).ry(y);
	  }
	});
	registerMethods('Path', {
	  // Get path length
	  length: function () {
	    return this.node.getTotalLength();
	  },
	  // Get point at length
	  pointAt: function (length) {
	    return new Point(this.node.getPointAtLength(length));
	  }
	});
	registerMethods(['Element', 'Runner'], {
	  // Set font
	  font: function (a, v) {
	    if (typeof a === 'object') {
	      for (v in a) this.font(v, a[v]);

	      return this;
	    }

	    return a === 'leading' ? this.leading(v) : a === 'anchor' ? this.attr('text-anchor', v) : a === 'size' || a === 'family' || a === 'weight' || a === 'stretch' || a === 'variant' || a === 'style' ? this.attr('font-' + a, v) : this.attr(a, v);
	  }
	}); // Add events to elements

	const methods = ['click', 'dblclick', 'mousedown', 'mouseup', 'mouseover', 'mouseout', 'mousemove', 'mouseenter', 'mouseleave', 'touchstart', 'touchmove', 'touchleave', 'touchend', 'touchcancel'].reduce(function (last, event) {
	  // add event to Element
	  const fn = function (f) {
	    if (f === null) {
	      this.off(event);
	    } else {
	      this.on(event, f);
	    }

	    return this;
	  };

	  last[event] = fn;
	  return last;
	}, {});
	registerMethods('Element', methods);

	function untransform() {
	  return this.attr('transform', null);
	} // merge the whole transformation chain into one matrix and returns it


	function matrixify() {
	  const matrix = (this.attr('transform') || ''). // split transformations
	  split(transforms).slice(0, -1).map(function (str) {
	    // generate key => value pairs
	    const kv = str.trim().split('(');
	    return [kv[0], kv[1].split(delimiter).map(function (str) {
	      return parseFloat(str);
	    })];
	  }).reverse() // merge every transformation into one matrix
	  .reduce(function (matrix, transform) {
	    if (transform[0] === 'matrix') {
	      return matrix.lmultiply(Matrix.fromArray(transform[1]));
	    }

	    return matrix[transform[0]].apply(matrix, transform[1]);
	  }, new Matrix());
	  return matrix;
	} // add an element to another parent without changing the visual representation on the screen


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


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


	function transform(o, relative) {
	  // Act as a getter if no object was passed
	  if (o == null || typeof o === 'string') {
	    const decomposed = new Matrix(this).decompose();
	    return o == null ? decomposed : decomposed[o];
	  }

	  if (!Matrix.isMatrixLike(o)) {
	    // Set the origin according to the defined transform
	    o = { ...o,
	      origin: getOrigin(o, this)
	    };
	  } // The user can pass a boolean, an Element or an Matrix or nothing


	  const cleanRelative = relative === true ? this : relative || false;
	  const result = new Matrix(cleanRelative).transform(o);
	  return this.attr('transform', result);
	}

	registerMethods('Element', {
	  untransform,
	  matrixify,
	  toParent,
	  toRoot,
	  transform
	});

	class Container extends Element {
	  flatten() {
	    this.each(function () {
	      if (this instanceof Container) {
	        return this.flatten().ungroup();
	      }
	    });
	    return this;
	  }

	  ungroup() {
	    let parent = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.parent();
	    let index = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : parent.index(this);
	    // when parent != this, we want append all elements to the end
	    index = index === -1 ? parent.children().length : index;
	    this.each(function (i, children) {
	      // reverse each
	      return children[children.length - i - 1].toParent(parent, index);
	    });
	    return this.remove();
	  }

	}

	register(Container, 'Container');

	class Defs extends Container {
	  constructor(node) {
	    let attrs = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : node;
	    super(nodeOrNew('defs', node), attrs);
	  }

	  flatten() {
	    return this;
	  }

	  ungroup() {
	    return this;
	  }

	}

	register(Defs, 'Defs');

	class Shape extends Element {}

	register(Shape, 'Shape');

	function rx(rx) {
	  return this.attr('rx', rx);
	} // Radius y value


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


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


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


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


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


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


	function height$2(height) {
	  return height == null ? this.ry() * 2 : this.ry(new SVGNumber(height).divide(2));
	}

	var circled = {
	  __proto__: null,
	  rx: rx,
	  ry: ry,
	  x: x$3,
	  y: y$3,
	  cx: cx$1,
	  cy: cy$1,
	  width: width$2,
	  height: height$2
	};

	class Ellipse extends Shape {
	  constructor(node) {
	    let attrs = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : node;
	    super(nodeOrNew('ellipse', node), attrs);
	  }

	  size(width, height) {
	    const p = proportionalSize(this, width, height);
	    return this.rx(new SVGNumber(p.width).divide(2)).ry(new SVGNumber(p.height).divide(2));
	  }

	}

	extend(Ellipse, circled);
	registerMethods('Container', {
	  // Create an ellipse
	  ellipse: wrapWithAttrCheck(function () {
	    let width = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
	    let height = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : width;
	    return this.put(new Ellipse()).size(width, height).move(0, 0);
	  })
	});
	register(Ellipse, 'Ellipse');

	class Fragment extends Dom {
	  constructor() {
	    let node = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : globals.document.createDocumentFragment();
	    super(node);
	  } // Import / Export raw xml


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


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


	    return super.xml(xmlOrFn, false, ns);
	  }

	}

	register(Fragment, 'Fragment');

	function from(x, y) {
	  return (this._element || this).type === 'radialGradient' ? this.attr({
	    fx: new SVGNumber(x),
	    fy: new SVGNumber(y)
	  }) : this.attr({
	    x1: new SVGNumber(x),
	    y1: new SVGNumber(y)
	  });
	}

	function to(x, y) {
	  return (this._element || this).type === 'radialGradient' ? this.attr({
	    cx: new SVGNumber(x),
	    cy: new SVGNumber(y)
	  }) : this.attr({
	    x2: new SVGNumber(x),
	    y2: new SVGNumber(y)
	  });
	}

	var gradiented = {
	  __proto__: null,
	  from: from,
	  to: to
	};

	class Gradient extends Container {
	  constructor(type, attrs) {
	    super(nodeOrNew(type + 'Gradient', typeof type === 'string' ? null : type), attrs);
	  } // custom attr to handle transform


	  attr(a, b, c) {
	    if (a === 'transform') a = 'gradientTransform';
	    return super.attr(a, b, c);
	  }

	  bbox() {
	    return new Box();
	  }

	  targets() {
	    return baseFind('svg [fill*="' + this.id() + '"]');
	  } // Alias string conversion to fill


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


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

	    if (typeof block === 'function') {
	      block.call(this, this);
	    }

	    return this;
	  } // Return the fill id


	  url() {
	    return 'url("#' + this.id() + '")';
	  }

	}

	extend(Gradient, gradiented);
	registerMethods({
	  Container: {
	    // Create gradient element in defs
	    gradient() {
	      return this.defs().gradient(...arguments);
	    }

	  },
	  // define gradient
	  Defs: {
	    gradient: wrapWithAttrCheck(function (type, block) {
	      return this.put(new Gradient(type)).update(block);
	    })
	  }
	});
	register(Gradient, 'Gradient');

	class Pattern extends Container {
	  // Initialize node
	  constructor(node) {
	    let attrs = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : node;
	    super(nodeOrNew('pattern', node), attrs);
	  } // custom attr to handle transform


	  attr(a, b, c) {
	    if (a === 'transform') a = 'patternTransform';
	    return super.attr(a, b, c);
	  }

	  bbox() {
	    return new Box();
	  }

	  targets() {
	    return baseFind('svg [fill*="' + this.id() + '"]');
	  } // Alias string conversion to fill


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


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

	    if (typeof block === 'function') {
	      block.call(this, this);
	    }

	    return this;
	  } // Return the fill id


	  url() {
	    return 'url("#' + this.id() + '")';
	  }

	}

	registerMethods({
	  Container: {
	    // Create pattern element in defs
	    pattern() {
	      return this.defs().pattern(...arguments);
	    }

	  },
	  Defs: {
	    pattern: wrapWithAttrCheck(function (width, height, block) {
	      return this.put(new Pattern()).update(block).attr({
	        x: 0,
	        y: 0,
	        width: width,
	        height: height,
	        patternUnits: 'userSpaceOnUse'
	      });
	    })
	  }
	});
	register(Pattern, 'Pattern');

	class Image extends Shape {
	  constructor(node) {
	    let attrs = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : node;
	    super(nodeOrNew('image', node), attrs);
	  } // (re)load image


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

	      if (this.width() === 0 && this.height() === 0) {
	        this.size(img.width, img.height);
	      }

	      if (p instanceof Pattern) {
	        // ensure pattern size if not set
	        if (p.width() === 0 && p.height() === 0) {
	          p.size(this.width(), this.height());
	        }
	      }

	      if (typeof callback === 'function') {
	        callback.call(this, e);
	      }
	    }, this);
	    on(img, 'load error', function () {
	      // dont forget to unbind memory leaking events
	      off(img);
	    });
	    return this.attr('href', img.src = url, xlink);
	  }

	}

	registerAttrHook(function (attr, val, _this) {
	  // convert image fill and stroke to patterns
	  if (attr === 'fill' || attr === 'stroke') {
	    if (isImage.test(val)) {
	      val = _this.root().defs().image(val);
	    }
	  }

	  if (val instanceof Image) {
	    val = _this.root().defs().pattern(0, 0, pattern => {
	      pattern.add(val);
	    });
	  }

	  return val;
	});
	registerMethods({
	  Container: {
	    // create image element, load image and set its size
	    image: wrapWithAttrCheck(function (source, callback) {
	      return this.put(new Image()).size(0, 0).load(source, callback);
	    })
	  }
	});
	register(Image, 'Image');

	class PointArray extends SVGArray {
	  // Get bounding box of points
	  bbox() {
	    let maxX = -Infinity;
	    let maxY = -Infinity;
	    let minX = Infinity;
	    let minY = Infinity;
	    this.forEach(function (el) {
	      maxX = Math.max(el[0], maxX);
	      maxY = Math.max(el[1], maxY);
	      minX = Math.min(el[0], minX);
	      minY = Math.min(el[1], minY);
	    });
	    return new Box(minX, minY, maxX - minX, maxY - minY);
	  } // Move point string


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

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

	    if (!isNaN(x) && !isNaN(y)) {
	      for (let i = this.length - 1; i >= 0; i--) {
	        this[i] = [this[i][0] + x, this[i][1] + y];
	      }
	    }

	    return this;
	  } // Parse point string and flat array


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

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


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

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

	    return points;
	  } // Resize poly string


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

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

	    return this;
	  } // Convert array to line object


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


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

	    for (let i = 0, il = this.length; i < il; i++) {
	      array.push(this[i].join(','));
	    }

	    return array.join(' ');
	  }

	  transform(m) {
	    return this.clone().transformO(m);
	  } // transform points with matrix (similar to Point.transform)


	  transformO(m) {
	    if (!Matrix.isMatrixLike(m)) {
	      m = new Matrix(m);
	    }

	    for (let i = this.length; i--;) {
	      // Perform the matrix multiplication
	      const [x, y] = this[i];
	      this[i][0] = m.a * x + m.c * y + m.e;
	      this[i][1] = m.b * x + m.d * y + m.f;
	    }

	    return this;
	  }

	}

	const MorphArray = PointArray; // Move by left top corner over x-axis

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


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


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


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

	var pointed = {
	  __proto__: null,
	  MorphArray: MorphArray,
	  x: x$2,
	  y: y$2,
	  width: width$1,
	  height: height$1
	};

	class Line extends Shape {
	  // Initialize node
	  constructor(node) {
	    let attrs = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : node;
	    super(nodeOrNew('line', node), attrs);
	  } // Get array


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


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


	  plot(x1, y1, x2, y2) {
	    if (x1 == null) {
	      return this.array();
	    } else if (typeof y1 !== 'undefined') {
	      x1 = {
	        x1,
	        y1,
	        x2,
	        y2
	      };
	    } else {
	      x1 = new PointArray(x1).toLine();
	    }

	    return this.attr(x1);
	  } // Set element size to given width and height


	  size(width, height) {
	    const p = proportionalSize(this, width, height);
	    return this.attr(this.array().size(p.width, p.height).toLine());
	  }

	}

	extend(Line, pointed);
	registerMethods({
	  Container: {
	    // Create a line element
	    line: wrapWithAttrCheck(function () {
	      for (var _len5 = arguments.length, args = new Array(_len5), _key5 = 0; _key5 < _len5; _key5++) {
	        args[_key5] = arguments[_key5];
	      }

	      // make sure plot is called as a setter
	      // x1 is not necessarily a number, it can also be an array, a string and a PointArray
	      return Line.prototype.plot.apply(this.put(new Line()), args[0] != null ? args : [0, 0, 0, 0]);
	    })
	  }
	});
	register(Line, 'Line');

	class Marker extends Container {
	  // Initialize node
	  constructor(node) {
	    let attrs = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : node;
	    super(nodeOrNew('marker', node), attrs);
	  } // Set height of element


	  height(height) {
	    return this.attr('markerHeight', height);
	  }

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


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


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


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

	    if (typeof block === 'function') {
	      block.call(this, this);
	    }

	    return this;
	  } // Set width of element


	  width(width) {
	    return this.attr('markerWidth', width);
	  }

	}

	registerMethods({
	  Container: {
	    marker() {
	      // Create marker element in defs
	      return this.defs().marker(...arguments);
	    }

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

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

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

	  }
	});
	register(Marker, 'Marker');
	/***
	Base Class
	==========
	The base stepper class that will be
	***/

	function makeSetterGetter(k, f) {
	  return function (v) {
	    if (v == null) return this[k];
	    this[k] = v;
	    if (f) f.call(this);
	    return this;
	  };
	}

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

	    if (stepPosition === 'none') {
	      --jumps;
	    } else if (stepPosition === 'both') {
	      ++jumps;
	    } // The beforeFlag is essentially useless


	    return function (t) {
	      let beforeFlag = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
	      // Step is called currentStep in referenced url
	      let step = Math.floor(t * steps);
	      const jumping = t * step % 1 === 0;

	      if (stepPosition === 'start' || stepPosition === 'both') {
	        ++step;
	      }

	      if (beforeFlag && jumping) {
	        --step;
	      }

	      if (t >= 0 && step < 0) {
	        step = 0;
	      }

	      if (t <= 1 && step > jumps) {
	        step = jumps;
	      }

	      return step / jumps;
	    };
	  }
	};

	class Stepper {
	  done() {
	    return false;
	  }

	}
	/***
	Easing Functions
	================
	***/


	class Ease extends Stepper {
	  constructor() {
	    let fn = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : timeline.ease;
	    super();
	    this.ease = easing[fn] || fn;
	  }

	  step(from, to, pos) {
	    if (typeof from !== 'number') {
	      return pos < 1 ? from : to;
	    }

	    return from + (to - from) * this.ease(pos);
	  }

	}
	/***
	Controller Types
	================
	***/


	class Controller extends Stepper {
	  constructor(fn) {
	    super();
	    this.stepper = fn;
	  }

	  done(c) {
	    return c.done;
	  }

	  step(current, target, dt, c) {
	    return this.stepper(current, target, dt, c);
	  }

	}

	function recalculate() {
	  // Apply the default parameters
	  const duration = (this._duration || 500) / 1000;
	  const overshoot = this._overshoot || 0; // Calculate the PID natural response

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

	  this.d = 2 * zeta * wn;
	  this.k = wn * wn;
	}

	class Spring extends Controller {
	  constructor() {
	    let duration = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 500;
	    let overshoot = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
	    super();
	    this.duration(duration).overshoot(overshoot);
	  }

	  step(current, target, dt, c) {
	    if (typeof current === 'string') return current;
	    c.done = dt === Infinity;
	    if (dt === Infinity) return target;
	    if (dt === 0) return current;
	    if (dt > 100) dt = 16;
	    dt /= 1000; // Get the previous velocity

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

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

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

	    c.done = Math.abs(target - newPosition) + Math.abs(velocity) < 0.002;
	    return c.done ? target : newPosition;
	  }

	}

	extend(Spring, {
	  duration: makeSetterGetter('_duration', recalculate),
	  overshoot: makeSetterGetter('_overshoot', recalculate)
	});

	class PID extends Controller {
	  constructor() {
	    let p = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0.1;
	    let i = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0.01;
	    let d = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
	    let windup = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 1000;
	    super();
	    this.p(p).i(i).d(d).windup(windup);
	  }

	  step(current, target, dt, c) {
	    if (typeof current === 'string') return current;
	    c.done = dt === Infinity;
	    if (dt === Infinity) return target;
	    if (dt === 0) return current;
	    const p = target - current;
	    let i = (c.integral || 0) + p * dt;
	    const d = (p - (c.error || 0)) / dt;
	    const windup = this._windup; // antiwindup

	    if (windup !== false) {
	      i = Math.max(-windup, Math.min(i, windup));
	    }

	    c.error = p;
	    c.integral = i;
	    c.done = Math.abs(p) < 0.001;
	    return c.done ? target : current + (this.P * p + this.I * i + this.D * d);
	  }

	}

	extend(PID, {
	  windup: makeSetterGetter('_windup'),
	  p: makeSetterGetter('P'),
	  i: makeSetterGetter('I'),
	  d: makeSetterGetter('D')
	});
	const segmentParameters = {
	  M: 2,
	  L: 2,
	  H: 1,
	  V: 1,
	  C: 6,
	  S: 4,
	  Q: 4,
	  T: 2,
	  A: 7,
	  Z: 0
	};
	const pathHandlers = {
	  M: function (c, p, p0) {
	    p.x = p0.x = c[0];
	    p.y = p0.y = c[1];
	    return ['M', p.x, p.y];
	  },
	  L: function (c, p) {
	    p.x = c[0];
	    p.y = c[1];
	    return ['L', c[0], c[1]];
	  },
	  H: function (c, p) {
	    p.x = c[0];
	    return ['H', c[0]];
	  },
	  V: function (c, p) {
	    p.y = c[0];
	    return ['V', c[0]];
	  },
	  C: function (c, p) {
	    p.x = c[4];
	    p.y = c[5];
	    return ['C', c[0], c[1], c[2], c[3], c[4], c[5]];
	  },
	  S: function (c, p) {
	    p.x = c[2];
	    p.y = c[3];
	    return ['S', c[0], c[1], c[2], c[3]];
	  },
	  Q: function (c, p) {
	    p.x = c[2];
	    p.y = c[3];
	    return ['Q', c[0], c[1], c[2], c[3]];
	  },
	  T: function (c, p) {
	    p.x = c[0];
	    p.y = c[1];
	    return ['T', c[0], c[1]];
	  },
	  Z: function (c, p, p0) {
	    p.x = p0.x;
	    p.y = p0.y;
	    return ['Z'];
	  },
	  A: function (c, p) {
	    p.x = c[5];
	    p.y = c[6];
	    return ['A', c[0], c[1], c[2], c[3], c[4], c[5], c[6]];
	  }
	};
	const mlhvqtcsaz = 'mlhvqtcsaz'.split('');

	for (let i = 0, il = mlhvqtcsaz.length; i < il; ++i) {
	  pathHandlers[mlhvqtcsaz[i]] = function (i) {
	    return function (c, p, p0) {
	      if (i === 'H') c[0] = c[0] + p.x;else if (i === 'V') c[0] = c[0] + p.y;else if (i === 'A') {
	        c[5] = c[5] + p.x;
	        c[6] = c[6] + p.y;
	      } else {
	        for (let j = 0, jl = c.length; j < jl; ++j) {
	          c[j] = c[j] + (j % 2 ? p.y : p.x);
	        }
	      }
	      return pathHandlers[i](c, p, p0);
	    };
	  }(mlhvqtcsaz[i].toUpperCase());
	}

	function makeAbsolut(parser) {
	  const command = parser.segment[0];
	  return pathHandlers[command](parser.segment.slice(1), parser.p, parser.p0);
	}

	function segmentComplete(parser) {
	  return parser.segment.length && parser.segment.length - 1 === segmentParameters[parser.segment[0].toUpperCase()];
	}

	function startNewSegment(parser, token) {
	  parser.inNumber && finalizeNumber(parser, false);
	  const pathLetter = isPathLetter.test(token);

	  if (pathLetter) {
	    parser.segment = [token];
	  } else {
	    const lastCommand = parser.lastCommand;
	    const small = lastCommand.toLowerCase();
	    const isSmall = lastCommand === small;
	    parser.segment = [small === 'm' ? isSmall ? 'l' : 'L' : lastCommand];
	  }

	  parser.inSegment = true;
	  parser.lastCommand = parser.segment[0];
	  return pathLetter;
	}

	function finalizeNumber(parser, inNumber) {
	  if (!parser.inNumber) throw new Error('Parser Error');
	  parser.number && parser.segment.push(parseFloat(parser.number));
	  parser.inNumber = inNumber;
	  parser.number = '';
	  parser.pointSeen = false;
	  parser.hasExponent = false;

	  if (segmentComplete(parser)) {
	    finalizeSegment(parser);
	  }
	}

	function finalizeSegment(parser) {
	  parser.inSegment = false;

	  if (parser.absolute) {
	    parser.segment = makeAbsolut(parser);
	  }

	  parser.segments.push(parser.segment);
	}

	function isArcFlag(parser) {
	  if (!parser.segment.length) return false;
	  const isArc = parser.segment[0].toUpperCase() === 'A';
	  const length = parser.segment.length;
	  return isArc && (length === 4 || length === 5);
	}

	function isExponential(parser) {
	  return parser.lastToken.toUpperCase() === 'E';
	}

	function pathParser(d) {
	  let toAbsolute = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
	  let index = 0;
	  let token = '';
	  const parser = {
	    segment: [],
	    inNumber: false,
	    number: '',
	    lastToken: '',
	    inSegment: false,
	    segments: [],
	    pointSeen: false,
	    hasExponent: false,
	    absolute: toAbsolute,
	    p0: new Point(),
	    p: new Point()
	  };

	  while (parser.lastToken = token, token = d.charAt(index++)) {
	    if (!parser.inSegment) {
	      if (startNewSegment(parser, token)) {
	        continue;
	      }
	    }

	    if (token === '.') {
	      if (parser.pointSeen || parser.hasExponent) {
	        finalizeNumber(parser, false);
	        --index;
	        continue;
	      }

	      parser.inNumber = true;
	      parser.pointSeen = true;
	      parser.number += token;
	      continue;
	    }

	    if (!isNaN(parseInt(token))) {
	      if (parser.number === '0' || isArcFlag(parser)) {
	        parser.inNumber = true;
	        parser.number = token;
	        finalizeNumber(parser, true);
	        continue;
	      }

	      parser.inNumber = true;
	      parser.number += token;
	      continue;
	    }

	    if (token === ' ' || token === ',') {
	      if (parser.inNumber) {
	        finalizeNumber(parser, false);
	      }

	      continue;
	    }

	    if (token === '-') {
	      if (parser.inNumber && !isExponential(parser)) {
	        finalizeNumber(parser, false);
	        --index;
	        continue;
	      }

	      parser.number += token;
	      parser.inNumber = true;
	      continue;
	    }

	    if (token.toUpperCase() === 'E') {
	      parser.number += token;
	      parser.hasExponent = true;
	      continue;
	    }

	    if (isPathLetter.test(token)) {
	      if (parser.inNumber) {
	        finalizeNumber(parser, false);
	      } else if (!segmentComplete(parser)) {
	        throw new Error('parser Error');
	      } else {
	        finalizeSegment(parser);
	      }

	      --index;
	    }
	  }

	  if (parser.inNumber) {
	    finalizeNumber(parser, false);
	  }

	  if (parser.inSegment && segmentComplete(parser)) {
	    finalizeSegment(parser);
	  }

	  return parser.segments;
	}

	function arrayToString(a) {
	  let s = '';

	  for (let i = 0, il = a.length; i < il; i++) {
	    s += a[i][0];

	    if (a[i][1] != null) {
	      s += a[i][1];

	      if (a[i][2] != null) {
	        s += ' ';
	        s += a[i][2];

	        if (a[i][3] != null) {
	          s += ' ';
	          s += a[i][3];
	          s += ' ';
	          s += a[i][4];

	          if (a[i][5] != null) {
	            s += ' ';
	            s += a[i][5];
	            s += ' ';
	            s += a[i][6];

	            if (a[i][7] != null) {
	              s += ' ';
	              s += a[i][7];
	            }
	          }
	        }
	      }
	    }
	  }

	  return s + ' ';
	}

	class PathArray extends SVGArray {
	  // Get bounding box of path
	  bbox() {
	    parser().path.setAttribute('d', this.toString());
	    return new Box(parser.nodes.path.getBBox());
	  } // Move path string


	  move(x, y) {
	    // get bounding box of current situation
	    const box = this.bbox(); // get relative offset

	    x -= box.x;
	    y -= box.y;

	    if (!isNaN(x) && !isNaN(y)) {
	      // move every point
	      for (let l, i = this.length - 1; i >= 0; i--) {
	        l = this[i][0];

	        if (l === 'M' || l === 'L' || l === 'T') {
	          this[i][1] += x;
	          this[i][2] += y;
	        } else if (l === 'H') {
	          this[i][1] += x;
	        } else if (l === 'V') {
	          this[i][1] += y;
	        } else if (l === 'C' || l === 'S' || l === 'Q') {
	          this[i][1] += x;
	          this[i][2] += y;
	          this[i][3] += x;
	          this[i][4] += y;

	          if (l === 'C') {
	            this[i][5] += x;
	            this[i][6] += y;
	          }
	        } else if (l === 'A') {
	          this[i][6] += x;
	          this[i][7] += y;
	        }
	      }
	    }

	    return this;
	  } // Absolutize and parse path to array


	  parse() {
	    let d = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'M0 0';

	    if (Array.isArray(d)) {
	      d = Array.prototype.concat.apply([], d).toString();
	    }

	    return pathParser(d);
	  } // Resize path string


	  size(width, height) {
	    // get bounding box of current situation
	    const box = this.bbox();
	    let i, l; // If the box width or height is 0 then we ignore
	    // transformations on the respective axis

	    box.width = box.width === 0 ? 1 : box.width;
	    box.height = box.height === 0 ? 1 : box.height; // recalculate position of all points according to new size

	    for (i = this.length - 1; i >= 0; i--) {
	      l = this[i][0];

	      if (l === 'M' || l === 'L' || l === 'T') {
	        this[i][1] = (this[i][1] - box.x) * width / box.width + box.x;
	        this[i][2] = (this[i][2] - box.y) * height / box.height + box.y;
	      } else if (l === 'H') {
	        this[i][1] = (this[i][1] - box.x) * width / box.width + box.x;
	      } else if (l === 'V') {
	        this[i][1] = (this[i][1] - box.y) * height / box.height + box.y;
	      } else if (l === 'C' || l === 'S' || l === 'Q') {
	        this[i][1] = (this[i][1] - box.x) * width / box.width + box.x;
	        this[i][2] = (this[i][2] - box.y) * height / box.height + box.y;
	        this[i][3] = (this[i][3] - box.x) * width / box.width + box.x;
	        this[i][4] = (this[i][4] - box.y) * height / box.height + box.y;

	        if (l === 'C') {
	          this[i][5] = (this[i][5] - box.x) * width / box.width + box.x;
	          this[i][6] = (this[i][6] - box.y) * height / box.height + box.y;
	        }
	      } else if (l === 'A') {
	        // resize radii
	        this[i][1] = this[i][1] * width / box.width;
	        this[i][2] = this[i][2] * height / box.height; // move position values

	        this[i][6] = (this[i][6] - box.x) * width / box.width + box.x;
	        this[i][7] = (this[i][7] - box.y) * height / box.height + box.y;
	      }
	    }

	    return this;
	  } // Convert array to string


	  toString() {
	    return arrayToString(this);
	  }

	}

	const getClassForType = value => {
	  const type = typeof value;

	  if (type === 'number') {
	    return SVGNumber;
	  } else if (type === 'string') {
	    if (Color.isColor(value)) {
	      return Color;
	    } else if (delimiter.test(value)) {
	      return isPathLetter.test(value) ? PathArray : SVGArray;
	    } else if (numberAndUnit.test(value)) {
	      return SVGNumber;
	    } else {
	      return NonMorphable;
	    }
	  } else if (morphableTypes.indexOf(value.constructor) > -1) {
	    return value.constructor;
	  } else if (Array.isArray(value)) {
	    return SVGArray;
	  } else if (type === 'object') {
	    return ObjectBag;
	  } else {
	    return NonMorphable;
	  }
	};

	class Morphable {
	  constructor(stepper) {
	    this._stepper = stepper || new Ease('-');
	    this._from = null;
	    this._to = null;
	    this._type = null;
	    this._context = null;
	    this._morphObj = null;
	  }

	  at(pos) {
	    const _this = this;

	    return this._morphObj.fromArray(this._from.map(function (i, index) {
	      return _this._stepper.step(i, _this._to[index], pos, _this._context[index], _this._context);
	    }));
	  }

	  done() {
	    const complete = this._context.map(this._stepper.done).reduce(function (last, curr) {
	      return last && curr;
	    }, true);

	    return complete;
	  }

	  from(val) {
	    if (val == null) {
	      return this._from;
	    }

	    this._from = this._set(val);
	    return this;
	  }

	  stepper(stepper) {
	    if (stepper == null) return this._stepper;
	    this._stepper = stepper;
	    return this;
	  }

	  to(val) {
	    if (val == null) {
	      return this._to;
	    }

	    this._to = this._set(val);
	    return this;
	  }

	  type(type) {
	    // getter
	    if (type == null) {
	      return this._type;
	    } // setter


	    this._type = type;
	    return this;
	  }

	  _set(value) {
	    if (!this._type) {
	      this.type(getClassForType(value));
	    }

	    let result = new this._type(value);

	    if (this._type === Color) {
	      result = this._to ? result[this._to[4]]() : this._from ? result[this._from[4]]() : result;
	    }

	    if (this._type === ObjectBag) {
	      result = this._to ? result.align(this._to) : this._from ? result.align(this._from) : result;
	    }

	    result = result.toArray();
	    this._morphObj = this._morphObj || new this._type();
	    this._context = this._context || Array.apply(null, Array(result.length)).map(Object).map(function (o) {
	      o.done = true;
	      return o;
	    });
	    return result;
	  }

	}

	class NonMorphable {
	  constructor() {
	    this.init(...arguments);
	  }

	  init(val) {
	    val = Array.isArray(val) ? val[0] : val;
	    this.value = val;
	    return this;
	  }

	  toArray() {
	    return [this.value];
	  }

	  valueOf() {
	    return this.value;
	  }

	}

	class TransformBag {
	  constructor() {
	    this.init(...arguments);
	  }

	  init(obj) {
	    if (Array.isArray(obj)) {
	      obj = {
	        scaleX: obj[0],
	        scaleY: obj[1],
	        shear: obj[2],
	        rotate: obj[3],
	        translateX: obj[4],
	        translateY: obj[5],
	        originX: obj[6],
	        originY: obj[7]
	      };
	    }

	    Object.assign(this, TransformBag.defaults, obj);
	    return this;
	  }

	  toArray() {
	    const v = this;
	    return [v.scaleX, v.scaleY, v.shear, v.rotate, v.translateX, v.translateY, v.originX, v.originY];
	  }

	}

	TransformBag.defaults = {
	  scaleX: 1,
	  scaleY: 1,
	  shear: 0,
	  rotate: 0,
	  translateX: 0,
	  translateY: 0,
	  originX: 0,
	  originY: 0
	};

	const sortByKey = (a, b) => {
	  return a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0;
	};

	class ObjectBag {
	  constructor() {
	    this.init(...arguments);
	  }

	  align(other) {
	    for (let i = 0, il = this.values.length; i < il; ++i) {
	      if (this.values[i] === Color) {
	        const space = other[i + 6];
	        const color = new Color(this.values.splice(i + 2, 5))[space]().toArray();
	        this.values.splice(i + 2, 0, ...color);
	      }
	    }

	    return this;
	  }

	  init(objOrArr) {
	    this.values = [];

	    if (Array.isArray(objOrArr)) {
	      this.values = objOrArr.slice();
	      return;
	    }

	    objOrArr = objOrArr || {};
	    const entries = [];

	    for (const i in objOrArr) {
	      const Type = getClassForType(objOrArr[i]);
	      const val = new Type(objOrArr[i]).toArray();
	      entries.push([i, Type, val.length, ...val]);
	    }

	    entries.sort(sortByKey);
	    this.values = entries.reduce((last, curr) => last.concat(curr), []);
	    return this;
	  }

	  toArray() {
	    return this.values;
	  }

	  valueOf() {
	    const obj = {};
	    const arr = this.values; // for (var i = 0, len = arr.length; i < len; i += 2) {

	    while (arr.length) {
	      const key = arr.shift();
	      const Type = arr.shift();
	      const num = arr.shift();
	      const values = arr.splice(0, num);
	      obj[key] = new Type(values).valueOf();
	    }

	    return obj;
	  }

	}

	const morphableTypes = [NonMorphable, TransformBag, ObjectBag];

	function registerMorphableType() {
	  let type = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
	  morphableTypes.push(...[].concat(type));
	}

	function makeMorphable() {
	  extend(morphableTypes, {
	    to(val) {
	      return new Morphable().type(this.constructor).from(this.valueOf()).to(val);
	    },

	    fromArray(arr) {
	      this.init(arr);
	      return this;
	    }

	  });
	}

	class Path extends Shape {
	  // Initialize node
	  constructor(node) {
	    let attrs = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : node;
	    super(nodeOrNew('path', node), attrs);
	  } // Get array


	  array() {
	    return this._array || (this._array = new PathArray(this.attr('d')));
	  } // Clear array cache


	  clear() {
	    delete this._array;
	    return this;
	  } // Set height of element


	  height(height) {
	    return height == null ? this.bbox().height : this.size(this.bbox().width, height);
	  } // Move by left top corner


	  move(x, y) {
	    return this.attr('d', this.array().move(x, y));
	  } // Plot new path


	  plot(d) {
	    return d == null ? this.array() : this.clear().attr('d', typeof d === 'string' ? d : this._array = new PathArray(d));
	  } // Set element size to given width and height


	  size(width, height) {
	    const p = proportionalSize(this, width, height);
	    return this.attr('d', this.array().size(p.width, p.height));
	  } // Set width of element


	  width(width) {
	    return width == null ? this.bbox().width : this.size(width, this.bbox().height);
	  } // Move by left top corner over x-axis


	  x(x) {
	    return x == null ? this.bbox().x : this.move(x, this.bbox().y);
	  } // Move by left top corner over y-axis


	  y(y) {
	    return y == null ? this.bbox().y : this.move(this.bbox().x, y);
	  }

	} // Define morphable array


	Path.prototype.MorphArray = PathArray; // Add parent method

	registerMethods({
	  Container: {
	    // Create a wrapped path element
	    path: wrapWithAttrCheck(function (d) {
	      // make sure plot is called as a setter
	      return this.put(new Path()).plot(d || new PathArray());
	    })
	  }
	});
	register(Path, 'Path');

	function array() {
	  return this._array || (this._array = new PointArray(this.attr('points')));
	} // Clear array cache


	function clear() {
	  delete this._array;
	  return this;
	} // Move by left top corner


	function move$2(x, y) {
	  return this.attr('points', this.array().move(x, y));
	} // Plot new path


	function plot(p) {
	  return p == null ? this.array() : this.clear().attr('points', typeof p === 'string' ? p : this._array = new PointArray(p));
	} // Set element size to given width and height


	function size$1(width, height) {
	  const p = proportionalSize(this, width, height);
	  return this.attr('points', this.array().size(p.width, p.height));
	}

	var poly = {
	  __proto__: null,
	  array: array,
	  clear: clear,
	  move: move$2,
	  plot: plot,
	  size: size$1
	};

	class Polygon extends Shape {
	  // Initialize node
	  constructor(node) {
	    let attrs = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : node;
	    super(nodeOrNew('polygon', node), attrs);
	  }

	}

	registerMethods({
	  Container: {
	    // Create a wrapped polygon element
	    polygon: wrapWithAttrCheck(function (p) {
	      // make sure plot is called as a setter
	      return this.put(new Polygon()).plot(p || new PointArray());
	    })
	  }
	});
	extend(Polygon, pointed);
	extend(Polygon, poly);
	register(Polygon, 'Polygon');

	class Polyline extends Shape {
	  // Initialize node
	  constructor(node) {
	    let attrs = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : node;
	    super(nodeOrNew('polyline', node), attrs);
	  }

	}

	registerMethods({
	  Container: {
	    // Create a wrapped polygon element
	    polyline: wrapWithAttrCheck(function (p) {
	      // make sure plot is called as a setter
	      return this.put(new Polyline()).plot(p || new PointArray());
	    })
	  }
	});
	extend(Polyline, pointed);
	extend(Polyline, poly);
	register(Polyline, 'Polyline');

	class Rect extends Shape {
	  // Initialize node
	  constructor(node) {
	    let attrs = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : node;
	    super(nodeOrNew('rect', node), attrs);
	  }

	}

	extend(Rect, {
	  rx,
	  ry
	});
	registerMethods({
	  Container: {
	    // Create a rect element
	    rect: wrapWithAttrCheck(function (width, height) {
	      return this.put(new Rect()).size(width, height);
	    })
	  }
	});
	register(Rect, 'Rect');

	class Queue {
	  constructor() {
	    this._first = null;
	    this._last = null;
	  } // Shows us the first item in the list


	  first() {
	    return this._first && this._first.value;
	  } // Shows us the last item in the list


	  last() {
	    return this._last && this._last.value;
	  }

	  push(value) {
	    // An item stores an id and the provided value
	    const item = typeof value.next !== 'undefined' ? value : {
	      value: value,
	      next: null,
	      prev: null
	    }; // Deal with the queue being empty or populated

	    if (this._last) {
	      item.prev = this._last;
	      this._last.next = item;
	      this._last = item;
	    } else {
	      this._last = item;
	      this._first = item;
	    } // Return the current item


	    return item;
	  } // Removes the item that was returned from the push


	  remove(item) {
	    // Relink the previous item
	    if (item.prev) item.prev.next = item.next;
	    if (item.next) item.next.prev = item.prev;
	    if (item === this._last) this._last = item.prev;
	    if (item === this._first) this._first = item.next; // Invalidate item

	    item.prev = null;
	    item.next = null;
	  }

	  shift() {
	    // Check if we have a value
	    const remove = this._first;
	    if (!remove) return null; // If we do, remove it and relink things

	    this._first = remove.next;
	    if (this._first) this._first.prev = null;
	    this._last = this._first ? this._last : null;
	    return remove.value;
	  }

	}

	const Animator = {
	  nextDraw: null,
	  frames: new Queue(),
	  timeouts: new Queue(),
	  immediates: new Queue(),
	  timer: () => globals.window.performance || globals.window.Date,
	  transforms: [],

	  frame(fn) {
	    // Store the node
	    const node = Animator.frames.push({
	      run: fn
	    }); // Request an animation frame if we don't have one

	    if (Animator.nextDraw === null) {
	      Animator.nextDraw = globals.window.requestAnimationFrame(Animator._draw);
	    } // Return the node so we can remove it easily


	    return node;
	  },

	  timeout(fn, delay) {
	    delay = delay || 0; // Work out when the event should fire

	    const time = Animator.timer().now() + delay; // Add the timeout to the end of the queue

	    const node = Animator.timeouts.push({
	      run: fn,
	      time: time
	    }); // Request another animation frame if we need one

	    if (Animator.nextDraw === null) {
	      Animator.nextDraw = globals.window.requestAnimationFrame(Animator._draw);
	    }

	    return node;
	  },

	  immediate(fn) {
	    // Add the immediate fn to the end of the queue
	    const node = Animator.immediates.push(fn); // Request another animation frame if we need one

	    if (Animator.nextDraw === null) {
	      Animator.nextDraw = globals.window.requestAnimationFrame(Animator._draw);
	    }

	    return node;
	  },

	  cancelFrame(node) {
	    node != null && Animator.frames.remove(node);
	  },

	  clearTimeout(node) {
	    node != null && Animator.timeouts.remove(node);
	  },

	  cancelImmediate(node) {
	    node != null && Animator.immediates.remove(node);
	  },

	  _draw(now) {
	    // Run all the timeouts we can run, if they are not ready yet, add them
	    // to the end of the queue immediately! (bad timeouts!!! [sarcasm])
	    let nextTimeout = null;
	    const lastTimeout = Animator.timeouts.last();

	    while (nextTimeout = Animator.timeouts.shift()) {
	      // Run the timeout if its time, or push it to the end
	      if (now >= nextTimeout.time) {
	        nextTimeout.run();
	      } else {
	        Animator.timeouts.push(nextTimeout);
	      } // If we hit the last item, we should stop shifting out more items


	      if (nextTimeout === lastTimeout) break;
	    } // Run all of the animation frames


	    let nextFrame = null;
	    const lastFrame = Animator.frames.last();

	    while (nextFrame !== lastFrame && (nextFrame = Animator.frames.shift())) {
	      nextFrame.run(now);
	    }

	    let nextImmediate = null;

	    while (nextImmediate = Animator.immediates.shift()) {
	      nextImmediate();
	    } // If we have remaining timeouts or frames, draw until we don't anymore


	    Animator.nextDraw = Animator.timeouts.first() || Animator.frames.first() ? globals.window.requestAnimationFrame(Animator._draw) : null;
	  }

	};

	const makeSchedule = function (runnerInfo) {
	  const start = runnerInfo.start;
	  const duration = runnerInfo.runner.duration();
	  const end = start + duration;
	  return {
	    start: start,
	    duration: duration,
	    end: end,
	    runner: runnerInfo.runner
	  };
	};

	const defaultSource = function () {
	  const w = globals.window;
	  return (w.performance || w.Date).now();
	};

	class Timeline extends EventTarget {
	  // Construct a new timeline on the given element
	  constructor() {
	    let timeSource = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : defaultSource;
	    super();
	    this._timeSource = timeSource; // Store the timing variables

	    this._startTime = 0;
	    this._speed = 1.0; // Determines how long a runner is hold in memory. Can be a dt or true/false

	    this._persist = 0; // Keep track of the running animations and their starting parameters

	    this._nextFrame = null;
	    this._paused = true;
	    this._runners = [];
	    this._runnerIds = [];
	    this._lastRunnerId = -1;
	    this._time = 0;
	    this._lastSourceTime = 0;
	    this._lastStepTime = 0; // Make sure that step is always called in class context

	    this._step = this._stepFn.bind(this, false);
	    this._stepImmediate = this._stepFn.bind(this, true);
	  }

	  active() {
	    return !!this._nextFrame;
	  }

	  finish() {
	    // Go to end and pause
	    this.time(this.getEndTimeOfTimeline() + 1);
	    return this.pause();
	  } // Calculates the end of the timeline


	  getEndTime() {
	    const lastRunnerInfo = this.getLastRunnerInfo();
	    const lastDuration = lastRunnerInfo ? lastRunnerInfo.runner.duration() : 0;
	    const lastStartTime = lastRunnerInfo ? lastRunnerInfo.start : this._time;
	    return lastStartTime + lastDuration;
	  }

	  getEndTimeOfTimeline() {
	    const endTimes = this._runners.map(i => i.start + i.runner.duration());

	    return Math.max(0, ...endTimes);
	  }

	  getLastRunnerInfo() {
	    return this.getRunnerInfoById(this._lastRunnerId);
	  }

	  getRunnerInfoById(id) {
	    return this._runners[this._runnerIds.indexOf(id)] || null;
	  }

	  pause() {
	    this._paused = true;
	    return this._continue();
	  }

	  persist(dtOrForever) {
	    if (dtOrForever == null) return this._persist;
	    this._persist = dtOrForever;
	    return this;
	  }

	  play() {
	    // Now make sure we are not paused and continue the animation
	    this._paused = false;
	    return this.updateTime()._continue();
	  }

	  reverse(yes) {
	    const currentSpeed = this.speed();
	    if (yes == null) return this.speed(-currentSpeed);
	    const positive = Math.abs(currentSpeed);
	    return this.speed(yes ? -positive : positive);
	  } // schedules a runner on the timeline


	  schedule(runner, delay, when) {
	    if (runner == null) {
	      return this._runners.map(makeSchedule);
	    } // The start time for the next animation can either be given explicitly,
	    // derived from the current timeline time or it can be relative to the
	    // last start time to chain animations directly


	    let absoluteStartTime = 0;
	    const endTime = this.getEndTime();
	    delay = delay || 0; // Work out when to start the animation

	    if (when == null || when === 'last' || when === 'after') {
	      // Take the last time and increment
	      absoluteStartTime = endTime;
	    } else if (when === 'absolute' || when === 'start') {
	      absoluteStartTime = delay;
	      delay = 0;
	    } else if (when === 'now') {
	      absoluteStartTime = this._time;
	    } else if (when === 'relative') {
	      const runnerInfo = this.getRunnerInfoById(runner.id);

	      if (runnerInfo) {
	        absoluteStartTime = runnerInfo.start + delay;
	        delay = 0;
	      }
	    } else if (when === 'with-last') {
	      const lastRunnerInfo = this.getLastRunnerInfo();
	      const lastStartTime = lastRunnerInfo ? lastRunnerInfo.start : this._time;
	      absoluteStartTime = lastStartTime;
	    } else {
	      throw new Error('Invalid value for the "when" parameter');
	    } // Manage runner


	    runner.unschedule();
	    runner.timeline(this);
	    const persist = runner.persist();
	    const runnerInfo = {
	      persist: persist === null ? this._persist : persist,
	      start: absoluteStartTime + delay,
	      runner
	    };
	    this._lastRunnerId = runner.id;

	    this._runners.push(runnerInfo);

	    this._runners.sort((a, b) => a.start - b.start);

	    this._runnerIds = this._runners.map(info => info.runner.id);

	    this.updateTime()._continue();

	    return this;
	  }

	  seek(dt) {
	    return this.time(this._time + dt);
	  }

	  source(fn) {
	    if (fn == null) return this._timeSource;
	    this._timeSource = fn;
	    return this;
	  }

	  speed(speed) {
	    if (speed == null) return this._speed;
	    this._speed = speed;
	    return this;
	  }

	  stop() {
	    // Go to start and pause
	    this.time(0);
	    return this.pause();
	  }

	  time(time) {
	    if (time == null) return this._time;
	    this._time = time;
	    return this._continue(true);
	  } // Remove the runner from this timeline


	  unschedule(runner) {
	    const index = this._runnerIds.indexOf(runner.id);

	    if (index < 0) return this;

	    this._runners.splice(index, 1);

	    this._runnerIds.splice(index, 1);

	    runner.timeline(null);
	    return this;
	  } // Makes sure, that after pausing the time doesn't jump


	  updateTime() {
	    if (!this.active()) {
	      this._lastSourceTime = this._timeSource();
	    }

	    return this;
	  } // Checks if we are running and continues the animation


	  _continue() {
	    let immediateStep = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
	    Animator.cancelFrame(this._nextFrame);
	    this._nextFrame = null;
	    if (immediateStep) return this._stepImmediate();
	    if (this._paused) return this;
	    this._nextFrame = Animator.frame(this._step);
	    return this;
	  }

	  _stepFn() {
	    let immediateStep = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;

	    // Get the time delta from the last time and update the time
	    const time = this._timeSource();

	    let dtSource = time - this._lastSourceTime;
	    if (immediateStep) dtSource = 0;
	    const dtTime = this._speed * dtSource + (this._time - this._lastStepTime);
	    this._lastSourceTime = time; // Only update the time if we use the timeSource.
	    // Otherwise use the current time

	    if (!immediateStep) {
	      // Update the time
	      this._time += dtTime;
	      this._time = this._time < 0 ? 0 : this._time;
	    }

	    this._lastStepTime = this._time;
	    this.fire('time', this._time); // This is for the case that the timeline was seeked so that the time
	    // is now before the startTime of the runner. Thats why we need to set
	    // the runner to position 0
	    // FIXME:
	    // However, reseting in insertion order leads to bugs. Considering the case,
	    // where 2 runners change the same attribute but in different times,
	    // reseting both of them will lead to the case where the later defined
	    // runner always wins the reset even if the other runner started earlier
	    // and therefore should win the attribute battle
	    // this can be solved by reseting them backwards

	    for (let k = this._runners.length; k--;) {
	      // Get and run the current runner and ignore it if its inactive
	      const runnerInfo = this._runners[k];
	      const runner = runnerInfo.runner; // Make sure that we give the actual difference
	      // between runner start time and now

	      const dtToStart = this._time - runnerInfo.start; // Dont run runner if not started yet
	      // and try to reset it

	      if (dtToStart <= 0) {
	        runner.reset();
	      }
	    } // Run all of the runners directly


	    let runnersLeft = false;

	    for (let i = 0, len = this._runners.length; i < len; i++) {
	      // Get and run the current runner and ignore it if its inactive
	      const runnerInfo = this._runners[i];
	      const runner = runnerInfo.runner;
	      let dt = dtTime; // Make sure that we give the actual difference
	      // between runner start time and now

	      const dtToStart = this._time - runnerInfo.start; // Dont run runner if not started yet

	      if (dtToStart <= 0) {
	        runnersLeft = true;
	        continue;
	      } else if (dtToStart < dt) {
	        // Adjust dt to make sure that animation is on point
	        dt = dtToStart;
	      }

	      if (!runner.active()) continue; // If this runner is still going, signal that we need another animation
	      // frame, otherwise, remove the completed runner

	      const finished = runner.step(dt).done;

	      if (!finished) {
	        runnersLeft = true; // continue
	      } else if (runnerInfo.persist !== true) {
	        // runner is finished. And runner might get removed
	        const endTime = runner.duration() - runner.time() + this._time;

	        if (endTime + runnerInfo.persist < this._time) {
	          // Delete runner and correct index
	          runner.unschedule();
	          --i;
	          --len;
	        }
	      }
	    } // Basically: we continue when there are runners right from us in time
	    // when -->, and when runners are left from us when <--


	    if (runnersLeft && !(this._speed < 0 && this._time === 0) || this._runnerIds.length && this._speed < 0 && this._time > 0) {
	      this._continue();
	    } else {
	      this.pause();
	      this.fire('finished');
	    }

	    return this;
	  }

	}

	registerMethods({
	  Element: {
	    timeline: function (timeline) {
	      if (timeline == null) {
	        this._timeline = this._timeline || new Timeline();
	        return this._timeline;
	      } else {
	        this._timeline = timeline;
	        return this;
	      }
	    }
	  }
	});

	class Runner extends EventTarget {
	  constructor(options) {
	    super(); // Store a unique id on the runner, so that we can identify it later

	    this.id = Runner.id++; // Ensure a default value

	    options = options == null ? timeline.duration : options; // Ensure that we get a controller

	    options = typeof options === 'function' ? new Controller(options) : options; // Declare all of the variables

	    this._element = null;
	    this._timeline = null;
	    this.done = false;
	    this._queue = []; // Work out the stepper and the duration

	    this._duration = typeof options === 'number' && options;
	    this._isDeclarative = options instanceof Controller;
	    this._stepper = this._isDeclarative ? options : new Ease(); // We copy the current values from the timeline because they can change

	    this._history = {}; // Store the state of the runner

	    this.enabled = true;
	    this._time = 0;
	    this._lastTime = 0; // At creation, the runner is in reseted state

	    this._reseted = true; // Save transforms applied to this runner

	    this.transforms = new Matrix();
	    this.transformId = 1; // Looping variables

	    this._haveReversed = false;
	    this._reverse = false;
	    this._loopsDone = 0;
	    this._swing = false;
	    this._wait = 0;
	    this._times = 1;
	    this._frameId = null; // Stores how long a runner is stored after beeing done

	    this._persist = this._isDeclarative ? true : null;
	  }

	  static sanitise(duration, delay, when) {
	    // Initialise the default parameters
	    let times = 1;
	    let swing = false;
	    let wait = 0;
	    duration = duration || timeline.duration;
	    delay = delay || timeline.delay;
	    when = when || 'last'; // If we have an object, unpack the values

	    if (typeof duration === 'object' && !(duration instanceof Stepper)) {
	      delay = duration.delay || delay;
	      when = duration.when || when;
	      swing = duration.swing || swing;
	      times = duration.times || times;
	      wait = duration.wait || wait;
	      duration = duration.duration || timeline.duration;
	    }

	    return {
	      duration: duration,
	      delay: delay,
	      swing: swing,
	      times: times,
	      wait: wait,
	      when: when
	    };
	  }

	  active(enabled) {
	    if (enabled == null) return this.enabled;
	    this.enabled = enabled;
	    return this;
	  }
	  /*
	  Private Methods
	  ===============
	  Methods that shouldn't be used externally
	  */


	  addTransform(transform, index) {
	    this.transforms.lmultiplyO(transform);
	    return this;
	  }

	  after(fn) {
	    return this.on('finished', fn);
	  }

	  animate(duration, delay, when) {
	    const o = Runner.sanitise(duration, delay, when);
	    const runner = new Runner(o.duration);
	    if (this._timeline) runner.timeline(this._timeline);
	    if (this._element) runner.element(this._element);
	    return runner.loop(o).schedule(o.delay, o.when);
	  }

	  clearTransform() {
	    this.transforms = new Matrix();
	    return this;
	  } // TODO: Keep track of all transformations so that deletion is faster


	  clearTransformsFromQueue() {
	    if (!this.done || !this._timeline || !this._timeline._runnerIds.includes(this.id)) {
	      this._queue = this._queue.filter(item => {
	        return !item.isTransform;
	      });
	    }
	  }

	  delay(delay) {
	    return this.animate(0, delay);
	  }

	  duration() {
	    return this._times * (this._wait + this._duration) - this._wait;
	  }

	  during(fn) {
	    return this.queue(null, fn);
	  }

	  ease(fn) {
	    this._stepper = new Ease(fn);
	    return this;
	  }
	  /*
	  Runner Definitions
	  ==================
	  These methods help us define the runtime behaviour of the Runner or they
	  help us make new runners from the current runner
	  */


	  element(element) {
	    if (element == null) return this._element;
	    this._element = element;

	    element._prepareRunner();

	    return this;
	  }

	  finish() {
	    return this.step(Infinity);
	  }

	  loop(times, swing, wait) {
	    // Deal with the user passing in an object
	    if (typeof times === 'object') {
	      swing = times.swing;
	      wait = times.wait;
	      times = times.times;
	    } // Sanitise the values and store them


	    this._times = times || Infinity;
	    this._swing = swing || false;
	    this._wait = wait || 0; // Allow true to be passed

	    if (this._times === true) {
	      this._times = Infinity;
	    }

	    return this;
	  }

	  loops(p) {
	    const loopDuration = this._duration + this._wait;

	    if (p == null) {
	      const loopsDone = Math.floor(this._time / loopDuration);
	      const relativeTime = this._time - loopsDone * loopDuration;
	      const position = relativeTime / this._duration;
	      return Math.min(loopsDone + position, this._times);
	    }

	    const whole = Math.floor(p);
	    const partial = p % 1;
	    const time = loopDuration * whole + this._duration * partial;
	    return this.time(time);
	  }

	  persist(dtOrForever) {
	    if (dtOrForever == null) return this._persist;
	    this._persist = dtOrForever;
	    return this;
	  }

	  position(p) {
	    // Get all of the variables we need
	    const x = this._time;
	    const d = this._duration;
	    const w = this._wait;
	    const t = this._times;
	    const s = this._swing;
	    const r = this._reverse;
	    let position;

	    if (p == null) {
	      /*
	      This function converts a time to a position in the range [0, 1]
	      The full explanation can be found in this desmos demonstration
	        https://www.desmos.com/calculator/u4fbavgche
	      The logic is slightly simplified here because we can use booleans
	      */
	      // Figure out the value without thinking about the start or end time
	      const f = function (x) {
	        const swinging = s * Math.floor(x % (2 * (w + d)) / (w + d));
	        const backwards = swinging && !r || !swinging && r;
	        const uncliped = Math.pow(-1, backwards) * (x % (w + d)) / d + backwards;
	        const clipped = Math.max(Math.min(uncliped, 1), 0);
	        return clipped;
	      }; // Figure out the value by incorporating the start time


	      const endTime = t * (w + d) - w;
	      position = x <= 0 ? Math.round(f(1e-5)) : x < endTime ? f(x) : Math.round(f(endTime - 1e-5));
	      return position;
	    } // Work out the loops done and add the position to the loops done


	    const loopsDone = Math.floor(this.loops());
	    const swingForward = s && loopsDone % 2 === 0;
	    const forwards = swingForward && !r || r && swingForward;
	    position = loopsDone + (forwards ? p : 1 - p);
	    return this.loops(position);
	  }

	  progress(p) {
	    if (p == null) {
	      return Math.min(1, this._time / this.duration());
	    }

	    return this.time(p * this.duration());
	  }
	  /*
	  Basic Functionality
	  ===================
	  These methods allow us to attach basic functions to the runner directly
	  */


	  queue(initFn, runFn, retargetFn, isTransform) {
	    this._queue.push({
	      initialiser: initFn || noop,
	      runner: runFn || noop,
	      retarget: retargetFn,
	      isTransform: isTransform,
	      initialised: false,
	      finished: false
	    });

	    const timeline = this.timeline();
	    timeline && this.timeline()._continue();
	    return this;
	  }

	  reset() {
	    if (this._reseted) return this;
	    this.time(0);
	    this._reseted = true;
	    return this;
	  }

	  reverse(reverse) {
	    this._reverse = reverse == null ? !this._reverse : reverse;
	    return this;
	  }

	  schedule(timeline, delay, when) {
	    // The user doesn't need to pass a timeline if we already have one
	    if (!(timeline instanceof Timeline)) {
	      when = delay;
	      delay = timeline;
	      timeline = this.timeline();
	    } // If there is no timeline, yell at the user...


	    if (!timeline) {
	      throw Error('Runner cannot be scheduled without timeline');
	    } // Schedule the runner on the timeline provided


	    timeline.schedule(this, delay, when);
	    return this;
	  }

	  step(dt) {
	    // If we are inactive, this stepper just gets skipped
	    if (!this.enabled) return this; // Update the time and get the new position

	    dt = dt == null ? 16 : dt;
	    this._time += dt;
	    const position = this.position(); // Figure out if we need to run the stepper in this frame

	    const running = this._lastPosition !== position && this._time >= 0;
	    this._lastPosition = position; // Figure out if we just started

	    const duration = this.duration();
	    const justStarted = this._lastTime <= 0 && this._time > 0;
	    const justFinished = this._lastTime < duration && this._time >= duration;
	    this._lastTime = this._time;

	    if (justStarted) {
	      this.fire('start', this);
	    } // Work out if the runner is finished set the done flag here so animations
	    // know, that they are running in the last step (this is good for
	    // transformations which can be merged)


	    const declarative = this._isDeclarative;
	    this.done = !declarative && !justFinished && this._time >= duration; // Runner is running. So its not in reseted state anymore

	    this._reseted = false;
	    let converged = false; // Call initialise and the run function

	    if (running || declarative) {
	      this._initialise(running); // clear the transforms on this runner so they dont get added again and again


	      this.transforms = new Matrix();
	      converged = this._run(declarative ? dt : position);
	      this.fire('step', this);
	    } // correct the done flag here
	    // declaritive animations itself know when they converged


	    this.done = this.done || converged && declarative;

	    if (justFinished) {
	      this.fire('finished', this);
	    }

	    return this;
	  }
	  /*
	  Runner animation methods
	  ========================
	  Control how the animation plays
	  */


	  time(time) {
	    if (time == null) {
	      return this._time;
	    }

	    const dt = time - this._time;
	    this.step(dt);
	    return this;
	  }

	  timeline(timeline) {
	    // check explicitly for undefined so we can set the timeline to null
	    if (typeof timeline === 'undefined') return this._timeline;
	    this._timeline = timeline;
	    return this;
	  }

	  unschedule() {
	    const timeline = this.timeline();
	    timeline && timeline.unschedule(this);
	    return this;
	  } // Run each initialise function in the runner if required


	  _initialise(running) {
	    // If we aren't running, we shouldn't initialise when not declarative
	    if (!running && !this._isDeclarative) return; // Loop through all of the initialisers

	    for (let i = 0, len = this._queue.length; i < len; ++i) {
	      // Get the current initialiser
	      const current = this._queue[i]; // Determine whether we need to initialise

	      const needsIt = this._isDeclarative || !current.initialised && running;
	      running = !current.finished; // Call the initialiser if we need to

	      if (needsIt && running) {
	        current.initialiser.call(this);
	        current.initialised = true;
	      }
	    }
	  } // Save a morpher to the morpher list so that we can retarget it later


	  _rememberMorpher(method, morpher) {
	    this._history[method] = {
	      morpher: morpher,
	      caller: this._queue[this._queue.length - 1]
	    }; // We have to resume the timeline in case a controller
	    // is already done without being ever run
	    // This can happen when e.g. this is done:
	    //    anim = el.animate(new SVG.Spring)
	    // and later
	    //    anim.move(...)

	    if (this._isDeclarative) {
	      const timeline = this.timeline();
	      timeline && timeline.play();
	    }
	  } // Try to set the target for a morpher if the morpher exists, otherwise
	  // Run each run function for the position or dt given


	  _run(positionOrDt) {
	    // Run all of the _queue directly
	    let allfinished = true;

	    for (let i = 0, len = this._queue.length; i < len; ++i) {
	      // Get the current function to run
	      const current = this._queue[i]; // Run the function if its not finished, we keep track of the finished
	      // flag for the sake of declarative _queue

	      const converged = current.runner.call(this, positionOrDt);
	      current.finished = current.finished || converged === true;
	      allfinished = allfinished && current.finished;
	    } // We report when all of the constructors are finished


	    return allfinished;
	  } // do nothing and return false


	  _tryRetarget(method, target, extra) {
	    if (this._history[method]) {
	      // if the last method wasnt even initialised, throw it away
	      if (!this._history[method].caller.initialised) {
	        const index = this._queue.indexOf(this._history[method].caller);

	        this._queue.splice(index, 1);

	        return false;
	      } // for the case of transformations, we use the special retarget function
	      // which has access to the outer scope


	      if (this._history[method].caller.retarget) {
	        this._history[method].caller.retarget.call(this, target, extra); // for everything else a simple morpher change is sufficient

	      } else {
	        this._history[method].morpher.to(target);
	      }

	      this._history[method].caller.finished = false;
	      const timeline = this.timeline();
	      timeline && timeline.play();
	      return true;
	    }

	    return false;
	  }

	}

	Runner.id = 0;

	class FakeRunner {
	  constructor() {
	    let transforms = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : new Matrix();
	    let id = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : -1;
	    let done = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
	    this.transforms = transforms;
	    this.id = id;
	    this.done = done;
	  }

	  clearTransformsFromQueue() {}

	}

	extend([Runner, FakeRunner], {
	  mergeWith(runner) {
	    return new FakeRunner(runner.transforms.lmultiply(this.transforms), runner.id);
	  }

	}); // FakeRunner.emptyRunner = new FakeRunner()

	const lmultiply = (last, curr) => last.lmultiplyO(curr);

	const getRunnerTransform = runner => runner.transforms;

	function mergeTransforms() {
	  // Find the matrix to apply to the element and apply it
	  const runners = this._transformationRunners.runners;
	  const netTransform = runners.map(getRunnerTransform).reduce(lmultiply, new Matrix());
	  this.transform(netTransform);

	  this._transformationRunners.merge();

	  if (this._transformationRunners.length() === 1) {
	    this._frameId = null;
	  }
	}

	class RunnerArray {
	  constructor() {
	    this.runners = [];
	    this.ids = [];
	  }

	  add(runner) {
	    if (this.runners.includes(runner)) return;
	    const id = runner.id + 1;
	    this.runners.push(runner);
	    this.ids.push(id);
	    return this;
	  }

	  clearBefore(id) {
	    const deleteCnt = this.ids.indexOf(id + 1) || 1;
	    this.ids.splice(0, deleteCnt, 0);
	    this.runners.splice(0, deleteCnt, new FakeRunner()).forEach(r => r.clearTransformsFromQueue());
	    return this;
	  }

	  edit(id, newRunner) {
	    const index = this.ids.indexOf(id + 1);
	    this.ids.splice(index, 1, id + 1);
	    this.runners.splice(index, 1, newRunner);
	    return this;
	  }

	  getByID(id) {
	    return this.runners[this.ids.indexOf(id + 1)];
	  }

	  length() {
	    return this.ids.length;
	  }

	  merge() {
	    let lastRunner = null;

	    for (let i = 0; i < this.runners.length; ++i) {
	      const runner = this.runners[i];
	      const condition = lastRunner && runner.done && lastRunner.done // don't merge runner when persisted on timeline
	      && (!runner._timeline || !runner._timeline._runnerIds.includes(runner.id)) && (!lastRunner._timeline || !lastRunner._timeline._runnerIds.includes(lastRunner.id));

	      if (condition) {
	        // the +1 happens in the function
	        this.remove(runner.id);
	        const newRunner = runner.mergeWith(lastRunner);
	        this.edit(lastRunner.id, newRunner);
	        lastRunner = newRunner;
	        --i;
	      } else {
	        lastRunner = runner;
	      }
	    }

	    return this;
	  }

	  remove(id) {
	    const index = this.ids.indexOf(id + 1);
	    this.ids.splice(index, 1);
	    this.runners.splice(index, 1);
	    return this;
	  }

	}

	registerMethods({
	  Element: {
	    animate(duration, delay, when) {
	      const o = Runner.sanitise(duration, delay, when);
	      const timeline = this.timeline();
	      return new Runner(o.duration).loop(o).element(this).timeline(timeline.play()).schedule(o.delay, o.when);
	    },

	    delay(by, when) {
	      return this.animate(0, by, when);
	    },

	    // this function searches for all runners on the element and deletes the ones
	    // which run before the current one. This is because absolute transformations
	    // overwfrite anything anyway so there is no need to waste time computing
	    // other runners
	    _clearTransformRunnersBefore(currentRunner) {
	      this._transformationRunners.clearBefore(currentRunner.id);
	    },

	    _currentTransform(current) {
	      return this._transformationRunners.runners // we need the equal sign here to make sure, that also transformations
	      // on the same runner which execute before the current transformation are
	      // taken into account
	      .filter(runner => runner.id <= current.id).map(getRunnerTransform).reduce(lmultiply, new Matrix());
	    },

	    _addRunner(runner) {
	      this._transformationRunners.add(runner); // Make sure that the runner merge is executed at the very end of
	      // all Animator functions. Thats why we use immediate here to execute
	      // the merge right after all frames are run


	      Animator.cancelImmediate(this._frameId);
	      this._frameId = Animator.immediate(mergeTransforms.bind(this));
	    },

	    _prepareRunner() {
	      if (this._frameId == null) {
	        this._transformationRunners = new RunnerArray().add(new FakeRunner(new Matrix(this)));
	      }
	    }

	  }
	}); // Will output the elements from array A that are not in the array B

	const difference = (a, b) => a.filter(x => !b.includes(x));

	extend(Runner, {
	  attr(a, v) {
	    return this.styleAttr('attr', a, v);
	  },

	  // Add animatable styles
	  css(s, v) {
	    return this.styleAttr('css', s, v);
	  },

	  styleAttr(type, nameOrAttrs, val) {
	    if (typeof nameOrAttrs === 'string') {
	      return this.styleAttr(type, {
	        [nameOrAttrs]: val
	      });
	    }

	    let attrs = nameOrAttrs;
	    if (this._tryRetarget(type, attrs)) return this;
	    let morpher = new Morphable(this._stepper).to(attrs);
	    let keys = Object.keys(attrs);
	    this.queue(function () {
	      morpher = morpher.from(this.element()[type](keys));
	    }, function (pos) {
	      this.element()[type](morpher.at(pos).valueOf());
	      return morpher.done();
	    }, function (newToAttrs) {
	      // Check if any new keys were added
	      const newKeys = Object.keys(newToAttrs);
	      const differences = difference(newKeys, keys); // If their are new keys, initialize them and add them to morpher

	      if (differences.length) {
	        // Get the values
	        const addedFromAttrs = this.element()[type](differences); // Get the already initialized values

	        const oldFromAttrs = new ObjectBag(morpher.from()).valueOf(); // Merge old and new

	        Object.assign(oldFromAttrs, addedFromAttrs);
	        morpher.from(oldFromAttrs);
	      } // Get the object from the morpher


	      const oldToAttrs = new ObjectBag(morpher.to()).valueOf(); // Merge in new attributes

	      Object.assign(oldToAttrs, newToAttrs); // Change morpher target

	      morpher.to(oldToAttrs); // Make sure that we save the work we did so we don't need it to do again

	      keys = newKeys;
	      attrs = newToAttrs;
	    });

	    this._rememberMorpher(type, morpher);

	    return this;
	  },

	  zoom(level, point) {
	    if (this._tryRetarget('zoom', level, point)) return this;
	    let morpher = new Morphable(this._stepper).to(new SVGNumber(level));
	    this.queue(function () {
	      morpher = morpher.from(this.element().zoom());
	    }, function (pos) {
	      this.element().zoom(morpher.at(pos), point);
	      return morpher.done();
	    }, function (newLevel, newPoint) {
	      point = newPoint;
	      morpher.to(newLevel);
	    });

	    this._rememberMorpher('zoom', morpher);

	    return this;
	  },

	  /**
	   ** absolute transformations
	   **/
	  //
	  // M v -----|-----(D M v = F v)------|----->  T v
	  //
	  // 1. define the final state (T) and decompose it (once)
	  //    t = [tx, ty, the, lam, sy, sx]
	  // 2. on every frame: pull the current state of all previous transforms
	  //    (M - m can change)
	  //   and then write this as m = [tx0, ty0, the0, lam0, sy0, sx0]
	  // 3. Find the interpolated matrix F(pos) = m + pos * (t - m)
	  //   - Note F(0) = M
	  //   - Note F(1) = T
	  // 4. Now you get the delta matrix as a result: D = F * inv(M)
	  transform(transforms, relative, affine) {
	    // If we have a declarative function, we should retarget it if possible
	    relative = transforms.relative || relative;

	    if (this._isDeclarative && !relative && this._tryRetarget('transform', transforms)) {
	      return this;
	    } // Parse the parameters


	    const isMatrix = Matrix.isMatrixLike(transforms);
	    affine = transforms.affine != null ? transforms.affine : affine != null ? affine : !isMatrix; // Create a morepher and set its type

	    const morpher = new Morphable(this._stepper).type(affine ? TransformBag : Matrix);
	    let origin;
	    let element;
	    let current;
	    let currentAngle;
	    let startTransform;

	    function setup() {
	      // make sure element and origin is defined
	      element = element || this.element();
	      origin = origin || getOrigin(transforms, element);
	      startTransform = new Matrix(relative ? undefined : element); // add the runner to the element so it can merge transformations

	      element._addRunner(this); // Deactivate all transforms that have run so far if we are absolute


	      if (!relative) {
	        element._clearTransformRunnersBefore(this);
	      }
	    }

	    function run(pos) {
	      // clear all other transforms before this in case something is saved
	      // on this runner. We are absolute. We dont need these!
	      if (!relative) this.clearTransform();
	      const {
	        x,
	        y
	      } = new Point(origin).transform(element._currentTransform(this));
	      let target = new Matrix({ ...transforms,
	        origin: [x, y]
	      });
	      let start = this._isDeclarative && current ? current : startTransform;

	      if (affine) {
	        target = target.decompose(x, y);
	        start = start.decompose(x, y); // Get the current and target angle as it was set

	        const rTarget = target.rotate;
	        const rCurrent = start.rotate; // Figure out the shortest path to rotate directly

	        const possibilities = [rTarget - 360, rTarget, rTarget + 360];
	        const distances = possibilities.map(a => Math.abs(a - rCurrent));
	        const shortest = Math.min(...distances);
	        const index = distances.indexOf(shortest);
	        target.rotate = possibilities[index];
	      }

	      if (relative) {
	        // we have to be careful here not to overwrite the rotation
	        // with the rotate method of Matrix
	        if (!isMatrix) {
	          target.rotate = transforms.rotate || 0;
	        }

	        if (this._isDeclarative && currentAngle) {
	          start.rotate = currentAngle;
	        }
	      }

	      morpher.from(start);
	      morpher.to(target);
	      const affineParameters = morpher.at(pos);
	      currentAngle = affineParameters.rotate;
	      current = new Matrix(affineParameters);
	      this.addTransform(current);

	      element._addRunner(this);

	      return morpher.done();
	    }

	    function retarget(newTransforms) {
	      // only get a new origin if it changed since the last call
	      if ((newTransforms.origin || 'center').toString() !== (transforms.origin || 'center').toString()) {
	        origin = getOrigin(newTransforms, element);
	      } // overwrite the old transformations with the new ones


	      transforms = { ...newTransforms,
	        origin
	      };
	    }

	    this.queue(setup, run, retarget, true);
	    this._isDeclarative && this._rememberMorpher('transform', morpher);
	    return this;
	  },

	  // Animatable x-axis
	  x(x, relative) {
	    return this._queueNumber('x', x);
	  },

	  // Animatable y-axis
	  y(y) {
	    return this._queueNumber('y', y);
	  },

	  dx() {
	    let x = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
	    return this._queueNumberDelta('x', x);
	  },

	  dy() {
	    let y = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
	    return this._queueNumberDelta('y', y);
	  },

	  dmove(x, y) {
	    return this.dx(x).dy(y);
	  },

	  _queueNumberDelta(method, to) {
	    to = new SVGNumber(to); // Try to change the target if we have this method already registerd

	    if (this._tryRetarget(method, to)) return this; // Make a morpher and queue the animation

	    const morpher = new Morphable(this._stepper).to(to);
	    let from = null;
	    this.queue(function () {
	      from = this.element()[method]();
	      morpher.from(from);
	      morpher.to(from + to);
	    }, function (pos) {
	      this.element()[method](morpher.at(pos));
	      return morpher.done();
	    }, function (newTo) {
	      morpher.to(from + new SVGNumber(newTo));
	    }); // Register the morpher so that if it is changed again, we can retarget it

	    this._rememberMorpher(method, morpher);

	    return this;
	  },

	  _queueObject(method, to) {
	    // Try to change the target if we have this method already registerd
	    if (this._tryRetarget(method, to)) return this; // Make a morpher and queue the animation

	    const morpher = new Morphable(this._stepper).to(to);
	    this.queue(function () {
	      morpher.from(this.element()[method]());
	    }, function (pos) {
	      this.element()[method](morpher.at(pos));
	      return morpher.done();
	    }); // Register the morpher so that if it is changed again, we can retarget it

	    this._rememberMorpher(method, morpher);

	    return this;
	  },

	  _queueNumber(method, value) {
	    return this._queueObject(method, new SVGNumber(value));
	  },

	  // Animatable center x-axis
	  cx(x) {
	    return this._queueNumber('cx', x);
	  },

	  // Animatable center y-axis
	  cy(y) {
	    return this._queueNumber('cy', y);
	  },

	  // Add animatable move
	  move(x, y) {
	    return this.x(x).y(y);
	  },

	  // Add animatable center
	  center(x, y) {
	    return this.cx(x).cy(y);
	  },

	  // Add animatable size
	  size(width, height) {
	    // animate bbox based size for all other elements
	    let box;

	    if (!width || !height) {
	      box = this._element.bbox();
	    }

	    if (!width) {
	      width = box.width / box.height * height;
	    }

	    if (!height) {
	      height = box.height / box.width * width;
	    }

	    return this.width(width).height(height);
	  },

	  // Add animatable width
	  width(width) {
	    return this._queueNumber('width', width);
	  },

	  // Add animatable height
	  height(height) {
	    return this._queueNumber('height', height);
	  },

	  // Add animatable plot
	  plot(a, b, c, d) {
	    // Lines can be plotted with 4 arguments
	    if (arguments.length === 4) {
	      return this.plot([a, b, c, d]);
	    }

	    if (this._tryRetarget('plot', a)) return this;
	    const morpher = new Morphable(this._stepper).type(this._element.MorphArray).to(a);
	    this.queue(function () {
	      morpher.from(this._element.array());
	    }, function (pos) {
	      this._element.plot(morpher.at(pos));

	      return morpher.done();
	    });

	    this._rememberMorpher('plot', morpher);

	    return this;
	  },

	  // Add leading method
	  leading(value) {
	    return this._queueNumber('leading', value);
	  },

	  // Add animatable viewbox
	  viewbox(x, y, width, height) {
	    return this._queueObject('viewbox', new Box(x, y, width, height));
	  },

	  update(o) {
	    if (typeof o !== 'object') {
	      return this.update({
	        offset: arguments[0],
	        color: arguments[1],
	        opacity: arguments[2]
	      });
	    }

	    if (o.opacity != null) this.attr('stop-opacity', o.opacity);
	    if (o.color != null) this.attr('stop-color', o.color);
	    if (o.offset != null) this.attr('offset', o.offset);
	    return this;
	  }

	});
	extend(Runner, {
	  rx,
	  ry,
	  from,
	  to
	});
	register(Runner, 'Runner');

	class Svg extends Container {
	  constructor(node) {
	    let attrs = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : node;
	    super(nodeOrNew('svg', node), attrs);
	    this.namespace();
	  } // Creates and returns defs element


	  defs() {
	    if (!this.isRoot()) return this.root().defs();
	    return adopt(this.node.querySelector('defs')) || this.put(new Defs());
	  }

	  isRoot() {
	    return !this.node.parentNode || !(this.node.parentNode instanceof globals.window.SVGElement) && this.node.parentNode.nodeName !== '#document-fragment';
	  } // Add namespaces


	  namespace() {
	    if (!this.isRoot()) return this.root().namespace();
	    return this.attr({
	      xmlns: svg,
	      version: '1.1'
	    }).attr('xmlns:xlink', xlink, xmlns).attr('xmlns:svgjs', svgjs, xmlns);
	  }

	  removeNamespace() {
	    return this.attr({
	      xmlns: null,
	      version: null
	    }).attr('xmlns:xlink', null, xmlns).attr('xmlns:svgjs', null, xmlns);
	  } // Check if this is a root svg
	  // If not, call root() from this element


	  root() {
	    if (this.isRoot()) return this;
	    return super.root();
	  }

	}

	registerMethods({
	  Container: {
	    // Create nested svg document
	    nested: wrapWithAttrCheck(function () {
	      return this.put(new Svg());
	    })
	  }
	});
	register(Svg, 'Svg', true);

	class Symbol$1 extends Container {
	  // Initialize node
	  constructor(node) {
	    let attrs = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : node;
	    super(nodeOrNew('symbol', node), attrs);
	  }

	}

	registerMethods({
	  Container: {
	    symbol: wrapWithAttrCheck(function () {
	      return this.put(new Symbol$1());
	    })
	  }
	});
	register(Symbol$1, 'Symbol');

	function plain(text) {
	  // clear if build mode is disabled
	  if (this._build === false) {
	    this.clear();
	  } // create text node


	  this.node.appendChild(globals.document.createTextNode(text));
	  return this;
	} // Get length of text element


	function length() {
	  return this.node.getComputedTextLength();
	} // Move over x-axis
	// Text is moved by its bounding box
	// text-anchor does NOT matter


	function x$1(x) {
	  let box = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.bbox();

	  if (x == null) {
	    return box.x;
	  }

	  return this.attr('x', this.attr('x') + x - box.x);
	} // Move over y-axis


	function y$1(y) {
	  let box = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.bbox();

	  if (y == null) {
	    return box.y;
	  }

	  return this.attr('y', this.attr('y') + y - box.y);
	}

	function move$1(x, y) {
	  let box = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : this.bbox();
	  return this.x(x, box).y(y, box);
	} // Move center over x-axis


	function cx(x) {
	  let box = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.bbox();

	  if (x == null) {
	    return box.cx;
	  }

	  return this.attr('x', this.attr('x') + x - box.cx);
	} // Move center over y-axis


	function cy(y) {
	  let box = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.bbox();

	  if (y == null) {
	    return box.cy;
	  }

	  return this.attr('y', this.attr('y') + y - box.cy);
	}

	function center(x, y) {
	  let box = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : this.bbox();
	  return this.cx(x, box).cy(y, box);
	}

	function ax(x) {
	  return this.attr('x', x);
	}

	function ay(y) {
	  return this.attr('y', y);
	}

	function amove(x, y) {
	  return this.ax(x).ay(y);
	} // Enable / disable build mode


	function build(build) {
	  this._build = !!build;
	  return this;
	}

	var textable = {
	  __proto__: null,
	  plain: plain,
	  length: length,
	  x: x$1,
	  y: y$1,
	  move: move$1,
	  cx: cx,
	  cy: cy,
	  center: center,
	  ax: ax,
	  ay: ay,
	  amove: amove,
	  build: build
	};

	class Text extends Shape {
	  // Initialize node
	  constructor(node) {
	    let attrs = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : node;
	    super(nodeOrNew('text', node), attrs);
	    this.dom.leading = new SVGNumber(1.3); // store leading value for rebuilding

	    this._rebuild = true; // enable automatic updating of dy values

	    this._build = false; // disable build mode for adding multiple lines
	  } // Set / get leading


	  leading(value) {
	    // act as getter
	    if (value == null) {
	      return this.dom.leading;
	    } // act as setter


	    this.dom.leading = new SVGNumber(value);
	    return this.rebuild();
	  } // Rebuild appearance type


	  rebuild(rebuild) {
	    // store new rebuild flag if given
	    if (typeof rebuild === 'boolean') {
	      this._rebuild = rebuild;
	    } // define position of all lines


	    if (this._rebuild) {
	      const self = this;
	      let blankLineOffset = 0;
	      const leading = this.dom.leading;
	      this.each(function (i) {
	        const fontSize = globals.window.getComputedStyle(this.node).getPropertyValue('font-size');
	        const dy = leading * new SVGNumber(fontSize);

	        if (this.dom.newLined) {
	          this.attr('x', self.attr('x'));

	          if (this.text() === '\n') {
	            blankLineOffset += dy;
	          } else {
	            this.attr('dy', i ? dy + blankLineOffset : 0);
	            blankLineOffset = 0;
	          }
	        }
	      });
	      this.fire('rebuild');
	    }

	    return this;
	  } // overwrite method from parent to set data properly


	  setData(o) {
	    this.dom = o;
	    this.dom.leading = new SVGNumber(o.leading || 1.3);
	    return this;
	  } // Set the text content


	  text(text) {
	    // act as getter
	    if (text === undefined) {
	      const children = this.node.childNodes;
	      let firstLine = 0;
	      text = '';

	      for (let i = 0, len = children.length; i < len; ++i) {
	        // skip textPaths - they are no lines
	        if (children[i].nodeName === 'textPath') {
	          if (i === 0) firstLine = 1;
	          continue;
	        } // add newline if its not the first child and newLined is set to true


	        if (i !== firstLine && children[i].nodeType !== 3 && adopt(children[i]).dom.newLined === true) {
	          text += '\n';
	        } // add content of this node


	        text += children[i].textContent;
	      }

	      return text;
	    } // remove existing content


	    this.clear().build(true);

	    if (typeof text === 'function') {
	      // call block
	      text.call(this, this);
	    } else {
	      // store text and make sure text is not blank
	      text = (text + '').split('\n'); // build new lines

	      for (let j = 0, jl = text.length; j < jl; j++) {
	        this.newLine(text[j]);
	      }
	    } // disable build mode and rebuild lines


	    return this.build(false).rebuild();
	  }

	}

	extend(Text, textable);
	registerMethods({
	  Container: {
	    // Create text element
	    text: wrapWithAttrCheck(function () {
	      let text = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
	      return this.put(new Text()).text(text);
	    }),
	    // Create plain text element
	    plain: wrapWithAttrCheck(function () {
	      let text = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
	      return this.put(new Text()).plain(text);
	    })
	  }
	});
	register(Text, 'Text');

	class Tspan extends Shape {
	  // Initialize node
	  constructor(node) {
	    let attrs = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : node;
	    super(nodeOrNew('tspan', node), attrs);
	    this._build = false; // disable build mode for adding multiple lines
	  } // Shortcut dx


	  dx(dx) {
	    return this.attr('dx', dx);
	  } // Shortcut dy


	  dy(dy) {
	    return this.attr('dy', dy);
	  } // Create new line


	  newLine() {
	    // mark new line
	    this.dom.newLined = true; // fetch parent

	    const text = this.parent(); // early return in case we are not in a text element

	    if (!(text instanceof Text)) {
	      return this;
	    }

	    const i = text.index(this);
	    const fontSize = globals.window.getComputedStyle(this.node).getPropertyValue('font-size');
	    const dy = text.dom.leading * new SVGNumber(fontSize); // apply new position

	    return this.dy(i ? dy : 0).attr('x', text.x());
	  } // Set text content


	  text(text) {
	    if (text == null) return this.node.textContent + (this.dom.newLined ? '\n' : '');

	    if (typeof text === 'function') {
	      this.clear().build(true);
	      text.call(this, this);
	      this.build(false);
	    } else {
	      this.plain(text);
	    }

	    return this;
	  }

	}

	extend(Tspan, textable);
	registerMethods({
	  Tspan: {
	    tspan: wrapWithAttrCheck(function () {
	      let text = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
	      const tspan = new Tspan(); // clear if build mode is disabled

	      if (!this._build) {
	        this.clear();
	      } // add new tspan


	      return this.put(tspan).text(text);
	    })
	  },
	  Text: {
	    newLine: function () {
	      let text = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
	      return this.tspan(text).newLine();
	    }
	  }
	});
	register(Tspan, 'Tspan');

	class Circle extends Shape {
	  constructor(node) {
	    let attrs = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : node;
	    super(nodeOrNew('circle', node), attrs);
	  }

	  radius(r) {
	    return this.attr('r', r);
	  } // Radius x value


	  rx(rx) {
	    return this.attr('r', rx);
	  } // Alias radius x value


	  ry(ry) {
	    return this.rx(ry);
	  }

	  size(size) {
	    return this.radius(new SVGNumber(size).divide(2));
	  }

	}

	extend(Circle, {
	  x: x$3,
	  y: y$3,
	  cx: cx$1,
	  cy: cy$1,
	  width: width$2,
	  height: height$2
	});
	registerMethods({
	  Container: {
	    // Create circle element
	    circle: wrapWithAttrCheck(function () {
	      let size = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
	      return this.put(new Circle()).size(size).move(0, 0);
	    })
	  }
	});
	register(Circle, 'Circle');

	class ClipPath extends Container {
	  constructor(node) {
	    let attrs = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : node;
	    super(nodeOrNew('clipPath', node), attrs);
	  } // Unclip all clipped elements and remove itself


	  remove() {
	    // unclip all targets
	    this.targets().forEach(function (el) {
	      el.unclip();
	    }); // remove clipPath from parent

	    return super.remove();
	  }

	  targets() {
	    return baseFind('svg [clip-path*="' + this.id() + '"]');
	  }

	}

	registerMethods({
	  Container: {
	    // Create clipping element
	    clip: wrapWithAttrCheck(function () {
	      return this.defs().put(new ClipPath());
	    })
	  },
	  Element: {
	    // Distribute clipPath to svg element
	    clipper() {
	      return this.reference('clip-path');
	    },

	    clipWith(element) {
	      // use given clip or create a new one
	      const clipper = element instanceof ClipPath ? element : this.parent().clip().add(element); // apply mask

	      return this.attr('clip-path', 'url("#' + clipper.id() + '")');
	    },

	    // Unclip element
	    unclip() {
	      return this.attr('clip-path', null);
	    }

	  }
	});
	register(ClipPath, 'ClipPath');

	class ForeignObject extends Element {
	  constructor(node) {
	    let attrs = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : node;
	    super(nodeOrNew('foreignObject', node), attrs);
	  }

	}

	registerMethods({
	  Container: {
	    foreignObject: wrapWithAttrCheck(function (width, height) {
	      return this.put(new ForeignObject()).size(width, height);
	    })
	  }
	});
	register(ForeignObject, 'ForeignObject');

	function dmove(dx, dy) {
	  this.children().forEach((child, i) => {
	    let bbox; // We have to wrap this for elements that dont have a bbox
	    // e.g. title and other descriptive elements

	    try {
	      // Get the childs bbox
	      bbox = child.bbox();
	    } catch (e) {
	      return;
	    } // Get childs matrix


	    const m = new Matrix(child); // Translate childs matrix by amount and
	    // transform it back into parents space

	    const matrix = m.translate(dx, dy).transform(m.inverse()); // Calculate new x and y from old box

	    const p = new Point(bbox.x, bbox.y).transform(matrix); // Move element

	    child.move(p.x, p.y);
	  });
	  return this;
	}

	function dx(dx) {
	  return this.dmove(dx, 0);
	}

	function dy(dy) {
	  return this.dmove(0, dy);
	}

	function height(height) {
	  let box = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.bbox();
	  if (height == null) return box.height;
	  return this.size(box.width, height, box);
	}

	function move() {
	  let x = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
	  let y = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
	  let box = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : this.bbox();
	  const dx = x - box.x;
	  const dy = y - box.y;
	  return this.dmove(dx, dy);
	}

	function size(width, height) {
	  let box = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : this.bbox();
	  const p = proportionalSize(this, width, height, box);
	  const scaleX = p.width / box.width;
	  const scaleY = p.height / box.height;
	  this.children().forEach((child, i) => {
	    const o = new Point(box).transform(new Matrix(child).inverse());
	    child.scale(scaleX, scaleY, o.x, o.y);
	  });
	  return this;
	}

	function width(width) {
	  let box = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.bbox();
	  if (width == null) return box.width;
	  return this.size(width, box.height, box);
	}

	function x(x) {
	  let box = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.bbox();
	  if (x == null) return box.x;
	  return this.move(x, box.y, box);
	}

	function y(y) {
	  let box = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.bbox();
	  if (y == null) return box.y;
	  return this.move(box.x, y, box);
	}

	var containerGeometry = {
	  __proto__: null,
	  dmove: dmove,
	  dx: dx,
	  dy: dy,
	  height: height,
	  move: move,
	  size: size,
	  width: width,
	  x: x,
	  y: y
	};

	class G extends Container {
	  constructor(node) {
	    let attrs = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : node;
	    super(nodeOrNew('g', node), attrs);
	  }

	}

	extend(G, containerGeometry);
	registerMethods({
	  Container: {
	    // Create a group element
	    group: wrapWithAttrCheck(function () {
	      return this.put(new G());
	    })
	  }
	});
	register(G, 'G');

	class A extends Container {
	  constructor(node) {
	    let attrs = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : node;
	    super(nodeOrNew('a', node), attrs);
	  } // Link target attribute


	  target(target) {
	    return this.attr('target', target);
	  } // Link url


	  to(url) {
	    return this.attr('href', url, xlink);
	  }

	}

	extend(A, containerGeometry);
	registerMethods({
	  Container: {
	    // Create a hyperlink element
	    link: wrapWithAttrCheck(function (url) {
	      return this.put(new A()).to(url);
	    })
	  },
	  Element: {
	    unlink() {
	      const link = this.linker();
	      if (!link) return this;
	      const parent = link.parent();

	      if (!parent) {
	        return this.remove();
	      }

	      const index = parent.index(link);
	      parent.add(this, index);
	      link.remove();
	      return this;
	    },

	    linkTo(url) {
	      // reuse old link if possible
	      let link = this.linker();

	      if (!link) {
	        link = new A();
	        this.wrap(link);
	      }

	      if (typeof url === 'function') {
	        url.call(link, link);
	      } else {
	        link.to(url);
	      }

	      return this;
	    },

	    linker() {
	      const link = this.parent();

	      if (link && link.node.nodeName.toLowerCase() === 'a') {
	        return link;
	      }

	      return null;
	    }

	  }
	});
	register(A, 'A');

	class Mask extends Container {
	  // Initialize node
	  constructor(node) {
	    let attrs = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : node;
	    super(nodeOrNew('mask', node), attrs);
	  } // Unmask all masked elements and remove itself


	  remove() {
	    // unmask all targets
	    this.targets().forEach(function (el) {
	      el.unmask();
	    }); // remove mask from parent

	    return super.remove();
	  }

	  targets() {
	    return baseFind('svg [mask*="' + this.id() + '"]');
	  }

	}

	registerMethods({
	  Container: {
	    mask: wrapWithAttrCheck(function () {
	      return this.defs().put(new Mask());
	    })
	  },
	  Element: {
	    // Distribute mask to svg element
	    masker() {
	      return this.reference('mask');
	    },

	    maskWith(element) {
	      // use given mask or create a new one
	      const masker = element instanceof Mask ? element : this.parent().mask().add(element); // apply mask

	      return this.attr('mask', 'url("#' + masker.id() + '")');
	    },

	    // Unmask element
	    unmask() {
	      return this.attr('mask', null);
	    }

	  }
	});
	register(Mask, 'Mask');

	class Stop extends Element {
	  constructor(node) {
	    let attrs = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : node;
	    super(nodeOrNew('stop', node), attrs);
	  } // add color stops


	  update(o) {
	    if (typeof o === 'number' || o instanceof SVGNumber) {
	      o = {
	        offset: arguments[0],
	        color: arguments[1],
	        opacity: arguments[2]
	      };
	    } // set attributes


	    if (o.opacity != null) this.attr('stop-opacity', o.opacity);
	    if (o.color != null) this.attr('stop-color', o.color);
	    if (o.offset != null) this.attr('offset', new SVGNumber(o.offset));
	    return this;
	  }

	}

	registerMethods({
	  Gradient: {
	    // Add a color stop
	    stop: function (offset, color, opacity) {
	      return this.put(new Stop()).update(offset, color, opacity);
	    }
	  }
	});
	register(Stop, 'Stop');

	function cssRule(selector, rule) {
	  if (!selector) return '';
	  if (!rule) return selector;
	  let ret = selector + '{';

	  for (const i in rule) {
	    ret += unCamelCase(i) + ':' + rule[i] + ';';
	  }

	  ret += '}';
	  return ret;
	}

	class Style extends Element {
	  constructor(node) {
	    let attrs = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : node;
	    super(nodeOrNew('style', node), attrs);
	  }

	  addText() {
	    let w = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
	    this.node.textContent += w;
	    return this;
	  }

	  font(name, src) {
	    let params = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
	    return this.rule('@font-face', {
	      fontFamily: name,
	      src: src,
	      ...params
	    });
	  }

	  rule(selector, obj) {
	    return this.addText(cssRule(selector, obj));
	  }

	}

	registerMethods('Dom', {
	  style(selector, obj) {
	    return this.put(new Style()).rule(selector, obj);
	  },

	  fontface(name, src, params) {
	    return this.put(new Style()).font(name, src, params);
	  }

	});
	register(Style, 'Style');

	class TextPath extends Text {
	  // Initialize node
	  constructor(node) {
	    let attrs = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : node;
	    super(nodeOrNew('textPath', node), attrs);
	  } // return the array of the path track element


	  array() {
	    const track = this.track();
	    return track ? track.array() : null;
	  } // Plot path if any


	  plot(d) {
	    const track = this.track();
	    let pathArray = null;

	    if (track) {
	      pathArray = track.plot(d);
	    }

	    return d == null ? pathArray : this;
	  } // Get the path element


	  track() {
	    return this.reference('href');
	  }

	}

	registerMethods({
	  Container: {
	    textPath: wrapWithAttrCheck(function (text, path) {
	      // Convert text to instance if needed
	      if (!(text instanceof Text)) {
	        text = this.text(text);
	      }

	      return text.path(path);
	    })
	  },
	  Text: {
	    // Create path for text to run on
	    path: wrapWithAttrCheck(function (track) {
	      let importNodes = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
	      const textPath = new TextPath(); // if track is a path, reuse it

	      if (!(track instanceof Path)) {
	        // create path element
	        track = this.defs().path(track);
	      } // link textPath to path and add content


	      textPath.attr('href', '#' + track, xlink); // Transplant all nodes from text to textPath

	      let node;

	      if (importNodes) {
	        while (node = this.node.firstChild) {
	          textPath.node.appendChild(node);
	        }
	      } // add textPath element as child node and return textPath


	      return this.put(textPath);
	    }),

	    // Get the textPath children
	    textPath() {
	      return this.findOne('textPath');
	    }

	  },
	  Path: {
	    // creates a textPath from this path
	    text: wrapWithAttrCheck(function (text) {
	      // Convert text to instance if needed
	      if (!(text instanceof Text)) {
	        text = new Text().addTo(this.parent()).text(text);
	      } // Create textPath from text and path and return


	      return text.path(this);
	    }),

	    targets() {
	      return baseFind('svg textPath').filter(node => {
	        return (node.attr('href') || '').includes(this.id());
	      }); // Does not work in IE11. Use when IE support is dropped
	      // return baseFind('svg textPath[*|href*="' + this.id() + '"]')
	    }

	  }
	});
	TextPath.prototype.MorphArray = PathArray;
	register(TextPath, 'TextPath');

	class Use extends Shape {
	  constructor(node) {
	    let attrs = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : node;
	    super(nodeOrNew('use', node), attrs);
	  } // Use element as a reference


	  use(element, file) {
	    // Set lined element
	    return this.attr('href', (file || '') + '#' + element, xlink);
	  }

	}

	registerMethods({
	  Container: {
	    // Create a use element
	    use: wrapWithAttrCheck(function (element, file) {
	      return this.put(new Use()).use(element, file);
	    })
	  }
	});
	register(Use, 'Use');
	/* Optional Modules */

	const SVG$1 = makeInstance;
	extend([Svg, Symbol$1, Image, Pattern, Marker], getMethodsFor('viewbox'));
	extend([Line, Polyline, Polygon, Path], getMethodsFor('marker'));
	extend(Text, getMethodsFor('Text'));
	extend(Path, getMethodsFor('Path'));
	extend(Defs, getMethodsFor('Defs'));
	extend([Text, Tspan], getMethodsFor('Tspan'));
	extend([Rect, Ellipse, Gradient, Runner], getMethodsFor('radius'));
	extend(EventTarget, getMethodsFor('EventTarget'));
	extend(Dom, getMethodsFor('Dom'));
	extend(Element, getMethodsFor('Element'));
	extend(Shape, getMethodsFor('Shape'));
	extend([Container, Fragment], getMethodsFor('Container'));
	extend(Gradient, getMethodsFor('Gradient'));
	extend(Runner, getMethodsFor('Runner'));
	List.extend(getMethodNames());
	registerMorphableType([SVGNumber, Color, Box, Matrix, SVGArray, PointArray, PathArray]);
	makeMorphable();

	var svg_esm = /*#__PURE__*/Object.freeze({
		__proto__: null,
		A: A,
		Animator: Animator,
		Array: SVGArray,
		Box: Box,
		Circle: Circle,
		ClipPath: ClipPath,
		Color: Color,
		Container: Container,
		Controller: Controller,
		Defs: Defs,
		Dom: Dom,
		Ease: Ease,
		Element: Element,
		Ellipse: Ellipse,
		EventTarget: EventTarget,
		ForeignObject: ForeignObject,
		Fragment: Fragment,
		G: G,
		Gradient: Gradient,
		Image: Image,
		Line: Line,
		List: List,
		Marker: Marker,
		Mask: Mask,
		Matrix: Matrix,
		Morphable: Morphable,
		NonMorphable: NonMorphable,
		Number: SVGNumber,
		ObjectBag: ObjectBag,
		PID: PID,
		Path: Path,
		PathArray: PathArray,
		Pattern: Pattern,
		Point: Point,
		PointArray: PointArray,
		Polygon: Polygon,
		Polyline: Polyline,
		Queue: Queue,
		Rect: Rect,
		Runner: Runner,
		SVG: SVG$1,
		Shape: Shape,
		Spring: Spring,
		Stop: Stop,
		Style: Style,
		Svg: Svg,
		Symbol: Symbol$1,
		Text: Text,
		TextPath: TextPath,
		Timeline: Timeline,
		TransformBag: TransformBag,
		Tspan: Tspan,
		Use: Use,
		adopt: adopt,
		assignNewId: assignNewId,
		clearEvents: clearEvents,
		create: create,
		defaults: defaults,
		dispatch: dispatch,
		easing: easing,
		eid: eid,
		extend: extend,
		find: baseFind,
		getClass: getClass,
		getEventTarget: getEventTarget,
		getEvents: getEvents,
		getWindow: getWindow,
		makeInstance: makeInstance,
		makeMorphable: makeMorphable,
		mockAdopt: mockAdopt,
		namespaces: namespaces,
		nodeOrNew: nodeOrNew,
		off: off,
		on: on,
		parser: parser,
		regex: regex,
		register: register,
		registerMorphableType: registerMorphableType,
		registerWindow: registerWindow,
		restoreWindow: restoreWindow,
		root: root,
		saveWindow: saveWindow,
		utils: utils,
		windowEvents: windowEvents,
		withWindow: withWindow,
		wrapWithAttrCheck: wrapWithAttrCheck
	});

	var require$$0 = /*@__PURE__*/getAugmentedNamespace(svg_esm);

	const {
	  SVG
	} = require$$0;

	function getPaper$2() {
	  return SVG();
	}

	var getPaper_browser = getPaper$2;

	const getPaper$1 = getPaper_browser;

	function appendResiduesPosition$1(data) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const residues = data.residues;
	  const {
	    leftRightBorders = 20,
	    spaceBetweenResidues = 20,
	    width = 600
	  } = options;
	  let xPos = leftRightBorders;
	  let xOld = xPos;
	  let line = 0; // we create a temporary paper in order to get the width of the text blocs

	  const paper = getPaper$1();

	  for (let i = 0; i < residues.all.length; i++) {
	    let residue = residues.all[i];
	    let textWidth = getTextWidth(paper, residue.label, options);
	    xPos += textWidth;

	    if (xPos > width - leftRightBorders) {
	      xOld = leftRightBorders;
	      xPos = leftRightBorders + textWidth;
	      line++;
	    }

	    setPaper(residue, xOld, xPos, line);
	    xPos += spaceBetweenResidues;
	    xOld = xPos;
	  }

	  residues.nbLines = line;
	}

	function setPaper(residue, xFrom, xTo, line) {
	  residue.paper = {
	    xFrom,
	    xTo,
	    line,
	    usedSlots: [],
	    topPosition: 0,
	    bottomPosition: 0
	  };
	}

	function getTextWidth(paper, label) {
	  let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
	  const {
	    labelFontFamily = 'Verdana',
	    labelSize = 12
	  } = options;
	  let text = paper.text(label);
	  text.font({
	    family: labelFontFamily,
	    size: labelSize,
	    weight: 'bold',
	    fill: '#888'
	  });
	  let textWidth = text.length() || text.bbox().width;
	  text.remove();
	  return textWidth;
	}

	var appendResiduesPosition_1 = appendResiduesPosition$1;

	function appendResults$1(data, analysisResult) {
	  let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
	  const numberResidues = data.residues.residues.length;
	  const {
	    merge = {},
	    filter = {}
	  } = options;
	  let results = JSON.parse(JSON.stringify(analysisResult));
	  results = results.filter(result => !result.type.match(/^-B[0-9]$/)); // we calculate all the lines based on the results

	  for (let result of results) {
	    let parts = result.type.split(/:|(?=[a-z])/); // we may have ':' but not mandatory

	    if (parts.length === 2) {
	      result.internal = true;

	      if (parts[1].match(/^[abcd][1-9]/)) {
	        [parts[0], parts[1]] = [parts[1], parts[0]];
	      }

	      result.to = getNumber(parts[0]) - 1;
	      result.from = numberResidues - getNumber(parts[1]);
	    } else {
	      if (parts[0].match(/^[abcd][1-9]/)) {
	        result.fromBegin = true;
	        result.position = getNumber(parts[0]) - 1;
	      }

	      if (parts[0].match(/^[wxyz][1-9]/)) {
	        result.fromEnd = true;
	        result.position = numberResidues - 1 - getNumber(parts[0]);
	      }
	    }

	    if (result.fromEnd) result.color = 'red';
	    if (result.fromBegin) result.color = 'blue';

	    if (result.internal) {
	      switch (result.type.substring(0, 1)) {
	        case 'a':
	          result.color = 'green';
	          break;

	        case 'b':
	          result.color = 'orange';
	          break;

	        case 'c':
	          result.color = 'cyan';
	          break;

	        default:
	          result.color = 'green';
	      }
	    }
	  }

	  if (merge.charge) {
	    const unique = {};

	    for (let result of results) {
	      if (!unique[result.type]) {
	        unique[result.type] = [];
	      }

	      unique[result.type].push(result);
	    }

	    results = [];

	    for (let key in unique) {
	      let current = unique[key][0];
	      current.similarity = unique[key].reduce((previous, item) => previous + item.similarity, 0);
	      current.similarity = current.similarity / unique[key].length;
	      results.push(current);
	      current.charge = '';
	    }
	  }

	  for (let result of results) {
	    if (result.similarity > 0.95) {
	      result.textColor = 'black';
	    } else if (result.similarity > 0.9) {
	      result.textColor = '#333';
	    } else if (result.similariy > 0.8) {
	      result.textColor = '#666';
	    } else {
	      result.textColor = '#999';
	    }
	  }

	  results = filterResults(results, filter); // sort by residue length

	  results.sort((a, b) => a.length - b.length);
	  data.results = results;
	}

	function getNumber(text) {
	  return Number(text.replace(/^.([0-9]+).*$/, '$1'));
	}

	function filterResults(results, filter) {
	  if (!filter) return;
	  let {
	    minRelativeQuantity = 0,
	    minSimilarity = 0,
	    minQuantity = 0,
	    showInternals = true
	  } = filter;

	  if (minRelativeQuantity) {
	    minQuantity = Math.max(...results.map(entry => entry.quantity)) * minRelativeQuantity;
	  }

	  if (minSimilarity) {
	    results = results.filter(result => !result.similarity || result.similarity >= minSimilarity);
	  }

	  if (minQuantity) {
	    results = results.filter(result => !result.quantity || result.quantity >= minQuantity);
	  }

	  if (!showInternals) {
	    results = results.filter(result => !result.internal);
	  }

	  return results;
	}

	var appendResults_1 = appendResults$1;

	function appendRows$1(data) {
	  let allResidues = data.residues.all.sort((a, b) => a.line - b.line);
	  data.rows = [];

	  for (let residue of allResidues) {
	    let line = residue.paper.line;

	    if (!data.rows[line]) {
	      data.rows[line] = {
	        residues: []
	      };
	    }

	    data.rows[line].residues.push(residue);
	  }

	  for (let row of data.rows) {
	    row.info = {};
	  }
	}

	var appendRows_1 = appendRows$1;

	/* eslint-disable no-loop-func */
	/**
	 * For each row we calculate internals, label over and label under
	 * @param {*} data
	 */


	function appendRowsInformation$1(data) {
	  for (let row of data.rows) {
	    let filtered = row.residues.filter(entry => entry.fromBegin !== undefined);

	    if (filtered.length > 0) {
	      row.info.firstResidue = filtered[0].fromBegin;
	      row.info.lastResidue = filtered[filtered.length - 1].fromBegin;
	    }

	    row.internals = [];
	  }

	  for (let result of data.results) {
	    if (result.internal) {
	      let fromResidue = data.residues.residues[result.from];
	      let from = fromResidue.fromBegin;
	      let toResidue = data.residues.residues[result.to];
	      let to = toResidue.fromBegin;

	      for (let row of data.rows) {
	        if (from <= row.info.lastResidue && to >= row.info.firstResidue) {
	          result = JSON.parse(JSON.stringify(result));
	          result.fromResidue = fromResidue;

	          if (from < row.info.firstResidue) {
	            result.firstIndex = true;
	          } else {
	            row.residues.forEach((residue, index) => {
	              if (residue.fromBegin === from) {
	                result.firstIndex = index;
	              }
	            });
	          }

	          result.toResidue = toResidue;

	          if (to > row.info.lastResidue) {
	            result.lastIndex = true;
	          } else {
	            row.residues.forEach((residue, index) => {
	              if (residue.fromBegin === to) {
	                result.lastIndex = index;
	              }
	            });
	          }

	          row.internals.push(result);
	        }
	      }
	    } else {
	      if (result.position !== undefined && data.residues.residues[result.position]) {
	        let residue = data.residues.residues[result.position];

	        if (result.fromEnd) {
	          residue.info.nbOver++;
	          residue.results.end.push(result);
	        }

	        if (result.fromBegin) {
	          residue.info.nbUnder++;
	          residue.results.begin.push(result);
	        }
	      }
	    }
	  }

	  for (let row of data.rows) {
	    let maxNbOver = 0;
	    let maxNbUnder = 0;

	    for (let residue of row.residues) {
	      if (residue.info.nbOver > maxNbOver) maxNbOver = residue.info.nbOver;
	      if (residue.info.nbUnder > maxNbUnder) maxNbUnder = residue.info.nbUnder;
	    }

	    row.info.nbOver = maxNbOver;
	    row.info.nbUnder = maxNbUnder;
	  }
	}

	var appendRowsInformation_1 = appendRowsInformation$1;

	function addCSS$1(paper) {
	  let cssCode = `
    .highlight {
        stroke: yellow;
        stroke-width: 5px;
        fill: red;
    }
    .highlightText {
        fill: red;
    }
    `;
	  let style = paper.element('style');
	  style.words(cssCode);
	}

	var addCSS_1 = addCSS$1;

	function addScript$1(paper) {
	  let scriptCode = ` // <![CDATA[
        function mouseOver(evt) {
           
            let targetRange=evt.target.id.replace(/^line/,'');
            let from=targetRange.replace(/-.*/,'')*1;
            let to=targetRange.replace(/.*-/,'')*1;
            let children=evt.target.parentNode.children;
            for (let child of children) {
                if (child.id === evt.target.id) {
                    child.setAttribute('class','highlight');
                }
                if (child.nodeName === 'text' && child.id.startsWith("residue")) {
                    let residueNumber=child.id.replace(/residue-/,'')*1;
                    if (residueNumber>=from && residueNumber<=to) {
                        child.setAttribute('class','highlightText');
                    }
                }
            }
        }
        function mouseOut(evt) {
            let children=evt.target.parentNode.children;
            for (let child of children) {
                if (child.id === evt.target.id) {
                    child.setAttribute('class','');
                }
                if (child.nodeName === 'text' && child.id.startsWith("residue")) {
                    child.setAttribute('class','');
                }
            }
        }
     // ]]>
    `;
	  let script = paper.element('script');
	  script.attr({
	    type: 'application/ecmascript'
	  });
	  script.words(scriptCode);
	}

	var addScript_1 = addScript$1;

	function drawLabel$2(paper, result, x, y, options) {
	  let label = result.type;
	  let similarity = String(Math.round(result.similarity * 100));
	  let charge = result.charge > 0 ? `+${result.charge}` : result.charge;
	  let text = paper.plain(label);
	  text.font({
	    fill: result.textColor,
	    family: options.labelFontFamily,
	    weight: 'bold',
	    size: options.labelSize,
	    anchor: 'end'
	  });
	  text.attr({
	    x,
	    y
	  });
	  let textWidth = 0;
	  text = paper.plain(charge);
	  text.font({
	    fill: result.textColor,
	    family: options.labelFontFamily,
	    size: options.labelSize / 2
	  });
	  text.attr({
	    x: x + textWidth,
	    y: y - options.labelSize / 2
	  });
	  text = paper.plain(similarity);
	  text.font({
	    fill: result.textColor,
	    family: options.labelFontFamily,
	    size: options.labelSize / 2
	  });
	  text.attr({
	    x: x + textWidth,
	    y
	  });
	}

	var drawLabel_1 = drawLabel$2;

	const drawLabel$1 = drawLabel_1;

	function drawInternals$1(paper, row, options) {
	  let fromX = 0;
	  let toX = 0;

	  for (const internal of row.internals) {
	    options.verticalPosition += options.spaceBetweenInternalLines;

	    if (internal.firstIndex === true) {
	      fromX = 0;
	    } else {
	      fromX = internal.fromResidue.paper.xFrom - options.spaceBetweenResidues / 2;
	    }

	    if (internal.lastIndex === true) {
	      toX = options.width - 1;
	    } else {
	      toX = internal.toResidue.paper.xTo + options.spaceBetweenResidues / 2;
	    }

	    let y = options.verticalPosition;
	    let drawLine = paper.line(fromX, y, toX, y);
	    drawLine.attr({
	      onmouseover: 'mouseOver(evt)',
	      onmouseout: 'mouseOut(evt)',
	      id: `line${internal.fromResidue.fromBegin}-${internal.toResidue.fromBegin}`
	    });
	    drawLine.stroke({
	      color: internal.color,
	      width: options.strokeWidth
	    });
	    let center = (fromX + toX + internal.type.length * options.labelSize * 2 / 3) / 2;
	    drawLabel$1(paper, internal, center, y - 2, options);
	  }

	  options.verticalPosition += options.spaceBetweenInternalLines * 2;
	}

	var drawInternals_1 = drawInternals$1;

	function drawReplacements$1(paper, data, options) {
	  let replacements = data.residues.replacements;
	  replacements = Object.keys(replacements).map(key => {
	    return {
	      key,
	      ...replacements[key]
	    };
	  });

	  for (let replacement of replacements) {
	    options.verticalPosition += options.spaceBetweenInternalLines;
	    let text = paper.plain(`${replacement.label} = ${replacement.key}`);
	    text.font({
	      fill: 'darkviolet',
	      family: options.labelFontFamily,
	      weight: 'bold',
	      size: 10
	    });
	    text.attr({
	      x: options.leftRightBorders,
	      y: options.verticalPosition
	    });
	  }

	  options.verticalPosition += options.spaceBetweenInternalLines;
	}

	var drawReplacements_1 = drawReplacements$1;

	const drawLabel = drawLabel_1;

	function drawTerminals$1(paper, row, options) {
	  for (let residue of row.residues) {
	    if (!residue.results) continue;

	    for (let result of residue.results.begin) {
	      let line = paper.line(residue.paper.xTo + options.spaceBetweenResidues / 2, residue.paper.y, residue.paper.xTo + options.spaceBetweenResidues / 2, residue.paper.y - 8);
	      line.stroke({
	        color: result.color,
	        width: options.strokeWidth,
	        linecap: 'round'
	      });
	      line = paper.line(residue.paper.xTo + options.spaceBetweenResidues / 2, residue.paper.y, residue.paper.xTo + options.spaceBetweenResidues / 2 - 5, residue.paper.y + 5);
	      line.stroke({
	        color: result.color,
	        width: options.strokeWidth,
	        linecap: 'round'
	      });
	      drawLabel(paper, result, residue.paper.xTo + options.spaceBetweenResidues / 2, residue.paper.y + options.labelSize + 6 + residue.paper.bottomPosition * options.labelSize, options);
	      residue.paper.bottomPosition++;
	    }

	    for (let result of residue.results.end) {
	      let line = paper.line(residue.paper.xTo + options.spaceBetweenResidues / 2 + options.strokeWidth, residue.paper.y, residue.paper.xTo + options.spaceBetweenResidues / 2 + options.strokeWidth, residue.paper.y - 8);
	      line.stroke({
	        color: result.color,
	        width: options.strokeWidth,
	        linecap: 'round'
	      });
	      line = paper.line(residue.paper.xTo + options.spaceBetweenResidues / 2 + options.strokeWidth, residue.paper.y - 8, residue.paper.xTo + options.spaceBetweenResidues / 2 + 5 + options.strokeWidth, residue.paper.y - 13);
	      line.stroke({
	        color: result.color,
	        width: options.strokeWidth,
	        linecap: 'round'
	      });
	      drawLabel(paper, result, residue.paper.xTo + options.spaceBetweenResidues, residue.paper.y - 17 - residue.paper.topPosition * options.labelSize, options);
	      residue.paper.topPosition++;
	    }
	  }
	}

	var drawTerminals_1 = drawTerminals$1;

	const drawTerminals = drawTerminals_1;

	function drawSequence$1(paper, row, options) {
	  // need to plan some space for the OVER
	  options.verticalPosition += row.info.nbOver * (options.labelSize + 1);

	  for (const residue of row.residues) {
	    residue.paper.y = options.verticalPosition;
	    let text = paper.plain(residue.label);
	    let textColor = residue.replaced ? 'darkviolet' : residue.kind === 'residue' ? '#555' : '#CCC';
	    text.font({
	      family: options.labelFontFamily,
	      size: 12,
	      weight: 'bold',
	      fill: textColor
	    });
	    text.attr({
	      x: residue.paper.xFrom,
	      y: residue.paper.y
	    });
	    text.attr({
	      id: `residue-${residue.fromBegin}`
	    });
	  }

	  drawTerminals(paper, row, options); // need to plan some space for the UNDER

	  options.verticalPosition += row.info.nbUnder * (options.labelSize + 1);
	  options.verticalPosition += options.spaceBetweenInternalLines * 2;
	}

	var drawSequence_1 = drawSequence$1;

	const appendInternals = appendInternals_1;
	const appendResidues = appendResidues$1;
	const appendResiduesPosition = appendResiduesPosition_1;
	const appendResults = appendResults_1;
	const appendRows = appendRows_1;
	const appendRowsInformation = appendRowsInformation_1;
	const addCSS = addCSS_1;
	const addScript = addScript_1;
	const drawInternals = drawInternals_1;
	const drawReplacements = drawReplacements_1;
	const drawSequence = drawSequence_1;
	const getPaper = getPaper_browser;
	/**
	 *
	 * @param {string} sequence
	 * @param {array} analysisResults
	 * @param {object} [options={}]
	 * @param {number} [options.leftRightBorders=50]
	 * @param {number} [options.width=600]
	 * @param {number} [options.spaceBetweenResidues=30]
	 * @param {number} [options.spaceBetweenInternalLines=12]
	 * @param {number} [options.strokeWidth=2]
	 * @param {string} [options.labelFontFamily='Verdana']
	 * @param {number} [options.labelSize=8]
	 * @param {number} [options.parsing] Sequence parsing options
	 * @param {object} [options.merge={}]
	 * @param {boolean} [options.merge.charge] Merge results if only differs by charge
	 * @param {object} [options.filter={}] define some filters
	 * @param {number} [options.filter.minSimilarity=0]  minimal similarity
	 * @param {number} [options.filter.minQuantity=0]  minimal quantity
	 * @param {number} [options.filter.minRelativeQuantity=0]  minimal relative quantity. This value should be between 0 and 1 and supersede minQuantity.
	 * @param {boolean} [options.filter.showInternals=true] show the internal fragments
	 *
	 */

	function sequenceSVG(sequence, analysisResults) {
	  let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
	  const {
	    width = 600,
	    leftRightBorders = 50,
	    spaceBetweenResidues = 30,
	    spaceBetweenInternalLines = 12,
	    strokeWidth = 2,
	    labelFontFamily = 'Verdana',
	    labelSize = 8,
	    parsing,
	    merge,
	    filter
	  } = options;
	  const drawOptions = {
	    spaceBetweenResidues,
	    leftRightBorders,
	    spaceBetweenInternalLines,
	    strokeWidth,
	    labelSize,
	    labelFontFamily,
	    verticalPosition: spaceBetweenInternalLines,
	    width
	  };
	  let data = {};
	  appendResidues(data, sequence, parsing);
	  appendResults(data, analysisResults, {
	    merge,
	    filter
	  });
	  appendResiduesPosition(data, {
	    leftRightBorders,
	    spaceBetweenResidues,
	    labelFontFamily,
	    labelSize,
	    width
	  });
	  appendRows(data);
	  appendInternals(data);
	  appendRowsInformation(data); // We start to create the SVG and create the paper

	  const paper = getPaper();
	  addCSS(paper);
	  addScript(paper);

	  for (let row of data.rows) {
	    drawInternals(paper, row, drawOptions);
	    drawSequence(paper, row, drawOptions);
	  }

	  drawReplacements(paper, data, drawOptions);
	  paper.size(width, drawOptions.verticalPosition);
	  let svg = paper.svg();
	  paper.clear();
	  return svg;
	}

	var sequenceSVG_1 = sequenceSVG;

	var src$2 = {
	  sequenceSVG: sequenceSVG_1
	};

	const Kind = Kind$c;
	const MF = MF_1;
	const parse = parse$4;

	var preprocessEARanges$1 = function preprocessRanges(ranges, targetEA) {
	  ranges = JSON.parse(JSON.stringify(ranges));

	  if (typeof ranges === 'string') {
	    // need to convert to ranges
	    let parsed = parse(ranges.replace(/[\r\n\t ]/g, ''));
	    let newRanges = [];
	    let current = {
	      mf: '',
	      min: 1,
	      max: 1
	    }; // example ClBr2(CH2)0-2NO
	    // the idea is that has long as we don't have a range we don't really care
	    // there is a limitation is that the range has to be first level of parenthesis

	    let parenthesisLevel = 0;
	    let currentMF = ''; // start at an atom first level or a parenthesis

	    for (let item of parsed) {
	      switch (item.kind) {
	        case Kind.ATOM:
	          if (parenthesisLevel === 0 && currentMF) {
	            current.mf += currentMF;
	            currentMF = '';
	          }

	          currentMF += item.value;
	          break;

	        case Kind.ISOTOPE:
	          if (parenthesisLevel === 0 && currentMF) {
	            current.mf += currentMF;
	            currentMF = '';
	          }

	          currentMF += `[${item.value.isotope}${item.value.atom}]`;
	          break;

	        case Kind.MULTIPLIER:
	          if (parenthesisLevel === 0 && currentMF) {
	            current.mf += currentMF + item.value;
	            currentMF = '';
	          } else {
	            currentMF += item.value;
	          }

	          break;

	        case Kind.MULTIPLIER_RANGE:
	          if (parenthesisLevel !== 0) {
	            throw new Error('Range multiplier can only be at the first level');
	          }

	          newRanges.push({
	            mf: currentMF,
	            min: item.value.from,
	            max: item.value.to
	          });
	          currentMF = '';
	          break;

	        case Kind.OPENING_PARENTHESIS:
	          parenthesisLevel++;
	          currentMF += '(';
	          break;

	        case Kind.CLOSING_PARENTHESIS:
	          parenthesisLevel--;
	          currentMF += ')';
	          break;

	        default:
	          throw Error(`can not preprocess ${ranges}`);
	      }
	    }

	    if (currentMF) {
	      current.mf += currentMF;
	    }

	    if (current.mf) {
	      newRanges.push(current);
	    }

	    ranges = newRanges;
	  }

	  let possibilities = [];

	  for (let i = 0; i < ranges.length; i++) {
	    let range = ranges[i];
	    let min = range.min === undefined ? 0 : range.min;
	    let max = range.max === undefined ? 1 : range.max;
	    let possibility = {
	      mf: range.mf,
	      minCount: min,
	      // value defined by the user
	      maxCount: max,
	      // value defined by the user
	      targetEA: targetEA[range.mf],
	      mw: 0,
	      // mw till this level
	      aw: 0,
	      // atomic weight (mass * currentCount)
	      maxRatio: 0,
	      // maximum ratio possible if all next elements are 0
	      currentValue: 0,
	      currentCount: min - 1,
	      currentUnsaturation: 0,
	      initialOrder: i
	    };
	    possibilities.push(possibility);
	    let info = new MF(range.mf).getInfo();
	    possibility.mass = info.mass;
	    possibility.unsaturation = range.unsaturation === undefined ? (info.unsaturation - 1) * 2 : range.unsaturation;
	    if (possibility.mf !== info.mf) possibility.isGroup = true;
	  }

	  possibilities = possibilities.filter(r => r.minCount !== 0 || r.maxCount !== 0);
	  possibilities.sort((a, b) => {
	    return b.targetEA - a.targetEA;
	  });
	  return possibilities;
	};

	const atomSorter = src$i;
	const preprocessEARanges = preprocessEARanges$1;
	/**
	 * Returns possible combinations
	 * @param {object} [targetEA]
	 * @param {object} [options={}]
	 * @param {string} [options.ranges='C0-100 H0-100 O0-100 N0-100'] - range of mfs to search
	 * @param {number} [options.maxElementError=0.003]
	 * @param {number} [options.maxTotalError=0.01]
	 * @param {number} [options.minMW=0] minimal molecular weight
	 * @param {number} [options.maxMW=+Infinity] maximal molecular weight
	 * @param {number} [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
	 * @return {Array<object>}
	 */

	var src$1 = function mfFromEA(targetEA) {
	  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
	  const {
	    unsaturation = {},
	    maxIterations = 1e8,
	    minMW = 0,
	    maxMW = +Infinity,
	    ranges = [{
	      mf: 'C',
	      min: 0,
	      max: 100
	    }, {
	      mf: 'H',
	      min: 0,
	      max: 100
	    }, {
	      mf: 'O',
	      min: 0,
	      max: 100
	    }, {
	      mf: 'N',
	      min: 0,
	      max: 100
	    }],
	    maxElementError = 0.003,
	    maxTotalError = 0.01
	  } = options;
	  let filterUnsaturation = unsaturation ? true : false; // we calculate not the real unsaturation but the one before dividing by 2 + 1

	  let fakeMinUnsaturation = unsaturation.min === undefined ? Number.MIN_SAFE_INTEGER : (unsaturation.min - 1) * 2;
	  let fakeMaxUnsaturation = unsaturation.max === undefined ? Number.MAX_SAFE_INTEGER : (unsaturation.max - 1) * 2;
	  let results = {
	    mfs: [],
	    info: {
	      numberMFEvaluated: 0,
	      numberResults: 0
	    }
	  };
	  let orderMapping = []; // used to sort the atoms

	  let possibilities = preprocessEARanges(ranges, targetEA, maxElementError);
	  orderMapping = getOrderMapping(possibilities);
	  if (possibilities.length === 0) return {
	    mfs: []
	  };
	  let currentPosition = 0;
	  let currentAtom; //  if (DEBUG) console.log('possibilities', possibilities.map((a) => `${a.mf + a.originalMinCount}-${a.originalMaxCount}`));

	  mfWhile: while (true) {
	    while (currentPosition < possibilities.length && currentPosition >= 0) {
	      let previousAtom = currentPosition === 0 ? {
	        mw: 0
	      } : possibilities[currentPosition - 1];
	      currentAtom = possibilities[currentPosition];

	      if (currentAtom.currentCount < currentAtom.maxCount) {
	        currentAtom.currentCount++;
	        currentAtom.aw = currentAtom.mass * currentAtom.currentCount;
	        currentAtom.mw = currentAtom.aw + previousAtom.mw;
	        currentAtom.maxRatio = currentAtom.aw / currentAtom.mw; // we should check if we can reach the target

	        if (currentAtom.targetEA && currentAtom.aw / currentAtom.mw - currentAtom.targetEA < -maxElementError) {
	          // we already don't have enough quantity of this element and it can only become worse
	          continue;
	        }

	        if (currentPosition < possibilities.length - 1) {
	          currentPosition++;
	        } else {
	          break;
	        }
	      } else {
	        currentAtom.currentCount = currentAtom.minCount - 1;
	        currentPosition--;
	      }
	    }

	    if (currentPosition < 0) {
	      break;
	    }

	    if (results.info.numberMFEvaluated++ > maxIterations) {
	      throw Error(`Iteration number is over the current maximum of: ${maxIterations}`);
	    }

	    if (filterUnsaturation) {
	      let unsaturationValue = 0;

	      for (const possibility of possibilities) {
	        unsaturationValue += possibility.unsaturation * possibility.currentCount;
	      }

	      let isOdd = Math.abs(unsaturationValue % 2);

	      if (unsaturation.onlyInteger && isOdd === 1 || unsaturation.onlyNonInteger && isOdd === 0 || fakeMinUnsaturation > unsaturationValue || fakeMaxUnsaturation < unsaturationValue) {
	        continue;
	      }
	    }

	    let mw = currentAtom.mw;
	    if (mw < minMW || mw > maxMW) continue;
	    let totalError = 0;

	    for (let i = 0; i < possibilities.length; i++) {
	      const possibility = possibilities[i];
	      let ratio = possibility.mass * possibility.currentCount / mw;

	      if (possibility.targetEA !== undefined) {
	        let error = Math.abs(possibility.targetEA - ratio);

	        if (error > maxElementError) {
	          continue mfWhile;
	        }

	        totalError += error;
	      }

	      possibility.currentValue = ratio;
	    }

	    if (isNaN(totalError) || totalError > maxTotalError) continue;
	    results.mfs.push(getResult(possibilities, totalError, orderMapping));
	    results.info.numberResults++;
	  }

	  results.mfs.sort((a, b) => Math.abs(a.totalError) - Math.abs(b.totalError));
	  return results;
	};

	function getResult(possibilities, totalError, orderMapping) {
	  const result = {
	    mf: '',
	    totalError
	  }; // we check that the first time we meet the ionization group it does not end
	  // in the final result

	  for (let i = 0; i < possibilities.length; i++) {
	    let possibility = possibilities[orderMapping[i]];

	    if (possibility.currentCount !== 0) {
	      if (possibility.isGroup) {
	        result.mf += `(${possibility.mf})`;

	        if (possibility.currentCount !== 1) {
	          result.mf += possibility.currentCount;
	        }
	      } else {
	        result.mf += possibility.mf;

	        if (possibility.currentCount !== 1) {
	          result.mf += possibility.currentCount;
	        }
	      }
	    }

	    result.ea = possibilities.map(current => ({
	      mf: current.mf,
	      value: current.currentValue,
	      expected: current.targetEA,
	      error: current.targetEA === undefined ? undefined : Math.abs(current.targetEA - current.currentValue)
	    }));
	  }

	  return result;
	}

	function getOrderMapping(possibilities) {
	  let mapping = possibilities.map((p, i) => ({
	    atom: p.mf,
	    index: i
	  }));
	  mapping.sort((a, b) => {
	    return atomSorter(a.atom, b.atom);
	  });
	  return mapping.map(a => a.index);
	}

	var src = {
	  EMDB: src$3,
	  groups: groups$7,
	  groupsObject: groupsObject_1,
	  elements: elementsAndIsotopes$1,
	  elementsObject: elementsAndIsotopesObject_1,
	  IsotopicDistribution: src$5,
	  MF: MF_1,
	  Peptide: src$8,
	  Nucleotide: src$7,
	  Spectrum: Spectrum_1,
	  getPeaks: getPeaks_1,
	  getBestPeaks: getBestPeaks_1,
	  generateMFs: src$c,
	  Report: src$2,
	  atomSorter: src$i,
	  mfFromEA: src$1
	};

	return src;

}));
//# sourceMappingURL=mass-tools.js.map
