/*
 * xps-analysis v1.0.0
 * XPS analysis
 * https://github.com/cheminfo/xps-analysis#readme
 *
 * Licensed under the MIT license.
 */
// eslint-disable-next-line @typescript-eslint/unbound-method
const toString$2 = Object.prototype.toString;
/**
 * Checks if an object is an instance of an Array (array or typed array, except those that contain bigint values).
 * @param value - Object to check.
 * @returns True if the object is an array or a typed array.
 */
function isAnyArray$2(value) {
  const tag = toString$2.call(value);
  return tag.endsWith('Array]') && !tag.includes('Big');
}

/**
 * This function
 * @param output - undefined or a new array
 * @param length - length of the output array
 * @returns
 */
function getOutputArray(output, length) {
  if (output !== undefined) {
    if (!isAnyArray$2(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);
  }
}

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

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

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

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

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

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

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

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

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

var matrix = {};

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

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

/**
 * Computes the maximum of the given values.
 *
 * @param input
 * @param options
 */
function max(input, options = {}) {
  if (!isAnyArray$1(input)) {
    throw new TypeError('input must be an array');
  }
  if (input.length === 0) {
    throw new TypeError('input must not be empty');
  }
  const {
    fromIndex = 0,
    toIndex = input.length
  } = options;
  if (fromIndex < 0 || fromIndex >= input.length || !Number.isInteger(fromIndex)) {
    throw new Error('fromIndex must be a positive integer smaller than length');
  }
  if (toIndex <= fromIndex || toIndex > input.length || !Number.isInteger(toIndex)) {
    throw new Error('toIndex must be an integer greater than fromIndex and at most equal to length');
  }
  let maxValue = input[fromIndex];
  for (let i = fromIndex + 1; i < toIndex; i++) {
    if (input[i] > maxValue) maxValue = input[i];
  }
  return maxValue;
}

/**
 * Computes the minimum of the given values.
 */
function min(input, options = {}) {
  if (!isAnyArray$1(input)) {
    throw new TypeError('input must be an array');
  }
  if (input.length === 0) {
    throw new TypeError('input must not be empty');
  }
  const {
    fromIndex = 0,
    toIndex = input.length
  } = options;
  if (fromIndex < 0 || fromIndex >= input.length || !Number.isInteger(fromIndex)) {
    throw new Error('fromIndex must be a positive integer smaller than length');
  }
  if (toIndex <= fromIndex || toIndex > input.length || !Number.isInteger(toIndex)) {
    throw new Error('toIndex must be an integer greater than fromIndex and at most equal to length');
  }
  let minValue = input[fromIndex];
  for (let i = fromIndex + 1; i < toIndex; i++) {
    if (input[i] < minValue) minValue = input[i];
  }
  return minValue;
}

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

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

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

  // well, if it's still too long the user should've used longer numbers
  let exp = num.toExponential(len);
  if (exp.length > len) {
    exp = num.toExponential(Math.max(0, len - (exp.length - len)));
  }
  return exp.slice(0);
}
function installMathOperations(AbstractMatrix, Matrix) {
  AbstractMatrix.prototype.add = function add(value) {
    if (typeof value === 'number') return this.addS(value);
    return this.addM(value);
  };
  AbstractMatrix.prototype.addS = function addS(value) {
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, this.get(i, j) + value);
      }
    }
    return this;
  };
  AbstractMatrix.prototype.addM = function addM(matrix) {
    matrix = Matrix.checkMatrix(matrix);
    if (this.rows !== matrix.rows || this.columns !== matrix.columns) {
      throw new RangeError('Matrices dimensions must be equal');
    }
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, this.get(i, j) + matrix.get(i, j));
      }
    }
    return this;
  };
  AbstractMatrix.add = function add(matrix, value) {
    const newMatrix = new Matrix(matrix);
    return newMatrix.add(value);
  };
  AbstractMatrix.prototype.sub = function sub(value) {
    if (typeof value === 'number') return this.subS(value);
    return this.subM(value);
  };
  AbstractMatrix.prototype.subS = function subS(value) {
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, this.get(i, j) - value);
      }
    }
    return this;
  };
  AbstractMatrix.prototype.subM = function subM(matrix) {
    matrix = Matrix.checkMatrix(matrix);
    if (this.rows !== matrix.rows || this.columns !== matrix.columns) {
      throw new RangeError('Matrices dimensions must be equal');
    }
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, this.get(i, j) - matrix.get(i, j));
      }
    }
    return this;
  };
  AbstractMatrix.sub = function sub(matrix, value) {
    const newMatrix = new Matrix(matrix);
    return newMatrix.sub(value);
  };
  AbstractMatrix.prototype.subtract = AbstractMatrix.prototype.sub;
  AbstractMatrix.prototype.subtractS = AbstractMatrix.prototype.subS;
  AbstractMatrix.prototype.subtractM = AbstractMatrix.prototype.subM;
  AbstractMatrix.subtract = AbstractMatrix.sub;
  AbstractMatrix.prototype.mul = function mul(value) {
    if (typeof value === 'number') return this.mulS(value);
    return this.mulM(value);
  };
  AbstractMatrix.prototype.mulS = function mulS(value) {
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, this.get(i, j) * value);
      }
    }
    return this;
  };
  AbstractMatrix.prototype.mulM = function mulM(matrix) {
    matrix = Matrix.checkMatrix(matrix);
    if (this.rows !== matrix.rows || this.columns !== matrix.columns) {
      throw new RangeError('Matrices dimensions must be equal');
    }
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, this.get(i, j) * matrix.get(i, j));
      }
    }
    return this;
  };
  AbstractMatrix.mul = function mul(matrix, value) {
    const newMatrix = new Matrix(matrix);
    return newMatrix.mul(value);
  };
  AbstractMatrix.prototype.multiply = AbstractMatrix.prototype.mul;
  AbstractMatrix.prototype.multiplyS = AbstractMatrix.prototype.mulS;
  AbstractMatrix.prototype.multiplyM = AbstractMatrix.prototype.mulM;
  AbstractMatrix.multiply = AbstractMatrix.mul;
  AbstractMatrix.prototype.div = function div(value) {
    if (typeof value === 'number') return this.divS(value);
    return this.divM(value);
  };
  AbstractMatrix.prototype.divS = function divS(value) {
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, this.get(i, j) / value);
      }
    }
    return this;
  };
  AbstractMatrix.prototype.divM = function divM(matrix) {
    matrix = Matrix.checkMatrix(matrix);
    if (this.rows !== matrix.rows || this.columns !== matrix.columns) {
      throw new RangeError('Matrices dimensions must be equal');
    }
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, this.get(i, j) / matrix.get(i, j));
      }
    }
    return this;
  };
  AbstractMatrix.div = function div(matrix, value) {
    const newMatrix = new Matrix(matrix);
    return newMatrix.div(value);
  };
  AbstractMatrix.prototype.divide = AbstractMatrix.prototype.div;
  AbstractMatrix.prototype.divideS = AbstractMatrix.prototype.divS;
  AbstractMatrix.prototype.divideM = AbstractMatrix.prototype.divM;
  AbstractMatrix.divide = AbstractMatrix.div;
  AbstractMatrix.prototype.mod = function mod(value) {
    if (typeof value === 'number') return this.modS(value);
    return this.modM(value);
  };
  AbstractMatrix.prototype.modS = function modS(value) {
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, this.get(i, j) % value);
      }
    }
    return this;
  };
  AbstractMatrix.prototype.modM = function modM(matrix) {
    matrix = Matrix.checkMatrix(matrix);
    if (this.rows !== matrix.rows || this.columns !== matrix.columns) {
      throw new RangeError('Matrices dimensions must be equal');
    }
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, this.get(i, j) % matrix.get(i, j));
      }
    }
    return this;
  };
  AbstractMatrix.mod = function mod(matrix, value) {
    const newMatrix = new Matrix(matrix);
    return newMatrix.mod(value);
  };
  AbstractMatrix.prototype.modulus = AbstractMatrix.prototype.mod;
  AbstractMatrix.prototype.modulusS = AbstractMatrix.prototype.modS;
  AbstractMatrix.prototype.modulusM = AbstractMatrix.prototype.modM;
  AbstractMatrix.modulus = AbstractMatrix.mod;
  AbstractMatrix.prototype.and = function and(value) {
    if (typeof value === 'number') return this.andS(value);
    return this.andM(value);
  };
  AbstractMatrix.prototype.andS = function andS(value) {
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, this.get(i, j) & value);
      }
    }
    return this;
  };
  AbstractMatrix.prototype.andM = function andM(matrix) {
    matrix = Matrix.checkMatrix(matrix);
    if (this.rows !== matrix.rows || this.columns !== matrix.columns) {
      throw new RangeError('Matrices dimensions must be equal');
    }
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, this.get(i, j) & matrix.get(i, j));
      }
    }
    return this;
  };
  AbstractMatrix.and = function and(matrix, value) {
    const newMatrix = new Matrix(matrix);
    return newMatrix.and(value);
  };
  AbstractMatrix.prototype.or = function or(value) {
    if (typeof value === 'number') return this.orS(value);
    return this.orM(value);
  };
  AbstractMatrix.prototype.orS = function orS(value) {
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, this.get(i, j) | value);
      }
    }
    return this;
  };
  AbstractMatrix.prototype.orM = function orM(matrix) {
    matrix = Matrix.checkMatrix(matrix);
    if (this.rows !== matrix.rows || this.columns !== matrix.columns) {
      throw new RangeError('Matrices dimensions must be equal');
    }
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, this.get(i, j) | matrix.get(i, j));
      }
    }
    return this;
  };
  AbstractMatrix.or = function or(matrix, value) {
    const newMatrix = new Matrix(matrix);
    return newMatrix.or(value);
  };
  AbstractMatrix.prototype.xor = function xor(value) {
    if (typeof value === 'number') return this.xorS(value);
    return this.xorM(value);
  };
  AbstractMatrix.prototype.xorS = function xorS(value) {
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, this.get(i, j) ^ value);
      }
    }
    return this;
  };
  AbstractMatrix.prototype.xorM = function xorM(matrix) {
    matrix = Matrix.checkMatrix(matrix);
    if (this.rows !== matrix.rows || this.columns !== matrix.columns) {
      throw new RangeError('Matrices dimensions must be equal');
    }
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, this.get(i, j) ^ matrix.get(i, j));
      }
    }
    return this;
  };
  AbstractMatrix.xor = function xor(matrix, value) {
    const newMatrix = new Matrix(matrix);
    return newMatrix.xor(value);
  };
  AbstractMatrix.prototype.leftShift = function leftShift(value) {
    if (typeof value === 'number') return this.leftShiftS(value);
    return this.leftShiftM(value);
  };
  AbstractMatrix.prototype.leftShiftS = function leftShiftS(value) {
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, this.get(i, j) << value);
      }
    }
    return this;
  };
  AbstractMatrix.prototype.leftShiftM = function leftShiftM(matrix) {
    matrix = Matrix.checkMatrix(matrix);
    if (this.rows !== matrix.rows || this.columns !== matrix.columns) {
      throw new RangeError('Matrices dimensions must be equal');
    }
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, this.get(i, j) << matrix.get(i, j));
      }
    }
    return this;
  };
  AbstractMatrix.leftShift = function leftShift(matrix, value) {
    const newMatrix = new Matrix(matrix);
    return newMatrix.leftShift(value);
  };
  AbstractMatrix.prototype.signPropagatingRightShift = function signPropagatingRightShift(value) {
    if (typeof value === 'number') return this.signPropagatingRightShiftS(value);
    return this.signPropagatingRightShiftM(value);
  };
  AbstractMatrix.prototype.signPropagatingRightShiftS = function signPropagatingRightShiftS(value) {
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, this.get(i, j) >> value);
      }
    }
    return this;
  };
  AbstractMatrix.prototype.signPropagatingRightShiftM = function signPropagatingRightShiftM(matrix) {
    matrix = Matrix.checkMatrix(matrix);
    if (this.rows !== matrix.rows || this.columns !== matrix.columns) {
      throw new RangeError('Matrices dimensions must be equal');
    }
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, this.get(i, j) >> matrix.get(i, j));
      }
    }
    return this;
  };
  AbstractMatrix.signPropagatingRightShift = function signPropagatingRightShift(matrix, value) {
    const newMatrix = new Matrix(matrix);
    return newMatrix.signPropagatingRightShift(value);
  };
  AbstractMatrix.prototype.rightShift = function rightShift(value) {
    if (typeof value === 'number') return this.rightShiftS(value);
    return this.rightShiftM(value);
  };
  AbstractMatrix.prototype.rightShiftS = function rightShiftS(value) {
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, this.get(i, j) >>> value);
      }
    }
    return this;
  };
  AbstractMatrix.prototype.rightShiftM = function rightShiftM(matrix) {
    matrix = Matrix.checkMatrix(matrix);
    if (this.rows !== matrix.rows || this.columns !== matrix.columns) {
      throw new RangeError('Matrices dimensions must be equal');
    }
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, this.get(i, j) >>> matrix.get(i, j));
      }
    }
    return this;
  };
  AbstractMatrix.rightShift = function rightShift(matrix, value) {
    const newMatrix = new Matrix(matrix);
    return newMatrix.rightShift(value);
  };
  AbstractMatrix.prototype.zeroFillRightShift = AbstractMatrix.prototype.rightShift;
  AbstractMatrix.prototype.zeroFillRightShiftS = AbstractMatrix.prototype.rightShiftS;
  AbstractMatrix.prototype.zeroFillRightShiftM = AbstractMatrix.prototype.rightShiftM;
  AbstractMatrix.zeroFillRightShift = AbstractMatrix.rightShift;
  AbstractMatrix.prototype.not = function not() {
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, ~this.get(i, j));
      }
    }
    return this;
  };
  AbstractMatrix.not = function not(matrix) {
    const newMatrix = new Matrix(matrix);
    return newMatrix.not();
  };
  AbstractMatrix.prototype.abs = function abs() {
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, Math.abs(this.get(i, j)));
      }
    }
    return this;
  };
  AbstractMatrix.abs = function abs(matrix) {
    const newMatrix = new Matrix(matrix);
    return newMatrix.abs();
  };
  AbstractMatrix.prototype.acos = function acos() {
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, Math.acos(this.get(i, j)));
      }
    }
    return this;
  };
  AbstractMatrix.acos = function acos(matrix) {
    const newMatrix = new Matrix(matrix);
    return newMatrix.acos();
  };
  AbstractMatrix.prototype.acosh = function acosh() {
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, Math.acosh(this.get(i, j)));
      }
    }
    return this;
  };
  AbstractMatrix.acosh = function acosh(matrix) {
    const newMatrix = new Matrix(matrix);
    return newMatrix.acosh();
  };
  AbstractMatrix.prototype.asin = function asin() {
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, Math.asin(this.get(i, j)));
      }
    }
    return this;
  };
  AbstractMatrix.asin = function asin(matrix) {
    const newMatrix = new Matrix(matrix);
    return newMatrix.asin();
  };
  AbstractMatrix.prototype.asinh = function asinh() {
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, Math.asinh(this.get(i, j)));
      }
    }
    return this;
  };
  AbstractMatrix.asinh = function asinh(matrix) {
    const newMatrix = new Matrix(matrix);
    return newMatrix.asinh();
  };
  AbstractMatrix.prototype.atan = function atan() {
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, Math.atan(this.get(i, j)));
      }
    }
    return this;
  };
  AbstractMatrix.atan = function atan(matrix) {
    const newMatrix = new Matrix(matrix);
    return newMatrix.atan();
  };
  AbstractMatrix.prototype.atanh = function atanh() {
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, Math.atanh(this.get(i, j)));
      }
    }
    return this;
  };
  AbstractMatrix.atanh = function atanh(matrix) {
    const newMatrix = new Matrix(matrix);
    return newMatrix.atanh();
  };
  AbstractMatrix.prototype.cbrt = function cbrt() {
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, Math.cbrt(this.get(i, j)));
      }
    }
    return this;
  };
  AbstractMatrix.cbrt = function cbrt(matrix) {
    const newMatrix = new Matrix(matrix);
    return newMatrix.cbrt();
  };
  AbstractMatrix.prototype.ceil = function ceil() {
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, Math.ceil(this.get(i, j)));
      }
    }
    return this;
  };
  AbstractMatrix.ceil = function ceil(matrix) {
    const newMatrix = new Matrix(matrix);
    return newMatrix.ceil();
  };
  AbstractMatrix.prototype.clz32 = function clz32() {
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, Math.clz32(this.get(i, j)));
      }
    }
    return this;
  };
  AbstractMatrix.clz32 = function clz32(matrix) {
    const newMatrix = new Matrix(matrix);
    return newMatrix.clz32();
  };
  AbstractMatrix.prototype.cos = function cos() {
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, Math.cos(this.get(i, j)));
      }
    }
    return this;
  };
  AbstractMatrix.cos = function cos(matrix) {
    const newMatrix = new Matrix(matrix);
    return newMatrix.cos();
  };
  AbstractMatrix.prototype.cosh = function cosh() {
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, Math.cosh(this.get(i, j)));
      }
    }
    return this;
  };
  AbstractMatrix.cosh = function cosh(matrix) {
    const newMatrix = new Matrix(matrix);
    return newMatrix.cosh();
  };
  AbstractMatrix.prototype.exp = function exp() {
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, Math.exp(this.get(i, j)));
      }
    }
    return this;
  };
  AbstractMatrix.exp = function exp(matrix) {
    const newMatrix = new Matrix(matrix);
    return newMatrix.exp();
  };
  AbstractMatrix.prototype.expm1 = function expm1() {
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, Math.expm1(this.get(i, j)));
      }
    }
    return this;
  };
  AbstractMatrix.expm1 = function expm1(matrix) {
    const newMatrix = new Matrix(matrix);
    return newMatrix.expm1();
  };
  AbstractMatrix.prototype.floor = function floor() {
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, Math.floor(this.get(i, j)));
      }
    }
    return this;
  };
  AbstractMatrix.floor = function floor(matrix) {
    const newMatrix = new Matrix(matrix);
    return newMatrix.floor();
  };
  AbstractMatrix.prototype.fround = function fround() {
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, Math.fround(this.get(i, j)));
      }
    }
    return this;
  };
  AbstractMatrix.fround = function fround(matrix) {
    const newMatrix = new Matrix(matrix);
    return newMatrix.fround();
  };
  AbstractMatrix.prototype.log = function log() {
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, Math.log(this.get(i, j)));
      }
    }
    return this;
  };
  AbstractMatrix.log = function log(matrix) {
    const newMatrix = new Matrix(matrix);
    return newMatrix.log();
  };
  AbstractMatrix.prototype.log1p = function log1p() {
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, Math.log1p(this.get(i, j)));
      }
    }
    return this;
  };
  AbstractMatrix.log1p = function log1p(matrix) {
    const newMatrix = new Matrix(matrix);
    return newMatrix.log1p();
  };
  AbstractMatrix.prototype.log10 = function log10() {
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, Math.log10(this.get(i, j)));
      }
    }
    return this;
  };
  AbstractMatrix.log10 = function log10(matrix) {
    const newMatrix = new Matrix(matrix);
    return newMatrix.log10();
  };
  AbstractMatrix.prototype.log2 = function log2() {
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, Math.log2(this.get(i, j)));
      }
    }
    return this;
  };
  AbstractMatrix.log2 = function log2(matrix) {
    const newMatrix = new Matrix(matrix);
    return newMatrix.log2();
  };
  AbstractMatrix.prototype.round = function round() {
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, Math.round(this.get(i, j)));
      }
    }
    return this;
  };
  AbstractMatrix.round = function round(matrix) {
    const newMatrix = new Matrix(matrix);
    return newMatrix.round();
  };
  AbstractMatrix.prototype.sign = function sign() {
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, Math.sign(this.get(i, j)));
      }
    }
    return this;
  };
  AbstractMatrix.sign = function sign(matrix) {
    const newMatrix = new Matrix(matrix);
    return newMatrix.sign();
  };
  AbstractMatrix.prototype.sin = function sin() {
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, Math.sin(this.get(i, j)));
      }
    }
    return this;
  };
  AbstractMatrix.sin = function sin(matrix) {
    const newMatrix = new Matrix(matrix);
    return newMatrix.sin();
  };
  AbstractMatrix.prototype.sinh = function sinh() {
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, Math.sinh(this.get(i, j)));
      }
    }
    return this;
  };
  AbstractMatrix.sinh = function sinh(matrix) {
    const newMatrix = new Matrix(matrix);
    return newMatrix.sinh();
  };
  AbstractMatrix.prototype.sqrt = function sqrt() {
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, Math.sqrt(this.get(i, j)));
      }
    }
    return this;
  };
  AbstractMatrix.sqrt = function sqrt(matrix) {
    const newMatrix = new Matrix(matrix);
    return newMatrix.sqrt();
  };
  AbstractMatrix.prototype.tan = function tan() {
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, Math.tan(this.get(i, j)));
      }
    }
    return this;
  };
  AbstractMatrix.tan = function tan(matrix) {
    const newMatrix = new Matrix(matrix);
    return newMatrix.tan();
  };
  AbstractMatrix.prototype.tanh = function tanh() {
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, Math.tanh(this.get(i, j)));
      }
    }
    return this;
  };
  AbstractMatrix.tanh = function tanh(matrix) {
    const newMatrix = new Matrix(matrix);
    return newMatrix.tanh();
  };
  AbstractMatrix.prototype.trunc = function trunc() {
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, Math.trunc(this.get(i, j)));
      }
    }
    return this;
  };
  AbstractMatrix.trunc = function trunc(matrix) {
    const newMatrix = new Matrix(matrix);
    return newMatrix.trunc();
  };
  AbstractMatrix.pow = function pow(matrix, arg0) {
    const newMatrix = new Matrix(matrix);
    return newMatrix.pow(arg0);
  };
  AbstractMatrix.prototype.pow = function pow(value) {
    if (typeof value === 'number') return this.powS(value);
    return this.powM(value);
  };
  AbstractMatrix.prototype.powS = function powS(value) {
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, this.get(i, j) ** value);
      }
    }
    return this;
  };
  AbstractMatrix.prototype.powM = function powM(matrix) {
    matrix = Matrix.checkMatrix(matrix);
    if (this.rows !== matrix.rows || this.columns !== matrix.columns) {
      throw new RangeError('Matrices dimensions must be equal');
    }
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, this.get(i, j) ** matrix.get(i, j));
      }
    }
    return this;
  };
}

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

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

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

/**
 * @private
 * Check that the provided vector is an array with the right length
 * @param {Matrix} matrix
 * @param {Array|Matrix} vector
 * @return {Array}
 * @throws {RangeError}
 */
function checkColumnVector(matrix, vector) {
  if (vector.to1DArray) {
    vector = vector.to1DArray();
  }
  if (vector.length !== matrix.rows) {
    throw new RangeError('vector size must be the same as the number of rows');
  }
  return vector;
}
function checkRowIndices(matrix, rowIndices) {
  if (!isAnyArray$1(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$1(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, value = 0) {
  let array = [];
  for (let i = 0; i < length; i++) {
    array.push(value);
  }
  return array;
}
function checkNumber(name, value) {
  if (typeof value !== 'number') {
    throw new TypeError(`${name} must be a number`);
  }
}
function checkNonEmpty(matrix) {
  if (matrix.isEmpty()) {
    throw new Error('Empty matrix has no elements to index');
  }
}
function sumByRow(matrix) {
  let sum = newArray(matrix.rows);
  for (let i = 0; i < matrix.rows; ++i) {
    for (let j = 0; j < matrix.columns; ++j) {
      sum[i] += matrix.get(i, j);
    }
  }
  return sum;
}
function sumByColumn(matrix) {
  let sum = newArray(matrix.columns);
  for (let i = 0; i < matrix.rows; ++i) {
    for (let j = 0; j < matrix.columns; ++j) {
      sum[j] += matrix.get(i, j);
    }
  }
  return sum;
}
function sumAll(matrix) {
  let v = 0;
  for (let i = 0; i < matrix.rows; i++) {
    for (let j = 0; j < matrix.columns; j++) {
      v += matrix.get(i, j);
    }
  }
  return v;
}
function productByRow(matrix) {
  let sum = newArray(matrix.rows, 1);
  for (let i = 0; i < matrix.rows; ++i) {
    for (let j = 0; j < matrix.columns; ++j) {
      sum[i] *= matrix.get(i, j);
    }
  }
  return sum;
}
function productByColumn(matrix) {
  let sum = newArray(matrix.columns, 1);
  for (let i = 0; i < matrix.rows; ++i) {
    for (let j = 0; j < matrix.columns; ++j) {
      sum[j] *= matrix.get(i, j);
    }
  }
  return sum;
}
function productAll(matrix) {
  let v = 1;
  for (let i = 0; i < matrix.rows; i++) {
    for (let j = 0; j < matrix.columns; j++) {
      v *= matrix.get(i, j);
    }
  }
  return v;
}
function varianceByRow(matrix, unbiased, mean) {
  const rows = matrix.rows;
  const cols = matrix.columns;
  const variance = [];
  for (let i = 0; i < rows; i++) {
    let sum1 = 0;
    let sum2 = 0;
    let x = 0;
    for (let j = 0; j < cols; j++) {
      x = matrix.get(i, j) - mean[i];
      sum1 += x;
      sum2 += x * x;
    }
    if (unbiased) {
      variance.push((sum2 - sum1 * sum1 / cols) / (cols - 1));
    } else {
      variance.push((sum2 - sum1 * sum1 / cols) / cols);
    }
  }
  return variance;
}
function varianceByColumn(matrix, unbiased, mean) {
  const rows = matrix.rows;
  const cols = matrix.columns;
  const variance = [];
  for (let j = 0; j < cols; j++) {
    let sum1 = 0;
    let sum2 = 0;
    let x = 0;
    for (let i = 0; i < rows; i++) {
      x = matrix.get(i, j) - mean[j];
      sum1 += x;
      sum2 += x * x;
    }
    if (unbiased) {
      variance.push((sum2 - sum1 * sum1 / rows) / (rows - 1));
    } else {
      variance.push((sum2 - sum1 * sum1 / rows) / rows);
    }
  }
  return variance;
}
function varianceAll(matrix, unbiased, mean) {
  const rows = matrix.rows;
  const cols = matrix.columns;
  const size = rows * cols;
  let sum1 = 0;
  let sum2 = 0;
  let x = 0;
  for (let i = 0; i < rows; i++) {
    for (let j = 0; j < cols; j++) {
      x = matrix.get(i, j) - mean;
      sum1 += x;
      sum2 += x * x;
    }
  }
  if (unbiased) {
    return (sum2 - sum1 * sum1 / size) / (size - 1);
  } else {
    return (sum2 - sum1 * sum1 / size) / size;
  }
}
function centerByRow(matrix, mean) {
  for (let i = 0; i < matrix.rows; i++) {
    for (let j = 0; j < matrix.columns; j++) {
      matrix.set(i, j, matrix.get(i, j) - mean[i]);
    }
  }
}
function centerByColumn(matrix, mean) {
  for (let i = 0; i < matrix.rows; i++) {
    for (let j = 0; j < matrix.columns; j++) {
      matrix.set(i, j, matrix.get(i, j) - mean[j]);
    }
  }
}
function centerAll(matrix, mean) {
  for (let i = 0; i < matrix.rows; i++) {
    for (let j = 0; j < matrix.columns; j++) {
      matrix.set(i, j, matrix.get(i, j) - mean);
    }
  }
}
function getScaleByRow(matrix) {
  const scale = [];
  for (let i = 0; i < matrix.rows; i++) {
    let sum = 0;
    for (let j = 0; j < matrix.columns; j++) {
      sum += matrix.get(i, j) ** 2 / (matrix.columns - 1);
    }
    scale.push(Math.sqrt(sum));
  }
  return scale;
}
function scaleByRow(matrix, scale) {
  for (let i = 0; i < matrix.rows; i++) {
    for (let j = 0; j < matrix.columns; j++) {
      matrix.set(i, j, matrix.get(i, j) / scale[i]);
    }
  }
}
function getScaleByColumn(matrix) {
  const scale = [];
  for (let j = 0; j < matrix.columns; j++) {
    let sum = 0;
    for (let i = 0; i < matrix.rows; i++) {
      sum += matrix.get(i, j) ** 2 / (matrix.rows - 1);
    }
    scale.push(Math.sqrt(sum));
  }
  return scale;
}
function scaleByColumn(matrix, scale) {
  for (let i = 0; i < matrix.rows; i++) {
    for (let j = 0; j < matrix.columns; j++) {
      matrix.set(i, j, matrix.get(i, j) / scale[j]);
    }
  }
}
function getScaleAll(matrix) {
  const divider = matrix.size - 1;
  let sum = 0;
  for (let j = 0; j < matrix.columns; j++) {
    for (let i = 0; i < matrix.rows; i++) {
      sum += matrix.get(i, j) ** 2 / divider;
    }
  }
  return Math.sqrt(sum);
}
function scaleAll(matrix, scale) {
  for (let i = 0; i < matrix.rows; i++) {
    for (let j = 0; j < matrix.columns; j++) {
      matrix.set(i, j, matrix.get(i, j) / scale);
    }
  }
}
class AbstractMatrix {
  static from1DArray(newRows, newColumns, newData) {
    let length = newRows * newColumns;
    if (length !== newData.length) {
      throw new RangeError('data length does not match given dimensions');
    }
    let newMatrix = new Matrix$1(newRows, newColumns);
    for (let row = 0; row < newRows; row++) {
      for (let column = 0; column < newColumns; column++) {
        newMatrix.set(row, column, newData[row * newColumns + column]);
      }
    }
    return newMatrix;
  }
  static rowVector(newData) {
    let vector = new Matrix$1(1, newData.length);
    for (let i = 0; i < newData.length; i++) {
      vector.set(0, i, newData[i]);
    }
    return vector;
  }
  static columnVector(newData) {
    let vector = new Matrix$1(newData.length, 1);
    for (let i = 0; i < newData.length; i++) {
      vector.set(i, 0, newData[i]);
    }
    return vector;
  }
  static zeros(rows, columns) {
    return new Matrix$1(rows, columns);
  }
  static ones(rows, columns) {
    return new Matrix$1(rows, columns).fill(1);
  }
  static rand(rows, columns, options = {}) {
    if (typeof options !== 'object') {
      throw new TypeError('options must be an object');
    }
    const {
      random = Math.random
    } = options;
    let matrix = new Matrix$1(rows, columns);
    for (let i = 0; i < rows; i++) {
      for (let j = 0; j < columns; j++) {
        matrix.set(i, j, random());
      }
    }
    return matrix;
  }
  static randInt(rows, columns, options = {}) {
    if (typeof options !== 'object') {
      throw new TypeError('options must be an object');
    }
    const {
      min = 0,
      max = 1000,
      random = Math.random
    } = options;
    if (!Number.isInteger(min)) throw new TypeError('min must be an integer');
    if (!Number.isInteger(max)) throw new TypeError('max must be an integer');
    if (min >= max) throw new RangeError('min must be smaller than max');
    let interval = max - min;
    let matrix = new Matrix$1(rows, columns);
    for (let i = 0; i < rows; i++) {
      for (let j = 0; j < columns; j++) {
        let value = min + Math.round(random() * interval);
        matrix.set(i, j, value);
      }
    }
    return matrix;
  }
  static eye(rows, columns, value) {
    if (columns === undefined) columns = rows;
    if (value === undefined) value = 1;
    let min = Math.min(rows, columns);
    let matrix = this.zeros(rows, columns);
    for (let i = 0; i < min; i++) {
      matrix.set(i, i, value);
    }
    return matrix;
  }
  static diag(data, rows, columns) {
    let l = data.length;
    if (rows === undefined) rows = l;
    if (columns === undefined) columns = rows;
    let min = Math.min(l, rows, columns);
    let matrix = this.zeros(rows, columns);
    for (let i = 0; i < min; i++) {
      matrix.set(i, i, data[i]);
    }
    return matrix;
  }
  static min(matrix1, matrix2) {
    matrix1 = this.checkMatrix(matrix1);
    matrix2 = this.checkMatrix(matrix2);
    let rows = matrix1.rows;
    let columns = matrix1.columns;
    let result = new Matrix$1(rows, columns);
    for (let i = 0; i < rows; i++) {
      for (let j = 0; j < columns; j++) {
        result.set(i, j, Math.min(matrix1.get(i, j), matrix2.get(i, j)));
      }
    }
    return result;
  }
  static max(matrix1, matrix2) {
    matrix1 = this.checkMatrix(matrix1);
    matrix2 = this.checkMatrix(matrix2);
    let rows = matrix1.rows;
    let columns = matrix1.columns;
    let result = new this(rows, columns);
    for (let i = 0; i < rows; i++) {
      for (let j = 0; j < columns; j++) {
        result.set(i, j, Math.max(matrix1.get(i, j), matrix2.get(i, j)));
      }
    }
    return result;
  }
  static checkMatrix(value) {
    return AbstractMatrix.isMatrix(value) ? value : new Matrix$1(value);
  }
  static isMatrix(value) {
    return value != null && value.klass === 'Matrix';
  }
  get size() {
    return this.rows * this.columns;
  }
  apply(callback) {
    if (typeof callback !== 'function') {
      throw new TypeError('callback must be a function');
    }
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        callback.call(this, i, j);
      }
    }
    return this;
  }
  to1DArray() {
    let array = [];
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        array.push(this.get(i, j));
      }
    }
    return array;
  }
  to2DArray() {
    let copy = [];
    for (let i = 0; i < this.rows; i++) {
      copy.push([]);
      for (let j = 0; j < this.columns; j++) {
        copy[i].push(this.get(i, j));
      }
    }
    return copy;
  }
  toJSON() {
    return this.to2DArray();
  }
  isRowVector() {
    return this.rows === 1;
  }
  isColumnVector() {
    return this.columns === 1;
  }
  isVector() {
    return this.rows === 1 || this.columns === 1;
  }
  isSquare() {
    return this.rows === this.columns;
  }
  isEmpty() {
    return this.rows === 0 || this.columns === 0;
  }
  isSymmetric() {
    if (this.isSquare()) {
      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j <= i; j++) {
          if (this.get(i, j) !== this.get(j, i)) {
            return false;
          }
        }
      }
      return true;
    }
    return false;
  }
  isDistance() {
    if (!this.isSymmetric()) return false;
    for (let i = 0; i < this.rows; i++) {
      if (this.get(i, i) !== 0) return false;
    }
    return true;
  }
  isEchelonForm() {
    let i = 0;
    let j = 0;
    let previousColumn = -1;
    let isEchelonForm = true;
    let checked = false;
    while (i < this.rows && isEchelonForm) {
      j = 0;
      checked = false;
      while (j < this.columns && checked === false) {
        if (this.get(i, j) === 0) {
          j++;
        } else if (this.get(i, j) === 1 && j > previousColumn) {
          checked = true;
          previousColumn = j;
        } else {
          isEchelonForm = false;
          checked = true;
        }
      }
      i++;
    }
    return isEchelonForm;
  }
  isReducedEchelonForm() {
    let i = 0;
    let j = 0;
    let previousColumn = -1;
    let isReducedEchelonForm = true;
    let checked = false;
    while (i < this.rows && isReducedEchelonForm) {
      j = 0;
      checked = false;
      while (j < this.columns && checked === false) {
        if (this.get(i, j) === 0) {
          j++;
        } else if (this.get(i, j) === 1 && j > previousColumn) {
          checked = true;
          previousColumn = j;
        } else {
          isReducedEchelonForm = false;
          checked = true;
        }
      }
      for (let k = j + 1; k < this.rows; k++) {
        if (this.get(i, k) !== 0) {
          isReducedEchelonForm = false;
        }
      }
      i++;
    }
    return isReducedEchelonForm;
  }
  echelonForm() {
    let result = this.clone();
    let h = 0;
    let k = 0;
    while (h < result.rows && k < result.columns) {
      let iMax = h;
      for (let i = h; i < result.rows; i++) {
        if (result.get(i, k) > result.get(iMax, k)) {
          iMax = i;
        }
      }
      if (result.get(iMax, k) === 0) {
        k++;
      } else {
        result.swapRows(h, iMax);
        let tmp = result.get(h, k);
        for (let j = k; j < result.columns; j++) {
          result.set(h, j, result.get(h, j) / tmp);
        }
        for (let i = h + 1; i < result.rows; i++) {
          let factor = result.get(i, k) / result.get(h, k);
          result.set(i, k, 0);
          for (let j = k + 1; j < result.columns; j++) {
            result.set(i, j, result.get(i, j) - result.get(h, j) * factor);
          }
        }
        h++;
        k++;
      }
    }
    return result;
  }
  reducedEchelonForm() {
    let result = this.echelonForm();
    let m = result.columns;
    let n = result.rows;
    let h = n - 1;
    while (h >= 0) {
      if (result.maxRow(h) === 0) {
        h--;
      } else {
        let p = 0;
        let pivot = false;
        while (p < n && pivot === false) {
          if (result.get(h, p) === 1) {
            pivot = true;
          } else {
            p++;
          }
        }
        for (let i = 0; i < h; i++) {
          let factor = result.get(i, p);
          for (let j = p; j < m; j++) {
            let tmp = result.get(i, j) - factor * result.get(h, j);
            result.set(i, j, tmp);
          }
        }
        h--;
      }
    }
    return result;
  }
  set() {
    throw new Error('set method is unimplemented');
  }
  get() {
    throw new Error('get method is unimplemented');
  }
  repeat(options = {}) {
    if (typeof options !== 'object') {
      throw new TypeError('options must be an object');
    }
    const {
      rows = 1,
      columns = 1
    } = options;
    if (!Number.isInteger(rows) || rows <= 0) {
      throw new TypeError('rows must be a positive integer');
    }
    if (!Number.isInteger(columns) || columns <= 0) {
      throw new TypeError('columns must be a positive integer');
    }
    let matrix = new Matrix$1(this.rows * rows, this.columns * columns);
    for (let i = 0; i < rows; i++) {
      for (let j = 0; j < columns; j++) {
        matrix.setSubMatrix(this, this.rows * i, this.columns * j);
      }
    }
    return matrix;
  }
  fill(value) {
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, value);
      }
    }
    return this;
  }
  neg() {
    return this.mulS(-1);
  }
  getRow(index) {
    checkRowIndex(this, index);
    let row = [];
    for (let i = 0; i < this.columns; i++) {
      row.push(this.get(index, i));
    }
    return row;
  }
  getRowVector(index) {
    return Matrix$1.rowVector(this.getRow(index));
  }
  setRow(index, array) {
    checkRowIndex(this, index);
    array = checkRowVector(this, array);
    for (let i = 0; i < this.columns; i++) {
      this.set(index, i, array[i]);
    }
    return this;
  }
  swapRows(row1, row2) {
    checkRowIndex(this, row1);
    checkRowIndex(this, row2);
    for (let i = 0; i < this.columns; i++) {
      let temp = this.get(row1, i);
      this.set(row1, i, this.get(row2, i));
      this.set(row2, i, temp);
    }
    return this;
  }
  getColumn(index) {
    checkColumnIndex(this, index);
    let column = [];
    for (let i = 0; i < this.rows; i++) {
      column.push(this.get(i, index));
    }
    return column;
  }
  getColumnVector(index) {
    return Matrix$1.columnVector(this.getColumn(index));
  }
  setColumn(index, array) {
    checkColumnIndex(this, index);
    array = checkColumnVector(this, array);
    for (let i = 0; i < this.rows; i++) {
      this.set(i, index, array[i]);
    }
    return this;
  }
  swapColumns(column1, column2) {
    checkColumnIndex(this, column1);
    checkColumnIndex(this, column2);
    for (let i = 0; i < this.rows; i++) {
      let temp = this.get(i, column1);
      this.set(i, column1, this.get(i, column2));
      this.set(i, column2, temp);
    }
    return this;
  }
  addRowVector(vector) {
    vector = checkRowVector(this, vector);
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, this.get(i, j) + vector[j]);
      }
    }
    return this;
  }
  subRowVector(vector) {
    vector = checkRowVector(this, vector);
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, this.get(i, j) - vector[j]);
      }
    }
    return this;
  }
  mulRowVector(vector) {
    vector = checkRowVector(this, vector);
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, this.get(i, j) * vector[j]);
      }
    }
    return this;
  }
  divRowVector(vector) {
    vector = checkRowVector(this, vector);
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, this.get(i, j) / vector[j]);
      }
    }
    return this;
  }
  addColumnVector(vector) {
    vector = checkColumnVector(this, vector);
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, this.get(i, j) + vector[i]);
      }
    }
    return this;
  }
  subColumnVector(vector) {
    vector = checkColumnVector(this, vector);
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, this.get(i, j) - vector[i]);
      }
    }
    return this;
  }
  mulColumnVector(vector) {
    vector = checkColumnVector(this, vector);
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, this.get(i, j) * vector[i]);
      }
    }
    return this;
  }
  divColumnVector(vector) {
    vector = checkColumnVector(this, vector);
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, this.get(i, j) / vector[i]);
      }
    }
    return this;
  }
  mulRow(index, value) {
    checkRowIndex(this, index);
    for (let i = 0; i < this.columns; i++) {
      this.set(index, i, this.get(index, i) * value);
    }
    return this;
  }
  mulColumn(index, value) {
    checkColumnIndex(this, index);
    for (let i = 0; i < this.rows; i++) {
      this.set(i, index, this.get(i, index) * value);
    }
    return this;
  }
  max(by) {
    if (this.isEmpty()) {
      return NaN;
    }
    switch (by) {
      case 'row':
        {
          const max = new Array(this.rows).fill(Number.NEGATIVE_INFINITY);
          for (let row = 0; row < this.rows; row++) {
            for (let column = 0; column < this.columns; column++) {
              if (this.get(row, column) > max[row]) {
                max[row] = this.get(row, column);
              }
            }
          }
          return max;
        }
      case 'column':
        {
          const max = new Array(this.columns).fill(Number.NEGATIVE_INFINITY);
          for (let row = 0; row < this.rows; row++) {
            for (let column = 0; column < this.columns; column++) {
              if (this.get(row, column) > max[column]) {
                max[column] = this.get(row, column);
              }
            }
          }
          return max;
        }
      case undefined:
        {
          let max = this.get(0, 0);
          for (let row = 0; row < this.rows; row++) {
            for (let column = 0; column < this.columns; column++) {
              if (this.get(row, column) > max) {
                max = this.get(row, column);
              }
            }
          }
          return max;
        }
      default:
        throw new Error(`invalid option: ${by}`);
    }
  }
  maxIndex() {
    checkNonEmpty(this);
    let v = this.get(0, 0);
    let idx = [0, 0];
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        if (this.get(i, j) > v) {
          v = this.get(i, j);
          idx[0] = i;
          idx[1] = j;
        }
      }
    }
    return idx;
  }
  min(by) {
    if (this.isEmpty()) {
      return NaN;
    }
    switch (by) {
      case 'row':
        {
          const min = new Array(this.rows).fill(Number.POSITIVE_INFINITY);
          for (let row = 0; row < this.rows; row++) {
            for (let column = 0; column < this.columns; column++) {
              if (this.get(row, column) < min[row]) {
                min[row] = this.get(row, column);
              }
            }
          }
          return min;
        }
      case 'column':
        {
          const min = new Array(this.columns).fill(Number.POSITIVE_INFINITY);
          for (let row = 0; row < this.rows; row++) {
            for (let column = 0; column < this.columns; column++) {
              if (this.get(row, column) < min[column]) {
                min[column] = this.get(row, column);
              }
            }
          }
          return min;
        }
      case undefined:
        {
          let min = this.get(0, 0);
          for (let row = 0; row < this.rows; row++) {
            for (let column = 0; column < this.columns; column++) {
              if (this.get(row, column) < min) {
                min = this.get(row, column);
              }
            }
          }
          return min;
        }
      default:
        throw new Error(`invalid option: ${by}`);
    }
  }
  minIndex() {
    checkNonEmpty(this);
    let v = this.get(0, 0);
    let idx = [0, 0];
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        if (this.get(i, j) < v) {
          v = this.get(i, j);
          idx[0] = i;
          idx[1] = j;
        }
      }
    }
    return idx;
  }
  maxRow(row) {
    checkRowIndex(this, row);
    if (this.isEmpty()) {
      return NaN;
    }
    let v = this.get(row, 0);
    for (let i = 1; i < this.columns; i++) {
      if (this.get(row, i) > v) {
        v = this.get(row, i);
      }
    }
    return v;
  }
  maxRowIndex(row) {
    checkRowIndex(this, row);
    checkNonEmpty(this);
    let v = this.get(row, 0);
    let idx = [row, 0];
    for (let i = 1; i < this.columns; i++) {
      if (this.get(row, i) > v) {
        v = this.get(row, i);
        idx[1] = i;
      }
    }
    return idx;
  }
  minRow(row) {
    checkRowIndex(this, row);
    if (this.isEmpty()) {
      return NaN;
    }
    let v = this.get(row, 0);
    for (let i = 1; i < this.columns; i++) {
      if (this.get(row, i) < v) {
        v = this.get(row, i);
      }
    }
    return v;
  }
  minRowIndex(row) {
    checkRowIndex(this, row);
    checkNonEmpty(this);
    let v = this.get(row, 0);
    let idx = [row, 0];
    for (let i = 1; i < this.columns; i++) {
      if (this.get(row, i) < v) {
        v = this.get(row, i);
        idx[1] = i;
      }
    }
    return idx;
  }
  maxColumn(column) {
    checkColumnIndex(this, column);
    if (this.isEmpty()) {
      return NaN;
    }
    let v = this.get(0, column);
    for (let i = 1; i < this.rows; i++) {
      if (this.get(i, column) > v) {
        v = this.get(i, column);
      }
    }
    return v;
  }
  maxColumnIndex(column) {
    checkColumnIndex(this, column);
    checkNonEmpty(this);
    let v = this.get(0, column);
    let idx = [0, column];
    for (let i = 1; i < this.rows; i++) {
      if (this.get(i, column) > v) {
        v = this.get(i, column);
        idx[0] = i;
      }
    }
    return idx;
  }
  minColumn(column) {
    checkColumnIndex(this, column);
    if (this.isEmpty()) {
      return NaN;
    }
    let v = this.get(0, column);
    for (let i = 1; i < this.rows; i++) {
      if (this.get(i, column) < v) {
        v = this.get(i, column);
      }
    }
    return v;
  }
  minColumnIndex(column) {
    checkColumnIndex(this, column);
    checkNonEmpty(this);
    let v = this.get(0, column);
    let idx = [0, column];
    for (let i = 1; i < this.rows; i++) {
      if (this.get(i, column) < v) {
        v = this.get(i, column);
        idx[0] = i;
      }
    }
    return idx;
  }
  diag() {
    let min = Math.min(this.rows, this.columns);
    let diag = [];
    for (let i = 0; i < min; i++) {
      diag.push(this.get(i, i));
    }
    return diag;
  }
  norm(type = 'frobenius') {
    switch (type) {
      case 'max':
        return this.max();
      case 'frobenius':
        return Math.sqrt(this.dot(this));
      default:
        throw new RangeError(`unknown norm type: ${type}`);
    }
  }
  cumulativeSum() {
    let sum = 0;
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        sum += this.get(i, j);
        this.set(i, j, sum);
      }
    }
    return this;
  }
  dot(vector2) {
    if (AbstractMatrix.isMatrix(vector2)) vector2 = vector2.to1DArray();
    let vector1 = this.to1DArray();
    if (vector1.length !== vector2.length) {
      throw new RangeError('vectors do not have the same size');
    }
    let dot = 0;
    for (let i = 0; i < vector1.length; i++) {
      dot += vector1[i] * vector2[i];
    }
    return dot;
  }
  mmul(other) {
    other = Matrix$1.checkMatrix(other);
    let m = this.rows;
    let n = this.columns;
    let p = other.columns;
    let result = new Matrix$1(m, p);
    let Bcolj = new Float64Array(n);
    for (let j = 0; j < p; j++) {
      for (let k = 0; k < n; k++) {
        Bcolj[k] = other.get(k, j);
      }
      for (let i = 0; i < m; i++) {
        let s = 0;
        for (let k = 0; k < n; k++) {
          s += this.get(i, k) * Bcolj[k];
        }
        result.set(i, j, s);
      }
    }
    return result;
  }
  mpow(scalar) {
    if (!this.isSquare()) {
      throw new RangeError('Matrix must be square');
    }
    if (!Number.isInteger(scalar) || scalar < 0) {
      throw new RangeError('Exponent must be a non-negative integer');
    }
    // Russian Peasant exponentiation, i.e. exponentiation by squaring
    let result = Matrix$1.eye(this.rows);
    let bb = this;
    // Note: Don't bit shift. In JS, that would truncate at 32 bits
    for (let e = scalar; e >= 1; e /= 2) {
      if ((e & 1) !== 0) {
        result = result.mmul(bb);
      }
      bb = bb.mmul(bb);
    }
    return result;
  }
  strassen2x2(other) {
    other = Matrix$1.checkMatrix(other);
    let result = new Matrix$1(2, 2);
    const a11 = this.get(0, 0);
    const b11 = other.get(0, 0);
    const a12 = this.get(0, 1);
    const b12 = other.get(0, 1);
    const a21 = this.get(1, 0);
    const b21 = other.get(1, 0);
    const a22 = this.get(1, 1);
    const b22 = other.get(1, 1);

    // Compute intermediate values.
    const m1 = (a11 + a22) * (b11 + b22);
    const m2 = (a21 + a22) * b11;
    const m3 = a11 * (b12 - b22);
    const m4 = a22 * (b21 - b11);
    const m5 = (a11 + a12) * b22;
    const m6 = (a21 - a11) * (b11 + b12);
    const m7 = (a12 - a22) * (b21 + b22);

    // Combine intermediate values into the output.
    const c00 = m1 + m4 - m5 + m7;
    const c01 = m3 + m5;
    const c10 = m2 + m4;
    const c11 = m1 - m2 + m3 + m6;
    result.set(0, 0, c00);
    result.set(0, 1, c01);
    result.set(1, 0, c10);
    result.set(1, 1, c11);
    return result;
  }
  strassen3x3(other) {
    other = Matrix$1.checkMatrix(other);
    let result = new Matrix$1(3, 3);
    const a00 = this.get(0, 0);
    const a01 = this.get(0, 1);
    const a02 = this.get(0, 2);
    const a10 = this.get(1, 0);
    const a11 = this.get(1, 1);
    const a12 = this.get(1, 2);
    const a20 = this.get(2, 0);
    const a21 = this.get(2, 1);
    const a22 = this.get(2, 2);
    const b00 = other.get(0, 0);
    const b01 = other.get(0, 1);
    const b02 = other.get(0, 2);
    const b10 = other.get(1, 0);
    const b11 = other.get(1, 1);
    const b12 = other.get(1, 2);
    const b20 = other.get(2, 0);
    const b21 = other.get(2, 1);
    const b22 = other.get(2, 2);
    const m1 = (a00 + a01 + a02 - a10 - a11 - a21 - a22) * b11;
    const m2 = (a00 - a10) * (-b01 + b11);
    const m3 = a11 * (-b00 + b01 + b10 - b11 - b12 - b20 + b22);
    const m4 = (-a00 + a10 + a11) * (b00 - b01 + b11);
    const m5 = (a10 + a11) * (-b00 + b01);
    const m6 = a00 * b00;
    const m7 = (-a00 + a20 + a21) * (b00 - b02 + b12);
    const m8 = (-a00 + a20) * (b02 - b12);
    const m9 = (a20 + a21) * (-b00 + b02);
    const m10 = (a00 + a01 + a02 - a11 - a12 - a20 - a21) * b12;
    const m11 = a21 * (-b00 + b02 + b10 - b11 - b12 - b20 + b21);
    const m12 = (-a02 + a21 + a22) * (b11 + b20 - b21);
    const m13 = (a02 - a22) * (b11 - b21);
    const m14 = a02 * b20;
    const m15 = (a21 + a22) * (-b20 + b21);
    const m16 = (-a02 + a11 + a12) * (b12 + b20 - b22);
    const m17 = (a02 - a12) * (b12 - b22);
    const m18 = (a11 + a12) * (-b20 + b22);
    const m19 = a01 * b10;
    const m20 = a12 * b21;
    const m21 = a10 * b02;
    const m22 = a20 * b01;
    const m23 = a22 * b22;
    const c00 = m6 + m14 + m19;
    const c01 = m1 + m4 + m5 + m6 + m12 + m14 + m15;
    const c02 = m6 + m7 + m9 + m10 + m14 + m16 + m18;
    const c10 = m2 + m3 + m4 + m6 + m14 + m16 + m17;
    const c11 = m2 + m4 + m5 + m6 + m20;
    const c12 = m14 + m16 + m17 + m18 + m21;
    const c20 = m6 + m7 + m8 + m11 + m12 + m13 + m14;
    const c21 = m12 + m13 + m14 + m15 + m22;
    const c22 = m6 + m7 + m8 + m9 + m23;
    result.set(0, 0, c00);
    result.set(0, 1, c01);
    result.set(0, 2, c02);
    result.set(1, 0, c10);
    result.set(1, 1, c11);
    result.set(1, 2, c12);
    result.set(2, 0, c20);
    result.set(2, 1, c21);
    result.set(2, 2, c22);
    return result;
  }
  mmulStrassen(y) {
    y = Matrix$1.checkMatrix(y);
    let x = this.clone();
    let r1 = x.rows;
    let c1 = x.columns;
    let r2 = y.rows;
    let c2 = y.columns;
    if (c1 !== r2) {
      // eslint-disable-next-line no-console
      console.warn(`Multiplying ${r1} x ${c1} and ${r2} x ${c2} matrix: dimensions do not match.`);
    }

    // Put a matrix into the top left of a matrix of zeros.
    // `rows` and `cols` are the dimensions of the output matrix.
    function embed(mat, rows, cols) {
      let r = mat.rows;
      let c = mat.columns;
      if (r === rows && c === cols) {
        return mat;
      } else {
        let resultat = AbstractMatrix.zeros(rows, cols);
        resultat = resultat.setSubMatrix(mat, 0, 0);
        return resultat;
      }
    }

    // Make sure both matrices are the same size.
    // This is exclusively for simplicity:
    // this algorithm can be implemented with matrices of different sizes.

    let r = Math.max(r1, r2);
    let c = Math.max(c1, c2);
    x = embed(x, r, c);
    y = embed(y, r, c);

    // Our recursive multiplication function.
    function blockMult(a, b, rows, cols) {
      // For small matrices, resort to naive multiplication.
      if (rows <= 512 || cols <= 512) {
        return a.mmul(b); // a is equivalent to this
      }

      // Apply dynamic padding.
      if (rows % 2 === 1 && cols % 2 === 1) {
        a = embed(a, rows + 1, cols + 1);
        b = embed(b, rows + 1, cols + 1);
      } else if (rows % 2 === 1) {
        a = embed(a, rows + 1, cols);
        b = embed(b, rows + 1, cols);
      } else if (cols % 2 === 1) {
        a = embed(a, rows, cols + 1);
        b = embed(b, rows, cols + 1);
      }
      let halfRows = parseInt(a.rows / 2, 10);
      let halfCols = parseInt(a.columns / 2, 10);
      // Subdivide input matrices.
      let a11 = a.subMatrix(0, halfRows - 1, 0, halfCols - 1);
      let b11 = b.subMatrix(0, halfRows - 1, 0, halfCols - 1);
      let a12 = a.subMatrix(0, halfRows - 1, halfCols, a.columns - 1);
      let b12 = b.subMatrix(0, halfRows - 1, halfCols, b.columns - 1);
      let a21 = a.subMatrix(halfRows, a.rows - 1, 0, halfCols - 1);
      let b21 = b.subMatrix(halfRows, b.rows - 1, 0, halfCols - 1);
      let a22 = a.subMatrix(halfRows, a.rows - 1, halfCols, a.columns - 1);
      let b22 = b.subMatrix(halfRows, b.rows - 1, halfCols, b.columns - 1);

      // Compute intermediate values.
      let m1 = blockMult(AbstractMatrix.add(a11, a22), AbstractMatrix.add(b11, b22), halfRows, halfCols);
      let m2 = blockMult(AbstractMatrix.add(a21, a22), b11, halfRows, halfCols);
      let m3 = blockMult(a11, AbstractMatrix.sub(b12, b22), halfRows, halfCols);
      let m4 = blockMult(a22, AbstractMatrix.sub(b21, b11), halfRows, halfCols);
      let m5 = blockMult(AbstractMatrix.add(a11, a12), b22, halfRows, halfCols);
      let m6 = blockMult(AbstractMatrix.sub(a21, a11), AbstractMatrix.add(b11, b12), halfRows, halfCols);
      let m7 = blockMult(AbstractMatrix.sub(a12, a22), AbstractMatrix.add(b21, b22), halfRows, halfCols);

      // Combine intermediate values into the output.
      let c11 = AbstractMatrix.add(m1, m4);
      c11.sub(m5);
      c11.add(m7);
      let c12 = AbstractMatrix.add(m3, m5);
      let c21 = AbstractMatrix.add(m2, m4);
      let c22 = AbstractMatrix.sub(m1, m2);
      c22.add(m3);
      c22.add(m6);

      // Crop output to the desired size (undo dynamic padding).
      let result = AbstractMatrix.zeros(2 * c11.rows, 2 * c11.columns);
      result = result.setSubMatrix(c11, 0, 0);
      result = result.setSubMatrix(c12, c11.rows, 0);
      result = result.setSubMatrix(c21, 0, c11.columns);
      result = result.setSubMatrix(c22, c11.rows, c11.columns);
      return result.subMatrix(0, rows - 1, 0, cols - 1);
    }
    return blockMult(x, y, r, c);
  }
  scaleRows(options = {}) {
    if (typeof options !== 'object') {
      throw new TypeError('options must be an object');
    }
    const {
      min = 0,
      max = 1
    } = options;
    if (!Number.isFinite(min)) throw new TypeError('min must be a number');
    if (!Number.isFinite(max)) throw new TypeError('max must be a number');
    if (min >= max) throw new RangeError('min must be smaller than max');
    let newMatrix = new Matrix$1(this.rows, this.columns);
    for (let i = 0; i < this.rows; i++) {
      const row = this.getRow(i);
      if (row.length > 0) {
        rescale$1(row, {
          min,
          max,
          output: row
        });
      }
      newMatrix.setRow(i, row);
    }
    return newMatrix;
  }
  scaleColumns(options = {}) {
    if (typeof options !== 'object') {
      throw new TypeError('options must be an object');
    }
    const {
      min = 0,
      max = 1
    } = options;
    if (!Number.isFinite(min)) throw new TypeError('min must be a number');
    if (!Number.isFinite(max)) throw new TypeError('max must be a number');
    if (min >= max) throw new RangeError('min must be smaller than max');
    let newMatrix = new Matrix$1(this.rows, this.columns);
    for (let i = 0; i < this.columns; i++) {
      const column = this.getColumn(i);
      if (column.length) {
        rescale$1(column, {
          min,
          max,
          output: column
        });
      }
      newMatrix.setColumn(i, column);
    }
    return newMatrix;
  }
  flipRows() {
    const middle = Math.ceil(this.columns / 2);
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < middle; j++) {
        let first = this.get(i, j);
        let last = this.get(i, this.columns - 1 - j);
        this.set(i, j, last);
        this.set(i, this.columns - 1 - j, first);
      }
    }
    return this;
  }
  flipColumns() {
    const middle = Math.ceil(this.rows / 2);
    for (let j = 0; j < this.columns; j++) {
      for (let i = 0; i < middle; i++) {
        let first = this.get(i, j);
        let last = this.get(this.rows - 1 - i, j);
        this.set(i, j, last);
        this.set(this.rows - 1 - i, j, first);
      }
    }
    return this;
  }
  kroneckerProduct(other) {
    other = Matrix$1.checkMatrix(other);
    let m = this.rows;
    let n = this.columns;
    let p = other.rows;
    let q = other.columns;
    let result = new Matrix$1(m * p, n * q);
    for (let i = 0; i < m; i++) {
      for (let j = 0; j < n; j++) {
        for (let k = 0; k < p; k++) {
          for (let l = 0; l < q; l++) {
            result.set(p * i + k, q * j + l, this.get(i, j) * other.get(k, l));
          }
        }
      }
    }
    return result;
  }
  kroneckerSum(other) {
    other = Matrix$1.checkMatrix(other);
    if (!this.isSquare() || !other.isSquare()) {
      throw new Error('Kronecker Sum needs two Square Matrices');
    }
    let m = this.rows;
    let n = other.rows;
    let AxI = this.kroneckerProduct(Matrix$1.eye(n, n));
    let IxB = Matrix$1.eye(m, m).kroneckerProduct(other);
    return AxI.add(IxB);
  }
  transpose() {
    let result = new Matrix$1(this.columns, this.rows);
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        result.set(j, i, this.get(i, j));
      }
    }
    return result;
  }
  sortRows(compareFunction = compareNumbers) {
    for (let i = 0; i < this.rows; i++) {
      this.setRow(i, this.getRow(i).sort(compareFunction));
    }
    return this;
  }
  sortColumns(compareFunction = compareNumbers) {
    for (let i = 0; i < this.columns; i++) {
      this.setColumn(i, this.getColumn(i).sort(compareFunction));
    }
    return this;
  }
  subMatrix(startRow, endRow, startColumn, endColumn) {
    checkRange(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() {
    return this.constructor.copy(this, new Matrix$1(this.rows, this.columns));
  }

  /**
   * @template {AbstractMatrix} M
   * @param {AbstractMatrix} from
   * @param {M} to
   * @return {M}
   */
  static copy(from, to) {
    for (const [row, column, value] of from.entries()) {
      to.set(row, column, value);
    }
    return to;
  }
  sum(by) {
    switch (by) {
      case 'row':
        return sumByRow(this);
      case 'column':
        return sumByColumn(this);
      case undefined:
        return sumAll(this);
      default:
        throw new Error(`invalid option: ${by}`);
    }
  }
  product(by) {
    switch (by) {
      case 'row':
        return productByRow(this);
      case 'column':
        return productByColumn(this);
      case undefined:
        return productAll(this);
      default:
        throw new Error(`invalid option: ${by}`);
    }
  }
  mean(by) {
    const sum = this.sum(by);
    switch (by) {
      case 'row':
        {
          for (let i = 0; i < this.rows; i++) {
            sum[i] /= this.columns;
          }
          return sum;
        }
      case 'column':
        {
          for (let i = 0; i < this.columns; i++) {
            sum[i] /= this.rows;
          }
          return sum;
        }
      case undefined:
        return sum / this.size;
      default:
        throw new Error(`invalid option: ${by}`);
    }
  }
  variance(by, options = {}) {
    if (typeof by === 'object') {
      options = by;
      by = undefined;
    }
    if (typeof options !== 'object') {
      throw new TypeError('options must be an object');
    }
    const {
      unbiased = true,
      mean = this.mean(by)
    } = options;
    if (typeof unbiased !== 'boolean') {
      throw new TypeError('unbiased must be a boolean');
    }
    switch (by) {
      case 'row':
        {
          if (!isAnyArray$1(mean)) {
            throw new TypeError('mean must be an array');
          }
          return varianceByRow(this, unbiased, mean);
        }
      case 'column':
        {
          if (!isAnyArray$1(mean)) {
            throw new TypeError('mean must be an array');
          }
          return varianceByColumn(this, unbiased, mean);
        }
      case undefined:
        {
          if (typeof mean !== 'number') {
            throw new TypeError('mean must be a number');
          }
          return varianceAll(this, unbiased, mean);
        }
      default:
        throw new Error(`invalid option: ${by}`);
    }
  }
  standardDeviation(by, options) {
    if (typeof by === 'object') {
      options = by;
      by = undefined;
    }
    const variance = this.variance(by, options);
    if (by === undefined) {
      return Math.sqrt(variance);
    } else {
      for (let i = 0; i < variance.length; i++) {
        variance[i] = Math.sqrt(variance[i]);
      }
      return variance;
    }
  }
  center(by, options = {}) {
    if (typeof by === 'object') {
      options = by;
      by = undefined;
    }
    if (typeof options !== 'object') {
      throw new TypeError('options must be an object');
    }
    const {
      center = this.mean(by)
    } = options;
    switch (by) {
      case 'row':
        {
          if (!isAnyArray$1(center)) {
            throw new TypeError('center must be an array');
          }
          centerByRow(this, center);
          return this;
        }
      case 'column':
        {
          if (!isAnyArray$1(center)) {
            throw new TypeError('center must be an array');
          }
          centerByColumn(this, center);
          return this;
        }
      case undefined:
        {
          if (typeof center !== 'number') {
            throw new TypeError('center must be a number');
          }
          centerAll(this, center);
          return this;
        }
      default:
        throw new Error(`invalid option: ${by}`);
    }
  }
  scale(by, options = {}) {
    if (typeof by === 'object') {
      options = by;
      by = undefined;
    }
    if (typeof options !== 'object') {
      throw new TypeError('options must be an object');
    }
    let scale = options.scale;
    switch (by) {
      case 'row':
        {
          if (scale === undefined) {
            scale = getScaleByRow(this);
          } else if (!isAnyArray$1(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$1(scale)) {
            throw new TypeError('scale must be an array');
          }
          scaleByColumn(this, scale);
          return this;
        }
      case undefined:
        {
          if (scale === undefined) {
            scale = getScaleAll(this);
          } else if (typeof scale !== 'number') {
            throw new TypeError('scale must be a number');
          }
          scaleAll(this, scale);
          return this;
        }
      default:
        throw new Error(`invalid option: ${by}`);
    }
  }
  toString(options) {
    return inspectMatrixWithOptions(this, options);
  }
  [Symbol.iterator]() {
    return this.entries();
  }

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

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

// Synonyms
AbstractMatrix.random = AbstractMatrix.rand;
AbstractMatrix.randomInt = AbstractMatrix.randInt;
AbstractMatrix.diagonal = AbstractMatrix.diag;
AbstractMatrix.prototype.diagonal = AbstractMatrix.prototype.diag;
AbstractMatrix.identity = AbstractMatrix.eye;
AbstractMatrix.prototype.negate = AbstractMatrix.prototype.neg;
AbstractMatrix.prototype.tensorProduct = AbstractMatrix.prototype.kroneckerProduct;
let Matrix$1 = class Matrix extends AbstractMatrix {
  /**
   * @type {Float64Array[]}
   */
  data;

  /**
   * Init an empty matrix
   * @param {number} nRows
   * @param {number} nColumns
   */
  #initData(nRows, nColumns) {
    this.data = [];
    if (Number.isInteger(nColumns) && nColumns >= 0) {
      for (let i = 0; i < nRows; i++) {
        this.data.push(new Float64Array(nColumns));
      }
    } else {
      throw new TypeError('nColumns must be a positive integer');
    }
    this.rows = nRows;
    this.columns = nColumns;
  }
  constructor(nRows, nColumns) {
    super();
    if (Matrix.isMatrix(nRows)) {
      this.#initData(nRows.rows, nRows.columns);
      Matrix.copy(nRows, this);
    } else if (Number.isInteger(nRows) && nRows >= 0) {
      this.#initData(nRows, nColumns);
    } else if (isAnyArray$1(nRows)) {
      // Copy the values from the 2D array
      const arrayData = nRows;
      nRows = arrayData.length;
      nColumns = nRows ? arrayData[0].length : 0;
      if (typeof nColumns !== 'number') {
        throw new TypeError('Data must be a 2D array with at least one element');
      }
      this.data = [];
      for (let i = 0; i < nRows; i++) {
        if (arrayData[i].length !== nColumns) {
          throw new RangeError('Inconsistent array dimensions');
        }
        if (!isArrayOfNumbers(arrayData[i])) {
          throw new TypeError('Input data contains non-numeric values');
        }
        this.data.push(Float64Array.from(arrayData[i]));
      }
      this.rows = nRows;
      this.columns = nColumns;
    } else {
      throw new TypeError('First argument must be a positive number or an array');
    }
  }
  set(rowIndex, columnIndex, value) {
    this.data[rowIndex][columnIndex] = value;
    return this;
  }
  get(rowIndex, columnIndex) {
    return this.data[rowIndex][columnIndex];
  }
  removeRow(index) {
    checkRowIndex(this, index);
    this.data.splice(index, 1);
    this.rows -= 1;
    return this;
  }
  addRow(index, array) {
    if (array === undefined) {
      array = index;
      index = this.rows;
    }
    checkRowIndex(this, index, true);
    array = Float64Array.from(checkRowVector(this, array));
    this.data.splice(index, 0, array);
    this.rows += 1;
    return this;
  }
  removeColumn(index) {
    checkColumnIndex(this, index);
    for (let i = 0; i < this.rows; i++) {
      const newRow = new Float64Array(this.columns - 1);
      for (let j = 0; j < index; j++) {
        newRow[j] = this.data[i][j];
      }
      for (let j = index + 1; j < this.columns; j++) {
        newRow[j - 1] = this.data[i][j];
      }
      this.data[i] = newRow;
    }
    this.columns -= 1;
    return this;
  }
  addColumn(index, array) {
    if (typeof array === 'undefined') {
      array = index;
      index = this.columns;
    }
    checkColumnIndex(this, index, true);
    array = checkColumnVector(this, array);
    for (let i = 0; i < this.rows; i++) {
      const newRow = new Float64Array(this.columns + 1);
      let j = 0;
      for (; j < index; j++) {
        newRow[j] = this.data[i][j];
      }
      newRow[j++] = array[i];
      for (; j < this.columns + 1; j++) {
        newRow[j] = this.data[i][j - 1];
      }
      this.data[i] = newRow;
    }
    this.columns += 1;
    return this;
  }
};
installMathOperations(AbstractMatrix, Matrix$1);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

/*
Adapted from: https://github.com/compute-io/erfcinv/blob/aa116e23883839359e310ad41a7c42f72815fc1e/lib/number.js

The MIT License (MIT)

Copyright (c) 2014-2015 The Compute.io Authors. All rights reserved.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


Boost Software License - Version 1.0 - August 17th, 2003

Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following:

The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/* eslint-disable no-loss-of-precision */
// Coefficients for erfcinv on [0, 0.5]:
const Y1 = 8.91314744949340820313e-2;
const P1 = [-0.005387729650712429, 8.22687874676915743155e-3, 2.19878681111168899165e-2, -0.03656379714117627, -0.012692614766297404, 3.34806625409744615033e-2, -0.008368748197417368, -5087819496582806e-19];
const Q1 = [8.86216390456424707504e-4, -0.0023339375937419, 7.95283687341571680018e-2, -0.05273963823400997, -0.7122890234154284, 6.62328840472002992063e-1, 1.56221558398423026363, -1.5657455823417585, -0.9700050433032906, 1];
// Coefficients for erfcinv for 0.5 > 1-x >= 0:
const Y2 = 2.249481201171875;
const P2 = [-3.6719225470772936, 2.11294655448340526258e1, 1.7445385985570866523e1, -44.6382324441787, -18.851064805871424, 1.76447298408374015486e1, 8.37050328343119927838, 1.05264680699391713268e-1, -0.20243350835593876];
const Q2 = [1.72114765761200282724, -22.643693341313973, 1.08268667355460159008e1, 4.85609213108739935468e1, -20.14326346804852, -28.66081804998, 3.9713437953343869095, 6.24264124854247537712, 1];
// Coefficients for erfcinv for sqrt( -log(1-x)):
const Y3 = 8.07220458984375e-1;
const P3 = [-681149956853777e-24, 2.85225331782217055858e-8, -6.794655751811263e-7, 2.14558995388805277169e-3, 2.90157910005329060432e-2, 1.42869534408157156766e-1, 3.37785538912035898924e-1, 3.87079738972604337464e-1, 1.17030156341995252019e-1, -0.16379404719331705, -0.1311027816799519];
const Q3 = [1.105924229346489121e-2, 1.52264338295331783612e-1, 8.48854343457902036425e-1, 2.59301921623620271374, 4.77846592945843778382, 5.38168345707006855425, 3.46625407242567245975, 1];
const Y4 = 9.3995571136474609375e-1;
const P4 = [2.66339227425782031962e-12, -2304047769118826e-25, 4.60469890584317994083e-6, 1.57544617424960554631e-4, 1.87123492819559223345e-3, 9.50804701325919603619e-3, 1.85573306514231072324e-2, -0.0022242652921344794, -0.0350353787183178];
const Q4 = [7.64675292302794483503e-5, 2.63861676657015992959e-3, 3.41589143670947727934e-2, 2.20091105764131249824e-1, 7.62059164553623404043e-1, 1.3653349817554063097, 1];
const Y5 = 9.8362827301025390625e-1;
const P5 = [9.9055709973310326855e-17, -2811287356288318e-29, 4.62596163522878599135e-9, 4.49696789927706453732e-7, 1.49624783758342370182e-5, 2.09386317487588078668e-4, 1.05628862152492910091e-3, -0.0011295143874558028, -0.016743100507663373];
const Q5 = [2.82243172016108031869e-7, 2.75335474764726041141e-5, 9.64011807005165528527e-4, 1.60746087093676504695e-2, 1.38151865749083321638e-1, 5.91429344886417493481e-1, 1];
/**
 * Polyval.
 * @param c - Array of Number.
 * @param x - Number.
 * @returns Number.
 */
function polyval(c, x) {
  let p = 0;
  for (const coef of c) {
    p = p * x + coef;
  }
  return p;
}
/**
 * Calculates a rational approximation.
 * @private
 * @param x - Number.
 * @param v - Number.
 * @param P - Array of polynomial coefficients.
 * @param Q - Array of polynomial coefficients.
 * @param Y - Number.
 * @returns Rational approximation.
 */
function calc(x, v, P, Q, Y) {
  const s = x - v;
  const r = polyval(P, s) / polyval(Q, s);
  return Y * x + r * x;
}
/**
 * Evaluates the complementary inverse error function for an input value.
 * @private
 * @param x - Input value.
 * @returns Evaluated complementary inverse error function.
 */
function erfcinv(x) {
  let sign = false;
  let val;
  let q;
  let g;
  let r;
  // [1] Special cases...
  // NaN:
  if (Number.isNaN(x)) {
    return Number.NaN;
  }
  // x not on the interval: [0,2]
  if (x < 0 || x > 2) {
    throw new RangeError(`erfcinv()::invalid input argument. Value must be on the interval [0,2]. Value: \`${x}\`.`);
  }
  if (x === 0) {
    return Number.POSITIVE_INFINITY;
  }
  if (x === 2) {
    return Number.NEGATIVE_INFINITY;
  }
  if (x === 1) {
    return 0;
  }
  // [2] Get the sign and make use of `erfc` reflection formula: `erfc(-z)=2 - erfc(z)`...
  if (x > 1) {
    q = 2 - x;
    x = 1 - q;
    sign = true;
  } else {
    q = x;
    x = 1 - x;
  }
  // [3] |x| <= 0.5
  if (x <= 0.5) {
    g = x * (x + 10);
    r = polyval(P1, x) / polyval(Q1, x);
    val = g * Y1 + g * r;
    return sign ? -val : val;
  }
  // [4] 1-|x| >= 0.25
  if (q >= 0.25) {
    g = Math.sqrt(-2 * Math.log(q));
    q = q - 0.25;
    r = polyval(P2, q) / polyval(Q2, q);
    val = g / (Y2 + r);
    return sign ? -val : val;
  }
  q = Math.sqrt(-Math.log(q));
  // [5] q < 3
  if (q < 3) {
    return calc(q, 1.125, P3, Q3, Y3);
  }
  // [6] q < 6
  if (q < 6) {
    return calc(q, 3, P4, Q4, Y4);
  }
  // Note that the smallest number in JavaScript is 5e-324. Math.sqrt( -Math.log( 5e-324 ) ) ~27.2844
  return calc(q, 6, P5, Q5, Y5);
  // Note that in the boost library, they are able to go to much smaller values, as 128 bit long doubles support ~1e-5000; something which JavaScript does not natively support.
}

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

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

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

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

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

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

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

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

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

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

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

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

/*
The MIT License (MIT)

Copyright (c) 2013 Eric Arnebäck

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
/**
 * Solves a system of linear equations using the Cholesky decomposition method.
 * It is a direct conversion to TS from {@link https://github.com/scijs/cholesky-solve}
 * @param nonZerosArray - The matrix in triplet form (array of arrays), where each sub-array contains three elements: row index, column index, and value.
 * @param dimension - The order of the matrix (number of rows/columns).
 * @param permutationEncoded - Optional permutation array. If provided, it will be used to permute the matrix.
 * @returns A function that takes a right-hand side vector `b` and returns the solution vector `x`, or `null` if the decomposition fails.
 */
function matrixCholeskySolver(nonZerosArray, dimension, permutationEncoded) {
  if (permutationEncoded) {
    const pinv = new Int32Array(dimension);
    for (let k = 0; k < dimension; k++) {
      pinv[permutationEncoded[k]] = k;
    }
    // Build a permuted copy (kept as plain arrays for compatibility)
    const mt = new Array(nonZerosArray.length);
    for (let a = 0; a < nonZerosArray.length; ++a) {
      const [r, c, value] = nonZerosArray[a];
      const ar = pinv[r];
      const ac = pinv[c];
      mt[a] = ac < ar ? [ac, ar, value] : [ar, ac, value];
    }
    nonZerosArray = mt;
  } else {
    // use a typed identity permutation
    const p = new Int32Array(dimension);
    for (let i = 0; i < dimension; ++i) {
      p[i] = i;
    }
    permutationEncoded = p;
  }
  const nnz = nonZerosArray.length;
  const ap = new Int32Array(dimension + 1);
  const ai = new Int32Array(nnz);
  const ax = new Float64Array(nnz);
  const lnz = new Int32Array(dimension);
  for (let idx = 0; idx < nnz; idx++) {
    const col = nonZerosArray[idx][1];
    lnz[col]++;
  }
  ap[0] = 0;
  for (let i = 0; i < dimension; ++i) {
    ap[i + 1] = ap[i] + lnz[i];
  }
  const colOffset = new Int32Array(dimension);
  for (let idx = 0; idx < nnz; idx++) {
    const e = nonZerosArray[idx];
    const col = e[1];
    const adr = ap[col] + colOffset[col];
    ai[adr] = e[0];
    ax[adr] = e[2];
    colOffset[col]++;
  }
  const d = new Float64Array(dimension);
  const y = new Float64Array(dimension);
  const lp = new Int32Array(dimension + 1);
  const parent = new Int32Array(dimension);
  const lnzArray = new Int32Array(dimension);
  const flag = new Int32Array(dimension);
  const pattern = new Int32Array(dimension);
  const bp1 = new Float64Array(dimension);
  const x = new Float64Array(dimension);
  ldlSymbolic(dimension, ap, ai, lp, parent, lnzArray, flag);
  const nz = lp[dimension];
  const lx = new Float64Array(nz);
  const li = new Int32Array(nz);
  const result = ldlNumeric(dimension, ap, ai, ax, lp, parent, lnzArray, li, lx, d, y, pattern, flag);
  if (result === dimension) {
    return b => {
      ldlPerm(dimension, bp1, b, permutationEncoded);
      ldlLsolve(dimension, bp1, lp, li, lx);
      ldlDsolve(dimension, bp1, d);
      ldlLTsolve(dimension, bp1, lp, li, lx);
      ldlPermt(dimension, x, bp1, permutationEncoded);
      return x;
    };
  } else {
    return null;
  }
}
function ldlSymbolic(dimension, ap, ai, lp, parent, lnz, flag) {
  const apLoc = ap;
  const aiLoc = ai;
  const parentLoc = parent;
  const lnzLoc = lnz;
  const flagLoc = flag;
  for (let k = 0; k < dimension; k++) {
    parentLoc[k] = -1;
    flagLoc[k] = k;
    lnzLoc[k] = 0;
    const kk = k;
    const p2 = apLoc[kk + 1];
    for (let p = apLoc[kk]; p < p2; p++) {
      let i = aiLoc[p];
      if (i < k) {
        for (; flagLoc[i] !== k; i = parentLoc[i]) {
          if (parentLoc[i] === -1) parentLoc[i] = k;
          lnzLoc[i]++;
          flagLoc[i] = k;
        }
      }
    }
  }
  lp[0] = 0;
  for (let k = 0; k < dimension; k++) {
    lp[k + 1] = lp[k] + lnz[k];
  }
}
function ldlNumeric(dimension, ap, ai, ax, lp, parent, lnz, li, lx, d, y, pattern, flag) {
  const apLoc = ap;
  const aiLoc = ai;
  const axLoc = ax;
  const lpLoc = lp;
  const parentLoc = parent;
  const lnzLoc = lnz;
  const liLoc = li;
  const lxLoc = lx;
  const dLoc = d;
  const yLoc = y;
  const patternLoc = pattern;
  const flagLoc = flag;
  let yi, lKi;
  for (let k = 0; k < dimension; k++) {
    yLoc[k] = 0;
    let top = dimension;
    flagLoc[k] = k;
    lnzLoc[k] = 0;
    const kk = k;
    const p2col = apLoc[kk + 1];
    for (let p = apLoc[kk]; p < p2col; p++) {
      let i = aiLoc[p];
      if (i <= k) {
        yLoc[i] += axLoc[p];
        let len = 0;
        for (; flagLoc[i] !== k; i = parentLoc[i]) {
          patternLoc[len++] = i;
          flagLoc[i] = k;
        }
        while (len > 0) patternLoc[--top] = patternLoc[--len];
      }
    }
    dLoc[k] = yLoc[k];
    yLoc[k] = 0;
    for (; top < dimension; top++) {
      const i = patternLoc[top];
      yi = yLoc[i];
      yLoc[i] = 0;
      const p2 = lpLoc[i] + lnzLoc[i];
      let p;
      for (p = lpLoc[i]; p < p2; p++) {
        yLoc[liLoc[p]] -= lxLoc[p] * yi;
      }
      lKi = yi / dLoc[i];
      dLoc[k] -= lKi * yi;
      liLoc[p] = k;
      lxLoc[p] = lKi;
      lnzLoc[i]++;
    }
    if (dLoc[k] === 0) return k;
  }
  return dimension;
}
function ldlLsolve(dimension, x, lp, li, lx) {
  const lpLoc = lp;
  const liLoc = li;
  const lxLoc = lx;
  const xLoc = x;
  for (let j = 0; j < dimension; j++) {
    const p2 = lpLoc[j + 1];
    for (let p = lpLoc[j]; p < p2; p++) {
      xLoc[liLoc[p]] -= lxLoc[p] * xLoc[j];
    }
  }
}
function ldlDsolve(dimension, x, d) {
  const xLoc = x;
  const dLoc = d;
  for (let j = 0; j < dimension; j++) {
    xLoc[j] /= dLoc[j];
  }
}
function ldlLTsolve(dimension, x, lp, li, lx) {
  const lpLoc = lp;
  const liLoc = li;
  const lxLoc = lx;
  const xLoc = x;
  for (let j = dimension - 1; j >= 0; j--) {
    const p2 = lpLoc[j + 1];
    for (let p = lpLoc[j]; p < p2; p++) {
      xLoc[j] -= lxLoc[p] * xLoc[liLoc[p]];
    }
  }
}
function ldlPerm(dimension, x, b, permutationEncoded) {
  const perm = permutationEncoded;
  const xLoc = x;
  const bLoc = b;
  for (let j = 0; j < dimension; j++) {
    xLoc[j] = bLoc[perm[j]];
  }
}
function ldlPermt(dimension, x, b, permutationEncoded) {
  const perm = permutationEncoded;
  const xLoc = x;
  const bLoc = b;
  for (let j = 0; j < dimension; j++) {
    xLoc[perm[j]] = bLoc[j];
  }
}

/**
 * The MIT License (MIT)
 *
 * Copyright (c) 2013 Mikola Lysenko
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
/**
 * The reverse Cuthill-Mckee method is a fast and effective preconditioner for reducing the bandwidth of sparse linear systems.
 * When solving a positive semidefinite linear system using Cholesky factorization, it greatly reduces fill-in.
 * It is a direct conversion to TS from {@link github.com/mikolalysenko/cuthill-mckee}
 * @param list - lower triangular non zeros from a symmetric sparse matrix.
 * @param dimension - matrix dimension
 * @returns A Float64Array where the value at each index represents the new position of the node
 *          in the bandwidth-reduced ordering.
 */
function matrixCuthillMckee(list, dimension) {
  const adj = new Array(dimension);
  const visited = new Array(dimension).fill(false);
  for (let i = 0; i < dimension; ++i) {
    adj[i] = [];
  }
  for (const l of list) {
    adj[l[0]].push(l[1]);
  }
  const toVisit = new Float64Array(dimension);
  let eol = 0;
  let ptr = 0;
  for (let i = 0; i < dimension; ++i) {
    if (visited[i]) {
      continue;
    }
    toVisit[eol++] = i;
    visited[i] = true;
    while (ptr < eol) {
      const v = toVisit[ptr++];
      const nbhd = Float64Array.from(adj[v]);
      nbhd.sort();
      for (const u of nbhd) {
        if (visited[u]) {
          continue;
        }
        visited[u] = true;
        toVisit[eol++] = u;
      }
    }
  }
  const result = new Float64Array(dimension);
  for (let i = 0; i < dimension; ++i) {
    result[toVisit[i]] = i;
  }
  return result;
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

function sequentialFill(input, options = {}) {
  if (!isAnyArray$2(input)) {
    if (typeof input === 'object') {
      options = input ?? {};
    }
    input = [];
  }
  if (!isAnyArray$2(input)) {
    throw new TypeError('input must be an array');
  }
  let {
    from = 0,
    size = input.length,
    step
  } = options;
  const {
    to = 10
  } = options;
  if (size !== 0 && step) {
    throw new Error('step is defined by the array size');
  }
  if (!size) {
    if (step) {
      size = Math.floor((to - from) / step) + 1;
    } else {
      size = to - from + 1;
    }
  }
  if (!step && size) {
    step = (to - from) / (size - 1);
  }
  if (typeof step !== 'number') {
    throw new TypeError('No checks initialized the step');
  }
  if (Array.isArray(input)) {
    // only works with normal array
    input.length = 0;
    for (let i = 0; i < size; i++) {
      input.push(from);
      from += step;
    }
  } else {
    if (input.length !== size) {
      throw new Error('sequentialFill typed array must have the correct length');
    }
    for (let i = 0; i < size; i++) {
      input[i] = from;
      from += step;
    }
  }
  return input;
}

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

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

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

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

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

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

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

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

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

/**

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

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

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

/**

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

/**
 * Refine the `x` and `y` coordinates of each peak by running a quadratic
 * interpolation over the peak and its 3 closest neighbors.
 * The correction is performed in place.
 * @param data - Object with `x` and `y` arrays.
 * @param peaks - Peaks to refine (mutated in place).
 */
function optimizeTop$1(data, peaks) {
  const {
    x,
    y
  } = data;
  for (const peak of peaks) {
    let currentIndex = peak.index;
    // The detected peak could be moved 1 or 2 units to left or right.
    if (y[currentIndex - 1] >= y[currentIndex - 2] && y[currentIndex - 1] >= y[currentIndex]) {
      currentIndex--;
    } else if (y[currentIndex + 1] >= y[currentIndex] && y[currentIndex + 1] >= y[currentIndex + 2]) {
      currentIndex++;
    } else if (y[currentIndex - 2] >= y[currentIndex - 3] && y[currentIndex - 2] >= y[currentIndex - 1]) {
      currentIndex -= 2;
    } else if (y[currentIndex + 2] >= y[currentIndex + 1] && y[currentIndex + 2] >= y[currentIndex + 3]) {
      currentIndex += 2;
    }
    // Quadratic interpolation on log-intensities to refine the peak top.
    if (y[currentIndex - 1] > 0 && y[currentIndex + 1] > 0 && y[currentIndex] >= y[currentIndex - 1] && y[currentIndex] >= y[currentIndex + 1] && (y[currentIndex] !== y[currentIndex - 1] || y[currentIndex] !== y[currentIndex + 1])) {
      const alpha = Math.log10(y[currentIndex - 1]);
      const beta = Math.log10(y[currentIndex]);
      const gamma = Math.log10(y[currentIndex + 1]);
      const p = 0.5 * (alpha - gamma) / (alpha - 2 * beta + gamma);
      const xCurrent = x[currentIndex];
      const xPrevious = x[currentIndex - 1];
      peak.x = xCurrent + (xCurrent - xPrevious) * p;
      peak.y = y[currentIndex] - 0.25 * (y[currentIndex - 1] - y[currentIndex + 1]) * p;
    }
  }
}

/**
 * Global spectra deconvolution.
 * @param data - Object with `x` and `y` arrays. `x` must be monotone increasing.
 * @param options - Peak detection options.
 * @returns The detected peaks, sorted by ascending `x`.
 */
function gsd$1(data, options = {}) {
  let {
    noiseLevel
  } = options;
  const {
    sgOptions = {
      windowSize: 9,
      polynomial: 3
    },
    smoothY = false,
    maxCriteria = true,
    minMaxRatio = 0.00025,
    realTopDetection = false,
    peakDetectionAlgorithm = 'second'
  } = options;
  const {
    x
  } = data;
  let {
    y
  } = data;
  if (xIsMonotonic(x) !== 1) {
    throw new Error('GSD only accepts monotone increasing x values');
  }
  // Copy so the `maxCriteria` / clipping loops below don't mutate the caller's array.
  y = y.slice();
  // If the max difference between delta x is less than 5%, then,
  // we can assume it to be equally spaced variable
  const isEquallySpaced = xIsEquallySpaced(x);
  if (noiseLevel === undefined) {
    if (isEquallySpaced) {
      const noiseInfo = xNoiseStandardDeviation(y);
      if (maxCriteria) {
        noiseLevel = noiseInfo.median + 1.5 * noiseInfo.sd;
      } else {
        noiseLevel = -noiseInfo.median + 1.5 * noiseInfo.sd;
      }
    } else {
      noiseLevel = 0;
    }
  } else if (!maxCriteria) {
    noiseLevel *= -1;
  }
  if (!maxCriteria) {
    for (let i = 0; i < y.length; i++) {
      y[i] *= -1;
    }
  }
  if (noiseLevel !== undefined) {
    for (let i = 0; i < y.length; i++) {
      if (y[i] < noiseLevel) {
        y[i] = noiseLevel;
      }
    }
  }
  const xValue = isEquallySpaced ? x[1] - x[0] : x;
  const yData = smoothY ? sgg(y, xValue, {
    ...sgOptions,
    derivative: 0
  }) : y;
  const {
    min: minY,
    max: maxY
  } = xMinMaxValues(yData);
  if (minY > maxY || minY === maxY) return [];
  const dY = sgg(y, xValue, {
    ...sgOptions,
    derivative: 1
  });
  const ddY = sgg(y, xValue, {
    ...sgOptions,
    derivative: 2
  });
  const yThreshold = Math.max(noiseLevel, minY + (maxY - minY) * minMaxRatio);
  const dX = x[1] - x[0];
  const peakData = {
    x,
    y,
    yData,
    dY,
    ddY,
    dX,
    yThreshold
  };
  let peaks = [];
  if (peakDetectionAlgorithm === 'first') {
    peaks = firstDerivative$1(peakData);
  } else if (peakDetectionAlgorithm === 'second') {
    peaks = secondDerivative$1(peakData);
  } else {
    peaks = autoAlgorithm$1(peakData);
  }
  if (realTopDetection) {
    optimizeTop$1({
      x,
      y: yData
    }, peaks);
  }
  for (const peak of peaks) {
    if (!maxCriteria) {
      peak.y *= -1;
      peak.ddY = peak.ddY * -1;
    }
  }
  peaks.sort((a, b) => {
    return a.x - b.x;
  });
  return peaks;
}

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

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

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

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

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

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

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

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

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

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

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
var quantities = quantities$1.exports;
(function (module, exports) {
  (function (global, factory) {
    module.exports = factory() ;
  })(quantities, function () {

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

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

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

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

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

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

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

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

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

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

    // Setup

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

var medianQuickselect_min = {exports: {}};

(function (module) {
  (function () {
    function a(d) {
      for (var e = 0, f = d.length - 1, g = void 0, h = void 0, i = void 0, j = c(e, f); true;) {
        if (f <= e) return d[j];
        if (f == e + 1) return d[e] > d[f] && b(d, e, f), d[j];
        for (g = c(e, f), d[g] > d[f] && b(d, g, f), d[e] > d[f] && b(d, e, f), d[g] > d[e] && b(d, g, e), b(d, g, e + 1), h = e + 1, i = f; true;) {
          do h++; while (d[e] > d[h]);
          do i--; while (d[i] > d[e]);
          if (i < h) break;
          b(d, h, i);
        }
        b(d, e, i), i <= j && (e = h), i >= j && (f = i - 1);
      }
    }
    var b = function b(d, e, f) {
        var _ref;
        return _ref = [d[f], d[e]], d[e] = _ref[0], d[f] = _ref[1], _ref;
      },
      c = function c(d, e) {
        return ~~((d + e) / 2);
      };
    module.exports ? module.exports = a : window.median = a;
  })();
})(medianQuickselect_min);
var medianQuickselect_minExports = medianQuickselect_min.exports;
var quickSelectMedian = /*@__PURE__*/getDefaultExportFromCjs(medianQuickselect_minExports);

/// <reference path="median-quickselect.d.ts" />
function median(input) {
  if (!isAnyArray$2(input)) {
    throw new TypeError('input must be an array');
  }
  if (input.length === 0) {
    throw new TypeError('input must not be empty');
  }
  return quickSelectMedian(input.slice());
}

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

/**
 * Creates a new Analysis from a JCAMP string.
 * @param jcamp - String containing the JCAMP data
 * @param options - Options for the analysis
 * @returns New Analysis instance with the parsed data
 */
function fromJcamp(jcamp, options = {}) {
  const analysis = new Analysis(options);
  addJcamp(analysis, jcamp);
  return analysis;
}
function addJcamp(analysis, jcamp) {
  const converted = pt(jcamp, {
    keepRecordsRegExp: /.*/
  });
  for (const entry of converted.flatten) {
    if (!entry.spectra?.[0]) continue;
    const currentSpectrum = entry.spectra[0];
    // we ensure variables
    if (!currentSpectrum.variables) {
      const variables = {};
      currentSpectrum.variables = variables;
      variables.x = {
        label: currentSpectrum.xUnits,
        symbol: 'X',
        data: currentSpectrum.data.x || currentSpectrum.data.X
      };
      variables.y = {
        label: currentSpectrum.yUnits,
        symbol: 'Y',
        data: currentSpectrum.data.y || currentSpectrum.data.Y
      };
    } else {
      for (const key in currentSpectrum.variables) {
        const variable = currentSpectrum.variables[key];
        if (variable.label) continue;
        variable.label = variable.name || variable.symbol || key;
        if (variable.units && !variable.label.includes(variable.units)) {
          // eslint-disable-next-line @typescript-eslint/restrict-plus-operands
          variable.label += ` [${variable.units}]`;
        }
      }
    }
    analysis.pushSpectrum(currentSpectrum.variables, {
      dataType: entry.dataType,
      title: entry.title,
      meta: entry.meta
    });
  }
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

/**
 * Convert an Analysis to a single JCAMP string (all spectra joined).
 * @param analysis - The analysis to convert
 * @param options - Options for the JCAMP output
 * @returns JCAMP string
 */
function toJcamp(analysis, options = {}) {
  return toJcamps(analysis, options).join('\n');
}

const JSGraph$1 = {
  getJSGraph,
  getNormalizationAnnotations
};

/**
 * @typedef {object} Component
 * @property {number} kineticEnergy
 * @property {number} bindingEnergy
 * @property {number} intensity
 * @property {number} kind
 * @property {number} assignment
 */

/**
 * Creates annotations for jsgraph that allows to display the result of component picking
 * @param {Array<Component>} components
 * @param {object} [options={}]
 * @param {string} [options.fillColor='green']
 * @param {string} [options.strokeColor='red']
 * @param {string} [options.showAssignment=true] - Display the assignment
 * @param {Function} [options.creationFct] - (annotation, component) => {}: callback allowing to add properties
 * @param {string} [options.mode='binding'] - 'binding' or 'kinetic'
 * @returns array
 */

function getComponentsAnnotations(components, options = {}) {
  const {
    fillColor = 'green',
    strokeColor = 'red',
    creationFct
  } = options;
  let annotations = components.map(component => {
    let annotation = {
      line: 1,
      type: 'rect',
      strokeColor,
      strokeWidth: 0,
      fillColor
    };
    if (creationFct) {
      creationFct(annotation, component);
    }
    return getAnnotation$1(annotation, component, options);
  });
  return annotations;
}
function getAnnotation$1(annotation, component, options = {}) {
  const {
    showAssignment = true,
    mode = 'binding'
  } = options;
  let labels = [];
  let line = 0;
  let energy = mode === 'kinetic' ? component.kineticEnergy.value : component.bindingEnergy.value;
  let intensity = component.intensity || '20px';
  if (showAssignment) {
    labels.push({
      text: component.assignment,
      size: '18px',
      anchor: 'middle',
      color: 'darkred',
      position: {
        x: energy,
        y: intensity,
        dy: `${23 + line * 14}px`
      }
    });
    line++;
  }
  annotation.labels = labels;
  annotation.position = [{
    x: energy,
    y: intensity,
    dy: '10px',
    dx: '-1px'
  }, {
    x: energy,
    y: intensity,
    dy: '5px',
    dx: '1px'
  }];
  return annotation;
}

/**
 * Creates annotations for jsgraph that allows to display the result of component picking
 * @param {Array<any>} regions
 * @param {object} [options={}]
 * @param {string} [options.fillColor='rgba(0,255,255,0.1)']
 * @param {string} [options.strokeColor='red']
 * @param {string} [options.mode='binding'] - 'binding' or 'kinetic'
 * @returns array
 */

function getRegionsAnnotations(regions, options = {}) {
  const {
    fillColor = 'rgba(0,255,255,0.1)',
    strokeColor = 'red'
  } = options;
  regions = regions.filter(region => region?.background);
  const annotations = regions.map(region => {
    let annotation = {
      line: 1,
      type: 'rect',
      strokeColor,
      strokeWidth: 0,
      fillColor
    };
    return getAnnotation(annotation, region, options);
  });
  return annotations;
}
function getAnnotation(annotation, region, options = {}) {
  const {
    mode = 'binding',
    showRegionID = true
  } = options;
  let labels = [];
  let line = 0;
  const energyStart = mode === 'kinetic' ? region.background.parameters.kineticEnergyStart.value : region.background.parameters.bindingEnergyStart.value;
  const energyEnd = mode === 'kinetic' ? region.background.parameters.kineticEnergyEnd.value : region.background.parameters.bindingEnergyEnd.value;
  if (showRegionID) {
    labels.push({
      text: region.regionID,
      size: '18px',
      anchor: 'middle',
      color: 'darkcyan',
      position: {
        x: (energyStart + energyEnd) / 2,
        y: '0px',
        dy: `${13 + line * 14}px`
      }
    });
    line++;
  }
  annotation.labels = labels;
  annotation.position = [{
    x: energyStart,
    y: '0px'
  }, {
    x: energyEnd,
    y: '1000px'
  }];
  return annotation;
}

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

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

    UTF8-1    = %x00-7F

    UTF8-2    = %xC2-DF UTF8-tail

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

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

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

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

function parse(blob) {
  const text = ensureString(blob);
  const lines = text.split(/\r?\n/);
  let pointer = 0;
  let parsed = {
    header: {},
    blocks: [],
    info: {}
  };
  pointer = parseHeader(lines, parsed, pointer);
  for (let i = 0; i < parsed.info.nbBlocks; i++) {
    pointer = parseBlock(lines, parsed, pointer);
  }
  return parsed;
}
function parseHeader(lines, parsed, pointer) {
  const {
    header,
    info
  } = parsed;
  header['format identifier'] = lines[pointer++];
  header['institution identifier'] = lines[pointer++];
  header['instrument model identifier'] = lines[pointer++];
  header['operator identifier'] = lines[pointer++];
  header['experiment identifier'] = lines[pointer++];
  info.nbComments = Number(lines[pointer++]);
  header['number of lines in comment'] = info.nbComments;
  const comments = [];
  for (let i = 0; i < info.nbComments; i++) {
    comments.push(lines[pointer++]);
  }
  header['comment line'] = comments.join('\n');
  header['experiment mode'] = lines[pointer++];
  header['scan mode'] = lines[pointer++];
  if (['MAP', 'MAPD', 'NORM', 'SDP'].includes(header['experiment mode'])) {
    header['number of spectral regions'] = Number(lines[pointer++]);
  }
  if (['MAP', 'MAPD'].includes(header['experiment mode'])) {
    header['number of analysis positions'] = Number(lines[pointer++]);
    header['number of discrete x coordinates available in full map'] = Number(lines[pointer++]);
    header['number of discrete y coordinates available in full map'] = Number(lines[pointer++]);
  }
  info.nbExperimentVariables = Number(lines[pointer++]);
  const experimentVariables = [];
  header['number of experimental variables'] = info.nbExperimentVariables;
  for (let i = 0; i < info.nbExperimentVariables; i++) {
    experimentVariables.push({
      label: lines[pointer++],
      unit: lines[pointer++]
    });
  }
  header.experimentVariables = experimentVariables;

  /*
    If the values of any of the block parameters are the same in all of the
    blocks their values may be sent in the first block and then omitted
    from all subsequent blocks.
    - n > 0 : the parameters listed are to be included
    - n < 0 : the parameters listed are to be excluded
    - n = 0 : all parameters are to be given in all blocks
    A complete block contains 40 parts.
    */
  info.nbEntriesInclusionExclusion = Number(lines[pointer++]);
  header['number of entries in parameter inclusion or exclusion list'] = info.nbEntriesInclusionExclusion;
  info.blockParametersincludes = new Array(40).fill(info.nbEntriesInclusionExclusion <= 0);
  for (let i = 0; i < Math.abs(info.nbEntriesInclusionExclusion); i++) {
    info.blockParametersincludes[Number(lines[pointer++]) + 1] = info.nbEntriesInclusionExclusion > 0;
  }
  header['number of manually entered items in block'] = Number(lines[pointer++]);
  info.nbFutureUpgradeExperimentEntries = Number(lines[pointer++]);
  header['number of future upgrade experiment entries'] = info.nbFutureUpgradeExperimentEntries;
  const futureUpgradeExperimentEntries = [];
  for (let i = 0; i < info.nbFutureUpgradeExperimentEntries; i++) {
    futureUpgradeExperimentEntries.push({
      label: lines[pointer++],
      unit: lines[pointer++]
    });
  }
  header.futureUpgradeExperimentEntries = futureUpgradeExperimentEntries;
  if (info.nbFutureUpgradeExperimentEntries !== 0) {
    throw Error('unsupported future upgrade experiment entries');
  }
  header['number of future upgrade block entries'] = Number(lines[pointer++]);
  if (header['number of future upgrade block entries'] !== 0) {
    throw Error('unsupported future upgrade block entries');
  }
  info.nbBlocks = Number(lines[pointer++]);
  header['number of blocks'] = info.nbBlocks;
  return pointer;
}
function parseBlock(lines, parsed, pointer) {
  const {
    blocks,
    header,
    info
  } = parsed;
  const firstBlock = blocks[0];
  const includes = blocks.length === 0 ? new Array(40).fill(true) : info.blockParametersincludes;
  const block = {};
  block['block identifier'] = lines[pointer++];
  block['sample identifier'] = lines[pointer++];
  block['year in full'] = includes[0] ? Number(lines[pointer++]) : firstBlock['year in full'];
  block.month = includes[1] ? Number(lines[pointer++]) : firstBlock.month;
  block['day of month'] = includes[2] ? Number(lines[pointer++]) : firstBlock['day of month'];
  block.hours = includes[3] ? Number(lines[pointer++]) : firstBlock.hours;
  block.minutes = includes[4] ? Number(lines[pointer++]) : firstBlock.minutes;
  block.seconds = includes[5] ? Number(lines[pointer++]) : firstBlock.seconds;
  block['number of hours in advance of Greenwich Mean Time'] = includes[6] ? Number(lines[pointer++]) : firstBlock['number of hours in advance of Greenwich Mean Time'];
  if (includes[7]) {
    const nbComments = Number(lines[pointer++]);
    block['number of lines in block comment'] = nbComments;
    const comments = [];
    for (let i = 0; i < nbComments; i++) {
      comments.push(lines[pointer++]);
    }
    block.blockComment = comments.join('\n');
  } else {
    block['number of lines in block comment'] = firstBlock['number of lines in block comment'];
    block.blockComment = firstBlock.blockComment;
  }
  block.technique = includes[8] ? lines[pointer++] : firstBlock.technique;
  if (['MAP', 'MAPDP'].includes(header['experiment mode'])) {
    block['x coordinate'] = includes[9] ? Number(lines[pointer++]) : firstBlock['x coordinate'];
    block['y coordinate'] = includes[9] ? Number(lines[pointer++]) : firstBlock['y coordinate'];
  }
  if (includes[10]) {
    let values = [];
    for (let i = 0; i < header.experimentVariables.length; i++) {
      values.push(lines[pointer++]);
    }
    block['value of experimental variable'] = values;
  } else {
    block['value of experimental variable'] = firstBlock['value of experimental variable'];
  }
  block['analysis source label'] = includes[11] ? lines[pointer++] : firstBlock['analysis source label'];
  if (['MAPDP', 'MAPSVDP', 'SDP', 'SDPSV'].includes(header['experiment mode']) || ['SNMS energy spec', 'FABMS', 'FABMS energy spec', 'ISS', 'SIMS', 'SIMS energy spec', 'SNMS'].includes(block.technique)) {
    block['sputtering ion or atom atomic number'] = includes[12] ? Number(lines[pointer++]) : firstBlock['sputtering ion or atom atomic number'];
    block['number of atoms in sputtering ion or atom particle'] = includes[12] ? Number(lines[pointer++]) : firstBlock['number of atoms in sputtering ion or atom particle'];
    block['sputtering ion or atom charge sign and number'] = includes[12] ? lines[pointer++] : firstBlock['sputtering ion or atom charge sign and number'];
  }
  block['analysis source characteristic energy'] = includes[13] ? Number(lines[pointer++]) : firstBlock['analysis source characteristic energy'];
  block['analysis source strength'] = includes[14] ? Number(lines[pointer++]) : firstBlock['analysis source strength'];
  block['analysis source beam width x'] = includes[15] ? Number(lines[pointer++]) : firstBlock['analysis source beam width x'];
  block['analysis source beam width y'] = includes[15] ? Number(lines[pointer++]) : firstBlock['analysis source beam width y'];
  if (['MAP', 'MAPDP', 'MAPSV', 'MAPSVDP', 'SEM'].includes(header['experiment mode'])) {
    block['field of view x'] = includes[16] ? Number(lines[pointer++]) : firstBlock['field of view x'];
    block['field of view y'] = includes[16] ? Number(lines[pointer++]) : firstBlock['field of view y'];
  }
  if (['SEM', 'MAPSV', 'MAPSVDP'].includes(header['experiment mode'])) {
    block['first linescan start x coordinate'] = includes[16] ? Number(lines[pointer++]) : firstBlock['first linescan start x coordinate'];
    block['first linescan start y coordinate'] = includes[16] ? Number(lines[pointer++]) : firstBlock['first linescan start y coordinate'];
    block['first linescan finish x coordinate'] = includes[16] ? Number(lines[pointer++]) : firstBlock['first linescan finish x coordinate'];
    block['first linescan finish y coordinate'] = includes[16] ? Number(lines[pointer++]) : firstBlock['first linescan finish y coordinate'];
    block['last linescan start x coordinate'] = includes[16] ? Number(lines[pointer++]) : firstBlock['first linescan last x coordinate'];
    block['last linescan start y coordinate'] = includes[16] ? Number(lines[pointer++]) : firstBlock['first linescan last y coordinate'];
  }
  block['analysis source polar angle of incidence'] = includes[18] ? Number(lines[pointer++]) : firstBlock['analysis source polar angle of incidence'];
  block['analysis source azimuth'] = includes[19] ? Number(lines[pointer++]) : firstBlock['analysis source azimuth'];
  block['analyser mode'] = includes[20] ? lines[pointer++] : firstBlock['analyser mode'];
  block['analyser pass energy or retard ratio or mass resolution'] = includes[21] ? Number(lines[pointer++]) : firstBlock['analyser pass energy or retard ratio or mass resolution'];
  if (block.technique === 'AES diff') {
    block['differential width'] = includes[22] ? lines[pointer++] : firstBlock['differential width'];
  }
  block['magnification of analyser transfer lens'] = includes[23] ? Number(lines[pointer++]) : firstBlock['magnification of analyser transfer lens'];
  block['analyser work function or acceptance energy of atom or ion'] = includes[24] ? Number(lines[pointer++]) : firstBlock['analyser work function or acceptance energy of atom or ion'];
  block['target bias'] = includes[25] ? Number(lines[pointer++]) : firstBlock['target bias'];
  block['analysis width x'] = includes[26] ? Number(lines[pointer++]) : firstBlock['analysis width x'];
  block['analysis width y'] = includes[26] ? Number(lines[pointer++]) : firstBlock['analysis width y'];
  block['analyser axis take off polar angle'] = includes[27] ? Number(lines[pointer++]) : firstBlock['analyser axis take off polar angle'];
  block['analyser axis take off azimuth'] = includes[27] ? Number(lines[pointer++]) : firstBlock['analyser axis take off azimuth'];
  block['species label'] = includes[28] ? lines[pointer++] : firstBlock['species label'];
  block['transition or charge state label'] = includes[29] ? lines[pointer++] : firstBlock['transition or charge state label'];
  block['charge of detected particle'] = includes[29] ? Number(lines[pointer++]) : firstBlock['charge of detected particle'];
  if (header['scan mode'] !== 'REGULAR') {
    throw Error('Only REGULAR scans are supported');
  }
  block['abscissa label'] = includes[30] ? lines[pointer++] : firstBlock['abscissa label'];
  block['abscissa units'] = includes[30] ? lines[pointer++] : firstBlock['abscissa units'];
  block['abscissa start'] = includes[30] ? Number(lines[pointer++]) : firstBlock['abscissa start'];
  block['abscissa increment'] = includes[30] ? Number(lines[pointer++]) : firstBlock['abscissa increment'];
  if (includes[31]) {
    const nbCorrespondingVariables = Number(lines[pointer++]);
    block['number of corresponding variables'] = nbCorrespondingVariables;
    const correspondingVariables = [];
    for (let i = 0; i < nbCorrespondingVariables; i++) {
      correspondingVariables.push({
        label: lines[pointer++],
        unit: lines[pointer++],
        array: []
      });
    }
    block.correspondingVariables = correspondingVariables;
  } else {
    block['number of corresponding variables'] = firstBlock['number of corresponding variables'];
    block.correspondingVariables = JSON.parse(JSON.stringify(firstBlock.correspondingVariables));
    block.correspondingVariables.array = [];
  }
  block['signal mode'] = includes[32] ? lines[pointer++] : firstBlock['signal mode'];
  block['signal collection time'] = includes[33] ? Number(lines[pointer++]) : firstBlock['signal collection time'];
  block['number of scans to compile this block'] = includes[34] ? Number(lines[pointer++]) : firstBlock['number of scans to compile this block'];
  block['signal time correction'] = includes[35] ? Number(lines[pointer++]) : firstBlock['signal time correction'];
  if (['MAPDP', 'MAPSVDP', 'SDP', 'SDPSV'].includes(header['experiment mode']) && ['AES diff', 'AES dir', 'EDX', 'ELS', 'UPS', 'XPS', 'XRF'].includes(block.technique)) {
    block['sputtering source energy'] = includes[36] ? Number(lines[pointer++]) : firstBlock['sputtering source energy'];
    block['sputtering source beam current'] = includes[36] ? Number(lines[pointer++]) : firstBlock['sputtering source beam current'];
    block['sputtering source width x'] = includes[36] ? Number(lines[pointer++]) : firstBlock['sputtering source width x'];
    block['sputtering source width y'] = includes[36] ? Number(lines[pointer++]) : firstBlock['sputtering source width y'];
    block['sputtering source polar angle of incidence'] = includes[36] ? Number(lines[pointer++]) : firstBlock['sputtering source polar angle of incidence'];
    block['sputtering source azimuth'] = includes[36] ? Number(lines[pointer++]) : firstBlock['sputtering source azimuth'];
    block['sputtering mode'] = includes[36] ? lines[pointer++] : firstBlock['sputtering mode'];
  }
  block['sample normal polar angle of tilt'] = includes[37] ? Number(lines[pointer++]) : firstBlock['sample normal polar angle of tilt'];
  block['sample normal tilt azimuth'] = includes[37] ? Number(lines[pointer++]) : firstBlock['sample normal tilt azimuth'];
  block['sample rotation angle'] = includes[38] ? Number(lines[pointer++]) : firstBlock['sample rotation angle'];
  if (includes[39]) {
    const nbAdditionalNumericalParameters = Number(lines[pointer++]);
    block['number of additional numerical parameters'] = nbAdditionalNumericalParameters;
    const additionalNumericalParameters = [];
    for (let i = 0; i < nbAdditionalNumericalParameters; i++) {
      additionalNumericalParameters.push({
        label: lines[pointer++],
        unit: lines[pointer++],
        value: lines[pointer++]
      });
    }
    block.additionalNumericalParameters = additionalNumericalParameters;
  } else {
    block['number of additional numerical parameters'] = firstBlock['number of additional numerical parameters'];
    block.additionalNumericalParameters = firstBlock.additionalNumericalParameters;
  }
  block.nbOrdinateValues = Number(lines[pointer++]);
  for (let correspondingVariable of block.correspondingVariables) {
    correspondingVariable.minimumOrdinateValue = Number(lines[pointer++]);
    correspondingVariable.maximumOrdinateValue = Number(lines[pointer++]);
  }
  for (let i = 0; i < block.nbOrdinateValues / block.correspondingVariables.length; i++) {
    for (let correspondingVariable of block.correspondingVariables) {
      correspondingVariable.array.push(Number(lines[pointer++]));
    }
  }
  parsed.blocks.push(block);
  return pointer;
}

/**
 * Referencing: (energy calibration, typically performed on the binding energy scale)
" Calib M = 0 A = 3.111 ADD"
 * means that the block should be shifted in binding energy by adding the difference A - M = 3.111 eV. M is not necessarily 0.
 * If the line contains BE, it means that the operation should be done on the BE scale,
 * alternatively, simply change the sign of the difference, here it would be -3,111, to be added to the data in the binding energy scale.
 * @param {object} calibrations 
 * @param {string} line 
 */

function appendCalibration(calibrations, line) {
  let calibration = {};
  // Calib M = 281.1700 A = 284.8 BE ADD
  // calibration may be present on many lines

  let fields = line.match(/Calib M = (?<measured>.*) A = (?<referenced>[^ ]*) (?<rest>.*)/);
  if (!fields) {
    throw new Error(`appendCalibration fails on: ${line}`);
  }
  if (!fields.groups.rest.includes('ADD')) {
    throw new Error(`appendCalibration fails because line does not contain ADD: ${line}`);
  }
  calibration.measured = Number(fields.groups.measured);
  calibration.referenced = Number(fields.groups.referenced);
  if (fields.groups.rest.includes('BE')) {
    calibration.bindingEnergyShift = calibration.referenced - calibration.measured;
    calibration.kind = 'bindingEnergy';
  } else {
    calibration.bindingEnergyShift = calibration.measured - calibration.referenced;
    calibration.kind = 'kineticEnergy';
  }
  calibration.kineticEnergyShift = -calibration.bindingEnergyShift;
  calibrations.push(calibration);
}

/**
Fitting / each 'component' is a fitted lineshape, and can be from a different element while on a given orbital, for instance Ruthenium has peaks in the C1s region. The component will often have overlapping lineshapes

"CASA comp (*C 1s compA*) (*GL(30)*) Area 2447.211 1e-020 223027.03 -1 1 MFWHM 0.72109706 0.21 5.25 -1 1 Position 1203.2974 1198.6232 1207.3108 -1 1 RSF 0.278 MASS 12.011 INDEX -1 (*C 1s*) CONST (**) UNCORRECTEDRSF 0.278"
This relates to the fittings (comments on the constraints later on):

componentID: C 1s compA
lineshape: GL(30)
area: 2447.211
areaConstraint lb: 1e-020
areaConstraint ub: 223027.03
fwhm: 0.72109706
fwhmConstraint lb: 0.21
fwhmConstraint ub: 5.25
position: 1203.2974 // in kinetic energy eV
positionConstraint lb: 1198.6232 // in kinetic energy eV
positionConstraint ub: 1207.3108 // in kinetic energy eV
relativeSensitivityFactor: 0.278
mass: 12.011
index: -1	// index allows to group different components to plot their manifold
uncorrectedRSF: 0.278

Constraints: the constraints can either be absolute, in which case the value to constraint is directly followed by the constaints, e.g.:
Position 1203.4193 1202.686 1207.3108

or relative to another component, with this weird notation:
"Position 1202.261 0 0 2 -1.05017"
here the position is separated from the constaints by "0 0", then "2" indicates the component to which is is constrained, here the 2nd component (third line of the comp, i.e, starting from 0). And the last number "-1.05017" is the shift (in the kinetic energy scale)
*/

function appendComponent(components, line) {
  // CASA comp (*Mo 3d MoS2 2H*) (*LA(1.53,243)*) Area 230.36971 1e-020 2327991 -1 1 MFWHM 0.88528218 0.2 2 -1 1 Position 1257.22 1257.02 1257.22 -1 1 RSF 10.804667 MASS 95.9219 INDEX -1 (*Mo 3d*) CONST (**) UNCORRECTEDRSF 9.5
  let component = {};
  const componentRegex = new RegExp([/CASA comp /, /\(\*(?<componentID>.*)\*\) /, /\((?<shape>[^ ]*)\) /, /(?<area>Area .*)/, /(?<fwhm>MFWHM .*)/, /(?<position>Position .*) /, /(?<rsf>RSF .*) /, /(?<mass>MASS .*) /, /(?<index>INDEX .*) /, /(?<const>CONST .*) /, /(?<uncorrectedRSF>UNCORRECTEDRSF.*)/].map(r => r.source).join(''));
  let fields = line.match(componentRegex);
  if (!fields) {
    throw new Error(`appendComponent fails on: ${line}`);
  }
  component = {
    componentID: fields.groups.componentID,
    shape: parseShape(fields.groups.shape),
    area: parseMultiplicationConstrain(fields.groups.area),
    fwhm: parseMultiplicationConstrain(fields.groups.fwhm),
    position: parseKEShiftConstrain(fields.groups.position),
    relativeSensitivityFactor: parseRSF(fields.groups.rsf),
    atomicMass: parseMass(fields.groups.mass),
    groupIndex: parseIndex(fields.groups.index),
    uncorrectedRSF: parseUncorrectedRSF(fields.groups.uncorrectedRSF)
  };
  components.push(component);
}
function parseShape(value) {
  let parts = value.replace(/[*(),]/g, ' ').trim().split(/ +/);
  let options = {};
  let kind;
  switch (parts[0]) {
    case 'LA':
      // http://www.casaxps.com/help_manual/manual_updates/LA_Lineshape.pdf
      kind = 'lorentzianAsymmetric';
      options.alpha = Number(parts[1]);
      options.beta = Number(parts[2]);
      if (parts[3] !== undefined) {
        options.m = Number(parts[3]);
      }
      break;
    case 'GL':
      // http://www.casaxps.com/help_manual/line_shapes.htm
      kind = 'gaussianLorentzianProduct';
      options.mixingRatio = Number(parts[1]);
      break;
    case 'SGL':
      // https://docs.mantidproject.org/nightly/fitting/fitfunctions/PseudoVoigt.html
      kind = 'pseudoVoigt';
      options.mu = Number(parts[1]);
      break;
    case 'LF':
      kind = 'gaussianLorentzianSum';
      break;
    case 'LS':
      kind = 'gaussianLorentzianSum';
      break;
    default:
      throw Error(`appendComponent: unknown shape: ${parts[0]}`);
  }
  return {
    kind,
    options
  };
}
function parseMultiplicationConstrain(value) {
  const parts = value.split(' ');
  const result = {
    value: Number(parts[1]),
    lowerBound: Number(parts[2]),
    upperBound: Number(parts[3])
  };
  const constrain = {
    linkedComponent: Number(parts[4]),
    // line number, no constrain if -1
    factor: Number(parts[5]) // multiplication factor of the value in the linked line
  };
  if (constrain.linkedComponent >= 0) {
    result.constrain = constrain;
  }
  return result;
}
function parseKEShiftConstrain(value) {
  const parts = value.split(' ');
  const result = {
    value: Number(parts[1]),
    lowerBound: Number(parts[2]),
    upperBound: Number(parts[3])
  };
  const constrain = {
    linkedComponent: Number(parts[4]),
    // line number, no constrain if -1
    shift: Number(parts[5]) // shift of the value in the linked line
  };
  if (constrain.linkedComponent >= 0) {
    result.constrain = constrain;
  }
  return result;
}
function parseRSF(value) {
  let parts = value.split(' ');
  return Number(parts[1]);
}
function parseMass(value) {
  let parts = value.split(' ');
  return Number(parts[1]);
}
function parseIndex(value) {
  let parts = value.split(' ');
  return Number(parts[1]);
}
function parseUncorrectedRSF(value) {
  let parts = value.split(' ');
  return Number(parts[1]);
}

/**
 * Regions are used to calculate the baseline.
 * There may be several overlapping regions.
 * We could just throw an error if it happens has this is likely just a bug from casaXPS to allow for this.
 * "CASA region (*C 1s*) (*Shirley*) 1202.686 1208.454 0.278 2 0 0 115.3918 -450 0 0 (*C 1s*) 12.011 0 0.278"
 *
 * THe last 3 numbers:  12.011 0 0.278, are the mass of the element (Carbon here), a separator
 * and the relative sensitivity factor of the element.
 *  Note that the relative sensitivity factor given within the background part ((*C 1s*) (*Shirley*) 1202.686 1208.454 0.278 2 0 0 115.3918 -450 0 0 (*C 1s*)), can be different than the last one.
 */

function appendRegion(regions, line) {
  // CASA region (*Mo 3d*) (*Shirley*) 1249.3343 1262.7065 10.804667 2 0 0 392.54541 -450 0 0 (*Mo 3d*) 95.9219 0 9.5
  let fields = line.match(/CASA region \(\*(?<regionID>.*)\*\) \(\*(?<backgroundType>.*)\*\) (?<backgroundOptions>.*) \(\*(?<regionBlockID>.*)\*\) (?<atomicMass>.*) (?<separator>.) (?<relativeSensitivityFactor>.*)/);
  if (!fields) {
    throw new Error(`appendRegion fails on: ${line}`);
  }
  let region = {
    regionID: fields.groups.regionID,
    block: {
      regionBlockID: fields.groups.regionBlockID,
      atomicMass: parseFloat(fields.groups.atomicMass),
      relativeSensitivityFactor: parseFloat(fields.groups.relativeSensitivityFactor)
    },
    background: {
      type: fields.groups.backgroundType,
      parameters: parseBackgroundParameters(fields.groups.backgroundType, fields.groups.backgroundOptions),
      rawParameters: fields.groups.backgroundOptions
    }
  };
  regions.push(region);
}
function parseBackgroundParameters(type, string) {
  const [kineticEnergyStart, kineticEnergyEnd, relativeSensitivityFactor, averageWidth, startOffset, endOffset, ...otherParameters] = string.split(/ +/).filter(field => field.trim() !== '').map(field => parseFloat(field));

  // todo: strange we need to remove 1 from averageWidth
  return {
    kineticEnergyStart,
    kineticEnergyEnd,
    relativeSensitivityFactor,
    averageWidth: averageWidth - 1,
    startOffset,
    endOffset,
    crossSection: otherParameters
  };
}

function parseCASA(text) {
  const casa = {
    regions: [],
    components: [],
    calibrations: []
  };
  const lines = text.split(/\r?\n/);
  for (const line of lines) {
    if (line.startsWith('CASA comp ')) {
      appendComponent(casa.components, line);
    }
    if (line.startsWith('CASA region')) {
      appendRegion(casa.regions, line);
    }
    if (line.startsWith('Calib')) {
      appendCalibration(casa.calibrations, line);
    }
  }
  return casa;
}

function mapComponents(parsedBlockComment, sourceEnergy = undefined, energyUnits = 'eV') {
  const components = [];
  if (parsedBlockComment.components) {
    for (let component of parsedBlockComment.components) {
      components.push({
        ...component,
        kineticEnergy: {
          value: component.position.value,
          units: energyUnits
        },
        bindingEnergy: {
          value: sourceEnergy - component.position.value,
          units: energyUnits
        },
        assignment: component.componentID
      });
    }
  }
  return components;
}

function mapRegions(parsedBlockComment, sourceEnergy, energyUnits = 'eV') {
  const regions = [];
  if (parsedBlockComment.regions) {
    for (let region of parsedBlockComment.regions) {
      const newRegion = {
        ...region
      };
      if (newRegion?.background?.parameters?.kineticEnergyStart) {
        newRegion.background.parameters.kineticEnergyStart = {
          value: newRegion.background.parameters.kineticEnergyStart,
          units: energyUnits
        };
        newRegion.background.parameters.bindingEnergyStart = {
          value: sourceEnergy - newRegion.background.parameters.kineticEnergyStart.value,
          units: energyUnits
        };
      }
      if (newRegion?.background?.parameters?.kineticEnergyEnd) {
        newRegion.background.parameters.kineticEnergyEnd = {
          value: newRegion.background.parameters.kineticEnergyEnd,
          units: energyUnits
        };
        newRegion.background.parameters.bindingEnergyEnd = {
          value: sourceEnergy - newRegion.background.parameters.kineticEnergyEnd.value,
          units: energyUnits
        };
      }
      regions.push(newRegion);
    }
  }
  if (parsedBlockComment.regions) {
    return parsedBlockComment.regions;
  }
  return regions;
}

const XPS_REGEX = /(?<element>[a-zA-Z]{0,3})\s(?<shell>[1-6])(?<angularMomentum>[a-z])/;
const AUGER_REGEX = /(?<element>[a-zA-Z]{0,3})\s(?<transition>[a-zA-Z]{0,3})/;
function parseRegion(string) {
  const result = {
    name: string.trim(),
    orbital: {
      element: null,
      shell: null,
      angularMomentum: null
    },
    auger: {
      element: null,
      transition: null
    }
  };
  let xpsMatches = string.match(XPS_REGEX);
  let augerMatches = string.match(AUGER_REGEX);
  if (xpsMatches && xpsMatches.groups.shell) {
    result.orbital.element = xpsMatches.groups.element;
    result.orbital.shell = Number.parseInt(xpsMatches.groups.shell, 10);
    result.orbital.angularMomentum = xpsMatches.groups.angularMomentum;
  }
  if (augerMatches && augerMatches.groups.transition) {
    result.auger.element = augerMatches.groups.element;
    result.auger.transition = augerMatches.groups.transition;
  }
  return result;
}

function getNormalizedMeta(meta = {}) {
  const normalized = {
    region: parseRegion(meta['block identifier'])
  };
  const energyType = {
    kind: meta['abscissa label'].replace('energy', '').replaceAll(/\s/g, '').toLowerCase(),
    units: meta['abscissa units']
  };
  normalized.energyType = energyType;
  const source = {
    label: meta['analysis source label'],
    characteristicEnergy: {
      value: meta['analysis source characteristic energy'],
      units: 'eV'
    },
    beamWidthX: {
      value: meta['analysis source beam width x'],
      units: 'um'
    },
    beamWidthY: {
      value: meta['analysis source beam width y'],
      units: 'um'
    }
  };
  normalized.analysisSource = source;
  normalized.speciesLabel = meta['species label'];
  const parsedCASAs = parseCASA(meta.blockComment);
  normalized.components = mapComponents(parsedCASAs, source.characteristicEnergy.value);
  normalized.regions = mapRegions(parsedCASAs, source.characteristicEnergy.value);
  const increment = meta['abscissa increment'];
  normalized.from = meta['abscissa start'];
  normalized.to = meta['abscissa start'] + increment * (meta.nbOrdinateValues - 1);
  normalized.analyserMode = meta['analyser mode'];
  return normalized;
}

/**
 * Returns an Analysis from a VAMAS text file
 * @param {ArrayBuffer|string} [text] - the vamas text file
 */
function fromVamas(text) {
  text = ensureString$1(text);
  let parsed = parse(text);
  let header = parsed.header;
  let blocks = parsed.blocks;
  let title = header['experiment identifier'];
  let analysis = new Analysis();
  for (let block of blocks) {
    let sourceEnergy = block['analysis source characteristic energy'];
    let yVariable = block.correspondingVariables[0];
    let yLabel = yVariable.label;
    let yValues = yVariable.array;
    let xLabel = block['abscissa label'];
    let xUnits = block['abscissa units'];
    let xStart = block['abscissa start'];
    let xIncrement = block['abscissa increment'];
    let xValues = new Float64Array(yValues.length);
    for (let i = 0; i < yValues.length; i++) {
      xValues[i] = xStart + xIncrement * i;
    }
    // currently we take the first corresponding variables

    let meta = {};
    for (let key in block) {
      if (typeof block[key] === 'string' || typeof block[key] === 'number') {
        meta[key] = block[key];
      }
    }
    for (let key in header) {
      if (typeof header[key] === 'string' || typeof header[key] === 'number') {
        meta[key] = header[key];
      }
    }
    meta.cheminfo = {
      meta: getNormalizedMeta(meta)
    };
    const variables = {};
    if (xLabel === 'Kinetic energy' && sourceEnergy) {
      // we will calculate binding energy
      variables.x = {
        data: xValues.map(value => sourceEnergy - value).toReversed(),
        label: 'Binding energy',
        units: xUnits,
        type: 'DEPENDENT'
      };
      variables.y = {
        data: yValues.toReversed(),
        label: yLabel,
        type: 'DEPENDENT'
      };
      variables.k = {
        data: xValues.toReversed(),
        label: xLabel,
        units: xUnits,
        type: 'INDEPENDENT'
      };
    } else {
      variables.x = {
        data: xValues,
        label: xLabel,
        units: xUnits,
        type: 'INDEPENDENT'
      };
      variables.y = {
        data: yValues,
        label: yLabel,
        type: 'DEPENDENT'
      };
    }
    analysis.pushSpectrum(variables, {
      dataType: 'XPS',
      title,
      meta
    });
  }
  return analysis;
}

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

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

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

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

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

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

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

/**
 * Refine the `x` and `y` coordinates of each peak by running a quadratic
 * interpolation over the peak and its 3 closest neighbors.
 * The correction is performed in place.
 * @param data - Object with `x` and `y` arrays.
 * @param peaks - Peaks to refine (mutated in place).
 */
function optimizeTop(data, peaks) {
  const {
    x,
    y
  } = data;
  for (const peak of peaks) {
    let currentIndex = peak.index;
    // The detected peak could be moved 1 or 2 units to left or right.
    if (y[currentIndex - 1] >= y[currentIndex - 2] && y[currentIndex - 1] >= y[currentIndex]) {
      currentIndex--;
    } else if (y[currentIndex + 1] >= y[currentIndex] && y[currentIndex + 1] >= y[currentIndex + 2]) {
      currentIndex++;
    } else if (y[currentIndex - 2] >= y[currentIndex - 3] && y[currentIndex - 2] >= y[currentIndex - 1]) {
      currentIndex -= 2;
    } else if (y[currentIndex + 2] >= y[currentIndex + 1] && y[currentIndex + 2] >= y[currentIndex + 3]) {
      currentIndex += 2;
    }
    // Quadratic interpolation on log-intensities to refine the peak top.
    if (y[currentIndex - 1] > 0 && y[currentIndex + 1] > 0 && y[currentIndex] >= y[currentIndex - 1] && y[currentIndex] >= y[currentIndex + 1] && (y[currentIndex] !== y[currentIndex - 1] || y[currentIndex] !== y[currentIndex + 1])) {
      const alpha = Math.log10(y[currentIndex - 1]);
      const beta = Math.log10(y[currentIndex]);
      const gamma = Math.log10(y[currentIndex + 1]);
      const p = 0.5 * (alpha - gamma) / (alpha - 2 * beta + gamma);
      const xCurrent = x[currentIndex];
      const xPrevious = x[currentIndex - 1];
      peak.x = xCurrent + (xCurrent - xPrevious) * p;
      peak.y = y[currentIndex] - 0.25 * (y[currentIndex - 1] - y[currentIndex + 1]) * p;
    }
  }
}

/**
 * Global spectra deconvolution.
 * @param data - Object with `x` and `y` arrays. `x` must be monotone increasing.
 * @param options - Peak detection options.
 * @returns The detected peaks, sorted by ascending `x`.
 */
function gsd(data, options = {}) {
  let {
    noiseLevel
  } = options;
  const {
    sgOptions = {
      windowSize: 9,
      polynomial: 3
    },
    smoothY = false,
    maxCriteria = true,
    maxAbsoluteRatio = 0,
    minMaxRatio = 0.00025,
    realTopDetection = false,
    peakDetectionAlgorithm = 'second'
  } = options;
  if (maxAbsoluteRatio < 0 || maxAbsoluteRatio > 1) {
    throw new Error('maxAbsoluteRatio must be between 0 and 1');
  }
  const {
    x
  } = data;
  let {
    y
  } = data;
  if (xIsMonotonic(x) !== 1) {
    throw new Error('GSD only accepts monotone increasing x values');
  }
  // Copy so the `maxCriteria` / clipping loops below don't mutate the caller's array.
  y = y.slice();
  // If the max difference between delta x is less than 5%, then,
  // we can assume it to be equally spaced variable
  const isEquallySpaced = xIsEquallySpaced(x);
  if (noiseLevel === undefined) {
    if (isEquallySpaced) {
      const noiseInfo = xNoiseStandardDeviation(y);
      if (maxCriteria) {
        noiseLevel = noiseInfo.median + 1.5 * noiseInfo.sd;
      } else {
        noiseLevel = -noiseInfo.median + 1.5 * noiseInfo.sd;
      }
    } else {
      noiseLevel = 0;
    }
  } else if (!maxCriteria) {
    noiseLevel *= -1;
  }
  if (!maxCriteria) {
    for (let i = 0; i < y.length; i++) {
      y[i] = -y[i];
    }
  }
  const maxAbsoluteValue = maxAbsoluteRatio > 0 ? maxAbsoluteRatio * xMaxAbsoluteValue(y) : noiseLevel;
  if (noiseLevel !== undefined) {
    for (let i = 0; i < y.length; i++) {
      if (y[i] < noiseLevel) {
        y[i] = noiseLevel;
      }
    }
  }
  const xValue = isEquallySpaced ? x[1] - x[0] : x;
  const yData = smoothY ? sgg(y, xValue, {
    ...sgOptions,
    derivative: 0
  }) : y;
  const {
    min: minY,
    max: maxY
  } = xMinMaxValues(yData);
  if (minY > maxY || minY === maxY) return [];
  const dY = sgg(y, xValue, {
    ...sgOptions,
    derivative: 1
  });
  const ddY = sgg(y, xValue, {
    ...sgOptions,
    derivative: 2
  });
  const yThreshold = xMaxValue([noiseLevel, minY + (maxY - minY) * minMaxRatio, maxAbsoluteValue]);
  const dX = x[1] - x[0];
  const peakData = {
    x,
    y,
    yData,
    dY,
    ddY,
    dX,
    yThreshold
  };
  let peaks = [];
  if (peakDetectionAlgorithm === 'first') {
    peaks = firstDerivative(peakData);
  } else if (peakDetectionAlgorithm === 'second') {
    peaks = secondDerivative(peakData);
  } else {
    peaks = autoAlgorithm(peakData);
  }
  if (realTopDetection) {
    optimizeTop({
      x,
      y: yData
    }, peaks);
  }
  for (const peak of peaks) {
    if (!maxCriteria) {
      peak.y *= -1;
      peak.ddY = peak.ddY * -1;
    }
  }
  peaks.sort((a, b) => {
    return a.x - b.x;
  });
  return peaks;
}

/**
 * Allow
 * @param {object} spectrum
 * @param {object} [options={}]
 * @param {object} [options.gsd={}] - global spectra deconvolution options. Available options: http://mljs.github.io/global-spectral-deconvolution/#gsd
 * @returns {Array} Array of peaks
 */

function peakPicking(spectrum, options = {}) {
  const {
    gsd: gsdOptions = {
      realTopDetection: true
    }
  } = options;
  let data = {
    x: spectrum.variables.x.data,
    y: spectrum.variables.y.data
  };
  let peaks = gsd(data, gsdOptions);
  return peaks;
}

/**
 * Array of object containing element, orbital and be
 */
const references = [{
  element: 'H',
  orbital: '1s',
  be: 14
}, {
  element: 'He',
  orbital: '1s',
  be: 25
}, {
  element: 'Li',
  orbital: '1s',
  be: 55
}, {
  element: 'Be',
  orbital: '1s',
  be: 111
}, {
  element: 'B',
  orbital: '1s',
  be: 188
}, {
  element: 'C',
  orbital: '1s',
  be: 284
}, {
  element: 'N',
  orbital: '1s',
  be: 399
}, {
  element: 'O',
  orbital: '1s',
  be: 532
}, {
  element: 'F',
  orbital: '1s',
  be: 686
}, {
  element: 'Ne',
  orbital: '1s',
  be: 867
}, {
  element: 'Na',
  orbital: '1s',
  be: 1072
}, {
  element: 'O',
  orbital: '2s',
  be: 24
}, {
  element: 'F',
  orbital: '2s',
  be: 31
}, {
  element: 'Ne',
  orbital: '2s',
  be: 45
}, {
  element: 'Na',
  orbital: '2s',
  be: 63
}, {
  element: 'Mg',
  orbital: '2s',
  be: 89
}, {
  element: 'Al',
  orbital: '2s',
  be: 118
}, {
  element: 'Si',
  orbital: '2s',
  be: 149
}, {
  element: 'P',
  orbital: '2s',
  be: 189
}, {
  element: 'S',
  orbital: '2s',
  be: 229
}, {
  element: 'Cl',
  orbital: '2s',
  be: 270
}, {
  element: 'Ar',
  orbital: '2s',
  be: 320
}, {
  element: 'K',
  orbital: '2s',
  be: 377
}, {
  element: 'Ca',
  orbital: '2s',
  be: 438
}, {
  element: 'Sc',
  orbital: '2s',
  be: 500
}, {
  element: 'Ti',
  orbital: '2s',
  be: 564
}, {
  element: 'V',
  orbital: '2s',
  be: 628
}, {
  element: 'Cr',
  orbital: '2s',
  be: 695
}, {
  element: 'Mn',
  orbital: '2s',
  be: 769
}, {
  element: 'Fe',
  orbital: '2s',
  be: 846
}, {
  element: 'Co',
  orbital: '2s',
  be: 926
}, {
  element: 'Ni',
  orbital: '2s',
  be: 1008
}, {
  element: 'Cu',
  orbital: '2s',
  be: 1096
}, {
  element: 'Zn',
  orbital: '2s',
  be: 1194
}, {
  element: 'B',
  orbital: '2p1/2',
  be: 5
}, {
  element: 'C',
  orbital: '2p1/2',
  be: 7
}, {
  element: 'N',
  orbital: '2p1/2',
  be: 9
}, {
  element: 'O',
  orbital: '2p1/2',
  be: 7
}, {
  element: 'F',
  orbital: '2p1/2',
  be: 9
}, {
  element: 'Ne',
  orbital: '2p1/2',
  be: 18
}, {
  element: 'Na',
  orbital: '2p1/2',
  be: 31
}, {
  element: 'Mg',
  orbital: '2p1/2',
  be: 52
}, {
  element: 'Al',
  orbital: '2p1/2',
  be: 74
}, {
  element: 'Si',
  orbital: '2p1/2',
  be: 100
}, {
  element: 'P',
  orbital: '2p1/2',
  be: 136
}, {
  element: 'S',
  orbital: '2p1/2',
  be: 165
}, {
  element: 'Cl',
  orbital: '2p1/2',
  be: 202
}, {
  element: 'Ar',
  orbital: '2p1/2',
  be: 247
}, {
  element: 'K',
  orbital: '2p1/2',
  be: 297
}, {
  element: 'Ca',
  orbital: '2p1/2',
  be: 350
}, {
  element: 'Sc',
  orbital: '2p1/2',
  be: 407
}, {
  element: 'Ti',
  orbital: '2p1/2',
  be: 461
}, {
  element: 'V',
  orbital: '2p1/2',
  be: 520
}, {
  element: 'Cr',
  orbital: '2p1/2',
  be: 584
}, {
  element: 'Mn',
  orbital: '2p1/2',
  be: 652
}, {
  element: 'Fe',
  orbital: '2p1/2',
  be: 723
}, {
  element: 'Co',
  orbital: '2p1/2',
  be: 794
}, {
  element: 'Ni',
  orbital: '2p1/2',
  be: 872
}, {
  element: 'Cu',
  orbital: '2p1/2',
  be: 951
}, {
  element: 'Zn',
  orbital: '2p1/2',
  be: 1044
}, {
  element: 'Ga',
  orbital: '2p1/2',
  be: 1143
}, {
  element: 'Ge',
  orbital: '2p1/2',
  be: 1249
}, {
  element: 'B',
  orbital: '2p3/2',
  be: 5
}, {
  element: 'C',
  orbital: '2p3/2',
  be: 7
}, {
  element: 'N',
  orbital: '2p3/2',
  be: 9
}, {
  element: 'O',
  orbital: '2p3/2',
  be: 7
}, {
  element: 'F',
  orbital: '2p3/2',
  be: 9
}, {
  element: 'Ne',
  orbital: '2p3/2',
  be: 18
}, {
  element: 'Na',
  orbital: '2p3/2',
  be: 31
}, {
  element: 'Mg',
  orbital: '2p3/2',
  be: 52
}, {
  element: 'Al',
  orbital: '2p3/2',
  be: 73
}, {
  element: 'Si',
  orbital: '2p3/2',
  be: 99
}, {
  element: 'P',
  orbital: '2p3/2',
  be: 135
}, {
  element: 'S',
  orbital: '2p3/2',
  be: 164
}, {
  element: 'Cl',
  orbital: '2p3/2',
  be: 200
}, {
  element: 'Ar',
  orbital: '2p3/2',
  be: 245
}, {
  element: 'K',
  orbital: '2p3/2',
  be: 294
}, {
  element: 'Ca',
  orbital: '2p3/2',
  be: 347
}, {
  element: 'Sc',
  orbital: '2p3/2',
  be: 402
}, {
  element: 'Ti',
  orbital: '2p3/2',
  be: 455
}, {
  element: 'V',
  orbital: '2p3/2',
  be: 513
}, {
  element: 'Cr',
  orbital: '2p3/2',
  be: 575
}, {
  element: 'Mn',
  orbital: '2p3/2',
  be: 641
}, {
  element: 'Fe',
  orbital: '2p3/2',
  be: 710
}, {
  element: 'Co',
  orbital: '2p3/2',
  be: 779
}, {
  element: 'Ni',
  orbital: '2p3/2',
  be: 855
}, {
  element: 'Cu',
  orbital: '2p3/2',
  be: 931
}, {
  element: 'Zn',
  orbital: '2p3/2',
  be: 1021
}, {
  element: 'Ga',
  orbital: '2p3/2',
  be: 1116
}, {
  element: 'Ge',
  orbital: '2p3/2',
  be: 1217
}, {
  element: 'B',
  orbital: '2p',
  be: 5
}, {
  element: 'C',
  orbital: '2p',
  be: 7
}, {
  element: 'N',
  orbital: '2p',
  be: 9
}, {
  element: 'O',
  orbital: '2p',
  be: 7
}, {
  element: 'F',
  orbital: '2p',
  be: 9
}, {
  element: 'Ne',
  orbital: '2p',
  be: 18
}, {
  element: 'Na',
  orbital: '2p',
  be: 31
}, {
  element: 'Mg',
  orbital: '2p',
  be: 52
}, {
  element: 'Al',
  orbital: '2p',
  be: 73
}, {
  element: 'Si',
  orbital: '2p',
  be: 99
}, {
  element: 'P',
  orbital: '2p',
  be: 135
}, {
  element: 'S',
  orbital: '2p',
  be: 164
}, {
  element: 'Cl',
  orbital: '2p',
  be: 200
}, {
  element: 'Ar',
  orbital: '2p',
  be: 245
}, {
  element: 'K',
  orbital: '2p',
  be: 294
}, {
  element: 'Ca',
  orbital: '2p',
  be: 347
}, {
  element: 'Sc',
  orbital: '2p',
  be: 402
}, {
  element: 'Ti',
  orbital: '2p',
  be: 455
}, {
  element: 'V',
  orbital: '2p',
  be: 513
}, {
  element: 'Cr',
  orbital: '2p',
  be: 575
}, {
  element: 'Mn',
  orbital: '2p',
  be: 641
}, {
  element: 'Fe',
  orbital: '2p',
  be: 710
}, {
  element: 'Co',
  orbital: '2p',
  be: 779
}, {
  element: 'Ni',
  orbital: '2p',
  be: 855
}, {
  element: 'Cu',
  orbital: '2p',
  be: 931
}, {
  element: 'Zn',
  orbital: '2p',
  be: 1021
}, {
  element: 'Ga',
  orbital: '2p',
  be: 1116
}, {
  element: 'Ge',
  orbital: '2p',
  be: 1217
}, {
  element: 'Na',
  orbital: '3s',
  be: 1
}, {
  element: 'Mg',
  orbital: '3s',
  be: 2
}, {
  element: 'Al',
  orbital: '3s',
  be: 1
}, {
  element: 'Si',
  orbital: '3s',
  be: 8
}, {
  element: 'P',
  orbital: '3s',
  be: 16
}, {
  element: 'S',
  orbital: '3s',
  be: 16
}, {
  element: 'Cl',
  orbital: '3s',
  be: 18
}, {
  element: 'Ar',
  orbital: '3s',
  be: 25
}, {
  element: 'K',
  orbital: '3s',
  be: 34
}, {
  element: 'Ca',
  orbital: '3s',
  be: 44
}, {
  element: 'Sc',
  orbital: '3s',
  be: 54
}, {
  element: 'Ti',
  orbital: '3s',
  be: 59
}, {
  element: 'V',
  orbital: '3s',
  be: 66
}, {
  element: 'Cr',
  orbital: '3s',
  be: 74
}, {
  element: 'Mn',
  orbital: '3s',
  be: 84
}, {
  element: 'Fe',
  orbital: '3s',
  be: 95
}, {
  element: 'Co',
  orbital: '3s',
  be: 101
}, {
  element: 'Ni',
  orbital: '3s',
  be: 112
}, {
  element: 'Cu',
  orbital: '3s',
  be: 120
}, {
  element: 'Zn',
  orbital: '3s',
  be: 137
}, {
  element: 'Ga',
  orbital: '3s',
  be: 158
}, {
  element: 'Ge',
  orbital: '3s',
  be: 181
}, {
  element: 'As',
  orbital: '3s',
  be: 204
}, {
  element: 'Se',
  orbital: '3s',
  be: 232
}, {
  element: 'Br',
  orbital: '3s',
  be: 257
}, {
  element: 'Kr',
  orbital: '3s',
  be: 289
}, {
  element: 'Rb',
  orbital: '3s',
  be: 322
}, {
  element: 'Sr',
  orbital: '3s',
  be: 358
}, {
  element: 'Y',
  orbital: '3s',
  be: 395
}, {
  element: 'Zr',
  orbital: '3s',
  be: 431
}, {
  element: 'Nb',
  orbital: '3s',
  be: 469
}, {
  element: 'Mo',
  orbital: '3s',
  be: 505
}, {
  element: 'Tc',
  orbital: '3s',
  be: 544
}, {
  element: 'Ru',
  orbital: '3s',
  be: 585
}, {
  element: 'Rh',
  orbital: '3s',
  be: 627
}, {
  element: 'Pd',
  orbital: '3s',
  be: 670
}, {
  element: 'Ag',
  orbital: '3s',
  be: 717
}, {
  element: 'Cd',
  orbital: '3s',
  be: 770
}, {
  element: 'In',
  orbital: '3s',
  be: 826
}, {
  element: 'Sn',
  orbital: '3s',
  be: 884
}, {
  element: 'Sb',
  orbital: '3s',
  be: 944
}, {
  element: 'Te',
  orbital: '3s',
  be: 1006
}, {
  element: 'I',
  orbital: '3s',
  be: 1072
}, {
  element: 'Xe',
  orbital: '3s',
  be: 1145
}, {
  element: 'Cs',
  orbital: '3s',
  be: 1217
}, {
  element: 'Si',
  orbital: '3p1/2',
  be: 3
}, {
  element: 'P',
  orbital: '3p1/2',
  be: 10
}, {
  element: 'S',
  orbital: '3p1/2',
  be: 8
}, {
  element: 'Cl',
  orbital: '3p1/2',
  be: 7
}, {
  element: 'Ar',
  orbital: '3p1/2',
  be: 12
}, {
  element: 'K',
  orbital: '3p1/2',
  be: 18
}, {
  element: 'Ca',
  orbital: '3p1/2',
  be: 26
}, {
  element: 'Sc',
  orbital: '3p1/2',
  be: 32
}, {
  element: 'Ti',
  orbital: '3p1/2',
  be: 34
}, {
  element: 'V',
  orbital: '3p1/2',
  be: 38
}, {
  element: 'Cr',
  orbital: '3p1/2',
  be: 43
}, {
  element: 'Mn',
  orbital: '3p1/2',
  be: 49
}, {
  element: 'Fe',
  orbital: '3p1/2',
  be: 56
}, {
  element: 'Co',
  orbital: '3p1/2',
  be: 60
}, {
  element: 'Ni',
  orbital: '3p1/2',
  be: 68
}, {
  element: 'Cu',
  orbital: '3p1/2',
  be: 74
}, {
  element: 'Zn',
  orbital: '3p1/2',
  be: 87
}, {
  element: 'Ga',
  orbital: '3p1/2',
  be: 107
}, {
  element: 'Ge',
  orbital: '3p1/2',
  be: 129
}, {
  element: 'As',
  orbital: '3p1/2',
  be: 147
}, {
  element: 'Se',
  orbital: '3p1/2',
  be: 168
}, {
  element: 'Br',
  orbital: '3p1/2',
  be: 189
}, {
  element: 'Kr',
  orbital: '3p1/2',
  be: 223
}, {
  element: 'Rb',
  orbital: '3p1/2',
  be: 248
}, {
  element: 'Sr',
  orbital: '3p1/2',
  be: 280
}, {
  element: 'Y',
  orbital: '3p1/2',
  be: 313
}, {
  element: 'Zr',
  orbital: '3p1/2',
  be: 345
}, {
  element: 'Nb',
  orbital: '3p1/2',
  be: 379
}, {
  element: 'Mo',
  orbital: '3p1/2',
  be: 410
}, {
  element: 'Tc',
  orbital: '3p1/2',
  be: 445
}, {
  element: 'Ru',
  orbital: '3p1/2',
  be: 483
}, {
  element: 'Rh',
  orbital: '3p1/2',
  be: 521
}, {
  element: 'Pd',
  orbital: '3p1/2',
  be: 559
}, {
  element: 'Ag',
  orbital: '3p1/2',
  be: 602
}, {
  element: 'Cd',
  orbital: '3p1/2',
  be: 651
}, {
  element: 'In',
  orbital: '3p1/2',
  be: 702
}, {
  element: 'Sn',
  orbital: '3p1/2',
  be: 757
}, {
  element: 'Sb',
  orbital: '3p1/2',
  be: 812
}, {
  element: 'Te',
  orbital: '3p1/2',
  be: 870
}, {
  element: 'I',
  orbital: '3p1/2',
  be: 931
}, {
  element: 'Xe',
  orbital: '3p1/2',
  be: 999
}, {
  element: 'Cs',
  orbital: '3p1/2',
  be: 1065
}, {
  element: 'Ba',
  orbital: '3p1/2',
  be: 1137
}, {
  element: 'La',
  orbital: '3p1/2',
  be: 1205
}, {
  element: 'Si',
  orbital: '3p3/2',
  be: 3
}, {
  element: 'P',
  orbital: '3p3/2',
  be: 10
}, {
  element: 'S',
  orbital: '3p3/2',
  be: 8
}, {
  element: 'Cl',
  orbital: '3p3/2',
  be: 7
}, {
  element: 'Ar',
  orbital: '3p3/2',
  be: 12
}, {
  element: 'K',
  orbital: '3p3/2',
  be: 18
}, {
  element: 'Ca',
  orbital: '3p3/2',
  be: 26
}, {
  element: 'Sc',
  orbital: '3p3/2',
  be: 32
}, {
  element: 'Ti',
  orbital: '3p3/2',
  be: 34
}, {
  element: 'V',
  orbital: '3p3/2',
  be: 38
}, {
  element: 'Cr',
  orbital: '3p3/2',
  be: 43
}, {
  element: 'Mn',
  orbital: '3p3/2',
  be: 49
}, {
  element: 'Fe',
  orbital: '3p3/2',
  be: 56
}, {
  element: 'Co',
  orbital: '3p3/2',
  be: 60
}, {
  element: 'Ni',
  orbital: '3p3/2',
  be: 68
}, {
  element: 'Cu',
  orbital: '3p3/2',
  be: 74
}, {
  element: 'Zn',
  orbital: '3p3/2',
  be: 87
}, {
  element: 'Ga',
  orbital: '3p3/2',
  be: 103
}, {
  element: 'Ge',
  orbital: '3p3/2',
  be: 122
}, {
  element: 'As',
  orbital: '3p3/2',
  be: 141
}, {
  element: 'Se',
  orbital: '3p3/2',
  be: 162
}, {
  element: 'Br',
  orbital: '3p3/2',
  be: 182
}, {
  element: 'Kr',
  orbital: '3p3/2',
  be: 214
}, {
  element: 'Rb',
  orbital: '3p3/2',
  be: 239
}, {
  element: 'Sr',
  orbital: '3p3/2',
  be: 269
}, {
  element: 'Y',
  orbital: '3p3/2',
  be: 301
}, {
  element: 'Zr',
  orbital: '3p3/2',
  be: 331
}, {
  element: 'Nb',
  orbital: '3p3/2',
  be: 363
}, {
  element: 'Mo',
  orbital: '3p3/2',
  be: 393
}, {
  element: 'Tc',
  orbital: '3p3/2',
  be: 425
}, {
  element: 'Ru',
  orbital: '3p3/2',
  be: 461
}, {
  element: 'Rh',
  orbital: '3p3/2',
  be: 496
}, {
  element: 'Pd',
  orbital: '3p3/2',
  be: 531
}, {
  element: 'Ag',
  orbital: '3p3/2',
  be: 571
}, {
  element: 'Cd',
  orbital: '3p3/2',
  be: 617
}, {
  element: 'In',
  orbital: '3p3/2',
  be: 664
}, {
  element: 'Sn',
  orbital: '3p3/2',
  be: 715
}, {
  element: 'Sb',
  orbital: '3p3/2',
  be: 766
}, {
  element: 'Te',
  orbital: '3p3/2',
  be: 819
}, {
  element: 'I',
  orbital: '3p3/2',
  be: 875
}, {
  element: 'Xe',
  orbital: '3p3/2',
  be: 937
}, {
  element: 'Cs',
  orbital: '3p3/2',
  be: 998
}, {
  element: 'Ba',
  orbital: '3p3/2',
  be: 1063
}, {
  element: 'La',
  orbital: '3p3/2',
  be: 1124
}, {
  element: 'Ce',
  orbital: '3p3/2',
  be: 1186
}, {
  element: 'Pr',
  orbital: '3p3/2',
  be: 1243
}, {
  element: 'Si',
  orbital: '3p',
  be: 3
}, {
  element: 'P',
  orbital: '3p',
  be: 10
}, {
  element: 'S',
  orbital: '3p',
  be: 8
}, {
  element: 'Cl',
  orbital: '3p',
  be: 7
}, {
  element: 'Ar',
  orbital: '3p',
  be: 12
}, {
  element: 'K',
  orbital: '3p',
  be: 18
}, {
  element: 'Ca',
  orbital: '3p',
  be: 26
}, {
  element: 'Sc',
  orbital: '3p',
  be: 32
}, {
  element: 'Ti',
  orbital: '3p',
  be: 34
}, {
  element: 'V',
  orbital: '3p',
  be: 38
}, {
  element: 'Cr',
  orbital: '3p',
  be: 43
}, {
  element: 'Mn',
  orbital: '3p',
  be: 49
}, {
  element: 'Fe',
  orbital: '3p',
  be: 56
}, {
  element: 'Co',
  orbital: '3p',
  be: 60
}, {
  element: 'Ni',
  orbital: '3p',
  be: 68
}, {
  element: 'Cu',
  orbital: '3p',
  be: 74
}, {
  element: 'Zn',
  orbital: '3p',
  be: 87
}, {
  element: 'Ga',
  orbital: '3p',
  be: 103
}, {
  element: 'Ge',
  orbital: '3p',
  be: 122
}, {
  element: 'As',
  orbital: '3p',
  be: 141
}, {
  element: 'Se',
  orbital: '3p',
  be: 162
}, {
  element: 'Br',
  orbital: '3p',
  be: 182
}, {
  element: 'Kr',
  orbital: '3p',
  be: 214
}, {
  element: 'Rb',
  orbital: '3p',
  be: 239
}, {
  element: 'Sr',
  orbital: '3p',
  be: 269
}, {
  element: 'Y',
  orbital: '3p',
  be: 301
}, {
  element: 'Zr',
  orbital: '3p',
  be: 331
}, {
  element: 'Nb',
  orbital: '3p',
  be: 363
}, {
  element: 'Mo',
  orbital: '3p',
  be: 393
}, {
  element: 'Tc',
  orbital: '3p',
  be: 425
}, {
  element: 'Ru',
  orbital: '3p',
  be: 461
}, {
  element: 'Rh',
  orbital: '3p',
  be: 496
}, {
  element: 'Pd',
  orbital: '3p',
  be: 531
}, {
  element: 'Ag',
  orbital: '3p',
  be: 571
}, {
  element: 'Cd',
  orbital: '3p',
  be: 617
}, {
  element: 'In',
  orbital: '3p',
  be: 664
}, {
  element: 'Sn',
  orbital: '3p',
  be: 715
}, {
  element: 'Sb',
  orbital: '3p',
  be: 766
}, {
  element: 'Te',
  orbital: '3p',
  be: 819
}, {
  element: 'I',
  orbital: '3p',
  be: 875
}, {
  element: 'Xe',
  orbital: '3p',
  be: 937
}, {
  element: 'Cs',
  orbital: '3p',
  be: 998
}, {
  element: 'Ba',
  orbital: '3p',
  be: 1063
}, {
  element: 'La',
  orbital: '3p',
  be: 1124
}, {
  element: 'Ce',
  orbital: '3p',
  be: 1186
}, {
  element: 'Pr',
  orbital: '3p',
  be: 1243
}, {
  element: 'Sc',
  orbital: '3d3/2',
  be: 7
}, {
  element: 'Ti',
  orbital: '3d3/2',
  be: 3
}, {
  element: 'V',
  orbital: '3d3/2',
  be: 2
}, {
  element: 'Cr',
  orbital: '3d3/2',
  be: 2
}, {
  element: 'Mn',
  orbital: '3d3/2',
  be: 4
}, {
  element: 'Fe',
  orbital: '3d3/2',
  be: 6
}, {
  element: 'Co',
  orbital: '3d3/2',
  be: 3
}, {
  element: 'Ni',
  orbital: '3d3/2',
  be: 4
}, {
  element: 'Cu',
  orbital: '3d3/2',
  be: 2
}, {
  element: 'Zn',
  orbital: '3d3/2',
  be: 9
}, {
  element: 'Ga',
  orbital: '3d3/2',
  be: 18
}, {
  element: 'Ge',
  orbital: '3d3/2',
  be: 29
}, {
  element: 'As',
  orbital: '3d3/2',
  be: 41
}, {
  element: 'Se',
  orbital: '3d3/2',
  be: 57
}, {
  element: 'Br',
  orbital: '3d3/2',
  be: 70
}, {
  element: 'Kr',
  orbital: '3d3/2',
  be: 89
}, {
  element: 'Rb',
  orbital: '3d3/2',
  be: 112
}, {
  element: 'Sr',
  orbital: '3d3/2',
  be: 135
}, {
  element: 'Y',
  orbital: '3d3/2',
  be: 160
}, {
  element: 'Zr',
  orbital: '3d3/2',
  be: 183
}, {
  element: 'Nb',
  orbital: '3d3/2',
  be: 208
}, {
  element: 'Mo',
  orbital: '3d3/2',
  be: 230
}, {
  element: 'Tc',
  orbital: '3d3/2',
  be: 257
}, {
  element: 'Ru',
  orbital: '3d3/2',
  be: 284
}, {
  element: 'Rh',
  orbital: '3d3/2',
  be: 312
}, {
  element: 'Pd',
  orbital: '3d3/2',
  be: 340
}, {
  element: 'Ag',
  orbital: '3d3/2',
  be: 373
}, {
  element: 'Cd',
  orbital: '3d3/2',
  be: 411
}, {
  element: 'In',
  orbital: '3d3/2',
  be: 451
}, {
  element: 'Sn',
  orbital: '3d3/2',
  be: 494
}, {
  element: 'Sb',
  orbital: '3d3/2',
  be: 537
}, {
  element: 'Te',
  orbital: '3d3/2',
  be: 582
}, {
  element: 'I',
  orbital: '3d3/2',
  be: 631
}, {
  element: 'Xe',
  orbital: '3d3/2',
  be: 685
}, {
  element: 'Cs',
  orbital: '3d3/2',
  be: 740
}, {
  element: 'Ba',
  orbital: '3d3/2',
  be: 796
}, {
  element: 'La',
  orbital: '3d3/2',
  be: 849
}, {
  element: 'Ce',
  orbital: '3d3/2',
  be: 902
}, {
  element: 'Pr',
  orbital: '3d3/2',
  be: 951
}, {
  element: 'Nd',
  orbital: '3d3/2',
  be: 1000
}, {
  element: 'Pm',
  orbital: '3d3/2',
  be: 1052
}, {
  element: 'Sm',
  orbital: '3d3/2',
  be: 1107
}, {
  element: 'Eu',
  orbital: '3d3/2',
  be: 1161
}, {
  element: 'Gd',
  orbital: '3d3/2',
  be: 1218
}, {
  element: 'Sc',
  orbital: '3d5/2',
  be: 7
}, {
  element: 'Ti',
  orbital: '3d5/2',
  be: 3
}, {
  element: 'V',
  orbital: '3d5/2',
  be: 2
}, {
  element: 'Cr',
  orbital: '3d5/2',
  be: 2
}, {
  element: 'Mn',
  orbital: '3d5/2',
  be: 4
}, {
  element: 'Fe',
  orbital: '3d5/2',
  be: 6
}, {
  element: 'Co',
  orbital: '3d5/2',
  be: 3
}, {
  element: 'Ni',
  orbital: '3d5/2',
  be: 4
}, {
  element: 'Cu',
  orbital: '3d5/2',
  be: 2
}, {
  element: 'Zn',
  orbital: '3d5/2',
  be: 9
}, {
  element: 'Ga',
  orbital: '3d5/2',
  be: 18
}, {
  element: 'Ge',
  orbital: '3d5/2',
  be: 29
}, {
  element: 'As',
  orbital: '3d5/2',
  be: 41
}, {
  element: 'Se',
  orbital: '3d5/2',
  be: 57
}, {
  element: 'Br',
  orbital: '3d5/2',
  be: 69
}, {
  element: 'Kr',
  orbital: '3d5/2',
  be: 89
}, {
  element: 'Rb',
  orbital: '3d5/2',
  be: 111
}, {
  element: 'Sr',
  orbital: '3d5/2',
  be: 133
}, {
  element: 'Y',
  orbital: '3d5/2',
  be: 158
}, {
  element: 'Zr',
  orbital: '3d5/2',
  be: 180
}, {
  element: 'Nb',
  orbital: '3d5/2',
  be: 205
}, {
  element: 'Mo',
  orbital: '3d5/2',
  be: 227
}, {
  element: 'Tc',
  orbital: '3d5/2',
  be: 253
}, {
  element: 'Ru',
  orbital: '3d5/2',
  be: 279
}, {
  element: 'Rh',
  orbital: '3d5/2',
  be: 307
}, {
  element: 'Pd',
  orbital: '3d5/2',
  be: 335
}, {
  element: 'Ag',
  orbital: '3d5/2',
  be: 367
}, {
  element: 'Cd',
  orbital: '3d5/2',
  be: 404
}, {
  element: 'In',
  orbital: '3d5/2',
  be: 443
}, {
  element: 'Sn',
  orbital: '3d5/2',
  be: 485
}, {
  element: 'Sb',
  orbital: '3d5/2',
  be: 528
}, {
  element: 'Te',
  orbital: '3d5/2',
  be: 572
}, {
  element: 'I',
  orbital: '3d5/2',
  be: 620
}, {
  element: 'Xe',
  orbital: '3d5/2',
  be: 672
}, {
  element: 'Cs',
  orbital: '3d5/2',
  be: 726
}, {
  element: 'Ba',
  orbital: '3d5/2',
  be: 781
}, {
  element: 'La',
  orbital: '3d5/2',
  be: 832
}, {
  element: 'Ce',
  orbital: '3d5/2',
  be: 884
}, {
  element: 'Pr',
  orbital: '3d5/2',
  be: 931
}, {
  element: 'Nd',
  orbital: '3d5/2',
  be: 978
}, {
  element: 'Pm',
  orbital: '3d5/2',
  be: 1027
}, {
  element: 'Sm',
  orbital: '3d5/2',
  be: 1081
}, {
  element: 'Eu',
  orbital: '3d5/2',
  be: 1131
}, {
  element: 'Gd',
  orbital: '3d5/2',
  be: 1186
}, {
  element: 'Tb',
  orbital: '3d5/2',
  be: 1242
}, {
  element: 'Sc',
  orbital: '3d',
  be: 7
}, {
  element: 'Ti',
  orbital: '3d',
  be: 3
}, {
  element: 'V',
  orbital: '3d',
  be: 2
}, {
  element: 'Cr',
  orbital: '3d',
  be: 2
}, {
  element: 'Mn',
  orbital: '3d',
  be: 4
}, {
  element: 'Fe',
  orbital: '3d',
  be: 6
}, {
  element: 'Co',
  orbital: '3d',
  be: 3
}, {
  element: 'Ni',
  orbital: '3d',
  be: 4
}, {
  element: 'Cu',
  orbital: '3d',
  be: 2
}, {
  element: 'Zn',
  orbital: '3d',
  be: 9
}, {
  element: 'Ga',
  orbital: '3d',
  be: 18
}, {
  element: 'Ge',
  orbital: '3d',
  be: 29
}, {
  element: 'As',
  orbital: '3d',
  be: 41
}, {
  element: 'Se',
  orbital: '3d',
  be: 57
}, {
  element: 'Br',
  orbital: '3d',
  be: 69
}, {
  element: 'Kr',
  orbital: '3d',
  be: 89
}, {
  element: 'Rb',
  orbital: '3d',
  be: 111
}, {
  element: 'Sr',
  orbital: '3d',
  be: 133
}, {
  element: 'Y',
  orbital: '3d',
  be: 158
}, {
  element: 'Zr',
  orbital: '3d',
  be: 180
}, {
  element: 'Nb',
  orbital: '3d',
  be: 205
}, {
  element: 'Mo',
  orbital: '3d',
  be: 227
}, {
  element: 'Tc',
  orbital: '3d',
  be: 253
}, {
  element: 'Ru',
  orbital: '3d',
  be: 279
}, {
  element: 'Rh',
  orbital: '3d',
  be: 307
}, {
  element: 'Pd',
  orbital: '3d',
  be: 335
}, {
  element: 'Ag',
  orbital: '3d',
  be: 367
}, {
  element: 'Cd',
  orbital: '3d',
  be: 404
}, {
  element: 'In',
  orbital: '3d',
  be: 443
}, {
  element: 'Sn',
  orbital: '3d',
  be: 485
}, {
  element: 'Sb',
  orbital: '3d',
  be: 528
}, {
  element: 'Te',
  orbital: '3d',
  be: 572
}, {
  element: 'I',
  orbital: '3d',
  be: 620
}, {
  element: 'Xe',
  orbital: '3d',
  be: 672
}, {
  element: 'Cs',
  orbital: '3d',
  be: 726
}, {
  element: 'Ba',
  orbital: '3d',
  be: 781
}, {
  element: 'La',
  orbital: '3d',
  be: 832
}, {
  element: 'Ce',
  orbital: '3d',
  be: 884
}, {
  element: 'Pr',
  orbital: '3d',
  be: 931
}, {
  element: 'Nd',
  orbital: '3d',
  be: 978
}, {
  element: 'Pm',
  orbital: '3d',
  be: 1027
}, {
  element: 'Sm',
  orbital: '3d',
  be: 1081
}, {
  element: 'Eu',
  orbital: '3d',
  be: 1131
}, {
  element: 'Gd',
  orbital: '3d',
  be: 1186
}, {
  element: 'Tb',
  orbital: '3d',
  be: 1242
}, {
  element: 'Br',
  orbital: '4s',
  be: 27
}, {
  element: 'Kr',
  orbital: '4s',
  be: 24
}, {
  element: 'Rb',
  orbital: '4s',
  be: 30
}, {
  element: 'Sr',
  orbital: '4s',
  be: 38
}, {
  element: 'Y',
  orbital: '4s',
  be: 46
}, {
  element: 'Zr',
  orbital: '4s',
  be: 52
}, {
  element: 'Nb',
  orbital: '4s',
  be: 58
}, {
  element: 'Mo',
  orbital: '4s',
  be: 62
}, {
  element: 'Tc',
  orbital: '4s',
  be: 68
}, {
  element: 'Ru',
  orbital: '4s',
  be: 75
}, {
  element: 'Rh',
  orbital: '4s',
  be: 81
}, {
  element: 'Pd',
  orbital: '4s',
  be: 86
}, {
  element: 'Ag',
  orbital: '4s',
  be: 95
}, {
  element: 'Cd',
  orbital: '4s',
  be: 108
}, {
  element: 'In',
  orbital: '4s',
  be: 122
}, {
  element: 'Sn',
  orbital: '4s',
  be: 137
}, {
  element: 'Sb',
  orbital: '4s',
  be: 152
}, {
  element: 'Te',
  orbital: '4s',
  be: 168
}, {
  element: 'I',
  orbital: '4s',
  be: 186
}, {
  element: 'Xe',
  orbital: '4s',
  be: 208
}, {
  element: 'Cs',
  orbital: '4s',
  be: 231
}, {
  element: 'Ba',
  orbital: '4s',
  be: 253
}, {
  element: 'La',
  orbital: '4s',
  be: 271
}, {
  element: 'Ce',
  orbital: '4s',
  be: 290
}, {
  element: 'Pr',
  orbital: '4s',
  be: 305
}, {
  element: 'Nd',
  orbital: '4s',
  be: 316
}, {
  element: 'Pm',
  orbital: '4s',
  be: 331
}, {
  element: 'Sm',
  orbital: '4s',
  be: 347
}, {
  element: 'Eu',
  orbital: '4s',
  be: 360
}, {
  element: 'Gd',
  orbital: '4s',
  be: 376
}, {
  element: 'Tb',
  orbital: '4s',
  be: 398
}, {
  element: 'Dy',
  orbital: '4s',
  be: 416
}, {
  element: 'Ho',
  orbital: '4s',
  be: 436
}, {
  element: 'Er',
  orbital: '4s',
  be: 449
}, {
  element: 'Tm',
  orbital: '4s',
  be: 472
}, {
  element: 'Yb',
  orbital: '4s',
  be: 487
}, {
  element: 'Lu',
  orbital: '4s',
  be: 506
}, {
  element: 'Hf',
  orbital: '4s',
  be: 538
}, {
  element: 'Ta',
  orbital: '4s',
  be: 566
}, {
  element: 'W',
  orbital: '4s',
  be: 595
}, {
  element: 'Re',
  orbital: '4s',
  be: 625
}, {
  element: 'Os',
  orbital: '4s',
  be: 655
}, {
  element: 'Ir',
  orbital: '4s',
  be: 690
}, {
  element: 'Pt',
  orbital: '4s',
  be: 724
}, {
  element: 'Au',
  orbital: '4s',
  be: 759
}, {
  element: 'Hg',
  orbital: '4s',
  be: 800
}, {
  element: 'Tl',
  orbital: '4s',
  be: 846
}, {
  element: 'Pb',
  orbital: '4s',
  be: 894
}, {
  element: 'Bi',
  orbital: '4s',
  be: 939
}, {
  element: 'Po',
  orbital: '4s',
  be: 995
}, {
  element: 'At',
  orbital: '4s',
  be: 1042
}, {
  element: 'Rn',
  orbital: '4s',
  be: 1097
}, {
  element: 'Fr',
  orbital: '4s',
  be: 1153
}, {
  element: 'Ra',
  orbital: '4s',
  be: 1208
}, {
  element: 'U',
  orbital: '4s',
  be: 1043
}, {
  element: 'Ga',
  orbital: '4p1/2',
  be: 1
}, {
  element: 'Ge',
  orbital: '4p1/2',
  be: 3
}, {
  element: 'As',
  orbital: '4p1/2',
  be: 3
}, {
  element: 'Se',
  orbital: '4p1/2',
  be: 6
}, {
  element: 'Br',
  orbital: '4p1/2',
  be: 5
}, {
  element: 'Kr',
  orbital: '4p1/2',
  be: 11
}, {
  element: 'Rb',
  orbital: '4p1/2',
  be: 15
}, {
  element: 'Sr',
  orbital: '4p1/2',
  be: 20
}, {
  element: 'Y',
  orbital: '4p1/2',
  be: 26
}, {
  element: 'Zr',
  orbital: '4p1/2',
  be: 29
}, {
  element: 'Nb',
  orbital: '4p1/2',
  be: 34
}, {
  element: 'Mo',
  orbital: '4p1/2',
  be: 35
}, {
  element: 'Tc',
  orbital: '4p1/2',
  be: 39
}, {
  element: 'Ru',
  orbital: '4p1/2',
  be: 43
}, {
  element: 'Rh',
  orbital: '4p1/2',
  be: 48
}, {
  element: 'Pd',
  orbital: '4p1/2',
  be: 51
}, {
  element: 'Ag',
  orbital: '4p1/2',
  be: 62
}, {
  element: 'Cd',
  orbital: '4p1/2',
  be: 67
}, {
  element: 'In',
  orbital: '4p1/2',
  be: 77
}, {
  element: 'Sn',
  orbital: '4p1/2',
  be: 89
}, {
  element: 'Sb',
  orbital: '4p1/2',
  be: 99
}, {
  element: 'Te',
  orbital: '4p1/2',
  be: 110
}, {
  element: 'I',
  orbital: '4p1/2',
  be: 123
}, {
  element: 'Xe',
  orbital: '4p1/2',
  be: 147
}, {
  element: 'Cs',
  orbital: '4p1/2',
  be: 172
}, {
  element: 'Ba',
  orbital: '4p1/2',
  be: 192
}, {
  element: 'La',
  orbital: '4p1/2',
  be: 206
}, {
  element: 'Ce',
  orbital: '4p1/2',
  be: 224
}, {
  element: 'Pr',
  orbital: '4p1/2',
  be: 237
}, {
  element: 'Nd',
  orbital: '4p1/2',
  be: 244
}, {
  element: 'Pm',
  orbital: '4p1/2',
  be: 255
}, {
  element: 'Sm',
  orbital: '4p1/2',
  be: 267
}, {
  element: 'Eu',
  orbital: '4p1/2',
  be: 284
}, {
  element: 'Gd',
  orbital: '4p1/2',
  be: 289
}, {
  element: 'Tb',
  orbital: '4p1/2',
  be: 311
}, {
  element: 'Dy',
  orbital: '4p1/2',
  be: 332
}, {
  element: 'Ho',
  orbital: '4p1/2',
  be: 343
}, {
  element: 'Er',
  orbital: '4p1/2',
  be: 366
}, {
  element: 'Tm',
  orbital: '4p1/2',
  be: 386
}, {
  element: 'Yb',
  orbital: '4p1/2',
  be: 396
}, {
  element: 'Lu',
  orbital: '4p1/2',
  be: 410
}, {
  element: 'Hf',
  orbital: '4p1/2',
  be: 437
}, {
  element: 'Ta',
  orbital: '4p1/2',
  be: 465
}, {
  element: 'W',
  orbital: '4p1/2',
  be: 492
}, {
  element: 'Re',
  orbital: '4p1/2',
  be: 518
}, {
  element: 'Os',
  orbital: '4p1/2',
  be: 547
}, {
  element: 'Ir',
  orbital: '4p1/2',
  be: 577
}, {
  element: 'Pt',
  orbital: '4p1/2',
  be: 608
}, {
  element: 'Au',
  orbital: '4p1/2',
  be: 644
}, {
  element: 'Hg',
  orbital: '4p1/2',
  be: 677
}, {
  element: 'Tl',
  orbital: '4p1/2',
  be: 722
}, {
  element: 'Pb',
  orbital: '4p1/2',
  be: 764
}, {
  element: 'Bi',
  orbital: '4p1/2',
  be: 806
}, {
  element: 'Po',
  orbital: '4p1/2',
  be: 851
}, {
  element: 'At',
  orbital: '4p1/2',
  be: 886
}, {
  element: 'Rn',
  orbital: '4p1/2',
  be: 929
}, {
  element: 'Fr',
  orbital: '4p1/2',
  be: 980
}, {
  element: 'Ra',
  orbital: '4p1/2',
  be: 1058
}, {
  element: 'Ac',
  orbital: '4p1/2',
  be: 1080
}, {
  element: 'Th',
  orbital: '4p1/2',
  be: 1168
}, {
  element: 'Ga',
  orbital: '4p3/2',
  be: 1
}, {
  element: 'Ge',
  orbital: '4p3/2',
  be: 3
}, {
  element: 'As',
  orbital: '4p3/2',
  be: 3
}, {
  element: 'Se',
  orbital: '4p3/2',
  be: 6
}, {
  element: 'Br',
  orbital: '4p3/2',
  be: 5
}, {
  element: 'Kr',
  orbital: '4p3/2',
  be: 11
}, {
  element: 'Rb',
  orbital: '4p3/2',
  be: 14
}, {
  element: 'Sr',
  orbital: '4p3/2',
  be: 20
}, {
  element: 'Y',
  orbital: '4p3/2',
  be: 26
}, {
  element: 'Zr',
  orbital: '4p3/2',
  be: 29
}, {
  element: 'Nb',
  orbital: '4p3/2',
  be: 34
}, {
  element: 'Mo',
  orbital: '4p3/2',
  be: 35
}, {
  element: 'Tc',
  orbital: '4p3/2',
  be: 39
}, {
  element: 'Ru',
  orbital: '4p3/2',
  be: 43
}, {
  element: 'Rh',
  orbital: '4p3/2',
  be: 48
}, {
  element: 'Pd',
  orbital: '4p3/2',
  be: 56
}, {
  element: 'Ag',
  orbital: '4p3/2',
  be: 56
}, {
  element: 'Cd',
  orbital: '4p3/2',
  be: 67
}, {
  element: 'In',
  orbital: '4p3/2',
  be: 77
}, {
  element: 'Sn',
  orbital: '4p3/2',
  be: 89
}, {
  element: 'Sb',
  orbital: '4p3/2',
  be: 99
}, {
  element: 'Te',
  orbital: '4p3/2',
  be: 110
}, {
  element: 'I',
  orbital: '4p3/2',
  be: 123
}, {
  element: 'Xe',
  orbital: '4p3/2',
  be: 147
}, {
  element: 'Cs',
  orbital: '4p3/2',
  be: 162
}, {
  element: 'Ba',
  orbital: '4p3/2',
  be: 180
}, {
  element: 'La',
  orbital: '4p3/2',
  be: 192
}, {
  element: 'Ce',
  orbital: '4p3/2',
  be: 208
}, {
  element: 'Pr',
  orbital: '4p3/2',
  be: 218
}, {
  element: 'Nd',
  orbital: '4p3/2',
  be: 225
}, {
  element: 'Pm',
  orbital: '4p3/2',
  be: 237
}, {
  element: 'Sm',
  orbital: '4p3/2',
  be: 249
}, {
  element: 'Eu',
  orbital: '4p3/2',
  be: 257
}, {
  element: 'Gd',
  orbital: '4p3/2',
  be: 271
}, {
  element: 'Tb',
  orbital: '4p3/2',
  be: 286
}, {
  element: 'Dy',
  orbital: '4p3/2',
  be: 293
}, {
  element: 'Ho',
  orbital: '4p3/2',
  be: 306
}, {
  element: 'Er',
  orbital: '4p3/2',
  be: 320
}, {
  element: 'Tm',
  orbital: '4p3/2',
  be: 337
}, {
  element: 'Yb',
  orbital: '4p3/2',
  be: 343
}, {
  element: 'Lu',
  orbital: '4p3/2',
  be: 359
}, {
  element: 'Hf',
  orbital: '4p3/2',
  be: 380
}, {
  element: 'Ta',
  orbital: '4p3/2',
  be: 405
}, {
  element: 'W',
  orbital: '4p3/2',
  be: 426
}, {
  element: 'Re',
  orbital: '4p3/2',
  be: 445
}, {
  element: 'Os',
  orbital: '4p3/2',
  be: 469
}, {
  element: 'Ir',
  orbital: '4p3/2',
  be: 495
}, {
  element: 'Pt',
  orbital: '4p3/2',
  be: 519
}, {
  element: 'Au',
  orbital: '4p3/2',
  be: 546
}, {
  element: 'Hg',
  orbital: '4p3/2',
  be: 571
}, {
  element: 'Tl',
  orbital: '4p3/2',
  be: 609
}, {
  element: 'Pb',
  orbital: '4p3/2',
  be: 645
}, {
  element: 'Bi',
  orbital: '4p3/2',
  be: 679
}, {
  element: 'Po',
  orbital: '4p3/2',
  be: 705
}, {
  element: 'At',
  orbital: '4p3/2',
  be: 740
}, {
  element: 'Rn',
  orbital: '4p3/2',
  be: 768
}, {
  element: 'Fr',
  orbital: '4p3/2',
  be: 810
}, {
  element: 'Ra',
  orbital: '4p3/2',
  be: 879
}, {
  element: 'Ac',
  orbital: '4p3/2',
  be: 890
}, {
  element: 'Th',
  orbital: '4p3/2',
  be: 968
}, {
  element: 'Ga',
  orbital: '4p',
  be: 1
}, {
  element: 'Ge',
  orbital: '4p',
  be: 3
}, {
  element: 'As',
  orbital: '4p',
  be: 3
}, {
  element: 'Se',
  orbital: '4p',
  be: 6
}, {
  element: 'Br',
  orbital: '4p',
  be: 5
}, {
  element: 'Kr',
  orbital: '4p',
  be: 11
}, {
  element: 'Rb',
  orbital: '4p',
  be: 14
}, {
  element: 'Sr',
  orbital: '4p',
  be: 20
}, {
  element: 'Y',
  orbital: '4p',
  be: 26
}, {
  element: 'Zr',
  orbital: '4p',
  be: 29
}, {
  element: 'Nb',
  orbital: '4p',
  be: 34
}, {
  element: 'Mo',
  orbital: '4p',
  be: 35
}, {
  element: 'Tc',
  orbital: '4p',
  be: 39
}, {
  element: 'Ru',
  orbital: '4p',
  be: 43
}, {
  element: 'Rh',
  orbital: '4p',
  be: 48
}, {
  element: 'Pd',
  orbital: '4p',
  be: 56
}, {
  element: 'Ag',
  orbital: '4p',
  be: 56
}, {
  element: 'Cd',
  orbital: '4p',
  be: 67
}, {
  element: 'In',
  orbital: '4p',
  be: 77
}, {
  element: 'Sn',
  orbital: '4p',
  be: 89
}, {
  element: 'Sb',
  orbital: '4p',
  be: 99
}, {
  element: 'Te',
  orbital: '4p',
  be: 110
}, {
  element: 'I',
  orbital: '4p',
  be: 123
}, {
  element: 'Xe',
  orbital: '4p',
  be: 147
}, {
  element: 'Cs',
  orbital: '4p',
  be: 162
}, {
  element: 'Ba',
  orbital: '4p',
  be: 180
}, {
  element: 'La',
  orbital: '4p',
  be: 192
}, {
  element: 'Ce',
  orbital: '4p',
  be: 208
}, {
  element: 'Pr',
  orbital: '4p',
  be: 218
}, {
  element: 'Nd',
  orbital: '4p',
  be: 225
}, {
  element: 'Pm',
  orbital: '4p',
  be: 237
}, {
  element: 'Sm',
  orbital: '4p',
  be: 249
}, {
  element: 'Eu',
  orbital: '4p',
  be: 257
}, {
  element: 'Gd',
  orbital: '4p',
  be: 271
}, {
  element: 'Tb',
  orbital: '4p',
  be: 286
}, {
  element: 'Dy',
  orbital: '4p',
  be: 293
}, {
  element: 'Ho',
  orbital: '4p',
  be: 306
}, {
  element: 'Er',
  orbital: '4p',
  be: 320
}, {
  element: 'Tm',
  orbital: '4p',
  be: 337
}, {
  element: 'Yb',
  orbital: '4p',
  be: 343
}, {
  element: 'Lu',
  orbital: '4p',
  be: 359
}, {
  element: 'Hf',
  orbital: '4p',
  be: 380
}, {
  element: 'Ta',
  orbital: '4p',
  be: 405
}, {
  element: 'W',
  orbital: '4p',
  be: 426
}, {
  element: 'Re',
  orbital: '4p',
  be: 445
}, {
  element: 'Os',
  orbital: '4p',
  be: 469
}, {
  element: 'Ir',
  orbital: '4p',
  be: 495
}, {
  element: 'Pt',
  orbital: '4p',
  be: 519
}, {
  element: 'Au',
  orbital: '4p',
  be: 546
}, {
  element: 'Hg',
  orbital: '4p',
  be: 571
}, {
  element: 'Tl',
  orbital: '4p',
  be: 609
}, {
  element: 'Pb',
  orbital: '4p',
  be: 645
}, {
  element: 'Bi',
  orbital: '4p',
  be: 679
}, {
  element: 'Po',
  orbital: '4p',
  be: 705
}, {
  element: 'At',
  orbital: '4p',
  be: 740
}, {
  element: 'Rn',
  orbital: '4p',
  be: 768
}, {
  element: 'Fr',
  orbital: '4p',
  be: 810
}, {
  element: 'Ra',
  orbital: '4p',
  be: 879
}, {
  element: 'Ac',
  orbital: '4p',
  be: 890
}, {
  element: 'Th',
  orbital: '4p',
  be: 968
}, {
  element: 'Y',
  orbital: '4d3/2',
  be: 3
}, {
  element: 'Zr',
  orbital: '4d3/2',
  be: 3
}, {
  element: 'Nb',
  orbital: '4d3/2',
  be: 4
}, {
  element: 'Mo',
  orbital: '4d3/2',
  be: 2
}, {
  element: 'Tc',
  orbital: '4d3/2',
  be: 2
}, {
  element: 'Ru',
  orbital: '4d3/2',
  be: 2
}, {
  element: 'Rh',
  orbital: '4d3/2',
  be: 3
}, {
  element: 'Pd',
  orbital: '4d3/2',
  be: 1
}, {
  element: 'Ag',
  orbital: '4d3/2',
  be: 3
}, {
  element: 'Cd',
  orbital: '4d3/2',
  be: 9
}, {
  element: 'In',
  orbital: '4d3/2',
  be: 16
}, {
  element: 'Sn',
  orbital: '4d3/2',
  be: 24
}, {
  element: 'Sb',
  orbital: '4d3/2',
  be: 32
}, {
  element: 'Te',
  orbital: '4d3/2',
  be: 40
}, {
  element: 'I',
  orbital: '4d3/2',
  be: 50
}, {
  element: 'Xe',
  orbital: '4d3/2',
  be: 63
}, {
  element: 'Cs',
  orbital: '4d3/2',
  be: 79
}, {
  element: 'Ba',
  orbital: '4d3/2',
  be: 93
}, {
  element: 'La',
  orbital: '4d3/2',
  be: 99
}, {
  element: 'Ce',
  orbital: '4d3/2',
  be: 111
}, {
  element: 'Pr',
  orbital: '4d3/2',
  be: 114
}, {
  element: 'Nd',
  orbital: '4d3/2',
  be: 118
}, {
  element: 'Pm',
  orbital: '4d3/2',
  be: 121
}, {
  element: 'Sm',
  orbital: '4d3/2',
  be: 130
}, {
  element: 'Eu',
  orbital: '4d3/2',
  be: 134
}, {
  element: 'Gd',
  orbital: '4d3/2',
  be: 141
}, {
  element: 'Tb',
  orbital: '4d3/2',
  be: 148
}, {
  element: 'Dy',
  orbital: '4d3/2',
  be: 154
}, {
  element: 'Ho',
  orbital: '4d3/2',
  be: 161
}, {
  element: 'Er',
  orbital: '4d3/2',
  be: 177
}, {
  element: 'Tm',
  orbital: '4d3/2',
  be: 180
}, {
  element: 'Yb',
  orbital: '4d3/2',
  be: 197
}, {
  element: 'Lu',
  orbital: '4d3/2',
  be: 205
}, {
  element: 'Hf',
  orbital: '4d3/2',
  be: 224
}, {
  element: 'Ta',
  orbital: '4d3/2',
  be: 242
}, {
  element: 'W',
  orbital: '4d3/2',
  be: 259
}, {
  element: 'Re',
  orbital: '4d3/2',
  be: 274
}, {
  element: 'Os',
  orbital: '4d3/2',
  be: 290
}, {
  element: 'Ir',
  orbital: '4d3/2',
  be: 312
}, {
  element: 'Pt',
  orbital: '4d3/2',
  be: 331
}, {
  element: 'Au',
  orbital: '4d3/2',
  be: 352
}, {
  element: 'Hg',
  orbital: '4d3/2',
  be: 379
}, {
  element: 'Tl',
  orbital: '4d3/2',
  be: 407
}, {
  element: 'Pb',
  orbital: '4d3/2',
  be: 435
}, {
  element: 'Bi',
  orbital: '4d3/2',
  be: 464
}, {
  element: 'Po',
  orbital: '4d3/2',
  be: 500
}, {
  element: 'At',
  orbital: '4d3/2',
  be: 533
}, {
  element: 'Rn',
  orbital: '4d3/2',
  be: 567
}, {
  element: 'Fr',
  orbital: '4d3/2',
  be: 603
}, {
  element: 'Ra',
  orbital: '4d3/2',
  be: 636
}, {
  element: 'Ac',
  orbital: '4d3/2',
  be: 675
}, {
  element: 'Th',
  orbital: '4d3/2',
  be: 714
}, {
  element: 'U',
  orbital: '4d3/2',
  be: 781
}, {
  element: 'Y',
  orbital: '4d5/2',
  be: 3
}, {
  element: 'Zr',
  orbital: '4d5/2',
  be: 3
}, {
  element: 'Nb',
  orbital: '4d5/2',
  be: 4
}, {
  element: 'Mo',
  orbital: '4d5/2',
  be: 2
}, {
  element: 'Tc',
  orbital: '4d5/2',
  be: 2
}, {
  element: 'Ru',
  orbital: '4d5/2',
  be: 2
}, {
  element: 'Rh',
  orbital: '4d5/2',
  be: 3
}, {
  element: 'Pd',
  orbital: '4d5/2',
  be: 1
}, {
  element: 'Ag',
  orbital: '4d5/2',
  be: 3
}, {
  element: 'Cd',
  orbital: '4d5/2',
  be: 9
}, {
  element: 'In',
  orbital: '4d5/2',
  be: 16
}, {
  element: 'Sn',
  orbital: '4d5/2',
  be: 24
}, {
  element: 'Sb',
  orbital: '4d5/2',
  be: 32
}, {
  element: 'Te',
  orbital: '4d5/2',
  be: 40
}, {
  element: 'I',
  orbital: '4d5/2',
  be: 50
}, {
  element: 'Xe',
  orbital: '4d5/2',
  be: 63
}, {
  element: 'Cs',
  orbital: '4d5/2',
  be: 77
}, {
  element: 'Ba',
  orbital: '4d5/2',
  be: 90
}, {
  element: 'La',
  orbital: '4d5/2',
  be: 99
}, {
  element: 'Ce',
  orbital: '4d5/2',
  be: 111
}, {
  element: 'Pr',
  orbital: '4d5/2',
  be: 114
}, {
  element: 'Nd',
  orbital: '4d5/2',
  be: 118
}, {
  element: 'Pm',
  orbital: '4d5/2',
  be: 121
}, {
  element: 'Sm',
  orbital: '4d5/2',
  be: 130
}, {
  element: 'Eu',
  orbital: '4d5/2',
  be: 134
}, {
  element: 'Gd',
  orbital: '4d5/2',
  be: 141
}, {
  element: 'Tb',
  orbital: '4d5/2',
  be: 148
}, {
  element: 'Dy',
  orbital: '4d5/2',
  be: 154
}, {
  element: 'Ho',
  orbital: '4d5/2',
  be: 161
}, {
  element: 'Er',
  orbital: '4d5/2',
  be: 168
}, {
  element: 'Tm',
  orbital: '4d5/2',
  be: 180
}, {
  element: 'Yb',
  orbital: '4d5/2',
  be: 184
}, {
  element: 'Lu',
  orbital: '4d5/2',
  be: 195
}, {
  element: 'Hf',
  orbital: '4d5/2',
  be: 214
}, {
  element: 'Ta',
  orbital: '4d5/2',
  be: 230
}, {
  element: 'W',
  orbital: '4d5/2',
  be: 246
}, {
  element: 'Re',
  orbital: '4d5/2',
  be: 260
}, {
  element: 'Os',
  orbital: '4d5/2',
  be: 273
}, {
  element: 'Ir',
  orbital: '4d5/2',
  be: 295
}, {
  element: 'Pt',
  orbital: '4d5/2',
  be: 314
}, {
  element: 'Au',
  orbital: '4d5/2',
  be: 334
}, {
  element: 'Hg',
  orbital: '4d5/2',
  be: 360
}, {
  element: 'Tl',
  orbital: '4d5/2',
  be: 386
}, {
  element: 'Pb',
  orbital: '4d5/2',
  be: 413
}, {
  element: 'Bi',
  orbital: '4d5/2',
  be: 440
}, {
  element: 'Po',
  orbital: '4d5/2',
  be: 473
}, {
  element: 'At',
  orbital: '4d5/2',
  be: 507
}, {
  element: 'Rn',
  orbital: '4d5/2',
  be: 541
}, {
  element: 'Fr',
  orbital: '4d5/2',
  be: 577
}, {
  element: 'Ra',
  orbital: '4d5/2',
  be: 603
}, {
  element: 'Ac',
  orbital: '4d5/2',
  be: 639
}, {
  element: 'Th',
  orbital: '4d5/2',
  be: 677
}, {
  element: 'U',
  orbital: '4d5/2',
  be: 739
}, {
  element: 'Y',
  orbital: '4d',
  be: 3
}, {
  element: 'Zr',
  orbital: '4d',
  be: 3
}, {
  element: 'Nb',
  orbital: '4d',
  be: 4
}, {
  element: 'Mo',
  orbital: '4d',
  be: 2
}, {
  element: 'Tc',
  orbital: '4d',
  be: 2
}, {
  element: 'Ru',
  orbital: '4d',
  be: 2
}, {
  element: 'Rh',
  orbital: '4d',
  be: 3
}, {
  element: 'Pd',
  orbital: '4d',
  be: 1
}, {
  element: 'Ag',
  orbital: '4d',
  be: 3
}, {
  element: 'Cd',
  orbital: '4d',
  be: 9
}, {
  element: 'In',
  orbital: '4d',
  be: 16
}, {
  element: 'Sn',
  orbital: '4d',
  be: 24
}, {
  element: 'Sb',
  orbital: '4d',
  be: 32
}, {
  element: 'Te',
  orbital: '4d',
  be: 40
}, {
  element: 'I',
  orbital: '4d',
  be: 50
}, {
  element: 'Xe',
  orbital: '4d',
  be: 63
}, {
  element: 'Cs',
  orbital: '4d',
  be: 77
}, {
  element: 'Ba',
  orbital: '4d',
  be: 90
}, {
  element: 'La',
  orbital: '4d',
  be: 99
}, {
  element: 'Ce',
  orbital: '4d',
  be: 111
}, {
  element: 'Pr',
  orbital: '4d',
  be: 114
}, {
  element: 'Nd',
  orbital: '4d',
  be: 118
}, {
  element: 'Pm',
  orbital: '4d',
  be: 121
}, {
  element: 'Sm',
  orbital: '4d',
  be: 130
}, {
  element: 'Eu',
  orbital: '4d',
  be: 134
}, {
  element: 'Gd',
  orbital: '4d',
  be: 141
}, {
  element: 'Tb',
  orbital: '4d',
  be: 148
}, {
  element: 'Dy',
  orbital: '4d',
  be: 154
}, {
  element: 'Ho',
  orbital: '4d',
  be: 161
}, {
  element: 'Er',
  orbital: '4d',
  be: 168
}, {
  element: 'Tm',
  orbital: '4d',
  be: 180
}, {
  element: 'Yb',
  orbital: '4d',
  be: 184
}, {
  element: 'Lu',
  orbital: '4d',
  be: 195
}, {
  element: 'Hf',
  orbital: '4d',
  be: 214
}, {
  element: 'Ta',
  orbital: '4d',
  be: 230
}, {
  element: 'W',
  orbital: '4d',
  be: 246
}, {
  element: 'Re',
  orbital: '4d',
  be: 260
}, {
  element: 'Os',
  orbital: '4d',
  be: 273
}, {
  element: 'Ir',
  orbital: '4d',
  be: 295
}, {
  element: 'Pt',
  orbital: '4d',
  be: 314
}, {
  element: 'Au',
  orbital: '4d',
  be: 334
}, {
  element: 'Hg',
  orbital: '4d',
  be: 360
}, {
  element: 'Tl',
  orbital: '4d',
  be: 386
}, {
  element: 'Pb',
  orbital: '4d',
  be: 413
}, {
  element: 'Bi',
  orbital: '4d',
  be: 440
}, {
  element: 'Po',
  orbital: '4d',
  be: 473
}, {
  element: 'At',
  orbital: '4d',
  be: 507
}, {
  element: 'Rn',
  orbital: '4d',
  be: 541
}, {
  element: 'Fr',
  orbital: '4d',
  be: 577
}, {
  element: 'Ra',
  orbital: '4d',
  be: 603
}, {
  element: 'Ac',
  orbital: '4d',
  be: 639
}, {
  element: 'Th',
  orbital: '4d',
  be: 677
}, {
  element: 'U',
  orbital: '4d',
  be: 739
}, {
  element: 'Ce',
  orbital: '4f5/2',
  be: 1
}, {
  element: 'Pr',
  orbital: '4f5/2',
  be: 2
}, {
  element: 'Nd',
  orbital: '4f5/2',
  be: 2
}, {
  element: 'Pm',
  orbital: '4f5/2',
  be: 4
}, {
  element: 'Sm',
  orbital: '4f5/2',
  be: 7
}, {
  element: 'Tb',
  orbital: '4f5/2',
  be: 3
}, {
  element: 'Dy',
  orbital: '4f5/2',
  be: 4
}, {
  element: 'Ho',
  orbital: '4f5/2',
  be: 4
}, {
  element: 'Er',
  orbital: '4f5/2',
  be: 4
}, {
  element: 'Tm',
  orbital: '4f5/2',
  be: 5
}, {
  element: 'Yb',
  orbital: '4f5/2',
  be: 6
}, {
  element: 'Lu',
  orbital: '4f5/2',
  be: 7
}, {
  element: 'Hf',
  orbital: '4f5/2',
  be: 19
}, {
  element: 'Ta',
  orbital: '4f5/2',
  be: 27
}, {
  element: 'W',
  orbital: '4f5/2',
  be: 37
}, {
  element: 'Re',
  orbital: '4f5/2',
  be: 47
}, {
  element: 'Os',
  orbital: '4f5/2',
  be: 52
}, {
  element: 'Ir',
  orbital: '4f5/2',
  be: 63
}, {
  element: 'Pt',
  orbital: '4f5/2',
  be: 74
}, {
  element: 'Au',
  orbital: '4f5/2',
  be: 87
}, {
  element: 'Hg',
  orbital: '4f5/2',
  be: 103
}, {
  element: 'Tl',
  orbital: '4f5/2',
  be: 122
}, {
  element: 'Pb',
  orbital: '4f5/2',
  be: 143
}, {
  element: 'Bi',
  orbital: '4f5/2',
  be: 163
}, {
  element: 'Po',
  orbital: '4f5/2',
  be: 184
}, {
  element: 'At',
  orbital: '4f5/2',
  be: 210
}, {
  element: 'Rn',
  orbital: '4f5/2',
  be: 238
}, {
  element: 'Fr',
  orbital: '4f5/2',
  be: 268
}, {
  element: 'Ra',
  orbital: '4f5/2',
  be: 299
}, {
  element: 'Ac',
  orbital: '4f5/2',
  be: 319
}, {
  element: 'Th',
  orbital: '4f5/2',
  be: 344
}, {
  element: 'U',
  orbital: '4f5/2',
  be: 391
}, {
  element: 'Ce',
  orbital: '4f7/2',
  be: 1
}, {
  element: 'Pr',
  orbital: '4f7/2',
  be: 2
}, {
  element: 'Nd',
  orbital: '4f7/2',
  be: 2
}, {
  element: 'Pm',
  orbital: '4f7/2',
  be: 4
}, {
  element: 'Sm',
  orbital: '4f7/2',
  be: 7
}, {
  element: 'Tb',
  orbital: '4f7/2',
  be: 3
}, {
  element: 'Dy',
  orbital: '4f7/2',
  be: 4
}, {
  element: 'Ho',
  orbital: '4f7/2',
  be: 4
}, {
  element: 'Er',
  orbital: '4f7/2',
  be: 4
}, {
  element: 'Tm',
  orbital: '4f7/2',
  be: 5
}, {
  element: 'Yb',
  orbital: '4f7/2',
  be: 6
}, {
  element: 'Lu',
  orbital: '4f7/2',
  be: 7
}, {
  element: 'Hf',
  orbital: '4f7/2',
  be: 18
}, {
  element: 'Ta',
  orbital: '4f7/2',
  be: 25
}, {
  element: 'W',
  orbital: '4f7/2',
  be: 34
}, {
  element: 'Re',
  orbital: '4f7/2',
  be: 45
}, {
  element: 'Os',
  orbital: '4f7/2',
  be: 50
}, {
  element: 'Ir',
  orbital: '4f7/2',
  be: 60
}, {
  element: 'Pt',
  orbital: '4f7/2',
  be: 70
}, {
  element: 'Au',
  orbital: '4f7/2',
  be: 83
}, {
  element: 'Hg',
  orbital: '4f7/2',
  be: 99
}, {
  element: 'Tl',
  orbital: '4f7/2',
  be: 118
}, {
  element: 'Pb',
  orbital: '4f7/2',
  be: 138
}, {
  element: 'Bi',
  orbital: '4f7/2',
  be: 158
}, {
  element: 'Po',
  orbital: '4f7/2',
  be: 184
}, {
  element: 'At',
  orbital: '4f7/2',
  be: 210
}, {
  element: 'Rn',
  orbital: '4f7/2',
  be: 238
}, {
  element: 'Fr',
  orbital: '4f7/2',
  be: 268
}, {
  element: 'Ra',
  orbital: '4f7/2',
  be: 299
}, {
  element: 'Ac',
  orbital: '4f7/2',
  be: 319
}, {
  element: 'Th',
  orbital: '4f7/2',
  be: 335
}, {
  element: 'U',
  orbital: '4f7/2',
  be: 380
}, {
  element: 'Ce',
  orbital: '4f',
  be: 1
}, {
  element: 'Pr',
  orbital: '4f',
  be: 2
}, {
  element: 'Nd',
  orbital: '4f',
  be: 2
}, {
  element: 'Pm',
  orbital: '4f',
  be: 4
}, {
  element: 'Sm',
  orbital: '4f',
  be: 7
}, {
  element: 'Tb',
  orbital: '4f',
  be: 3
}, {
  element: 'Dy',
  orbital: '4f',
  be: 4
}, {
  element: 'Ho',
  orbital: '4f',
  be: 4
}, {
  element: 'Er',
  orbital: '4f',
  be: 4
}, {
  element: 'Tm',
  orbital: '4f',
  be: 5
}, {
  element: 'Yb',
  orbital: '4f',
  be: 6
}, {
  element: 'Lu',
  orbital: '4f',
  be: 7
}, {
  element: 'Hf',
  orbital: '4f',
  be: 18
}, {
  element: 'Ta',
  orbital: '4f',
  be: 25
}, {
  element: 'W',
  orbital: '4f',
  be: 34
}, {
  element: 'Re',
  orbital: '4f',
  be: 45
}, {
  element: 'Os',
  orbital: '4f',
  be: 50
}, {
  element: 'Ir',
  orbital: '4f',
  be: 60
}, {
  element: 'Pt',
  orbital: '4f',
  be: 70
}, {
  element: 'Au',
  orbital: '4f',
  be: 83
}, {
  element: 'Hg',
  orbital: '4f',
  be: 99
}, {
  element: 'Tl',
  orbital: '4f',
  be: 118
}, {
  element: 'Pb',
  orbital: '4f',
  be: 138
}, {
  element: 'Bi',
  orbital: '4f',
  be: 158
}, {
  element: 'Po',
  orbital: '4f',
  be: 184
}, {
  element: 'At',
  orbital: '4f',
  be: 210
}, {
  element: 'Rn',
  orbital: '4f',
  be: 238
}, {
  element: 'Fr',
  orbital: '4f',
  be: 268
}, {
  element: 'Ra',
  orbital: '4f',
  be: 299
}, {
  element: 'Ac',
  orbital: '4f',
  be: 319
}, {
  element: 'Th',
  orbital: '4f',
  be: 335
}, {
  element: 'U',
  orbital: '4f',
  be: 380
}, {
  element: 'Sn',
  orbital: '5s',
  be: 1
}, {
  element: 'Sb',
  orbital: '5s',
  be: 7
}, {
  element: 'Te',
  orbital: '5s',
  be: 12
}, {
  element: 'I',
  orbital: '5s',
  be: 14
}, {
  element: 'Xe',
  orbital: '5s',
  be: 18
}, {
  element: 'Cs',
  orbital: '5s',
  be: 23
}, {
  element: 'Ba',
  orbital: '5s',
  be: 40
}, {
  element: 'La',
  orbital: '5s',
  be: 33
}, {
  element: 'Ce',
  orbital: '5s',
  be: 38
}, {
  element: 'Pr',
  orbital: '5s',
  be: 38
}, {
  element: 'Nd',
  orbital: '5s',
  be: 38
}, {
  element: 'Pm',
  orbital: '5s',
  be: 38
}, {
  element: 'Sm',
  orbital: '5s',
  be: 39
}, {
  element: 'Eu',
  orbital: '5s',
  be: 32
}, {
  element: 'Gd',
  orbital: '5s',
  be: 36
}, {
  element: 'Tb',
  orbital: '5s',
  be: 40
}, {
  element: 'Dy',
  orbital: '5s',
  be: 63
}, {
  element: 'Ho',
  orbital: '5s',
  be: 51
}, {
  element: 'Er',
  orbital: '5s',
  be: 60
}, {
  element: 'Tm',
  orbital: '5s',
  be: 53
}, {
  element: 'Yb',
  orbital: '5s',
  be: 53
}, {
  element: 'Lu',
  orbital: '5s',
  be: 57
}, {
  element: 'Hf',
  orbital: '5s',
  be: 65
}, {
  element: 'Ta',
  orbital: '5s',
  be: 71
}, {
  element: 'W',
  orbital: '5s',
  be: 77
}, {
  element: 'Re',
  orbital: '5s',
  be: 83
}, {
  element: 'Os',
  orbital: '5s',
  be: 84
}, {
  element: 'Ir',
  orbital: '5s',
  be: 96
}, {
  element: 'Pt',
  orbital: '5s',
  be: 102
}, {
  element: 'Au',
  orbital: '5s',
  be: 108
}, {
  element: 'Hg',
  orbital: '5s',
  be: 120
}, {
  element: 'Tl',
  orbital: '5s',
  be: 137
}, {
  element: 'Pb',
  orbital: '5s',
  be: 148
}, {
  element: 'Bi',
  orbital: '5s',
  be: 160
}, {
  element: 'Po',
  orbital: '5s',
  be: 177
}, {
  element: 'At',
  orbital: '5s',
  be: 195
}, {
  element: 'Rn',
  orbital: '5s',
  be: 214
}, {
  element: 'Fr',
  orbital: '5s',
  be: 234
}, {
  element: 'Ra',
  orbital: '5s',
  be: 254
}, {
  element: 'Ac',
  orbital: '5s',
  be: 272
}, {
  element: 'Th',
  orbital: '5s',
  be: 290
}, {
  element: 'In',
  orbital: '5p1/2',
  be: 1
}, {
  element: 'Sn',
  orbital: '5p1/2',
  be: 1
}, {
  element: 'Sb',
  orbital: '5p1/2',
  be: 2
}, {
  element: 'Te',
  orbital: '5p1/2',
  be: 2
}, {
  element: 'I',
  orbital: '5p1/2',
  be: 3
}, {
  element: 'Xe',
  orbital: '5p1/2',
  be: 7
}, {
  element: 'Cs',
  orbital: '5p1/2',
  be: 13
}, {
  element: 'Ba',
  orbital: '5p1/2',
  be: 17
}, {
  element: 'La',
  orbital: '5p1/2',
  be: 15
}, {
  element: 'Ce',
  orbital: '5p1/2',
  be: 20
}, {
  element: 'Pr',
  orbital: '5p1/2',
  be: 23
}, {
  element: 'Nd',
  orbital: '5p1/2',
  be: 22
}, {
  element: 'Pm',
  orbital: '5p1/2',
  be: 22
}, {
  element: 'Sm',
  orbital: '5p1/2',
  be: 22
}, {
  element: 'Eu',
  orbital: '5p1/2',
  be: 22
}, {
  element: 'Gd',
  orbital: '5p1/2',
  be: 21
}, {
  element: 'Tb',
  orbital: '5p1/2',
  be: 26
}, {
  element: 'Dy',
  orbital: '5p1/2',
  be: 26
}, {
  element: 'Ho',
  orbital: '5p1/2',
  be: 20
}, {
  element: 'Er',
  orbital: '5p1/2',
  be: 29
}, {
  element: 'Tm',
  orbital: '5p1/2',
  be: 32
}, {
  element: 'Yb',
  orbital: '5p1/2',
  be: 23
}, {
  element: 'Lu',
  orbital: '5p1/2',
  be: 28
}, {
  element: 'Hf',
  orbital: '5p1/2',
  be: 38
}, {
  element: 'Ta',
  orbital: '5p1/2',
  be: 45
}, {
  element: 'W',
  orbital: '5p1/2',
  be: 47
}, {
  element: 'Re',
  orbital: '5p1/2',
  be: 46
}, {
  element: 'Os',
  orbital: '5p1/2',
  be: 58
}, {
  element: 'Ir',
  orbital: '5p1/2',
  be: 63
}, {
  element: 'Pt',
  orbital: '5p1/2',
  be: 66
}, {
  element: 'Au',
  orbital: '5p1/2',
  be: 72
}, {
  element: 'Hg',
  orbital: '5p1/2',
  be: 81
}, {
  element: 'Tl',
  orbital: '5p1/2',
  be: 100
}, {
  element: 'Pb',
  orbital: '5p1/2',
  be: 105
}, {
  element: 'Bi',
  orbital: '5p1/2',
  be: 117
}, {
  element: 'Po',
  orbital: '5p1/2',
  be: 132
}, {
  element: 'At',
  orbital: '5p1/2',
  be: 148
}, {
  element: 'Rn',
  orbital: '5p1/2',
  be: 164
}, {
  element: 'Fr',
  orbital: '5p1/2',
  be: 182
}, {
  element: 'Ra',
  orbital: '5p1/2',
  be: 200
}, {
  element: 'Ac',
  orbital: '5p1/2',
  be: 215
}, {
  element: 'Th',
  orbital: '5p1/2',
  be: 229
}, {
  element: 'In',
  orbital: '5p3/2',
  be: 1
}, {
  element: 'Sn',
  orbital: '5p3/2',
  be: 1
}, {
  element: 'Sb',
  orbital: '5p3/2',
  be: 2
}, {
  element: 'Te',
  orbital: '5p3/2',
  be: 2
}, {
  element: 'I',
  orbital: '5p3/2',
  be: 3
}, {
  element: 'Xe',
  orbital: '5p3/2',
  be: 7
}, {
  element: 'Cs',
  orbital: '5p3/2',
  be: 12
}, {
  element: 'Ba',
  orbital: '5p3/2',
  be: 15
}, {
  element: 'La',
  orbital: '5p3/2',
  be: 15
}, {
  element: 'Ce',
  orbital: '5p3/2',
  be: 20
}, {
  element: 'Pr',
  orbital: '5p3/2',
  be: 23
}, {
  element: 'Nd',
  orbital: '5p3/2',
  be: 22
}, {
  element: 'Pm',
  orbital: '5p3/2',
  be: 22
}, {
  element: 'Sm',
  orbital: '5p3/2',
  be: 22
}, {
  element: 'Eu',
  orbital: '5p3/2',
  be: 22
}, {
  element: 'Gd',
  orbital: '5p3/2',
  be: 21
}, {
  element: 'Tb',
  orbital: '5p3/2',
  be: 26
}, {
  element: 'Dy',
  orbital: '5p3/2',
  be: 26
}, {
  element: 'Ho',
  orbital: '5p3/2',
  be: 20
}, {
  element: 'Er',
  orbital: '5p3/2',
  be: 29
}, {
  element: 'Tm',
  orbital: '5p3/2',
  be: 32
}, {
  element: 'Yb',
  orbital: '5p3/2',
  be: 23
}, {
  element: 'Lu',
  orbital: '5p3/2',
  be: 28
}, {
  element: 'Hf',
  orbital: '5p3/2',
  be: 31
}, {
  element: 'Ta',
  orbital: '5p3/2',
  be: 37
}, {
  element: 'W',
  orbital: '5p3/2',
  be: 37
}, {
  element: 'Re',
  orbital: '5p3/2',
  be: 35
}, {
  element: 'Os',
  orbital: '5p3/2',
  be: 46
}, {
  element: 'Ir',
  orbital: '5p3/2',
  be: 51
}, {
  element: 'Pt',
  orbital: '5p3/2',
  be: 51
}, {
  element: 'Au',
  orbital: '5p3/2',
  be: 54
}, {
  element: 'Hg',
  orbital: '5p3/2',
  be: 58
}, {
  element: 'Tl',
  orbital: '5p3/2',
  be: 76
}, {
  element: 'Pb',
  orbital: '5p3/2',
  be: 86
}, {
  element: 'Bi',
  orbital: '5p3/2',
  be: 93
}, {
  element: 'Po',
  orbital: '5p3/2',
  be: 104
}, {
  element: 'At',
  orbital: '5p3/2',
  be: 115
}, {
  element: 'Rn',
  orbital: '5p3/2',
  be: 127
}, {
  element: 'Fr',
  orbital: '5p3/2',
  be: 140
}, {
  element: 'Ra',
  orbital: '5p3/2',
  be: 153
}, {
  element: 'Ac',
  orbital: '5p3/2',
  be: 167
}, {
  element: 'Th',
  orbital: '5p3/2',
  be: 182
}, {
  element: 'In',
  orbital: '5p',
  be: 1
}, {
  element: 'Sn',
  orbital: '5p',
  be: 1
}, {
  element: 'Sb',
  orbital: '5p',
  be: 2
}, {
  element: 'Te',
  orbital: '5p',
  be: 2
}, {
  element: 'I',
  orbital: '5p',
  be: 3
}, {
  element: 'Xe',
  orbital: '5p',
  be: 7
}, {
  element: 'Cs',
  orbital: '5p',
  be: 12
}, {
  element: 'Ba',
  orbital: '5p',
  be: 15
}, {
  element: 'La',
  orbital: '5p',
  be: 15
}, {
  element: 'Ce',
  orbital: '5p',
  be: 20
}, {
  element: 'Pr',
  orbital: '5p',
  be: 23
}, {
  element: 'Nd',
  orbital: '5p',
  be: 22
}, {
  element: 'Pm',
  orbital: '5p',
  be: 22
}, {
  element: 'Sm',
  orbital: '5p',
  be: 22
}, {
  element: 'Eu',
  orbital: '5p',
  be: 22
}, {
  element: 'Gd',
  orbital: '5p',
  be: 21
}, {
  element: 'Tb',
  orbital: '5p',
  be: 26
}, {
  element: 'Dy',
  orbital: '5p',
  be: 26
}, {
  element: 'Ho',
  orbital: '5p',
  be: 20
}, {
  element: 'Er',
  orbital: '5p',
  be: 29
}, {
  element: 'Tm',
  orbital: '5p',
  be: 32
}, {
  element: 'Yb',
  orbital: '5p',
  be: 23
}, {
  element: 'Lu',
  orbital: '5p',
  be: 28
}, {
  element: 'Hf',
  orbital: '5p',
  be: 31
}, {
  element: 'Ta',
  orbital: '5p',
  be: 37
}, {
  element: 'W',
  orbital: '5p',
  be: 37
}, {
  element: 'Re',
  orbital: '5p',
  be: 35
}, {
  element: 'Os',
  orbital: '5p',
  be: 46
}, {
  element: 'Ir',
  orbital: '5p',
  be: 51
}, {
  element: 'Pt',
  orbital: '5p',
  be: 51
}, {
  element: 'Au',
  orbital: '5p',
  be: 54
}, {
  element: 'Hg',
  orbital: '5p',
  be: 58
}, {
  element: 'Tl',
  orbital: '5p',
  be: 76
}, {
  element: 'Pb',
  orbital: '5p',
  be: 86
}, {
  element: 'Bi',
  orbital: '5p',
  be: 93
}, {
  element: 'Po',
  orbital: '5p',
  be: 104
}, {
  element: 'At',
  orbital: '5p',
  be: 115
}, {
  element: 'Rn',
  orbital: '5p',
  be: 127
}, {
  element: 'Fr',
  orbital: '5p',
  be: 140
}, {
  element: 'Ra',
  orbital: '5p',
  be: 153
}, {
  element: 'Ac',
  orbital: '5p',
  be: 167
}, {
  element: 'Th',
  orbital: '5p',
  be: 182
}, {
  element: 'Lu',
  orbital: '5d3/2',
  be: 5
}, {
  element: 'Hf',
  orbital: '5d3/2',
  be: 7
}, {
  element: 'Ta',
  orbital: '5d3/2',
  be: 6
}, {
  element: 'W',
  orbital: '5d3/2',
  be: 6
}, {
  element: 'Re',
  orbital: '5d3/2',
  be: 4
}, {
  element: 'Ir',
  orbital: '5d3/2',
  be: 4
}, {
  element: 'Pt',
  orbital: '5d3/2',
  be: 2
}, {
  element: 'Au',
  orbital: '5d3/2',
  be: 3
}, {
  element: 'Hg',
  orbital: '5d3/2',
  be: 7
}, {
  element: 'Tl',
  orbital: '5d3/2',
  be: 16
}, {
  element: 'Pb',
  orbital: '5d3/2',
  be: 22
}, {
  element: 'Bi',
  orbital: '5d3/2',
  be: 27
}, {
  element: 'Po',
  orbital: '5d3/2',
  be: 31
}, {
  element: 'At',
  orbital: '5d3/2',
  be: 40
}, {
  element: 'Rn',
  orbital: '5d3/2',
  be: 48
}, {
  element: 'Fr',
  orbital: '5d3/2',
  be: 58
}, {
  element: 'Ra',
  orbital: '5d3/2',
  be: 68
}, {
  element: 'Ac',
  orbital: '5d3/2',
  be: 80
}, {
  element: 'Th',
  orbital: '5d3/2',
  be: 95
}, {
  element: 'U',
  orbital: '5d3/2',
  be: 104
}, {
  element: 'Lu',
  orbital: '5d5/2',
  be: 5
}, {
  element: 'Hf',
  orbital: '5d5/2',
  be: 7
}, {
  element: 'Ta',
  orbital: '5d5/2',
  be: 6
}, {
  element: 'W',
  orbital: '5d5/2',
  be: 6
}, {
  element: 'Re',
  orbital: '5d5/2',
  be: 4
}, {
  element: 'Ir',
  orbital: '5d5/2',
  be: 4
}, {
  element: 'Pt',
  orbital: '5d5/2',
  be: 2
}, {
  element: 'Au',
  orbital: '5d5/2',
  be: 3
}, {
  element: 'Hg',
  orbital: '5d5/2',
  be: 7
}, {
  element: 'Tl',
  orbital: '5d5/2',
  be: 13
}, {
  element: 'Pb',
  orbital: '5d5/2',
  be: 20
}, {
  element: 'Bi',
  orbital: '5d5/2',
  be: 25
}, {
  element: 'Po',
  orbital: '5d5/2',
  be: 31
}, {
  element: 'At',
  orbital: '5d5/2',
  be: 40
}, {
  element: 'Rn',
  orbital: '5d5/2',
  be: 48
}, {
  element: 'Fr',
  orbital: '5d5/2',
  be: 58
}, {
  element: 'Ra',
  orbital: '5d5/2',
  be: 68
}, {
  element: 'Ac',
  orbital: '5d5/2',
  be: 80
}, {
  element: 'Th',
  orbital: '5d5/2',
  be: 88
}, {
  element: 'U',
  orbital: '5d5/2',
  be: 96
}, {
  element: 'Lu',
  orbital: '5d',
  be: 5
}, {
  element: 'Hf',
  orbital: '5d',
  be: 7
}, {
  element: 'Ta',
  orbital: '5d',
  be: 6
}, {
  element: 'W',
  orbital: '5d',
  be: 6
}, {
  element: 'Re',
  orbital: '5d',
  be: 4
}, {
  element: 'Ir',
  orbital: '5d',
  be: 4
}, {
  element: 'Pt',
  orbital: '5d',
  be: 2
}, {
  element: 'Au',
  orbital: '5d',
  be: 3
}, {
  element: 'Hg',
  orbital: '5d',
  be: 7
}, {
  element: 'Tl',
  orbital: '5d',
  be: 13
}, {
  element: 'Pb',
  orbital: '5d',
  be: 20
}, {
  element: 'Bi',
  orbital: '5d',
  be: 25
}, {
  element: 'Po',
  orbital: '5d',
  be: 31
}, {
  element: 'At',
  orbital: '5d',
  be: 40
}, {
  element: 'Rn',
  orbital: '5d',
  be: 48
}, {
  element: 'Fr',
  orbital: '5d',
  be: 58
}, {
  element: 'Ra',
  orbital: '5d',
  be: 68
}, {
  element: 'Ac',
  orbital: '5d',
  be: 80
}, {
  element: 'Th',
  orbital: '5d',
  be: 88
}, {
  element: 'U',
  orbital: '5d',
  be: 96
}, {
  element: 'Pb',
  orbital: '6s',
  be: 3
}, {
  element: 'Bi',
  orbital: '6s',
  be: 8
}, {
  element: 'Po',
  orbital: '6s',
  be: 12
}, {
  element: 'At',
  orbital: '6s',
  be: 18
}, {
  element: 'Rn',
  orbital: '6s',
  be: 26
}, {
  element: 'Fr',
  orbital: '6s',
  be: 34
}, {
  element: 'Ra',
  orbital: '6s',
  be: 44
}, {
  element: 'Th',
  orbital: '6s',
  be: 60
}, {
  element: 'Pb',
  orbital: '6p1/2',
  be: 1
}, {
  element: 'Bi',
  orbital: '6p1/2',
  be: 3
}, {
  element: 'Po',
  orbital: '6p1/2',
  be: 5
}, {
  element: 'At',
  orbital: '6p1/2',
  be: 8
}, {
  element: 'Rn',
  orbital: '6p1/2',
  be: 11
}, {
  element: 'Fr',
  orbital: '6p1/2',
  be: 15
}, {
  element: 'Ra',
  orbital: '6p1/2',
  be: 19
}, {
  element: 'Th',
  orbital: '6p1/2',
  be: 49
}, {
  element: 'U',
  orbital: '6p1/2',
  be: 28.9
}, {
  element: 'Pb',
  orbital: '6p3/2',
  be: 1
}, {
  element: 'Bi',
  orbital: '6p3/2',
  be: 3
}, {
  element: 'Po',
  orbital: '6p3/2',
  be: 5
}, {
  element: 'At',
  orbital: '6p3/2',
  be: 8
}, {
  element: 'Rn',
  orbital: '6p3/2',
  be: 11
}, {
  element: 'Fr',
  orbital: '6p3/2',
  be: 15
}, {
  element: 'Ra',
  orbital: '6p3/2',
  be: 19
}, {
  element: 'Th',
  orbital: '6p3/2',
  be: 43
}, {
  element: 'U',
  orbital: '6p3/2',
  be: 19
}, {
  element: 'Pb',
  orbital: '6p',
  be: 1
}, {
  element: 'Bi',
  orbital: '6p',
  be: 3
}, {
  element: 'Po',
  orbital: '6p',
  be: 5
}, {
  element: 'At',
  orbital: '6p',
  be: 8
}, {
  element: 'Rn',
  orbital: '6p',
  be: 11
}, {
  element: 'Fr',
  orbital: '6p',
  be: 15
}, {
  element: 'Ra',
  orbital: '6p',
  be: 19
}, {
  element: 'Th',
  orbital: '6p',
  be: 43
}, {
  element: 'U',
  orbital: '6p',
  be: 19
}, {
  element: 'H',
  orbital: '1s',
  be: 14
}, {
  element: 'He',
  orbital: '1s',
  be: 25
}, {
  element: 'Li',
  orbital: '1s',
  be: 55
}, {
  element: 'Be',
  orbital: '1s',
  be: 111
}, {
  element: 'B',
  orbital: '1s',
  be: 188
}, {
  element: 'C',
  orbital: '1s',
  be: 284
}, {
  element: 'N',
  orbital: '1s',
  be: 399
}, {
  element: 'O',
  orbital: '1s',
  be: 532
}, {
  element: 'F',
  orbital: '1s',
  be: 686
}, {
  element: 'Ne',
  orbital: '1s',
  be: 867
}, {
  element: 'Na',
  orbital: '1s',
  be: 1072
}, {
  element: 'O',
  orbital: '2s',
  be: 24
}, {
  element: 'F',
  orbital: '2s',
  be: 31
}, {
  element: 'Ne',
  orbital: '2s',
  be: 45
}, {
  element: 'Na',
  orbital: '2s',
  be: 63
}, {
  element: 'Mg',
  orbital: '2s',
  be: 89
}, {
  element: 'Al',
  orbital: '2s',
  be: 118
}, {
  element: 'Si',
  orbital: '2s',
  be: 149
}, {
  element: 'P',
  orbital: '2s',
  be: 189
}, {
  element: 'S',
  orbital: '2s',
  be: 229
}, {
  element: 'Cl',
  orbital: '2s',
  be: 270
}, {
  element: 'Ar',
  orbital: '2s',
  be: 320
}, {
  element: 'K',
  orbital: '2s',
  be: 377
}, {
  element: 'Ca',
  orbital: '2s',
  be: 438
}, {
  element: 'Sc',
  orbital: '2s',
  be: 500
}, {
  element: 'Ti',
  orbital: '2s',
  be: 564
}, {
  element: 'V',
  orbital: '2s',
  be: 628
}, {
  element: 'Cr',
  orbital: '2s',
  be: 695
}, {
  element: 'Mn',
  orbital: '2s',
  be: 769
}, {
  element: 'Fe',
  orbital: '2s',
  be: 846
}, {
  element: 'Co',
  orbital: '2s',
  be: 926
}, {
  element: 'Ni',
  orbital: '2s',
  be: 1008
}, {
  element: 'Cu',
  orbital: '2s',
  be: 1096
}, {
  element: 'Zn',
  orbital: '2s',
  be: 1194
}, {
  element: 'B',
  orbital: '2p1/2',
  be: 5
}, {
  element: 'C',
  orbital: '2p1/2',
  be: 7
}, {
  element: 'N',
  orbital: '2p1/2',
  be: 9
}, {
  element: 'O',
  orbital: '2p1/2',
  be: 7
}, {
  element: 'F',
  orbital: '2p1/2',
  be: 9
}, {
  element: 'Ne',
  orbital: '2p1/2',
  be: 18
}, {
  element: 'Na',
  orbital: '2p1/2',
  be: 31
}, {
  element: 'Mg',
  orbital: '2p1/2',
  be: 52
}, {
  element: 'Al',
  orbital: '2p1/2',
  be: 74
}, {
  element: 'Si',
  orbital: '2p1/2',
  be: 100
}, {
  element: 'P',
  orbital: '2p1/2',
  be: 136
}, {
  element: 'S',
  orbital: '2p1/2',
  be: 165
}, {
  element: 'Cl',
  orbital: '2p1/2',
  be: 202
}, {
  element: 'Ar',
  orbital: '2p1/2',
  be: 247
}, {
  element: 'K',
  orbital: '2p1/2',
  be: 297
}, {
  element: 'Ca',
  orbital: '2p1/2',
  be: 350
}, {
  element: 'Sc',
  orbital: '2p1/2',
  be: 407
}, {
  element: 'Ti',
  orbital: '2p1/2',
  be: 461
}, {
  element: 'V',
  orbital: '2p1/2',
  be: 520
}, {
  element: 'Cr',
  orbital: '2p1/2',
  be: 584
}, {
  element: 'Mn',
  orbital: '2p1/2',
  be: 652
}, {
  element: 'Fe',
  orbital: '2p1/2',
  be: 723
}, {
  element: 'Co',
  orbital: '2p1/2',
  be: 794
}, {
  element: 'Ni',
  orbital: '2p1/2',
  be: 872
}, {
  element: 'Cu',
  orbital: '2p1/2',
  be: 951
}, {
  element: 'Zn',
  orbital: '2p1/2',
  be: 1044
}, {
  element: 'Ga',
  orbital: '2p1/2',
  be: 1143
}, {
  element: 'B',
  orbital: '2p3/2',
  be: 5
}, {
  element: 'C',
  orbital: '2p3/2',
  be: 7
}, {
  element: 'N',
  orbital: '2p3/2',
  be: 9
}, {
  element: 'O',
  orbital: '2p3/2',
  be: 7
}, {
  element: 'F',
  orbital: '2p3/2',
  be: 9
}, {
  element: 'Ne',
  orbital: '2p3/2',
  be: 18
}, {
  element: 'Na',
  orbital: '2p3/2',
  be: 31
}, {
  element: 'Mg',
  orbital: '2p3/2',
  be: 52
}, {
  element: 'Al',
  orbital: '2p3/2',
  be: 73
}, {
  element: 'Si',
  orbital: '2p3/2',
  be: 99
}, {
  element: 'P',
  orbital: '2p3/2',
  be: 135
}, {
  element: 'S',
  orbital: '2p3/2',
  be: 164
}, {
  element: 'Cl',
  orbital: '2p3/2',
  be: 200
}, {
  element: 'Ar',
  orbital: '2p3/2',
  be: 245
}, {
  element: 'K',
  orbital: '2p3/2',
  be: 294
}, {
  element: 'Ca',
  orbital: '2p3/2',
  be: 347
}, {
  element: 'Sc',
  orbital: '2p3/2',
  be: 402
}, {
  element: 'Ti',
  orbital: '2p3/2',
  be: 455
}, {
  element: 'V',
  orbital: '2p3/2',
  be: 513
}, {
  element: 'Cr',
  orbital: '2p3/2',
  be: 575
}, {
  element: 'Mn',
  orbital: '2p3/2',
  be: 641
}, {
  element: 'Fe',
  orbital: '2p3/2',
  be: 710
}, {
  element: 'Co',
  orbital: '2p3/2',
  be: 779
}, {
  element: 'Ni',
  orbital: '2p3/2',
  be: 855
}, {
  element: 'Cu',
  orbital: '2p3/2',
  be: 931
}, {
  element: 'Zn',
  orbital: '2p3/2',
  be: 1021
}, {
  element: 'Ga',
  orbital: '2p3/2',
  be: 1116
}, {
  element: 'Ge',
  orbital: '2p3/2',
  be: 1217
}, {
  element: 'B',
  orbital: '2p',
  be: 5
}, {
  element: 'C',
  orbital: '2p',
  be: 7
}, {
  element: 'N',
  orbital: '2p',
  be: 9
}, {
  element: 'O',
  orbital: '2p',
  be: 7
}, {
  element: 'F',
  orbital: '2p',
  be: 9
}, {
  element: 'Ne',
  orbital: '2p',
  be: 18
}, {
  element: 'Na',
  orbital: '2p',
  be: 31
}, {
  element: 'Mg',
  orbital: '2p',
  be: 52
}, {
  element: 'Al',
  orbital: '2p',
  be: 73
}, {
  element: 'Si',
  orbital: '2p',
  be: 99
}, {
  element: 'P',
  orbital: '2p',
  be: 135
}, {
  element: 'S',
  orbital: '2p',
  be: 164
}, {
  element: 'Cl',
  orbital: '2p',
  be: 200
}, {
  element: 'Ar',
  orbital: '2p',
  be: 245
}, {
  element: 'K',
  orbital: '2p',
  be: 294
}, {
  element: 'Ca',
  orbital: '2p',
  be: 347
}, {
  element: 'Sc',
  orbital: '2p',
  be: 402
}, {
  element: 'Ti',
  orbital: '2p',
  be: 455
}, {
  element: 'V',
  orbital: '2p',
  be: 513
}, {
  element: 'Cr',
  orbital: '2p',
  be: 575
}, {
  element: 'Mn',
  orbital: '2p',
  be: 641
}, {
  element: 'Fe',
  orbital: '2p',
  be: 710
}, {
  element: 'Co',
  orbital: '2p',
  be: 779
}, {
  element: 'Ni',
  orbital: '2p',
  be: 855
}, {
  element: 'Cu',
  orbital: '2p',
  be: 931
}, {
  element: 'Zn',
  orbital: '2p',
  be: 1021
}, {
  element: 'Ga',
  orbital: '2p',
  be: 1116
}, {
  element: 'Ge',
  orbital: '2p',
  be: 1217
}, {
  element: 'Na',
  orbital: '3s',
  be: 1
}, {
  element: 'Mg',
  orbital: '3s',
  be: 2
}, {
  element: 'Al',
  orbital: '3s',
  be: 1
}, {
  element: 'Si',
  orbital: '3s',
  be: 8
}, {
  element: 'P',
  orbital: '3s',
  be: 16
}, {
  element: 'S',
  orbital: '3s',
  be: 16
}, {
  element: 'Cl',
  orbital: '3s',
  be: 18
}, {
  element: 'Ar',
  orbital: '3s',
  be: 25
}, {
  element: 'K',
  orbital: '3s',
  be: 34
}, {
  element: 'Ca',
  orbital: '3s',
  be: 44
}, {
  element: 'Sc',
  orbital: '3s',
  be: 54
}, {
  element: 'Ti',
  orbital: '3s',
  be: 59
}, {
  element: 'V',
  orbital: '3s',
  be: 66
}, {
  element: 'Cr',
  orbital: '3s',
  be: 74
}, {
  element: 'Mn',
  orbital: '3s',
  be: 84
}, {
  element: 'Fe',
  orbital: '3s',
  be: 95
}, {
  element: 'Co',
  orbital: '3s',
  be: 101
}, {
  element: 'Ni',
  orbital: '3s',
  be: 112
}, {
  element: 'Cu',
  orbital: '3s',
  be: 120
}, {
  element: 'Zn',
  orbital: '3s',
  be: 137
}, {
  element: 'Ga',
  orbital: '3s',
  be: 158
}, {
  element: 'Ge',
  orbital: '3s',
  be: 181
}, {
  element: 'As',
  orbital: '3s',
  be: 204
}, {
  element: 'Se',
  orbital: '3s',
  be: 232
}, {
  element: 'Br',
  orbital: '3s',
  be: 257
}, {
  element: 'Kr',
  orbital: '3s',
  be: 289
}, {
  element: 'Rb',
  orbital: '3s',
  be: 322
}, {
  element: 'Sr',
  orbital: '3s',
  be: 358
}, {
  element: 'Y',
  orbital: '3s',
  be: 395
}, {
  element: 'Zr',
  orbital: '3s',
  be: 431
}, {
  element: 'Nb',
  orbital: '3s',
  be: 469
}, {
  element: 'Mo',
  orbital: '3s',
  be: 505
}, {
  element: 'Tc',
  orbital: '3s',
  be: 544
}, {
  element: 'Ru',
  orbital: '3s',
  be: 585
}, {
  element: 'Rh',
  orbital: '3s',
  be: 627
}, {
  element: 'Pd',
  orbital: '3s',
  be: 670
}, {
  element: 'Ag',
  orbital: '3s',
  be: 717
}, {
  element: 'Cd',
  orbital: '3s',
  be: 770
}, {
  element: 'In',
  orbital: '3s',
  be: 826
}, {
  element: 'Sn',
  orbital: '3s',
  be: 884
}, {
  element: 'Sb',
  orbital: '3s',
  be: 944
}, {
  element: 'Te',
  orbital: '3s',
  be: 1006
}, {
  element: 'I',
  orbital: '3s',
  be: 1072
}, {
  element: 'Xe',
  orbital: '3s',
  be: 1145
}, {
  element: 'Cs',
  orbital: '3s',
  be: 1217
}, {
  element: 'Si',
  orbital: '3p1/2',
  be: 3
}, {
  element: 'P',
  orbital: '3p1/2',
  be: 10
}, {
  element: 'S',
  orbital: '3p1/2',
  be: 8
}, {
  element: 'Cl',
  orbital: '3p1/2',
  be: 7
}, {
  element: 'Ar',
  orbital: '3p1/2',
  be: 12
}, {
  element: 'K',
  orbital: '3p1/2',
  be: 18
}, {
  element: 'Ca',
  orbital: '3p1/2',
  be: 26
}, {
  element: 'Sc',
  orbital: '3p1/2',
  be: 32
}, {
  element: 'Ti',
  orbital: '3p1/2',
  be: 34
}, {
  element: 'V',
  orbital: '3p1/2',
  be: 38
}, {
  element: 'Cr',
  orbital: '3p1/2',
  be: 43
}, {
  element: 'Mn',
  orbital: '3p1/2',
  be: 49
}, {
  element: 'Fe',
  orbital: '3p1/2',
  be: 56
}, {
  element: 'Co',
  orbital: '3p1/2',
  be: 60
}, {
  element: 'Ni',
  orbital: '3p1/2',
  be: 68
}, {
  element: 'Cu',
  orbital: '3p1/2',
  be: 74
}, {
  element: 'Zn',
  orbital: '3p1/2',
  be: 87
}, {
  element: 'Ga',
  orbital: '3p1/2',
  be: 107
}, {
  element: 'Ge',
  orbital: '3p1/2',
  be: 129
}, {
  element: 'As',
  orbital: '3p1/2',
  be: 147
}, {
  element: 'Se',
  orbital: '3p1/2',
  be: 168
}, {
  element: 'Br',
  orbital: '3p1/2',
  be: 189
}, {
  element: 'Kr',
  orbital: '3p1/2',
  be: 223
}, {
  element: 'Rb',
  orbital: '3p1/2',
  be: 248
}, {
  element: 'Sr',
  orbital: '3p1/2',
  be: 280
}, {
  element: 'Y',
  orbital: '3p1/2',
  be: 313
}, {
  element: 'Zr',
  orbital: '3p1/2',
  be: 345
}, {
  element: 'Nb',
  orbital: '3p1/2',
  be: 379
}, {
  element: 'Mo',
  orbital: '3p1/2',
  be: 410
}, {
  element: 'Tc',
  orbital: '3p1/2',
  be: 445
}, {
  element: 'Ru',
  orbital: '3p1/2',
  be: 483
}, {
  element: 'Rh',
  orbital: '3p1/2',
  be: 521
}, {
  element: 'Pd',
  orbital: '3p1/2',
  be: 559
}, {
  element: 'Ag',
  orbital: '3p1/2',
  be: 602
}, {
  element: 'Cd',
  orbital: '3p1/2',
  be: 651
}, {
  element: 'In',
  orbital: '3p1/2',
  be: 702
}, {
  element: 'Sn',
  orbital: '3p1/2',
  be: 757
}, {
  element: 'Sb',
  orbital: '3p1/2',
  be: 812
}, {
  element: 'Te',
  orbital: '3p1/2',
  be: 870
}, {
  element: 'I',
  orbital: '3p1/2',
  be: 931
}, {
  element: 'Xe',
  orbital: '3p1/2',
  be: 999
}, {
  element: 'Cs',
  orbital: '3p1/2',
  be: 1065
}, {
  element: 'Ba',
  orbital: '3p1/2',
  be: 1137
}, {
  element: 'La',
  orbital: '3p1/2',
  be: 1205
}, {
  element: 'Si',
  orbital: '3p3/2',
  be: 3
}, {
  element: 'P',
  orbital: '3p3/2',
  be: 10
}, {
  element: 'S',
  orbital: '3p3/2',
  be: 8
}, {
  element: 'Cl',
  orbital: '3p3/2',
  be: 7
}, {
  element: 'Ar',
  orbital: '3p3/2',
  be: 12
}, {
  element: 'K',
  orbital: '3p3/2',
  be: 18
}, {
  element: 'Ca',
  orbital: '3p3/2',
  be: 26
}, {
  element: 'Sc',
  orbital: '3p3/2',
  be: 32
}, {
  element: 'Ti',
  orbital: '3p3/2',
  be: 34
}, {
  element: 'V',
  orbital: '3p3/2',
  be: 38
}, {
  element: 'Cr',
  orbital: '3p3/2',
  be: 43
}, {
  element: 'Mn',
  orbital: '3p3/2',
  be: 49
}, {
  element: 'Fe',
  orbital: '3p3/2',
  be: 56
}, {
  element: 'Co',
  orbital: '3p3/2',
  be: 60
}, {
  element: 'Ni',
  orbital: '3p3/2',
  be: 68
}, {
  element: 'Cu',
  orbital: '3p3/2',
  be: 74
}, {
  element: 'Zn',
  orbital: '3p3/2',
  be: 87
}, {
  element: 'Ga',
  orbital: '3p3/2',
  be: 103
}, {
  element: 'Ge',
  orbital: '3p3/2',
  be: 122
}, {
  element: 'As',
  orbital: '3p3/2',
  be: 141
}, {
  element: 'Se',
  orbital: '3p3/2',
  be: 162
}, {
  element: 'Br',
  orbital: '3p3/2',
  be: 182
}, {
  element: 'Kr',
  orbital: '3p3/2',
  be: 214
}, {
  element: 'Rb',
  orbital: '3p3/2',
  be: 239
}, {
  element: 'Sr',
  orbital: '3p3/2',
  be: 269
}, {
  element: 'Y',
  orbital: '3p3/2',
  be: 301
}, {
  element: 'Zr',
  orbital: '3p3/2',
  be: 331
}, {
  element: 'Nb',
  orbital: '3p3/2',
  be: 363
}, {
  element: 'Mo',
  orbital: '3p3/2',
  be: 393
}, {
  element: 'Tc',
  orbital: '3p3/2',
  be: 425
}, {
  element: 'Ru',
  orbital: '3p3/2',
  be: 461
}, {
  element: 'Rh',
  orbital: '3p3/2',
  be: 496
}, {
  element: 'Pd',
  orbital: '3p3/2',
  be: 531
}, {
  element: 'Ag',
  orbital: '3p3/2',
  be: 571
}, {
  element: 'Cd',
  orbital: '3p3/2',
  be: 617
}, {
  element: 'In',
  orbital: '3p3/2',
  be: 664
}, {
  element: 'Sn',
  orbital: '3p3/2',
  be: 715
}, {
  element: 'Sb',
  orbital: '3p3/2',
  be: 766
}, {
  element: 'Te',
  orbital: '3p3/2',
  be: 819
}, {
  element: 'I',
  orbital: '3p3/2',
  be: 875
}, {
  element: 'Xe',
  orbital: '3p3/2',
  be: 937
}, {
  element: 'Cs',
  orbital: '3p3/2',
  be: 998
}, {
  element: 'Ba',
  orbital: '3p3/2',
  be: 1063
}, {
  element: 'La',
  orbital: '3p3/2',
  be: 1124
}, {
  element: 'Ce',
  orbital: '3p3/2',
  be: 1186
}, {
  element: 'Pr',
  orbital: '3p3/2',
  be: 1243
}, {
  element: 'Si',
  orbital: '3p',
  be: 3
}, {
  element: 'P',
  orbital: '3p',
  be: 10
}, {
  element: 'S',
  orbital: '3p',
  be: 8
}, {
  element: 'Cl',
  orbital: '3p',
  be: 7
}, {
  element: 'Ar',
  orbital: '3p',
  be: 12
}, {
  element: 'K',
  orbital: '3p',
  be: 18
}, {
  element: 'Ca',
  orbital: '3p',
  be: 26
}, {
  element: 'Sc',
  orbital: '3p',
  be: 32
}, {
  element: 'Ti',
  orbital: '3p',
  be: 34
}, {
  element: 'V',
  orbital: '3p',
  be: 38
}, {
  element: 'Cr',
  orbital: '3p',
  be: 43
}, {
  element: 'Mn',
  orbital: '3p',
  be: 49
}, {
  element: 'Fe',
  orbital: '3p',
  be: 56
}, {
  element: 'Co',
  orbital: '3p',
  be: 60
}, {
  element: 'Ni',
  orbital: '3p',
  be: 68
}, {
  element: 'Cu',
  orbital: '3p',
  be: 74
}, {
  element: 'Zn',
  orbital: '3p',
  be: 87
}, {
  element: 'Ga',
  orbital: '3p',
  be: 103
}, {
  element: 'Ge',
  orbital: '3p',
  be: 122
}, {
  element: 'As',
  orbital: '3p',
  be: 141
}, {
  element: 'Se',
  orbital: '3p',
  be: 162
}, {
  element: 'Br',
  orbital: '3p',
  be: 182
}, {
  element: 'Kr',
  orbital: '3p',
  be: 214
}, {
  element: 'Rb',
  orbital: '3p',
  be: 239
}, {
  element: 'Sr',
  orbital: '3p',
  be: 269
}, {
  element: 'Y',
  orbital: '3p',
  be: 301
}, {
  element: 'Zr',
  orbital: '3p',
  be: 331
}, {
  element: 'Nb',
  orbital: '3p',
  be: 363
}, {
  element: 'Mo',
  orbital: '3p',
  be: 393
}, {
  element: 'Tc',
  orbital: '3p',
  be: 425
}, {
  element: 'Ru',
  orbital: '3p',
  be: 461
}, {
  element: 'Rh',
  orbital: '3p',
  be: 496
}, {
  element: 'Pd',
  orbital: '3p',
  be: 531
}, {
  element: 'Ag',
  orbital: '3p',
  be: 571
}, {
  element: 'Cd',
  orbital: '3p',
  be: 617
}, {
  element: 'In',
  orbital: '3p',
  be: 664
}, {
  element: 'Sn',
  orbital: '3p',
  be: 715
}, {
  element: 'Sb',
  orbital: '3p',
  be: 766
}, {
  element: 'Te',
  orbital: '3p',
  be: 819
}, {
  element: 'I',
  orbital: '3p',
  be: 875
}, {
  element: 'Xe',
  orbital: '3p',
  be: 937
}, {
  element: 'Cs',
  orbital: '3p',
  be: 998
}, {
  element: 'Ba',
  orbital: '3p',
  be: 1063
}, {
  element: 'La',
  orbital: '3p',
  be: 1124
}, {
  element: 'Ce',
  orbital: '3p',
  be: 1186
}, {
  element: 'Pr',
  orbital: '3p',
  be: 1243
}, {
  element: 'Sc',
  orbital: '3d3/2',
  be: 7
}, {
  element: 'Ti',
  orbital: '3d3/2',
  be: 3
}, {
  element: 'V',
  orbital: '3d3/2',
  be: 2
}, {
  element: 'Cr',
  orbital: '3d3/2',
  be: 2
}, {
  element: 'Mn',
  orbital: '3d3/2',
  be: 4
}, {
  element: 'Fe',
  orbital: '3d3/2',
  be: 6
}, {
  element: 'Co',
  orbital: '3d3/2',
  be: 3
}, {
  element: 'Ni',
  orbital: '3d3/2',
  be: 4
}, {
  element: 'Cu',
  orbital: '3d3/2',
  be: 2
}, {
  element: 'Zn',
  orbital: '3d3/2',
  be: 9
}, {
  element: 'Ga',
  orbital: '3d3/2',
  be: 18
}, {
  element: 'Ge',
  orbital: '3d3/2',
  be: 29
}, {
  element: 'As',
  orbital: '3d3/2',
  be: 41
}, {
  element: 'Se',
  orbital: '3d3/2',
  be: 57
}, {
  element: 'Br',
  orbital: '3d3/2',
  be: 70
}, {
  element: 'Kr',
  orbital: '3d3/2',
  be: 89
}, {
  element: 'Rb',
  orbital: '3d3/2',
  be: 112
}, {
  element: 'Sr',
  orbital: '3d3/2',
  be: 135
}, {
  element: 'Y',
  orbital: '3d3/2',
  be: 160
}, {
  element: 'Zr',
  orbital: '3d3/2',
  be: 183
}, {
  element: 'Nb',
  orbital: '3d3/2',
  be: 208
}, {
  element: 'Mo',
  orbital: '3d3/2',
  be: 230
}, {
  element: 'Tc',
  orbital: '3d3/2',
  be: 257
}, {
  element: 'Ru',
  orbital: '3d3/2',
  be: 284
}, {
  element: 'Rh',
  orbital: '3d3/2',
  be: 312
}, {
  element: 'Pd',
  orbital: '3d3/2',
  be: 340
}, {
  element: 'Ag',
  orbital: '3d3/2',
  be: 373
}, {
  element: 'Cd',
  orbital: '3d3/2',
  be: 411
}, {
  element: 'In',
  orbital: '3d3/2',
  be: 451
}, {
  element: 'Sn',
  orbital: '3d3/2',
  be: 494
}, {
  element: 'Sb',
  orbital: '3d3/2',
  be: 537
}, {
  element: 'Te',
  orbital: '3d3/2',
  be: 582
}, {
  element: 'I',
  orbital: '3d3/2',
  be: 631
}, {
  element: 'Xe',
  orbital: '3d3/2',
  be: 685
}, {
  element: 'Cs',
  orbital: '3d3/2',
  be: 740
}, {
  element: 'Ba',
  orbital: '3d3/2',
  be: 796
}, {
  element: 'La',
  orbital: '3d3/2',
  be: 849
}, {
  element: 'Ce',
  orbital: '3d3/2',
  be: 902
}, {
  element: 'Pr',
  orbital: '3d3/2',
  be: 951
}, {
  element: 'Nd',
  orbital: '3d3/2',
  be: 1000
}, {
  element: 'Pm',
  orbital: '3d3/2',
  be: 1052
}, {
  element: 'Sm',
  orbital: '3d3/2',
  be: 1107
}, {
  element: 'Eu',
  orbital: '3d3/2',
  be: 1161
}, {
  element: 'Gd',
  orbital: '3d3/2',
  be: 1218
}, {
  element: 'Sc',
  orbital: '3d5/2',
  be: 7
}, {
  element: 'Ti',
  orbital: '3d5/2',
  be: 3
}, {
  element: 'V',
  orbital: '3d5/2',
  be: 2
}, {
  element: 'Cr',
  orbital: '3d5/2',
  be: 2
}, {
  element: 'Mn',
  orbital: '3d5/2',
  be: 4
}, {
  element: 'Fe',
  orbital: '3d5/2',
  be: 6
}, {
  element: 'Co',
  orbital: '3d5/2',
  be: 3
}, {
  element: 'Ni',
  orbital: '3d5/2',
  be: 4
}, {
  element: 'Cu',
  orbital: '3d5/2',
  be: 2
}, {
  element: 'Zn',
  orbital: '3d5/2',
  be: 9
}, {
  element: 'Ga',
  orbital: '3d5/2',
  be: 18
}, {
  element: 'Ge',
  orbital: '3d5/2',
  be: 29
}, {
  element: 'As',
  orbital: '3d5/2',
  be: 41
}, {
  element: 'Se',
  orbital: '3d5/2',
  be: 57
}, {
  element: 'Br',
  orbital: '3d5/2',
  be: 69
}, {
  element: 'Kr',
  orbital: '3d5/2',
  be: 89
}, {
  element: 'Rb',
  orbital: '3d5/2',
  be: 111
}, {
  element: 'Sr',
  orbital: '3d5/2',
  be: 133
}, {
  element: 'Y',
  orbital: '3d5/2',
  be: 158
}, {
  element: 'Zr',
  orbital: '3d5/2',
  be: 180
}, {
  element: 'Nb',
  orbital: '3d5/2',
  be: 205
}, {
  element: 'Mo',
  orbital: '3d5/2',
  be: 227
}, {
  element: 'Tc',
  orbital: '3d5/2',
  be: 253
}, {
  element: 'Ru',
  orbital: '3d5/2',
  be: 279
}, {
  element: 'Rh',
  orbital: '3d5/2',
  be: 307
}, {
  element: 'Pd',
  orbital: '3d5/2',
  be: 335
}, {
  element: 'Ag',
  orbital: '3d5/2',
  be: 367
}, {
  element: 'Cd',
  orbital: '3d5/2',
  be: 404
}, {
  element: 'In',
  orbital: '3d5/2',
  be: 443
}, {
  element: 'Sn',
  orbital: '3d5/2',
  be: 485
}, {
  element: 'Sb',
  orbital: '3d5/2',
  be: 528
}, {
  element: 'Te',
  orbital: '3d5/2',
  be: 572
}, {
  element: 'I',
  orbital: '3d5/2',
  be: 620
}, {
  element: 'Xe',
  orbital: '3d5/2',
  be: 672
}, {
  element: 'Cs',
  orbital: '3d5/2',
  be: 726
}, {
  element: 'Ba',
  orbital: '3d5/2',
  be: 781
}, {
  element: 'La',
  orbital: '3d5/2',
  be: 832
}, {
  element: 'Ce',
  orbital: '3d5/2',
  be: 884
}, {
  element: 'Pr',
  orbital: '3d5/2',
  be: 931
}, {
  element: 'Nd',
  orbital: '3d5/2',
  be: 978
}, {
  element: 'Pm',
  orbital: '3d5/2',
  be: 1027
}, {
  element: 'Sm',
  orbital: '3d5/2',
  be: 1081
}, {
  element: 'Eu',
  orbital: '3d5/2',
  be: 1131
}, {
  element: 'Gd',
  orbital: '3d5/2',
  be: 1186
}, {
  element: 'Tb',
  orbital: '3d5/2',
  be: 1242
}, {
  element: 'Sc',
  orbital: '3d',
  be: 7
}, {
  element: 'Ti',
  orbital: '3d',
  be: 3
}, {
  element: 'V',
  orbital: '3d',
  be: 2
}, {
  element: 'Cr',
  orbital: '3d',
  be: 2
}, {
  element: 'Mn',
  orbital: '3d',
  be: 4
}, {
  element: 'Fe',
  orbital: '3d',
  be: 6
}, {
  element: 'Co',
  orbital: '3d',
  be: 3
}, {
  element: 'Ni',
  orbital: '3d',
  be: 4
}, {
  element: 'Cu',
  orbital: '3d',
  be: 2
}, {
  element: 'Zn',
  orbital: '3d',
  be: 9
}, {
  element: 'Ga',
  orbital: '3d',
  be: 18
}, {
  element: 'Ge',
  orbital: '3d',
  be: 29
}, {
  element: 'As',
  orbital: '3d',
  be: 41
}, {
  element: 'Se',
  orbital: '3d',
  be: 57
}, {
  element: 'Br',
  orbital: '3d',
  be: 69
}, {
  element: 'Kr',
  orbital: '3d',
  be: 89
}, {
  element: 'Rb',
  orbital: '3d',
  be: 111
}, {
  element: 'Sr',
  orbital: '3d',
  be: 133
}, {
  element: 'Y',
  orbital: '3d',
  be: 158
}, {
  element: 'Zr',
  orbital: '3d',
  be: 180
}, {
  element: 'Nb',
  orbital: '3d',
  be: 205
}, {
  element: 'Mo',
  orbital: '3d',
  be: 227
}, {
  element: 'Tc',
  orbital: '3d',
  be: 253
}, {
  element: 'Ru',
  orbital: '3d',
  be: 279
}, {
  element: 'Rh',
  orbital: '3d',
  be: 307
}, {
  element: 'Pd',
  orbital: '3d',
  be: 335
}, {
  element: 'Ag',
  orbital: '3d',
  be: 367
}, {
  element: 'Cd',
  orbital: '3d',
  be: 404
}, {
  element: 'In',
  orbital: '3d',
  be: 443
}, {
  element: 'Sn',
  orbital: '3d',
  be: 485
}, {
  element: 'Sb',
  orbital: '3d',
  be: 528
}, {
  element: 'Te',
  orbital: '3d',
  be: 572
}, {
  element: 'I',
  orbital: '3d',
  be: 620
}, {
  element: 'Xe',
  orbital: '3d',
  be: 672
}, {
  element: 'Cs',
  orbital: '3d',
  be: 726
}, {
  element: 'Ba',
  orbital: '3d',
  be: 781
}, {
  element: 'La',
  orbital: '3d',
  be: 832
}, {
  element: 'Ce',
  orbital: '3d',
  be: 884
}, {
  element: 'Pr',
  orbital: '3d',
  be: 931
}, {
  element: 'Nd',
  orbital: '3d',
  be: 978
}, {
  element: 'Pm',
  orbital: '3d',
  be: 1027
}, {
  element: 'Sm',
  orbital: '3d',
  be: 1081
}, {
  element: 'Eu',
  orbital: '3d',
  be: 1131
}, {
  element: 'Gd',
  orbital: '3d',
  be: 1186
}, {
  element: 'Tb',
  orbital: '3d',
  be: 1242
}, {
  element: 'Br',
  orbital: '4s',
  be: 27
}, {
  element: 'Kr',
  orbital: '4s',
  be: 24
}, {
  element: 'Rb',
  orbital: '4s',
  be: 30
}, {
  element: 'Sr',
  orbital: '4s',
  be: 38
}, {
  element: 'Y',
  orbital: '4s',
  be: 46
}, {
  element: 'Zr',
  orbital: '4s',
  be: 52
}, {
  element: 'Nb',
  orbital: '4s',
  be: 58
}, {
  element: 'Mo',
  orbital: '4s',
  be: 62
}, {
  element: 'Tc',
  orbital: '4s',
  be: 68
}, {
  element: 'Ru',
  orbital: '4s',
  be: 75
}, {
  element: 'Rh',
  orbital: '4s',
  be: 81
}, {
  element: 'Pd',
  orbital: '4s',
  be: 86
}, {
  element: 'Ag',
  orbital: '4s',
  be: 95
}, {
  element: 'Cd',
  orbital: '4s',
  be: 108
}, {
  element: 'In',
  orbital: '4s',
  be: 122
}, {
  element: 'Sn',
  orbital: '4s',
  be: 137
}, {
  element: 'Sb',
  orbital: '4s',
  be: 152
}, {
  element: 'Te',
  orbital: '4s',
  be: 168
}, {
  element: 'I',
  orbital: '4s',
  be: 186
}, {
  element: 'Xe',
  orbital: '4s',
  be: 208
}, {
  element: 'Cs',
  orbital: '4s',
  be: 231
}, {
  element: 'Ba',
  orbital: '4s',
  be: 253
}, {
  element: 'La',
  orbital: '4s',
  be: 271
}, {
  element: 'Ce',
  orbital: '4s',
  be: 290
}, {
  element: 'Pr',
  orbital: '4s',
  be: 305
}, {
  element: 'Nd',
  orbital: '4s',
  be: 316
}, {
  element: 'Pm',
  orbital: '4s',
  be: 331
}, {
  element: 'Sm',
  orbital: '4s',
  be: 347
}, {
  element: 'Eu',
  orbital: '4s',
  be: 360
}, {
  element: 'Gd',
  orbital: '4s',
  be: 376
}, {
  element: 'Tb',
  orbital: '4s',
  be: 398
}, {
  element: 'Dy',
  orbital: '4s',
  be: 416
}, {
  element: 'Ho',
  orbital: '4s',
  be: 436
}, {
  element: 'Er',
  orbital: '4s',
  be: 449
}, {
  element: 'Tm',
  orbital: '4s',
  be: 472
}, {
  element: 'Yb',
  orbital: '4s',
  be: 487
}, {
  element: 'Lu',
  orbital: '4s',
  be: 506
}, {
  element: 'Hf',
  orbital: '4s',
  be: 538
}, {
  element: 'Ta',
  orbital: '4s',
  be: 566
}, {
  element: 'W',
  orbital: '4s',
  be: 595
}, {
  element: 'Re',
  orbital: '4s',
  be: 625
}, {
  element: 'Os',
  orbital: '4s',
  be: 655
}, {
  element: 'Ir',
  orbital: '4s',
  be: 690
}, {
  element: 'Pt',
  orbital: '4s',
  be: 724
}, {
  element: 'Au',
  orbital: '4s',
  be: 759
}, {
  element: 'Hg',
  orbital: '4s',
  be: 800
}, {
  element: 'Tl',
  orbital: '4s',
  be: 846
}, {
  element: 'Pb',
  orbital: '4s',
  be: 894
}, {
  element: 'Bi',
  orbital: '4s',
  be: 939
}, {
  element: 'Po',
  orbital: '4s',
  be: 995
}, {
  element: 'At',
  orbital: '4s',
  be: 1042
}, {
  element: 'Rn',
  orbital: '4s',
  be: 1097
}, {
  element: 'Fr',
  orbital: '4s',
  be: 1153
}, {
  element: 'Ra',
  orbital: '4s',
  be: 1208
}, {
  element: 'Ga',
  orbital: '4p1/2',
  be: 1
}, {
  element: 'Ge',
  orbital: '4p1/2',
  be: 3
}, {
  element: 'As',
  orbital: '4p1/2',
  be: 3
}, {
  element: 'Se',
  orbital: '4p1/2',
  be: 6
}, {
  element: 'Br',
  orbital: '4p1/2',
  be: 5
}, {
  element: 'Kr',
  orbital: '4p1/2',
  be: 11
}, {
  element: 'Rb',
  orbital: '4p1/2',
  be: 15
}, {
  element: 'Sr',
  orbital: '4p1/2',
  be: 20
}, {
  element: 'Y',
  orbital: '4p1/2',
  be: 26
}, {
  element: 'Zr',
  orbital: '4p1/2',
  be: 29
}, {
  element: 'Nb',
  orbital: '4p1/2',
  be: 34
}, {
  element: 'Mo',
  orbital: '4p1/2',
  be: 35
}, {
  element: 'Tc',
  orbital: '4p1/2',
  be: 39
}, {
  element: 'Ru',
  orbital: '4p1/2',
  be: 43
}, {
  element: 'Rh',
  orbital: '4p1/2',
  be: 48
}, {
  element: 'Pd',
  orbital: '4p1/2',
  be: 51
}, {
  element: 'Ag',
  orbital: '4p1/2',
  be: 62
}, {
  element: 'Cd',
  orbital: '4p1/2',
  be: 67
}, {
  element: 'In',
  orbital: '4p1/2',
  be: 77
}, {
  element: 'Sn',
  orbital: '4p1/2',
  be: 89
}, {
  element: 'Sb',
  orbital: '4p1/2',
  be: 99
}, {
  element: 'Te',
  orbital: '4p1/2',
  be: 110
}, {
  element: 'I',
  orbital: '4p1/2',
  be: 123
}, {
  element: 'Xe',
  orbital: '4p1/2',
  be: 147
}, {
  element: 'Cs',
  orbital: '4p1/2',
  be: 172
}, {
  element: 'Ba',
  orbital: '4p1/2',
  be: 192
}, {
  element: 'La',
  orbital: '4p1/2',
  be: 206
}, {
  element: 'Ce',
  orbital: '4p1/2',
  be: 224
}, {
  element: 'Pr',
  orbital: '4p1/2',
  be: 237
}, {
  element: 'Nd',
  orbital: '4p1/2',
  be: 244
}, {
  element: 'Pm',
  orbital: '4p1/2',
  be: 255
}, {
  element: 'Sm',
  orbital: '4p1/2',
  be: 267
}, {
  element: 'Eu',
  orbital: '4p1/2',
  be: 284
}, {
  element: 'Gd',
  orbital: '4p1/2',
  be: 289
}, {
  element: 'Tb',
  orbital: '4p1/2',
  be: 311
}, {
  element: 'Dy',
  orbital: '4p1/2',
  be: 332
}, {
  element: 'Ho',
  orbital: '4p1/2',
  be: 343
}, {
  element: 'Er',
  orbital: '4p1/2',
  be: 366
}, {
  element: 'Tm',
  orbital: '4p1/2',
  be: 386
}, {
  element: 'Yb',
  orbital: '4p1/2',
  be: 396
}, {
  element: 'Lu',
  orbital: '4p1/2',
  be: 410
}, {
  element: 'Hf',
  orbital: '4p1/2',
  be: 437
}, {
  element: 'Ta',
  orbital: '4p1/2',
  be: 465
}, {
  element: 'W',
  orbital: '4p1/2',
  be: 492
}, {
  element: 'Re',
  orbital: '4p1/2',
  be: 518
}, {
  element: 'Os',
  orbital: '4p1/2',
  be: 547
}, {
  element: 'Ir',
  orbital: '4p1/2',
  be: 577
}, {
  element: 'Pt',
  orbital: '4p1/2',
  be: 608
}, {
  element: 'Au',
  orbital: '4p1/2',
  be: 644
}, {
  element: 'Hg',
  orbital: '4p1/2',
  be: 677
}, {
  element: 'Tl',
  orbital: '4p1/2',
  be: 722
}, {
  element: 'Pb',
  orbital: '4p1/2',
  be: 764
}, {
  element: 'Bi',
  orbital: '4p1/2',
  be: 806
}, {
  element: 'Po',
  orbital: '4p1/2',
  be: 851
}, {
  element: 'At',
  orbital: '4p1/2',
  be: 886
}, {
  element: 'Rn',
  orbital: '4p1/2',
  be: 929
}, {
  element: 'Fr',
  orbital: '4p1/2',
  be: 980
}, {
  element: 'Ra',
  orbital: '4p1/2',
  be: 1058
}, {
  element: 'Ac',
  orbital: '4p1/2',
  be: 1080
}, {
  element: 'Th',
  orbital: '4p1/2',
  be: 1168
}, {
  element: 'Ga',
  orbital: '4p3/2',
  be: 1
}, {
  element: 'Ge',
  orbital: '4p3/2',
  be: 3
}, {
  element: 'As',
  orbital: '4p3/2',
  be: 3
}, {
  element: 'Se',
  orbital: '4p3/2',
  be: 6
}, {
  element: 'Br',
  orbital: '4p3/2',
  be: 5
}, {
  element: 'Kr',
  orbital: '4p3/2',
  be: 11
}, {
  element: 'Rb',
  orbital: '4p3/2',
  be: 14
}, {
  element: 'Sr',
  orbital: '4p3/2',
  be: 20
}, {
  element: 'Y',
  orbital: '4p3/2',
  be: 26
}, {
  element: 'Zr',
  orbital: '4p3/2',
  be: 29
}, {
  element: 'Nb',
  orbital: '4p3/2',
  be: 34
}, {
  element: 'Mo',
  orbital: '4p3/2',
  be: 35
}, {
  element: 'Tc',
  orbital: '4p3/2',
  be: 39
}, {
  element: 'Ru',
  orbital: '4p3/2',
  be: 43
}, {
  element: 'Rh',
  orbital: '4p3/2',
  be: 48
}, {
  element: 'Pd',
  orbital: '4p3/2',
  be: 56
}, {
  element: 'Ag',
  orbital: '4p3/2',
  be: 56
}, {
  element: 'Cd',
  orbital: '4p3/2',
  be: 67
}, {
  element: 'In',
  orbital: '4p3/2',
  be: 77
}, {
  element: 'Sn',
  orbital: '4p3/2',
  be: 89
}, {
  element: 'Sb',
  orbital: '4p3/2',
  be: 99
}, {
  element: 'Te',
  orbital: '4p3/2',
  be: 110
}, {
  element: 'I',
  orbital: '4p3/2',
  be: 123
}, {
  element: 'Xe',
  orbital: '4p3/2',
  be: 147
}, {
  element: 'Cs',
  orbital: '4p3/2',
  be: 162
}, {
  element: 'Ba',
  orbital: '4p3/2',
  be: 180
}, {
  element: 'La',
  orbital: '4p3/2',
  be: 192
}, {
  element: 'Ce',
  orbital: '4p3/2',
  be: 208
}, {
  element: 'Pr',
  orbital: '4p3/2',
  be: 218
}, {
  element: 'Nd',
  orbital: '4p3/2',
  be: 225
}, {
  element: 'Pm',
  orbital: '4p3/2',
  be: 237
}, {
  element: 'Sm',
  orbital: '4p3/2',
  be: 249
}, {
  element: 'Eu',
  orbital: '4p3/2',
  be: 257
}, {
  element: 'Gd',
  orbital: '4p3/2',
  be: 271
}, {
  element: 'Tb',
  orbital: '4p3/2',
  be: 286
}, {
  element: 'Dy',
  orbital: '4p3/2',
  be: 293
}, {
  element: 'Ho',
  orbital: '4p3/2',
  be: 306
}, {
  element: 'Er',
  orbital: '4p3/2',
  be: 320
}, {
  element: 'Tm',
  orbital: '4p3/2',
  be: 337
}, {
  element: 'Yb',
  orbital: '4p3/2',
  be: 343
}, {
  element: 'Lu',
  orbital: '4p3/2',
  be: 359
}, {
  element: 'Hf',
  orbital: '4p3/2',
  be: 380
}, {
  element: 'Ta',
  orbital: '4p3/2',
  be: 405
}, {
  element: 'W',
  orbital: '4p3/2',
  be: 426
}, {
  element: 'Re',
  orbital: '4p3/2',
  be: 445
}, {
  element: 'Os',
  orbital: '4p3/2',
  be: 469
}, {
  element: 'Ir',
  orbital: '4p3/2',
  be: 495
}, {
  element: 'Pt',
  orbital: '4p3/2',
  be: 519
}, {
  element: 'Au',
  orbital: '4p3/2',
  be: 546
}, {
  element: 'Hg',
  orbital: '4p3/2',
  be: 571
}, {
  element: 'Tl',
  orbital: '4p3/2',
  be: 609
}, {
  element: 'Pb',
  orbital: '4p3/2',
  be: 645
}, {
  element: 'Bi',
  orbital: '4p3/2',
  be: 679
}, {
  element: 'Po',
  orbital: '4p3/2',
  be: 705
}, {
  element: 'At',
  orbital: '4p3/2',
  be: 740
}, {
  element: 'Rn',
  orbital: '4p3/2',
  be: 768
}, {
  element: 'Fr',
  orbital: '4p3/2',
  be: 810
}, {
  element: 'Ra',
  orbital: '4p3/2',
  be: 879
}, {
  element: 'Ac',
  orbital: '4p3/2',
  be: 890
}, {
  element: 'Th',
  orbital: '4p3/2',
  be: 968
}, {
  element: 'Ga',
  orbital: '4p',
  be: 1
}, {
  element: 'Ge',
  orbital: '4p',
  be: 3
}, {
  element: 'As',
  orbital: '4p',
  be: 3
}, {
  element: 'Se',
  orbital: '4p',
  be: 6
}, {
  element: 'Br',
  orbital: '4p',
  be: 5
}, {
  element: 'Kr',
  orbital: '4p',
  be: 11
}, {
  element: 'Rb',
  orbital: '4p',
  be: 14
}, {
  element: 'Sr',
  orbital: '4p',
  be: 20
}, {
  element: 'Y',
  orbital: '4p',
  be: 26
}, {
  element: 'Zr',
  orbital: '4p',
  be: 29
}, {
  element: 'Nb',
  orbital: '4p',
  be: 34
}, {
  element: 'Mo',
  orbital: '4p',
  be: 35
}, {
  element: 'Tc',
  orbital: '4p',
  be: 39
}, {
  element: 'Ru',
  orbital: '4p',
  be: 43
}, {
  element: 'Rh',
  orbital: '4p',
  be: 48
}, {
  element: 'Pd',
  orbital: '4p',
  be: 56
}, {
  element: 'Ag',
  orbital: '4p',
  be: 56
}, {
  element: 'Cd',
  orbital: '4p',
  be: 67
}, {
  element: 'In',
  orbital: '4p',
  be: 77
}, {
  element: 'Sn',
  orbital: '4p',
  be: 89
}, {
  element: 'Sb',
  orbital: '4p',
  be: 99
}, {
  element: 'Te',
  orbital: '4p',
  be: 110
}, {
  element: 'I',
  orbital: '4p',
  be: 123
}, {
  element: 'Xe',
  orbital: '4p',
  be: 147
}, {
  element: 'Cs',
  orbital: '4p',
  be: 162
}, {
  element: 'Ba',
  orbital: '4p',
  be: 180
}, {
  element: 'La',
  orbital: '4p',
  be: 192
}, {
  element: 'Ce',
  orbital: '4p',
  be: 208
}, {
  element: 'Pr',
  orbital: '4p',
  be: 218
}, {
  element: 'Nd',
  orbital: '4p',
  be: 225
}, {
  element: 'Pm',
  orbital: '4p',
  be: 237
}, {
  element: 'Sm',
  orbital: '4p',
  be: 249
}, {
  element: 'Eu',
  orbital: '4p',
  be: 257
}, {
  element: 'Gd',
  orbital: '4p',
  be: 271
}, {
  element: 'Tb',
  orbital: '4p',
  be: 286
}, {
  element: 'Dy',
  orbital: '4p',
  be: 293
}, {
  element: 'Ho',
  orbital: '4p',
  be: 306
}, {
  element: 'Er',
  orbital: '4p',
  be: 320
}, {
  element: 'Tm',
  orbital: '4p',
  be: 337
}, {
  element: 'Yb',
  orbital: '4p',
  be: 343
}, {
  element: 'Lu',
  orbital: '4p',
  be: 359
}, {
  element: 'Hf',
  orbital: '4p',
  be: 380
}, {
  element: 'Ta',
  orbital: '4p',
  be: 405
}, {
  element: 'W',
  orbital: '4p',
  be: 426
}, {
  element: 'Re',
  orbital: '4p',
  be: 445
}, {
  element: 'Os',
  orbital: '4p',
  be: 469
}, {
  element: 'Ir',
  orbital: '4p',
  be: 495
}, {
  element: 'Pt',
  orbital: '4p',
  be: 519
}, {
  element: 'Au',
  orbital: '4p',
  be: 546
}, {
  element: 'Hg',
  orbital: '4p',
  be: 571
}, {
  element: 'Tl',
  orbital: '4p',
  be: 609
}, {
  element: 'Pb',
  orbital: '4p',
  be: 645
}, {
  element: 'Bi',
  orbital: '4p',
  be: 679
}, {
  element: 'Po',
  orbital: '4p',
  be: 705
}, {
  element: 'At',
  orbital: '4p',
  be: 740
}, {
  element: 'Rn',
  orbital: '4p',
  be: 768
}, {
  element: 'Fr',
  orbital: '4p',
  be: 810
}, {
  element: 'Ra',
  orbital: '4p',
  be: 879
}, {
  element: 'Ac',
  orbital: '4p',
  be: 890
}, {
  element: 'Th',
  orbital: '4p',
  be: 968
}, {
  element: 'Y',
  orbital: '4d3/2',
  be: 3
}, {
  element: 'Zr',
  orbital: '4d3/2',
  be: 3
}, {
  element: 'Nb',
  orbital: '4d3/2',
  be: 4
}, {
  element: 'Mo',
  orbital: '4d3/2',
  be: 2
}, {
  element: 'Tc',
  orbital: '4d3/2',
  be: 2
}, {
  element: 'Ru',
  orbital: '4d3/2',
  be: 2
}, {
  element: 'Rh',
  orbital: '4d3/2',
  be: 3
}, {
  element: 'Pd',
  orbital: '4d3/2',
  be: 1
}, {
  element: 'Ag',
  orbital: '4d3/2',
  be: 3
}, {
  element: 'Cd',
  orbital: '4d3/2',
  be: 9
}, {
  element: 'In',
  orbital: '4d3/2',
  be: 16
}, {
  element: 'Sn',
  orbital: '4d3/2',
  be: 24
}, {
  element: 'Sb',
  orbital: '4d3/2',
  be: 32
}, {
  element: 'Te',
  orbital: '4d3/2',
  be: 40
}, {
  element: 'I',
  orbital: '4d3/2',
  be: 50
}, {
  element: 'Xe',
  orbital: '4d3/2',
  be: 63
}, {
  element: 'Cs',
  orbital: '4d3/2',
  be: 79
}, {
  element: 'Ba',
  orbital: '4d3/2',
  be: 93
}, {
  element: 'La',
  orbital: '4d3/2',
  be: 99
}, {
  element: 'Ce',
  orbital: '4d3/2',
  be: 111
}, {
  element: 'Pr',
  orbital: '4d3/2',
  be: 114
}, {
  element: 'Nd',
  orbital: '4d3/2',
  be: 118
}, {
  element: 'Pm',
  orbital: '4d3/2',
  be: 121
}, {
  element: 'Sm',
  orbital: '4d3/2',
  be: 130
}, {
  element: 'Eu',
  orbital: '4d3/2',
  be: 134
}, {
  element: 'Gd',
  orbital: '4d3/2',
  be: 141
}, {
  element: 'Tb',
  orbital: '4d3/2',
  be: 148
}, {
  element: 'Dy',
  orbital: '4d3/2',
  be: 154
}, {
  element: 'Ho',
  orbital: '4d3/2',
  be: 161
}, {
  element: 'Er',
  orbital: '4d3/2',
  be: 177
}, {
  element: 'Tm',
  orbital: '4d3/2',
  be: 180
}, {
  element: 'Yb',
  orbital: '4d3/2',
  be: 197
}, {
  element: 'Lu',
  orbital: '4d3/2',
  be: 205
}, {
  element: 'Hf',
  orbital: '4d3/2',
  be: 224
}, {
  element: 'Ta',
  orbital: '4d3/2',
  be: 242
}, {
  element: 'W',
  orbital: '4d3/2',
  be: 259
}, {
  element: 'Re',
  orbital: '4d3/2',
  be: 274
}, {
  element: 'Os',
  orbital: '4d3/2',
  be: 290
}, {
  element: 'Ir',
  orbital: '4d3/2',
  be: 312
}, {
  element: 'Pt',
  orbital: '4d3/2',
  be: 331
}, {
  element: 'Au',
  orbital: '4d3/2',
  be: 352
}, {
  element: 'Hg',
  orbital: '4d3/2',
  be: 379
}, {
  element: 'Tl',
  orbital: '4d3/2',
  be: 407
}, {
  element: 'Pb',
  orbital: '4d3/2',
  be: 435
}, {
  element: 'Bi',
  orbital: '4d3/2',
  be: 464
}, {
  element: 'Po',
  orbital: '4d3/2',
  be: 500
}, {
  element: 'At',
  orbital: '4d3/2',
  be: 533
}, {
  element: 'Rn',
  orbital: '4d3/2',
  be: 567
}, {
  element: 'Fr',
  orbital: '4d3/2',
  be: 603
}, {
  element: 'Ra',
  orbital: '4d3/2',
  be: 636
}, {
  element: 'Ac',
  orbital: '4d3/2',
  be: 675
}, {
  element: 'Th',
  orbital: '4d3/2',
  be: 714
}, {
  element: 'U',
  orbital: '4d3/2',
  be: 781
}, {
  element: 'Y',
  orbital: '4d5/2',
  be: 3
}, {
  element: 'Zr',
  orbital: '4d5/2',
  be: 3
}, {
  element: 'Nb',
  orbital: '4d5/2',
  be: 4
}, {
  element: 'Mo',
  orbital: '4d5/2',
  be: 2
}, {
  element: 'Tc',
  orbital: '4d5/2',
  be: 2
}, {
  element: 'Ru',
  orbital: '4d5/2',
  be: 2
}, {
  element: 'Rh',
  orbital: '4d5/2',
  be: 3
}, {
  element: 'Pd',
  orbital: '4d5/2',
  be: 1
}, {
  element: 'Ag',
  orbital: '4d5/2',
  be: 3
}, {
  element: 'Cd',
  orbital: '4d5/2',
  be: 9
}, {
  element: 'In',
  orbital: '4d5/2',
  be: 16
}, {
  element: 'Sn',
  orbital: '4d5/2',
  be: 24
}, {
  element: 'Sb',
  orbital: '4d5/2',
  be: 32
}, {
  element: 'Te',
  orbital: '4d5/2',
  be: 40
}, {
  element: 'I',
  orbital: '4d5/2',
  be: 50
}, {
  element: 'Xe',
  orbital: '4d5/2',
  be: 63
}, {
  element: 'Cs',
  orbital: '4d5/2',
  be: 77
}, {
  element: 'Ba',
  orbital: '4d5/2',
  be: 90
}, {
  element: 'La',
  orbital: '4d5/2',
  be: 99
}, {
  element: 'Ce',
  orbital: '4d5/2',
  be: 111
}, {
  element: 'Pr',
  orbital: '4d5/2',
  be: 114
}, {
  element: 'Nd',
  orbital: '4d5/2',
  be: 118
}, {
  element: 'Pm',
  orbital: '4d5/2',
  be: 121
}, {
  element: 'Sm',
  orbital: '4d5/2',
  be: 130
}, {
  element: 'Eu',
  orbital: '4d5/2',
  be: 134
}, {
  element: 'Gd',
  orbital: '4d5/2',
  be: 141
}, {
  element: 'Tb',
  orbital: '4d5/2',
  be: 148
}, {
  element: 'Dy',
  orbital: '4d5/2',
  be: 154
}, {
  element: 'Ho',
  orbital: '4d5/2',
  be: 161
}, {
  element: 'Er',
  orbital: '4d5/2',
  be: 168
}, {
  element: 'Tm',
  orbital: '4d5/2',
  be: 180
}, {
  element: 'Yb',
  orbital: '4d5/2',
  be: 184
}, {
  element: 'Lu',
  orbital: '4d5/2',
  be: 195
}, {
  element: 'Hf',
  orbital: '4d5/2',
  be: 214
}, {
  element: 'Ta',
  orbital: '4d5/2',
  be: 230
}, {
  element: 'W',
  orbital: '4d5/2',
  be: 246
}, {
  element: 'Re',
  orbital: '4d5/2',
  be: 260
}, {
  element: 'Os',
  orbital: '4d5/2',
  be: 273
}, {
  element: 'Ir',
  orbital: '4d5/2',
  be: 295
}, {
  element: 'Pt',
  orbital: '4d5/2',
  be: 314
}, {
  element: 'Au',
  orbital: '4d5/2',
  be: 334
}, {
  element: 'Hg',
  orbital: '4d5/2',
  be: 360
}, {
  element: 'Tl',
  orbital: '4d5/2',
  be: 386
}, {
  element: 'Pb',
  orbital: '4d5/2',
  be: 413
}, {
  element: 'Bi',
  orbital: '4d5/2',
  be: 440
}, {
  element: 'Po',
  orbital: '4d5/2',
  be: 473
}, {
  element: 'At',
  orbital: '4d5/2',
  be: 507
}, {
  element: 'Rn',
  orbital: '4d5/2',
  be: 541
}, {
  element: 'Fr',
  orbital: '4d5/2',
  be: 577
}, {
  element: 'Ra',
  orbital: '4d5/2',
  be: 603
}, {
  element: 'Ac',
  orbital: '4d5/2',
  be: 639
}, {
  element: 'Th',
  orbital: '4d5/2',
  be: 677
}, {
  element: 'U',
  orbital: '4d5/2',
  be: 739
}, {
  element: 'Y',
  orbital: '4d',
  be: 3
}, {
  element: 'Zr',
  orbital: '4d',
  be: 3
}, {
  element: 'Nb',
  orbital: '4d',
  be: 4
}, {
  element: 'Mo',
  orbital: '4d',
  be: 2
}, {
  element: 'Tc',
  orbital: '4d',
  be: 2
}, {
  element: 'Ru',
  orbital: '4d',
  be: 2
}, {
  element: 'Rh',
  orbital: '4d',
  be: 3
}, {
  element: 'Pd',
  orbital: '4d',
  be: 1
}, {
  element: 'Ag',
  orbital: '4d',
  be: 3
}, {
  element: 'Cd',
  orbital: '4d',
  be: 9
}, {
  element: 'In',
  orbital: '4d',
  be: 16
}, {
  element: 'Sn',
  orbital: '4d',
  be: 24
}, {
  element: 'Sb',
  orbital: '4d',
  be: 32
}, {
  element: 'Te',
  orbital: '4d',
  be: 40
}, {
  element: 'I',
  orbital: '4d',
  be: 50
}, {
  element: 'Xe',
  orbital: '4d',
  be: 63
}, {
  element: 'Cs',
  orbital: '4d',
  be: 77
}, {
  element: 'Ba',
  orbital: '4d',
  be: 90
}, {
  element: 'La',
  orbital: '4d',
  be: 99
}, {
  element: 'Ce',
  orbital: '4d',
  be: 111
}, {
  element: 'Pr',
  orbital: '4d',
  be: 114
}, {
  element: 'Nd',
  orbital: '4d',
  be: 118
}, {
  element: 'Pm',
  orbital: '4d',
  be: 121
}, {
  element: 'Sm',
  orbital: '4d',
  be: 130
}, {
  element: 'Eu',
  orbital: '4d',
  be: 134
}, {
  element: 'Gd',
  orbital: '4d',
  be: 141
}, {
  element: 'Tb',
  orbital: '4d',
  be: 148
}, {
  element: 'Dy',
  orbital: '4d',
  be: 154
}, {
  element: 'Ho',
  orbital: '4d',
  be: 161
}, {
  element: 'Er',
  orbital: '4d',
  be: 168
}, {
  element: 'Tm',
  orbital: '4d',
  be: 180
}, {
  element: 'Yb',
  orbital: '4d',
  be: 184
}, {
  element: 'Lu',
  orbital: '4d',
  be: 195
}, {
  element: 'Hf',
  orbital: '4d',
  be: 214
}, {
  element: 'Ta',
  orbital: '4d',
  be: 230
}, {
  element: 'W',
  orbital: '4d',
  be: 246
}, {
  element: 'Re',
  orbital: '4d',
  be: 260
}, {
  element: 'Os',
  orbital: '4d',
  be: 273
}, {
  element: 'Ir',
  orbital: '4d',
  be: 295
}, {
  element: 'Pt',
  orbital: '4d',
  be: 314
}, {
  element: 'Au',
  orbital: '4d',
  be: 334
}, {
  element: 'Hg',
  orbital: '4d',
  be: 360
}, {
  element: 'Tl',
  orbital: '4d',
  be: 386
}, {
  element: 'Pb',
  orbital: '4d',
  be: 413
}, {
  element: 'Bi',
  orbital: '4d',
  be: 440
}, {
  element: 'Po',
  orbital: '4d',
  be: 473
}, {
  element: 'At',
  orbital: '4d',
  be: 507
}, {
  element: 'Rn',
  orbital: '4d',
  be: 541
}, {
  element: 'Fr',
  orbital: '4d',
  be: 577
}, {
  element: 'Ra',
  orbital: '4d',
  be: 603
}, {
  element: 'Ac',
  orbital: '4d',
  be: 639
}, {
  element: 'Th',
  orbital: '4d',
  be: 677
}, {
  element: 'U',
  orbital: '4d',
  be: 739
}, {
  element: 'Ce',
  orbital: '4f5/2',
  be: 1
}, {
  element: 'Pr',
  orbital: '4f5/2',
  be: 2
}, {
  element: 'Nd',
  orbital: '4f5/2',
  be: 2
}, {
  element: 'Pm',
  orbital: '4f5/2',
  be: 4
}, {
  element: 'Sm',
  orbital: '4f5/2',
  be: 7
}, {
  element: 'Tb',
  orbital: '4f5/2',
  be: 3
}, {
  element: 'Dy',
  orbital: '4f5/2',
  be: 4
}, {
  element: 'Ho',
  orbital: '4f5/2',
  be: 4
}, {
  element: 'Er',
  orbital: '4f5/2',
  be: 4
}, {
  element: 'Tm',
  orbital: '4f5/2',
  be: 5
}, {
  element: 'Yb',
  orbital: '4f5/2',
  be: 6
}, {
  element: 'Lu',
  orbital: '4f5/2',
  be: 7
}, {
  element: 'Hf',
  orbital: '4f5/2',
  be: 19
}, {
  element: 'Ta',
  orbital: '4f5/2',
  be: 27
}, {
  element: 'W',
  orbital: '4f5/2',
  be: 37
}, {
  element: 'Re',
  orbital: '4f5/2',
  be: 47
}, {
  element: 'Os',
  orbital: '4f5/2',
  be: 52
}, {
  element: 'Ir',
  orbital: '4f5/2',
  be: 63
}, {
  element: 'Pt',
  orbital: '4f5/2',
  be: 74
}, {
  element: 'Au',
  orbital: '4f5/2',
  be: 87
}, {
  element: 'Hg',
  orbital: '4f5/2',
  be: 103
}, {
  element: 'Tl',
  orbital: '4f5/2',
  be: 122
}, {
  element: 'Pb',
  orbital: '4f5/2',
  be: 143
}, {
  element: 'Bi',
  orbital: '4f5/2',
  be: 163
}, {
  element: 'Po',
  orbital: '4f5/2',
  be: 184
}, {
  element: 'At',
  orbital: '4f5/2',
  be: 210
}, {
  element: 'Rn',
  orbital: '4f5/2',
  be: 238
}, {
  element: 'Fr',
  orbital: '4f5/2',
  be: 268
}, {
  element: 'Ra',
  orbital: '4f5/2',
  be: 299
}, {
  element: 'Ac',
  orbital: '4f5/2',
  be: 319
}, {
  element: 'Th',
  orbital: '4f5/2',
  be: 344
}, {
  element: 'U',
  orbital: '4f5/2',
  be: 391
}, {
  element: 'Ce',
  orbital: '4f7/2',
  be: 1
}, {
  element: 'Pr',
  orbital: '4f7/2',
  be: 2
}, {
  element: 'Nd',
  orbital: '4f7/2',
  be: 2
}, {
  element: 'Pm',
  orbital: '4f7/2',
  be: 4
}, {
  element: 'Sm',
  orbital: '4f7/2',
  be: 7
}, {
  element: 'Tb',
  orbital: '4f7/2',
  be: 3
}, {
  element: 'Dy',
  orbital: '4f7/2',
  be: 4
}, {
  element: 'Ho',
  orbital: '4f7/2',
  be: 4
}, {
  element: 'Er',
  orbital: '4f7/2',
  be: 4
}, {
  element: 'Tm',
  orbital: '4f7/2',
  be: 5
}, {
  element: 'Yb',
  orbital: '4f7/2',
  be: 6
}, {
  element: 'Lu',
  orbital: '4f7/2',
  be: 7
}, {
  element: 'Hf',
  orbital: '4f7/2',
  be: 18
}, {
  element: 'Ta',
  orbital: '4f7/2',
  be: 25
}, {
  element: 'W',
  orbital: '4f7/2',
  be: 34
}, {
  element: 'Re',
  orbital: '4f7/2',
  be: 45
}, {
  element: 'Os',
  orbital: '4f7/2',
  be: 50
}, {
  element: 'Ir',
  orbital: '4f7/2',
  be: 60
}, {
  element: 'Pt',
  orbital: '4f7/2',
  be: 70
}, {
  element: 'Au',
  orbital: '4f7/2',
  be: 83
}, {
  element: 'Hg',
  orbital: '4f7/2',
  be: 99
}, {
  element: 'Tl',
  orbital: '4f7/2',
  be: 118
}, {
  element: 'Pb',
  orbital: '4f7/2',
  be: 138
}, {
  element: 'Bi',
  orbital: '4f7/2',
  be: 158
}, {
  element: 'Po',
  orbital: '4f7/2',
  be: 184
}, {
  element: 'At',
  orbital: '4f7/2',
  be: 210
}, {
  element: 'Rn',
  orbital: '4f7/2',
  be: 238
}, {
  element: 'Fr',
  orbital: '4f7/2',
  be: 268
}, {
  element: 'Ra',
  orbital: '4f7/2',
  be: 299
}, {
  element: 'Ac',
  orbital: '4f7/2',
  be: 319
}, {
  element: 'Th',
  orbital: '4f7/2',
  be: 335
}, {
  element: 'U',
  orbital: '4f7/2',
  be: 380
}, {
  element: 'Ce',
  orbital: '4f',
  be: 1
}, {
  element: 'Pr',
  orbital: '4f',
  be: 2
}, {
  element: 'Nd',
  orbital: '4f',
  be: 2
}, {
  element: 'Pm',
  orbital: '4f',
  be: 4
}, {
  element: 'Sm',
  orbital: '4f',
  be: 7
}, {
  element: 'Tb',
  orbital: '4f',
  be: 3
}, {
  element: 'Dy',
  orbital: '4f',
  be: 4
}, {
  element: 'Ho',
  orbital: '4f',
  be: 4
}, {
  element: 'Er',
  orbital: '4f',
  be: 4
}, {
  element: 'Tm',
  orbital: '4f',
  be: 5
}, {
  element: 'Yb',
  orbital: '4f',
  be: 6
}, {
  element: 'Lu',
  orbital: '4f',
  be: 7
}, {
  element: 'Hf',
  orbital: '4f',
  be: 18
}, {
  element: 'Ta',
  orbital: '4f',
  be: 25
}, {
  element: 'W',
  orbital: '4f',
  be: 34
}, {
  element: 'Re',
  orbital: '4f',
  be: 45
}, {
  element: 'Os',
  orbital: '4f',
  be: 50
}, {
  element: 'Ir',
  orbital: '4f',
  be: 60
}, {
  element: 'Pt',
  orbital: '4f',
  be: 70
}, {
  element: 'Au',
  orbital: '4f',
  be: 83
}, {
  element: 'Hg',
  orbital: '4f',
  be: 99
}, {
  element: 'Tl',
  orbital: '4f',
  be: 118
}, {
  element: 'Pb',
  orbital: '4f',
  be: 138
}, {
  element: 'Bi',
  orbital: '4f',
  be: 158
}, {
  element: 'Po',
  orbital: '4f',
  be: 184
}, {
  element: 'At',
  orbital: '4f',
  be: 210
}, {
  element: 'Rn',
  orbital: '4f',
  be: 238
}, {
  element: 'Fr',
  orbital: '4f',
  be: 268
}, {
  element: 'Ra',
  orbital: '4f',
  be: 299
}, {
  element: 'Ac',
  orbital: '4f',
  be: 319
}, {
  element: 'Th',
  orbital: '4f',
  be: 335
}, {
  element: 'U',
  orbital: '4f',
  be: 380
}, {
  element: 'Sn',
  orbital: '5s',
  be: 1
}, {
  element: 'Sb',
  orbital: '5s',
  be: 7
}, {
  element: 'Te',
  orbital: '5s',
  be: 12
}, {
  element: 'I',
  orbital: '5s',
  be: 14
}, {
  element: 'Xe',
  orbital: '5s',
  be: 18
}, {
  element: 'Cs',
  orbital: '5s',
  be: 23
}, {
  element: 'Ba',
  orbital: '5s',
  be: 40
}, {
  element: 'La',
  orbital: '5s',
  be: 33
}, {
  element: 'Ce',
  orbital: '5s',
  be: 38
}, {
  element: 'Pr',
  orbital: '5s',
  be: 38
}, {
  element: 'Nd',
  orbital: '5s',
  be: 38
}, {
  element: 'Pm',
  orbital: '5s',
  be: 38
}, {
  element: 'Sm',
  orbital: '5s',
  be: 39
}, {
  element: 'Eu',
  orbital: '5s',
  be: 32
}, {
  element: 'Gd',
  orbital: '5s',
  be: 36
}, {
  element: 'Tb',
  orbital: '5s',
  be: 40
}, {
  element: 'Dy',
  orbital: '5s',
  be: 63
}, {
  element: 'Ho',
  orbital: '5s',
  be: 51
}, {
  element: 'Er',
  orbital: '5s',
  be: 60
}, {
  element: 'Tm',
  orbital: '5s',
  be: 53
}, {
  element: 'Yb',
  orbital: '5s',
  be: 53
}, {
  element: 'Lu',
  orbital: '5s',
  be: 57
}, {
  element: 'Hf',
  orbital: '5s',
  be: 65
}, {
  element: 'Ta',
  orbital: '5s',
  be: 71
}, {
  element: 'W',
  orbital: '5s',
  be: 77
}, {
  element: 'Re',
  orbital: '5s',
  be: 83
}, {
  element: 'Os',
  orbital: '5s',
  be: 84
}, {
  element: 'Ir',
  orbital: '5s',
  be: 96
}, {
  element: 'Pt',
  orbital: '5s',
  be: 102
}, {
  element: 'Au',
  orbital: '5s',
  be: 108
}, {
  element: 'Hg',
  orbital: '5s',
  be: 120
}, {
  element: 'Tl',
  orbital: '5s',
  be: 137
}, {
  element: 'Pb',
  orbital: '5s',
  be: 148
}, {
  element: 'Bi',
  orbital: '5s',
  be: 160
}, {
  element: 'Po',
  orbital: '5s',
  be: 177
}, {
  element: 'At',
  orbital: '5s',
  be: 195
}, {
  element: 'Rn',
  orbital: '5s',
  be: 214
}, {
  element: 'Fr',
  orbital: '5s',
  be: 234
}, {
  element: 'Ra',
  orbital: '5s',
  be: 254
}, {
  element: 'Ac',
  orbital: '5s',
  be: 272
}, {
  element: 'Th',
  orbital: '5s',
  be: 290
}, {
  element: 'In',
  orbital: '5p1/2',
  be: 1
}, {
  element: 'Sn',
  orbital: '5p1/2',
  be: 1
}, {
  element: 'Sb',
  orbital: '5p1/2',
  be: 2
}, {
  element: 'Te',
  orbital: '5p1/2',
  be: 2
}, {
  element: 'I',
  orbital: '5p1/2',
  be: 3
}, {
  element: 'Xe',
  orbital: '5p1/2',
  be: 7
}, {
  element: 'Cs',
  orbital: '5p1/2',
  be: 13
}, {
  element: 'Ba',
  orbital: '5p1/2',
  be: 17
}, {
  element: 'La',
  orbital: '5p1/2',
  be: 15
}, {
  element: 'Ce',
  orbital: '5p1/2',
  be: 20
}, {
  element: 'Pr',
  orbital: '5p1/2',
  be: 23
}, {
  element: 'Nd',
  orbital: '5p1/2',
  be: 22
}, {
  element: 'Pm',
  orbital: '5p1/2',
  be: 22
}, {
  element: 'Sm',
  orbital: '5p1/2',
  be: 22
}, {
  element: 'Eu',
  orbital: '5p1/2',
  be: 22
}, {
  element: 'Gd',
  orbital: '5p1/2',
  be: 21
}, {
  element: 'Tb',
  orbital: '5p1/2',
  be: 26
}, {
  element: 'Dy',
  orbital: '5p1/2',
  be: 26
}, {
  element: 'Ho',
  orbital: '5p1/2',
  be: 20
}, {
  element: 'Er',
  orbital: '5p1/2',
  be: 29
}, {
  element: 'Tm',
  orbital: '5p1/2',
  be: 32
}, {
  element: 'Yb',
  orbital: '5p1/2',
  be: 23
}, {
  element: 'Lu',
  orbital: '5p1/2',
  be: 28
}, {
  element: 'Hf',
  orbital: '5p1/2',
  be: 38
}, {
  element: 'Ta',
  orbital: '5p1/2',
  be: 45
}, {
  element: 'W',
  orbital: '5p1/2',
  be: 47
}, {
  element: 'Re',
  orbital: '5p1/2',
  be: 46
}, {
  element: 'Os',
  orbital: '5p1/2',
  be: 58
}, {
  element: 'Ir',
  orbital: '5p1/2',
  be: 63
}, {
  element: 'Pt',
  orbital: '5p1/2',
  be: 66
}, {
  element: 'Au',
  orbital: '5p1/2',
  be: 72
}, {
  element: 'Hg',
  orbital: '5p1/2',
  be: 81
}, {
  element: 'Tl',
  orbital: '5p1/2',
  be: 100
}, {
  element: 'Pb',
  orbital: '5p1/2',
  be: 105
}, {
  element: 'Bi',
  orbital: '5p1/2',
  be: 117
}, {
  element: 'Po',
  orbital: '5p1/2',
  be: 132
}, {
  element: 'At',
  orbital: '5p1/2',
  be: 148
}, {
  element: 'Rn',
  orbital: '5p1/2',
  be: 164
}, {
  element: 'Fr',
  orbital: '5p1/2',
  be: 182
}, {
  element: 'Ra',
  orbital: '5p1/2',
  be: 200
}, {
  element: 'Ac',
  orbital: '5p1/2',
  be: 215
}, {
  element: 'Th',
  orbital: '5p1/2',
  be: 229
}, {
  element: 'In',
  orbital: '5p3/2',
  be: 1
}, {
  element: 'Sn',
  orbital: '5p3/2',
  be: 1
}, {
  element: 'Sb',
  orbital: '5p3/2',
  be: 2
}, {
  element: 'Te',
  orbital: '5p3/2',
  be: 2
}, {
  element: 'I',
  orbital: '5p3/2',
  be: 3
}, {
  element: 'Xe',
  orbital: '5p3/2',
  be: 7
}, {
  element: 'Cs',
  orbital: '5p3/2',
  be: 12
}, {
  element: 'Ba',
  orbital: '5p3/2',
  be: 15
}, {
  element: 'La',
  orbital: '5p3/2',
  be: 15
}, {
  element: 'Ce',
  orbital: '5p3/2',
  be: 20
}, {
  element: 'Pr',
  orbital: '5p3/2',
  be: 23
}, {
  element: 'Nd',
  orbital: '5p3/2',
  be: 22
}, {
  element: 'Pm',
  orbital: '5p3/2',
  be: 22
}, {
  element: 'Sm',
  orbital: '5p3/2',
  be: 22
}, {
  element: 'Eu',
  orbital: '5p3/2',
  be: 22
}, {
  element: 'Gd',
  orbital: '5p3/2',
  be: 21
}, {
  element: 'Tb',
  orbital: '5p3/2',
  be: 26
}, {
  element: 'Dy',
  orbital: '5p3/2',
  be: 26
}, {
  element: 'Ho',
  orbital: '5p3/2',
  be: 20
}, {
  element: 'Er',
  orbital: '5p3/2',
  be: 29
}, {
  element: 'Tm',
  orbital: '5p3/2',
  be: 32
}, {
  element: 'Yb',
  orbital: '5p3/2',
  be: 23
}, {
  element: 'Lu',
  orbital: '5p3/2',
  be: 28
}, {
  element: 'Hf',
  orbital: '5p3/2',
  be: 31
}, {
  element: 'Ta',
  orbital: '5p3/2',
  be: 37
}, {
  element: 'W',
  orbital: '5p3/2',
  be: 37
}, {
  element: 'Re',
  orbital: '5p3/2',
  be: 35
}, {
  element: 'Os',
  orbital: '5p3/2',
  be: 46
}, {
  element: 'Ir',
  orbital: '5p3/2',
  be: 51
}, {
  element: 'Pt',
  orbital: '5p3/2',
  be: 51
}, {
  element: 'Au',
  orbital: '5p3/2',
  be: 54
}, {
  element: 'Hg',
  orbital: '5p3/2',
  be: 58
}, {
  element: 'Tl',
  orbital: '5p3/2',
  be: 76
}, {
  element: 'Pb',
  orbital: '5p3/2',
  be: 86
}, {
  element: 'Bi',
  orbital: '5p3/2',
  be: 93
}, {
  element: 'Po',
  orbital: '5p3/2',
  be: 104
}, {
  element: 'At',
  orbital: '5p3/2',
  be: 115
}, {
  element: 'Rn',
  orbital: '5p3/2',
  be: 127
}, {
  element: 'Fr',
  orbital: '5p3/2',
  be: 140
}, {
  element: 'Ra',
  orbital: '5p3/2',
  be: 153
}, {
  element: 'Ac',
  orbital: '5p3/2',
  be: 167
}, {
  element: 'Th',
  orbital: '5p3/2',
  be: 182
}, {
  element: 'In',
  orbital: '5p',
  be: 1
}, {
  element: 'Sn',
  orbital: '5p',
  be: 1
}, {
  element: 'Sb',
  orbital: '5p',
  be: 2
}, {
  element: 'Te',
  orbital: '5p',
  be: 2
}, {
  element: 'I',
  orbital: '5p',
  be: 3
}, {
  element: 'Xe',
  orbital: '5p',
  be: 7
}, {
  element: 'Cs',
  orbital: '5p',
  be: 12
}, {
  element: 'Ba',
  orbital: '5p',
  be: 15
}, {
  element: 'La',
  orbital: '5p',
  be: 15
}, {
  element: 'Ce',
  orbital: '5p',
  be: 20
}, {
  element: 'Pr',
  orbital: '5p',
  be: 23
}, {
  element: 'Nd',
  orbital: '5p',
  be: 22
}, {
  element: 'Pm',
  orbital: '5p',
  be: 22
}, {
  element: 'Sm',
  orbital: '5p',
  be: 22
}, {
  element: 'Eu',
  orbital: '5p',
  be: 22
}, {
  element: 'Gd',
  orbital: '5p',
  be: 21
}, {
  element: 'Tb',
  orbital: '5p',
  be: 26
}, {
  element: 'Dy',
  orbital: '5p',
  be: 26
}, {
  element: 'Ho',
  orbital: '5p',
  be: 20
}, {
  element: 'Er',
  orbital: '5p',
  be: 29
}, {
  element: 'Tm',
  orbital: '5p',
  be: 32
}, {
  element: 'Yb',
  orbital: '5p',
  be: 23
}, {
  element: 'Lu',
  orbital: '5p',
  be: 28
}, {
  element: 'Hf',
  orbital: '5p',
  be: 31
}, {
  element: 'Ta',
  orbital: '5p',
  be: 37
}, {
  element: 'W',
  orbital: '5p',
  be: 37
}, {
  element: 'Re',
  orbital: '5p',
  be: 35
}, {
  element: 'Os',
  orbital: '5p',
  be: 46
}, {
  element: 'Ir',
  orbital: '5p',
  be: 51
}, {
  element: 'Pt',
  orbital: '5p',
  be: 51
}, {
  element: 'Au',
  orbital: '5p',
  be: 54
}, {
  element: 'Hg',
  orbital: '5p',
  be: 58
}, {
  element: 'Tl',
  orbital: '5p',
  be: 76
}, {
  element: 'Pb',
  orbital: '5p',
  be: 86
}, {
  element: 'Bi',
  orbital: '5p',
  be: 93
}, {
  element: 'Po',
  orbital: '5p',
  be: 104
}, {
  element: 'At',
  orbital: '5p',
  be: 115
}, {
  element: 'Rn',
  orbital: '5p',
  be: 127
}, {
  element: 'Fr',
  orbital: '5p',
  be: 140
}, {
  element: 'Ra',
  orbital: '5p',
  be: 153
}, {
  element: 'Ac',
  orbital: '5p',
  be: 167
}, {
  element: 'Th',
  orbital: '5p',
  be: 182
}, {
  element: 'Lu',
  orbital: '5d3/2',
  be: 5
}, {
  element: 'Hf',
  orbital: '5d3/2',
  be: 7
}, {
  element: 'Ta',
  orbital: '5d3/2',
  be: 6
}, {
  element: 'W',
  orbital: '5d3/2',
  be: 6
}, {
  element: 'Re',
  orbital: '5d3/2',
  be: 4
}, {
  element: 'Ir',
  orbital: '5d3/2',
  be: 4
}, {
  element: 'Pt',
  orbital: '5d3/2',
  be: 2
}, {
  element: 'Au',
  orbital: '5d3/2',
  be: 3
}, {
  element: 'Hg',
  orbital: '5d3/2',
  be: 7
}, {
  element: 'Tl',
  orbital: '5d3/2',
  be: 16
}, {
  element: 'Pb',
  orbital: '5d3/2',
  be: 22
}, {
  element: 'Bi',
  orbital: '5d3/2',
  be: 27
}, {
  element: 'Po',
  orbital: '5d3/2',
  be: 31
}, {
  element: 'At',
  orbital: '5d3/2',
  be: 40
}, {
  element: 'Rn',
  orbital: '5d3/2',
  be: 48
}, {
  element: 'Fr',
  orbital: '5d3/2',
  be: 58
}, {
  element: 'Ra',
  orbital: '5d3/2',
  be: 68
}, {
  element: 'Ac',
  orbital: '5d3/2',
  be: 80
}, {
  element: 'Th',
  orbital: '5d3/2',
  be: 95
}, {
  element: 'U',
  orbital: '5d3/2',
  be: 104
}, {
  element: 'Lu',
  orbital: '5d5/2',
  be: 5
}, {
  element: 'Hf',
  orbital: '5d5/2',
  be: 7
}, {
  element: 'Ta',
  orbital: '5d5/2',
  be: 6
}, {
  element: 'W',
  orbital: '5d5/2',
  be: 6
}, {
  element: 'Re',
  orbital: '5d5/2',
  be: 4
}, {
  element: 'Ir',
  orbital: '5d5/2',
  be: 4
}, {
  element: 'Pt',
  orbital: '5d5/2',
  be: 2
}, {
  element: 'Au',
  orbital: '5d5/2',
  be: 3
}, {
  element: 'Hg',
  orbital: '5d5/2',
  be: 7
}, {
  element: 'Tl',
  orbital: '5d5/2',
  be: 13
}, {
  element: 'Pb',
  orbital: '5d5/2',
  be: 20
}, {
  element: 'Bi',
  orbital: '5d5/2',
  be: 25
}, {
  element: 'Po',
  orbital: '5d5/2',
  be: 31
}, {
  element: 'At',
  orbital: '5d5/2',
  be: 40
}, {
  element: 'Rn',
  orbital: '5d5/2',
  be: 48
}, {
  element: 'Fr',
  orbital: '5d5/2',
  be: 58
}, {
  element: 'Ra',
  orbital: '5d5/2',
  be: 68
}, {
  element: 'Ac',
  orbital: '5d5/2',
  be: 80
}, {
  element: 'Th',
  orbital: '5d5/2',
  be: 88
}, {
  element: 'U',
  orbital: '5d5/2',
  be: 96
}, {
  element: 'Lu',
  orbital: '5d',
  be: 5
}, {
  element: 'Hf',
  orbital: '5d',
  be: 7
}, {
  element: 'Ta',
  orbital: '5d',
  be: 6
}, {
  element: 'W',
  orbital: '5d',
  be: 6
}, {
  element: 'Re',
  orbital: '5d',
  be: 4
}, {
  element: 'Ir',
  orbital: '5d',
  be: 4
}, {
  element: 'Pt',
  orbital: '5d',
  be: 2
}, {
  element: 'Au',
  orbital: '5d',
  be: 3
}, {
  element: 'Hg',
  orbital: '5d',
  be: 7
}, {
  element: 'Tl',
  orbital: '5d',
  be: 13
}, {
  element: 'Pb',
  orbital: '5d',
  be: 20
}, {
  element: 'Bi',
  orbital: '5d',
  be: 25
}, {
  element: 'Po',
  orbital: '5d',
  be: 31
}, {
  element: 'At',
  orbital: '5d',
  be: 40
}, {
  element: 'Rn',
  orbital: '5d',
  be: 48
}, {
  element: 'Fr',
  orbital: '5d',
  be: 58
}, {
  element: 'Ra',
  orbital: '5d',
  be: 68
}, {
  element: 'Ac',
  orbital: '5d',
  be: 80
}, {
  element: 'Th',
  orbital: '5d',
  be: 88
}, {
  element: 'U',
  orbital: '5d',
  be: 96
}, {
  element: 'Pb',
  orbital: '6s',
  be: 3
}, {
  element: 'Bi',
  orbital: '6s',
  be: 8
}, {
  element: 'Po',
  orbital: '6s',
  be: 12
}, {
  element: 'At',
  orbital: '6s',
  be: 18
}, {
  element: 'Rn',
  orbital: '6s',
  be: 26
}, {
  element: 'Fr',
  orbital: '6s',
  be: 34
}, {
  element: 'Ra',
  orbital: '6s',
  be: 44
}, {
  element: 'Th',
  orbital: '6s',
  be: 60
}, {
  element: 'Pb',
  orbital: '6p1/2',
  be: 1
}, {
  element: 'Bi',
  orbital: '6p1/2',
  be: 3
}, {
  element: 'Po',
  orbital: '6p1/2',
  be: 5
}, {
  element: 'At',
  orbital: '6p1/2',
  be: 8
}, {
  element: 'Rn',
  orbital: '6p1/2',
  be: 11
}, {
  element: 'Fr',
  orbital: '6p1/2',
  be: 15
}, {
  element: 'Ra',
  orbital: '6p1/2',
  be: 19
}, {
  element: 'Th',
  orbital: '6p1/2',
  be: 49
}, {
  element: 'U',
  orbital: '6p1/2',
  be: 28.9
}, {
  element: 'Pb',
  orbital: '6p3/2',
  be: 1
}, {
  element: 'Bi',
  orbital: '6p3/2',
  be: 3
}, {
  element: 'Po',
  orbital: '6p3/2',
  be: 5
}, {
  element: 'At',
  orbital: '6p3/2',
  be: 8
}, {
  element: 'Rn',
  orbital: '6p3/2',
  be: 11
}, {
  element: 'Fr',
  orbital: '6p3/2',
  be: 15
}, {
  element: 'Ra',
  orbital: '6p3/2',
  be: 19
}, {
  element: 'Th',
  orbital: '6p3/2',
  be: 43
}, {
  element: 'U',
  orbital: '6p3/2',
  be: 19
}, {
  element: 'Pb',
  orbital: '6p',
  be: 1
}, {
  element: 'Bi',
  orbital: '6p',
  be: 3
}, {
  element: 'Po',
  orbital: '6p',
  be: 5
}, {
  element: 'At',
  orbital: '6p',
  be: 8
}, {
  element: 'Rn',
  orbital: '6p',
  be: 11
}, {
  element: 'Fr',
  orbital: '6p',
  be: 15
}, {
  element: 'Ra',
  orbital: '6p',
  be: 19
}, {
  element: 'Th',
  orbital: '6p',
  be: 43
}, {
  element: 'U',
  orbital: '6p',
  be: 19
}];

let xAtomicNumber = 0;
/**
 * Returns the atomic number of the X atom
 * @param {import('openchemlib').Molecule} molecule - An instance of a molecule
 * @returns
 */
function getXAtomicNumber(molecule) {
  if (!xAtomicNumber) {
    const OCL = molecule.getOCL();
    xAtomicNumber = OCL.Molecule.getAtomicNoFromLabel('X', OCL.Molecule.cPseudoAtomX);
  }
  return xAtomicNumber;
}

/**
 * Check if a specific atom is a sp3 carbon
 * @param {import('openchemlib').Molecule} molecule
 * @param {number} atomID
 */
function isCsp3(molecule, atomID) {
  if (molecule.getAtomicNo(atomID) !== 6) return false;
  if (molecule.getAtomCharge(atomID) !== 0) return false;
  if (molecule.getImplicitHydrogens(atomID) + molecule.getConnAtoms(atomID) !== 4) {
    return false;
  }
  return true;
}

/**
 * This method put all the chiral centers in the molecule in the same group and racemic
 * @param {import('openchemlib').Molecule} molecule - An instance of a molecule
 * @param {object} [options={}]
 * @param {object} [options.OCL] - openchemlib library
 */
function makeRacemic(molecule) {
  const {
    Molecule
  } = molecule.getOCL();
  // if we don't calculate this we have 2 epimers
  molecule.ensureHelperArrays(Molecule.cHelperCIP);
  // we need to make one group "AND" for chiral (to force to racemic, this means diastereotopic and not enantiotopic)
  for (let i = 0; i < molecule.getAllAtoms(); i++) {
    if (molecule.getAtomParity(i) !== Molecule.cAtomParityNone) {
      molecule.setAtomESR(i, Molecule.cESRTypeAnd, 0); // changed to group 0; TLS 9.Nov.2015
    }
  }
  // after the change we need to recalculate the CIP
  molecule.ensureHelperArrays(Molecule.cHelperCIP);
}

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

const FULL_HOSE_CODE = 1;
const HOSE_CODE_CUT_C_SP3_SP3 = 2;
/**
 * Returns an array of hose code fragments for the specified molecule.
 * @param molecule - The OCL molecule to process.
 * @param options - Options for generating hose codes.
 * @returns An array of hose code fragments.
 */
function getHoseCodesForAtomsAsFragments(molecule, options = {}) {
  const OCL = molecule.getOCL();
  const {
    allowedCustomLabels,
    minSphereSize = 0,
    maxSphereSize = 4,
    kind = FULL_HOSE_CODE,
    tagAtoms = [],
    tagAtomFct = tagAtom
  } = options;
  const rootAtoms = options.rootAtoms ? options.rootAtoms.slice() : [];
  molecule = molecule.getCompactCopy();
  if (tagAtoms.length > 0) {
    internalTagAtoms(molecule, tagAtoms, rootAtoms, tagAtomFct);
  } else {
    // this force reordering of atoms in order to have hydrogens at the end
    molecule.ensureHelperArrays(OCL.Molecule.cHelperNeighbours);
  }
  if (rootAtoms.length === 0) {
    for (let j = 0; j < molecule.getAllAtoms(); j++) {
      if (allowedCustomLabels?.includes(molecule.getAtomCustomLabel(j) ?? '') || molecule.getAtomCustomLabel(j)) {
        rootAtoms.push(j);
      }
    }
  }
  const fragments = [];
  // keep track of the atoms when creating the fragment
  const mappings = [];
  let min = 0;
  let max = 0;
  const atomMask = new Array(molecule.getAllAtoms()).fill(false);
  const atomList = new Array(molecule.getAllAtoms());
  for (let sphere = 0; sphere <= maxSphereSize; sphere++) {
    if (max === 0) {
      for (const rootAtom of rootAtoms) {
        atomList[max] = rootAtom;
        atomMask[rootAtom] = true;
        max++;
      }
    } else {
      let newMax = max;
      for (let i = min; i < max; i++) {
        const atom = atomList[i];
        for (let j = 0; j < molecule.getAllConnAtoms(atom); j++) {
          const connAtom = molecule.getConnAtom(atom, j);
          if (!atomMask[connAtom]) {
            switch (kind) {
              case FULL_HOSE_CODE:
                atomMask[connAtom] = true;
                atomList[newMax++] = connAtom;
                break;
              case HOSE_CODE_CUT_C_SP3_SP3:
                if (!(isCsp3(molecule, atom) && isCsp3(molecule, connAtom))) {
                  atomMask[connAtom] = true;
                  atomList[newMax++] = connAtom;
                }
                break;
              default:
                throw new Error('getHoseCoesForAtom unknown kind');
            }
          }
        }
      }
      min = max;
      max = newMax;
    }
    if (sphere >= minSphereSize) {
      const fragment = new OCL.Molecule(0, 0);
      molecule.copyMoleculeByAtoms(fragment, atomMask, true, mappings);
      // we using atomMapNo field in order to keep track of the original atom number even if we remove hydrogens
      for (let i = 0; i < fragment.getAllAtoms(); i++) {
        fragment.setAtomMapNo(i, mappings.indexOf(i) + 1);
      }
      fragment.removeExplicitHydrogens();
      makeRacemic(fragment);
      // we encode atom characteristics in the query features
      addQueryFeaturesAndRemoveMapNo(fragment, molecule);
      fragments.push(fragment);
    }
  }
  return fragments;
}
/**
 * If the atom is not an halogen, X or an hydrogen
 * we add query features to the atom
 * This includes aromaticity, ring size, number of hydrogens
 * @param fragment
 * @param molecule
 */
function addQueryFeaturesAndRemoveMapNo(fragment, molecule) {
  const Molecule = molecule.getOCL().Molecule;
  for (let i = 0; i < fragment.getAllAtoms(); i++) {
    const mapping = fragment.getAtomMapNo(i) - 1;
    fragment.setAtomMapNo(i, 0);
    if ([1, 9, 17, 35, 53, getXAtomicNumber(molecule)].includes(fragment.getAtomicNo(i))) {
      continue;
    }
    // aromaticity
    const isAromatic = molecule.isAromaticAtom(mapping);
    if (isAromatic) {
      fragment.setAtomQueryFeature(i, Molecule.cAtomQFAromatic, true);
      fragment.setAtomQueryFeature(i, Molecule.cAtomQFNotAromatic, false);
    } else {
      fragment.setAtomQueryFeature(i, Molecule.cAtomQFAromatic, false);
      fragment.setAtomQueryFeature(i, Molecule.cAtomQFNotAromatic, true);
    }
    // cycles
    const smallestRing = molecule.getAtomRingSize(mapping);
    switch (smallestRing) {
      case 0:
        break;
      case 3:
        fragment.setAtomQueryFeature(i, Molecule.cAtomQFRingSize3, true);
        break;
      case 4:
        fragment.setAtomQueryFeature(i, Molecule.cAtomQFRingSize4, true);
        break;
      case 5:
        fragment.setAtomQueryFeature(i, Molecule.cAtomQFRingSize5, true);
        break;
      case 6:
        fragment.setAtomQueryFeature(i, Molecule.cAtomQFRingSize6, true);
        break;
      case 7:
        fragment.setAtomQueryFeature(i, Molecule.cAtomQFRingSize7, true);
        break;
      default:
        fragment.setAtomQueryFeature(i, Molecule.cAtomQFRingSizeLarge, true);
    }
    const nbHydrogens = molecule.getAllHydrogens(mapping);
    if (nbHydrogens === 0) {
      fragment.setAtomQueryFeature(i, Molecule.cAtomQFNot0Hydrogen, false);
    } else {
      fragment.setAtomQueryFeature(i, Molecule.cAtomQFNot0Hydrogen, true);
    }
    if (nbHydrogens === 1) {
      fragment.setAtomQueryFeature(i, Molecule.cAtomQFNot1Hydrogen, false);
    } else {
      fragment.setAtomQueryFeature(i, Molecule.cAtomQFNot1Hydrogen, true);
    }
    if (nbHydrogens === 2) {
      fragment.setAtomQueryFeature(i, Molecule.cAtomQFNot2Hydrogen, false);
    } else {
      fragment.setAtomQueryFeature(i, Molecule.cAtomQFNot2Hydrogen, true);
    }
    if (nbHydrogens === 3) {
      fragment.setAtomQueryFeature(i, Molecule.cAtomQFNot3Hydrogen, false);
    } else {
      fragment.setAtomQueryFeature(i, Molecule.cAtomQFNot3Hydrogen, true);
    }
  }
}
// tagging atoms may change the order of the atoms because hydrogens must be at the end of the file
// in order to remember the rootAtoms we will tag before
function internalTagAtoms(molecule, tagAtoms, rootAtoms, tagAtomFct) {
  const OCL = molecule.getOCL();
  if (tagAtoms) {
    for (let i = 0; i < molecule.getAllAtoms(); i++) {
      molecule.setAtomMapNo(i, i + 1);
    }
    if (tagAtoms.length > 0) {
      for (const atom of tagAtoms) {
        tagAtomFct(molecule, atom);
      }
    }
  }
  // this force reordering of atoms in order to have hydrogens at the end
  molecule.ensureHelperArrays(OCL.Molecule.cHelperNeighbours);
  if (rootAtoms.length > 0) {
    const mapping = new Int32Array(molecule.getAllAtoms());
    for (let i = 0; i < molecule.getAllAtoms(); i++) {
      mapping[molecule.getAtomMapNo(i) - 1] = i;
    }
    for (let i = 0; i < rootAtoms.length; i++) {
      rootAtoms[i] = mapping[rootAtoms[i]];
    }
  }
}

/**
 * Creates a compact copy of the molecule without custom labels.
 * We don't want that custom labels interfere with hose code and diaID generation.
 * @param molecule
 * @returns
 */
function getCompactCopyWithoutCustomLabels(molecule) {
  const tempMolecule = molecule.getCompactCopy();
  for (let i = 0; i < tempMolecule.getAllAtoms(); i++) {
    tempMolecule.setAtomCustomLabel(i, '');
  }
  return tempMolecule;
}

/**
 * Returns an array of strings (idCodes) specified molecule. Each string corresponds to a
 * hose code. By default it will calculate the hose codes for sphere 0 to 4 and will reuse
 * the existing tagged atoms.
 * @param molecule - The OCL molecule to process.
 * @param options - Options for generating hose codes.
 * @returns An array of hose code strings.
 */
function getHoseCodesForAtomsAsStrings(molecule, options = {}) {
  const fragments = getHoseCodesForAtomsAsFragments(molecule, options);
  const OCL = molecule.getOCL();
  const hoses = [];
  for (const fragment of fragments) {
    hoses.push(fragment.getCanonizedIDCode(OCL.Molecule.CANONIZER_ENCODE_ATOM_CUSTOM_LABELS));
  }
  return hoses;
}

/**
 * Returns the atoms that are chiral or pseudo chiral.
 * There could be some issues if the original molecule lacks chiral bonds.
 * The function will add them and this could lead to some issues in the case of pseudochiral atoms.
 * @param {import('openchemlib').Molecule} molecule
 * @returns {number[]}
 */
function getChiralOrHeterotopicCarbons(molecule) {
  const {
    Molecule
  } = molecule.getOCL();
  const xAtomicNumber = getXAtomicNumber(molecule);
  const internalMolecule = molecule.getCompactCopy();
  // hydrogens may be diastereotopic, we need to add them
  internalMolecule.addImplicitHydrogens();
  for (let i = 0; i < internalMolecule.getAllAtoms(); i++) {
    // hydrogens are not taken into account during canonization, we need to change them with an atom with a valence of 1
    if (internalMolecule.getAtomicNo(i) === 1) {
      internalMolecule.setAtomicNo(i, xAtomicNumber);
    }
  }
  addPossibleChiralBonds(internalMolecule);
  internalMolecule.ensureHelperArrays(Molecule.cHelperSymmetryStereoHeterotopicity);
  const atoms = [];
  for (let i = 0; i < molecule.getAllAtoms(); i++) {
    if (internalMolecule.getAtomicNo(i) === xAtomicNumber) {
      continue;
    }
    if (molecule.getAtomicNo(i) !== internalMolecule.getAtomicNo(i)) {
      throw new Error('getChiralOrHeterotopicCarbons: mismatching atomic numbers');
    }
    if (internalMolecule.getAtomicNo(i) !== 6) {
      continue;
    }
    const neighbourSymmetries = getNeighbourSymmetries(internalMolecule, i);
    if (neighbourSymmetries.length === 4) {
      atoms.push(i);
    }
  }
  return atoms;
}
function addPossibleChiralBonds(molecule) {
  const {
    Molecule
  } = molecule.getOCL();
  molecule.ensureHelperArrays(Molecule.cHelperSymmetryStereoHeterotopicity);
  for (let i = 0; i < molecule.getAtoms(); i++) {
    if (molecule.getAtomicNo(i) !== 6) continue;
    if (molecule.getStereoBond(i) >= 0) continue;
    const neighbourSymmetries = getNeighbourSymmetries(molecule, i);
    if (neighbourSymmetries.length <= 2) continue;
    const stereoBond = molecule.getAtomPreferredStereoBond(i);
    if (stereoBond !== -1) {
      molecule.setBondType(stereoBond, Molecule.cBondTypeUp);
      if (molecule.getBondAtom(1, stereoBond) === i) {
        const connAtom = molecule.getBondAtom(0, stereoBond);
        molecule.setBondAtom(0, stereoBond, i);
        molecule.setBondAtom(1, stereoBond, connAtom);
      }
      // To me it seems that we have to add all stereo centers into AND group 0. TLS 9.Nov.2015
      molecule.setAtomESR(i, Molecule.cESRTypeAnd, 0);
    }
  }
}
function getNeighbourSymmetries(molecule, iAtom) {
  const neighbourSymmetries = [];
  for (let j = 0; j < molecule.getAllConnAtoms(iAtom); j++) {
    const connAtom = molecule.getConnAtom(iAtom, j);
    const symmetryRank = molecule.getSymmetryRank(connAtom);
    if (!neighbourSymmetries.includes(symmetryRank)) {
      neighbourSymmetries.push(molecule.getSymmetryRank(connAtom));
    }
  }
  return neighbourSymmetries;
}

/**
 * This function will add missing chiral bonds on carbons ensure that all enantiotopic
 * or diastereotopic atoms can be identified uniquely
 * @param {import('openchemlib').Molecule} molecule
 * @param {object} [options={}]
 * @param {number} [options.esrType=Molecule.cESRTypeAnd]
 * @param {boolean} [options.atLeastThreeAtoms=true] - if true, only carbons with at least three atoms will be considered
 */
function ensureHeterotopicChiralBonds(molecule, options = {}) {
  const {
    Molecule
  } = molecule.getOCL();
  const {
    esrType = Molecule.cESRTypeAnd,
    atLeastThreeAtoms = true
  } = options;
  molecule.ensureHelperArrays(Molecule.cHelperBitNeighbours);
  const heterotopicCarbons = getChiralOrHeterotopicCarbons(molecule);
  for (const i of heterotopicCarbons) {
    if (atLeastThreeAtoms && molecule.getAllConnAtoms(i) < 3) continue;
    if (molecule.getStereoBond(i) === -1) {
      const stereoBond = molecule.getAtomPreferredStereoBond(i);
      if (stereoBond !== -1) {
        molecule.setBondType(stereoBond, Molecule.cBondTypeUp);
        if (molecule.getBondAtom(1, stereoBond) === i) {
          const connAtom = molecule.getBondAtom(0, stereoBond);
          molecule.setBondAtom(0, stereoBond, i);
          molecule.setBondAtom(1, stereoBond, connAtom);
        }
        // To me it seems that we have to add all stereo centers into AND group 0. TLS 9.Nov.2015
        molecule.setAtomESR(i, esrType, 0);
      }
    }
  }
}

/**
 * Returns an array of symmetry ranks.
 * @param {import('openchemlib').Molecule} molecule - An instance of a molecule
 * @returns
 */
function getSymmetryRanks(molecule) {
  const {
    Molecule
  } = molecule.getOCL();
  const xAtomNumber = getXAtomicNumber(molecule);
  // most of the molecules have some symmetry
  const internalMolecule = molecule.getCompactCopy();
  for (let i = 0; i < internalMolecule.getAllAtoms(); i++) {
    // hydrogens are not taken into account during canonization, we need to change them with an atom with a valence of 1
    if (internalMolecule.getAtomicNo(i) === 1) {
      internalMolecule.setAtomicNo(i, xAtomNumber);
    }
  }
  internalMolecule.ensureHelperArrays(Molecule.cHelperSymmetryStereoHeterotopicity);
  const symmetryRanks = [];
  for (let i = 0; i < molecule.getAllAtoms(); i++) {
    symmetryRanks.push(internalMolecule.getSymmetryRank(i));
  }
  return symmetryRanks;
}

/**
 * Returns an array of diastereotopic ID (as idCode)
 * @param {import('openchemlib').Molecule} molecule
 */
function getDiastereotopicAtomIDs(molecule) {
  const {
    Molecule
  } = molecule.getOCL();
  ensureHeterotopicChiralBonds(molecule);
  const symmetryRanks = getSymmetryRanks(molecule);
  const numberAtoms = molecule.getAllAtoms();
  const ids = [];
  const cache = {};
  for (let iAtom = 0; iAtom < numberAtoms; iAtom++) {
    const rank = symmetryRanks[iAtom];
    if (rank && cache[rank]) {
      ids[iAtom] = cache[rank];
      continue;
    }
    const tempMolecule = getCompactCopyWithoutCustomLabels(molecule);
    tagAtom(tempMolecule, iAtom);
    makeRacemic(tempMolecule);
    // We need to ensure the helper array in order to get correctly the result of racemisation
    ids[iAtom] = tempMolecule.getCanonizedIDCode(Molecule.CANONIZER_ENCODE_ATOM_CUSTOM_LABELS);
    cache[rank] = ids[iAtom];
  }
  return ids;
}

/**
 *
 * @param {import('openchemlib').Molecule} molecule
 */
function getDiastereotopicAtomIDsAndH(molecule) {
  const OCL = molecule.getOCL();
  molecule = molecule.getCompactCopy();
  molecule.addImplicitHydrogens();
  molecule.ensureHelperArrays(OCL.Molecule.cHelperNeighbours);
  const diaIDs = getDiastereotopicAtomIDs(molecule);
  const newDiaIDs = [];
  for (let i = 0; i < diaIDs.length; i++) {
    const diaID = diaIDs[i];
    const newDiaID = {
      oclID: diaID,
      hydrogenOCLIDs: [],
      nbHydrogens: 0
    };
    if (molecule.getAtomicNo(i) === 1) {
      const atom = molecule.getConnAtom(i, 0);
      newDiaID.heavyAtom = diaIDs[atom];
    }
    for (let j = 0; j < molecule.getAllConnAtoms(i); j++) {
      const atom = molecule.getConnAtom(i, j);
      if (molecule.getAtomicNo(atom) === 1) {
        newDiaID.nbHydrogens++;
        if (!newDiaID.hydrogenOCLIDs.includes(diaIDs[atom])) {
          newDiaID.hydrogenOCLIDs.push(diaIDs[atom]);
        }
      }
    }
    newDiaIDs.push(newDiaID);
  }
  return newDiaIDs;
}

/**
 * Returns the hose codes for all atoms in the molecule
 * @param {*} molecule
 * @param {object} [options={}]
 * @param {string[]} [options.atomLabels]
 * @param {number} [options.minSphereSize=0]
 * @param {number} [options.maxSphereSize=4]
 * @returns
 */
function getHoseCodes(molecule, options = {}) {
  const {
    atomLabels,
    minSphereSize,
    maxSphereSize
  } = options;
  const {
    Molecule
  } = molecule.getOCL();
  const atomicNumbers = atomLabels?.map(label => Molecule.getAtomicNoFromLabel(label));
  const internalMolecule = getCompactCopyWithoutCustomLabels(molecule);
  internalMolecule.addImplicitHydrogens();
  ensureHeterotopicChiralBonds(internalMolecule);
  const hoses = [];
  for (let i = 0; i < internalMolecule.getAllAtoms(); i++) {
    if (atomicNumbers && !atomicNumbers.includes(internalMolecule.getAtomicNo(i))) {
      hoses.push(undefined);
    } else {
      const tempMolecule = internalMolecule.getCompactCopy();
      tagAtom(tempMolecule, i);
      hoses.push(getHoseCodesForAtomsAsStrings(tempMolecule, {
        minSphereSize,
        maxSphereSize
      }));
    }
  }
  return hoses;
}

/**
 * Create a polymer from a unit, alpha and omega groups
 * A unit must contain a R1 and a R2
 * An alpha end group must contain a R1
 * An omega end group must contain a R2
 * @param {import('openchemlib').Molecule} unit - an instance of OCL.Molecule
 * @param {object} options
 * @param {number} [options.count=10] - number of units
 * @param {boolean} [options.markMonomer=false] - mark the different units of the polymer in the atom map
 * @param {import('openchemlib').Molecule} [options.alpha] - alpha end group, default is an hydrogen
 * @param {import('openchemlib').Molecule} [options.gamma] - omega end group, default is an hydrogen
 */
function createPolymer(unit, options = {}) {
  const {
    count = 10
  } = options;
  checkEntity(unit, 'unit');
  const {
    Molecule
  } = unit.getOCL();
  const {
    alpha,
    gamma
  } = getAlphaGamma$1(unit, options);
  checkEntity(alpha, 'alpha');
  checkEntity(gamma, 'gamma');
  const {
    r1AtomicNo,
    r2AtomicNo
  } = getR1R2AtomicNo(Molecule);
  const polymer = alpha.getCompactCopy();
  polymer.addMolecule(getUnit(unit, 1, options));
  addBond(polymer, r1AtomicNo, r1AtomicNo);
  for (let i = 0; i < count - 1; i++) {
    polymer.addMolecule(getUnit(unit, i + 2, options));
    addBond(polymer, r2AtomicNo, r1AtomicNo);
  }
  polymer.addMolecule(gamma);
  addBond(polymer, r2AtomicNo, r2AtomicNo);
  polymer.ensureHelperArrays(Molecule.cHelperNeighbours);
  // encoding directly in atomNapNo didn't work out because it was removed when deleting atoms
  for (let i = 0; i < polymer.getAtoms(); i++) {
    polymer.setAtomMapNo(i, (polymer.getAtomCustomLabel(i) || '').replace(/monomer_/, ''));
    polymer.setAtomCustomLabel(i, '');
  }
  return polymer;
}
function getUnit(unit, index, options) {
  const {
    markMonomer = false
  } = options;
  if (markMonomer) {
    unit = unit.getCompactCopy();
    unit.ensureHelperArrays(unit.getOCL().Molecule.cHelperNeighbours);
    for (let j = 0; j < unit.getAtoms(); j++) {
      unit.setAtomCustomLabel(j, `monomer_${index}`);
    }
  }
  return unit;
}
function addBond(molecule, firstAtomicNo, secondAtomicNo) {
  molecule.ensureHelperArrays(molecule.getOCL().Molecule.cHelperNeighbours);
  let i, j;
  loop: for (i = 0; i < molecule.getAtoms(); i++) {
    if (molecule.getAtomicNo(i) === firstAtomicNo) {
      for (j = i + 1; j < molecule.getAtoms(); j++) {
        if (molecule.getAtomicNo(j) === secondAtomicNo) {
          molecule.addBond(molecule.getConnAtom(i, 0), molecule.getConnAtom(j, 0), 1);
          break loop;
        }
      }
    }
  }
  molecule.deleteAtoms([i, j]);
}
function checkEntity(unit, kind) {
  let nbR1 = 1;
  let nbR2 = 1;
  switch (kind) {
    case 'unit':
      break;
    case 'alpha':
      nbR2 = 0;
      break;
    case 'gamma':
      nbR1 = 0;
      break;
    default:
      throw new Error('Unknown kind');
  }
  if (!unit) {
    throw new Error('unit is required');
  }
  const {
    Molecule
  } = unit.getOCL();
  // unit must contain ONE R1 and ONE R2
  const {
    r1AtomicNo,
    r2AtomicNo
  } = getR1R2AtomicNo(Molecule);
  let r1Count = 0;
  let r2Count = 0;
  for (let i = 0; i < unit.getAtoms(); i++) {
    if (unit.getAtomicNo(i) === r1AtomicNo) {
      r1Count++;
    }
    if (unit.getAtomicNo(i) === r2AtomicNo) {
      r2Count++;
    }
  }
  if (r1Count !== nbR1) {
    throw new Error(`${kind} must contain ${nbR1} R1`);
  }
  if (r2Count !== nbR2) {
    throw new Error(`${kind} must contain ${nbR2} R2`);
  }
}
function getAlphaGamma$1(unit, options) {
  let {
    alpha,
    gamma
  } = options;
  const {
    Molecule
  } = unit.getOCL();
  const {
    r1AtomicNo,
    r2AtomicNo
  } = getR1R2AtomicNo(Molecule);
  if (!alpha) {
    alpha = Molecule.fromSmiles('CC');
    alpha.setAtomicNo(0, r1AtomicNo);
    alpha.setAtomicNo(1, 1);
  }
  if (!gamma) {
    gamma = Molecule.fromSmiles('CC');
    gamma.setAtomicNo(0, r2AtomicNo);
    gamma.setAtomicNo(1, 1);
  }
  return {
    alpha,
    gamma
  };
}
function getR1R2AtomicNo(Molecule) {
  const r1AtomicNo = Molecule.getAtomicNoFromLabel('R1', Molecule.cPseudoAtomsRGroups);
  const r2AtomicNo = Molecule.getAtomicNoFromLabel('R2', Molecule.cPseudoAtomsRGroups);
  return {
    r1AtomicNo,
    r2AtomicNo
  };
}

var src = {};

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

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

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

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

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

class Gaussian {
  /**
   * Full width at half maximum.
   * @default 500
   */
  fwhm;
  constructor(options = {}) {
    const {
      fwhm = 500,
      sd
    } = options;
    this.fwhm = sd ? gaussianWidthToFWHM(2 * sd) : fwhm;
  }
  fwhmToWidth(fwhm = this.fwhm) {
    return gaussianFwhmToWidth(fwhm);
  }
  widthToFWHM(width) {
    return gaussianWidthToFWHM(width);
  }
  fct(x) {
    return gaussianFct(x, this.fwhm);
  }
  getArea(height = calculateGaussianHeight({
    fwhm: this.fwhm
  })) {
    return getGaussianArea({
      fwhm: this.fwhm,
      height
    });
  }
  getFactor(area) {
    return getGaussianFactor(area);
  }
  getData(options = {}) {
    return getGaussianData(this, options);
  }
  calculateHeight(area = 1) {
    return calculateGaussianHeight({
      fwhm: this.fwhm,
      area
    });
  }
  getParameters() {
    return ['fwhm'];
  }
}
/**
 * Calculate the peak height for a given area and fwhm.
 * @param options - fwhm, area, and optional sd.
 * @returns the peak height.
 */
function calculateGaussianHeight(options) {
  const {
    area = 1,
    sd
  } = options;
  let {
    fwhm = 500
  } = options;
  if (sd) fwhm = gaussianWidthToFWHM(2 * sd);
  return 2 * area / ROOT_PI_OVER_LN2 / fwhm;
}
/**
 * Evaluate the gaussian function centered at x=0.
 * @param x - position at which to evaluate.
 * @param fwhm - full width at half maximum.
 * @returns the intensity at x.
 */
function gaussianFct(x, fwhm) {
  return Math.exp(GAUSSIAN_EXP_FACTOR * (x / fwhm) ** 2);
}
/**
 * Convert inflection-point width to full width at half maximum.
 * @param width - width between inflection points.
 * @returns full width at half maximum.
 */
function gaussianWidthToFWHM(width) {
  return width * ROOT_2LN2;
}
/**
 * Convert full width at half maximum to inflection-point width.
 * @param fwhm - full width at half maximum.
 * @returns width between inflection points.
 */
function gaussianFwhmToWidth(fwhm) {
  return fwhm / ROOT_2LN2;
}
/**
 * Calculate the area under a gaussian peak.
 * @param options - fwhm, height, and optional sd.
 * @returns the area.
 */
function getGaussianArea(options) {
  const {
    sd,
    height = 1
  } = options;
  let {
    fwhm = 500
  } = options;
  if (sd) fwhm = gaussianWidthToFWHM(2 * sd);
  return height * ROOT_PI_OVER_LN2 * fwhm / 2;
}
/**
 * Calculate the half-width factor corresponding to a given area coverage fraction.
 * @param area - target area fraction (0–1). Defaults to `0.9999`.
 * @returns the factor by which to multiply fwhm to cover the given area.
 */
function getGaussianFactor(area = 0.9999) {
  return Math.sqrt(2) * erfinv(area);
}
/**
 * Generate an intensity array for a gaussian shape.
 * @param shape - gaussian shape parameters (fwhm, sd).
 * @param options - sampling options (length, factor, height).
 * @returns Float64Array of intensity values.
 */
function getGaussianData(shape = {}, options = {}) {
  const {
    sd
  } = shape;
  let {
    fwhm = 500
  } = shape;
  if (sd) fwhm = gaussianWidthToFWHM(2 * sd);
  const {
    factor = getGaussianFactor(),
    height = calculateGaussianHeight({
      fwhm
    })
  } = options;
  let {
    length
  } = options;
  if (!length) {
    length = Math.min(Math.ceil(fwhm * factor), 2 ** 25 - 1);
    if (length % 2 === 0) length++;
  }
  const center = (length - 1) / 2;
  const data = new Float64Array(length);
  for (let i = 0; i <= center; i++) {
    const value = gaussianFct(i - center, fwhm) * height;
    data[i] = value;
    data[length - 1 - i] = value;
  }
  return data;
}

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

class LorentzianDispersive {
  /**
   * Full width at half maximum.
   * @default 500
   */
  fwhm;
  constructor(options = {}) {
    const {
      fwhm = 500
    } = options;
    this.fwhm = fwhm;
  }
  fwhmToWidth(fwhm = this.fwhm) {
    return lorentzianFwhmToWidth(fwhm);
  }
  widthToFWHM(width) {
    return lorentzianWidthToFWHM(width);
  }
  fct(x) {
    return lorentzianDispersiveFct(x, this.fwhm);
  }
  getArea() {
    return 0;
  }
  getFactor(area) {
    return getLorentzianFactor(area);
  }
  getData(options = {}) {
    return getLorentzianDispersiveData(this, options);
  }
  calculateHeight(area = 1) {
    return calculateLorentzianHeight({
      fwhm: this.fwhm,
      area
    });
  }
  getParameters() {
    return ['fwhm'];
  }
}
const lorentzianDispersiveFct = (x, fwhm) => {
  return 2 * fwhm * x / (4 * x ** 2 + fwhm ** 2);
};
const getLorentzianDispersiveData = (shape = {}, options = {}) => {
  const {
    fwhm = 500
  } = shape;
  const {
    factor = getLorentzianFactor(),
    height = calculateLorentzianHeight({
      fwhm,
      area: 1
    })
  } = options;
  let {
    length
  } = options;
  if (!length) {
    length = Math.min(Math.ceil(fwhm * factor), 2 ** 25 - 1);
    if (length % 2 === 0) length++;
  }
  const center = (length - 1) / 2;
  const data = new Float64Array(length);
  for (let i = 0; i <= center; i++) {
    const value = lorentzianDispersiveFct(i - center, fwhm) * height;
    data[i] = value;
    data[length - 1 - i] = -value;
  }
  return data;
};

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

class PseudoVoigt {
  fwhm;
  /**
   * Ratio of gaussian contribution in the shape
   * @default 0.5
   */
  mu;
  constructor(options = {}) {
    const {
      fwhm = 500,
      mu = 0.5
    } = options;
    this.mu = mu;
    this.fwhm = fwhm;
  }
  fwhmToWidth(fwhm = this.fwhm, mu = this.mu) {
    return pseudoVoigtFwhmToWidth(fwhm, mu);
  }
  widthToFWHM(width, mu = this.mu) {
    return pseudoVoigtWidthToFWHM(width, mu);
  }
  fct(x) {
    return pseudoVoigtFct(x, this.fwhm, this.mu);
  }
  getArea(height = 1) {
    return getPseudoVoigtArea({
      fwhm: this.fwhm,
      height,
      mu: this.mu
    });
  }
  getFactor(area) {
    return getPseudoVoigtFactor(area, this.mu);
  }
  getData(options = {}) {
    const {
      length,
      factor,
      height = calculatePseudoVoigtHeight({
        fwhm: this.fwhm,
        mu: this.mu,
        area: 1
      })
    } = options;
    return getPseudoVoigtData(this, {
      factor,
      length,
      height
    });
  }
  calculateHeight(area = 1) {
    return calculatePseudoVoigtHeight({
      fwhm: this.fwhm,
      mu: this.mu,
      area
    });
  }
  getParameters() {
    return ['fwhm', 'mu'];
  }
}
const calculatePseudoVoigtHeight = (options = {}) => {
  const {
    fwhm = 1,
    mu = 0.5,
    area = 1
  } = options;
  return 2 * area / (fwhm * (mu * ROOT_PI_OVER_LN2 + (1 - mu) * Math.PI));
};
const pseudoVoigtFct = (x, fwhm, mu) => {
  return (1 - mu) * lorentzianFct(x, fwhm) + mu * gaussianFct(x, fwhm);
};
const pseudoVoigtWidthToFWHM = (width, mu = 0.5) => {
  return width * (mu * ROOT_2LN2_MINUS_ONE + 1);
};
const pseudoVoigtFwhmToWidth = (fwhm, mu = 0.5) => {
  return fwhm / (mu * ROOT_2LN2_MINUS_ONE + 1);
};
const getPseudoVoigtArea = options => {
  const {
    fwhm = 500,
    height = 1,
    mu = 0.5
  } = options;
  return fwhm * height * (mu * ROOT_PI_OVER_LN2 + (1 - mu) * Math.PI) / 2;
};
const getPseudoVoigtFactor = (area = 0.9999, mu = 0.5) => {
  return pseudoVoigtFindFactor(area, mu);
};
const getPseudoVoigtData = (shape = {}, options = {}) => {
  const {
    fwhm = 500,
    mu = 0.5
  } = shape;
  const {
    factor = getPseudoVoigtFactor(0.999, mu)
  } = options;
  let {
    length,
    height = calculatePseudoVoigtHeight({
      fwhm,
      mu,
      area: 1
    })
  } = options;
  if (!height) {
    height = 1 / (mu / Math.sqrt(-GAUSSIAN_EXP_FACTOR / Math.PI) * fwhm + (1 - mu) * fwhm * Math.PI / 2);
  }
  if (!length) {
    length = Math.min(Math.ceil(fwhm * factor), 2 ** 25 - 1);
    if (length % 2 === 0) length++;
  }
  const center = (length - 1) / 2;
  const data = new Float64Array(length);
  for (let i = 0; i <= center; i++) {
    const value = pseudoVoigtFct(i - center, fwhm, mu) * height;
    data[i] = value;
    data[length - 1 - i] = value;
  }
  return data;
};

/**
 * TCH-style pseudo-Voigt where gaussian and lorentzian widths are independent.
 * The effective fwhm and mixing parameter mu are derived from fwhmG and fwhmL
 * via the Thompson–Cox–Hastings approximation.
 */
class PseudoVoigtTCH {
  _fwhmG;
  _fwhmL;
  _fwhm;
  _mu;
  _lorentzianWidthFraction;
  constructor(options = {}) {
    const {
      fwhmG,
      fwhmL,
      fwhm,
      mu = 0.5
    } = options;
    this._mu = mu;
    this._fwhm = 0;
    this._fwhmG = 0;
    this._fwhmL = 0;
    this._lorentzianWidthFraction = lorentzianWidthFraction(1 - mu);
    if (fwhmG && fwhmL) {
      this._fwhmG = fwhmG;
      this.fwhmL = fwhmL;
    } else if (fwhm) {
      this.fwhm = fwhm;
    }
  }
  set fwhmG(value) {
    const effectiveFwhm = computeEffectiveWidth(value, this._fwhmL);
    const lorentzianFraction = this._fwhmL / effectiveFwhm;
    this._fwhm = effectiveFwhm;
    this._mu = 1 - (1.36603 * lorentzianFraction - 0.47719 * lorentzianFraction * lorentzianFraction + 0.11116 * lorentzianFraction * lorentzianFraction * lorentzianFraction);
    this._fwhmG = value;
    this._lorentzianWidthFraction = lorentzianFraction;
  }
  get fwhmG() {
    return this._fwhmG;
  }
  set fwhmL(value) {
    const effectiveFwhm = computeEffectiveWidth(this._fwhmG, value);
    const lorentzianFraction = value / effectiveFwhm;
    this._fwhm = effectiveFwhm;
    this._mu = 1 - (1.36603 * lorentzianFraction - 0.47719 * lorentzianFraction * lorentzianFraction + 0.11116 * lorentzianFraction * lorentzianFraction * lorentzianFraction);
    this._fwhmL = value;
    this._lorentzianWidthFraction = lorentzianFraction;
  }
  get fwhmL() {
    return this._fwhmL;
  }
  set mu(value) {
    const lorentzianFraction = lorentzianWidthFraction(1 - value);
    this._lorentzianWidthFraction = lorentzianFraction;
    this._fwhmL = this._fwhm * lorentzianFraction;
    this._fwhmG = this._fwhm * (1 - lorentzianFraction);
    this._mu = value;
  }
  get mu() {
    return this._mu;
  }
  set fwhm(value) {
    const lorentzianFraction = this._lorentzianWidthFraction || lorentzianWidthFraction(1 - this._mu);
    this._fwhmL = value * lorentzianFraction;
    this._fwhmG = value * (1 - lorentzianFraction);
    this._fwhm = value;
  }
  get fwhm() {
    return this._fwhm;
  }
  fwhmToWidth(fwhm = this._fwhm, mu = this._mu) {
    return pseudoVoigtFwhmToWidth(fwhm, mu);
  }
  widthToFWHM(width, mu = this._mu) {
    return pseudoVoigtWidthToFWHM(width, mu);
  }
  fct(x) {
    return pseudoVoigtFct(x, this._fwhm, this._mu);
  }
  getArea(height = 1) {
    return getPseudoVoigtArea({
      fwhm: this._fwhm,
      height,
      mu: this._mu
    });
  }
  getFactor(area) {
    return getPseudoVoigtFactor(area, this._mu);
  }
  getData(options = {}) {
    const {
      length,
      factor,
      height = calculatePseudoVoigtHeight({
        fwhm: this._fwhm,
        mu: this._mu,
        area: 1
      })
    } = options;
    return getPseudoVoigtData(this, {
      factor,
      length,
      height
    });
  }
  calculateHeight(area = 1) {
    return calculatePseudoVoigtHeight({
      fwhm: this._fwhm,
      mu: this._mu,
      area
    });
  }
  getParameters() {
    return ['fwhmG', 'fwhmL'];
  }
}
/**
 * Compute the effective FWHM from gaussian and lorentzian component widths
 * using the Thompson–Cox–Hastings approximation.
 * @param fwhmG - gaussian component FWHM.
 * @param fwhmL - lorentzian component FWHM.
 * @returns effective combined FWHM.
 */
function computeEffectiveWidth(fwhmG, fwhmL) {
  return (fwhmG ** 5 + 2.69269 * fwhmG ** 4 * fwhmL + 2.42843 * fwhmG ** 3 * fwhmL ** 2 + 4.47163 * fwhmG ** 2 * fwhmL ** 3 + 0.07842 * fwhmG * fwhmL ** 4 + fwhmL ** 5) ** 0.2;
}
/**
 * Solve for the lorentzian width fraction fwhmL/fwhm given lorentzianFraction = 1 - mu,
 * using Newton's method on: 1.36603·x - 0.47719·x² + 0.11116·x³ = lorentzianFraction.
 * @param lorentzianFraction - TCH lorentzian mixing parameter (= 1 - mu).
 * @returns the lorentzian width fraction fwhmL/fwhm.
 */
function lorentzianWidthFraction(lorentzianFraction) {
  let fraction = lorentzianFraction;
  for (let i = 0; i < 6; i++) {
    const f = 1.36603 * fraction - 0.47719 * fraction * fraction + 0.11116 * fraction * fraction * fraction - lorentzianFraction;
    const df = 1.36603 - 2 * 0.47719 * fraction + 3 * 0.11116 * fraction * fraction;
    fraction -= f / df;
  }
  return fraction;
}

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

/**
 * Generate a instance of a specific kind of shape.
 * @param shape - shape descriptor specifying the kind and parameters.
 * @returns an instance of the requested shape class.
 */
function getShape1D(shape) {
  const {
    kind
  } = shape;
  switch (kind) {
    case 'gaussian':
      return new Gaussian(shape);
    case 'lorentzian':
      return new Lorentzian(shape);
    case 'pseudoVoigt':
      return new PseudoVoigt(shape);
    case 'pseudoVoigtTCH':
      return new PseudoVoigtTCH(shape);
    case 'lorentzianDispersive':
      return new LorentzianDispersive(shape);
    case 'generalizedLorentzian':
      return new GeneralizedLorentzian(shape);
    default:
      {
        throw new Error(`Unknown distribution ${kind}`);
      }
  }
}

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

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

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

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

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

let data;
const xShiftFcts = {
  solid: {
    C1s: x => -2501.002792622873 + 18.12807741 * x - 0.02938832 * x ** 2,
    O1s: x => -14914.578039681715 + 5.67166123e1 * x - 5.20506217e-2 * x ** 2
  }
};

/**
 *
 * @param {import('openchemlib').Molecule} [molecule]
 * @param {object} options
 * @param {string} [options.statsKey='gw']
 * @param {number|undefined} [options.atomMapNo] - // allows to filter for specific atoms
 * @param {'solid'|'none'} [options.energyReference='solid']
 * @param {import('spectrum-generator').GenerateSpectrumOptions} [options.spectrum]
 * @param {object} [options.data] - Pre-loaded QM9 hose-code statistics (`{ spheres: { ... } }`). When provided, the network fetch from data.cheminfo.org is skipped, which makes the prediction work offline and in the browser.
 */
async function predictUsingHoseCodes(molecule, options = {}) {
  const {
    statsKey = 'gw',
    spectrum: spectrumOptions,
    energyReference = 'solid',
    atomMapNo,
    data: injectedData
  } = options;
  const spheresData = await ensureSpheres(injectedData);
  const spheres = spheresData.spheres[statsKey];
  const diaIDs = getDiastereotopicAtomIDsAndH(molecule);
  const annotations = [];
  const annotationOptions = {
    verticalPosition: 0
  };
  let values = getHoseCodes(molecule, {
    maxSphereSize: 2
  }).map((hoses, index) => ({
    hoses,
    atomLabel: molecule.getAtomLabel(index),
    atomNumber: index,
    atomMapNo: molecule.getAtomMapNo(index),
    _highlight: [diaIDs[index].oclID]
  })).filter(value => value.atomLabel && value.atomLabel !== 'H' && value.atomLabel !== '?').filter(value => value.hoses).filter(value => !atomMapNo || value.atomMapNo === atomMapNo);
  for (const hoseCode of values) {
    for (let i = hoseCode.hoses.length - 1; i >= 0; i--) {
      const hose = hoseCode.hoses[i];
      const sphere = spheres[i];
      if (!sphere) {
        throw new Error(`sphere not found: ${i} - ${hoseCode.hoses.length}`);
      }
      const prediction = sphere.hoses[hose];
      if (prediction) {
        hoseCode.prediction = structuredClone(prediction);
        break;
      }
    }
  }

  // before anything else we should shift if required
  for (const hoseCode of values) {
    const transition = `${hoseCode.atomLabel}1s`;
    const xShiftFct = xShiftFcts[energyReference]?.[transition];
    if (xShiftFct) {
      for (const key of Object.keys(hoseCode.prediction.boxplot)) {
        hoseCode.prediction.boxplot[key] = xShiftFct(hoseCode.prediction.boxplot[key]);
      }
    }
  }
  const peaks = values.filter(value => value.prediction).map(value => ({
    x: value.prediction.boxplot.median,
    y: 1
  }));

  // todo shift values if required
  const spectrum = generateSpectrum(peaks, spectrumOptions);

  // values should be unique based on value.prediction.idCode
  // during this operation we should take care to combine _highlights
  const uniqueValuesObject = {};
  for (const value of values) {
    const key = `${value.prediction.idCode}`;
    if (uniqueValuesObject[key]) {
      uniqueValuesObject[key].atomNumbers.push(value.atomNumber);
      const highlight = value._highlight[0];
      if (!uniqueValuesObject[key]._highlight.includes(highlight)) {
        uniqueValuesObject[key]._highlight.push(highlight);
      }
    } else {
      // eslint-disable-next-line no-unused-vars
      const {
        atomNumber,
        ...uniqueValueObject
      } = {
        ...value,
        atomNumbers: [value.atomNumber]
      };
      uniqueValuesObject[key] = uniqueValueObject;
    }
  }
  const grouped = Object.values(uniqueValuesObject).toSorted((a, b) => a.prediction.boxplot.median - b.prediction.boxplot.median);
  for (const hoseCode of grouped) {
    annotations.push(...getAnnotations(hoseCode.prediction, hoseCode._highlight, annotationOptions));
  }
  return {
    grouped,
    spectrum,
    annotations,
    peaks
  };
}
async function ensureSpheres(injectedData) {
  if (injectedData) return injectedData;
  if (data) return data;
  const url = `https://data.cheminfo.org/xps/qm9.json`;
  const response = await fetch(url);
  //We do a global in place modification,
  // eslint-disable-next-line require-atomic-updates
  data = await response.json();
  return data;
}
function getAnnotations(prediction, highlight, options) {
  const annotations = [{
    position: [{
      x: prediction.boxplot.q1,
      y: `${options.verticalPosition * 8 + 2}px`
    }, {
      x: prediction.boxplot.q3,
      y: `${options.verticalPosition * 8 + 6}px`
    }],
    type: 'rect',
    fillColor: 'transparent',
    strokeColor: 'orange',
    _highlight: highlight
  }, {
    position: [{
      x: prediction.boxplot.min,
      y: `${options.verticalPosition * 8 + 4}px`
    }, {
      x: prediction.boxplot.max,
      y: `${options.verticalPosition * 8 + 4}px`
    }],
    type: 'line',
    fillColor: 'transparent',
    strokeColor: 'orange'
  }, {
    position: [{
      x: prediction.boxplot.median,
      y: `${options.verticalPosition * 8 + 2}px`
    }, {
      x: prediction.boxplot.median,
      y: `${options.verticalPosition * 8 + 6}px`
    }],
    type: 'line',
    fillColor: 'transparent',
    strokeColor: 'darkorange'
  }];
  options.verticalPosition = (options.verticalPosition + 1) % 7;
  return annotations;
}

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

var crypt = {exports: {}};

(function () {
  var base64map = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/',
    crypt$1 = {
      // Bit-wise rotation left
      rotl: function (n, b) {
        return n << b | n >>> 32 - b;
      },
      // Bit-wise rotation right
      rotr: function (n, b) {
        return n << 32 - b | n >>> b;
      },
      // Swap big-endian to little-endian and vice versa
      endian: function (n) {
        // If number given, swap endian
        if (n.constructor == Number) {
          return crypt$1.rotl(n, 8) & 0x00FF00FF | crypt$1.rotl(n, 24) & 0xFF00FF00;
        }

        // Else, assume array and swap all items
        for (var i = 0; i < n.length; i++) n[i] = crypt$1.endian(n[i]);
        return n;
      },
      // Generate an array of any length of random bytes
      randomBytes: function (n) {
        for (var bytes = []; n > 0; n--) bytes.push(Math.floor(Math.random() * 256));
        return bytes;
      },
      // Convert a byte array to big-endian 32-bit words
      bytesToWords: function (bytes) {
        for (var words = [], i = 0, b = 0; i < bytes.length; i++, b += 8) words[b >>> 5] |= bytes[i] << 24 - b % 32;
        return words;
      },
      // Convert big-endian 32-bit words to a byte array
      wordsToBytes: function (words) {
        for (var bytes = [], b = 0; b < words.length * 32; b += 8) bytes.push(words[b >>> 5] >>> 24 - b % 32 & 0xFF);
        return bytes;
      },
      // Convert a byte array to a hex string
      bytesToHex: function (bytes) {
        for (var hex = [], i = 0; i < bytes.length; i++) {
          hex.push((bytes[i] >>> 4).toString(16));
          hex.push((bytes[i] & 0xF).toString(16));
        }
        return hex.join('');
      },
      // Convert a hex string to a byte array
      hexToBytes: function (hex) {
        for (var bytes = [], c = 0; c < hex.length; c += 2) bytes.push(parseInt(hex.substr(c, 2), 16));
        return bytes;
      },
      // Convert a byte array to a base-64 string
      bytesToBase64: function (bytes) {
        for (var base64 = [], i = 0; i < bytes.length; i += 3) {
          var triplet = bytes[i] << 16 | bytes[i + 1] << 8 | bytes[i + 2];
          for (var j = 0; j < 4; j++) if (i * 8 + j * 6 <= bytes.length * 8) base64.push(base64map.charAt(triplet >>> 6 * (3 - j) & 0x3F));else base64.push('=');
        }
        return base64.join('');
      },
      // Convert a base-64 string to a byte array
      base64ToBytes: function (base64) {
        // Remove non-base-64 characters
        base64 = base64.replace(/[^A-Z0-9+\/]/ig, '');
        for (var bytes = [], i = 0, imod4 = 0; i < base64.length; imod4 = ++i % 4) {
          if (imod4 == 0) continue;
          bytes.push((base64map.indexOf(base64.charAt(i - 1)) & Math.pow(2, -2 * imod4 + 8) - 1) << imod4 * 2 | base64map.indexOf(base64.charAt(i)) >>> 6 - imod4 * 2);
        }
        return bytes;
      }
    };
  crypt.exports = crypt$1;
})();
var cryptExports = crypt.exports;

var charenc = {
  // UTF-8 encoding
  utf8: {
    // Convert a string to a byte array
    stringToBytes: function (str) {
      return charenc.bin.stringToBytes(unescape(encodeURIComponent(str)));
    },
    // Convert a byte array to a string
    bytesToString: function (bytes) {
      return decodeURIComponent(escape(charenc.bin.bytesToString(bytes)));
    }
  },
  // Binary encoding
  bin: {
    // Convert a string to a byte array
    stringToBytes: function (str) {
      for (var bytes = [], i = 0; i < str.length; i++) bytes.push(str.charCodeAt(i) & 0xFF);
      return bytes;
    },
    // Convert a byte array to a string
    bytesToString: function (bytes) {
      for (var str = [], i = 0; i < bytes.length; i++) str.push(String.fromCharCode(bytes[i]));
      return str.join('');
    }
  }
};
var charenc_1 = charenc;

/*!
 * Determine if an object is a Buffer
 *
 * @author   Feross Aboukhadijeh <https://feross.org>
 * @license  MIT
 */

// The _isBuffer check is for Safari 5-7 support, because it's missing
// Object.prototype.constructor. Remove this eventually
var isBuffer_1 = function (obj) {
  return obj != null && (isBuffer(obj) || isSlowBuffer(obj) || !!obj._isBuffer);
};
function isBuffer(obj) {
  return !!obj.constructor && typeof obj.constructor.isBuffer === 'function' && obj.constructor.isBuffer(obj);
}

// For Node v0.10 support. Remove this eventually.
function isSlowBuffer(obj) {
  return typeof obj.readFloatLE === 'function' && typeof obj.slice === 'function' && isBuffer(obj.slice(0, 0));
}

(function () {
  var crypt = cryptExports,
    utf8 = charenc_1.utf8,
    isBuffer = isBuffer_1,
    bin = charenc_1.bin,
    // The core
    md5 = function (message, options) {
      // Convert to byte array
      if (message.constructor == String) {
        if (options && options.encoding === 'binary') message = bin.stringToBytes(message);else message = utf8.stringToBytes(message);
      } else if (isBuffer(message)) message = Array.prototype.slice.call(message, 0);else if (!Array.isArray(message) && message.constructor !== Uint8Array) message = message.toString();
      // else, assume byte array already

      var m = crypt.bytesToWords(message),
        l = message.length * 8,
        a = 1732584193,
        b = -271733879,
        c = -1732584194,
        d = 271733878;

      // Swap endian
      for (var i = 0; i < m.length; i++) {
        m[i] = (m[i] << 8 | m[i] >>> 24) & 0x00FF00FF | (m[i] << 24 | m[i] >>> 8) & 0xFF00FF00;
      }

      // Padding
      m[l >>> 5] |= 0x80 << l % 32;
      m[(l + 64 >>> 9 << 4) + 14] = l;

      // Method shortcuts
      var FF = md5._ff,
        GG = md5._gg,
        HH = md5._hh,
        II = md5._ii;
      for (var i = 0; i < m.length; i += 16) {
        var aa = a,
          bb = b,
          cc = c,
          dd = d;
        a = FF(a, b, c, d, m[i + 0], 7, -680876936);
        d = FF(d, a, b, c, m[i + 1], 12, -389564586);
        c = FF(c, d, a, b, m[i + 2], 17, 606105819);
        b = FF(b, c, d, a, m[i + 3], 22, -1044525330);
        a = FF(a, b, c, d, m[i + 4], 7, -176418897);
        d = FF(d, a, b, c, m[i + 5], 12, 1200080426);
        c = FF(c, d, a, b, m[i + 6], 17, -1473231341);
        b = FF(b, c, d, a, m[i + 7], 22, -45705983);
        a = FF(a, b, c, d, m[i + 8], 7, 1770035416);
        d = FF(d, a, b, c, m[i + 9], 12, -1958414417);
        c = FF(c, d, a, b, m[i + 10], 17, -42063);
        b = FF(b, c, d, a, m[i + 11], 22, -1990404162);
        a = FF(a, b, c, d, m[i + 12], 7, 1804603682);
        d = FF(d, a, b, c, m[i + 13], 12, -40341101);
        c = FF(c, d, a, b, m[i + 14], 17, -1502002290);
        b = FF(b, c, d, a, m[i + 15], 22, 1236535329);
        a = GG(a, b, c, d, m[i + 1], 5, -165796510);
        d = GG(d, a, b, c, m[i + 6], 9, -1069501632);
        c = GG(c, d, a, b, m[i + 11], 14, 643717713);
        b = GG(b, c, d, a, m[i + 0], 20, -373897302);
        a = GG(a, b, c, d, m[i + 5], 5, -701558691);
        d = GG(d, a, b, c, m[i + 10], 9, 38016083);
        c = GG(c, d, a, b, m[i + 15], 14, -660478335);
        b = GG(b, c, d, a, m[i + 4], 20, -405537848);
        a = GG(a, b, c, d, m[i + 9], 5, 568446438);
        d = GG(d, a, b, c, m[i + 14], 9, -1019803690);
        c = GG(c, d, a, b, m[i + 3], 14, -187363961);
        b = GG(b, c, d, a, m[i + 8], 20, 1163531501);
        a = GG(a, b, c, d, m[i + 13], 5, -1444681467);
        d = GG(d, a, b, c, m[i + 2], 9, -51403784);
        c = GG(c, d, a, b, m[i + 7], 14, 1735328473);
        b = GG(b, c, d, a, m[i + 12], 20, -1926607734);
        a = HH(a, b, c, d, m[i + 5], 4, -378558);
        d = HH(d, a, b, c, m[i + 8], 11, -2022574463);
        c = HH(c, d, a, b, m[i + 11], 16, 1839030562);
        b = HH(b, c, d, a, m[i + 14], 23, -35309556);
        a = HH(a, b, c, d, m[i + 1], 4, -1530992060);
        d = HH(d, a, b, c, m[i + 4], 11, 1272893353);
        c = HH(c, d, a, b, m[i + 7], 16, -155497632);
        b = HH(b, c, d, a, m[i + 10], 23, -1094730640);
        a = HH(a, b, c, d, m[i + 13], 4, 681279174);
        d = HH(d, a, b, c, m[i + 0], 11, -358537222);
        c = HH(c, d, a, b, m[i + 3], 16, -722521979);
        b = HH(b, c, d, a, m[i + 6], 23, 76029189);
        a = HH(a, b, c, d, m[i + 9], 4, -640364487);
        d = HH(d, a, b, c, m[i + 12], 11, -421815835);
        c = HH(c, d, a, b, m[i + 15], 16, 530742520);
        b = HH(b, c, d, a, m[i + 2], 23, -995338651);
        a = II(a, b, c, d, m[i + 0], 6, -198630844);
        d = II(d, a, b, c, m[i + 7], 10, 1126891415);
        c = II(c, d, a, b, m[i + 14], 15, -1416354905);
        b = II(b, c, d, a, m[i + 5], 21, -57434055);
        a = II(a, b, c, d, m[i + 12], 6, 1700485571);
        d = II(d, a, b, c, m[i + 3], 10, -1894986606);
        c = II(c, d, a, b, m[i + 10], 15, -1051523);
        b = II(b, c, d, a, m[i + 1], 21, -2054922799);
        a = II(a, b, c, d, m[i + 8], 6, 1873313359);
        d = II(d, a, b, c, m[i + 15], 10, -30611744);
        c = II(c, d, a, b, m[i + 6], 15, -1560198380);
        b = II(b, c, d, a, m[i + 13], 21, 1309151649);
        a = II(a, b, c, d, m[i + 4], 6, -145523070);
        d = II(d, a, b, c, m[i + 11], 10, -1120210379);
        c = II(c, d, a, b, m[i + 2], 15, 718787259);
        b = II(b, c, d, a, m[i + 9], 21, -343485551);
        a = a + aa >>> 0;
        b = b + bb >>> 0;
        c = c + cc >>> 0;
        d = d + dd >>> 0;
      }
      return crypt.endian([a, b, c, d]);
    };

  // Auxiliary functions
  md5._ff = function (a, b, c, d, x, s, t) {
    var n = a + (b & c | ~b & d) + (x >>> 0) + t;
    return (n << s | n >>> 32 - s) + b;
  };
  md5._gg = function (a, b, c, d, x, s, t) {
    var n = a + (b & d | c & ~d) + (x >>> 0) + t;
    return (n << s | n >>> 32 - s) + b;
  };
  md5._hh = function (a, b, c, d, x, s, t) {
    var n = a + (b ^ c ^ d) + (x >>> 0) + t;
    return (n << s | n >>> 32 - s) + b;
  };
  md5._ii = function (a, b, c, d, x, s, t) {
    var n = a + (c ^ (b | ~d)) + (x >>> 0) + t;
    return (n << s | n >>> 32 - s) + b;
  };

  // Package private blocksize
  md5._blocksize = 16;
  md5._digestsize = 16;
  md5$1.exports = function (message, options) {
    if (message === undefined || message === null) throw new Error('Illegal argument ' + message);
    var digestbytes = crypt.wordsToBytes(md5(message, options));
    return options && options.asBytes ? digestbytes : options && options.asString ? bin.bytesToString(digestbytes) : crypt.bytesToHex(digestbytes);
  };
})();
var md5Exports = md5$1.exports;
var md5 = /*@__PURE__*/getDefaultExportFromCjs(md5Exports);

const cache = {};

/**
 *
 * @param {import('openchemlib').Molecule} [molecule]
 * @param {object} options
 * @param {number} [options.xShift=0]
 * @param {import('spectrum-generator').GenerateSpectrumOptions} [options.spectrum]
 */
async function predictUsingAI(molecule, options = {}) {
  const {
    spectrum: spectrumOptions,
    energyReference = 'solid'
  } = options;
  const moleculeWithH = molecule.getCompactCopy();
  moleculeWithH.addImplicitHydrogens();
  const molfile = moleculeWithH.toMolfile();
  const cacheKey = md5(molfile + energyReference);
  if (!cache[cacheKey]) {
    const response = await fetch('https://xps-service.cheminfo.org/v1/predict_binding_energies', {
      method: 'post',
      headers: {
        'content-type': 'application/json'
      },
      body: JSON.stringify({
        molFile: molfile,
        method: 'GFN2xTB',
        fmax: 0.01,
        energyReference
      })
    });
    cache[cacheKey] = await response.json();
  }
  const predictions = cache[cacheKey].predictions;
  const peaks = [];
  const xyz = [predictions.length, 'XTB optimized geometry'];
  for (const prediction of predictions) {
    xyz.push([prediction.atom, prediction.position.x, prediction.position.y, prediction.position.z].join(' '));
    const orbitals = Object.keys(prediction.prediction);
    if (!orbitals) continue;
    for (const orbital of orbitals) {
      peaks.push({
        x: prediction.prediction[orbital].bindingEnergy,
        y: 1,
        standardDeviation: prediction.prediction[orbital].standardDeviation
      });
    }
  }
  const spectrum = generateSpectrum(peaks, spectrumOptions);
  return {
    peaks,
    spectrum,
    xyz: xyz.join('\n')
  };
}

async function predictPolymer(monomer, options = {}) {
  const {
    Molecule
  } = monomer.getOCL();
  const polymer = createPolymer(monomer, {
    count: 5,
    // we create 5 monomers and we use the middle one
    markMonomer: true,
    ...getAlphaGamma(Molecule)
  });
  const prediction = await predictUsingHoseCodes(polymer, {
    atomMapNo: 3,
    ...options
  });
  return prediction;
}
function getAlphaGamma(Molecule) {
  const r1AtomicNo = Molecule.getAtomicNoFromLabel('R1', Molecule.cPseudoAtomsRGroups);
  const r2AtomicNo = Molecule.getAtomicNoFromLabel('R2', Molecule.cPseudoAtomsRGroups);
  const alpha = Molecule.fromSmiles('C[2H]');
  alpha.setAtomicNo(0, r1AtomicNo);
  const gamma = Molecule.fromSmiles('C[2H]');
  gamma.setAtomicNo(0, r2AtomicNo);
  return {
    alpha,
    gamma
  };
}

const JSGraph = {
  ...JSGraph$1,
  getRegionsAnnotations,
  getComponentsAnnotations
};

export { AnalysesManager, Analysis, JSGraph, fromJcamp, fromVamas, peakPicking, predictPolymer, predictUsingAI, predictUsingHoseCodes, references, toJcamp, toJcamps };
//# sourceMappingURL=xps-analysis.esm.js.map
