ts-quantum
Version:
TypeScript library for quantum mechanics calculations and utilities
223 lines • 8.3 kB
JavaScript
/**
* Wigner symbols implementation
* Includes 3j, 6j, and 9j symbols for angular momentum coupling
*/
import { clebschGordan } from './composition';
import { logFactorial, triangleCoefficient } from '../utils/math';
import * as math from 'mathjs';
/**
* Validates triangle inequality for three angular momenta
*
* @param j1 First angular momentum
* @param j2 Second angular momentum
* @param j3 Third angular momentum
* @returns true if triangle inequality is satisfied
*/
export function isValidTriangle(j1, j2, j3) {
return (Math.abs(j1 - j2) <= j3 && j3 <= j1 + j2 &&
Math.abs(j2 - j3) <= j1 && j1 <= j2 + j3 &&
Math.abs(j3 - j1) <= j2 && j2 <= j3 + j1);
}
/**
* Validates all four triangle conditions for Wigner 6j symbols
*
* @param j1 Angular momentum j1
* @param j2 Angular momentum j2
* @param j3 Angular momentum j3
* @param l1 Angular momentum l1
* @param l2 Angular momentum l2
* @param l3 Angular momentum l3
* @returns true if all triangle conditions are satisfied
*/
function validate6jTriangles(j1, j2, j3, l1, l2, l3) {
return (isValidTriangle(j1, j2, j3) &&
isValidTriangle(j1, l2, l3) &&
isValidTriangle(l1, j2, l3) &&
isValidTriangle(l1, l2, j3));
}
/**
* Calculates phase factor (-1)^n
*
* @param n Power for phase factor
* @returns 1 or -1
*/
function phaseFactor(n) {
return Math.pow(-1, Math.round(n));
}
/**
* Validates Wigner 3j symbol quantum numbers
*
* @param j1 First angular momentum
* @param j2 Second angular momentum
* @param j3 Third angular momentum
* @param m1 First magnetic quantum number
* @param m2 Second magnetic quantum number
* @param m3 Third magnetic quantum number
* @returns true if all quantum numbers are valid
*/
function validateWigner3j(j1, j2, j3, m1, m2, m3) {
// Check that j values are non-negative
if (j1 < 0 || j2 < 0 || j3 < 0) {
return false;
}
// Check that m values are within bounds
if (Math.abs(m1) > j1 || Math.abs(m2) > j2 || Math.abs(m3) > j3) {
return false;
}
// Check triangle inequality
if (!isValidTriangle(j1, j2, j3)) {
return false;
}
// Check magnetic quantum number conservation
if (Math.abs(m1 + m2 + m3) > 1e-10) {
return false;
}
return true;
}
/**
* Calculates Wigner 3j symbol using Clebsch-Gordan coefficients
*
* The Wigner 3j symbol is related to Clebsch-Gordan coefficients by:
* (j1 j2 j3) = (-1)^(j1-j2-m3) / sqrt(2*j3+1) * ⟨j1,m1;j2,m2|j3,-m3⟩
* (m1 m2 m3)
*
* Based on verified formula from Sage/SymPy documentation:
* ⟨j₁ m₁ j₂ m₂ | j₃ m₃⟩ = (-1)^(j₁-j₂+m₃) * √(2j₃+1) * Wigner3j(j₁, j₂, j₃, m₁, m₂, -m₃)
*
* @param j1 First angular momentum
* @param j2 Second angular momentum
* @param j3 Third angular momentum
* @param m1 First magnetic quantum number
* @param m2 Second magnetic quantum number
* @param m3 Third magnetic quantum number
* @returns Wigner 3j symbol value
*/
export function wigner3j(j1, j2, j3, m1, m2, m3) {
// Validate quantum numbers
if (!validateWigner3j(j1, j2, j3, m1, m2, m3)) {
return math.complex(0, 0);
}
// Get Clebsch-Gordan coefficient ⟨j1,m1;j2,m2|j3,-m3⟩
// NOTE: Critical fix - using -m3 as the last argument
const cgCoeff = clebschGordan(j1, m1, j2, m2, j3, -m3);
// Phase factor: (-1)^(j1-j2+m3)
const phase = phaseFactor(j1 - j2 + m3);
// Normalization factor: 1/sqrt(2*j3+1)
const normalization = 1 / Math.sqrt(2 * j3 + 1);
// Combine all factors: phase * normalization * CG coefficient
const result = math.multiply(phase * normalization, cgCoeff);
return result;
}
/**
* Applies symmetry operation to Wigner 3j symbol
* There are 12 symmetry operations for 3j symbols
*
* @param j1 First angular momentum
* @param j2 Second angular momentum
* @param j3 Third angular momentum
* @param m1 First magnetic quantum number
* @param m2 Second magnetic quantum number
* @param m3 Third magnetic quantum number
* @param operation Symmetry operation index (0-11)
* @returns Transformed 3j symbol with phase factor
*/
export function wigner3jSymmetry(j1, j2, j3, m1, m2, m3, operation) {
const base = wigner3j(j1, j2, j3, m1, m2, m3);
switch (operation) {
case 0: // Identity
return { value: base, phase: 1 };
case 1: // Cyclic permutation (j1,j2,j3) → (j2,j3,j1)
return {
value: wigner3j(j2, j3, j1, m2, m3, m1),
phase: phaseFactor(j1 + j2 + j3)
};
case 2: // Cyclic permutation (j1,j2,j3) → (j3,j1,j2)
return {
value: wigner3j(j3, j1, j2, m3, m1, m2),
phase: phaseFactor(j1 + j2 + j3)
};
case 3: // Exchange first two: (j1,j2,j3) → (j2,j1,j3)
return {
value: wigner3j(j2, j1, j3, m2, m1, m3),
phase: phaseFactor(j1 + j2 + j3)
};
case 4: // Sign reversal: (m1,m2,m3) → (-m1,-m2,-m3)
return {
value: wigner3j(j1, j2, j3, -m1, -m2, -m3),
phase: phaseFactor(j1 + j2 + j3)
};
default:
return { value: base, phase: 1 };
}
}
/**
* Calculates Wigner 6j symbol using Racah's formula
*
* Uses the explicit sum formula:
* {j1 j2 j3} = Delta(j1,j2,j3)Delta(j1,l2,l3)Delta(l1,j2,l3)Delta(l1,l2,j3) ×
* {l1 l2 l3} Σ_z (-1)^z [(z-j1-j2-j3)!(z-j1-l2-l3)!(z-l1-j2-l3)!(z-l1-l2-j3)! ×
* (j1+j2+l1+l2-z)!(j2+j3+l2+l3-z)!(j3+j1+l3+l1-z)!]^(-1)
*
* where Delta(a,b,c) is the triangle coefficient.
*
* @param j1 Angular momentum j1
* @param j2 Angular momentum j2
* @param j3 Angular momentum j3
* @param l1 Angular momentum l1
* @param l2 Angular momentum l2
* @param l3 Angular momentum l3
* @returns Wigner 6j symbol value
*/
export function wigner6j(j1, j2, j3, l1, l2, l3) {
// Validate all triangle conditions
if (!validate6jTriangles(j1, j2, j3, l1, l2, l3)) {
return math.complex(0, 0);
}
// Check for negative angular momenta
if (j1 < 0 || j2 < 0 || j3 < 0 || l1 < 0 || l2 < 0 || l3 < 0) {
return math.complex(0, 0);
}
// Calculate triangle coefficients
const delta1 = triangleCoefficient(j1, j2, j3);
const delta2 = triangleCoefficient(j1, l2, l3);
const delta3 = triangleCoefficient(l1, j2, l3);
const delta4 = triangleCoefficient(l1, l2, j3);
// If any triangle coefficient is zero, the 6j symbol is zero
if (delta1 === 0 || delta2 === 0 || delta3 === 0 || delta4 === 0) {
return math.complex(0, 0);
}
// Calculate sum limits
const zMin = Math.max(j1 + j2 + j3, j1 + l2 + l3, l1 + j2 + l3, l1 + l2 + j3);
const zMax = Math.min(j1 + j2 + l1 + l2, j2 + j3 + l2 + l3, j3 + j1 + l3 + l1);
// Calculate sum using log factorials for numerical stability
let sumLog = -Infinity;
for (let z = Math.round(zMin); z <= Math.round(zMax); z++) {
// Calculate log of denominator terms
const logDenom = (logFactorial(z - Math.round(j1 + j2 + j3)) +
logFactorial(z - Math.round(j1 + l2 + l3)) +
logFactorial(z - Math.round(l1 + j2 + l3)) +
logFactorial(z - Math.round(l1 + l2 + j3)) +
logFactorial(Math.round(j1 + j2 + l1 + l2 - z)) +
logFactorial(Math.round(j2 + j3 + l2 + l3 - z)) +
logFactorial(Math.round(j3 + j1 + l3 + l1 - z)));
// Add term to sum (in log space)
const logTerm = -logDenom;
if (sumLog === -Infinity) {
sumLog = logTerm;
}
else {
// Use log sum exp trick for numerical stability
const maxLog = Math.max(sumLog, logTerm);
sumLog = maxLog + Math.log(Math.exp(sumLog - maxLog) + Math.exp(logTerm - maxLog));
}
}
// Calculate final result
const phaseFactor = Math.pow(-1, Math.round(j1 + j2 + j3 + l1 + l2 + l3));
const result = phaseFactor * delta1 * delta2 * delta3 * delta4 * Math.exp(sumLog);
return math.complex(result, 0);
}
export function wigner9j(j1, j2, j3, l1, l2, l3, k1, k2, k3) {
// TODO: Implement in Phase 3
return math.complex(0, 0);
}
//# sourceMappingURL=wignerSymbols.js.map