Source code for pylcp.hamiltonians

import numpy as np
from sympy.physics.wigner import wigner_3j, wigner_6j, wigner_9j
import scipy.constants as cts
from . import XFmolecules

def wig3j(j1, j2, j3, m1, m2, m3):
    return float(wigner_3j(j1, j2, j3, m1, m2, m3))


def wig6j(j1, j2, j3, m1, m2, m3):
    return float(wigner_6j(j1, j2, j3, m1, m2, m3))


def uncoupled_index(J, I, mJ, mI):
    return int((mI+I)+(2*I+1)*(mJ+J))


[docs]def fine_structure_uncoupled(L, S, I, xi, a_c, a_orb, a_dip, gL, gS, gI, muB=(cts.value("Bohr magneton in Hz/T")*1e-4), return_basis=False): """ Returns the full fine structure manifold in the uncoupled basis. Parameters ---------- L : int Orbital angular momentum of interest S : int or float Spin angular momentum of interest I : int or float Nuclear angular momentum of interest xi : float Fine structure splitting a_c : float Contact interaction constant a_orb : float Orbital interaction constant a_dip : float Dipole interaction constant gL : float Orbital g-factor gS : float Spin g-factor gI : float Nuclear g-factor muB : float, optional Bohr magneton. Default: the CODATA value in Hz/G return_basis : bool, optional Return the basis vectors as well as gthe Returns ------- H_0 : array (NxN) Field free Hamiltonian, where N is the number of states mu_q : array (3xNxN) Zeeman splitting array Notes ----- See J.D.Lyons and T.P.Das, Phys.Rev.A,2,2250 (1970) and H.Orth et al,Z.Physik A,273,221 (1975) for details of Hamiltonian and splitting constants. This function is adapted from the one found in Tollet, "Permanent magnetic trap for Li atoms", thesis, Rice University, 1994. """ # Set up a basis basis = [] for mL in np.arange(-L, L+1): for mS in np.arange(-S, S+1): for mI in np.arange(-I, I+1): basis.append((mL, mS, mI)) n_basis = len(basis) mu_q = np.zeros((3, n_basis, n_basis)) # Start with the magnetic field dependent matrices: for kk, q in enumerate([-1, 0, 1]): for jj, (mLp, mSp, mIp) in enumerate(basis): for ii, (mL, mS, mI) in enumerate(basis): if mS==mSp and mIp==mI: mu_q[kk, ii, jj] += gL*muB*(-1)**(L-mL)*wig3j(L, 1, L, -mL, q, mLp)*np.sqrt(L*(L+1)*(2*L+1)) if mL==mLp and mIp==mI: mu_q[kk, ii, jj] += gS*muB*(-1)**(S-mS)*wig3j(S, 1, S, -mS, q, mSp)*np.sqrt(S*(S+1)*(2*S+1)) if mL==mLp and mSp==mS: mu_q[kk, ii, jj] -= gI*muB*(-1)**(I-mI)*wig3j(I, 1, I, -mI, q, mIp)*np.sqrt(I*(I+1)*(2*I+1)) # Need to define the OTHER mu_q matrices! # Next, fill in the field independent matrix # terms, dmL dmS dmI # (I) 0 0 0 (diagonal terms) # (II) +1 -1 0 # -1 +1 0 # (III) 0 +1 -1 # 0 -1 +1 # (IV) +1 0 -1 # -1 0 +1 # (V) +2 -1 -1 # -2 +1 +1 # Initialize field-independent matrix: H_0 = np.zeros((n_basis, n_basis)) # Populate! for ii, (mL, mS, mI) in enumerate(basis): # (I) if S>0: H_0[ii, ii] += mS*mI*(L+S)*a_c/S if S>0 and L>0: H_0[ii, ii] += (mL*mI*(L+S)*a_orb/L + mS*mI*(3*mL**2 - L*(L+1))*(L+S)*a_dip/(1*S*(2*L-1)) + mL*mS*xi) # (II) if mL+1<=L and mS-1>=-S and L>0: t1 = np.sqrt((L-mL)*(L+mL+1)*(S+mS)*(S-mS+1)) drow = int(np.round(2*S*(2*I+1))) H_0[ii+drow, ii] += t1*xi/2 + t1*3*(2*mL+1)*mI*(L+S)*a_dip/(4*L*S*(2*L-1)) if mS+1<=S and mL-1>=-L and L>0: t1 = np.sqrt((S-mS)*(S+mS+1)*(L+mL)*(L-mL+1)) drow = int(np.round(-2*S*(2*I+1))) H_0[ii+drow, ii] += t1*xi/2 + t1*3*(2*mL-1)*mI*(L+S)*a_dip/(4*L*S*(2*L-1)) # (III) if mS+1<=S and mI-1>=-I: t1 = np.sqrt((S-mS)*(S+mS+1)*(I+mI)*(I-mI+1)) drow = int(np.round(2*I)); if np.abs(a_c)>0.: H_0[ii+drow, ii] += t1*(L+S)*a_c/2/S if L>0 and np.abs(a_dip)>0.: H_0[ii+drow, ii] += - t1*(3*mL**2-L*(L+1))*(L+S)*a_dip/(4*L*S*(2*L-1)) if mI+1<=I and mS-1>=-S: t1 = np.sqrt((I-mI)*(I+mI+1)*(S+mS)*(S-mS+1)) drow = int(np.round(-2*I)) if np.abs(a_c)>0.: H_0[ii+drow, ii] += t1*(L+S)*a_c/2/S if L>0 and np.abs(a_dip)>0.: H_0[ii+drow, ii] += - t1*(3*mL**2-L*(L+1))*(L+S)*a_dip/(4*L*S*(2*L-1)) # (IV) if mL+1<=1 and mI-1>=-I and L>0 and (np.abs(a_orb)>0. or np.abs(a_dip)>0.): t1 = np.sqrt((L-mL)*(L+mL+1)*(I+mI)*(I-mI+1)) drow = int((2*I+1)*(2*S+1) - 1) H_0[ii+drow, ii] += t1*(1+S)*a_orb/2/L + t1*3*(2*mL+1)*mS*(1+S)*a_dip/(4*L*S*(2*L-1)) if mI+1<=I and mL-L>=-1 and L>0 and (np.abs(a_orb)>0. or np.abs(a_dip)>0.): t1 = np.sqrt((I-mI)*(I+mI+1)*(L+mL)*(L-mL+1)) drow = int(1 - (2*I+1)*(2*S+1)) H_0[ii+drow, ii] += t1*(1+S)*a_orb/2/L + t1*3*(2*mL-1)*mS*(1+S)*a_dip/(4*L*S*(2*L-1)) # (V) if mL+2<=L and mS-1>=-S and mI-1>=-I and np.abs(a_dip)>0. and L>0 and S>0: t1 = (L-mL)*(L+mL+1) t1 = t1*np.sqrt( (S+mS)*(S-mS+1)*(I+mI)*(I-mI+1) ) drow = int(2*(2*I+1)*(2*S+1) - (2*I+1) - 1) H_0[ii+drow, ii] += t1*3*(1+S)*a_dip/(4*L*S*(2*L-1)) if mL-2>=-L and mS+1<=S and mI+1<=I and np.abs(a_dip)>0. and L>0 and S>0: t1 = (L+mL)*(L-mL+1) t1 = t1*np.sqrt( (S-mS)*(S+mS+1)*(I-mI)*(I+mI+1) ) drow = int(-2*(2*I+1)*(2*S+1) + (2*I+1) + 1) H_0[ii+drow, ii] += t1*3*(1+S)*a_dip/(4*L*S*(2*L-1)) if return_basis: return H_0, mu_q, basis else: return H_0, mu_q
[docs]def dqij_two_fine_stucture_manifolds_uncoupled(basis_g, basis_e): """ Return the coupling between two fine structure manifolds Parameters ---------- basis_g : list or array_like A list of the basis vectors for the ground state. In the uncoupled basis, they are of the form :math:`|m_L, m_S, m_I\\rangle` basis_e : list or array_like A list of the basis vectors for the ground state. In the uncoupled basis, they are of the form :math:`|m_L\', m_S\', m_I\'\\rangle` Returns ------- d_q : array with shape (3, N, M) The dipole coupling array. N is the number of ground states and M is the number of excited states. """ d_q = np.zeros((3, len(basis_g), len(basis_e))) for kk, q in enumerate(range(-1, 2)): for ii, (mL, mS, mI) in enumerate(basis_g): for jj, (mLp, mSp, mIp) in enumerate(basis_e): d_q[kk, ii, jj] = (mI==mIp)*(mS==mSp)*(mL==mLp+q) return d_q
[docs]def hyperfine_uncoupled(J, I, gJ, gI, Ahfs, Bhfs=0, Chfs=0, muB=(cts.value("Bohr magneton in Hz/T")*1e-4), return_basis = False): """ Construct the hyperfine Hamiltonian in the coupled basis. For parameterization of this Hamiltonian, see Steck, Alkali D line data, which contains a useful description of the hyperfine Hamiltonian. Parameters ---------- J : int or float Lower hyperfine manifold :math:`J` quantum number I : int or float Nuclear spin associated with both manifolds gJ : float Electronic Lande g-factor gI : float Nuclear g-factor Ahfs : float Hyperfine :math:`A` parameter Bhfs : float, optional Hyperfine :math:`B` parameter. Default: 0. Chfs : float, optional Hyperfine :math:`C` parameter. Default: 0. muB : float, optional Bohr magneton. Default: the CODATA value in Hz/G return_basis : boolean, optional If true, return the basis. Default: False Returns ------- H_0 : array_like Field independent component of the Hamiltonian mu_q : array_like Magnetic field dependent component of the Hamiltonian basis : list List of :math:`(J, I, m_J, m_I)` basis states """ index = lambda J, I, mJ, mI: uncoupled_index(J, I, mJ, mI) num_of_states = int((2*J+1)*(2*I+1)) H_0 = np.zeros((num_of_states, num_of_states)) H_Bq = np.zeros((3,num_of_states, num_of_states)) # Start with the magnetic field dependent matrices: for kk, q in enumerate([-1, 0, 1]): for mJ in np.arange(-J, J+0.1, 1): for mJp in np.arange(-J, J+0.1, 1): for mI in np.arange(-I, I+0.1, 1): for mIp in np.arange(-I, I+0.1, 1): if mIp==mI: mu_q[kk, index(J, I, mJ, mI), index(J, I, mJp, mIp)] += \ gJ*muB*(-1)**(J-mJ)*wig3j(J, 1, J, -mJ, q, mJp)*np.sqrt(J*(J+1)*(2*J+1)) if mJ==mJp: mu_q[kk, index(J, I, mJ, mI), index(J, I, mJp, mIp)] -= \ gI*muB*(-1)**(I-mI)*wig3j(I, 1, I, -mI, q, mIp)*np.sqrt(I*(I+1)*(2*I+1)) # Next, do the J_zI_z diagonal elements of J\dotI operator: for mJ in np.arange(-J, J+1, 1): for mI in np.arange(-I, I+1, 1): H_0[index(J, I, mJ, mI), index(J, I, mJ, mI)] += Ahfs*mJ*mI # Now, go through and do the J_+I_- term: for mJ in np.arange(-J, J, 1): for mI in np.arange(-I+1, I+1, 1): H_0[index(J, I, mJ+1, mI-1), index(J, I, mJ, mI)] += \ 0.5*Ahfs*np.sqrt((J-mJ)*(J+mJ+1))*np.sqrt((I+mI)*(I-mI+1)) # Now, go through and do the J_-I_+ term: for mJ in np.arange(-J+1, J+1, 1): for mI in np.arange(-I, I, 1): H_0[index(J, I, mJ-1, mI+1), index(J, I, mJ, mI)] += \ 0.5*Ahfs*np.sqrt((J+mJ)*(J-mJ+1))*np.sqrt((I-mI)*(I+mI+1)) if Bhfs > 0: Bhfs = Bhfs/(2*I*(2*I-1)*J*(2*J-1)) # rescale, include the denominator # Next, do the J_zI_z diagonal elements\ for mJ in np.arange(-J, J+1, 1): for mI in np.arange(-I, I+1, 1): H_0[index(J, I, mJ, mI), index(J, I, mJ, mI)] += \ Bhfs*( # I_z^2J_z^2 from (I\cdotJ)^2 3*mJ**2*mI**2 + # J_-J_+I_+I_- from (I\cdotJ)^2 3/4*(J*(J+1)-mJ**2-mJ)*(I*(I+1)-mI**2+mI) + # J_+J_-I_-I_+ from (I\cdotJ)^2 3/4*(J*(J+1)-mJ**2+mJ)*(I*(I+1)-mI**2-mI) + # J_zI_z from (I\cdotJ) 3/2*mJ*mI - # the rest: I*(I+1)*J*(J+1) ) # Now, go through and do the J_+I_- terms: for mJ in np.arange(-J, J, 1): for mI in np.arange(-I+1, I+1, 1): H_0[index(J, I, mJ+1, mI-1), index(J, I, mJ, mI)] += Bhfs * \ ( # J_z I_z J_+I_- + J_+I_-J_z I_z term form 3(I\cdotJ)^2: 3/2*((mJ+1)*(mI-1)+mJ*mI)*np.sqrt((J-mJ)*(J+mJ+1) * (I+mI)*(I-mI+1)) + # J_+I_- term form 3/2(I\cdotJ): 3/4*np.sqrt((J-mJ)*(J+mJ+1)*(I+mI)*(I-mI+1)) ) # Now, go through and do the J_-I_+ terms: for mJ in np.arange(-J+1, J+1, 1): for mI in np.arange(-I, I, 1): H_0[index(J, I, mJ-1, mI+1), index(J, I, mJ, mI)] += Bhfs * \ ( # J_z I_z J_-I_+ + J_-I_+J_z I_z term form 3(I\cdotJ)^2: 3/2*((mJ-1)*(mI+1)+mJ*mI)*np.sqrt((J+mJ)*(J-mJ+1) * (I-mI)*(I+mI+1)) + # J_+I_- term form 3/2(I\cdotJ): 3/4*np.sqrt((J+mJ)*(J-mJ+1)*(I-mI)*(I+mI+1)) ) # Now, go through and do the J_-J_-I_+I_+ terms: for mJ in np.arange(-J+2, J+1, 1): for mI in np.arange(-I, I-1, 1): H_0[index(J, I, mJ-2, mI+2), index(J, I, mJ, mI)] += 3/4*Bhfs*\ np.sqrt((J+mJ-1)*(J+mJ)*(J-mJ+1)*(J-mJ+2)) * \ np.sqrt((I-mI-1)*(I-mI)*(I+mI+1)*(I+mI+2)) # Now, go through and do the J_+J_+I_-I_- terms: for mJ in np.arange(-J, J-1, 1): for mI in np.arange(-I+2, I+1, 1): H_0[index(J, I, mJ+2, mI-2), index(J, I, mJ, mI)] += 3/4*Bhfs*\ np.sqrt((J-mJ-1)*(J-mJ)*(J+mJ+1)*(J+mJ+2)) * \ np.sqrt((I+mI-1)*(I+mI)*(I-mI+1)*(I-mI+2)) if return_basis: basis = np.zeros((4,num_of_states)) for mJ in range(-J,J+1): for mI in range(-I,I+1): basis[index(J, I, mJ, mI)] = np.array([J, I, mJ, mI]) return H_0, H_Bq, basis else: return H_0, H_Bq
def coupled_index(F, mF, Fmin): if np.abs(mF) > F: raise ValueError("mF=%.1f not a good value for F=%.1f"%(mF, F)) return int(np.sum((2*np.arange(Fmin, F, 1)+1))+(F+mF))
[docs]def hyperfine_coupled(J, I, gJ, gI, Ahfs, Bhfs=0, Chfs=0, muB=(cts.value("Bohr magneton in Hz/T")*1e-4), return_basis=False): """ Construct the hyperfine Hamiltonian in the coupled basis. For parameterization of this Hamiltonian, see Steck, Alkali D line data, which contains a useful description of the hyperfine Hamiltonian. Parameters ---------- J : int or float Lower hyperfine manifold :math:`J` quantum number I : int or float Nuclear spin associated with both manifolds gJ : float Electronic Lande g-factor gI : float Nuclear g-factor Ahfs : float Hyperfine :math:`A` parameter Bhfs : float, optional Hyperfine :math:`B` parameter. Default: 0. Chfs : float, optional Hyperfine :math:`C` parameter. Default: 0. muB : float, optional Bohr magneton. Default: the CODATA value in Hz/G return_basis : boolean, optional If true, return the basis. Default: False Returns ------- H_0 : array_like Field independent component of the Hamiltonian mu_q : array_like Magnetic field dependent component of the Hamiltonian basis : list List of :math:`(F, m_F)` basis states """ # Determine the full number of F's: Fmin = np.abs(I-J) Fmax = np.abs(I+J) index = lambda F, mF: coupled_index(F, mF, Fmin) # Make the quantum numbers of the basis states: num_of_states = int(np.sum(2*np.arange(Fmin, Fmax+0.5, 1)+1)) Fs = np.zeros((num_of_states,)) mFs = np.zeros((num_of_states,)) for F_i in np.arange(Fmin, Fmax+0.5, 1): for mF_i in np.arange(-F_i, F_i+0.5, 1): Fs[index(F_i, mF_i)] = F_i mFs[index(F_i, mF_i)] = mF_i # Now, populate the H_0 matrix (field independent part): H_0 = np.zeros((num_of_states, num_of_states)) # Calculate the diagonal elements: Ks = Fs*(Fs+1) - I*(I+1) - J*(J+1) diag_elem = 0.5*Ahfs*Ks if Bhfs!=0: diag_elem += Bhfs*(1.5*Ks*(Ks+1) - 2*I*(I+1)*J*(J+1))/\ (4*I*(2*I-1)*J*(2*J-1)) if Chfs!=0: diag_elem += Chfs*(5*Ks**2*(Ks/4+1) + Ks*(I*(I+1)+J*(J+1)+3-3*I*(I+1)*J*(J+1)) - 5*I*(I+1)*J*(J+1))/\ (I*(I-1)*(2*I-1)*J*(J-1)*(2*J-1)) # Insert the diagonal (field indepedent part): for ii in range(num_of_states): H_0[ii,ii] = diag_elem[ii] # Now work on the field dependent part: mu_q = np.zeros((3, num_of_states, num_of_states)) for ii, q in enumerate(range(-1, 2)): for Fp in np.arange(Fmin, Fmax+0.5, 1): for F in np.arange(Fmin, Fmax+0.5, 1): for mFp in np.arange(-Fp, Fp+0.5, 1): mF = mFp+q if not np.abs(mF)>F: mu_q[ii, index(F, mF), index(Fp, mFp)] -= gJ*muB*\ (-1)**np.abs(F-mF)*wig3j(F, 1, Fp, -mF, q, mFp)*\ np.sqrt((2*Fp+1)*(2*F+1))*(-1)**(J+I+Fp+1)*\ wig6j(J, Fp, I, F, J, 1)*\ np.sqrt(J*(J+1)*(2*J+1)) mu_q[ii, index(F, mF), index(Fp, mFp)] += gI*muB*\ (-1)**np.abs(F-mF)*wig3j(F, 1, Fp, -mF, q, mFp)*\ np.sqrt((2*Fp+1)*(2*F+1))*(-1)**(J+I+Fp+1)*\ wig6j(I, Fp, J, F, I, 1)*\ np.sqrt(I*(I+1)*(2*I+1)) if return_basis: return H_0, mu_q, np.vstack((Fs, mFs)) else: return H_0, mu_q
[docs]def singleF(F, gF=1, muB=(cts.value("Bohr magneton in Hz/T")*1e-4), return_basis=False): """ Construct the Hamiltonian for a lonely angular momentum state Parameters ---------- F : int or float Angular momentum quantum number gF : float Associated Lande g-factor muB : float, optional Bohr magneton. Default: the CODATA value in Hz/G return_basis : boolean, optional If true, return the basis. Default: False Returns ------- H_0 : array_like Field independent component of the Hamiltonian mu_q : array_like Magnetic field dependent component of the Hamiltonian basis : list List of :math:`(F, m_F)` basis states """ index = lambda mF: int(F+mF) # Initialize the matrix H_0 = np.zeros((int(2*F+1), int(2*F+1))) mu_q = np.zeros((3, int(2*F+1), int(2*F+1))) # No diagonal elements # Off-diagonal elemnts: for ii, q in enumerate(np.arange(-1, 2, 1)): for mFp in np.arange(-F, F+1, 1): mF = mFp + q if not np.abs(mF) > F: # The minus sign here comes from the fact that the hyperfine # magnetic moment is dominated by the electron, whose magnetic # moment points in the opposite direction as the spin. mu_q[ii, index(mF), index(mFp)] -= gF*muB*\ (-1)**(F-mF)*np.sqrt(F*(F+1)*(2*F+1))*\ wig3j(F, 1, F, -mF, q, mFp) if return_basis: basis = np.zeros((int(2*F+1), 2)) basis[:, 0] = F for mF in np.arange(-F, F+1, 1): basis[index(mF), 1] = mF argout = (H_0, mu_q, basis) else: argout = (H_0, mu_q) return argout
def dqij_norm(dqij): dqij_norm = np.zeros(dqij.shape) for ii in range(dqij.shape[0]): for jj in range(dqij.shape[2]): dqij_norm[ii, :, jj] = dqij[ii, :, jj]/\ np.linalg.norm(dqij[:, :, jj]) return dqij_norm
[docs]def dqij_two_hyperfine_manifolds(J, Jp, I, normalize=True, return_basis=False): """ Dipole matrix element matrix elements for transitions between two hyperfine manifolds. Parameters ---------- J : int or float Lower hyperfine manifold :math:`J` quantum number Jp : int or float Upper hyperfine manifold :math:`J\'` quantum number I : int or float Nuclear spin associated with both manifolds normalize : boolean, optional Normalize the d_q to one. Default: True return_basis : boolean, optional If true, returns the basis states as well as the :math:`d_q` Returns ------- d_q : array_like Dipole matrix elements between hyperfine manifolds basis_g : list If return_basis is true, list of (:math:`F`, :math:`m_F`) basis_e : list If return_basis is true, list of (:math:`F\'`, :math:`m_F\'`) """ def matrix_element(J, F, m_F, Jp, Fp, m_Fp, I, q): return (-1)**(F-m_F+J+I+Fp+1)*np.sqrt((2*F+1)*(2*Fp+1))*\ wig3j(F, 1, Fp, -m_F, q, m_Fp)*wig6j(J, F, I, Fp, Jp, 1) # A simple function for addressing the index: index = lambda Fmin, F, mF: coupled_index(F, mF, Fmin) # What's the minimum F1 and F2? Fmin = np.abs(I-J) Fpmin = np.abs(I-Jp) Fmax = np.abs(I+J) Fpmax = np.abs(I+Jp) dqij = np.zeros((3, int(np.sum(2*np.arange(Fmin, Fmax+0.5, 1)+1)), int(np.sum(2*np.arange(Fpmin, Fpmax+0.5, 1)+1)))) for ii, q in enumerate(range(-1, 2)): for F in np.arange(Fmin, Fmax+0.5, 1): for Fp in np.arange(Fpmin, Fpmax+0.5, 1): for m_Fp in np.arange(-Fp, Fp+0.5, 1): m_F = m_Fp+q if not np.abs(m_F) > F: dqij[ii, index(Fmin, F, m_F), index(Fpmin, Fp, m_Fp)] =\ matrix_element(J, F, m_F, Jp, Fp, m_Fp, I, q) if normalize: dqij = dqij_norm(dqij) if return_basis: basis_g = np.zeros((dqij.shape[1], 2)) basis_e = np.zeros((dqij.shape[2], 2)) for F in np.arange(Fmin, Fmax+0.5, 1): for m_F in np.arange(-F, F+0.5, 1): basis_g[index(Fmin, F, m_F), :] = np.array([F, m_F]) for Fp in np.arange(Fpmin, Fpmax+0.5, 1): for m_Fp in np.arange(-Fp, Fp+0.5, 1): basis_e[index(Fpmin, Fp, m_Fp), :] = np.array([Fp, m_Fp]) argout = (dqij, basis_g, basis_e) else: argout = dqij return argout
[docs]def dqij_two_bare_hyperfine(F, Fp, normalize=True): """ Calculates the dqij matrix for two bare hyperfine states. Specifically, it returns the matrix of the operator $d_q$, where a photon is created by a transition from the excited state to the ground state. Parameters ---------- F : integer or float (half integer) Total angular momentum quantum number of the F state. Fp : integer or float (half integer) Total angular momentum quantum number of the F\' state. normalize : boolean By default, 'normalize' is True """ # A simple function for addressing the index: index = lambda F, mF: int(F+mF) # Initialize the matrix. 'Ground' state is represented by rows, 'excited' # state by columns. dqij = np.zeros((3, int(2*F+1), int(2*Fp+1))) # Populate the matrix. First go through each q: for ii, q in enumerate(np.arange(-1, 2, 1)): # Go through each m_F2: for m_Fp in np.arange(-Fp, Fp+1, 1): # m_F1 takes on a q value: m_F = m_Fp+q if not np.abs(m_F) > F: dqij[ii, index(F, m_F), index(Fp, m_Fp)] =\ (-1)**(F-m_F)*wig3j(F, 1, Fp, -m_F, q, m_Fp) # Normalization involves normalzing each transition |g>->|e> to the norm of # all the transitions from the excited state sum(|e>->|g>). That means # summing each column and each if normalize: dqij = dqij_norm(dqij) # Return the matrix: return dqij