UNPKG

ts-quantum

Version:

TypeScript library for quantum mechanics calculations and utilities

896 lines 33 kB
"use strict"; /** * Quantum Matrix Operations * * This module provides pure functional implementations of matrix operations * specifically designed for quantum computations. It focuses on: * - Type safety through TypeScript * - Proper error handling with detailed messages * - Mathematical correctness and numerical stability * - Clean functional programming interface * * Key features: * - Complex matrix operations (multiplication, addition, scaling) * - Quantum-specific operations (tensor products, adjoints) * - Eigendecomposition with support for degenerate cases * - Numerical stability handling * - Comprehensive error checking * * @module quantum/matrixOperations */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.orthogonalizeStateVectors = exports.isUnitary = exports.isHermitian = exports.scaleMatrix = exports.normalizeMatrix = exports.addMatrices = exports.zeroMatrix = exports.eigenDecomposition = exports.tensorProduct = exports.matrixExponential = exports.transpose = exports.adjoint = exports.multiplyMatrices = void 0; const math = __importStar(require("mathjs")); const stateVector_1 = require("../states/stateVector"); // ==================== Configuration ==================== /** * Numerical threshold for floating point comparisons and noise reduction * * Used for: * - Comparing complex numbers for equality * - Cleaning up numerical noise in calculations * - Determining if a value is effectively zero * - Validating unitary and Hermitian properties * - Checking orthogonality of eigenvectors * * @constant * @default 1e-10 */ const NUMERICAL_THRESHOLD = 1e-10; // ==================== Validation Functions ==================== /** * Validates matrix structure and element types * * Checks for: * - Non-empty array structure * - Consistent row lengths * - Valid complex numbers in all positions * * @param matrix - Matrix to validate * @returns Validation result with error message if invalid */ function validateMatrix(matrix) { if (!Array.isArray(matrix) || matrix.length === 0) { return { valid: false, error: 'Matrix must be non-empty array' }; } const cols = matrix[0]?.length; if (!cols) { return { valid: false, error: 'Matrix must have at least one column' }; } for (let i = 0; i < matrix.length; i++) { if (!Array.isArray(matrix[i]) || matrix[i].length !== cols) { return { valid: false, error: `Inconsistent row length at row ${i}` }; } for (let j = 0; j < cols; j++) { const element = matrix[i][j]; if (!element || typeof element.re !== 'number' || typeof element.im !== 'number' || isNaN(element.re) || isNaN(element.im)) { return { valid: false, error: `Invalid complex number at position [${i},${j}]` }; } } } return { valid: true }; } /** * Validates matrix dimensions for multiplication * * Ensures that the number of columns in the first matrix * equals the number of rows in the second matrix * * @param a - First matrix * @param b - Second matrix * @returns Validation result with error message if dimensions are incompatible */ function validateMultiplicationDimensions(a, b) { if (!a[0] || !b[0]) { return { valid: false, error: 'Empty matrix provided' }; } if (a[0].length !== b.length) { return { valid: false, error: `Invalid dimensions for multiplication: ${a.length}x${a[0].length} and ${b.length}x${b[0].length}` }; } return { valid: true }; } /** * Validates that a matrix is square (same number of rows and columns) * * @param matrix - Matrix to validate * @returns Validation result with error message if not square */ function validateSquareMatrix(matrix) { if (!matrix[0] || matrix.length !== matrix[0].length) { return { valid: false, error: 'Matrix must be square' }; } return { valid: true }; } // ==================== Utility Functions ==================== /** * Cleans up numerical noise in results by zeroing very small values * * @param value - Numerical value to clean * @returns Cleaned value (0 if absolute value is below threshold) */ function cleanupNumericalNoise(value, precision = NUMERICAL_THRESHOLD) { return Math.abs(value) < precision ? 0 : value; } /** * Converts ComplexMatrix to math.js matrix format * * @param matrix - ComplexMatrix to convert * @returns math.js Matrix equivalent */ function toMathMatrix(matrix) { return math.matrix(matrix.map(row => row.map(x => math.complex(x.re, x.im)))); } /** * Converts math.js matrix to ComplexMatrix format * * Includes cleanup of numerical noise in the conversion process * * @param matrix - math.js Matrix to convert * @returns ComplexMatrix equivalent with cleaned numerical values */ function fromMathMatrix(matrix) { const data = matrix.valueOf(); return data.map(row => row.map(elem => math.complex(cleanupNumericalNoise(elem.re), cleanupNumericalNoise(elem.im)))); } // ==================== Core Matrix Operations ==================== /** * Multiplies two complex matrices using standard matrix multiplication * * For matrices A (m×n) and B (n×p), computes the product C = AB (m×p) where: * C[i,j] = Σ(k=0 to n-1) A[i,k] * B[k,j] * * In quantum mechanics, matrix multiplication represents: * - Sequential application of quantum operations * - Composition of quantum gates * - Transformation of quantum states * * @param a - First matrix (m×n) * @param b - Second matrix (n×p) * @returns Result matrix (m×p) * @throws Error if matrices have invalid dimensions or elements * * @example * // Multiply Hadamard gate by itself (H² = I) * const H = [[1/√2, 1/√2], [1/√2, -1/√2]]; * const HH = multiplyMatrices(H, H); // Should give identity matrix */ function multiplyMatrices(a, b) { const validationA = validateMatrix(a); if (!validationA.valid) { throw new Error(`First matrix invalid: ${validationA.error}`); } const validationB = validateMatrix(b); if (!validationB.valid) { throw new Error(`Second matrix invalid: ${validationB.error}`); } const dimValidation = validateMultiplicationDimensions(a, b); if (!dimValidation.valid) { throw new Error(dimValidation.error); } const matA = toMathMatrix(a); const matB = toMathMatrix(b); const result = math.multiply(matA, matB); return fromMathMatrix(result); } exports.multiplyMatrices = multiplyMatrices; /** * Computes the adjoint (conjugate transpose) of a matrix * * The adjoint A† of a matrix A is obtained by taking the complex conjugate * of each element and then transposing the matrix. For quantum operators, * the adjoint is essential for: * - Testing if an operator is Hermitian (A = A†) * - Testing if an operator is unitary (A†A = AA† = I) * - Computing expectation values ⟨ψ|A|ψ⟩ * * @param matrix - Input matrix * @returns Adjoint matrix * @throws Error if matrix is invalid * * @example * const matrix = [[{re: 1, im: 1}, {re: 0, im: 0}], * [{re: 0, im: 0}, {re: 1, im: -1}]]; * const adj = adjoint(matrix); // Conjugate transpose */ function adjoint(matrix) { const validation = validateMatrix(matrix); if (!validation.valid) { throw new Error(`Invalid matrix: ${validation.error}`); } // Compute adjoint using math.js Complex objects return matrix[0].map((_, colIndex) => matrix.map((row, rowIndex) => math.complex(matrix[rowIndex][colIndex].re, -matrix[rowIndex][colIndex].im))); } exports.adjoint = adjoint; /** * Computes the transpose of a matrix * * The transpose Aᵀ of a matrix A is obtained by swapping rows and columns. * * @param matrix - Input matrix * @returns Transpose matrix * @throws Error if matrix is invalid */ function transpose(matrix) { const validation = validateMatrix(matrix); if (!validation.valid) { throw new Error(`Invalid matrix: ${validation.error}`); } const rows = matrix.length; const cols = matrix[0].length; const result = Array(cols).fill(null).map(() => Array(rows).fill(null)); for (let i = 0; i < rows; i++) { for (let j = 0; j < cols; j++) { result[j][i] = matrix[i][j]; } } return result; } exports.transpose = transpose; /** * Computes tensor product (Kronecker product) of two matrices * * The tensor product A ⊗ B of matrices A and B is a matrix whose dimension * is the product of the dimensions of A and B. It represents the quantum * mechanical combination of two quantum systems. * * @param a - First matrix * @param b - Second matrix * @returns Tensor product matrix with dimensions (m*p) × (n*q) for m×n and p×q input matrices * @throws Error if either matrix is invalid * * @example * const a = [[1, 0], [0, 1]]; // 2×2 identity matrix * const b = [[0, 1], [1, 0]]; // Pauli X matrix * const result = tensorProduct(a, b); // 4×4 matrix */ /** * Computes matrix exponential exp(A) for a complex matrix A * * The matrix exponential is defined as: * exp(A) = I + A + A²/2! + A³/3! + ... * * In quantum mechanics, the matrix exponential is crucial for: * - Time evolution: U(t) = exp(-iHt/ħ) where H is the Hamiltonian * - Quantum gates: Many gates are exponentials of Pauli matrices * - Continuous transformations: exp(θA) gives continuous path of transformations * * @param matrix - Input matrix (must be square) * @returns Matrix exponential result * @throws Error if matrix is invalid or non-square * * @example * // Pauli X gate is exp(iπX/2) where X is the Pauli X matrix * const X = [[0, 1], [1, 0]]; * const theta = Math.PI/2; * const iX = scaleMatrix(X, {re: 0, im: theta}); * const expIX = matrixExponential(iX); */ function matrixExponential(matrix) { const validation = validateMatrix(matrix); if (!validation.valid) { throw new Error(`Invalid matrix: ${validation.error}`); } const squareValidation = validateSquareMatrix(matrix); if (!squareValidation.valid) { throw new Error(squareValidation.error); } const mathMatrix = toMathMatrix(matrix); const result = math.expm(mathMatrix); return fromMathMatrix(result); } exports.matrixExponential = matrixExponential; function tensorProduct(a, b) { const validationA = validateMatrix(a); if (!validationA.valid) { throw new Error(`First matrix invalid: ${validationA.error}`); } const validationB = validateMatrix(b); if (!validationB.valid) { throw new Error(`Second matrix invalid: ${validationB.error}`); } const matA = toMathMatrix(a); const matB = toMathMatrix(b); const result = math.kron(matA, matB); return fromMathMatrix(result); } exports.tensorProduct = tensorProduct; function eigenDecomposition(matrix, options = {}) { const validation = validateMatrix(matrix); if (!validation.valid) { throw new Error(`Invalid matrix: ${validation.error}`); } const squareValidation = validateSquareMatrix(matrix); if (!squareValidation.valid) { throw new Error(`Matrix must be square for eigendecomposition: ${squareValidation.error}`); } const mathMatrix = toMathMatrix(matrix); try { // Compute eigendecomposition using mathjs const result = math.eigs(mathMatrix); // Process eigenvalues const values = result.values.valueOf(); const complexValues = values.map(v => { if (typeof v === 'number') { return math.complex(v, 0); } return v; }); // If eigenvectors weren't requested, return early if (!options.computeEigenvectors) { return { values: complexValues }; } // Process eigenvectors let vectors; try { const eigenvectors = result.eigenvectors; // let vectors = eigenvectors; // Sort eigenvectors to match eigenvalue order const sortedEigenvectors = eigenvectors.sort((a, b) => { const aVal = math.isNumber(a.value) ? a.value : math.isComplex(a.value) ? a.value.re : Number(a.value.toString()); const bVal = math.isNumber(b.value) ? b.value : math.isComplex(b.value) ? b.value.re : Number(b.value.toString()); return aVal - bVal; }); vectors = sortedEigenvectors.map(entry => { const vectorData = entry.vector.valueOf(); return vectorData.map(v => { if (typeof v === 'number') { return math.complex(v, 0); } return v; }); }); // Normalize vectors if requested if (options.enforceOrthogonality && vectors) { vectors = normalizeVectors(vectors); } } catch (error) { if (error instanceof Error) { console.warn(`Eigenvector computation warning: ${error.message}`); } else { console.warn('Unknown eigenvector computation error'); } return { values: complexValues }; } return { values: complexValues, vectors }; } catch (error) { if (error instanceof Error) { throw new Error(`Eigendecomposition failed: ${error.message}`); } else { throw new Error('Unknown eigendecomposition error'); } } } exports.eigenDecomposition = eigenDecomposition; function zeroMatrix(rows, cols) { const matrix = math.zeros(rows, cols); return matrix; } exports.zeroMatrix = zeroMatrix; /** * Normalizes a set of complex vectors and adjusts their phases * * For each vector: * 1. Computes the norm (length) of the vector * 2. Divides by the norm to get unit length * 3. Adjusts the phase so the first significant component is real and positive * * This is important in quantum mechanics where: * - Vectors must be normalized (unit length) * - Global phase is arbitrary but should be consistent * * @param vectors - Array of complex vectors to normalize * @returns Normalized vectors with consistent phases * @private */ function normalizeVectors(vectors) { const n = vectors.length; // First normalize all vectors let normalized = vectors.map(vector => { const normSquared = vector.reduce((sum, v) => { const abs = Number(math.abs(v)); return sum + abs * abs; }, 0); const norm = Math.sqrt(normSquared); return vector.map(v => math.divide(v, norm)); }); // For 2x2 case, we need to ensure the sign of the product of off-diagonal // elements in the reconstruction matches the original if (n === 2) { // Get signs of first components of both vectors const sign1 = Math.sign(normalized[0][0].re); const sign2 = Math.sign(normalized[1][0].re); // If product of signs is wrong, flip second vector const signProduct = sign1 * sign2; if (signProduct > 0) { // Should be negative for positive off-diagonal normalized[1] = normalized[1].map(v => math.multiply(v, -1)); } } else { // For larger matrices, use magnitude-based normalization normalized = normalized.map(vector => { const maxMagnitudeIdx = vector.reduce((maxIdx, v, idx) => { const currentMag = Math.sqrt(v.re * v.re + v.im * v.im); const maxMag = Math.sqrt(vector[maxIdx].re * vector[maxIdx].re + vector[maxIdx].im * vector[maxIdx].im); return currentMag > maxMag ? idx : maxIdx; }, 0); const maxComponent = vector[maxMagnitudeIdx]; const currentPhase = math.arg(maxComponent); const correction = math.exp(-currentPhase); return vector.map(v => math.multiply(v, correction)); }); } return normalized; } /** * Processes eigenvectors to ensure proper normalization and orthogonality * * Performs several key operations: * 1. Cleans up numerical noise in eigenvector components * 2. Groups degenerate eigenvectors (same eigenvalue) * 3. Orthogonalizes degenerate eigenvectors * 4. Normalizes all vectors to unit length * * This is crucial for quantum mechanics where: * - Eigenvectors form an orthonormal basis * - Degenerate eigenstates need special handling * - Numerical precision affects physical meaning * * @param vectors - Raw eigenvectors from computation * @param values - Corresponding eigenvalues * @param dimension - Matrix dimension * @param precision - Numerical precision threshold * @param enforceOrthogonality - Whether to enforce orthogonality * @returns Processed eigenvectors * @private */ function processEigenvectors(vectors, values, dimension, precision, enforceOrthogonality) { const vectorsArray = vectors.valueOf(); // Initial processing of eigenvectors let complexVectors = vectorsArray.map(vec => vec.map(v => math.complex(cleanupNumericalNoise(v.re, precision), cleanupNumericalNoise(v.im, precision)))); if (enforceOrthogonality) { // Group eigenvectors by eigenvalue (within precision) for degenerate case handling const degenerateGroups = groupDegenerateEigenvectors(complexVectors, values, precision); // Orthogonalize within each degenerate group complexVectors = orthogonalizeDegenerateEigenvectors(degenerateGroups, precision); } // Normalize all eigenvectors complexVectors = complexVectors.map(vector => { const norm = math.sqrt(vector.reduce((sum, v) => math.add(sum, math.multiply(math.conj(v), v)), math.complex(0, 0))); return vector.map(v => math.divide(v, norm)); }); return complexVectors; } /** * Groups eigenvectors by their corresponding eigenvalues * * In quantum mechanics, degenerate eigenstates (states with same energy/eigenvalue) * require special handling. This function: * 1. Groups vectors with eigenvalues that are equal within precision * 2. Creates a map of eigenvalue -> vectors for efficient processing * 3. Handles both real and complex eigenvalues * * @param vectors - Array of eigenvectors * @param values - Corresponding eigenvalues * @param precision - Numerical threshold for considering eigenvalues equal * @returns Map of eigenvalue groups to their corresponding vectors * @private */ function groupDegenerateEigenvectors(vectors, values, precision) { const groups = new Map(); vectors.forEach((vector, idx) => { const value = values[idx]; // Use rounded values as keys to group degenerate eigenvalues const key = `${roundToPrec(value.re, precision)},${roundToPrec(value.im, precision)}`; if (!groups.has(key)) { groups.set(key, []); } groups.get(key).push(vector); }); return groups; } /** * Orthogonalizes degenerate eigenvectors using modified Gram-Schmidt process * * For quantum systems with degenerate states, we need an orthonormal basis. * This function: * 1. Takes groups of degenerate vectors (same eigenvalue) * 2. Applies modified Gram-Schmidt for numerical stability * 3. Ensures resulting vectors are orthogonal and normalized * * The modified Gram-Schmidt process is more numerically stable than * classical Gram-Schmidt, which is important for quantum computations. * * @param groups - Map of eigenvalue groups to their vectors * @param precision - Numerical threshold for orthogonality * @returns Array of orthogonalized vectors * @private */ function orthogonalizeDegenerateEigenvectors(groups, precision) { const orthogonalVectors = []; Array.from(groups.values()).forEach(vectors => { if (vectors.length > 1) { // Apply modified Gram-Schmidt for numerical stability for (let i = 0; i < vectors.length; i++) { let vi = vectors[i]; // Orthogonalize against all previous vectors for (let j = 0; j < i; j++) { const vj = vectors[j]; const proj = computeProjection(vi, vj); vi = subtractVectors(vi, proj); } // Add non-zero orthogonalized vector const norm = vectorNorm(vi); if (norm > precision) { orthogonalVectors.push(vi); } } } else { // Single vector case - just add it orthogonalVectors.push(vectors[0]); } }); return orthogonalVectors; } /** * Computes the projection of vector v onto vector u * * The projection is given by: proj_u(v) = (⟨v|u⟩/⟨u|u⟩)u * where ⟨v|u⟩ is the inner product. * * This is used in: * - Gram-Schmidt orthogonalization * - Finding components of quantum states * - Decomposing states into basis vectors * * @param v - Vector to project * @param u - Vector to project onto * @returns Projection vector * @private */ function computeProjection(v, u) { const uDotU = innerProduct(u, u); const vDotU = innerProduct(v, u); const scalar = math.divide(vDotU, uDotU); return u.map(ui => math.multiply(ui, scalar)); } /** * Computes the inner product ⟨v|u⟩ of two complex vectors * * The inner product is defined as: Σᵢ v̄ᵢuᵢ * where v̄ᵢ is the complex conjugate of vᵢ * * This is fundamental in quantum mechanics for: * - Computing probability amplitudes * - Calculating expectation values * - Determining orthogonality * - Normalizing quantum states * * @param v - First vector * @param u - Second vector * @returns Complex inner product * @private */ function innerProduct(v, u) { return v.reduce((sum, vi, i) => math.add(sum, math.multiply(math.conj(vi), u[i])), math.complex(0, 0)); } /** * Subtracts two complex vectors component-wise * * Used in: * - Gram-Schmidt orthogonalization * - Error calculation * - Vector space operations * * @param v - First vector * @param u - Second vector * @returns v - u component-wise * @private */ function subtractVectors(v, u) { return v.map((vi, i) => math.subtract(vi, u[i])); } /** * Computes the norm (length) of a complex vector * * The norm is defined as: √(⟨v|v⟩) * where ⟨v|v⟩ is the inner product of v with itself * * Critical in quantum mechanics for: * - Normalizing quantum states * - Computing probabilities * - Validating unitary transformations * * @param v - Complex vector * @returns Norm of the vector * @private */ function vectorNorm(v) { const normComplex = math.sqrt(innerProduct(v, v)); return Math.sqrt(normComplex.re * normComplex.re + normComplex.im * normComplex.im); } /** * Rounds a number to a specified precision * * Used for: * - Handling numerical noise * - Comparing floating point numbers * - Grouping nearly-equal eigenvalues * * @param value - Number to round * @param precision - Precision threshold * @returns Rounded value * @private */ function roundToPrec(value, precision) { return Math.round(value / precision) * precision; } /** * Verifies the correctness of eigendecomposition * * Checks that Av = λv for each eigenpair (λ,v) within specified precision. * This verification is important because: * - Numerical methods can accumulate errors * - Degenerate eigenvalues need special attention * - Physical meaning depends on mathematical accuracy * * @param matrix - Original matrix * @param values - Computed eigenvalues * @param vectors - Computed eigenvectors * @param precision - Tolerance for verification * @private */ function verifyDecomposition(matrix, values, vectors, precision) { // Verify each eigenpair vectors.forEach((vector, idx) => { const lambda = values[idx]; // Compute Av const Av = matrix.map(row => row.reduce((sum, aij, j) => math.add(sum, math.multiply(aij, vector[j])), math.complex(0, 0))); // Compute λv const lambdaV = vector.map(v => math.multiply(lambda, v)); // Check if Av = λv within precision Av.forEach((av, i) => { const diff = math.subtract(av, lambdaV[i]); const error = Math.sqrt(diff.re * diff.re + diff.im * diff.im); if (error > precision) { console.warn(`Eigenpair verification failed at index ${idx}, component ${i} with error ${error}`); } }); }); } /** * Adds two matrices * * @param a First matrix * @param b Second matrix * @returns Sum matrix * @throws Error if matrices have incompatible dimensions */ function addMatrices(a, b) { const validationA = validateMatrix(a); if (!validationA.valid) { throw new Error(`First matrix invalid: ${validationA.error}`); } const validationB = validateMatrix(b); if (!validationB.valid) { throw new Error(`Second matrix invalid: ${validationB.error}`); } if (a.length !== b.length || a[0].length !== b[0].length) { throw new Error('Matrices must have same dimensions for addition'); } const matA = toMathMatrix(a); const matB = toMathMatrix(b); const result = math.add(matA, matB); return fromMathMatrix(result); } exports.addMatrices = addMatrices; /** * Scales a matrix by a complex number * * @param matrix Input matrix * @param scalar Complex scaling factor * @returns Scaled matrix * @throws Error if matrix is invalid */ /** * Normalizes a matrix by dividing by its trace * This is particularly useful for density matrices which must have trace 1 * * @param matrix Input matrix * @returns Normalized matrix with trace 1 * @throws Error if matrix is invalid or has zero trace */ function normalizeMatrix(matrix) { const validation = validateMatrix(matrix); if (!validation.valid) { throw new Error(`Invalid matrix: ${validation.error}`); } const squareValidation = validateSquareMatrix(matrix); if (!squareValidation.valid) { throw new Error(squareValidation.error); } // Calculate trace let trace = math.complex(0, 0); for (let i = 0; i < matrix.length; i++) { trace = math.add(trace, matrix[i][i]); } // Check if trace is zero if (Math.abs(trace.re) < NUMERICAL_THRESHOLD && Math.abs(trace.im) < NUMERICAL_THRESHOLD) { throw new Error('Cannot normalize matrix with zero trace'); } // Scale matrix by 1/trace const scalar = math.divide(1, trace); return scaleMatrix(matrix, scalar); } exports.normalizeMatrix = normalizeMatrix; function scaleMatrix(matrix, scalar) { const validation = validateMatrix(matrix); if (!validation.valid) { throw new Error(`Invalid matrix: ${validation.error}`); } const matM = toMathMatrix(matrix); const s = math.complex(scalar.re, scalar.im); const result = math.multiply(matM, s); return fromMathMatrix(result); } exports.scaleMatrix = scaleMatrix; /** * Checks if a matrix is Hermitian (self-adjoint) * * @param matrix Input matrix * @param tolerance Numerical tolerance for comparison * @returns true if matrix is Hermitian * @throws Error if matrix is invalid or non-square */ function isHermitian(matrix, tolerance = NUMERICAL_THRESHOLD) { const validation = validateMatrix(matrix); if (!validation.valid) { throw new Error(`Invalid matrix: ${validation.error}`); } const squareValidation = validateSquareMatrix(matrix); if (!squareValidation.valid) { throw new Error(squareValidation.error); } const adj = adjoint(matrix); for (let i = 0; i < matrix.length; i++) { for (let j = 0; j < matrix[0].length; j++) { const diff = { re: Math.abs(matrix[i][j].re - adj[i][j].re), im: Math.abs(matrix[i][j].im - adj[i][j].im) }; if (diff.re > tolerance || diff.im > tolerance) { return false; } } } return true; } exports.isHermitian = isHermitian; /** * Checks if a matrix is unitary * * @param matrix Input matrix * @param tolerance Numerical tolerance for comparison * @returns true if matrix is unitary * @throws Error if matrix is invalid or non-square */ function isUnitary(matrix, tolerance = NUMERICAL_THRESHOLD) { const validation = validateMatrix(matrix); if (!validation.valid) { throw new Error(`Invalid matrix: ${validation.error}`); } const squareValidation = validateSquareMatrix(matrix); if (!squareValidation.valid) { throw new Error(squareValidation.error); } const adj = adjoint(matrix); const product = multiplyMatrices(matrix, adj); const n = matrix.length; for (let i = 0; i < n; i++) { for (let j = 0; j < n; j++) { const expected = i === j ? 1 : 0; const diff = { re: Math.abs(product[i][j].re - expected), im: Math.abs(product[i][j].im) }; if (diff.re > tolerance || diff.im > tolerance) { return false; } } } return true; } exports.isUnitary = isUnitary; /** * Orthogonalizes a set of state vectors using modified Gram-Schmidt process * * This function provides a convenient wrapper around the internal degenerate * eigenvector orthogonalization for use with IStateVector objects. It: * 1. Converts IStateVector objects to ComplexMatrix format * 2. Groups vectors by eigenvalue (assumes all have same eigenvalue) * 3. Applies the validated orthogonalization algorithm * 4. Converts back to IStateVector objects * * Useful for: * - Orthogonalizing intertwiner basis states * - Processing degenerate quantum states * - Ensuring orthonormal bases in quantum calculations * * @param stateVectors - Array of state vectors to orthogonalize * @param precision - Numerical threshold for orthogonality (default: 1e-10) * @returns Array of orthogonalized state vectors * @throws Error if state vectors have different dimensions */ function orthogonalizeStateVectors(stateVectors, precision = NUMERICAL_THRESHOLD) { if (stateVectors.length === 0) { return []; } // Validate dimensions const dimension = stateVectors[0].dimension; for (const state of stateVectors) { if (state.dimension !== dimension) { throw new Error(`All state vectors must have same dimension. Expected ${dimension}, got ${state.dimension}`); } } // Convert to ComplexMatrix format (each state vector is a column) const complexMatrix = stateVectors.map(state => state.getAmplitudes()); // Create dummy eigenvalues (all the same since we want to orthogonalize as a group) const eigenvalues = stateVectors.map(() => math.complex(1, 0)); // Group all vectors together (they all have the same dummy eigenvalue) const groups = new Map(); groups.set('1,0', complexMatrix); // Apply orthogonalization const orthogonalizedMatrix = orthogonalizeDegenerateEigenvectors(groups, precision); // Convert back to IStateVector objects const result = []; for (let i = 0; i < orthogonalizedMatrix.length && i < stateVectors.length; i++) { const originalState = stateVectors[i]; const orthogonalizedAmplitudes = orthogonalizedMatrix[i]; // Create new StateVector with orthogonalized amplitudes const newState = new stateVector_1.StateVector(dimension, orthogonalizedAmplitudes, originalState.basis ? `orth(${originalState.basis})` : 'orthogonalized', originalState.properties); result.push(newState); } return result; } exports.orthogonalizeStateVectors = orthogonalizeStateVectors; //# sourceMappingURL=matrixOperations.js.map