feat: add physically explicit narrowband MUSIC
This commit is contained in:
@@ -0,0 +1,555 @@
|
||||
const FLOAT64_BYTES = Float64Array.BYTES_PER_ELEMENT;
|
||||
|
||||
export const DEFAULT_HERMITIAN_JACOBI_MAX_SWEEPS = 32;
|
||||
export const DEFAULT_MAX_HERMITIAN_EIGEN_WORK_BYTES = 16 * 1024 * 1024;
|
||||
export const DEFAULT_MAX_HERMITIAN_JACOBI_PAIR_UPDATES = 1_000_000_000;
|
||||
|
||||
function assertPositiveInteger(value, label) {
|
||||
if (!Number.isSafeInteger(value) || value < 1) {
|
||||
throw new TypeError(`${label} must be a positive safe integer.`);
|
||||
}
|
||||
}
|
||||
|
||||
function assertMaximumSweeps(value) {
|
||||
if (
|
||||
!Number.isSafeInteger(value) ||
|
||||
value < 0 ||
|
||||
value > DEFAULT_HERMITIAN_JACOBI_MAX_SWEEPS
|
||||
) {
|
||||
throw new RangeError(
|
||||
`maximumSweeps must be an integer from 0 through ${DEFAULT_HERMITIAN_JACOBI_MAX_SWEEPS}.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function checkedProduct(factors, label) {
|
||||
let product = 1;
|
||||
for (const factor of factors) {
|
||||
if (factor !== 0 && product > Number.MAX_SAFE_INTEGER / factor) {
|
||||
throw new RangeError(`${label} exceeds the safe integer range.`);
|
||||
}
|
||||
product *= factor;
|
||||
}
|
||||
return product;
|
||||
}
|
||||
|
||||
function roundoffGamma(operationCount) {
|
||||
const product = operationCount * (Number.EPSILON / 2);
|
||||
if (!(product < 1)) {
|
||||
throw new RangeError("roundoff bound is undefined for this workload.");
|
||||
}
|
||||
return product / (1 - product);
|
||||
}
|
||||
|
||||
/**
|
||||
* Estimate the bounded typed-array allocation and cyclic-Jacobi work before
|
||||
* any decomposition workspace is allocated.
|
||||
*/
|
||||
export function estimateHermitianEigenWorkload({
|
||||
order,
|
||||
maximumSweeps = DEFAULT_HERMITIAN_JACOBI_MAX_SWEEPS,
|
||||
}) {
|
||||
assertPositiveInteger(order, "order");
|
||||
assertMaximumSweeps(maximumSweeps);
|
||||
|
||||
const matrixElementCount = checkedProduct([order, order], "matrix element count");
|
||||
const pairCount = checkedProduct([order, order - 1], "Jacobi pair count") / 2;
|
||||
const worstCaseRotations = checkedProduct(
|
||||
[pairCount, maximumSweeps],
|
||||
"Jacobi rotation count",
|
||||
);
|
||||
// A rotation updates n-2 matrix row pairs and n eigenvector row pairs.
|
||||
const pairUpdatesPerRotation = Math.max(0, 2 * order - 2);
|
||||
const worstCasePairUpdates = checkedProduct(
|
||||
[worstCaseRotations, pairUpdatesPerRotation],
|
||||
"Jacobi pair-update count",
|
||||
);
|
||||
// Two split-complex matrices plus normalized and physical eigenvalue arrays.
|
||||
const typedArrayElementCount = checkedProduct(
|
||||
[4 * matrixElementCount + 2 * order, FLOAT64_BYTES],
|
||||
"Hermitian eigensolver allocation",
|
||||
) / FLOAT64_BYTES;
|
||||
const allocationBytes = checkedProduct(
|
||||
[typedArrayElementCount, FLOAT64_BYTES],
|
||||
"Hermitian eigensolver allocation",
|
||||
);
|
||||
|
||||
return Object.freeze({
|
||||
order,
|
||||
maximumSweeps,
|
||||
matrixElementCount,
|
||||
pairCount,
|
||||
pairUpdatesPerRotation,
|
||||
worstCaseRotations,
|
||||
worstCasePairUpdates,
|
||||
allocationBytes,
|
||||
});
|
||||
}
|
||||
|
||||
function inferOrder(real, imaginary, explicitOrder) {
|
||||
if (real == null || imaginary == null) {
|
||||
throw new TypeError("real and imaginary matrix components are required.");
|
||||
}
|
||||
const order = explicitOrder ?? Math.sqrt(real.length);
|
||||
assertPositiveInteger(order, "order");
|
||||
const expectedLength = checkedProduct([order, order], "matrix element count");
|
||||
if (real.length !== expectedLength || imaginary.length !== expectedLength) {
|
||||
throw new RangeError(
|
||||
`Hermitian matrix components must each contain ${expectedLength} values.`,
|
||||
);
|
||||
}
|
||||
return order;
|
||||
}
|
||||
|
||||
function matrixFrobeniusNorm(real, imaginary, order) {
|
||||
let sum = 0;
|
||||
for (let index = 0; index < order * order; index += 1) {
|
||||
sum += real[index] * real[index] + imaginary[index] * imaginary[index];
|
||||
}
|
||||
return Math.sqrt(sum);
|
||||
}
|
||||
|
||||
function offDiagonalFrobeniusNorm(real, imaginary, order) {
|
||||
let upperTriangleSum = 0;
|
||||
for (let row = 0; row < order; row += 1) {
|
||||
for (let column = row + 1; column < order; column += 1) {
|
||||
const index = row * order + column;
|
||||
upperTriangleSum +=
|
||||
real[index] * real[index] + imaginary[index] * imaginary[index];
|
||||
}
|
||||
}
|
||||
return Math.sqrt(2 * upperTriangleSum);
|
||||
}
|
||||
|
||||
function rotatePair(matrixReal, matrixImaginary, vectorsReal, vectorsImaginary, order, p, q) {
|
||||
const pq = p * order + q;
|
||||
const offReal = matrixReal[pq];
|
||||
const offImaginary = matrixImaginary[pq];
|
||||
const magnitude = Math.hypot(offReal, offImaginary);
|
||||
if (magnitude === 0) return false;
|
||||
|
||||
const diagonalP = matrixReal[p * order + p];
|
||||
const diagonalQ = matrixReal[q * order + q];
|
||||
const halfDifference = (diagonalQ - diagonalP) / 2;
|
||||
const tangent = halfDifference === 0
|
||||
? 1
|
||||
: Math.sign(halfDifference) * magnitude /
|
||||
(Math.abs(halfDifference) + Math.hypot(halfDifference, magnitude));
|
||||
const cosine = 1 / Math.hypot(1, tangent);
|
||||
const sine = tangent * cosine;
|
||||
const phaseReal = offReal / magnitude;
|
||||
const phaseImaginary = offImaginary / magnitude;
|
||||
|
||||
for (let row = 0; row < order; row += 1) {
|
||||
if (row === p || row === q) continue;
|
||||
const rowP = row * order + p;
|
||||
const rowQ = row * order + q;
|
||||
const pReal = matrixReal[rowP];
|
||||
const pImaginary = matrixImaginary[rowP];
|
||||
const qReal = matrixReal[rowQ];
|
||||
const qImaginary = matrixImaginary[rowQ];
|
||||
|
||||
const conjugatePhaseTimesQReal =
|
||||
phaseReal * qReal + phaseImaginary * qImaginary;
|
||||
const conjugatePhaseTimesQImaginary =
|
||||
phaseReal * qImaginary - phaseImaginary * qReal;
|
||||
const phaseTimesPReal = phaseReal * pReal - phaseImaginary * pImaginary;
|
||||
const phaseTimesPImaginary = phaseReal * pImaginary + phaseImaginary * pReal;
|
||||
|
||||
const nextPReal = cosine * pReal - sine * conjugatePhaseTimesQReal;
|
||||
const nextPImaginary = cosine * pImaginary - sine * conjugatePhaseTimesQImaginary;
|
||||
const nextQReal = sine * phaseTimesPReal + cosine * qReal;
|
||||
const nextQImaginary = sine * phaseTimesPImaginary + cosine * qImaginary;
|
||||
|
||||
matrixReal[rowP] = nextPReal;
|
||||
matrixImaginary[rowP] = nextPImaginary;
|
||||
matrixReal[p * order + row] = nextPReal;
|
||||
matrixImaginary[p * order + row] = -nextPImaginary;
|
||||
matrixReal[rowQ] = nextQReal;
|
||||
matrixImaginary[rowQ] = nextQImaginary;
|
||||
matrixReal[q * order + row] = nextQReal;
|
||||
matrixImaginary[q * order + row] = -nextQImaginary;
|
||||
}
|
||||
|
||||
matrixReal[p * order + p] = diagonalP - tangent * magnitude;
|
||||
matrixImaginary[p * order + p] = 0;
|
||||
matrixReal[q * order + q] = diagonalQ + tangent * magnitude;
|
||||
matrixImaginary[q * order + q] = 0;
|
||||
matrixReal[pq] = 0;
|
||||
matrixImaginary[pq] = 0;
|
||||
matrixReal[q * order + p] = 0;
|
||||
matrixImaginary[q * order + p] = 0;
|
||||
|
||||
for (let row = 0; row < order; row += 1) {
|
||||
const rowP = row * order + p;
|
||||
const rowQ = row * order + q;
|
||||
const pReal = vectorsReal[rowP];
|
||||
const pImaginary = vectorsImaginary[rowP];
|
||||
const qReal = vectorsReal[rowQ];
|
||||
const qImaginary = vectorsImaginary[rowQ];
|
||||
const conjugatePhaseTimesQReal =
|
||||
phaseReal * qReal + phaseImaginary * qImaginary;
|
||||
const conjugatePhaseTimesQImaginary =
|
||||
phaseReal * qImaginary - phaseImaginary * qReal;
|
||||
const phaseTimesPReal = phaseReal * pReal - phaseImaginary * pImaginary;
|
||||
const phaseTimesPImaginary = phaseReal * pImaginary + phaseImaginary * pReal;
|
||||
vectorsReal[rowP] = cosine * pReal - sine * conjugatePhaseTimesQReal;
|
||||
vectorsImaginary[rowP] = cosine * pImaginary - sine * conjugatePhaseTimesQImaginary;
|
||||
vectorsReal[rowQ] = sine * phaseTimesPReal + cosine * qReal;
|
||||
vectorsImaginary[rowQ] = sine * phaseTimesPImaginary + cosine * qImaginary;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function canonicalizeColumnPhases(real, imaginary, order) {
|
||||
for (let column = 0; column < order; column += 1) {
|
||||
let pivotRow = 0;
|
||||
let pivotMagnitude = -1;
|
||||
for (let row = 0; row < order; row += 1) {
|
||||
const index = row * order + column;
|
||||
const magnitude = Math.hypot(real[index], imaginary[index]);
|
||||
if (magnitude > pivotMagnitude) {
|
||||
pivotMagnitude = magnitude;
|
||||
pivotRow = row;
|
||||
}
|
||||
}
|
||||
const pivotIndex = pivotRow * order + column;
|
||||
const pivotReal = real[pivotIndex];
|
||||
const pivotImaginary = imaginary[pivotIndex];
|
||||
const normalizedPivotMagnitude = Math.hypot(pivotReal, pivotImaginary);
|
||||
if (!(normalizedPivotMagnitude > 0) || !Number.isFinite(normalizedPivotMagnitude)) {
|
||||
throw new RangeError("Jacobi eigenvector phase canonicalization failed.");
|
||||
}
|
||||
const phaseReal = pivotReal / normalizedPivotMagnitude;
|
||||
const phaseImaginary = -pivotImaginary / normalizedPivotMagnitude;
|
||||
for (let row = 0; row < order; row += 1) {
|
||||
const index = row * order + column;
|
||||
const valueReal = real[index];
|
||||
const valueImaginary = imaginary[index];
|
||||
real[index] = valueReal * phaseReal - valueImaginary * phaseImaginary;
|
||||
imaginary[index] = valueReal * phaseImaginary + valueImaginary * phaseReal;
|
||||
}
|
||||
real[pivotIndex] = normalizedPivotMagnitude;
|
||||
imaginary[pivotIndex] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
function projectedInputEntry(real, imaginary, order, row, column, scale) {
|
||||
if (row === column) return [real[row * order + row] / scale, 0];
|
||||
const forward = row * order + column;
|
||||
const reverse = column * order + row;
|
||||
return [
|
||||
(real[forward] + real[reverse]) / (2 * scale),
|
||||
(imaginary[forward] - imaginary[reverse]) / (2 * scale),
|
||||
];
|
||||
}
|
||||
|
||||
function residualFrobeniusNorm(
|
||||
inputReal,
|
||||
inputImaginary,
|
||||
vectorsReal,
|
||||
vectorsImaginary,
|
||||
normalizedEigenvalues,
|
||||
order,
|
||||
scale,
|
||||
) {
|
||||
if (scale === 0) return 0;
|
||||
let residualSquared = 0;
|
||||
for (let column = 0; column < order; column += 1) {
|
||||
const eigenvalue = normalizedEigenvalues[column];
|
||||
for (let row = 0; row < order; row += 1) {
|
||||
let productReal = 0;
|
||||
let productImaginary = 0;
|
||||
for (let inner = 0; inner < order; inner += 1) {
|
||||
const [matrixReal, matrixImaginary] = projectedInputEntry(
|
||||
inputReal,
|
||||
inputImaginary,
|
||||
order,
|
||||
row,
|
||||
inner,
|
||||
scale,
|
||||
);
|
||||
const vectorIndex = inner * order + column;
|
||||
const vectorReal = vectorsReal[vectorIndex];
|
||||
const vectorImaginary = vectorsImaginary[vectorIndex];
|
||||
productReal += matrixReal * vectorReal - matrixImaginary * vectorImaginary;
|
||||
productImaginary += matrixReal * vectorImaginary + matrixImaginary * vectorReal;
|
||||
}
|
||||
const vectorIndex = row * order + column;
|
||||
const differenceReal = productReal - eigenvalue * vectorsReal[vectorIndex];
|
||||
const differenceImaginary =
|
||||
productImaginary - eigenvalue * vectorsImaginary[vectorIndex];
|
||||
residualSquared +=
|
||||
differenceReal * differenceReal + differenceImaginary * differenceImaginary;
|
||||
}
|
||||
}
|
||||
return Math.sqrt(residualSquared);
|
||||
}
|
||||
|
||||
function orthogonalityError(real, imaginary, order) {
|
||||
let errorSquared = 0;
|
||||
for (let left = 0; left < order; left += 1) {
|
||||
for (let right = 0; right < order; right += 1) {
|
||||
let productReal = 0;
|
||||
let productImaginary = 0;
|
||||
for (let row = 0; row < order; row += 1) {
|
||||
const leftIndex = row * order + left;
|
||||
const rightIndex = row * order + right;
|
||||
productReal +=
|
||||
real[leftIndex] * real[rightIndex] +
|
||||
imaginary[leftIndex] * imaginary[rightIndex];
|
||||
productImaginary +=
|
||||
real[leftIndex] * imaginary[rightIndex] -
|
||||
imaginary[leftIndex] * real[rightIndex];
|
||||
}
|
||||
if (left === right) productReal -= 1;
|
||||
errorSquared += productReal * productReal + productImaginary * productImaginary;
|
||||
}
|
||||
}
|
||||
return Math.sqrt(errorSquared);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deterministic full cyclic Jacobi decomposition of a complex Hermitian
|
||||
* matrix. Split-complex matrices are row-major; eigenvectors are returned as
|
||||
* row-major columns, so component `row` of eigenvector `column` is at
|
||||
* `row * order + column`.
|
||||
*
|
||||
* The input is projected to its nearest Hermitian matrix only after its
|
||||
* anti-Hermitian discrepancy has passed the reported roundoff-scale audit.
|
||||
* No diagonal loading or eigenvalue flooring is performed.
|
||||
*/
|
||||
export function decomposeHermitian({
|
||||
real,
|
||||
imaginary,
|
||||
order: explicitOrder,
|
||||
maximumSweeps = DEFAULT_HERMITIAN_JACOBI_MAX_SWEEPS,
|
||||
relativeTolerance,
|
||||
maximumWorkBytes = DEFAULT_MAX_HERMITIAN_EIGEN_WORK_BYTES,
|
||||
maximumPairUpdates = DEFAULT_MAX_HERMITIAN_JACOBI_PAIR_UPDATES,
|
||||
}) {
|
||||
const order = inferOrder(real, imaginary, explicitOrder);
|
||||
const workload = estimateHermitianEigenWorkload({ order, maximumSweeps });
|
||||
if (!(maximumWorkBytes >= 0) || !Number.isFinite(maximumWorkBytes)) {
|
||||
throw new TypeError("maximumWorkBytes must be finite and nonnegative.");
|
||||
}
|
||||
if (!(maximumPairUpdates >= 0) || !Number.isFinite(maximumPairUpdates)) {
|
||||
throw new TypeError("maximumPairUpdates must be finite and nonnegative.");
|
||||
}
|
||||
if (workload.allocationBytes > maximumWorkBytes) {
|
||||
throw new RangeError(
|
||||
`Hermitian eigensolver requires ${workload.allocationBytes} typed-array bytes; limit is ${maximumWorkBytes}.`,
|
||||
);
|
||||
}
|
||||
if (workload.worstCasePairUpdates > maximumPairUpdates) {
|
||||
throw new RangeError(
|
||||
`Hermitian eigensolver worst-case work is ${workload.worstCasePairUpdates} pair updates; limit is ${maximumPairUpdates}.`,
|
||||
);
|
||||
}
|
||||
|
||||
let scale = 0;
|
||||
for (let index = 0; index < workload.matrixElementCount; index += 1) {
|
||||
const valueReal = real[index];
|
||||
const valueImaginary = imaginary[index];
|
||||
if (!Number.isFinite(valueReal) || !Number.isFinite(valueImaginary)) {
|
||||
throw new TypeError("Hermitian matrix components must be finite.");
|
||||
}
|
||||
// Component scaling avoids overflow in hypot() for otherwise finite
|
||||
// split-complex entries near Number.MAX_VALUE.
|
||||
scale = Math.max(scale, Math.abs(valueReal), Math.abs(valueImaginary));
|
||||
}
|
||||
|
||||
const inputHermitianTolerance =
|
||||
scale === 0 ? 0 : 32 * Number.EPSILON * Math.max(1, order) * scale;
|
||||
let inputHermitianError = 0;
|
||||
for (let row = 0; row < order; row += 1) {
|
||||
inputHermitianError = Math.max(
|
||||
inputHermitianError,
|
||||
Math.abs(imaginary[row * order + row]),
|
||||
);
|
||||
for (let column = row + 1; column < order; column += 1) {
|
||||
const forward = row * order + column;
|
||||
const reverse = column * order + row;
|
||||
inputHermitianError = Math.max(
|
||||
inputHermitianError,
|
||||
Math.hypot(
|
||||
real[forward] - real[reverse],
|
||||
imaginary[forward] + imaginary[reverse],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
if (inputHermitianError > inputHermitianTolerance) {
|
||||
throw new RangeError(
|
||||
`matrix is not Hermitian within roundoff: error ${inputHermitianError}, tolerance ${inputHermitianTolerance}.`,
|
||||
);
|
||||
}
|
||||
|
||||
const matrixReal = new Float64Array(workload.matrixElementCount);
|
||||
const matrixImaginary = new Float64Array(workload.matrixElementCount);
|
||||
const vectorsReal = new Float64Array(workload.matrixElementCount);
|
||||
const vectorsImaginary = new Float64Array(workload.matrixElementCount);
|
||||
const normalizedEigenvalues = new Float64Array(order);
|
||||
const eigenvalues = new Float64Array(order);
|
||||
|
||||
if (scale > 0) {
|
||||
for (let row = 0; row < order; row += 1) {
|
||||
const diagonal = row * order + row;
|
||||
matrixReal[diagonal] = real[diagonal] / scale;
|
||||
for (let column = row + 1; column < order; column += 1) {
|
||||
const forward = row * order + column;
|
||||
const reverse = column * order + row;
|
||||
const projectedReal = (real[forward] + real[reverse]) / (2 * scale);
|
||||
const projectedImaginary =
|
||||
(imaginary[forward] - imaginary[reverse]) / (2 * scale);
|
||||
matrixReal[forward] = projectedReal;
|
||||
matrixImaginary[forward] = projectedImaginary;
|
||||
matrixReal[reverse] = projectedReal;
|
||||
matrixImaginary[reverse] = -projectedImaginary;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (let index = 0; index < order; index += 1) {
|
||||
vectorsReal[index * order + index] = 1;
|
||||
}
|
||||
|
||||
const matrixNormNormalized = matrixFrobeniusNorm(
|
||||
matrixReal,
|
||||
matrixImaginary,
|
||||
order,
|
||||
);
|
||||
const effectiveRelativeTolerance = relativeTolerance ?? roundoffGamma(32 * order);
|
||||
if (!(effectiveRelativeTolerance > 0) || !Number.isFinite(effectiveRelativeTolerance)) {
|
||||
throw new TypeError("relativeTolerance must be finite and positive.");
|
||||
}
|
||||
const absoluteTolerance = effectiveRelativeTolerance * matrixNormNormalized;
|
||||
let offDiagonalNormalized = offDiagonalFrobeniusNorm(
|
||||
matrixReal,
|
||||
matrixImaginary,
|
||||
order,
|
||||
);
|
||||
let converged = offDiagonalNormalized <= absoluteTolerance;
|
||||
let sweeps = 0;
|
||||
let rotations = 0;
|
||||
const pairTolerance = workload.pairCount === 0
|
||||
? 0
|
||||
: absoluteTolerance / Math.sqrt(2 * workload.pairCount);
|
||||
|
||||
while (!converged && sweeps < maximumSweeps) {
|
||||
let sweepRotations = 0;
|
||||
for (let p = 0; p < order; p += 1) {
|
||||
for (let q = p + 1; q < order; q += 1) {
|
||||
const pq = p * order + q;
|
||||
if (Math.hypot(matrixReal[pq], matrixImaginary[pq]) <= pairTolerance) {
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
rotatePair(
|
||||
matrixReal,
|
||||
matrixImaginary,
|
||||
vectorsReal,
|
||||
vectorsImaginary,
|
||||
order,
|
||||
p,
|
||||
q,
|
||||
)
|
||||
) {
|
||||
sweepRotations += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
rotations += sweepRotations;
|
||||
sweeps += 1;
|
||||
offDiagonalNormalized = offDiagonalFrobeniusNorm(
|
||||
matrixReal,
|
||||
matrixImaginary,
|
||||
order,
|
||||
);
|
||||
converged = offDiagonalNormalized <= absoluteTolerance;
|
||||
if (sweepRotations === 0) break;
|
||||
}
|
||||
|
||||
const stableOrder = Array.from({ length: order }, (_, index) => ({
|
||||
index,
|
||||
value: matrixReal[index * order + index],
|
||||
}));
|
||||
stableOrder.sort((left, right) => {
|
||||
if (left.value > right.value) return -1;
|
||||
if (left.value < right.value) return 1;
|
||||
return left.index - right.index;
|
||||
});
|
||||
for (let column = 0; column < order; column += 1) {
|
||||
const sourceColumn = stableOrder[column].index;
|
||||
const normalizedEigenvalue = stableOrder[column].value;
|
||||
normalizedEigenvalues[column] = Object.is(normalizedEigenvalue, -0)
|
||||
? 0
|
||||
: normalizedEigenvalue;
|
||||
const physicalEigenvalue = normalizedEigenvalues[column] * scale;
|
||||
if (!Number.isFinite(physicalEigenvalue)) {
|
||||
throw new RangeError("Hermitian eigenvalue exceeds the finite Float64 range.");
|
||||
}
|
||||
eigenvalues[column] = Object.is(physicalEigenvalue, -0) ? 0 : physicalEigenvalue;
|
||||
for (let row = 0; row < order; row += 1) {
|
||||
const destination = row * order + column;
|
||||
const source = row * order + sourceColumn;
|
||||
matrixReal[destination] = vectorsReal[source];
|
||||
matrixImaginary[destination] = vectorsImaginary[source];
|
||||
}
|
||||
}
|
||||
// A product of unitary Jacobi rotations already has unit-norm columns.
|
||||
// Canonicalize phase only; do not hide orthogonality drift by renormalizing.
|
||||
canonicalizeColumnPhases(matrixReal, matrixImaginary, order);
|
||||
|
||||
const residualNormalized = residualFrobeniusNorm(
|
||||
real,
|
||||
imaginary,
|
||||
matrixReal,
|
||||
matrixImaginary,
|
||||
normalizedEigenvalues,
|
||||
order,
|
||||
scale,
|
||||
);
|
||||
const relativeResidual = matrixNormNormalized === 0
|
||||
? 0
|
||||
: residualNormalized / matrixNormNormalized;
|
||||
const relativeOffDiagonal = matrixNormNormalized === 0
|
||||
? 0
|
||||
: offDiagonalNormalized / matrixNormNormalized;
|
||||
const residualTolerance = roundoffGamma(128 * order);
|
||||
const orthogonalityTolerance = residualTolerance * Math.sqrt(order);
|
||||
const measuredOrthogonalityError = orthogonalityError(
|
||||
matrixReal,
|
||||
matrixImaginary,
|
||||
order,
|
||||
);
|
||||
const numericallyVerified =
|
||||
converged &&
|
||||
relativeResidual <= residualTolerance &&
|
||||
measuredOrthogonalityError <= orthogonalityTolerance;
|
||||
|
||||
return {
|
||||
order,
|
||||
eigenvalues,
|
||||
eigenvectors: {
|
||||
real: matrixReal,
|
||||
imaginary: matrixImaginary,
|
||||
layout: "row-major-columns",
|
||||
},
|
||||
converged,
|
||||
sweeps,
|
||||
rotations,
|
||||
scale,
|
||||
inputHermitianError,
|
||||
inputHermitianTolerance,
|
||||
relativeTolerance: effectiveRelativeTolerance,
|
||||
offDiagonalFrobeniusNorm: offDiagonalNormalized * scale,
|
||||
relativeOffDiagonal,
|
||||
residualFrobeniusNorm: residualNormalized * scale,
|
||||
relativeResidual,
|
||||
residualTolerance,
|
||||
orthogonalityError: measuredOrthogonalityError,
|
||||
orthogonalityTolerance,
|
||||
numericallyVerified,
|
||||
workload,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user