ts-quantum
Version:
TypeScript library for quantum mechanics calculations and utilities
367 lines • 13.5 kB
JavaScript
/**
* State Analysis and Decomposition for Angular Momentum States
*
* Phase 1 implementation for T66: Multi-Spin Coupling and Intertwiner Implementation
* Provides functions to analyze composite angular momentum states and extract j-components.
*/
import { StateVector } from '../states/stateVector';
import * as math from 'mathjs';
/**
* Analyzes a StateVector to determine its angular momentum structure
*
* This is the core function that enables multi-spin coupling by identifying
* what j-components are present in a composite angular momentum state.
*
* @param state The state vector to analyze
* @param j1 First angular momentum (if known from coupling history) - optional for backwards compatibility
* @param j2 Second angular momentum (if known from coupling history) - optional for backwards compatibility
* @returns Analysis results showing j-component structure
*/
function analyzeAngularState(state, j1, j2) {
// First try to get metadata from the state itself
const metadata = state.getAngularMomentumMetadata();
if (metadata) {
// Use metadata-based analysis (preferred)
return analyzeFromMetadata(state, metadata);
}
// Fallback to legacy analysis requiring j1, j2 parameters
const properties = state.properties;
let couplingInfo;
if (properties?.type === 'angular_momentum' && properties.j1 !== undefined && properties.j2 !== undefined) {
couplingInfo = { j1: properties.j1, j2: properties.j2 };
}
else if (j1 !== undefined && j2 !== undefined) {
couplingInfo = { j1, j2 };
}
if (!couplingInfo) {
// Cannot analyze without knowing the original coupling
return {
isAngularMomentum: false,
components: new Map(),
dominantJ: null,
isPure: false
};
}
// Calculate possible j values from triangle inequality
const jMin = Math.abs(couplingInfo.j1 - couplingInfo.j2);
const jMax = couplingInfo.j1 + couplingInfo.j2;
const possibleJ = [];
for (let j = jMin; j <= jMax; j += 0.5) {
possibleJ.push(j);
}
// Analyze each possible j-component
const components = new Map();
let dominantJ = null;
let dominantMagnitude = 0;
for (const j of possibleJ) {
const componentInfo = analyzeJComponent(state, j, couplingInfo.j1, couplingInfo.j2);
components.set(j, componentInfo);
if (componentInfo.isPresent && componentInfo.magnitude > dominantMagnitude) {
dominantMagnitude = componentInfo.magnitude;
dominantJ = j;
}
}
// Determine if this is a pure state (only one j-component present)
const presentComponents = Array.from(components.values()).filter(c => c.isPresent);
const isPure = presentComponents.length === 1;
return {
isAngularMomentum: true,
components,
dominantJ,
isPure,
couplingInfo
};
}
/**
* Analyzes angular momentum state using metadata (preferred method)
*/
function analyzeFromMetadata(state, metadata) {
const components = new Map();
let dominantJ = null;
let dominantMagnitude = 0;
// Analyze each J component from metadata
for (const [j, componentMetadata] of metadata.jComponents) {
const componentInfo = analyzeJComponentFromMetadata(state, j, componentMetadata);
components.set(j, componentInfo);
if (componentInfo.isPresent && componentInfo.magnitude > dominantMagnitude) {
dominantMagnitude = componentInfo.magnitude;
dominantJ = j;
}
}
// Determine if this is a pure state
const presentComponents = Array.from(components.values()).filter(c => c.isPresent);
const isPure = presentComponents.length === 1;
// Get latest coupling info from history
const latestCoupling = getLatestCoupling(metadata.couplingHistory);
return {
isAngularMomentum: true,
components,
dominantJ,
isPure,
couplingInfo: latestCoupling
};
}
/**
* Analyzes a J component using metadata
*/
function analyzeJComponentFromMetadata(state, j, componentMetadata) {
const dimension = componentMetadata.dimension;
const startIndex = componentMetadata.startIndex;
const mStates = new Map();
let totalAmplitudeSquared = 0;
// Extract amplitudes for each m value of this j-component
for (let mIndex = 0; mIndex < dimension; mIndex++) {
const m = j - mIndex; // m goes from +j to -j
const amplitudeIndex = startIndex + mIndex;
if (amplitudeIndex < state.dimension) {
const amplitude = state.amplitudes[amplitudeIndex];
mStates.set(m, amplitude);
totalAmplitudeSquared += math.abs(amplitude) ** 2;
}
else {
mStates.set(m, math.complex(0, 0));
}
}
const magnitude = Math.sqrt(totalAmplitudeSquared);
const totalAmplitude = math.complex(magnitude, 0);
return {
j: j,
dimension,
totalAmplitude,
magnitude,
mStates,
isPresent: magnitude > 1e-10
};
}
/**
* Gets the latest coupling information from coupling history
*/
function getLatestCoupling(couplingHistory) {
for (let i = couplingHistory.length - 1; i >= 0; i--) {
const record = couplingHistory[i];
if (record.operation === 'coupling' && record.j1 !== undefined && record.j2 !== undefined) {
return { j1: record.j1, j2: record.j2 };
}
}
return undefined;
}
/**
* Analyzes a specific j-component within a composite state
*
* Uses the inverse Clebsch-Gordan transformation to determine how much
* of the given j-component is present in the composite state.
*
* @param state Composite state to analyze
* @param targetJ The j value to analyze
* @param j1 First original angular momentum
* @param j2 Second original angular momentum
* @returns Information about this j-component
*/
function analyzeJComponent(state, targetJ, j1, j2) {
const dimension = Math.floor(2 * targetJ + 1);
const mStates = new Map();
let totalAmplitudeSquared = 0;
// Calculate expected position of this j-component in the state vector
const jMin = Math.abs(j1 - j2);
const jMax = j1 + j2;
let stateIndex = 0;
let targetStartIndex = -1;
// Find where this j-component starts in the amplitude array
for (let j = jMax; j >= jMin; j -= 0.5) {
if (Math.abs(j - targetJ) < 1e-10) {
targetStartIndex = stateIndex;
break;
}
stateIndex += Math.floor(2 * j + 1);
}
if (targetStartIndex === -1) {
// This j value is not possible for this coupling
return {
j: targetJ,
dimension,
totalAmplitude: math.complex(0, 0),
magnitude: 0,
mStates,
isPresent: false
};
}
// Extract amplitudes for each m value of this j-component
for (let m = targetJ; m >= -targetJ; m -= 1) {
const index = targetStartIndex + Math.floor(targetJ - m);
if (index < state.dimension) {
const amplitude = state.amplitudes[index];
mStates.set(m, amplitude);
totalAmplitudeSquared += math.abs(amplitude) ** 2;
}
else {
mStates.set(m, math.complex(0, 0));
}
}
const magnitude = Math.sqrt(totalAmplitudeSquared);
const totalAmplitude = math.complex(magnitude, 0); // Simplified for prototype
return {
j: targetJ,
dimension,
totalAmplitude,
magnitude,
mStates,
isPresent: magnitude > 1e-10
};
}
/**
* Extracts a specific j-component from a composite angular momentum state
*
* This is the key function that enables multi-spin coupling by extracting
* pure j-components that can be used with existing addAngularMomenta function.
*
* @param state Composite state containing multiple j-components
* @param targetJ The j value to extract
* @param j1 First original angular momentum (optional for backwards compatibility)
* @param j2 Second original angular momentum (optional for backwards compatibility)
* @returns Extracted pure j-component state, or null if not present
*/
function extractJComponent(state, targetJ, j1, j2) {
// First try metadata-based extraction (preferred)
const metadata = state.getAngularMomentumMetadata();
if (metadata) {
return extractJComponentFromMetadata(state, targetJ, metadata);
}
// Fallback to legacy analysis
const analysis = analyzeAngularState(state, j1, j2);
if (!analysis.isAngularMomentum) {
return null;
}
const componentInfo = analysis.components.get(targetJ);
if (!componentInfo || !componentInfo.isPresent) {
return null;
}
// Create pure state vector for this j-component
const pureDimension = Math.floor(2 * targetJ + 1);
const pureAmplitudes = [];
// Extract amplitudes in proper order for pure j-state
for (let m = targetJ; m >= -targetJ; m -= 1) {
const amplitude = componentInfo.mStates.get(m) || math.complex(0, 0);
pureAmplitudes.push(amplitude);
}
// Create the pure state vector
const pureState = new StateVector(pureDimension, pureAmplitudes, `|${targetJ}⟩`);
// Normalize the extracted component
const norm = pureState.norm();
if (norm < 1e-10) {
return null; // Component is essentially zero
}
const normalizedState = pureState.normalize();
return {
state: normalizedState,
j: targetJ,
normalizationFactor: 1 / norm,
originalMagnitude: componentInfo.magnitude
};
}
/**
* Extracts J component using metadata (preferred method)
*/
function extractJComponentFromMetadata(state, targetJ, metadata) {
const componentMetadata = metadata.jComponents.get(targetJ);
if (!componentMetadata) {
return null;
}
const { startIndex, dimension } = componentMetadata;
// Extract amplitudes directly using metadata indices
const pureAmplitudes = [];
let totalAmplitudeSquared = 0;
for (let i = 0; i < dimension; i++) {
const amplitudeIndex = startIndex + i;
if (amplitudeIndex < state.dimension) {
const amplitude = state.amplitudes[amplitudeIndex];
pureAmplitudes.push(amplitude);
totalAmplitudeSquared += math.abs(amplitude) ** 2;
}
else {
pureAmplitudes.push(math.complex(0, 0));
}
}
const magnitude = Math.sqrt(totalAmplitudeSquared);
if (magnitude < 1e-10) {
return null; // Component is essentially zero
}
// Create the pure state vector
const pureState = new StateVector(dimension, pureAmplitudes, `|${targetJ}⟩`);
// Add metadata to extracted state
const extractedMetadata = {
type: 'angular_momentum',
j: targetJ,
mRange: [-targetJ, targetJ],
couplingHistory: [...metadata.couplingHistory],
jComponents: new Map([[targetJ, {
j: targetJ,
startIndex: 0,
dimension: dimension,
normalizationFactor: 1
}]]),
isComposite: false
};
pureState.setAngularMomentumMetadata(extractedMetadata);
// Normalize the extracted component
const normalizedState = pureState.normalize();
return {
state: normalizedState,
j: targetJ,
normalizationFactor: 1 / magnitude,
originalMagnitude: magnitude
};
}
/**
* Determines if a StateVector has angular momentum decomposition information
*
* @param state StateVector to check
* @returns True if state has angular momentum metadata
*/
function hasAngularMomentumData(state) {
const properties = state.properties;
return properties?.type === 'angular_momentum' &&
properties.j1 !== undefined &&
properties.j2 !== undefined;
}
/**
* Gets coupling information from a StateVector if available
*
* @param state StateVector to examine
* @returns Coupling information or null if not available
*/
function getCouplingInfo(state) {
const properties = state.properties;
if (properties?.type === 'angular_momentum' &&
properties.j1 !== undefined &&
properties.j2 !== undefined) {
return { j1: properties.j1, j2: properties.j2 };
}
return null;
}
/**
* Creates a detailed string representation of the analysis results
*
* @param analysis Analysis results from analyzeAngularState
* @returns Human-readable description
*/
function analysisToString(analysis) {
if (!analysis.isAngularMomentum) {
return 'Not an angular momentum state';
}
let result = `Angular Momentum State Analysis:\n`;
result += ` Type: ${analysis.isPure ? 'Pure' : 'Mixed'} state\n`;
result += ` Dominant J: ${analysis.dominantJ}\n`;
if (analysis.couplingInfo) {
result += ` Original coupling: j1=${analysis.couplingInfo.j1}, j2=${analysis.couplingInfo.j2}\n`;
}
result += ` Components:\n`;
for (const [j, info] of analysis.components) {
if (info.isPresent) {
result += ` J=${j}: magnitude=${info.magnitude.toFixed(4)}, dim=${info.dimension}\n`;
}
}
return result;
}
// Export the public API
export { analyzeAngularState, extractJComponent, hasAngularMomentumData, getCouplingInfo, analysisToString };
//# sourceMappingURL=stateAnalysis.js.map