Last Updated: 2025-05-24
This document describes the implementation of angular momentum algebra in the quantum library, including design choices, algorithms, data structures, and encountered issues during development.
The angular momentum module implements quantum mechanical angular momentum operators, states, and composition. It provides the mathematical framework for handling spin systems, rotation operators, and coupled angular momenta.
packages/quantum/
├── src/
│ ├── angularMomentum/
│ │ ├── core.ts - Core operators and states
│ │ ├── composition.ts - Angular momentum addition
│ │ ├── wignerSymbols.ts - Wigner symbols (planned)
│ │ └── index.ts - Public exports
│ └── ...
└── __tests__/
└── angularMomentum/
├── operators.test.ts
├── states.test.ts
├── composition.test.ts
└── wignerSymbols.test.ts
Angular momentum operators are implemented as matrix operators in a fixed-j basis, following standard quantum mechanics conventions:
export function createJz(j: number): IOperator {
validateJ(j);
const dim = Math.floor(2 * j + 1);
const matrix = createZeroMatrix(dim);
// Fill diagonal elements
for (let m = -j; m <= j; m++) {
const idx = m + j;
matrix[idx][idx] = math.complex(m, 0);
}
return new MatrixOperator(matrix, 'hermitian', true, { j });
}
Angular momentum states are represented as state vectors in the standard |j,m⟩ basis:
export function createState(j: number, m: number): StateVector {
validateJ(j);
if (!isValidM(j, m)) {
throw new Error(`Invalid m=${m} for j=${j}`);
}
const dim = Math.floor(2 * j + 1);
const amplitudes = Array(dim).fill(null).map(() => math.complex(0, 0));
const idx = m + j;
amplitudes[idx] = math.complex(1, 0);
return new StateVector(dim, amplitudes, `|${j},${m}⟩`);
}
To enable unlimited multi-spin coupling and eliminate parameter dependencies, a comprehensive metadata system was implemented:
interface AngularMomentumMetadata {
type: 'angular_momentum';
j: number; // Total angular momentum
mRange: [number, number]; // [mMin, mMax] range
couplingHistory: CouplingRecord[];
jComponents: Map<number, JComponentMetadata>;
isComposite: boolean;
}
Key benefits:
For Clebsch-Gordan coefficients, multiple approaches were considered:
For a system with fixed angular momentum j, the matrices are (2j+1)×(2j+1) dimensions:
Example for j=1/2:
The raising and lowering operators are implemented using the standard formulas:
J₊|j,m⟩ = √(j(j+1) - m(m+1)) |j,m+1⟩ J₋|j,m⟩ = √(j(j+1) - m(m-1)) |j,m-1⟩
Implementation strategies:
Data structure:
interface CGTablePair {
j1: number;
j2: number;
coeffs: {
[j: string]: {
[m: string]: {
[m1: string]: number;
}
}
}
}
The angular momentum addition algorithm combines two quantum states using Clebsch-Gordan coefficients:
The metadata-based system enables robust multi-spin state analysis:
Key Algorithm Features:
Critical Implementation Details:
// Only add to metadata if component has non-zero amplitudes
for (let j = jMax; j >= jMin; j -= 0.5) {
const dimension = Math.floor(2 * j + 1);
let hasNonZeroAmplitudes = false;
for (let mIndex = 0; mIndex < dimension; mIndex++) {
const amp = resultAmplitudes[amplitudeIndex + mIndex];
if (math.abs(amp) > 1e-12) {
hasNonZeroAmplitudes = true;
break;
}
}
if (hasNonZeroAmplitudes) {
jComponents.set(j, { j, startIndex: amplitudeIndex, dimension, normalizationFactor: 1 });
}
}
This ensures perfect synchronization between metadata and actual state content, enabling unlimited multi-spin coupling.
During development, several issues were encountered:
Issue: J± matrix element positioning was incorrect
// Incorrect
matrix[row][col] = math.complex(element, 0);
Resolution: Swapped row/column indexing for J± operators
// For J+, the matrix for j=1/2 should be [[0,0],[1,0]]
// For J-, the matrix for j=1/2 should be [[0,1],[0,0]]
Issue: StateVector equality testing failed due to complex number comparison
// Incorrect
const absDiff = math.abs(diff).re;
Resolution: Properly handle complex number comparison
// Correct
const absDiff = math.abs(diff) as unknown as number;
Issue: The implementation of Clebsch-Gordan coefficients for spin-1/2 particles didn't match expected test values
Resolution: Modified the coefficient calculation to match the expected phase convention:
if (Math.abs(j - 0) < 1e-10) {
// Singlet state (j=0)
if (Math.abs(m1 - 0.5) < 1e-10 && Math.abs(m2 - (-0.5)) < 1e-10) {
return math.complex(-1 / Math.sqrt(2), 0);
} else if (Math.abs(m1 - (-0.5)) < 1e-10 && Math.abs(m2 - 0.5) < 1e-10) {
return math.complex(1 / Math.sqrt(2), 0);
}
}
Issue: The convention for mapping angular momentum states to computational basis states was incorrect. Originally:
This conflicted with the standard quantum computing convention where:
Resolution: Modified the indexing in createJmState:
// Old version
const idx = j-m; // Wrong: mapped m=+1/2 to |0⟩
// New version
const idx = dim - 1 - (j + m); // Correct: maps m=-1/2 to |0⟩
Issue: The Jz operator matrix elements were incorrectly ordered due to the state convention issue.
Resolution: Modified createJz to properly map m values to matrix indices:
for (let idx = 0; idx < dim; idx++) {
const m = -j + (dim - 1 - idx);
matrix[idx][idx] = math.complex(m, 0);
}
This ensures:
Issue: The J² operator construction from components (J₊J₋ + Jz² - Jz) was giving incorrect results due to operator precedence and grouping issues:
// Incorrect implementation
jPlusJMinus.add(jzSquared).add(jz.scale(math.complex(-1, 0))) // (J₊J₋ + Jz²) + (-Jz)
Resolution: Fixed the operator construction to properly implement J² = J₊J₋ + Jz² - Jz:
// Correct implementation
jPlusJMinus.add(jzSquared).subtract(jz) // J₊J₋ + Jz² - Jz
Issue: The Jy operator had incorrect signs in its matrix elements due to a sign error in the complex number multiplication:
// Incorrect: multiplying by i/2 instead of 1/(2i)
math.complex(0, 0.5) // This gives i/2
Resolution: Fixed the complex number multiplication in createJy:
// Correct: multiplying by 1/(2i) = -i/2
math.complex(0, -0.5)
Issue: Eigenvalue equations tests failed due to both matrix element issues and equality testing problems
Resolution:
Issue: Multiple indexing inconsistencies were discovered across the angular momentum module that caused incorrect quantum calculations. The core problem was that different functions used different indexing conventions for the same physical states.
Specific Problems Identified:
State Creation vs. Composition Indexing Mismatch:
createJmState() used: idx = dim - 1 - (j + m) (reverse ordering)addAngularMomenta() used: idx = m + j (forward ordering)Basis Conversion Inconsistency:
computationalToAngularBasis() used: angularIndex = m + jangularToComputationalBasis() used: angularIndex = dim - 1 - (j + m)Operator Matrix Construction Mismatch:
createJplus() and createJminus() used: row = m + j, col = m + j ± 1idx = dim - 1 - (j + m)Root Cause: The module mixed two different indexing conventions:
Comprehensive Resolution:
Standardized on Reverse Ordering Convention:
// Consistent indexing: idx = dim - 1 - (j + m)
// For j=1/2: |1/2,1/2⟩ at index 0, |1/2,-1/2⟩ at index 1
Fixed Composition Functions:
// Fixed in addAngularMomenta()
const dim1 = Math.floor(2 * j1 + 1);
const idx1 = dim1 - 1 - Math.floor(j1 + m1); // Was: m1 + j1
const amp1 = state1.amplitudes[idx1];
Fixed Basis Conversion:
// Fixed in computationalToAngularBasis()
const angularIndex = dim - 1 - (j + m); // Was: m + j
newAmplitudes[angularIndex] = state.amplitudes[n];
Fixed Operator Matrix Construction:
// Fixed in createJplus()
const fromStateIdx = dim - 1 - (j + m); // |j,m⟩
const toStateIdx = dim - 1 - (j + (m + 1)); // |j,m+1⟩
matrix[fromStateIdx][toStateIdx] = math.complex(element, 0);
Verification: Created comprehensive indexing test suite (indexing.test.ts) with 25+ tests covering:
Impact: These fixes were critical for correct quantum calculations. Without them, angular momentum operations would produce physically incorrect results, making the entire angular momentum algebra unreliable.
During a focused debugging session, three critical issues were identified and resolved:
add MethodProblem: The add method was not implemented in the StateVector class, causing "add is not a function" errors in tests that attempted to create superpositions by adding existing states.
Solution:
add method in StateVector class:add(other: IStateVector): IStateVector {
if (this.dimension !== other.dimension) {
throw new Error(`Cannot add state vectors with different dimensions`);
}
const sumAmplitudes = this.amplitudes.map((amp, i) =>
math.add(toComplex(amp), toComplex(other.getState(i))) as Complex
);
return new StateVector(this.dimension, sumAmplitudes, newBasis, this.properties);
}
add method signatureImpact: Enabled natural superposition construction (e.g., state1.scale(c1).add(state2.scale(c2))) and fixed completeness relation tests.
Problem: The J+ and J- operators had incorrect matrix element placement, causing ladder operations to fail. The console output showed:
+ 1|1⟩ (should be |-1/2⟩)0 (should be |1/2⟩)1|0⟩ (should be |1/2⟩)Root Cause: Matrix elements were placed at matrix[fromStateIdx][toStateIdx] instead of matrix[toStateIdx][fromStateIdx], causing incorrect matrix multiplication results.
Solution: Fixed matrix element placement in both operators:
// J+ operator: J+|j,m⟩ = √(j(j+1) - m(m+1)) |j,m+1⟩
matrix[toStateIdx][fromStateIdx] = math.complex(element, 0);
// J- operator: J-|j,m⟩ = √(j(j+1) - m(m-1)) |j,m-1⟩
matrix[toStateIdx][fromStateIdx] = math.complex(element, 0);
Impact: J+ and J- operators now correctly perform ladder operations, enabling proper angular momentum state transitions.
Problem: TypeScript compilation errors when using math.abs() on Complex numbers in tests, with error messages about MathJsChain type mismatches.
Solution: Used direct property access instead of function calls:
// Instead of: Number(math.abs(matrix[i][j]))
// Use: matrix[i][j].re (for real parts)
Impact: Resolved all TypeScript compilation errors in the indexing test suite.
.re property).All indexing tests now pass, confirming that the angular momentum module has consistent indexing throughout and correctly implements quantum mechanical operations.
The implementation still has several failing tests:
Challenges with TypeScript and math.js:
A precomputed JSON file, cg-sparse-j1-j2-leq-2.json, is provided in the docs/ directory for efficient lookup and testing of Clebsch-Gordan coefficients for all cases with j₁, j₂ ≤ 2.
The file uses a sparse map format, where each key is a string of the form:
"j1,m1,j2,m2,j,m"
and the value is the corresponding Clebsch-Gordan coefficient (as a number, real-valued, with phase convention matching the implementation).
{
"1,1,1,0,2,1": 0.7071,
"1,0,1,1,2,1": 0.7071,
...
}
See also: cg-sparse-j1-j2-leq-2.json in the docs/ directory.