Source code for rydiqule.arc_utils

"""
Helper methods for interfacing with ARC atom classes
"""

from __future__ import annotations
from typing import TYPE_CHECKING, Literal, Dict, Callable, Tuple

from scipy.constants import pi, hbar, epsilon_0, physical_constants
from scipy.constants import c as C_c
from scipy.constants import h as C_h
from scipy.constants import e as C_e

import numpy as np

from math import sqrt

from .exceptions import AtomError
from .atom_utils import A_QState

if TYPE_CHECKING:
    import arc

C_a_0 = physical_constants["Bohr radius"][0]


[docs] class RQ_AlkaliAtom(object):
[docs] def __init__(self, arc_atom: arc.alkali_atom_functions.AlkaliAtom): """Rydiqule's wrapper class around ARC's Alkali atom classes. Designed predominantly for internal use, seldom needs to be accessed directly. Parameters ---------- arc_atom: :external+arc:class:`~arc.alkali_atom_functions.AlkaliAtom` ARC atom to use for calculations. Stored internally in the :attr:`atom` attribute. """ self.arc_atom = arc_atom "ARC atom with which to perform calculations." self._arc_dipole_functions: Dict[Tuple[str, str], Callable] = { ("HFS", "FS"): self.arc_atom.getDipoleMatrixElementHFStoFS, ("FS", "HFS"): self._getDipoleMatrixElementFStoHFS, ("HFS", "HFS"): self.arc_atom.getDipoleMatrixElementHFS, ("FS", "FS"): self.arc_atom.getDipoleMatrixElement, ("NLJ", "NLJ"): self._get_nlj_dipole }
[docs] def get_dipole_matrix_element(self, state1: A_QState, state2: A_QState, q: Literal[-1, 0, 1], s: float = 0.5) -> float: """Get dipole matrix element :math:`\\langle s1|e\\mathbf{r}|s2\\rangle` in units of :math:`a_0 e` If states 1 and 2 are sublevels, either FS or HFS, appropriate ARC function is used. If states 1 and 2 are NLJ, a simple average of the magnitudes of the dipole-allowed moments between mJ1 and mJ2 is returned. Cannot calculate dipole matrix elements between states with NLJ specification and those with either FS or HS splitting. ARC functions used are: - :external+arc:meth:`~arc.alkali_atom_functions.AlkaliAtom.getDipoleMatrixElement` - :external+arc:meth:`~arc.alkali_atom_functions.AlkaliAtom.getDipoleMatrixElementHFS` - :external+arc:meth:`~arc.alkali_atom_functions.AlkaliAtom.getDipoleMatrixElementHFStoFS` Parameters ---------- state1: A_QState `A_QState` namedtuple of quantum numbers for the quantum state :math:`s1`. state2: A_QState `A_QState` namedtuple of quantum numbers for the quantum state :math:`s2`. q: int Polarization of coupling field in spherical basis (+1, 0, -1), corresponding to :math:`\\sigma^+`, :math:`\\pi`, or :math:`\\sigma^-`. s: float, optional total spin angular momentum of the state. Default is 0.5 for Alkali atoms. Returns ------- float Dipole moment of the transition in atomic units (:math:`a_0 e`). Will be 0 if the transition is not dipole-allowed. Raises ------ AtomError If the two states to be coupled are in one each of the NLJ and FS/HFS definitions. Examples -------- >>> import arc >>> g_nlj = rq.A_QState(5, 0, 0.5) >>> g_fs = rq.A_QState(5, 0, 0.5, m_j=-0.5) >>> e_nlj = rq.A_QState(5, 1, 0.5) >>> e_hfs = rq.A_QState(5, 1, 0.5,f=2, m_f=0) >>> arc_atom = arc.alkali_atom_data.Rubidium85() >>> my_atom = rq.RQ_AlkaliAtom(arc_atom) >>> print(my_atom.get_dipole_matrix_element(g_nlj, e_nlj, q=0)) 1.7277475900721146 >>> print(my_atom.get_dipole_matrix_element(g_fs, e_hfs, q=0)) 1.2217020371187075 >>> print(my_atom.get_dipole_matrix_element(g_nlj, e_hfs, q=0)) # doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): File "<stdin>", line 1, in <module> rydiqule.exceptions.AtomError: Invalid transition type for dipole calculation. """ if (state1.stype, state2.stype) not in self._arc_dipole_functions.keys(): raise AtomError("Invalid transition type for dipole calculation. " + f"Allowed types are {list(self._arc_dipole_functions.keys())}") return self._arc_dipole_functions[(state1.stype, state2.stype)](*state1.qnums, *state2.qnums, q, s)
[docs] def get_transition_frequency(self, state1: A_QState, state2: A_QState, s1: float = 0.5, s2: float = 0.5) -> float: """Returns the transition frequency (energy difference) between two states, in Hz. Uses :meth:`get_state_energy` on both states to determine the energy difference. Parameters ---------- state1: A_QState `A_QState` namedtuple of quantum numbers for the first quantum state. state2: A_QState `A_QState` namedtuple of quantum numbers for the second quantum state. s1: float, optional spin of the initial state. Default is 0.5 for Alkali atom. s2: float, optional spin of the final state. Default is 0.5 for Alkali atom. Returns ------- float Transition frequency between the two states, in Hz. If negative, state1 has higher energy than state2. Examples -------- >>> import arc >>> g_nlj = rq.A_QState(5, 0, 0.5) >>> g_fs = rq.A_QState(5, 0, 0.5, m_j=-0.5) >>> e_nlj = rq.A_QState(5, 1, 0.5) >>> e_hfs = rq.A_QState(5, 1, 0.5,f=2, m_f=0) >>> arc_atom = arc.alkali_atom_data.Rubidium85() >>> my_atom = rq.RQ_AlkaliAtom(arc_atom) >>> print(my_atom.get_transition_frequency(g_nlj, e_nlj))# doctest: +SKIP 377107433259213.6 >>> print(my_atom.get_transition_frequency(g_fs, e_hfs))# doctest: +SKIP 377107222336963.6 >>> print(my_atom.get_transition_frequency(g_nlj, e_hfs))# doctest: +SKIP 377107222336963.6 """ e1 = self.get_state_energy(state1, s1) e2 = self.get_state_energy(state2, s2) return e2 - e1
[docs] def get_transition_wavelength(self, state1: A_QState, state2: A_QState, s1: float = 0.5, s2: float = 0.5) -> float: """Returns the transition wavelength between two states, in m. Uses :external+arc:meth:`~arc.alkali_atom_functions.AlkaliAtom.getTransitionWavelength`. Parameters ---------- state1: A_QState `A_QState` namedtuple of quantum numbers for the first quantum state. state2: A_QState `A_QState` namedtuple of quantum numbers for the first quantum state. s1: float, optional spin of the initial state. Default is 0.5 for Alkali atom. s2: float, optional spin of the final state. Default is 0.5 for Alkali atom. Returns ------- float Transition wavelength between the two states, in m. If negative, state2 has higher energy than state1. Examples -------- >>> import arc >>> g_nlj = rq.A_QState(5, 0, 0.5) >>> g_fs = rq.A_QState(5, 0, 0.5, m_j=-0.5) >>> e_nlj = rq.A_QState(5, 1, 0.5) >>> e_hfs = rq.A_QState(5, 1, 0.5,f=2, m_f=0) >>> arc_atom = arc.alkali_atom_data.Rubidium85() >>> my_atom = rq.RQ_AlkaliAtom(arc_atom) >>> print(my_atom.get_transition_wavelength(g_nlj, e_nlj)) 7.949789146530309e-07 >>> print(my_atom.get_transition_wavelength(g_fs, e_hfs)) 7.949789146530309e-07 >>> print(my_atom.get_transition_wavelength(g_nlj, e_hfs)) 7.949789146530309e-07 """ return self.arc_atom.getTransitionWavelength(*state1.qnums[:3], *state2.qnums[:3], s1, s2)
[docs] def get_state_energy(self, state: A_QState, s: float = 0.5) -> float: """Returns the energy of the level relative to the ionisation level in Hz. If `state` is in the fine basis or NLJ, energies are relative to the center of gravity of the hyperfine split states. If `state` is in the hyperfine basis, hyperfine shifts are applied. Uses ARC's :external+arc:meth:`~arc.alkali_atom_functions.AlkaliAtom.getEnergy` to get the energy of the fine structure state. Hyperfine shifts are applied using ARC's :external+arc:meth:`~arc.alkali_atom_functions.AlkaliAtom.getHFSCoefficients` and :external+arc:meth:`~arc.alkali_atom_functions.AlkaliAtom.getHFSEnergyShift` methods. Parameters ---------- state: A_QState `A_QState` namedtuple of quantum numbers of the quantum state for which energy will be calculated. s: float Total spin of the state. Default is 0.5 for Alkali atom. Returns ------- float Energy of state relative to the ionisation level in Hz. Examples -------- >>> from rydiqule import A_QState >>> import arc >>> g_nlj = A_QState(5, 0, 0.5) >>> g_fs = A_QState(5, 0, 0.5, m_j=-0.5) >>> e_hfs = A_QState(5, 1, 0.5,f=2, m_f=0) >>> arc_atom = arc.alkali_atom_data.Rubidium85() >>> my_atom = rq.RQ_AlkaliAtom(arc_atom) >>> print(my_atom.get_state_energy(g_nlj)/1e9) #GHz -1010024.7 >>> print(my_atom.get_state_energy(g_fs)/1e9) #GHz -1010024.7 >>> print(my_atom.get_state_energy(e_hfs)/1e9) #GHz -632917.5 """ base_energy = self.arc_atom.getEnergy(*state[:3], s)*C_e/C_h # in Hz if state.stype == "HFS": A, B = self.arc_atom.getHFSCoefficients(*state[:3]) hfs = self.arc_atom.getHFSEnergyShift(state[2], state[4], A, B) # in Hz else: hfs = 0.0 return base_energy + hfs
[docs] def gaussian_center_field(self, laserPower: float, laserWaist: float) -> float: """Returns the electric field for the center of a TEM00 gaussian spatial mode This calculates the peak intensity of the gaussian mode, and uses a plane wave assumption to get the electric field amplitude. Parameters ---------- laserPower: float laser power in Watts laserWaist: float laser :math:`1/e^2` waist (radius) in meters Returns ------- float Peak electric field for a gaussian spatial mode, in V/m """ maxIntensity = 2 * laserPower / (pi * laserWaist**2) electricField = sqrt(2.0 * maxIntensity / (C_c * epsilon_0)) return electricField
[docs] def get_rabi_frequency(self, state1: A_QState, state2: A_QState, q: Literal[-1, 0, 1], laserPower: float, laserWaist: float, s: float = 0.5) -> float: """Returns the Rabi frequency for resonantly driven atom in center of a TEM00 mode of a field. The field is calculated using :meth:`gaussian_center_field`. It then calls :meth:`get_rabi_frequency2` to get the rabi frequency. Parameters ---------- state1: A_QState NamedTuple of quantum numbers for state driving from state2: A_QState NamedTuple of quantum numbers for state driving to q: int laser polarization in spherical basis (-1,0,1) corresponding to :math:`\\sigma^-`, :math:`\\pi`, and :math:`\\sigma^+` laserPower: float laser power in Watts laserWaist: float laser :math:`1/e^2` waist (radius) in meters s: float, optional total spin angular momentum of the states. By default 0.5 for Alkali atoms. Returns ------- rabi_frequency: float Rabi frequency in rad/s. To get Hz, divide by :math:`2\\pi` Examples -------- >>> from rydiqule import A_QState >>> import arc >>> g_nlj = A_QState(5, 0, 0.5) >>> g_fs = A_QState(5, 0, 0.5, m_j=-0.5) >>> e_nlj = A_QState(5, 1, 0.5) >>> e_hfs = A_QState(5, 1, 0.5,f=2, m_f=0) >>> arc_atom = arc.alkali_atom_data.Rubidium85() >>> my_atom = rq.RQ_AlkaliAtom(arc_atom) >>> print(my_atom.get_rabi_frequency(g_nlj, e_nlj, q=0, laserPower=1, laserWaist=0.01)/1e6) #MHz 304.22 >>> print(my_atom.get_rabi_frequency(g_fs, e_hfs, q=0, laserPower=1, laserWaist=0.01)/1e6) #MHz 215.11 >>> print(my_atom.get_rabi_frequency(g_nlj, e_hfs, q=0, laserPower=1, laserWaist=0.01)) # doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ... rydiqule.exceptions.AtomError: Invalid transition type for dipole calculation. """ electricField = self.gaussian_center_field(laserPower, laserWaist) return self.get_rabi_frequency2(state1, state2, q, electricField, s)
[docs] def get_rabi_frequency2(self, state1: A_QState, state2: A_QState, q: Literal[-1, 0, 1], electricFieldAmplitude:float, s: float = 0.5) -> float: """Returns the Rabi frequency for resonantly driven atom in a given electric field amplitude. Uses :meth:`get_dipole_matrix_element` for the calculation. Parameters ---------- state1: A_QState NamedTuple of quantum numbers for state driving from state2: A_QState NamedTuple of quantum numbers for state driving to q: int laser polarization in spherical basis (-1,0,1) corresponding to :math:`\\sigma^-`, :math:`\\pi`, and :math:`\\sigma^+` electricFieldAmplitude: float amplitude of driving electric field, in V/m s: float, optional total spin angular momentum of the states. By default 0.5 for Alkali atoms. Returns ------- rabi_frequency: float Rabi frequency in rad/s. To get Hz, divide by :math:`2\\pi` Examples -------- >>> from rydiqule import A_QState >>> import arc >>> g_nlj = rq.A_QState(5, 0, 0.5) >>> g_fs = rq.A_QState(5, 0, 0.5, m_j=-0.5) >>> e_nlj = rq.A_QState(5, 1, 0.5) >>> e_hfs = rq.A_QState(5, 1, 0.5,f=2, m_f=0) >>> arc_atom = arc.alkali_atom_data.Rubidium85() >>> my_atom = rq.RQ_AlkaliAtom(arc_atom) >>> e_field = 0.1 #V/m >>> print(my_atom.get_rabi_frequency2(g_nlj, e_nlj, q=0, electricFieldAmplitude=e_field)) 13890.429 >>> print(my_atom.get_rabi_frequency2(g_fs, e_hfs, q=0, electricFieldAmplitude=e_field)) 9822.0166 >>> print(my_atom.get_rabi_frequency2(g_nlj, e_hfs, q=0, electricFieldAmplitude=e_field)) # doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ... rydiqule.exceptions.AtomError: Invalid transition type for dipole calculation. """ # TODO: may need to put checks to return 0 if just not dipole allowed dipole = self.get_dipole_matrix_element(state1, state2, q, s) * C_e * C_a_0 freq = electricFieldAmplitude * abs(dipole) / hbar return freq
[docs] def get_reduced_rabi_frequency(self, state1: A_QState, state2: A_QState, laserPower: float, laserWaist: float, s: float = 0.5) -> float: """Returns the Rabi frequency for resonantly driven atom in center of a TEM00 mode of a field. The field is calculated using :meth:`gaussian_center_field`. It then calls :meth:`get_rabi_frequency2` to get the rabi frequency. Note ---- This function preserves the sign of the dipole moment (i.e. the result could be negative). As such, state calling order matters. To get correct convention for use in Cell, `state` must be lower energy than `state2`. Parameters ---------- state1: A_QState NamedTuple of quantum numbers for state driving from state2: A_QState NamedTuple of quantum numbers for state driving to laserPower: float laser power in Watts laserWaist: float laser :math:`1/e^2` waist (radius) in meters s: float, optional total spin angular momentum of the states. By default 0.5 for Alkali atoms. Returns ------- rabi_frequency: float Rabi frequency in rad/s. To get Hz, divide by :math:`2\\pi` Examples -------- >>> from rydiqule import A_QState >>> import arc >>> g_nlj = A_QState(5, 0, 0.5) >>> g_fs = A_QState(5, 0, 0.5, m_j=-0.5) >>> e_nlj = A_QState(5, 1, 0.5) >>> e_hfs = A_QState(5, 1, 0.5,f=2, m_f=0) >>> arc_atom = arc.alkali_atom_data.Rubidium85() >>> my_atom = rq.RQ_AlkaliAtom(arc_atom) >>> print(my_atom.get_reduced_rabi_frequency(g_nlj, e_nlj, laserPower=1, laserWaist=0.01)/1e6) #MHz 372.59 >>> print(my_atom.get_reduced_rabi_frequency(g_fs, e_hfs, laserPower=1, laserWaist=0.01)/1e6) #MHz 372.59 >>> print(my_atom.get_reduced_rabi_frequency(g_nlj, e_hfs, laserPower=1, laserWaist=0.01)/1e6) #MHz 372.59 """ electricField = self.gaussian_center_field(laserPower, laserWaist) return self.get_reduced_rabi_frequency2(state1, state2, electricField, s)
[docs] def get_reduced_rabi_frequency2(self, state1: A_QState, state2: A_QState, electricFieldAmplitude:float, s: float = 0.5) -> float: """Returns the reduced Rabi frequency for resonantly driven atom in a given electric field amplitude. Uses :math:`1/2`:meth:`get_reduced_matrix_elementJ`. Note ---- This function preserves the sign of the dipole moment (i.e. the result could be negative). As such, state calling order matters. To get correct convention for use in Cell, `state1` must be lower energy than `state2`. Parameters ---------- state1: A_QState NamedTuple of quantum numbers for state driving from state2: A_QState NamedTuple of quantum numbers for state driving to electricFieldAmplitude: float amplitude of driving electric field, in V/m s: float, optional total spin angular momentum of the states. By default 0.5 for Alkali atoms. Returns ------- rabi_frequency: float Rabi frequency in rad/s. To get Hz, divide by :math:`2\\pi` Examples -------- >>> from rydiqule import A_QState >>> import arc >>> g_nlj = A_QState(5, 0, 0.5) >>> g_fs = A_QState(5, 0, 0.5, m_j=-0.5) >>> e_nlj = A_QState(5, 1, 0.5) >>> e_hfs = A_QState(5, 1, 0.5,f=2, m_f=0) >>> arc_atom = arc.alkali_atom_data.Rubidium85() >>> my_atom = rq.RQ_AlkaliAtom(arc_atom) >>> e_field = 0.1 #V/m >>> print(my_atom.get_reduced_rabi_frequency2(g_nlj, e_nlj, electricFieldAmplitude=e_field)) 17012.23 >>> print(my_atom.get_reduced_rabi_frequency2(g_fs, e_hfs, electricFieldAmplitude=e_field)) 17012.23 >>> print(my_atom.get_reduced_rabi_frequency2(g_nlj, e_hfs, electricFieldAmplitude=e_field)) 17012.23 """ reduced_dipole = self.get_reduced_matrix_elementJ(state1, state2, s) * C_e * C_a_0 / 2 return electricFieldAmplitude * reduced_dipole / hbar
[docs] def get_spherical_dipole_matrix_element(self, state1: A_QState, state2: A_QState, q: Literal[-1, 0, 1], s: float = 0.5) -> float: """Returns the spherical part of the dipole matrix element for a transition. Calculated by dividing the transition dipole moment by the reduced J matrix element. Parameters ---------- state1: A_QState NamedTuple of quantum numbers for first state state2: A_QState NamedTuple of quantum numbers for second state q: int field polarization in the spherical basis (-1,0,1) corresponding to :math:`\\sigma^-`, :math:`\\pi`, and :math:`\\sigma^+` s: float, optional total spin angular momentum of the states. By default 0.5 for Alkali atoms. Returns ------- float Spherical part of the dipole matrix element, in units of reduced matrix element :math:`\\langle J||d||J'\\rangle` Examples -------- >>> from rydiqule import A_QState >>> import arc >>> g_nlj = A_QState(5, 0, 0.5) >>> g_fs = A_QState(5, 0, 0.5, m_j=-0.5) >>> e_nlj = A_QState(5, 1, 0.5) >>> e_hfs = A_QState(5, 1, 0.5,f=2, m_f=0) >>> arc_atom = arc.alkali_atom_data.Rubidium85() >>> my_atom = rq.RQ_AlkaliAtom(arc_atom) >>> print(my_atom.get_spherical_dipole_matrix_element(g_nlj, e_nlj, q=0)) 0.4082 >>> print(my_atom.get_spherical_dipole_matrix_element(g_fs, e_hfs, q=0)) 0.2887 >>> print(my_atom.get_spherical_dipole_matrix_element(g_nlj, e_hfs, q=0)) # doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): File "<stdin>", line 1, in <module> rydiqule.exceptions.AtomError: Invalid transition type for dipole calculation. Allowed types are [('HFS', 'FS'), ('FS', 'HFS'), ('HFS', 'HFS'), ('FS', 'FS'), ('NLJ', 'NLJ')] """ return (self.get_dipole_matrix_element(state1, state2, q, s) / self.get_reduced_matrix_elementJ(state1, state2, s) )
[docs] def get_reduced_matrix_elementJ(self, state1: A_QState, state2: A_QState, s: float = 0.5) -> float: """Returns the reduced dipole matrix element in the J basis. A convenience wrapper for :external+arc:meth:`~arc.alkali_atom_functions.AlkaliAtom.getReducedMatrixElementJ` Note ---- To get proper sign conventions, state order must go from lower energy to higher energy state. Parameters ---------- state1: A_QState NamedTuple of quantum numbers for lower state state2: A_QState NamedTuple of quantum numbers for higher state s: float, optional total spin angular momentum of the states. By default 0.5 for Alkali atoms. Returns ------- float Reduced matrix element :math:`\\langle J||d||J'\\rangle` Examples -------- >>> from rydiqule import A_QState >>> import arc >>> g_nlj = A_QState(5, 0, 0.5) >>> g_fs = A_QState(5, 0, 0.5, m_j=-0.5) >>> e_nlj = A_QState(5, 1, 0.5) >>> e_hfs = A_QState(5, 1, 0.5,f=2, m_f=0) >>> arc_atom = arc.alkali_atom_data.Rubidium85() >>> my_atom = rq.RQ_AlkaliAtom(arc_atom) >>> print(my_atom.get_reduced_matrix_elementJ(g_nlj, e_nlj)) 4.2321 >>> print(my_atom.get_reduced_matrix_elementJ(g_fs, e_hfs)) 4.2321 >>> print(my_atom.get_reduced_matrix_elementJ(g_nlj, e_hfs)) 4.2321 """ return self.arc_atom.getReducedMatrixElementJ(*state1[:3], *state2[:3], s)
[docs] def get_transition_rate(self, state1: A_QState, state2: A_QState, temperature: float = 0.0, s: float = 0.5) -> float: """Returns transition rate between two states due to spontaneous emission. If temperature is provided, Black-Body Radiation induced transitions are included. Otherwise, rate is due to the natural radiative lifetime only. Uses ARC's :external+arc:meth:`~arc.alkali_atom_functions.AlkaliAtom.getTransitionRate` to get the base transition rate between :math:`|n_1,l_1,j_1\\rangle` to :math:`|n_2,l_2,j_2\\rangle`. If state1 and/or state2 are sublevels in the fine or hyperfine structures, this further applies the appropriate branching ratio. Parameters ---------- state1: A_QState NamedTuple of quantum numbers of the originating state state2: A_QState NamedTuple of quantum numbers of the target state temperature: float, optional Temperature of the atomic environment for calculating BBR-induced decays, in Kelvin. With default of 0.0, only include natural lifetime. s: float, optional total spin angular momentum. Default of 0.5 for Alkali atoms Returns ------- float Transition rate in 1/s Raises ------ AtomError If states are in both NLJ and FS/HFS definition. Examples -------- >>> from rydiqule import A_QState >>> import arc >>> g_nlj = A_QState(5, 0, 0.5) >>> g_fs = A_QState(5, 0, 0.5, m_j=-0.5) >>> e_nlj = A_QState(5, 1, 0.5) >>> e_hfs = A_QState(5, 1, 0.5,f=2, m_f=0) >>> arc_atom = arc.alkali_atom_data.Rubidium85() >>> my_atom = rq.RQ_AlkaliAtom(arc_atom) >>> print(my_atom.get_transition_rate(e_nlj, g_nlj)/1e6) #GHz 36.11 >>> print(my_atom.get_transition_rate(e_hfs, g_fs)/1e6) #GHz 18.06 >>> print(my_atom.get_transition_rate(e_hfs, g_nlj)) # doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): File "<stdin>", line 1, in <module> rydiqule.exceptions.AtomError: Transition between HFS and NLJ are not allowed. """ #dict of functions based on state types arc_transition_prefactor_functions: Dict[Tuple[str, str], Callable] = { ("NLJ","NLJ"): lambda *x: 1, #there is no prefactor ("FS","FS"): self._tr_prefactor_fs_fs, ("HFS", "HFS"): self._tr_prefactor_hfs_hfs, ("FS", "HFS"): self._tr_prefactor_fs_hfs, ("HFS", "FS"): self._tr_prefactor_hfs_fs } try: base_transition_rate = self.arc_atom.getTransitionRate(*state1[:3], *state2[:3], temperature, s) except ValueError: return 0.0 try: prefactor = arc_transition_prefactor_functions[(state1.stype, state2.stype)](state1, state2, s) except KeyError: msg = f"Transition between {state1.stype} and {state2.stype} are not allowed. \ Allowed transition types are {list(arc_transition_prefactor_functions.keys())}." raise AtomError(msg) return base_transition_rate*prefactor
[docs] def get_state_lifetime(self, state: A_QState, temperature: float = 0.0, includeLevelsUpTo:int = 0, s: float = 0.5) -> float: """Get lifetime of the state. If temperature is provided, includes Black-Body Radiation induced transitions. Otherwise, this is the natural lifetime of the state. This is a thin wrapper around ARC's :external+arc:meth:`~arc.alkali_atom_functions.AlkaliAtom.getStateLifetime` method. It adds basic validation of the state and selects the correct quantum numbers for the calculation. Parameters ---------- state: A_QState NamedTuple of quantum numbers of state for which to calculate lifetime. temperature: float, optional Temperature at which the atom environment is, in Kelvin. Used for calculating the black-body-induced state lifetime. If 0.0 (default), result does not include BBR term. includeLevelsUpTo: int, optional If `temperature` is non-zero, this specifies the highest principal quantum number states to include in the BBR calculation. Must be at least `n+1` of the provided state. s: float, optional total spin angular momentum of the state. Default is 0.5, for alkali atoms. Returns ------- float State lifetime in seconds. Examples -------- >>> from rydiqule import A_QState >>> import arc >>> e = A_QState(10, 0, 0.5) >>> e_fs = A_QState(10, 0, 0.5, m_j=-0.5) >>> e_nlj = A_QState(10, 1, 0.5) >>> e_hfs = A_QState(10, 1, 0.5,f=2, m_f=0) >>> arc_atom = arc.alkali_atom_data.Rubidium85() >>> my_atom = rq.RQ_AlkaliAtom(arc_atom) >>> print(my_atom.get_state_lifetime(e_nlj)) 1.162e-06 >>> print(my_atom.get_state_lifetime(e_fs)) 4.209e-07 """ return self.arc_atom.getStateLifetime(*state[:3], temperature, includeLevelsUpTo, s)
"""Utility functions for appropriately dispatching between state types not directly supported by arc""" #dipole functions
[docs] def _get_nlj_dipole(self, n1, l1, j1, n2, l2, j2, q, s): """Dipole matrix element between a pair of NLJ states, in units of e*a0""" dme = 0.0 n_dme = 0 mj1s = np.arange(-1*j1, j1+1) mj2s = mj1s + q for mj1, mj2 in zip(mj1s, mj2s): if abs(mj2) > j2: continue dme += abs(self.arc_atom.getDipoleMatrixElement(n1, l1, j1, mj1, n2, l2, j2, mj2, q, s)) n_dme += 1 return dme/n_dme
[docs] def _getDipoleMatrixElementFStoHFS(self, n1, l1, j1, mj1, n2, l2, j2, f2, mf2, q, s): """Funtion which inverts arcs HFS to FS dipole matrix element function, , in units of e*a0""" return self.arc_atom.getDipoleMatrixElementHFStoFS(n2, l2, j2, f2, mf2, n1, l1, j1, mj1, q, s)
#transition rate prefactor functions
[docs] def _tr_prefactor_fs_fs(self, state1, state2, s): """transition rate prefactor between 2 hyperfine states. Units of reduced matrix element :math:`<j|er|j'>`""" return self.arc_atom.getBranchingRatioFStoFS(*state2.qnums[2:], *state1.qnums[2:], s)
[docs] def _tr_prefactor_hfs_hfs(self, state1, state2, s): """transition rate prefactor between hyperfine states. returns the branching ratio for a particular F state. Units of reduced matrix element :math:`<j|er|j'>`""" return self.arc_atom.getBranchingRatio(*state2.qnums[2:], *state1.qnums[2:], s)
[docs] def _tr_prefactor_fs_hfs(self, state1, state2, s): """transition rate prefactor for transitions from a fine structure state to a hyperfine state. Units of reduced matrix element :math:`<j|er|j'>`""" return self.arc_atom.getBranchingRatioFStoHFS(*state2.qnums[2:], *state1.qnums[2:], s)
[docs] def _tr_prefactor_hfs_fs(self, state1, state2, s): """transition rate prefactor for transitions from a hyperfine state to a fine structure state. Units of reduced matrix element :math:`<j|er|j'>`""" return self.arc_atom.getBranchingRatioHFStoFS(*state2.qnums[2:], *state1.qnums[2:], s)