Source code for atomsmltr.atoms.transitions

"""
transitions
=======================

This module implements the ``AtomicTransition`` class, that is meant to be embedded
in the ``Atom`` class.

Example
-------

.. code-block:: python

    from atomsmltr.atoms import Atom
    from atomsmltr.atoms.transitions import DummyTransition
    from scipy import constants as csts
    atom = Atom(mass=4 * csts.m_u, name="Helium")
    transition = DummyTransition("transition", Gamma=1, wavelength=1)
    atom.add_transition(transition, tag="transition")

See Also
--------
atomsmltr.atoms.generic.Atom
"""

# % IMPORTS
from abc import ABC, abstractmethod
import numpy as np
import scipy.constants as csts

# % LOCAL IMPORTS
from ..utils.infostring import InfoString

# % PHYSICS DEFINITIONS
"""In the following, we will define transitions using two parameters:
    > the wavelength in vacuum `lbda`
    > the natural linewidth `Gamma`

    All other parameters are derived from that
 """


def _w0(lbda: float) -> float:
    """returns the pulsation, in rad/s

    Parameters
    ----------
    lbda : float
        wavelength (m)

    Returns
    -------
    w0 : float
        pulsation (rad/s)
    """

    w0 = 2 * np.pi * csts.c / lbda
    return w0


def _Isat(lbda: float, Gamma: float) -> float:
    """Returns the saturation intensity, in W/m^2

    Parameters
    ----------
        lbda : float
            vacuum wavelength (in meters)
        Gamma : float
            natural linewidth (in rad/s)

    Returns
    -------
        Isat : float
            saturation intensity (in W/m^2)
    """
    w0 = _w0(lbda)
    Isat = csts.hbar * Gamma * w0**3 / 12 / np.pi / csts.c**2
    return Isat


def _Isat_mW_per_cm2(lbda: float, Gamma: float) -> float:
    """Returns the saturation intensity, in mW/cm^2

    Parameters
    ----------
        lbda : float
            vacuum wavelength (in meters)
        Gamma : float
            natural linewidth (in rad/s)

    Returns
    -------
        Isat: float
            saturation intensity (in mW/cm^2)
    """
    Isat_SI = _Isat(lbda, Gamma)
    Isat = Isat_SI * 1e3 / (1e2) ** 2
    return Isat


def _OmegaR(lbda: float, Gamma: float, I: float) -> float:
    """Returns the bare Rabi frequency for a two level system

    Parameters
    ----------
        lbda : float
            vacuum wavelength (in meters)
        Gamma : float
            natural linewidth (in rad/s)
        I : float
            saturation intensity (in W/m^2)

    Returns
    -------
        OmegaR : float
            the bare Rabi frequency (in rad/s)
    """
    Isat = _Isat(lbda, Gamma)
    OmegaR = Gamma * np.sqrt(I / 2 / Isat)
    return OmegaR


def _sat_param(lbda: float, Gamma: float, I: float, detuning: float) -> float:
    """Returns the saturation parameter for a two-level system.

    Beware, detuning is 2pi * (f_laser - f_transition)

    Parameters
    ----------
        lbda : float
            vacuum wavelength (in meters)
        Gamma : float
            natural linewidth (in rad/s)
        I : float
            saturation intensity (in W/m^2)
        detuning float
            laser detuning (in rad/s)

    Returns
    -------
        s : float
            the saturation parameter
    """
    Isat = _Isat(lbda, Gamma)
    s = (I / Isat) * (Gamma**2 / 4) / (detuning**2 + Gamma**2 / 4)
    return s


def _scattering_rate(lbda: float, Gamma: float, I: float, detuning: float) -> float:
    """Returns the scattering rate for a two-level system

    Beware, detuning is 2pi * (f_laser - f_transition)

    Parameters
    ----------
        lbda : float
            vacuum wavelength (in meters)
        Gamma : float
            natural linewidth (in rad/s)
        I : float
            saturation intensity (in W/m^2)
        detuning float
            laser detuning (in rad/s)

    Returns
    -------
        gamma_scatt : float
            the scattering rate (in /s)
    """
    s = _sat_param(lbda, Gamma, I, detuning)
    gamma_scatt = 0.5 * Gamma * s / (1 + s)
    return gamma_scatt


def _Doppler_temperature(Gamma: float, delta: float) -> float:
    """Returns the Doppler temperature for a transition


    Parameters
    ----------
        Gamma : float
            natural linewidth (in rad/s)

        delta : float
            laser detuning (in Hz)

    Returns
    -------
        T_Doppler : float
            Doppler temperature in K
    """
    T_Dopp = csts.hbar / 2 / csts.k * (delta**2 + Gamma**2 / 4) / np.abs(delta)
    return T_Dopp


# % ABSTRACT CLASSES


[docs] class AtomicTransition(ABC): """A generic (abstract) class to define electronic transitions in atoms Parameters ---------- wavelength : float the vacuum wavelength (in m) Gamma : float the transition natural linewidth (in rad/s) tag : str a tag identifying the transition Notes ------ This is an abstract class that cannot be used in simulations. For that purpose, see actual implementations of transitions. See also -------- DummyTransition J0J1Transition """ def __init__(self, wavelength: float, Gamma: float, tag: str): self.__tag = tag self.__wavelength = wavelength self.__Gamma = Gamma super().__init__() # -- READ-ONLY PROPERTIES # only with getters, no setters @property def tag(self): """str: transition identifier""" return self.__tag @property def wavelength(self): """float: transition vacuum wavelength (m)""" return self.__wavelength @property def Gamma(self): """float: transition natural linewidth (rad/s)""" return self.__Gamma @property def Isat(self): """float: transition saturation intensity (W/m^2)""" return _Isat(self.wavelength, self.Gamma) @property def Isat_mW_per_cm2(self): """float: transition saturation intensity (mW/cm^2)""" return _Isat_mW_per_cm2(self.wavelength, self.Gamma) @property def k(self): """float: transition wavenumber k = 2π / λ (m^-1)""" return 2 * np.pi / self.wavelength @property def Doppler_temperature(self): """float: Doppler temperature TD = hbar k / 2 / kB (K)""" return _Doppler_temperature(self.Gamma, -0.5 * self.Gamma) # -- METHODS
[docs] def get_Doppler_temperature(self, detuning: float) -> float: """Returns the Doppler temperature for a given laser detuning Parameters ---------- detuning : float laser detuning, in Hz Returns ------- float the Doppler temperature, in K """ return _Doppler_temperature(self.Gamma, detuning)
[docs] def get_saturation_parameter(self, intensity: float, detuning: float) -> float: """Returns the saturation parameter (for a two-level system) Parameters ---------- intensity : float laser intensity in W/m^2 detuning : float laser detuning in rad/s Returns ------- s : float the saturation parameter """ s = _sat_param(self.wavelength, self.Gamma, intensity, detuning) return s
[docs] @abstractmethod def get_scattering_rate( self, intensity: float, mag_field: float, polarization: np.ndarray, detuning: float, ): """Returns the scattering rate for a given laser / mag. field configuration Parameters ---------- intensity : float laser intensity (W/m^) mag_field : float (scalar) magnetic field amplitude (T) polarization : ndarray, shape (,3) projection of the laser polarization on (pi, sigma+, sigma-) detuning : float laser detuning (rad/s) Returns ------- scattering rate : float the transition scattering rate """ pass
[docs] @abstractmethod def get_resonant_speed( self, mag_field: float, polarization: str, detuning: float, ): """Returns the resonant speed for a given mag. field configuration Parameters ---------- mag_field : float (scalar) magnetic field amplitude (T) polarization : str laser polarization : "pi", "sigma+" or "sigma-" detuning : float laser detuning (rad/s) Returns ------- speed : float resonant speed (m/s) """ pass
def _gen_infostring_obj(self): """Generates an info string object""" info = InfoString(title=self.tag) info.add_section("Parameters") info.add_element("λ", f"{self.wavelength * 1e9:.2f} nm") info.add_element("Γ", f"2π × {self.Gamma / 2 / np.pi:.2e} Hz") info.add_element("Isat", f"{self.Isat_mW_per_cm2:.2f} mw/cm²") info.add_element("Doppler temp.", f"{self.Doppler_temperature:.2e} K") return info
[docs] def gen_infostring_obj(self): """generates an ``InfoString`` object. Returns ------- InfoString an ``InfoString`` object See also -------- atomsmltr.utils.infostring.InfoString """ return self._gen_infostring_obj()
[docs] def gen_info_string(self, **kwargs): """generates an info string Returns ------- info_string: str a string with information on the atom """ return self.gen_infostring_obj().generate(**kwargs)
[docs] def print_info(self): """prints the atom infostring""" print(self.gen_info_string())
[docs] class DummyTransition(AtomicTransition): """Dummy class, only for testing purposes"""
[docs] def get_scattering_rate( self, intensity: float, mag_field: float, polarization: str, detuning: float ): """Returns the scattering rate for the DummyTransition model This is just a two-level atom with no dependence on mag. field or polarization Parameters ---------- intensity : float laser intensity (W/m^) mag_field : float (scalar) magnetic field amplitude (T) polarization : ndarray, shape (,3) projection of the laser polarization on (pi, sigma+, sigma-) detuning : float laser detuning (rad/s) Returns ------- scattering rate : float the transition scattering rate """ rate = _scattering_rate(self.__wavelength, self.__Gamma, intensity, detuning) return rate
[docs] def get_resonant_speed( self, mag_field: float, # the amplitude of the magnetic field polarization: str, # "pi", "sigma+", "sigma-" detuning: float, # laser detuning ): """Returns the resonant speed for the DummyTransition model This actually always return zero... Parameters ---------- mag_field : float (scalar) magnetic field amplitude (T) polarization : str laser polarization : "pi", "sigma+" or "sigma-" detuning : float laser detuning (rad/s) Returns ------- speed : float resonant speed (m/s) """ return 0
# % REAL IMPLEMENTATIONS
[docs] class J0J1Transition(AtomicTransition): """A class to handle J=0 -> J=1 transitions Parameters ---------- wavelength : float the vacuum wavelength (in m) Gamma : float the transition natural linewidth (in rad/s) lande_factor : float the lande g-factor for the transition tag : str a tag identifying the transition """ def __init__(self, wavelength: float, Gamma: float, lande_factor: float, tag: str): self.__lande_factor = lande_factor super().__init__(wavelength=wavelength, Gamma=Gamma, tag=tag) @property def lande_factor(self): """float: the lande g-factor for the transition""" return self.__lande_factor
[docs] def gen_infostring_obj(self): info = self._gen_infostring_obj() info.add_element("lande factor g", f"{self.lande_factor}") return info
[docs] def get_scattering_rate( self, intensity: float, # the intensity in W/cm^2 mag_field: float, # the amplitude of the magnetic field polarization: list, # projection (squared) of laser polarization on (pi, sigma+, sigma-) detuning: float, # laser detuning (in rad/s !!!!!!) ): # -- get projections # TODO : checks here polarization = np.asanyarray(polarization) proj_pi, proj_sigm_plus, proj_sigm_minus = polarization.T proj_pi = proj_pi.T proj_sigm_minus = proj_sigm_minus.T proj_sigm_plus = proj_sigm_plus.T # -- Zeeman effect # NB : detuning is 2 * pi * (f_laser - f_atom) # constants mu_B = csts.physical_constants["Bohr magneton"][0] mu = self.lande_factor * mu_B / csts.hbar # compute detuning det_pi = detuning det_sigm_minus = detuning + mu * mag_field det_sigm_plus = detuning - mu * mag_field # -- Compute scattering rate # NB : we assume that the transition is not saturated and we can sum # all the polarization components scatt_pi = _scattering_rate( self.wavelength, self.Gamma, intensity * proj_pi, det_pi ) scatt_sigm_minus = _scattering_rate( self.wavelength, self.Gamma, intensity * proj_sigm_minus, det_sigm_minus ) scatt_sigm_plus = _scattering_rate( self.wavelength, self.Gamma, intensity * proj_sigm_plus, det_sigm_plus ) # sum scatt_total = scatt_pi + scatt_sigm_minus + scatt_sigm_plus return scatt_total
[docs] def get_resonant_speed( self, mag_field: float, # the amplitude of the magnetic field polarization: str, # "pi", "sigma+", "sigma-" detuning: float, # laser detuning ): # -- check input polar_list = ["pi", "sigma+", "sigma-"] msg = f"'polarization' should be in {polar_list}" assert polarization in polar_list, msg # -- factor mu_B = csts.physical_constants["Bohr magneton"][0] mu = self.lande_factor * mu_B / csts.hbar prefact = {"pi": 0, "sigma+": 1, "sigma-": -1} v_res = (detuning - mu * prefact[polarization] * mag_field) / self.k return v_res