Source code for lightweaver.zeeman

from dataclasses import dataclass
from fractions import Fraction
from typing import TYPE_CHECKING, Iterator, Optional, cast

import numpy as np

if TYPE_CHECKING:
    from .atomic_model import AtomicLine


[docs] def fraction_range( start: Fraction, stop: Fraction, step: Fraction = Fraction(1, 1) ) -> Iterator[Fraction]: """ Works like range, but with Fractions. Does no checking, so best to make sure the range you're asking for is sane and divides down properly. """ while start < stop: yield start start += step
[docs] @dataclass class ZeemanComponents: """ Storage for communicating the Zeeman components between functions, also shared with the backend, giving a slightly tighter contract than usual: all arrays must be contiguous and alpha must be of dtype np.int32. """ alpha: np.ndarray strength: np.ndarray shift: np.ndarray
[docs] def zeeman_strength(Ju: Fraction, Mu: Fraction, Jl: Fraction, Ml: Fraction) -> float: """ Computes the strength of a Zeeman component, following del Toro Iniesta (p. 137) albeit larger by a factor of 2 which is corrected by normalisation. Takes J upper and lower (u and l respectively), and M upper and lower. """ alpha = int(Ml - Mu) dJ = int(Ju - Jl) # These parameters are x2 those in del Toro Iniesta (p. 137), but we # normalise after the fact, so it's fine if dJ == 0: # jMin = ju = jl if alpha == 0: # pi trainsitions s = 2.0 * Mu**2 elif alpha == -1: # sigma_b transitions s = (Ju + Mu) * (Ju - Mu + 1.0) elif alpha == 1: # sigma_r transitions s = (Ju - Mu) * (Ju + Mu + 1.0) elif dJ == 1: # jMin = jl, Mi = Ml if alpha == 0: # pi trainsitions s = 2.0 * ((Jl + 1) ** 2 - Ml**2) elif alpha == -1: # sigma_b transitions s = (Jl + Ml + 1) * (Jl + Ml + 2.0) elif alpha == 1: # sigma_r transitions s = (Jl - Ml + 1.0) * (Jl - Ml + 2.0) elif dJ == -1: # jMin = ju, Mi = Mu if alpha == 0: # pi trainsitions s = 2.0 * ((Ju + 1) ** 2 - Mu**2) elif alpha == -1: # sigma_b transitions s = (Ju - Mu + 1) * (Ju - Mu + 2.0) elif alpha == 1: # sigma_r transitions s = (Ju + Mu + 1.0) * (Ju + Mu + 2.0) else: raise ValueError('Invalid dJ: %d' % dJ) return float(s)
[docs] def lande_factor(J: Fraction, L: int, S: Fraction) -> float: """ Computes the Lande g-factor for an atomic level from the J, L, and S quantum numbers. """ if J == 0.0: return 0.0 return float(1.5 + (S * (S + 1.0) - L * (L + 1)) / (2.0 * J * (J + 1.0)))
[docs] def effective_lande(line: 'AtomicLine'): """ Computes the effective Lande g-factor for an atomic line. """ if line.g_lande_eff is not None: return line.g_lande_eff i = line.i_level j = line.j_level if any(x is None for x in [i.J, i.L, i.S, j.J, j.L, j.S]): raise ValueError( ( 'Cannot compute g_lande_eff as g_lande_eff not set and some of ' 'J, L and S None for line %s' ) % repr(line) ) gL = lande_factor(i.J, i.L, i.S) # type: ignore gU = lande_factor(j.J, j.L, j.S) # type: ignore return 0.5 * (gU + gL) + 0.25 * (gU - gL) * (j.J * (j.J + 1.0) - i.J * (i.J + 1.0)) # type: ignore
[docs] def compute_zeeman_components(line: 'AtomicLine') -> Optional[ZeemanComponents]: """ Computes, if possible, the set of Zeeman components for an atomic line. If g_lande_eff is specified on the line, then basic three-component Zeeman splitting will be computed directly. Otherwise, if both the lower and upper levels of the line support LS-coupling (i.e. J, L, and S all specified, and J <= L + S), then the LS-coupling formalism is applied to compute the components of "anomalous" Zeeman splitting. If neither of these cases are fulfilled, then None is returned. Parameters ---------- line : AtomicLine The line to attempt to compute the Zeeman components from. Returns ------- components : ZeemanComponents or None The Zeeman splitting components, if possible. """ # NOTE(cmo): Just do basic three-component Zeeman splitting if an effective # Lande g-factor is specified on the line. if line.g_lande_eff is not None: alpha = np.array([-1, 0, 1], dtype=np.int32) strength = np.ones(3) shift = alpha * line.g_lande_eff return ZeemanComponents(alpha, strength, shift) # NOTE(cmo): Do LS coupling ("anomalous" Zeeman splitting) if line.i_level.LS_coupling and line.j_level.LS_coupling: # Mypy... you're a pain sometimes... (even if you are technically correct) Jl = cast(Fraction, line.i_level.J) Ll = cast(int, line.i_level.L) Sl = cast(Fraction, line.i_level.S) Ju = cast(Fraction, line.j_level.J) Lu = cast(int, line.j_level.L) Su = cast(Fraction, line.j_level.S) gLl = lande_factor(Jl, Ll, Sl) gLu = lande_factor(Ju, Lu, Su) alpha = [] strength = [] shift = [] norm = np.zeros(3) for ml in fraction_range(-Jl, Jl + 1): for mu in fraction_range(-Ju, Ju + 1): if abs(ml - mu) <= 1.0: alpha.append(int(ml - mu)) shift.append(gLl * ml - gLu * mu) strength.append(zeeman_strength(Ju, mu, Jl, ml)) norm[alpha[-1] + 1] += strength[-1] alpha = np.array(alpha, dtype=np.int32) strength = np.array(strength) shift = np.array(shift) strength /= norm[alpha + 1] return ZeemanComponents(alpha, strength, shift) return None