# Four level system

We study population dynamics and effects of optical pumping on spectral line singal for near-resonant cyclic $D_{2}$ line transitions in Cs and Na atoms with the help of simplified four-level system. A classical laser field of frequency $ω_{L}$ is in resonance between  highest energy ground state hyperfine component $\left|2\right>$ highest energy hyperfine state component $\left|4\right>$. Intensity of excitation laser field follows a Guassian pulse shape that is characterize by Rabi frequency coupling strengths $Ω_{24}(t)$ and $Ω_{23}(t)$ between states $\left|2\right> ↔ \left|4\right>$ and $\left|2\right> ↔ \left|3\right>$ respectively. States $\left|1\right>$ is a population trapping ground state (dark state) that does not interact with the excitation laser field.

```python
%%svg
4-level-schematics.svg
```

Corresponding $D_{2}$ line transition hyperfine level schematics in Na nd Cs atoms.

```python
%%svg
hyperfine-level-shematics.svg
```

## Atomic and interaction Hamiltonian using rotating wave approximation (RWA)

### Lindblad master equation
The standard approach for deriving the equation of motion for a system interacting with its environment is to expand the scope of the system to include the environment. The combined quantum system is closed and its evolution is governed by the von Neumann equation
\begin{equation}
\begin{aligned}
\dot{ρ}(t) &=-\frac{i}{ℏ}[H_{tot},ρ_{tot}(t)]\\
H_{tot} &= H_{sys} + H_{env} + H_{int}\text{,}
\end{aligned}
\end{equation}
where the total Hamiltonian $H_{tot}$ includes the original system Hamiltonian $H_{sys}$, the Hamiltonian for the environment $H_{env}$ and the interaction Hamiltonian $H_{int}$ between the system and its environment. To obtain the dynamics of system $H_{sys}$, we can perform a partial trace over the environmental degrees of freedom in von Neumann equation. The most general trace-preserving and completely positive form of this evolution is the Lindblad master equation [[Lindblad(1976)]][communmathphys.48.119], [[Gardiner and Zoller(2004)]][Gardiner_2004], [[Walls and Milburn(2008)]][Walls_2008]
\begin{equation}
\begin{aligned}
\dot{ρ}(t) &=-\frac{i}{ℏ}[H(t),ρ(t)] + \sum_n \frac{1}{2} \left(2 C_n \rho(t) C_n^{\dagger} - ρ(t) C_n^{\dagger} C_n - C_n^{\dagger} C_n ρ(t)\right)\\
H(t) &= H_{sys} + H_{int}\\
C_n &= \sqrt{γ_{n}} A_{n}\text{,}
\end{aligned}
\end{equation}
where $C_n$ are wave function collapse operators, $A_{n}$ are operators through which the environment couples to the system in $H_{int}$ and $γ_{n}$ are the corresponding decay rates.

### Atomic Hamiltonian $H_{sys}$ (RWA)
Let us obtain optical Bloch equation for four-level system. Using dressed state notation given in figure, system Hamiltonian can be written as
\begin{equation}
\begin{aligned}
H_{sys} &= ε_{1} \left|1,n+1\right>\left< 1,n+1\right| + ε_{2} \left|2,n+1\right>\left< 2,n+1\right| + ε_{3} \left|3,n\right>\left< 3,n\right| + ε_{4} \left|4,n\right>\left< 4,n\right|\\
&= \left[\begin{smallmatrix} ε_{1} & 0 & 0 & 0\\0 & ε_{2} & 0 & 0\\0 & 0 & ε_{3} & 0\\0 & 0 & 0 & ε_{4} \end{smallmatrix}\right]\text{.}
\end{aligned}
\end{equation}
And energies of the system Hamiltonain can be written as
\begin{equation}
\begin{aligned}
ε_{1} &= -ℏ ω_{21} &
ε_{2} &= 0 &
ε_{3} &= Δ_{23} = -ω_{43} - Δ &
ε_{4} &= Δ_{24} = - Δ \text{,}
\end{aligned}
\end{equation}
where $ω_{ab} = ΔE_{ab}/ℏ = (E_{a} - E_{b})/ℏ$ and energy level difference in SI unit system using frequency $ν$ and cyclic frequency $ω$ are written as $ΔE = h ν = ℏ ω$.

Corresponding four-level system decay channels are following, $A_{1}$ from state $\left|4,n\right>$ to state $\left|2,n+1\right>$, $A_{2}$ from state $\left|3,n\right>$ to state $\left|2,n+1\right>$, $A_{3}$ from state $\left|3,n\right>$ to state $\left|1,n+1\right>$, such that
\begin{equation}
\begin{aligned}
A_{1} &= \left|2,n+1\right>\left< 4,n\right| = \left[\begin{smallmatrix}0 & 0 & 1 & 0\\0 & 0 & 0 & 0\\0 & 0 & 0 & 0\\0 & 0 & 0 & 0\end{smallmatrix}\right] \\
A_{2} &= \left|2,n+1\right>\left< 3,n\right| = \left[\begin{smallmatrix}0 & 0 & 0 & 0\\0 & 0 & 1 & 0\\0 & 0 & 0 & 0\\0 & 0 & 0 & 0\end{smallmatrix}\right] \\
A_{3} &= \left|1,n+1\right>\left< 3,n\right| = \left[\begin{smallmatrix}0 & 0 & 0 & 0\\0 & 0 & 0 & 1\\0 & 0 & 0 & 0\\0 & 0 & 0 & 0\end{smallmatrix}\right]\text{.}
\end{aligned}
\end{equation}
Corresponding wave function collapse operators are
\begin{equation}
\begin{aligned}
C_{1} &= \sqrt{γ_{1}} A_{1} &
C_{2} &= \sqrt{γ_{2}} A_{2} &
C_{3} &= \sqrt{γ_{3}} A_{3} \\
γ_{1} &= Π_{42} Γ_{4} &
γ_{2} &= Π_{32} Γ_{3} &
γ_{3} &= Π_{31} Γ_{3}\\
Π_{42} &= 1 & & &
Π_{31} &= 1-Π_{32} \text{,}
\end{aligned}
\end{equation}
where $Γ_{4} = Γ_{3} = Γ = 1/τ_{e}$ is decay rate of excited states, $τ_{e}$ is natural lifetime of excited state and $Π_{F_{e} F_{g}}$ is branching ration for transition $\left|F_{e}\right> → \left|F_{g}\right>$.

### Laser-atom interaction Hamiltonian $H_{int}$ using rotating wave approximation (RWA)
The interaction of bound particles with laser light most often originates with the electric-dipole interaction. Therefore, the atom-laser interaction Hamiltonian $H_{int}$ in the dipole approximation is given by interaction energy operator, that is the projection of the electric dipole moment $\vec{d}$ onto the electric field
\begin{equation}
\begin{aligned}
H_{int} &= -\vec{d} · \vec{E}(t)\\
\vec{E}(t) &= \hat{\mathbf{e}} E(t)\cos\left(ω_{L} t - φ\right)\\
E(t) &= E_{0} \exp\left(-2\left(\frac{t}{τ_{tr}}\right)^{2}\right)
\end{aligned}
\end{equation}
and where the atomic dipole operator $\vec{d}$  is given in terms of the atomic electron position $\vec{r}_{e}$ as
\begin{equation}
\vec{d} = - \boldsymbol{e} \vec{r}_{e} \text{,}
\end{equation}
where we denote the fundamental charge by $\boldsymbol{e}$, so that the electron charge is $q = - \boldsymbol{e}$. The dipole transition moment between states $\left|ψ_{a}\right>$ and $\left|ψ_{b}\right>$, projected onto the field unit vector $\hat{\mathbf{e}}$, is
\begin{equation}
d_{ψ_{a}, ψ_{b}} = \left< ψ_{a}\,\middle|\, \vec{d} · \hat{\mathbf{e}}\,\middle|\,ψ_{b}\right> \text{.}
\end{equation}
For linear polarization we can take the unit vector $\hat{\mathbf{e}}$ to be real and write the interaction Hamiltonain between states $\left|ψ_{a}\right>$ and $\left|ψ_{b}\right>$ as [[Shore(2011)]][Shore_2011]
\begin{equation}
\begin{aligned}
H_{int,ab} &= H_{int,ab} = -d_{ψ_{a}, ψ_{b}} E(t) \cos\left(ω_{L} t - φ\right) ≡ ℏ Ω_{ab}(t) \cos\left(ω_{L}t + φ\right) \text{,}
\end{aligned}
\end{equation}
where  we denote the Rabi frequency by $Ω_{ab}(t)$. There are many definitions of Rabi frequency $Ω$ in the literature. The chosen Rabi frequency $Ω_{ab}(t)$ refers to the frequency of population oscillations of resonant two-state system, i.e., when the Rabi frequency remains constant, the populations of resonant two-state system undergo periodic Rabi oscillations at the Rabi frequency $Ω$. The Rabi frequency is defined by
\begin{equation}
Ω_{ab}(t) ≡ -\frac{d_{ψ_{a}, ψ_{b}} E(t)}{ℏ} \text{.}
\end{equation}

In rotating reference frame off-diagonal elements of the atom-laser interaction Hamiltonian $H_{int, ab}$ [[Shore(2011)]][Shore_2011] are
\begin{equation}
H_{int, ab} = H_{int, ba}^{*} = -d_{ψ_{a}, ψ_{b}} E(t) \cos\left(ω_{L} t - φ\right) e^{-iω_{L}t} = \frac{1}{2} ℏ Ω_{ab}(t) \left(e^{-i φ} + e^{-2i ω_{L}t + iφ}\right)
\end{equation}
Rotating wave approximation (RWA) of atom-laser interaction is performed by disregarding the terms that vary as $2ω_{L}t$ (counter-rotating terms) when added to constant terms
\begin{equation}
\cos\left(ω_{L} t - φ\right) e^{-iω_{L}t} = \frac{1}{2} \left(e^{-i φ} + e^{-2i ω_{L}t + iφ}\right) \xrightarrow{RWA} \frac{1}{2} e^{-i φ}
\end{equation}
Note that the absolute value of the phase $φ$ is not controllable and only when one compares two pulses, both within a coherence time, or pulses affecting two locations, does one need to keep track of phases, therefore, phase $φ$ can be taken as zero at any convenient time
\begin{equation}
\frac{1}{2} e^{-i φ} → \frac{1}{2}\text{.}
\end{equation}
Thus the Rabi frequency can be made real $Ω_{ab}(t)$ such that $Ω_{ab}^{*}(t) = Ω_{ab}(t)$. Thus we obtain the atom-laser interaction Hamiltonain in the rotating wave approximation between state $\left|ψ_{a}\right>$ and $\left|ψ_{b}\right>$ as
\begin{equation}
H_{int, ab} = \frac{1}{2} ℏ Ω_{ab}(t) \text{.}
\end{equation}
And the atom-laser interaction Hamiltonain in the rotating wave approximation is given by
\begin{equation}
H_{int} = \frac{ℏ Ω_{23}(t)}{2}\left|2,n+1\right>\left< 3,n\right| + \frac{ℏ Ω_{23}^{*}(t)}{2}\left|3,n\right>\left< 2,n+1\right| + \frac{ℏ Ω_{24}(t)}{2}\left|2,n+1\right>\left< 4,n\right| + \frac{ℏ Ω_{24}^{*}(t)}{2}\left|4,n\right>\left< 2,n+1\right|
\end{equation}

When simulating the dynamics of the Hamiltonian we are modeling hyperfine transitions between states $\left|F_{g}\right> → \left|F_{e}\right>$ without taking into account Zeeman sublevels, therefore, for linearly polarized excitation laser, effective reduced dipole moment $d_{eff (F_{g} → F_{e})}$ [[Steck(2010a)]][Steck_Cs_2010], [[Steck(2010b)]][Steck_Na_2010] can be expressed as
\begin{equation}
\begin{aligned}
\left|d_{eff (F_{g} → F_{e})}\right|^{2} &= \left|\left< F_{g}\,\middle|\middle|\, \vec{d} · \hat{\mathbf{e}} \,\middle|\middle|\,F_{e}\right>\right|^{2} = S_{F_{g} F_{e}} \left|\left< J_{g}\,\middle|\middle|\, \vec{d} · \hat{\mathbf{e}}\,\middle|\middle|\,J_{e}\right>\right|^{2} \\
S_{F_{g} F_{e}} &= ( 2 F_{e} + 1)(2 J_{g} + 1 )
\begin{Bmatrix}
J_{g} & J_{e} & 1\\
F_{e} & F_{g} & 1
\end{Bmatrix}^{2}\\
\sum_{F_{e}} S_{F_{g} F_{e}} &= 1 \text{,}
\end{aligned}
\end{equation}
where $S_{F_{g} F_{e}}$ are dimensionless relative hyperfine transition-strength factors of each of the $\left|F_{g}\right> → \left|F_{e}\right>$ transitions. Numerical value of the reduced dipole matrix element $\left< J_{g}\,\middle|\middle|\, \vec{d} · \hat{\mathbf{e}}\,\middle|\middle|\,J_{e}\right>$ can be calculated using excited state lifetime from expression [[Loudon(2000)]][Loudon_2000]
\begin{equation}
\frac{1}{τ_{e}} = Γ_{J_{e} J_{g}} = \frac{ω_{0}^{3}}{3 π \mathit{ε}_{0} ℏ c^3} \frac{2 J_{g} + 1}{2 J_{e} + 1} \left|\left< J_{g}\,\middle|\middle|\, \vec{d}\,\middle|\middle|\,J_{e}\right>\right|^{2} \text{.}
\end{equation}

It is convenient to rewrite Rabi frequency $Ω_{ab}(t)$ using reduced Rabi frequency $Ω_{red}(t)$ and relative hyperfine transition-strength factors $S_{F_{g} F_{e}}$, where reduced Raby frequency is defined by
\begin{equation}
Ω_{red}(t) ≡ \frac{ E(t) \left< J_{g}\,\middle|\middle|\, \vec{d}\,\middle|\middle|\,J_{e}\right>}{ℏ} \text{.}
\end{equation}
This allows us to obtain Rabi frequency between states  $\left|ψ_{a}\right>$ and $\left|ψ_{b}\right>$ as
\begin{equation}
Ω_{ab}(t) = Ω_{red}(t) \sqrt{S_{F_{a} F_{b}}}
\end{equation}

## Remarks

### Critical Rabi frequencies for reduced Rabi frequency $Ω_{red}$
Critical Rabi frequency $Ω_{red,cr}$ is used to estimate at what reduced Rabi frequency value $≈ 63 \%$ of the population will be trapped in a dark state after laser-atom interaction and effects of optical pumping become pronounced in spectral line signal.
\begin{equation}
\begin{aligned}
Ω_{red,cr} = \sqrt{\frac{4 τ_{e} ω_{43}^{2}}{τ_{tr} \Pi_{31} S_{32} \sqrt{\pi}}}
\end{aligned}
\end{equation}

Critical Rabi frequency $Ω_{red,cr99}$ is used to estimate at what reduced Rabi frequency value $≈ 99 \%$ of the population will be trapped in a dark state after laser-atom interaction, and leading to population depletion in time period smaller than the interaction time $τ_{tr}$.
\begin{equation}
\begin{aligned}
Ω_{red,cr99} = \sqrt{-\ln\left( 1-\frac{99}{100}\right)} Ω_{red,cr} ≈ 2.15 Ω_{red,cr}
\end{aligned}
\end{equation}

### Spectral line singal
Spectral line signal $J(Δ, Ω_{red})$ is given by a number of photons emitted from an atom during transit time through the excitation laser zone, therefore we can express spectral line signal as
\begin{equation}
J(Δ, Ω_{red}) = Γ \int\limits_{-∞}^{∞} (ρ_{4,4}(t) + ρ_{3,3}(t) )\, dt \text{.}
\end{equation}
By using adiabatic aproximation and optical Bloch equations from Lindblad master equation, we can obtain approximate analitic expression of spectral line signal $J(Δ, Ω_{red})$ as
\begin{equation}
\begin{aligned}
J(Δ, Ω_{red}) &= \frac{S_{24} 4 \left(- ω_{43} - Δ \right)^{2} }{S_{23} \left( 4 \left(-Δ \right)^{2} + S_{24} Ω_{red}^{2} + Γ^{2} \right) Π_{31} } \\
&× \left(1- \exp\left( -τ_{tr} \frac{\sqrt{π} Γ Π_{31} S_{23} Ω_{red}^{2}}{8 \left(- ω_{43} - Δ - ½\sqrt{S_{24}} Ω_{red} - ½\sqrt{S_{23}} Ω_{red}\right)^{2}} \right) \right)\text{.}
\end{aligned}
\end{equation}

[communmathphys.48.119]: http://dx.doi.org/10.1007/bf01608499 "G. Lindblad, Commun. Math. Phys. 48, 119 (1976)."

[Gardiner_2004]: http://www.springer.com/gp/book/9783540223016 "C. Gardiner and P. Zoller, Quantum Noise, Springer Series in Synergetics (Springer-Verlag Berlin Heidelberg, 2004)."

[Walls_2008]: http://dx.doi.org/10.1007/978-3-540-28574-8 "D. Walls and G. J. Milburn, Quantum Optics (Springer Science + Business Media, 2008)."

[Shore_2011]: http://dx.doi.org/10.1017/cbo9780511675713 "B. W. Shore, Manipulating Quantum Structures Using Laser Pulses (Cambridge University Press (CUP), 2011)."

[Steck_Cs_2010]: http://steck.us/alkalidata "D. A. Steck, Cesium D Line Data (http://steck.us/alkalidata, 2010)."

[Steck_Na_2010]: http://steck.us/alkalidata "D. A. Steck, Sodium D Line Data (http://steck.us/alkalidata, 2010)."

[Loudon_2000]: http://global.oup.com/academic/product/the-quantum-theory-of-light-9780198501763 "R. Loudon, The Quantum Theory of Light Third Edition (Oxford University Press, 2000)"

# Resulsts
## Compare and review $J(Δ,Ω_{red})$ evaluated numerically from density matrix with approximate analytic expression.

### Compare spectral singnal $J(Ω_{red})$ and review corresponding population dinamics in case of Na atoms

```python
las_plot_jored(na_jored_expcase_list_1)
plot4lme(expcase_list[0], expcase_list[0].result)
plot4lme(expcase_list[1], expcase_list[1].result)
```

### Compare spectral singnal $J(Ω_{red})$ and review corresponding population dinamics in case of Cs atoms

```python
las_plot_jored(cs_jored_expcase_list_1)
plot4lme(expcase_list[2], expcase_list[2].result)
plot4lme(expcase_list[3], expcase_list[3].result)
```

### Compare spectral singnal $J(Δ)$ of Na and Cs atoms

```python
las_plot_jdelta(na_jdelta_expcase_list_1)
las_plot_jdelta(na_jdelta_expcase_list_2)
las_plot_jdelta(cs_jdelta_expcase_list_1)
las_plot_jdelta(cs_jdelta_expcase_list_2)
```

# Simulating the dynamics of 4-level system with [QuTiP](http://qutip.org/)

Please note that when simulating the dynamics of the total Hamiltonian:

1. We set the reduced Plank constant value from QuTiP backend, i.e., it is set to
\begin{equation}
ℏ = 1
\end{equation}

2. For laser-atom interaction Hamiltonian we are using a precomputed product of all of the coefficients before time-depenent exponent function
\begin{equation}
hcf_{ab} = \frac{ℏ Ω_{red} \sqrt{S_{F_{a} F_{b}}} }{2}
\end{equation}

## Simulating the dynamics for following experimental conditions:

1. Compute population dinamics for $Ω_{red} = Ω_{red,cr}$ and $Ω_{red} = Ω_{red,cr99}$.

2. Compute $J(Ω_{red})$ for $Ω_{red}$ from $0.25$ MHz to $50$ MHz.

3. Compute $J(Δ)$ for $Δ$ from $-40$ MHz to $40$ MHz.

4. Compare $J(Δ,Ω_{red})$ evaluated numerically from density matrix with approximate analytic expression.

## Setup imports

```python
%pylab inline
%config InlineBackend.figure_formats = {'svg',}
import seaborn as sns
flatui = ["#9b59b6", "#3498db", "#95a5a6", "#e74c3c", "#34495e", "#2ecc71"]
# rc('axes', prop_cycle=cycler('color', flatui))
# sns.palplot(sns.color_palette(flatui))
sns.set(palette=flatui)
rc('svg', fonttype='none')
from qutip import *
from fractions import Fraction
from IPython.display import Markdown
```

    Populating the interactive namespace from numpy and matplotlib

## Define functions for computing 4-level RWA Hamiltonian

```python
def solve4lme(args):
    '''
    Solve RWA Hamiltonian for 4-level system.
    RWA Hamiltonian states |1,n+1⟩, |2,n+1⟩, |3,n⟩, |4,n⟩.
    RWA Hamiltonian energies ε1=-w21, ε2=0, ε3=-w43-Δ, ε4=-Δ.
    A classical laser field is in resonance between states |2⟩↔|4⟩.
    Excitation schematics (bare atomic states picture)
        #           |4⟩
        #       -----------E4
        #         ↓Δ
        #       -----------             |3⟩
        #       / ↓Γ*Π42         ---------------E3
        #  Ω24 ↕  ↓             / ↓Π32*Γ     ↓(1-Π32)*Γ
        #     /   ↓         Ω23↕  ↓          ↓
        #    -----------------------E2       ↓
        #             |2⟩              -----------E1
        #                                   |1⟩

    Usage::

        >>> args = {'delta': 0,  # detuning Δ from excited state |4⟩ in s^-1
                'gamma': 1,      # excited state decay rate Γ
                'tint': 5920,    # laser-atom interaction time, np.float32
                'w21': 1756,     # hyperfile splitting 1⇥2
                'w43': 48,       # hyperfile splitting 3⇥4
                'hcf23': 3,      # interaction coeff 2↔3, np.float32
                'hcf24': 5,      # interaction coeff 2↔4, np.float32
                'cbr1': 1,       # branching ratio Π42 for |2⟩→|4⟩
                'cbr2': 7/12,    # branching ratio Π32 for |3⟩→|2⟩
                'cbr3': 5/12,    # branching ratio Π31=(1-Π32) for |3⟩→|1⟩
                'nsteps': 50000} # Max. number of internal ode steps/call.
        >>> results = solve4lme(args)

    :param args: A dictionary of the form ``(parameter, value)``.
    :return: QuTiP Result object with mesolve data.
    :rtype: A :class:`qutip.solver.Result`
    '''

    # Define atomic states |1>, |2>, |3>, |4>
    st1, st2, st3, st4 = map(lambda st: basis(4, st), range(4))

    # Operators for the diagonal elements of atomic Hamiltonian
    # outer product of |1><1|, |2><2|, |3><3|, |4><4|
    sig11, sig22, sig33, sig44 = map(ket2dm, [st1, st2, st3, st4])

    # Operators for the off-diagonal elements of Hamiltonian
    # Interaction Hamiltonian |2>↔|3> and |2>↔|4>
    # Decay modes (collapse operators) for |4>→|2>, |3>→|2>, |3>→|1>
    sig24 = st2 * st4.dag()  # |2><4|
    sig23 = st2 * st3.dag()  # |2><3|
    sig13 = st1 * st3.dag()  # |1><3|

    # Define time independent collapse operators
    # collapse operator for 4->2, sqrt(Π42*Γ) * |2><4|
    C1 = np.sqrt(args['cbr1'] * args['gamma']) * sig24
    # collapse operator for 3->2, sqrt(Π32*Γ) * |2><3|
    C2 = np.sqrt(args['cbr2'] * args['gamma']) * sig23
    # collapse operator for 3->1, sqrt(Π31*Γ) * |1><3|
    C3 = np.sqrt(args['cbr3'] * args['gamma']) * sig13

    # Define list of collapse operators
    c_op_list = [C1, C2, C3]

    # Define time vector
    t = linspace(-args['tint']*2, args['tint']*2, 101)

    # Set up the time independent system Hamiltonians
    # ε1=-w21, ε2 = 0, ε3=-w43-Δ, ε4=-Δ.
    # state1 energy level position ε1=-w21
    HS1 = -args['w21'] * sig11
    # state3 energy level position ε3=-w43-Δ
    HS3 = (-args['w43'] - args['delta']) * sig33
    # state4 energy level position ε4=-Δ
    HS4 = -args['delta'] * sig44

    # Set up operators for the time varying Hamiltonians
    # for laser-atom interaction Ω23(t) and Ω24(t)
    HI1 = sig23.dag() + sig23
    HI2 = sig24.dag() + sig24

    # Set up the time varying RWA Hamiltonian with time dependant
    # coefficients based on QuTip Cython string functions format
    HRWA = [HS1, HS3, HS4,
            [HI1, 'hcf23 * exp(-2*(t / tint) ** 2)'],
            [HI2, 'hcf24 * exp(-2*(t / tint) ** 2)']]

    # Define initial state as state |2>
    psi0 = st2

    # Define ODE solver options
    opts=Odeoptions()
    opts.nsteps = args['nsteps']

    # Workaround for QuTip version 3.1.0 error with Cython string functions
    # Convert Cython string function variable values to numpy.float32
    # # error: two or more data types in declaration specifiers
    # # typedef npy_double _Complex __pyx_t_npy_double_complex;
    args['hcf23'] = np.float32(args['hcf23'])
    args['hcf24'] = np.float32(args['hcf24'])

    # Solve RWA Hamiltonian
    output = mesolve(HRWA, psi0, t, c_op_list, [sig11, sig22, sig33, sig44],
                     args=args, options=opts, progress_bar=True)
    # return mesolve
    return output

def plot4lme(atomdata, results=None, saveplot=None):
    '''Plot QuTiP Result object.'''

    if results != None:
        rs = results
    else:
        rs = qload(atomdata.filename)

    rho11, rho22, rho33, rho44 = rs.expect
    timescale = 1e6
    t = rs.times * timescale

    # Define pump strength as a function of time for plotting
    wp = lambda t, tw, A: A * np.exp(-2*(t / tw) ** 2)

    # Plot the results
    fig = figure()
    subplot(211)
    if atomdata != None:
        # colors #a6cee3 #2078b4 #afdd8a #35a12e #fa9897 #e31a1c
        detuning = atomdata.delta
        detuning_MHz = detuning / (timescale * 2*pi)
        detuning_MHz_str = str(round(detuning_MHz,2))
        fig.suptitle(atomdata.symbol + ', $τ_{int} = %s$ (s), ' % str(atomdata.tint) \
                     + '$Δ = %s$ (MHz)' % detuning_MHz_str)

        ored_sat_24_MHz = atomdata.osat / (timescale * 2 * pi * np.sqrt(atomdata.st24))
        ored_sat_24_MHz_str = str(round(ored_sat_24_MHz,2))
        axhline(y=ored_sat_24_MHz, color='#a6cee3',
                label='$Ω_{red,sat,24} ≈ %s$ MHz' % ored_sat_24_MHz_str)

        ored_cr_MHz = atomdata.__class__(atomdata.tint).ocr \
                    / (timescale * 2 * pi)
        ored_cr_MHz_str = str(round(ored_cr_MHz,2))
        axhline(y=ored_cr_MHz, color='#afdd8a',
                label='$Ω_{red,cr} ≈ %s$ MHz' % ored_cr_MHz_str)

        ored_cr_pop_99_MHz = atomdata.__class__(atomdata.tint, pop=99).ocr \
                           / (timescale * 2 * pi)
        ored_cr_pop_99_MHz_str = str(round(ored_cr_pop_99_MHz,2))
        axhline(y=ored_cr_pop_99_MHz, color='#fa9897',
                label='$Ω_{red,cr}|_{pop=99} ≈ %s$ MHz' % ored_cr_pop_99_MHz_str)
    plot(t, wp(t, atomdata.tint * timescale,
               atomdata.hcf23 * 2 / (timescale * 2*pi)), '-', label='$Ω_{23}$')
    plot(t, wp(t, atomdata.tint * timescale,
               atomdata.hcf24 * 2 / (timescale * 2*pi)), '-', label='$Ω_{24}$')
    ylabel('Coupling strength $Ω$ (MHz)')
    lg_1 = legend()
    lg_1.draw_frame(True)

    subplot(212)
    plot(t, rho11, '-', label='$ρ_{1,1}$')
    plot(t, rho22, '-', label='$ρ_{2,2}$')
    plot(t, rho33, '-', label='$ρ_{3,3}$')
    plot(t, rho44, '-', label='$ρ_{4,4}$')
    ylabel('Population $ρ$ (rel. units)')
    xlabel('Time ($\mu s$)')
    lg_2 = legend()
    lg_2.draw_frame(True)
    # check if filename for exporting figure is provided
    if saveplot != None:
        savefig(saveplot)
    show()

class AtomData4Levels:
    '''Setup parameters for 4-level system RWA Hamiltonian

    Usage::
        >>> class Atom4levelD2line(AtomData4Levels):
                gamma = 1     # excited state decay rate Γ in s^-1
                taue = 1      # exctied state lifetime in s
                w21 = 1756    # hyperfile splitting 1⇤⇥2 in s^-1
                w43 = 48      # hyperfile splitting 3⇤⇥4 in s^-1
                cbr1 = 1      # branching ratio Π42 for |2⟩→|4⟩
                cbr2 = 1/2    # branching ratio Π32 for |3⟩→|2⟩
                cbr3 = 1/2    # branching ratio Π31=(1-Π32) for |3⟩→|1⟩
                st23 = 1      # rel. HF trans. strength factor
                st24 = 1      # rel. HF trans. strength factor
        >>> tint = 5920       # laser-atom interaction time in s
        >>> csd2 = Atom4levelD2line(tint, delta)

    :param tint : laser-atom interaction time in s
    :param delta: detuning Δ from excited state |4⟩ in s^-1
    :param gamma: excited state decay rate Γ in s^-1
    :param taue : exctied state lifetime in s
    :param w21  : hyperfile splitting 1⇤⇥2 in s^-1
    :param w43  : hyperfile splitting 3⇤⇥4 in s^-1
    :param cbr1 : branching ratio Π42 for |2⟩→|4⟩
    :param cbr2 : branching ratio Π32 for |3⟩→|2⟩
    :param cbr3 : branching ratio Π31=(1-Π32) for |3⟩→|1⟩
    :param st23 : HF transition strength factors divided by
    :param st24 : square of reduced dipole matrix element
    :return: Parameters for 4-level system RWA Hamiltonian.
    :rtype: A :class:`AtomData4Levels`
    '''

    listargs = ('delta', 'tint', 'gamma', 'taue', 'w21', 'w43', 'hcf23', 'hcf24',
               'cbr1', 'cbr2', 'cbr3', 'nsteps')

    def __init__(self, tint, delta=0, ored=None, pop=None, nsteps=50000):
        self.tint = tint
        self.delta = delta
        self.ored = ored
        self.pop = pop
        self.nsteps = nsteps
        self.update()

    def update(self):
        '''Update parameters'''
        self.osat = self.omega_saturation(self.gamma)
        if self.pop == None:
            self.pop = (1-np.exp(-1))*100
            self.pop_coef = 1
        else:
            self.pop_coef = - np.log(1-self.pop/100)
        self.ocr = self.omega_critical(self.tint, self.taue, self.w43-self.delta,
                                       self.st23, self.cbr3, self.pop_coef)
        if self.ored == None:
            self.ored = self.ocr
        self.hcf23 = self.hcf(self.ored, self.st23)
        self.hcf24 = self.hcf(self.ored, self.st24)
        self.argsaslist = (self.delta, self.tint, self.gamma, self.taue, self.w21,
                           self.w43, self.hcf23, self.hcf24, self.cbr1, self.cbr2,
                           self.cbr3, self.nsteps)
        self.args = self.gendict(self.listargs, self.argsaslist)
        self.filename = self.args_to_filename(self.listargs, **self.args)

    def args_to_filename(self, listargs, **kwargs):
        '''Return filename from list of args'''
        return ''.join(list(map(lambda i: i + ':' + str(kwargs[i]), listargs)))

    def omega_saturation(self, gamma):
        '''Return aturation Rabi frequency value for reduced Rabi frequency'''
        return gamma / np.sqrt(2)

    def omega_critical(self, ti, te, w43, s23, p3, pop_coef):
        '''Return critical Rabi frequency value for reduced Rabi frequency'''
        return np.sqrt(4 * pop_coef * te * (w43 ** 2) / (ti * p3 * s23))

    def hcf(self, ored, strength):
        '''Return interaction Hamiltonian coeff'''
        return ored * np.sqrt(strength) / 2

    def gendict(self, listargs, args):
        '''Return Cesium D2 Line Data as dictionary'''
        return dict(zip(listargs, args))

class CsD2data:
    '''Store cesium D2 line data.
    Data obtained from Daniel A. Steck, Cesium D Line Data (2010).
    Available online at http://steck.us/alkalidata
    '''
    symbol = 'Cs'
    nspin = Fraction(7,2) # nuclear spin
    jg = Fraction(1,2)    # ground state J
    je = Fraction(3,2)    # excited state J
    # relative hyperfine transition strength factors [D. Steck, Alkali D Line Data]
    # S_{F_{g} F_{e}}
    # F_{g} = 3
    s32, s33, s34 = Fraction(20,56), Fraction(21,56), Fraction(15,56)
    # F_{g} = 4
    s43, s44, s45 = Fraction(7,72), Fraction(21,72), Fraction(44,72)
    skeys = ((3,2), (3,3), (3,4), (4,3), (4,4), (4,5))
    svals = (s32, s33, s34, s43, s44, s45)
    sge = dict(zip(skeys, svals))
    gamma = 32.889e+6             # decay rate Γ 32.889(84)e+6 in s^-1
    taue = 30.405e-9              # lifetime 30.405(77) in s
    wg43 = 2*pi * 9192.631770e+6  # F_{g} hfs 3⇤⇥4 9192.631770e+6 in s^-1
    we54 = 2*pi * 251.0916e+6     # F_{e} hfs 4⇤⇥5 251.0916(20)e+6 in s^-1
    we43 = 2*pi * 201.2871e+6     # F_{e} hfs 3⇤⇥4 201.2871(11)e+6 in s^-1
    we32 = 2*pi * 151.2247e+6     # F_{e} hfs 2⇤⇥3 151.2247(16)e+6 in s^-1
    cbr54 = 1                     # branching ratio Π54 for |F_{5}⟩→|F_{4}⟩
    cbr43 = Fraction(5,12)        # branching ratio Π43 for |F_{4}⟩→|F_{3}⟩
    cbr44 = Fraction(7,12)        # branching ratio Π44 for |F_{4}⟩→|F_{4}⟩
    cbr33 = Fraction(3,4)         # branching ratio Π33 for |F_{3}⟩→|F_{3}⟩
    cbr34 = Fraction(1,4)         # branching ratio Π34 for |F_{3}⟩→|F_{4}⟩
    cbr23 = 1                     # branching ratio Π23 for |F_{2}⟩→|F_{3}⟩

class Cs4L(AtomData4Levels,CsD2data):
    '''Store cesium D2 line data parameters for 4-level system RWA Hamiltonian'''
    w21 = np.float32(CsD2data.wg43)
    w43 = np.float32(CsD2data.we54)
    cbr1 = np.float32(CsD2data.cbr54)
    cbr2 = np.float32(CsD2data.cbr44)
    cbr3 = np.float32(CsD2data.cbr43)
    st24 = np.float32(CsD2data.sge[4,5])
    st23 = np.float32(CsD2data.sge[4,4])

class NaD2data:
    '''Store sodium D2 line data
    Data obtained from Daniel A. Steck, Sodium D Line Data (2010).
    Available online at http://steck.us/alkalidata
    '''
    symbol = 'Na'
    nspin = Fraction(3,2) # nuclear spin
    jg = Fraction(1,2)    # ground state J
    je = Fraction(3,2)    # excited state J
    # relative hyperfine transition strength factors [D. Steck, Alkali D Line Data]
    # S_{F_{g} F_{e}}
    # F_{g} = 1
    s10 = Fraction(2,12)
    s11 = Fraction(5,12)
    s12 = Fraction(5,12)
    # F_{g} = 2
    s21 = Fraction(1,20)
    s22 = Fraction(5,20)
    s23 = Fraction(14,20)
    skeys = ((1,0), (1,1), (1,2), (2,1), (2,2), (2,3))
    svals = (s10, s11, s12, s21, s22, s23)
    sge = dict(zip(skeys, svals))
    gamma = 61.542e+6             # decay rate Γ 61.542(29)e+6 in s^-1
    taue = 16.2492e-9             # lifetime 16.2492(77) in s
    wg21 = 2*pi * 1771.6261288e+6 # F_{g} hfs 1⇤⇥2 1771.6261288(10)e+6 in s^-1
    we32 = 2*pi * 58.326e+6       # F_{e} hfs 2⇤⇥3 58.326(43)e+6 in s^-1
    we21 = 2*pi * 34.344e+6       # F_{e} hfs 1⇤⇥2 34.344(49)e+6 in s^-1
    we10 = 2*pi * 15.810e+6       # F_{e} hfs 0⇤⇥1 15.810(80)e+6 in s^-1
    cbr32 = 1                     # branching ratio Π32 for |F_{3}⟩→|F_{2}⟩
    cbr21 = Fraction(1,2)         # branching ratio Π21 for |F_{2}⟩→|F_{1}⟩
    cbr22 = Fraction(1,2)         # branching ratio Π22 for |F_{2}⟩→|F_{2}⟩
    cbr11 = Fraction(5,6)         # branching ratio Π11 for |F_{1}⟩→|F_{1}⟩
    cbr12 = Fraction(1,6)         # branching ratio Π12 for |F_{1}⟩→|F_{2}⟩
    cbr01 = 1                     # branching ratio Π01 for |F_{0}⟩→|F_{1}⟩

class Na4L(AtomData4Levels,NaD2data):
    '''Store sodium D2 line data parameters for 4-level system RWA Hamiltonian'''
    w21 = np.float32(NaD2data.wg21);
    w43 = np.float32(NaD2data.we32)
    cbr1 = np.float32(NaD2data.cbr32)
    cbr2 = np.float32(NaD2data.cbr22)
    cbr3 = np.float32(NaD2data.cbr21)
    st24 = np.float32(NaD2data.sge[2,3])
    st23 = np.float32(NaD2data.sge[2,2])

def lorentz_norm(delta, deltapeak, gamma):
    '''Return Lorentzian profile of natural linewidth norlalized to 1 at peak.'''
    g, w, w0 = gamma, delta, deltapeak
    return g ** 2 / (4 * ((w - w0)**2 + (g ** 2)/4))

def las_analytic(delta, omegared, atom):
    '''Return laser absorption signal J using approximate analytic expression.'''
    delta23 = - atom.w43 - delta
    delta24 = - delta
    delta23eff = delta23 - omegared * (np.sqrt(atom.st23) + np.sqrt(atom.st24)) / 2
    delta24effsq = (delta24 ** 2) + (atom.st24 * omegared ** 2)/4
    tau_pump = (atom.taue * 8 * (delta23eff ** 2)) \
             / (np.sqrt(pi) * atom.cbr3 * atom.st23 * omegared ** 2)
    J = (atom.st24 * 4 * (delta23 ** 2) * (1 - np.exp(-atom.tint/tau_pump))) \
      / (atom.st23 * (4 * (delta24effsq) + (atom.gamma) ** 2) * atom.cbr3)
    return J

def las_numeric_single(results, gamma):
    '''Return laser absorption signal J by integrating populations
       rho33, rho44 from QuTiP result object using Trapezoidal rule
       and multiplying it by decay rate Γ.'''
    rho11, rho22, rho33, rho44 = results.expect
    times = results.times
    return (numpy.trapz(rho33, times) + numpy.trapz(rho44, times)) * gamma

def solve4lme_list(atomlist, precomputed=True):
    '''Compute and add RWA Hamiltonian QuTiP result object
       to AtomData4Levels object in atomlist.'''
    for atom in atomlist:
        if precomputed:
            atom.result = qload(atom.filename)
        else:
            atom.result = solve4lme(atom.args)
            qsave(atom.result, atom.filename)
        atom.jnumval = las_numeric_single(atom.result, atom.gamma)

def las_plot_jored(jored_expcase_list):
    '''Plot J(Ω_{red}) for fixed Δ'''
    # colors #a6cee3 #2078b4 #afdd8a #35a12e #fa9897 #e31a1c
    atom = jored_expcase_list[0]

    delta = atom.delta
    delta_MHz = delta * AFtoMHz
    delta_MHz_str = str(round(delta_MHz,2))

    jored_list = np.array(list(map(lambda atom: atom.ored, jored_expcase_list)))
    jored_list_vals = np.array(list(map(lambda atom: atom.jnumval, jored_expcase_list)))

    jored_num_list = np.linspace(jored_list[0], jored_list[-1], 201)

    ored_sat_24 = atom.osat / np.sqrt(atom.st24)
    ored_sat_24_MHz = ored_sat_24 * AFtoMHz
    ored_sat_24_MHz_str = str(round(ored_sat_24_MHz,2))

    orec_cr = atom.__class__(atom.tint).ocr
    ored_cr_MHz = orec_cr * AFtoMHz
    ored_cr_MHz_str = str(round(float(ored_cr_MHz),2))

    orec_cr_pop_99 = atom.__class__(atom.tint,pop=99).ocr
    ored_cr_pop_99_MHz = orec_cr_pop_99 * AFtoMHz
    ored_cr_pop_99_MHz_str = str(round(float(ored_cr_pop_99_MHz),2))

    figure()
    axvline(x=ored_sat_24_MHz, color='#a6cee3',
            label='$Ω_{red,sat,24} ≈$ %s MHz' % ored_sat_24_MHz_str)
    axvline(x=ored_cr_MHz, color='#afdd8a',
            label='$Ω_{red,cr} ≈$ %s MHz' % ored_cr_MHz_str)
    axvline(x=ored_cr_pop_99_MHz, color='#fa9897',
            label='$Ω_{red,cr}|_{pop=0.01} ≈$ %s MHz' % ored_cr_pop_99_MHz_str)

    plot(jored_list * AFtoMHz,jored_list_vals, '-', label='numeric calc.')

    plot(jored_num_list * AFtoMHz, las_analytic(delta, jored_num_list, atom),
         '--', label='analytic approx.')
    ylabel('$J(Ω_{red})$ (arb. units)')
    xlabel('$Ω_{red}$ (MHz)')
    title('%s, $Δ$ = %s (MHz), $τ_{int}$ = %s (s)' \
          % (atom.symbol, delta_MHz_str, str(atom.tint)))
    lg = legend()
    lg.draw_frame(True)
    show()

def las_plot_jdelta(jdelta_expcase_list):
    '''Plot J(Δ) for fixed Ω_{red}'''
    # colors #a6cee3 #2078b4 #afdd8a #35a12e #fa9897 #e31a1c
    atom = jdelta_expcase_list[0]

    ored = atom.ored
    ored_MHz = ored * AFtoMHz
    ored_MHz_str = str(round(ored_MHz,2))

    gamma = atom.gamma
    gamma_MHz = gamma * AFtoMHz
    gamma_MHz_str = str(round(gamma_MHz,2))

    jdelta_list = np.array(list(map(lambda atom: atom.delta, jdelta_expcase_list)))
    jdelta_list_vals = np.array(list(map(lambda atom: atom.jnumval, jdelta_expcase_list)))

    jdelta_num_list = np.linspace(jdelta_list[0], jdelta_list[-1], 201)

    figure()
    axvline(x=-gamma_MHz/2, color='#afdd8a',
            label='±$Γ/2$, $Γ≈$ %s MHz (FWHM)' % gamma_MHz_str)
    axvline(x=gamma_MHz/2, color='#afdd8a')

    plot(jdelta_num_list * AFtoMHz,
         lorentz_norm(jdelta_num_list, 0, gamma) * jdelta_list_vals.max(),
         '-', label='natural linewidth')
    plot(jdelta_list * AFtoMHz, jdelta_list_vals, '-', label='numeric calc.')

    plot(jdelta_num_list * AFtoMHz, las_analytic(jdelta_num_list, ored, atom),
         '--', label='analytic approx.')
    ylabel('$J(Δ)$ (arb. units)')
    xlabel('$Δ$ (MHz)')
    title('%s, $Ω_{red}$ = %s (MHz), $τ_{int}$ = %s (s)' \
          % (atom.symbol, ored_MHz_str, str(atom.tint)))
    lg = legend()
    lg.draw_frame(True)
    show()

# converting from/to frequency in MHz to/from angular frequency s^{-1}
MHztoAF = 2 * pi * 1e+6
AFtoMHz = 1/(MHztoAF)
```

## Laser-atom interaction time 0.0002 s

```python
%%capture

expcase_list = Na4L(0.0002), Na4L(0.0002, pop=99), Cs4L(0.0002), Cs4L(0.0002, pop=99)

# set precomputed to False to avoid loading precomputed and saved qutip results
solve4lme_list(expcase_list, precomputed=True)

```

```python
%%capture

# Calculate numerical laser absorption signal J(Ω_{red})|Δ=0
# laser-atom interaction time 0.0002 s,
# Ω_{red} from 0.5 MHz to 50 MHz with vairable step size
na_jored_list = np.linspace(0.25,2.25, 9)
na_jored_list = np.concatenate((na_jored_list, np.linspace(2.5,14.5, 25)), axis=0)
na_jored_list = np.concatenate((na_jored_list, np.linspace(15,50, 15)), axis=0)

na_jored_MHz_list_1 = na_jored_list

na_jored_expcase_list_1 = list(map(lambda omegaMHz:
                                   Na4L(0.0002, ored = omegaMHz * MHztoAF),
                                   na_jored_MHz_list_1))

# set precomputed to False to avoid loading precomputed and saved qutip results
solve4lme_list(na_jored_expcase_list_1, precomputed=True)

# Calculate numerical laser absorption signal J(Ω_{red})|Δ=0
# laser-atom interaction time 0.0002 s,
# Ω_{red} from 0.5 MHz to 50 MHz with vairable step size
cs_jored_list = np.linspace(0.25,2.25, 9)
cs_jored_list = np.concatenate((cs_jored_list, np.linspace(2.5,14.5, 13)), axis=0)
cs_jored_list = np.concatenate((cs_jored_list, np.linspace(15,50, 15)), axis=0)

cs_jored_MHz_list_1 = cs_jored_list

cs_jored_expcase_list_1 = list(map(lambda omegaMHz:
                                   Cs4L(0.0002, ored = omegaMHz * MHztoAF),
                                   cs_jored_MHz_list_1))

# set precomputed to False to avoid loading precomputed and saved qutip results
solve4lme_list(cs_jored_expcase_list_1, precomputed=True)

# Calculate numerical laser absorption signal J(Δ)|Ω_{red}=Ω_{red,cr}|Δ=0
# laser-atom interaction time 0.0002 s,
# Δ from -40 MHz to 40 MHz with vairable step size
na_jdelta_list = np.linspace(-40,-5, 15)
na_jdelta_list = np.concatenate((na_jdelta_list, np.linspace(-4,-1, 4)), axis=0)
na_jdelta_list = np.concatenate((na_jdelta_list, np.linspace(-0.5,0.5, 5)), axis=0)
na_jdelta_list = np.concatenate((na_jdelta_list, np.linspace(1,4, 4)), axis=0)
na_jdelta_list = np.concatenate((na_jdelta_list, np.linspace(5,40, 15)), axis=0)

na_jdelta_MHz_list_1 = na_jdelta_list

na_jdelta_expcase_list_1 = list(map(lambda deltaMHz:
                                   Na4L(0.0002, delta = deltaMHz * MHztoAF,
                                        ored = Na4L(0.0002).ocr),
                                   na_jdelta_MHz_list_1))

# set precomputed to False to avoid loading precomputed and saved qutip results
solve4lme_list(na_jdelta_expcase_list_1, precomputed=True)

# Calculate numerical laser absorption signal J(Δ)|Ω_{red}=Ω_{red,cr}|Δ=0
# laser-atom interaction time 0.0002 s,
# Δ from -40 MHz to 40 MHz with vairable step size
na_jdelta_list_2 = np.linspace(-40,-5, 15)
na_jdelta_list_2 = np.concatenate((na_jdelta_list_2, np.linspace(-4,-1, 4)), axis=0)
na_jdelta_list_2 = np.concatenate((na_jdelta_list_2, np.linspace(-0.5,0.5, 5)), axis=0)
na_jdelta_list_2 = np.concatenate((na_jdelta_list_2, np.linspace(1,4, 4)), axis=0)
na_jdelta_list_2 = np.concatenate((na_jdelta_list_2, np.linspace(5,40, 15)), axis=0)

na_jdelta_MHz_list_2 = na_jdelta_list_2

na_jdelta_expcase_list_2 = list(map(lambda deltaMHz:
                                   Na4L(0.0002, delta = deltaMHz * MHztoAF,
                                        ored = Na4L(0.0002, pop=99).ocr),
                                   na_jdelta_MHz_list_2))

# set precomputed to False to avoid loading precomputed and saved qutip results
solve4lme_list(na_jdelta_expcase_list_2, precomputed=True)

# Calculate numerical laser absorption signal J(Δ)|Ω_{red}=Ω_{red,cr}|Δ=0
# laser-atom interaction time 0.0002 s,
# Δ from -40 MHz to 40 MHz with vairable step size
cs_jdelta_list = np.linspace(-40,-5, 15)
cs_jdelta_list = np.concatenate((cs_jdelta_list, np.linspace(-4,-1, 4)), axis=0)
cs_jdelta_list = np.concatenate((cs_jdelta_list, np.linspace(-0.5,0.5, 5)), axis=0)
cs_jdelta_list = np.concatenate((cs_jdelta_list, np.linspace(1,4, 4)), axis=0)
cs_jdelta_list = np.concatenate((cs_jdelta_list, np.linspace(5,40, 15)), axis=0)

cs_jdelta_MHz_list_1 = cs_jdelta_list

cs_jdelta_expcase_list_1 = list(map(lambda deltaMHz:
                                   Cs4L(0.0002, delta = deltaMHz * MHztoAF,
                                        ored = Cs4L(0.0002).ocr,
                                        nsteps=100000),
                                   cs_jdelta_MHz_list_1))

# set precomputed to False to avoid loading precomputed and saved qutip results
solve4lme_list(cs_jdelta_expcase_list_1, precomputed=True)

# Calculate numerical laser absorption signal J(Δ)|Ω_{red}=Ω_{red,cr}|Δ=0
# laser-atom interaction time 0.0002 s,
# Δ from -40 MHz to 40 MHz with vairable step size
cs_jdelta_list_2 = np.linspace(-40,-5, 15)
cs_jdelta_list_2 = np.concatenate((cs_jdelta_list_2, np.linspace(-4,-1, 4)), axis=0)
cs_jdelta_list_2 = np.concatenate((cs_jdelta_list_2, np.linspace(-0.5,0.5, 5)), axis=0)
cs_jdelta_list_2 = np.concatenate((cs_jdelta_list_2, np.linspace(1,4, 4)), axis=0)
cs_jdelta_list_2 = np.concatenate((cs_jdelta_list_2, np.linspace(5,40, 15)), axis=0)

cs_jdelta_MHz_list_2 = na_jdelta_list_2

cs_jdelta_expcase_list_2 = list(map(lambda deltaMHz:
                                   Cs4L(0.0002, delta = deltaMHz * MHztoAF,
                                        ored = Cs4L(0.0002, pop=99).ocr,
                                        nsteps=100000),
                                   cs_jdelta_MHz_list_2))

# set precomputed to False to avoid loading precomputed and saved qutip results
solve4lme_list(cs_jdelta_expcase_list_2, precomputed=True)

```

\begin{thebibliography}{7}

    \bibitem[1]{communmathphys.48.119}
    G.~Lindblad, \href{http://dx.doi.org/10.1007/bf01608499}{Commun. Math. Phys. \textbf{48}, 119 (1976)}

    \bibitem[2]{Gardiner_2004}
    C.~Gardiner and P.~Zoller, \href{http://www.springer.com/gp/book/9783540223016}{\emph{Quantum Noise}}, Springer Series in Synergetics (Springer-Verlag Berlin Heidelberg, 2004)

    \bibitem[3]{Walls_2008}
    D.~Walls and G.~J. Milburn, \href{http://dx.doi.org/10.1007/978-3-540-28574-8}{\emph{Quantum Optics}} (Springer Science $\mathplus$ Business Media, 2008)

    \bibitem[4]{Shore_2011}
    B.~W. Shore, \href{http://dx.doi.org/10.1017/cbo9780511675713}{\emph{Manipulating Quantum Structures Using Laser Pulses}} (Cambridge University Press, 2011)

    \bibitem[5]{Steck_Cs_2010}
    D.~A. Steck, \href{http://steck.us/alkalidata}{\emph{Cesium D Line Data} (http://steck.us/alkalidata, 2010)}

    \bibitem[6]{Steck_Na_2010}
    D.~A. Steck, \href{http://steck.us/alkalidata}{\emph{Sodium D Line Data} (http://steck.us/alkalidata, 2010)}

    \bibitem[7]{Loudon_2000}
    R.~Loudon, \href{http://global.oup.com/academic/product/the-quantum-theory-of-light-9780198501763}{\emph {The Quantum Theory of Light Third Edition}} (Oxford University Press, 2000)

\end{thebibliography}

notebooks/4-level-system/Hyperfine transition dynamics of D2 line using 4-level system Hamiltonian and QuTiP.ipynb


# Código General para  PFR proyecto
  ## Problema con flujo molar
  Guía para Simulación de Producción de monóxido de carbono e hidrógeno a partir de
formaldehído

```python
#importar librería para evaluar la integral de la fórmula del reactor PFR
from sympy import *
init_printing()
```

```python
# definir datos del problema
XA= 0.73 #colocar la converión requerida
Fa0 = 0.199 #mol/s flujo molar
K = 0.014 #s^-1
Ya0 = 1
P = 107 #Pascales
T =400 #Kelvin
R =8.314 #Cte de los gases

```

```python
# Definir funciones del problema
Ca_0 = (Ya0*P)/(R*T) #mol/L
V0 = Fa0/Ca_0
x = symbols("x") #x representa la variable de la conversión
f=1/(K*Ca_0*(1-x)) #f representa la función dentro de la integral
V = Ca_0*V0*integrate(f, (x,0,0.73)) #los datos entre paréntesis representan lo límites de la integral definida en función de la conversión
```

```python
print(V) #encontrar valor de volumen
```

    578.444231574541

  ## Problema con flujo volumétrico
   Guía para Simulación de Isomerización de Cis-2-buteno a Trans-2-buteno:

```python
# definir datos del problema
XA= 0.78 #colocar la converión requerida
V0 = 0.8 #m3/h flujo volumétrico
K = (0.42*60) #conversión de min^-1 a h^-1

print(K)
```

    25.2

```python
x = symbols("x") #x representa la variable de la conversión
f=1/((1-x)) #f representa la función dentro de la integral
V =(V0/K)*integrate(f, (x,0,0.78)) #los datos entre paréntesis representan lo límites de la integral definida en función de la conversión
```

```python
print(V)
```

    0.0480675470676119

# Código general para CSTR
 Guía para Simulación de Producción de monóxido de carbono e hidrógeno a partir de
formaldehído

```python
# definir datos del problema
XA= 0.98 #colocar la converión requerida
Fa0 = 0.22 #mol/s flujo molar
K = 0.34 #s^-1
Ya0 = 1
P = 103.421 #Pascales
T =298 #Kelvin
R =8.314 #Cte de los gases
```

```python
# Definir funciones del problema
Ca_0 = (Ya0*P)/(R*T) #mol/L
V0 = Fa0/Ca_0
rA= (K*Ca_0)*(1-XA)
V = (Ca_0*V0*XA)/rA
print(rA)
```

    0.0002838516095596821

```python
print(V)
```

    759.5517965687926

 Guía para Simulación de Isomerización de Cis-2-buteno a Trans-2-buteno:

```python
# definir datos del problema
XA= 0.77 #colocar la converión requerida
K = (0.45*60) #conversión de min^-1 a h^-1
V0 = 0.9 #m3/h flujo volumétrico
print(K)
```

    27.0

```python
#V= Fa_0*XA/rA
#Fa_0= Ca_0*XA*V0/K*Ca_0*(1-XA)
V=(V0*XA)/(K*(1-XA))
```

```python
print(V)
```

    0.11159420289855075

# PFR y estequiometría
 Guía para Simulación de Reacción de descomposición del NOCl en NO y Cl2:

```python
# definir datos del problema
XA= 0.9 #colocar la converión requerida
Fa0 = 8 #mol/s flujo molar
Ca_0= 0.7 #mol/dm3
K = 0.29 #dm3/mol*s
V0=10 #m3/s
Ya_0=1
delta=(1+(1/2)-1)
epsilon=Ya_0*delta
x = symbols("x")
Ca= (Ca_0*(1-x))/(1+(epsilon*x))
rA=K*((Ca)**2)
f=((1+(epsilon*x))**2)/((1-x)**2)
Result= integrate(f, (x,0,0.9))
V=Result*V0/(K*Ca_0)
print(V)
```

    838.478934015219

## Problema PFR
Guía para Reacción de síntesis de Metanotiol

```python
#Datos de problema

#Entrada
Ya_0=0.4 #metano reactivo limitante
Yb_0=0.4 #azufre reactivo en exceso
Yinerte=0.2
FT_0=15 #mol/s flujo total inicial

#Salida del reactor
XA=0.8 #conversión a la salida del reactor

#Codiciones de reactor
T=700 #C
T0=700+273.15 #Kelvin
P= 2 #atm presión constante
#rA=K*Ca*Cb
K=15 #L/mol*s
R=0.082 #L*atm/mol*K

#Determinando CA_0
CA_0=(P*Ya_0)/(R*T0)
print(CA_0)

```

    0.010025276227689062

**Ecuación de diseño para PFR**
$tao = Ca0\int_0^x 1/(-rA) dxA$

```python
#términos de conversión:
#Ca=CA_0*(1-XA)/(1+(epsilon*XA))
#Cb=CA_0*(Mb-(b/a*XA))/((1-XA)/(1+(epsilon*XA)))
MB=Yb_0/Ya_0
print(MB)

```

    1.0

```python
#Razón de cambio
#deltan=sumatoria de coef reactivos -sumatoria coef productos
deltan=(1-(1+1))
a=1 #coef estequiométrico del reactivo limitante
epsilon=Ya_0*deltan/a
print(epsilon)

```

    -0.4

```python
#Sutituyendo en ecuación de velocidad de la reacción
#rA=K*((CA_0)**2)*((1-XA)*(MB-XA)/((1+(epsilon*XA)))**2)

#sustituyendo en la ecuación de diseño

x = symbols("x") #x representa la variable de la conversión
f=(((1+(epsilon*x)))**2)/((1-x)*(MB-x)) #f representa la función dentro de la integral
t =(1/(K*CA_0))*integrate(f, (x,0,0.6))
print(t) #tiempo de residencia en segundos

```

    7.15404760837253

### Calculando volumen para % de conversión

```python
#se sabe que el tiempo de residencia es igual al V/V0
Fa0=FT_0*Ya_0 #mol/s
V0=Fa0/CA_0 #L/s
V=V0*t
print(V) #L

```

    4281.60627950396

### Cálculo de espacio velocidad medio

```python
S=1/t
print(S)
```

    0.139781009959967

```python

```

Instrucciones de proyecto/Plantillas de proyecto(1).ipynb


# Ensemble methods. Bagging

Bagging consists of the following steps:
1. create a bootstrap samples \begin{equation}S_{i}\end{equation},
2. for each sample train a classifier,
3. vote $f(x)=\arg\max\sum_{i}^{T}(f_{i}(X)=y)$.
In other words, we have a list of prediction and set the prediction to the most occurance prediction.

Let's load the data set in the first place.

```python
%store -r data_set
%store -r labels
%store -r test_data_set
%store -r test_labels
%store -r unique_labels
```

We use the accuracy metric from scikit-learn and the the tree package.

```python
from sklearn import tree
import numpy as np
from sklearn.metrics import accuracy_score

decision_tree = tree.DecisionTreeClassifier()
```

A boostrap generation method take randomly data from out data set and return is as a bootstrap set.

```python
def create_bootstrap_data():
    bootstrap_ids = np.random.randint(0, len(data_set), size=len(data_set))
    return data_set[bootstrap_ids,:],labels[bootstrap_ids]
```

We build a classifier based on decision tree that is used later for generation the prediction. We can use different classifiers here.

```python
def build_classifier(data_set, labels):
    decision_tree = tree.DecisionTreeClassifier()
    decision_tree.fit(data_set, labels)
    return decision_tree
```

Based on the number of cases, we build many classifiers where each trains on different bootstrap data set.

```python
def build_classifiers(cases):
    classifiers = []
    for case in range(cases):
        bootstrap_set, bootstrap_labels = create_bootstrap_data()
        classifier = build_classifier(bootstrap_set, bootstrap_labels)
        classifiers.append(classifier)
    return classifiers
```

The voting part is just counting on the classified values and get the max occurency of it.

```python
def vote(classifiers, test_data):
    output = []
    for classifier in classifiers:
        output.append(classifier.predict(test_data))
    output = np.array(output)
    predicted = []
    for i in range(len(test_data)):
        classified = output[:, i]
        counts = np.bincount(classified)
        predicted.append(np.argmax(counts))
    return predicted
```

Finally, we can check the results based on ten classifiers.

```python
classifiers = build_classifiers(2)
predicted = vote(classifiers,test_data_set)
accuracy = accuracy_score(test_labels, predicted)
print(accuracy)
```

    0.85

We can easily compare it to just one tree:

```python
data_set, labels = create_bootstrap_data()
decision_tree = tree.DecisionTreeClassifier()
decision_tree.fit(data_set, labels)
predicted = decision_tree.predict(test_data_set)
accuracy = accuracy_score(test_labels, predicted)
print(accuracy)
```

    0.85

```python

```

```python

```


# Symbolic Filtering
This notebook contains the symbolic computations left out in *(Zander et al 2020)*. First we load the necessary packages.

```python
from sympy import *
from sympy.physics.quantum.dagger import Dagger
#from sympy.matrices import Matrix, PermutationMatrix
#from sympy.combinatorics import Permutation
#import itertools
```

## Isometry of the ambient space
We now will define the transformation from the Mueller matrix space to the Hermitian matrix space.
This transformation is taken from Theorem A.1 of *(Van Der Mee 1993)*.
We also compute the eigenvalues of this transformation.

```python
Ttr = Matrix([[1,  0,  0,  0,    0,  1,  0,  0,    0,  0,  1,  0,     0,  0,  0,  1],# first row
              [0,  1,  0,  0,    1,  0,  0,  0,    0,  0,  0, -I,     0,  0,  I,  0],
              [0,  0,  1,  0,    0,  0,  0, I,    1,  0,  0,  0,     0,  -I,  0,  0],
              [0,  0,  0,  1,    0,  0, -I,  0,    0,  I,  0,  0,     1,  0,  0,  0],
              [0,  1,  0,  0,    1,  0,  0,  0,    0,  0,  0,  I,     0,  0, -I,  0],# second row
              [1,  0,  0,  0,    0,  1,  0,  0,    0,  0, -1,  0,     0,  0,  0, -1],
              [0,  0,  0,  I,    0,  0,  1,  0,    0,  1,  0,  0,    -I,  0,  0,  0],
              [0,  0,  I,  0,    0,  0,  0,  1,   -I,  0,  0,  0,     0,  1,  0,  0],
              [0,  0,  1,  0,    0,  0,  0,  -I,    1,  0,  0,  0,     0, I,  0,  0],# third row
              [0,  0,  0, -I,    0,  0,  1,  0,    0,  1,  0,  0,     I,  0,  0,  0],
              [1,  0,  0,  0,    0, -1,  0,  0,    0,  0,  1,  0,     0,  0,  0, -1],
              [0,  I,  0,  0,   -I,  0,  0,  0,    0,  0,  0,  1,     0,  0,  1,  0],
              [0,  0,  0,  1,    0,  0,  I,  0,    0, -I,  0,  0,     1,  0,  0,  0],# fourth row
              [0,  0, -I,  0,    0,  0,  0,  1,    I,  0,  0,  0,     0,  1,  0,  0],
              [0, -I,  0,  0,    I,  0,  0,  0,    0,  0,  0,  1,     0,  0,  1,  0],
              [1,  0,  0,  0,    0, -1,  0,  0,    0,  0, -1,  0,     0,  0,  0,  1]])
T=1/2*Ttr
Tinv=T.inv()
T.eigenvals()
```

    {2.27681110432308e-64 - 1.0*I: 1,
     2.28106141364163e-64 + 1.0*I: 1,
     -1.0 - 1.74248350929473e-66*I: 1,
     -1.0 + 1.29526354172069e-67*I: 1,
     -1.00000000000000: 1,
     1.0 - 2.59055960378824e-65*I: 1,
     1.0 - 1.76463675822631e-65*I: 1,
     1.0 - 7.05024075375862e-65*I: 1,
     1.0 + 1.51572411110342e-64*I: 1,
     1.0 + 1.77251496337077e-65*I: 1,
     1.0 + 2.59040055766941e-64*I: 1,
     1.0 + 6.75358757001636e-65*I: 1,
     1.0 - 4.07661930536338e-64*I: 1,
     1.0 - 6.32755730257852e-65*I: 1,
     1.0 + 6.79447449556865e-65*I: 1,
     1.0 - 9.40836242257732e-66*I: 1}

Of course, this eigenvalues give us that the linear map `T` is unitary.
Alternatively, the following also shows that `T` is unitary.

```python
T*Dagger(T) == Dagger(T)*T == eye(16)
```

    True

In order to apply `T` and its inverse to $4\times 4$ matrices
we need to transform matrices to vectors and vice versa.
Hence with the following functions we can now conveniently handle matrices natively.

```python
def T0(x):
    return Matrix(4, 4, Ttr * Matrix(16, 1, x))
def T(x):
    return 1/2*T0(x)
def invT(x):
    return Matrix(4, 4, Tinv * Matrix(16, 1, x))
def invT0(x):
    return 2 * invT(x)
```

The reader might find it very burdensome to check that
the matrix `T` actually gives the same linear map as the one given in Theorem A.1 of *(Van Der Mee 1993)*.
For that we define a symbolic Mueller matrix and apply our transformations
to get some convient presentation of `T`.

```python
m11, m12, m13, m14, m21, m22, m23, m24, m31, m32, m33, m34, m41, m42, m43, m44 = symbols('m11, m12, m13, m14, m21, m22, m23, m24,  m31, m32, m33, m34, m41, m42, m43, m44', real=True)
h11, h12, h13, h14, h21, h22, h23, h24, h31, h32, h33, h34, h41, h42, h43, h44 = symbols('h11, h12, h13, h14, h21, h22, h23, h24,  h31, h32, h33, h34, h41, h42, h43, h44')
M=Matrix([[m11, m12, m13, m14],
          [m21, m22, m23, m24],
          [m31, m32, m33, m34],
          [m41, m42, m43, m44]])
T0(M)
```

$\displaystyle \left[\begin{matrix}m_{11} + m_{22} + m_{33} + m_{44} & m_{12} + m_{21} - i m_{34} + i m_{43} & m_{13} + i m_{24} + m_{31} - i m_{42} & m_{14} - i m_{23} + i m_{32} + m_{41}\\m_{12} + m_{21} + i m_{34} - i m_{43} & m_{11} + m_{22} - m_{33} - m_{44} & i m_{14} + m_{23} + m_{32} - i m_{41} & i m_{13} + m_{24} - i m_{31} + m_{42}\\m_{13} - i m_{24} + m_{31} + i m_{42} & - i m_{14} + m_{23} + m_{32} + i m_{41} & m_{11} - m_{22} + m_{33} - m_{44} & i m_{12} - i m_{21} + m_{34} + m_{43}\\m_{14} + i m_{23} - i m_{32} + m_{41} & - i m_{13} + m_{24} + i m_{31} + m_{42} & - i m_{12} + i m_{21} + m_{34} + m_{43} & m_{11} - m_{22} - m_{33} + m_{44}\end{matrix}\right]$

The same can be done for the inverse of `T`.

```python
H=Matrix([[h11, h12, h13, h14],
          [h21, h22, h23, h24],
          [h31, h32, h33, h34],
          [h41, h42, h43, h44]])
invT0(H)
```

$\displaystyle \left[\begin{matrix}h_{11} + h_{22} + h_{33} + h_{44} & h_{12} + h_{21} - 1.0 i h_{34} + i h_{43} & h_{13} - 1.0 i h_{24} + h_{31} + i h_{42} & h_{14} - 1.0 i h_{23} + i h_{32} + h_{41}\\h_{12} + h_{21} + i h_{34} - 1.0 i h_{43} & h_{11} + h_{22} - 1.0 h_{33} - 1.0 h_{44} & i h_{14} + h_{23} + h_{32} - 1.0 i h_{41} & - 1.0 i h_{13} + h_{24} + i h_{31} + h_{42}\\h_{13} + i h_{24} + h_{31} - 1.0 i h_{42} & - 1.0 i h_{14} + h_{23} + h_{32} + i h_{41} & h_{11} - 1.0 h_{22} + h_{33} - 1.0 h_{44} & i h_{12} - 1.0 i h_{21} + h_{34} + h_{43}\\h_{14} + i h_{23} - 1.0 i h_{32} + h_{41} & i h_{13} + h_{24} - 1.0 i h_{31} + h_{42} & - 1.0 i h_{12} + i h_{21} + h_{34} + h_{43} & h_{11} - 1.0 h_{22} - 1.0 h_{33} + h_{44}\end{matrix}\right]$

## Geometry of the Mueller matrix cone
These are the complimentary computation to the last section before the conclusions.
We start be setting up symbolic matrices which belong to the real manifold $C_{4k}$ with $1\le k\le 4$ in order to compute the map $F$ symbolically as both defined in *(Zander et al 2020)*.

```python
a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31 =symbols('a0:32', real=True)
L1= Matrix([[a0 + I*a1, a2 + I*a3, a4 + I*a5, a6 + I*a7],
            [0,0,0,0],
            [0,0,0,0],
            [0,0,0,0]]).T
L2= Matrix([[a0 + I*a1, a2 + I*a3, a4 + I*a5, a6 + I*a7],
            [a8 + I*a9, a10+ I*a11,a12+ I*a13,a14+ I*a15],
            [0,0,0,0],
            [0,0,0,0]]).T
L3= Matrix([[a0 + I*a1, a2 + I*a3, a4 + I*a5, a6 + I*a7],
            [a8 + I*a9, a10+ I*a11,a12+ I*a13,a14+ I*a15],
            [a16+ I*a17,a18+ I*a19,a20+ I*a21,a22+ I*a23],
            [0,0,0,0]]).T
L4= Matrix([[a0 + I*a1, a2 + I*a3, a4 + I*a5, a6 + I*a7],
            [a8 + I*a9, a10+ I*a11,a12+ I*a13,a14+ I*a15],
            [a16+ I*a17,a18+ I*a19,a20+ I*a21,a22+ I*a23],
            [a24+ I*a25,a26+ I*a27,a28+ I*a29,a30+ I*a31]]).T
```

We now compute the image under $F$ of these matrices and check that the polynomial equations in the resulting symbolic Mueller matrix are homogenous.

```python
M1=expand(invT(L1*Dagger(L1)))
M2=expand(invT(L2*Dagger(L2)))
M3=expand(invT(L3*Dagger(L3)))
M4=expand(invT(L4*Dagger(L4)))
def checkHomogenous(m):
    return all([x.is_homogeneous for x in map(poly, m)])
all([checkHomogenous(M1), checkHomogenous(M2), checkHomogenous(M3), checkHomogenous(M4)])
```

    True

Of course, because of the computation $LL^{\dagger}$ and the linear character of the map $T$  the resulting polynomials are quadratic. But we can also easily check it directly.

```python
def homogenousOrder(m):
    return [x.homogeneous_order() for x in map(poly, m)]
[homogenousOrder(M1), homogenousOrder(M2), homogenousOrder(M3), homogenousOrder(M4)]
```

    [[2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2],
     [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2],
     [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2],
     [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2]]

Another interesting following observation is that the upper left element of the Mueller matrix is the same as the quadratic Euclidean length of its corresponding element in $C_{4k}$.

```python
M4[0,0]
```

$\displaystyle 0.5 a_{0}^{2} + 0.5 a_{1}^{2} + 0.5 a_{10}^{2} + 0.5 a_{11}^{2} + 0.5 a_{12}^{2} + 0.5 a_{13}^{2} + 0.5 a_{14}^{2} + 0.5 a_{15}^{2} + 0.5 a_{16}^{2} + 0.5 a_{17}^{2} + 0.5 a_{18}^{2} + 0.5 a_{19}^{2} + 0.5 a_{2}^{2} + 0.5 a_{20}^{2} + 0.5 a_{21}^{2} + 0.5 a_{22}^{2} + 0.5 a_{23}^{2} + 0.5 a_{24}^{2} + 0.5 a_{25}^{2} + 0.5 a_{26}^{2} + 0.5 a_{27}^{2} + 0.5 a_{28}^{2} + 0.5 a_{29}^{2} + 0.5 a_{3}^{2} + 0.5 a_{30}^{2} + 0.5 a_{31}^{2} + 0.5 a_{4}^{2} + 0.5 a_{5}^{2} + 0.5 a_{6}^{2} + 0.5 a_{7}^{2} + 0.5 a_{8}^{2} + 0.5 a_{9}^{2}$

For completeness we state a unique form of the element in the matrix $C_{44}/{U}(k)$. Also we state one which has at most finitely many representations namely the ones of $C_{4k}/{U}(k)$ for $k\le 3$.
Note that we have to assume that the elements `a0, a10, a20, a30` are strictly positive.
To get the corresponding Mueller matrices in case of `L5` we just have to do same as above (see `M5`).
For the Matrices `L6, L7, L8` we need to conjugate $LL^{\dagger}$ by a permutation matrix according to Lemma 1 of *(Higham 1990)*.
We leave this computation out for now.
But we plan to do it soon, as conveniently
in the oncoming [1.6 Release of Sympy](https://github.com/sympy/sympy/wiki/Release-Notes-for-1.6) the permutation matrices will be included natively.

```python
L5= Matrix([[a0 ,0,0,0],
            [a8+I*a9, a10,0,0],
            [a16+ I*a17,a18+ I*a19,a20,0],
            [a24+ I*a25,a26+ I*a27,a28+ I*a29,a30]]).T
L6= Matrix([[0,0,0,0],
            [a8+I*a9, a10,0,0],
            [a16+ I*a17,a18+ I*a19,a20,0],
            [a24+ I*a25,a26+ I*a27,a28+ I*a29,a30]]).T
L7= Matrix([[0,0,0,0],
            [0,0,0,0],
            [a16+ I*a17,a18+ I*a19,a20,0],
            [a24+ I*a25,a26+ I*a27,a28+ I*a29,a30]]).T
L8= Matrix([[0,0,0,0],
            [0,0,0,0],
            [0,0,0,0],
            [a24+ I*a25,a26+ I*a27,a28+ I*a29,a30]]).T
M5=expand(invT(L5*Dagger(L5)))
#def ListPermuationMatrices(x):
#    y=map(lambda x: PermutationMatrix(Permutation(x)),
#            list(itertools.permutations(list(range(x)))))
#    return [z.as_explicit() for z in y]
#M6=[expand(invT(x*L6*Dagger(L6)*x.T)) for x in ListPermuationMatrices(4)]
```

## References

Higham, Nicholas J. 1990. “Analysis of the Cholesky Decomposition of a Semi-Definite Matrix.” In Reliable Numerical Computation, 161–85. Oxford Sci. Publ. Oxford Univ. Press, New York.

Van Der Mee, CVM. 1993. “An Eigenvalue Criterion for Matrices Transforming Stokes Parameters.” Journal of Mathematical Physics 34 (11): 5072–88.

Zander, T and J Beyerer. 2020. “The Mueller matrix cone and its application to filtering.” to appear

```python

```


```python
from sympy import *
import numpy as np
```

```python
def m_to_in(arg):
    return arg*39.3701

def in_to_m(arg):
    return arg/39.3701
```

### Inputs:

```python
P_tanks = 1.379e+6 #  Pressure in tanks, Pascals (200 PSI)
# Pressure_Tanks = 101325*2; # Pascals (2ATM)
D_tanks = in_to_m(10) # Diameter of tanks, meters (12 inches)
T_cryo = 90.15  # Kevlin *CHANGE ME* ACCORDING TO WHICH CRYOGRENIC FUEL YOU WANT TO EXAMINE
T3 = 270 # Kelvin * CHANGE ME* ACCORDING TO VEHICLE SIZING
```

### Constants:

```python
simgay_al = 324e+6  # Tensile_Strength_Yield_Al_6061T, Pascals (4700 PSI) @ 77.15 K -196 Celsius
# Tensile Chosen because structure will be in tension.
# http://www.matweb.com/search/datasheet_print.aspx?matguid=1b8c06d0ca7c456694c7777d9e10be5b
K_CFRP = 7.0  # CFRP Thermal Conductivity, Watts/Meter Kelvin
K_PU = 0.025  # Polyurethane_Thermal_Conductivity, Watts/Meter Kelvin
T_ambient = 299.15  # Kelvin
H = 35  # Convective Heat Transfer Coefficient, Watts/SQR Meter Kelvin

FS = 1.5  # Safety Factor
```

### Calculations:

```python
R1 = D_tanks /2
t = Symbol('t')
R = Symbol('R')

t_al = solve((P_tanks*D_tanks)/(4*t) - simgay_al, t)  # thickness of aluminum, meters
t_al = float(t_al[0])  # convert to floating point number

t_al = 0.00635

R2 = R1 + 1.5 * t_al  # Meters
T2 = T_cryo   # Kelvin Assumption: WORST CASE
L = 1.0

R_soln = solve(2*np.pi*R*L*H*(T_ambient-T3) - ((2*pi*L)*K_PU*(T3-T2)/log(R/R2)), R)

print('Thickness Aluminum:', m_to_in(t_al), 'in')
print('Radius3:', m_to_in(R_soln[0]), 'in')
print('Thickness of Polyurethane:', m_to_in(R_soln[0]-R2), 'in')
```

    Thickness Aluminum: 0.250000135 in
    Radius3: 5.54581854038781 in
    Thickness of Polyurethane: 0.170818337887813 in

```python

```

```python

```


# Statistics

## 1.1 Sales for the past week was the following amounts: [3505, 2400, 3027, 2798, 3700, 3250, 2689]. Without using library functions, what is the mean, variance, and standard deviation of of sales from last week? (for extra bonus points, write your own function that can calculate these two values for any sized list)

```
#import math functions
import math

# Writing function for mean (mea)
def mea(lis):
  return sum(lis)/len(lis)

#writing function for vaiance
def variance(lis):
  return(sum((xi-mea(lis))**2 for xi in lis)/(len(lis))-1)

#Writing function for stand dev
def std_dev(lis):
  return(math.sqrt((sum((xi-mea(lis))**2 for xi in lis)/(len(lis))-1)))

#entering in my sales values
sale=[3505, 2400, 3027, 2798, 3700, 3250, 2689]

#finding values for each, mean, variance, std_dev
print('The mean is: ', mea(sale))
print('The Variation is: ',variance(sale))
print('The standard deviation is: ', std_dev(sale))
```

    The mean is:  3052.714285714286
    The Variation is:  183760.06122448976
    The standard deviation is:  428.6724404769798

## 1.2 Find the covariance between last week's sales numbers and the number of customers that entered the store last week: [127, 80, 105, 92, 120, 115, 93] (you may use librray functions for calculating the covariance since we didn't specifically talk about its formula)

```
#importing the libraries I will need
import pandas as pd
import numpy as np

#entering customer num
cust=[127, 80, 105, 92, 120, 115, 93]

data={'sale': sale, 'cust':cust}
df=pd.DataFrame(data)

#finding covariance
df.cov()
```

      sale
      cust

      sale
      214387.904762
      7604.357143

      cust
      7604.357143
      290.952381

## 1.3 Find the standard deviation of customers who entered the store last week. Then, use the standard deviations of both sales and customers to standardize the covariance to find the correlation coefficient that summarizes the relationship between sales and customers. (You may use library functions to check your work.)

```
#std dev for customers entering store
print('The std dev for customers entering store is:' std_dev(cust))

```

    15.760322176340203

## 1.4 Use pandas to import a cleaned version of the titanic dataset from the following link: [Titanic Dataset](https://raw.githubusercontent.com/Geoyi/Cleaning-Titanic-Data/master/titanic_clean.csv)

## Calculate the variance-covariance matrix and correlation matrix for the titanic dataset's numeric columns. (you can encode some of the categorical variables and include them as a stretch goal if you finish early)

```
!wget https://raw.githubusercontent.com/Geoyi/Cleaning-Titanic-Data/master/titanic_clean.csv
```

    --2019-11-12 22:45:06--  https://raw.githubusercontent.com/Geoyi/Cleaning-Titanic-Data/master/titanic_clean.csv
    Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 151.101.0.133, 151.101.64.133, 151.101.128.133, ...
    Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|151.101.0.133|:443... connected.
    HTTP request sent, awaiting response... 200 OK
    Length: 137509 (134K) [text/plain]
    Saving to: ‘titanic_clean.csv’

    titanic_clean.csv   100%[===================>] 134.29K  --.-KB/s    in 0.04s

    2019-11-12 22:45:06 (3.61 MB/s) - ‘titanic_clean.csv’ saved [137509/137509]

```
column_headers=("","pclass",
                "survived",
                "name",
                "sex",
                "age",
                "sibsp",
                "parch",
                "ticket",
                "fare",
                "cabin",
                "embarked",
                "boat",
                "body",
                "home.dest",
                "has_cabin_number")
df=pd.read_csv('titanic_clean.csv', header = None, names= column_headers)
df.head()

```

      pclass
      survived
      name
      sex
      age
      sibsp
      parch
      ticket
      fare
      cabin
      embarked
      boat
      body
      home.dest
      has_cabin_number

      0
      NaN
      pclass
      survived
      name
      sex
      age
      sibsp
      parch
      ticket
      fare
      cabin
      embarked
      boat
      body
      home.dest
      has_cabin_number

      1
      1.0
      1
      1
      Allen, Miss. Elisabeth Walton
      female
      29
      0
      0
      24160
      211.3375
      B5
      S
      2
      NaN
      St Louis, MO
      1

      2
      2.0
      1
      1
      Allison, Master. Hudson Trevor
      male
      0.9167
      1
      2
      113781
      151.55
      C22 C26
      S
      11
      NaN
      Montreal, PQ / Chesterville, ON
      1

      3
      3.0
      1
      0
      Allison, Miss. Helen Loraine
      female
      2
      1
      2
      113781
      151.55
      C22 C26
      S
      NaN
      NaN
      Montreal, PQ / Chesterville, ON
      1

      4
      4.0
      1
      0
      Allison, Mr. Hudson Joshua Creighton
      male
      30
      1
      2
      113781
      151.55
      C22 C26
      S
      NaN
      135
      Montreal, PQ / Chesterville, ON
      1

```
df2=df[['age', 'survived']]
#df2=pd.DataFrame(data2)
print(df2.cov())

```

    Empty DataFrame
    Columns: []
    Index: []

# Orthogonality

## 2.1 Plot two vectors that are orthogonal to each other. What is a synonym for orthogonal?

```
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np

vect1=[5,5]
vect2=[-5,5]
plt.arrow(0,0, vect1[0], vect1[1])
plt.arrow(0,0, vect2[0], vect2[1])
plt.xlim(-8,8)
plt.ylim(0,8)
plt.show()
```

## 2.2 Are the following vectors orthogonal? Why or why not?

\begin{align}
a = \begin{bmatrix} -5 \\ 3 \\ 7 \end{bmatrix}
\qquad
b = \begin{bmatrix} 6 \\ -8 \\ 2 \end{bmatrix}
\end{align}

```
np_a=[[-5],
      [3],
      [7]]
np_b=[[6],
     [-8],
     [2]]

np.vdot(np_a,np_b)

#No the dot product is not 0 so they are not.
```

    -40

## 2.3 Compute the following values: What do these quantities have in common?

## What is $||c||^2$?

## What is $c \cdot c$?

## What is $c^{T}c$?

\begin{align}
c = \begin{bmatrix} 2 & -15 & 6 & 20 \end{bmatrix}
\end{align}

```
np_c=([2,-15,6,20])
print((np.linalg.norm(np_c))**2)
print(np.vdot(np_c,np_c))
print(np.matmul(np.transpose(np_c),np_c))

#the numbers are the same.
```

    665.0
    665
    665

# Unit Vectors

## 3.1 Using Latex, write the following vectors as a linear combination of scalars and unit vectors:

\begin{align}
d = \begin{bmatrix} 7 \\ 12 \end{bmatrix}
\qquad
e = \begin{bmatrix} 2 \\ 11 \\ -8  \end{bmatrix}
\end{align}

$7i+ 12j$

$2i+11j-8k$

## 3.2 Turn vector $f$ into a unit vector:

\begin{align}
f = \begin{bmatrix} 4 & 12 & 11 & 9 & 2 \end{bmatrix}
\end{align}

```
np_f=[4,12,11,9,2]
norm=np.linalg.norm(np_f)
print(norm)
print(norm*x1 for xi in np_f)
#unit vector is [4/19.13,12/19.13,11/19.13,9/19.13,2/19.13]
```

    19.131126469708992
    <generator object <genexpr> at 0x7f4d0584aca8>

# Linear Independence / Dependence

## 4.1 Plot two vectors that are linearly dependent and two vectors that are linearly independent (bonus points if done in $\mathbb{R}^3$).

```
vect_1=[2,10]
vect_2=[1,5]
vect_3=[-5,5]
vect_4=[1,7]
plt.arrow(0,0, vect_1[0], vect_1[1])
plt.arrow(0,0, vect_2[0], vect_2[1])
plt.arrow(0,0, vect_3[0], vect_3[1])
plt.arrow(0,0, vect_4[0], vect_4[1])
plt.xlim(-8,8)
plt.ylim(0,8)
plt.show()
```

# Span

## 5.1 What is the span of the following vectors?

\begin{align}
g = \begin{bmatrix} 1 & 2 \end{bmatrix}
\qquad
h = \begin{bmatrix} 4 & 8 \end{bmatrix}
\end{align}

```
# the span is one, you can see that they are simply factors of each other, g*4=h
```

## 5.2 What is the span of $\{l, m, n\}$?

\begin{align}
l = \begin{bmatrix} 1 & 2 & 3 \end{bmatrix}
\qquad
m = \begin{bmatrix} -1 & 0 & 7 \end{bmatrix}
\qquad
n = \begin{bmatrix} 4 & 8  & 2\end{bmatrix}
\end{align}

```
from sympy import *

np_l=[1,2,3]
np_m=[-1,0,7]
np_n=[4,8,2]

M=Matrix([[1,2,3],[-1,0,7],[4,8,2]])
M=M.rref()

print(M)
#the span is 3
```

    (Matrix([
    [1, 0, 0],
    [0, 1, 0],
    [0, 0, 1]]), (0, 1, 2))

# Basis

## 6.1 Graph two vectors that form a basis for $\mathbb{R}^2$

```
vect_1=[2,10]
vect_2=[5,5]

plt.arrow(0,0, vect_1[0], vect_1[1])
plt.arrow(0,0, vect_2[0], vect_2[1])
plt.xlim(-8,8)
plt.ylim(0,8)
plt.show()
```

## 6.2 What does it mean to form a basis?

It means to have a set of equations, that are linearly independent to allow you to reach any point on the space you want.

# Rank

## 7.1 What is the Rank of P?

\begin{align}
P = \begin{bmatrix}
1 & 2 & 3 \\
 -1 & 0 & 7 \\
4 & 8  & 2
\end{bmatrix}
\end{align}

The rank of P is 3. After putting this in RREF (on 5.2) I determined that this reduced is the identity matrix.

## 7.2 What does the rank of a matrix tell us?

How many leading 1s per row, or how many dimiensions this reaches.

# Linear Projections

## 8.1 Line $L$ is formed by all of the vectors that can be created by scaling vector $v$
\begin{align}
v = \begin{bmatrix} 1 & 3 \end{bmatrix}
\end{align}

\begin{align}
w = \begin{bmatrix} -1 & 2 \end{bmatrix}
\end{align}

## find $proj_{L}(w)$

## graph your projected vector to check your work (make sure your axis are square/even)

```

```

# Stretch Goal

## For vectors that begin at the origin, the coordinates of where the vector ends can be interpreted as regular data points. (See 3Blue1Brown videos about Spans, Basis, etc.)

## Write a function that can calculate the linear projection of each point (x,y) (vector) onto the line y=x. run the function and plot the original points in blue and the new projected points on the line y=x in red.

## For extra points plot the orthogonal vectors as a dashed line from the original blue points to the projected red points.

```
import pandas as pd
import matplotlib.pyplot as plt

# Creating a dataframe for you to work with -Feel free to not use the dataframe if you don't want to.
x_values = [1, 4, 7, 3, 9, 4, 5 ]
y_values = [4, 2, 5, 0, 8, 2, 8]

data = {"x": x_values, "y": y_values}

df = pd.DataFrame(data)

df.head()

plt.scatter(df.x, df.y)
plt.show()
```

```

```


# Concordia Plots
|Author  | Stanley A. Baronett|
|--------|--------------------|
|Created | 3/3/2022           |
|Updated | 5/16/2022          |

## Wetherill
From [Schoene (2014)](https://doi.org/10.1016/B978-0-08-095975-7.00310-7), the U-Th-Pb system leads to the following simplified, classic isochron equations, where the initial lead contribution in zircon is negligible compared to the radiogenic component:
\begin{align}
  \left(\frac{^{206}\mathrm{Pb}^*}{^{238}\mathrm{U}}\right)&=\left(e^{\lambda_{238}t-1}\right)\\
  \left(\frac{^{207}\mathrm{Pb}^*}{^{235}\mathrm{U}}\right)&=\left(e^{\lambda_{235}t-1}\right),
  \tag{6, 7}
\end{align}
where $^*$ stands for *radiogenic*, $t$ is the time since the system closed (e.g., crystallization of a mineral), $\lambda_{238}=1.55125\times10^{-10}\,\mathrm{yr}^{-1}$ and $\lambda_{235}=9.8485\times10^{-10}\,\mathrm{yr}^{-1}$ (Fig. 2) are the decay constants of $^{238}\mathrm{U}$ and $^{235}\mathrm{U}$.

```python
import matplotlib.pyplot as plt
import numpy as np

# Eqs. and decay constants from Schoene (2014)
decay = lambda t, hl : np.exp(hl*t) - 1 # Eqs. [6], [7]
lambda_238 = 1.55125e-10 # / yr
lambda_235 = 9.8485e-10  # / yr
ts = np.linspace(0, 3.4e9, 100)
Pb206_U238 = decay(ts, lambda_238)
Pb207_U235 = decay(ts, lambda_235)
pts = 1e6*np.arange(500, 3500, 500) # Myr
xpts = decay(pts, lambda_235)
ypts = decay(pts, lambda_238)
fig, ax = plt.subplots(dpi=150)

for i, pt in enumerate(pts):
    ax.annotate(f'{pt/1e6:n}', xy=(xpts[i], ypts[i]), ha='right', va='bottom')

ax.plot(Pb207_U235, Pb206_U238, 'k')
ax.plot(xpts, ypts, '.w', markeredgecolor='k', label=r'$t$ / Myr')
ax.legend()
ax.minorticks_on()
ax.set(xlabel=r'$^{207}\mathrm{Pb}/^{235}\mathrm{U}$',
       ylabel=r'$^{206}\mathrm{Pb}/^{238}\mathrm{U}$',
       xlim=(-1, 25), ylim=(-0.05,0.7), title='Wetherill')
ax.tick_params(axis='both', which='both', top=True, right=True)
```

## Tera–Wasserburg (T–W)

From [Schoene (2014)](https://doi.org/10.1016/B978-0-08-095975-7.00310-7),
\begin{equation}
  \left(\frac{^{235}\mathrm{U}^*}{^{238}\mathrm{U}}\right)\frac{\left(e^{\lambda_{235}t-1}\right)}{\left(e^{\lambda_{238}t-1}\right)}=\left(\frac{^{207}\mathrm{Pb}}{^{206}\mathrm{Pb}}\right)^*
  \tag{9}
\end{equation}
where $(^{235}\mathrm{U}^*/^{238}\mathrm{U})^{-1}=137.88$ of natural uranium in most terrestrial materials ($\S~4.10.5.2$, p. 359)

```python
import matplotlib.pyplot as plt
import numpy as np

# Eqs. and decay constants from Schoene (2014)
decay = lambda t, hl : np.exp(hl*t) - 1 # Eqs. [6], [7]
lambda_238 = 1.55125e-10 # / yr
lambda_235 = 9.8485e-10  # / yr
ts = np.linspace(3e8, 3.7e9, 100)
Pb206_U238 = decay(ts, lambda_238)
Pb207_U235 = decay(ts, lambda_235)
pts = 1e6*np.arange(500, 3500, 500) # Myr
U238_U235 = 137.88 # § 4.10.5.2, p. 359
Pb207_Pb206 = Pb207_U235/Pb206_U238/U238_U235 # Eq. [9]
xpts = 1/decay(pts, lambda_238)
ypts = decay(pts, lambda_235)*xpts/U238_U235
fig, ax = plt.subplots(dpi=150)

for i, pt in enumerate(pts):
    ax.annotate(f'{pt/1e6:n} ', xy=(xpts[i], ypts[i]), ha='right', va='top')

ax.plot(1/Pb206_U238, Pb207_Pb206, 'k')
ax.plot(xpts, ypts, '.w', markeredgecolor='k', label=r'$t$ / Myr')
ax.legend()
ax.minorticks_on()
ax.set(xlabel=r'$^{238}\mathrm{U}/^{206}\mathrm{Pb}$',
       ylabel=r'$^{207}\mathrm{Pb}/^{206}\mathrm{Pb}$',
       xlim=(0,16), ylim=(0.04,0.35), title='Tera–Wasserburg')
ax.tick_params(axis='both', which='both', top=True, right=True)
```

## Mixing
From [Langmuir et al. (1978)](https://doi.org/10.1016/0012-821X(78)90053-5), the general mixing, hyperbolic equation for two components is
\begin{equation}
  Ax+Bxy+Cy+D=0,
  \tag{1}
\end{equation}
where, from [Vollmer (1976)](https://doi-org.ezproxy.library.unlv.edu/10.1016/0016-7037(76)90205-2), the coefficients of Eq. (1) are
\begin{align}
  A &= a_2b_1y_2-a_1b_2y_1,\\
  B &= a_1b_2-a_2b_1,\\
  C &= a_2b_1x_1-a_1b_2x_2,\\
  D &= a_1b_2x_2y_1-a_2b_1x_1y_2,\\
  \tag{2}
\end{align}
and
\begin{align*}
  y &= fy_1+(1-f)y_2\\
  x &= \frac{f\frac{b_1}{a_1}x_1+(1-f)\frac{b_2}{a_2}x_2}{f\frac{b_1}{a_1}+(1-f)\frac{b_2}{a_2}}\\
  f &= \frac{a_1}{\sum a_i}\\
  r &= \frac{a_1b_2}{a_2b_1}.
\end{align*}

In general, $x$ and $y$ respectively are abscissa and ordinate variables, $x_i$ and $y_i$ are coordinates at data point $i$, and $a_i$ and $b_i$ are the denominators of $y_i$ and $x_i$, respectively.
If $r=1$, then $B=0$ and the mixing curve is a straight line.
For our Wetherill diagram, $x_i = (^{207}\mathrm{Pb}/^{235}\mathrm{U})_i$ and $y_i = (^{206}\mathrm{Pb}/^{238}\mathrm{U})_i$ , with $a_i = \,^{238}\mathrm{U}_i$ and $b_i = \,^{235}\mathrm{U}_i$.

### Figure 4(a), [Schoene (2014)](https://doi.org/10.1016/B978-0-08-095975-7.00310-7)
> Example of a 1700 Ma zircon losing Pb or mixing with metamorphic overgrowth. $t_0$ is the crystallization age of the zircons; after 1700 My of closed-system ingrowth of Pb, the zircons appears on concordia at $t_1$; at $t_2$ the zircon suffers Pb loss or growth of new zircon around old core; $t^\prime$ represents zircons that are discordant following partial Pb loss or mineral overgrowth at $t_2$.

```python
import matplotlib.pyplot as plt
import numpy as np

# Eqs. and decay constants from Schoene (2014)
decay = lambda t, hl : np.exp(hl*t) - 1 # Eqs. [6], [7]
lambda_238 = 1.55125e-10 # / yr
lambda_235 = 9.8485e-10  # / yr
ts = np.linspace(0, 3.4e9, 100)
Pb206_U238 = decay(ts, lambda_238)
Pb207_U235 = decay(ts, lambda_235)
pts = 1e6*np.arange(500, 3500, 500) # Myr
xpts = decay(pts, lambda_235)
ypts = decay(pts, lambda_238)

# Mixing lines
fs, f1, f2 = np.linspace(0, 1), 2/3, 1/3
ymix = lambda f, y1, y2 : f*y1 + (1 - f)*y2
xmix = lambda f, x1, x2 : (f*x1 + (1 - f)*x2)/(f + (1 - f))
t1, t2 = 1700e6, 0
x1, y1 = decay(t1, lambda_235), decay(t1, lambda_238)
x2, y2 = decay(t2, lambda_235), decay(t2, lambda_238)
xs, ys = xmix(fs, x1, x2), ymix(fs, y1, y2)
xp1, yp1 = xmix(f1, x1, x2), ymix(f1, y1, y2)
xp2, yp2 = xmix(f2, x1, x2), ymix(f2, y1, y2)
fig, ax = plt.subplots(dpi=150)

for i, pt in enumerate(pts):
    ax.annotate(f'{pt/1e6:n}', xy=(xpts[i], ypts[i]), ha='right', va='bottom')

ax.annotate('', xy=(0+0.5, 0), xytext=(x1+0.5, y1),
            arrowprops=dict(arrowstyle="->", color='tab:purple'))
ax.plot(xs, ys, '--k')
ax.plot(Pb207_U235, Pb206_U238, 'k')
ax.plot(xpts, ypts, '.w', markeredgecolor='k', label=r'$t$ / Myr')
ax.plot(x1, y1, 'o', color='tab:red', markeredgecolor='k')
ax.plot(x2, y2, 'o', color='tab:blue', markeredgecolor='k')
ax.annotate(r'$t_0$, zircon crystallization',
            xy=(x1+2, y1), ha='left', va='center')
ax.annotate(r'$t_1$, zircon age after ~1700-Myr Pb* ingrowth',
            xy=(x1+1, y1-0.05), ha='left', va='top', color='tab:red')
ax.annotate(r'$t_2$, new zircon growth or Pb-loss',
            xy=(x2+1, y2), ha='left', va='center', color='tab:blue')
ax.annotate(r'$t^\prime$, discordant dates between $t_1$ and $t_2$',
            xy=(xp1+0.5, yp1-0.067), ha='left', va='center', color='tab:purple')
ax.plot(xp1, yp1, 'o', color='tab:purple', markeredgecolor='k')
ax.plot(xp2, yp2, 'o', color='tab:purple', markeredgecolor='k')
ax.legend()
ax.minorticks_on()
ax.set(xlabel=r'$^{207}\mathrm{Pb}/^{235}\mathrm{U}$',
       ylabel=r'$^{206}\mathrm{Pb}/^{238}\mathrm{U}$',
       xlim=(-1, 25), ylim=(-0.05,0.7),
       title='Wetherill: Discordant Mixing\n(Fig. 4a, Schoene, 2014)')
ax.tick_params(axis='both', which='both', top=True, right=True)
```

### Figure 4(b), [Schoene (2014)](https://doi.org/10.1016/B978-0-08-095975-7.00310-7)
> The same data after the system has closed again and continues to evolve up the concordia curve. The discordia line defined by purple analyses now has an upper intercept with concordia representing the original igneous crystallization event at $t_1$, and a lower intercept age representing $t_2$, the time before the present at which Pb loss or overgrowth occurred.

```python
import matplotlib.pyplot as plt
import numpy as np

# Eqs. and decay constants from Schoene (2014)
decay = lambda t, hl : np.exp(hl*t) - 1 # Eqs. [6], [7]
lambda_238 = 1.55125e-10 # / yr
lambda_235 = 9.8485e-10  # / yr
ts = np.linspace(0, 3.4e9, 100)
Pb206_U238 = decay(ts, lambda_238)
Pb207_U235 = decay(ts, lambda_235)
pts = 1e6*np.arange(500, 3500, 500) # Myr
xpts = decay(pts, lambda_235)
ypts = decay(pts, lambda_238)

# Mixing lines
fs, f1, f2 = np.linspace(0, 1), 2/3, 1/3
ymix = lambda f, y1, y2 : f*y1 + (1 - f)*y2
xmix = lambda f, x1, x2 : (f*x1 + (1 - f)*x2)/(f + (1 - f))
t1, t2 = 1700e6, 0
x1, y1 = decay(t1, lambda_235), decay(t1, lambda_238)
x2, y2 = decay(t2, lambda_235), decay(t2, lambda_238)
xs, ys = xmix(fs, x1, x2), ymix(fs, y1, y2)
xp1, yp1 = xmix(f1, x1, x2), ymix(f1, y1, y2)
xp2, yp2 = xmix(f2, x1, x2), ymix(f2, y1, y2)
# Continued growth after re-closed
t2a, t3 = 1200e6, 2900e6
x2a, y2a = decay(t2a, lambda_235), decay(t2a, lambda_238)
x3, y3 = decay(t3, lambda_235), decay(t3, lambda_238)
xs3, ys3 = xmix(fs, x2a, x3), ymix(fs, y2a, y3)
# xp3a, yp3a = (f1*x2a + (1 - f1)*x3)/(f1 + (1 - f1)), f1*y2a + (1 - f1)*y3
# xp3b, yp3b = (f2*x2a + (1 - f2)*x3)/(f2 + (1 - f2)), f2*y2a + (1 - f2)*y3
xp3a, yp3a = xmix(f1, x2a, x3), ymix(f1, y2a, y3)
xp3b, yp3b = xmix(f2, x2a, x3), ymix(f2, y2a, y3)
fig, ax = plt.subplots(dpi=150)

for i, pt in enumerate(pts):
    ax.annotate(f'{pt/1e6:n}', xy=(xpts[i], ypts[i]), ha='right', va='bottom')

ax.plot(xs, ys, '--k', alpha=0.3)
ax.plot(Pb207_U235, Pb206_U238, 'k')
ax.plot(xpts, ypts, '.w', markeredgecolor='k', label=r'$t$ / Myr')
ax.plot(x1, y1, 'o', color='tab:red', markeredgecolor='k', alpha=0.3)
ax.plot(x2, y2, 'o', color='tab:blue', markeredgecolor='k', alpha=0.3)
# Age underestimates
ax.plot([x2, 9.7], [y2, 0.45], '--k', alpha=0.3)
ax.plot([x2, 13.9], [y2, 0.53], '--k', alpha=0.3)
ax.annotate('Purple Pb–Pb dates underestimate\nigneous age',
            xy=(0, 0.6), ha='left', va='top', color='tab:purple')
ax.annotate('', xy=(9.7, 0.45), xytext=(5, 0.55),
            arrowprops=dict(arrowstyle="->", color='tab:purple'))
ax.annotate('', xy=(13.7, 0.53), xytext=(5, 0.55),
            arrowprops=dict(arrowstyle="->", color='tab:purple'))
# Continued growth after re-closed
ax.plot(xs3, ys3, '--k')
ax.plot(x2a, y2a, 'o', color='tab:blue', markeredgecolor='k')
ax.plot(x3, y3, 'o', color='tab:red', markeredgecolor='k')
ax.plot(xp1, yp1, 'o', color='tab:purple', markeredgecolor='k', alpha=0.3)
ax.plot(xp2, yp2, 'o', color='tab:purple', markeredgecolor='k', alpha=0.3)
ax.plot(xp3a, yp3a, 'o', color='tab:purple', markeredgecolor='k')
ax.plot(xp3b, yp3b, 'o', color='tab:purple', markeredgecolor='k')
label = r'$t_3$, 1200-Myr Pb*'+'\n'+r'ingrowth after $t_2$'
ax.annotate(label, xy=(x3+0.2, y3), ha='left', va='top', color='tab:red')
ax.legend()
ax.minorticks_on()
ax.set(xlabel=r'$^{207}\mathrm{Pb}/^{235}\mathrm{U}$',
       ylabel=r'$^{206}\mathrm{Pb}/^{238}\mathrm{U}$',
       xlim=(-1, 25), ylim=(-0.05,0.7),
       title='Wetherill: Discordant Mixing\n(Fig. 4b, Schoene, 2014)')
ax.tick_params(axis='both', which='both', top=True, right=True)
```

### Figure 4(c), [Schoene (2014)](https://doi.org/10.1016/B978-0-08-095975-7.00310-7)
> The same scenario as in (b) but in a T–W diagram.

```python
import matplotlib.pyplot as plt
import numpy as np

# Eqs. and decay constants from Schoene (2014)
decay = lambda t, hl : np.exp(hl*t) - 1 # Eqs. [6], [7]
lambda_238 = 1.55125e-10 # / yr
lambda_235 = 9.8485e-10  # / yr
ts = np.linspace(3e8, 3.7e9, 100)
Pb206_U238 = decay(ts, lambda_238)
Pb207_U235 = decay(ts, lambda_235)
pts = 1e6*np.arange(500, 3500, 500) # Myr
U238_U235 = 137.88 # § 4.10.5.2, p. 359
Pb207_Pb206 = Pb207_U235/Pb206_U238/U238_U235 # Eq. [9]
xpts = 1/decay(pts, lambda_238)
ypts = decay(pts, lambda_235)*xpts/U238_U235
fig, ax = plt.subplots(dpi=150)

# Mixing lines
fs, f1, f2 = np.linspace(0, 1), 2/3, 1/3
ymix = lambda f, y1, y2 : f*y1 + (1 - f)*y2
xmix = lambda f, x1, x2 : (f*x1 + (1 - f)*x2)/(f + (1 - f))
t2a, t3 = 1200e6, 2900e6
x2a, x3 = 1/decay(t2a, lambda_238), 1/decay(t3, lambda_238)
y2a, y3 = decay(t2a, lambda_235)*x2a/U238_U235, decay(t3, lambda_235)*x3/U238_U235
xs3, ys3 = xmix(fs, x2a, x3), ymix(fs, y2a, y3)
xp3a, yp3a = xmix(f1, x2a, x3), ymix(f1, y2a, y3)
xp3b, yp3b = xmix(f2, x2a, x3), ymix(f2, y2a, y3)

for i, pt in enumerate(pts):
    ax.annotate(f'{pt/1e6:n} ', xy=(xpts[i], ypts[i]), ha='right', va='top')

ax.plot(1/Pb206_U238, Pb207_Pb206, 'k')
ax.plot(xpts, ypts, '.w', markeredgecolor='k', label=r'$t$ / Myr')
# Continued growth after re-closed
ax.plot(xs3, ys3, '--k')
ax.plot(x2a, y2a, 'o', color='tab:blue', markeredgecolor='k')
ax.plot(x3, y3, 'o', color='tab:red', markeredgecolor='k')
ax.plot(xp3a, yp3a, 'o', color='tab:purple', markeredgecolor='k')
ax.plot(xp3b, yp3b, 'o', color='tab:purple', markeredgecolor='k')
ax.annotate(r'Same scenario as (b), shown at $t_3$',
            xy=(xp3a+1, yp3a-0.01), ha='left', va='top')
ax.legend()
ax.minorticks_on()
ax.set(xlabel=r'$^{238}\mathrm{U}/^{206}\mathrm{Pb}$',
       ylabel=r'$^{207}\mathrm{Pb}/^{206}\mathrm{Pb}$',
       xlim=(0,16), ylim=(0.04,0.35),
       title='Tera–Wasserburg: Discordant Mixing\n(Fig. 4c, Schoene, 2014)')
ax.tick_params(axis='both', which='both', top=True, right=True)
```

### Figure 4(d), [Schoene (2014)](https://doi.org/10.1016/B978-0-08-095975-7.00310-7)

> Illustration of how the scenario in (a) and (b) would possibly
be recorded in the event of metamorphism as $t_2$.

### Figure 4(e), [Schoene (2014)](https://doi.org/10.1016/B978-0-08-095975-7.00310-7)

> The case where Pb loss happens at $t_2$.


```python
from __future__ import print_function
import os
import pandas as pd
%matplotlib inline
from matplotlib import pyplot as plt
```

```python
#Set current directory and work relative to it
os.chdir('../Data Files')
```

```python
#Load the dataset into a pandas.DataFrame
ibm_df = pd.read_csv('ibm-common-stock-closing-prices.csv')
ibm_df.index = ibm_df['Date']
```

```python
#Let's find out the shape of the DataFrame
print('Shape of the dataframe:', ibm_df.shape)
```

    Shape of the dataframe: (1009, 2)

```python
#Let's see the top 10 rows
ibm_df.head(10)
```

      Date
      IBM common stock closing prices

      Date

      1962-01-02
      1962-01-02
      572.00

      1962-01-03
      1962-01-03
      577.00

      1962-01-04
      1962-01-04
      571.25

      1962-01-05
      1962-01-05
      560.00

      1962-01-08
      1962-01-08
      549.50

      1962-01-09
      1962-01-09
      556.00

      1962-01-10
      1962-01-10
      557.00

      1962-01-11
      1962-01-11
      563.00

      1962-01-12
      1962-01-12
      564.00

      1962-01-15
      1962-01-15
      566.50

```python
#Rename the second column
ibm_df.rename(columns={'IBM common stock closing prices': 'Close_Price'},
              inplace=True)
ibm_df.head()
```

      Date
      Close_Price

      Date

      1962-01-02
      1962-01-02
      572.00

      1962-01-03
      1962-01-03
      577.00

      1962-01-04
      1962-01-04
      571.25

      1962-01-05
      1962-01-05
      560.00

      1962-01-08
      1962-01-08
      549.50

```python
#remove missing values
missing = (pd.isnull(ibm_df['Date'])) & (pd.isnull(ibm_df['Close_Price']))
print('No. of rows with missing values:', missing.sum())
ibm_df = ibm_df.loc[~missing, :]
```

    No. of rows with missing values: 0

```python
#To illustrate the idea of moving average we compute a weekly moving average taking
#a window of 5 days instead of 7 days because trading happens only during the weekdays.
ibm_df['5-Day Moving Avg'] = ibm_df['Close_Price'].rolling(5).mean()
```

```python
fig = plt.figure(figsize=(16, 8))
ax = fig.add_subplot(2,1,1)
ibm_df['Close_Price'].plot(ax=ax, color='b')
ax.set_title('IBM Common Stock Close Prices during 1962-1965')
ax = fig.add_subplot(2,1,2)
ibm_df['5-Day Moving Avg'].plot(ax=ax, color='r')
ax.set_title('5-day Moving Average')
plt.tight_layout(pad=0.4, w_pad=0.5, h_pad=2.0)
plt.savefig('../plots/ch2/B07887_02_14.png', format='png', dpi=300)
```

```python
#Calculate the moving averages using 'rolling' and 'mean' functions
MA2 = ibm_df['Close_Price'].rolling(window=2).mean()
TwoXMA2 = MA2.rolling(window=2).mean()

MA4 = ibm_df['Close_Price'].rolling(window=4).mean()
TwoXMA4 = MA4.rolling(window=2).mean()

MA3 = ibm_df['Close_Price'].rolling(window=3).mean()
ThreeXMA3 = MA3.rolling(window=3).mean()
```

```python
#Let's remove NaN from the above variables
MA2 = MA2.loc[~pd.isnull(MA2)]
TwoXMA2 = TwoXMA2.loc[~pd.isnull(TwoXMA2)]

MA4 = MA4.loc[~pd.isnull(MA4)]
TwoXMA4 = TwoXMA4.loc[~pd.isnull(TwoXMA4)]

MA3 = MA3.loc[~pd.isnull(MA3)]
ThreeXMA3 = TwoXMA4.loc[~pd.isnull(ThreeXMA3)]
```

```python
f, axarr = plt.subplots(3, sharex=True)
f.set_size_inches(16, 8)

ibm_df['Close_Price'].iloc[:45].plot(color='b', linestyle = '-', ax=axarr[0])
MA2.iloc[:45].plot(color='r', linestyle = '-', ax=axarr[0])
TwoXMA2.iloc[:45].plot(color='r', linestyle = '--', ax=axarr[0])
axarr[0].set_title('2 day MA & 2X2 day MA')

ibm_df['Close_Price'].iloc[:45].plot(color='b', linestyle = '-', ax=axarr[1])
MA4.iloc[:45].plot(color='g', linestyle = '-', ax=axarr[1])
TwoXMA4.iloc[:45].plot(color='g', linestyle = '--', ax=axarr[1])
axarr[1].set_title('4 day MA & 2X4 day MA')

ibm_df['Close_Price'].iloc[:45].plot(color='b', linestyle = '-', ax=axarr[2])
MA3.iloc[:45].plot(color='k', linestyle = '-', ax=axarr[2])
ThreeXMA3.iloc[:45].plot(color='k', linestyle = '--', ax=axarr[2])
axarr[2].set_title('3 day MA & 3X 3day MA')
plt.savefig('../plots/ch2/B07887_02_15.png', format='png', dpi=300)
```

\begin{equation}
\hat{F}_t=\frac{x_{t-k} + x_{t-k+1}+...+x_{t}+..+x_{t+k-1} + x_{t+k}}{2k+1}
\end{equation}

\begin{equation}
\hat{F}^{(2)}_t=\frac{x_{t-1} +x_{t}}{2}
\end{equation}

\begin{equation}
2x\hat{F}^{(2)}_t=\frac{\hat{F}^{(2)}_t + \hat{F}^{(2)}_{t+1}}{2} = \frac{1}{2}[\frac{x_{t-1}+x_t}{2} + \frac{x_t + x_{t+1}}{2}] = \frac{1}{4}x_{t-2} + \frac{1}{2}x_t + \frac{1}{4}x_{t+1}
\end{equation}

```python

```