```python
import numpy as np
from matplotlib import pyplot as plt
import real_space_electrostatic_sum
```

# Benchmarking

Using the shared library `real-space-electrostatic-sum.so`, together with the Python wrapper in `real_space_electrostatic_sum.py`, this notebook reproduces results from [Pickard, *Phys. Rev. Mat.* **2**, 013806 (2018)](https://doi.org/10.1103/PhysRevMaterials.2.013806).

## Reproducing part of Fig. 1(b)

Fig. 1(b) in the paper demonstrates the convergence of the real-space method for a simple cubic lattice with unit spacing.

```python
# lattice vectors
a_1 = np.array([1.0, 0.0, 0.0])
a_2 = np.array([0.0, 1.0, 0.0])
a_3 = np.array([0.0, 0.0, 1.0])

# ion locations and charge array
loc = np.zeros([1,3])
chg = np.ones(1)

# loop over cutoff radii
r_c = np.linspace(0.001,30,500)
r_d = 1.5
ene = np.zeros(len(r_c))
for i, r in enumerate(r_c):
    ene[i] = real_space_electrostatic_sum.energy(
            a_1, a_2, a_3, loc.shape[0], loc[:,0], loc[:,1], loc[:,2], chg, r, r_d)

# generate part of Fig. 1(b)
plt.plot(r_c, ene, 'r')
plt.title('Fig. 1(b)')
plt.xlim([0,30]); plt.ylim([-1.5,-1.25])
plt.xlabel('$R_c$');  plt.ylabel('$E_i$')
plt.show()
```

## Reproducing data in Table I

Table I in the paper contains ion-ion energies for four crystals obtained with the real-space method, as well as the Ewald method. The real-space method data are re-generated here, exhibiting near perfect agreement with Table I. The Ewald energies reported below were re-obtained with CASTEP using the exact lattice parameters employed here.

### _Al_

```python
# lattice vectors
a_1 = np.array([5.41141973394663, 0.00000000000000, 0.00000000000000])
a_2 = np.array([2.70570986697332, 4.68642696013821, 0.00000000000000])
a_3 = np.array([2.70570986697332, 1.56214232004608, 4.41840571073226])

# ion locations
loc = np.zeros([1,3])

# charge array
chg = 3.0 * np.ones(loc.shape[0])

# length scale
h_max = 4.42

# reference energy
ewald = -2.69595457432924945
print('Ewald:       energy = {0:12.9f}'.format(-2.69595457432924945))

# real-space-method energies
r_d_hat = 2.0
ene = real_space_electrostatic_sum.energy(
        a_1, a_2, a_3, loc.shape[0], loc[:,0], loc[:,1], loc[:,2], chg, 3.0*r_d_hat**2*h_max, r_d_hat*h_max)
print('R\u0302d = {0:3.1f}:    energy = {1:12.9f}'.format(r_d_hat, ene))
r_d_hat = 1.5
ene = real_space_electrostatic_sum.energy(
        a_1, a_2, a_3, loc.shape[0], loc[:,0], loc[:,1], loc[:,2], chg, 3.0*r_d_hat**2*h_max, r_d_hat*h_max)
print('R\u0302d = {0:3.1f}:    energy = {1:12.9f}'.format(r_d_hat, ene))
r_d_hat = 1.0
ene = real_space_electrostatic_sum.energy(
        a_1, a_2, a_3, loc.shape[0], loc[:,0], loc[:,1], loc[:,2], chg, 3.0*r_d_hat**2*h_max, r_d_hat*h_max)
print('R\u0302d = {0:3.1f}:    energy = {1:12.9f}'.format(r_d_hat, ene))
```

    Ewald:       energy = -2.695954574
    R̂d = 2.0:    energy = -2.695954574
    R̂d = 1.5:    energy = -2.695954574
    R̂d = 1.0:    energy = -2.696027958

### _Si_

```python
# lattice vectors
a_1 = np.array([7.25654832321381, 0.00000000000000, 0.00000000000000])
a_2 = np.array([3.62827416160690, 6.28435519169252, 0.00000000000000])
a_3 = np.array([3.62827416160690, 2.09478506389751, 5.92494689524090])

# ion locations
loc = np.array([[0.0,  0.0,  0.0],
                [0.25, 0.25, 0.25]])
loc = (np.vstack((a_1, a_2, a_3)).T).dot(loc.T).T # convert to cartesian

# charge array
chg = 4.0 * np.ones(loc.shape[0])

# length scale
h_max = 5.92

# reference energy
ewald = -8.39857465282205418
print('Ewald:       energy = {0:12.9f},    per ion = {1:12.9f}'.format(ewald, ewald/loc.shape[0]))

# real-space-method energies
r_d_hat = 2.0
ene = real_space_electrostatic_sum.energy(
        a_1, a_2, a_3, loc.shape[0], loc[:,0], loc[:,1], loc[:,2], chg, 3.0*r_d_hat**2*h_max, r_d_hat*h_max)
print('R\u0302d = {0:3.1f}:    energy = {1:12.9f},    per ion = {2:12.9f}'.format(r_d_hat, ene, ene/loc.shape[0]))
r_d_hat = 1.5
ene = real_space_electrostatic_sum.energy(
        a_1, a_2, a_3, loc.shape[0], loc[:,0], loc[:,1], loc[:,2], chg, 3.0*r_d_hat**2*h_max, r_d_hat*h_max)
print('R\u0302d = {0:3.1f}:    energy = {1:12.9f},    per ion = {2:12.9f}'.format(r_d_hat, ene, ene/loc.shape[0]))
r_d_hat = 1.0
ene = real_space_electrostatic_sum.energy(
        a_1, a_2, a_3, loc.shape[0], loc[:,0], loc[:,1], loc[:,2], chg, 3.0*r_d_hat**2*h_max, r_d_hat*h_max)
print('R\u0302d = {0:3.1f}:    energy = {1:12.9f},    per ion = {2:12.9f}'.format(r_d_hat, ene, ene/loc.shape[0]))
```

    Ewald:       energy = -8.398574653,    per ion = -4.199287326
    R̂d = 2.0:    energy = -8.398574653,    per ion = -4.199287326
    R̂d = 1.5:    energy = -8.398574651,    per ion = -4.199287326
    R̂d = 1.0:    energy = -8.398589712,    per ion = -4.199294856

### _SiO2_

```python
# lattice vectors
a_1 = np.array([ 9.28422445623683, 0.00000000000000, 0.00000000000000])
a_2 = np.array([-4.64211222811842, 8.04037423353787, 0.00000000000000])
a_3 = np.array([ 0.00000000000000, 0.00000000000000, 10.2139697101486])

# ion locations
loc = np.array([[0.41500, 0.27200, 0.21300],
                [0.72800, 0.14300, 0.54633],
                [0.85700, 0.58500, 0.87967],
                [0.27200, 0.41500, 0.78700],
                [0.14300, 0.72800, 0.45367],
                [0.58500, 0.85700, 0.12033],
                [0.46500, 0.00000, 0.33333],
                [0.00000, 0.46500, 0.66667],
                [0.53500, 0.53500, 0.00000]])
loc = (np.vstack((a_1, a_2, a_3)).T).dot(loc.T).T # convert to cartesian

# charge array
chg = 6.0 * np.ones(loc.shape[0])  # most are O
chg[6:] = 4.0                      # three are Si

# length scale
h_max = 10.21

# reference energy
ewald = -69.48809871723248932
print('Ewald:       energy = {0:12.9f},    per ion = {1:12.9f}'.format(ewald, ewald/loc.shape[0]))

# real-space-method energies
r_d_hat = 2.0
ene = real_space_electrostatic_sum.energy(
        a_1, a_2, a_3, loc.shape[0], loc[:,0], loc[:,1], loc[:,2], chg, 3.0*r_d_hat**2*h_max, r_d_hat*h_max)
print('R\u0302d = {0:3.1f}:    energy = {1:12.9f},    per ion = {2:12.9f}'.format(r_d_hat, ene, ene/loc.shape[0]))
r_d_hat = 1.5
ene = real_space_electrostatic_sum.energy(
        a_1, a_2, a_3, loc.shape[0], loc[:,0], loc[:,1], loc[:,2], chg, 3.0*r_d_hat**2*h_max, r_d_hat*h_max)
print('R\u0302d = {0:3.1f}:    energy = {1:12.9f},    per ion = {2:12.9f}'.format(r_d_hat, ene, ene/loc.shape[0]))
r_d_hat = 1.0
ene = real_space_electrostatic_sum.energy(
        a_1, a_2, a_3, loc.shape[0], loc[:,0], loc[:,1], loc[:,2], chg, 3.0*r_d_hat**2*h_max, r_d_hat*h_max)
print('R\u0302d = {0:3.1f}:    energy = {1:12.9f},    per ion = {2:12.9f}'.format(r_d_hat, ene, ene/loc.shape[0]))
```

    Ewald:       energy = -69.488098717,    per ion = -7.720899857
    R̂d = 2.0:    energy = -69.488098717,    per ion = -7.720899857
    R̂d = 1.5:    energy = -69.488098713,    per ion = -7.720899857
    R̂d = 1.0:    energy = -69.487434045,    per ion = -7.720826005

### _Al2SiO5_

```python
# lattice vectors
a_1 = np.array([14.7289033699982, 0.00000000000000, 0.00000000000000])
a_2 = np.array([0.00000000000000, 14.9260018049230, 0.00000000000000])
a_3 = np.array([0.00000000000000, 0.00000000000000, 10.5049875335275])

# ion locations
loc = np.array([[0.23030, 0.13430, 0.23900],
                [0.76970, 0.86570, 0.23900],
                [0.26970, 0.63430, 0.26100],
                [0.73030, 0.36570, 0.26100],
                [0.76970, 0.86570, 0.76100],
                [0.23030, 0.13430, 0.76100],
                [0.73030, 0.36570, 0.73900],
                [0.26970, 0.63430, 0.73900],
                [0.00000, 0.00000, 0.24220],
                [0.50000, 0.50000, 0.25780],
                [0.00000, 0.00000, 0.75780],
                [0.50000, 0.50000, 0.74220],
                [0.37080, 0.13870, 0.50000],
                [0.42320, 0.36270, 0.50000],
                [0.62920, 0.86130, 0.50000],
                [0.57680, 0.63730, 0.50000],
                [0.12920, 0.63870, 0.00000],
                [0.07680, 0.86270, 0.00000],
                [0.87080, 0.36130, 0.00000],
                [0.92320, 0.13730, 0.00000],
                [0.24620, 0.25290, 0.00000],
                [0.42400, 0.36290, 0.00000],
                [0.10380, 0.40130, 0.00000],
                [0.75380, 0.74710, 0.00000],
                [0.57600, 0.63710, 0.00000],
                [0.89620, 0.59870, 0.00000],
                [0.25380, 0.75290, 0.50000],
                [0.07600, 0.86290, 0.50000],
                [0.39620, 0.90130, 0.50000],
                [0.74620, 0.24710, 0.50000],
                [0.92400, 0.13710, 0.50000],
                [0.60380, 0.09870, 0.50000]])
loc = (np.vstack((a_1, a_2, a_3)).T).dot(loc.T).T # convert to cartesian

# charge array
chg = 6.0 * np.ones(loc.shape[0])  # most are O
chg[8:13]  = 3.0                   # eight are Al
chg[14] = 3.0
chg[16] = 3.0
chg[18] = 3.0
chg[20] = 4.0                      # four are Si
chg[23] = 4.0
chg[26] = 4.0
chg[29] = 4.0

# length scale
h_max = 14.93

# reference energy
ewald = -244.05500850908111943
print('Ewald:       energy = {0:14.9f},    per ion = {1:12.9f}'.format(ewald, ewald/loc.shape[0]))

# real-space-method energies
r_d_hat = 2.0
ene = real_space_electrostatic_sum.energy(
        a_1, a_2, a_3, loc.shape[0], loc[:,0], loc[:,1], loc[:,2], chg, 3.0*r_d_hat**2*h_max, r_d_hat*h_max)
print('R\u0302d = {0:3.1f}:    energy = {1:14.9f},    per ion = {2:12.9f}'.format(r_d_hat, ene, ene/loc.shape[0]))
r_d_hat = 1.5
ene = real_space_electrostatic_sum.energy(
        a_1, a_2, a_3, loc.shape[0], loc[:,0], loc[:,1], loc[:,2], chg, 3.0*r_d_hat**2*h_max, r_d_hat*h_max)
print('R\u0302d = {0:3.1f}:    energy = {1:14.9f},    per ion = {2:12.9f}'.format(r_d_hat, ene, ene/loc.shape[0]))
r_d_hat = 1.0
ene = real_space_electrostatic_sum.energy(
        a_1, a_2, a_3, loc.shape[0], loc[:,0], loc[:,1], loc[:,2], chg, 3.0*r_d_hat**2*h_max, r_d_hat*h_max)
print('R\u0302d = {0:3.1f}:    energy = {1:14.9f},    per ion = {2:12.9f}'.format(r_d_hat, ene, ene/loc.shape[0]))
```

    Ewald:       energy = -244.055008509,    per ion = -7.626719016
    R̂d = 2.0:    energy = -244.055008514,    per ion = -7.626719016
    R̂d = 1.5:    energy = -244.055008508,    per ion = -7.626719016
    R̂d = 1.0:    energy = -244.054921810,    per ion = -7.626716307

## Madelung energy for NaCl

See the discussion around Eq. (1) in [Mamode, _J. Math. Chem._ __55__, 734 (2017)](https://doi.org/10.1007/s10910-016-0705-9).

With $M_{\mathrm{NaCl}}$ as the Madelung energy and $E_{\mathrm{NaCl}}$ as the energy of a two-atom primitive cell having $z_{1,2}=\pm 1$, the following identities hold

\begin{equation}
\begin{split}
M_{\mathrm{NaCl}}
&= E_{\mathrm{NaCl}} \\
&= \sum_{i\in\{1,2\}} \sum_{j\ne i}^\infty \frac{z_i z_j}{2r_{ij}} \\
&= \sum_{i\in\{1,2\}}
    \left[ \sum_{\substack{j\ne i \\z_iz_j>0}}^\infty \frac{z_i z_j}{2r_{ij}} -
            \sum_{\substack{j\ne i \\z_iz_j<0}}^\infty \frac{|z_i z_j|}{2r_{ij}} \right] \\
&= \sum_{i\in\{1,2\}}
    \left[ 2 \sum_{\substack{j\ne i \\z_iz_j>0}}^\infty \frac{z_i z_j}{2r_{ij}} -
            \sum_{j\ne i}^\infty \frac{|z_i z_j|}{2r_{ij}} \right] \\
&= 4 E_{\mathrm{FCC}} -
    \sum_{i\in\{1,2\}} \sum_{j\ne i}^\infty \frac{|z_i z_j|}{2r_{ij}}
\end{split}
\end{equation}

and the final result should be $M_{\mathrm{NaCl}} = −1.747 564 594 633 \cdots$.

```python
# lattice vectors
a_1 = np.array([1.0, 1.0, 0.0])
a_2 = np.array([0.0, 1.0, 1.0])
a_3 = np.array([1.0, 0.0, 1.0])

# length scale and cutoff
h_max = np.sqrt(4.0/3.0)
r_d_hat = 3.0

# compute FCC energy
loc = np.zeros([1,3])
chg = np.ones(loc.shape[0])
E_FCC = real_space_electrostatic_sum.energy(
        a_1, a_2, a_3, loc.shape[0], loc[:,0], loc[:,1], loc[:,2], chg, 3.0*r_d_hat**2*h_max, r_d_hat*h_max)

# compute second term
loc = np.zeros([2,3])
loc[1,:] = np.array([1.0, 1.0, 1.0])
chg = np.ones(loc.shape[0])
E_2 = real_space_electrostatic_sum.energy(
        a_1, a_2, a_3, loc.shape[0], loc[:,0], loc[:,1], loc[:,2], chg, 3.0*r_d_hat**2*h_max, r_d_hat*h_max)

# print result
print('M = {0:15.12f}'.format(4*E_FCC - E_2))
```

    M = -1.747564594633


```python
# This cell is added by sphinx-gallery
!pip install mrsimulator --quiet

%matplotlib inline

import mrsimulator
print(f'You are using mrsimulator v{mrsimulator.__version__}')
```

# Arbitrary spin transition (single-quantum)

²⁷Al (I=5/2) quadrupolar spectrum simulation.

The mrsimulator built-in one-dimensional methods, BlochDecaySpectrum and
BlochDecayCTSpectrum, are designed to simulate spectrum from all single
quantum transitions or central transition selective transition, respectively. In this
example, we show how you can simulate any arbitrary transition using the generic
Method1D method.

```python
import matplotlib.pyplot as plt

from mrsimulator import Simulator, SpinSystem, Site
from mrsimulator.methods import Method1D
from mrsimulator.method.event import SpectralEvent
from mrsimulator.spin_system.tensors import SymmetricTensor
```

Create a single-site arbitrary spin system.

```python
site = Site(
    name="27Al",
    isotope="27Al",
    isotropic_chemical_shift=35.7,  # in ppm
    quadrupolar=SymmetricTensor(Cq=5.959e6, eta=0.32),  # Cq is in Hz
)
spin_system = SpinSystem(sites=[site])
```

## Selecting spin transitions for simulation

The arguments of the Method1D object are the same as the arguments of the
BlochDecaySpectrum method; however, unlike a BlochDecaySpectrum method, the
`spectral_dim_api` object in Method1D contains additional argument---`events`.

The `event_api` object is a collection of attributes, which are local to the
event. It is here where we define a `transition_query` to select one or more
transitions for simulating the spectrum. The two attributes of the `transition_query`
are `P` and `D`, which are given as,

\begin{align}P &= m_f - m_i \\
    D &= m_f^2 - m_i^2,\end{align}

where $m_f$ and $m_i$ are the spin quantum numbers for the final and
initial energy states. Based on the query, the method selects all transitions from
the spin system that satisfy the query selection criterion. For example, to simulate
a spectrum for the satellite transition, $|-1/2\rangle\rightarrow|-3/2\rangle$,
set the value of

\begin{align}P &= \left(-\frac{3}{2}\right) - \left(-\frac{1}{2}\right) = -1 \\
    D &= \frac{9}{4} - \frac{1}{4} = 2.\end{align}

For illustrative purposes, let's look at the infinite speed spectrum from this
satellite transition.

```python
method = Method1D(
    name="Inner Satellite Spectrum",
    channels=["27Al"],
    magnetic_flux_density=21.14,  # in T
    rotor_frequency=1e9,  # in Hz
    spectral_dimensions=[
        dict(
            count=1024,
            spectral_width=1e4,  # in Hz
            reference_offset=1e4,  # in Hz
            events=[
                SpectralEvent(
                    # Selecting the inner satellite transitions
                    transition_query=[dict(P=[-1], D=[2])],
                )
            ],
        )
    ],
)

# A graphical representation of the method object.
plt.figure(figsize=(5, 3))
method.plot()
plt.show()
```

Create the Simulator object and add the method and the spin system object.

```python
sim = Simulator()
sim.spin_systems += [spin_system]  # add the spin system
sim.methods += [method]  # add the method
```

Simulate the spectrum.

```python
sim.run()

# The plot of the simulation before signal processing.
plt.figure(figsize=(4.25, 3.0))
ax = plt.subplot(projection="csdm")
ax.plot(sim.methods[0].simulation.real, color="black", linewidth=1)
ax.invert_xaxis()
plt.tight_layout()
plt.show()
```

## Selecting both inner and outer-satellite transitions
You may use the same transition query selection criterion to select multiple
transitions. Consider the following transitions with respective P and D values.

- $|-1/2\rangle\rightarrow|-3/2\rangle$ ($P=-1, D=2$)
- $|-3/2\rangle\rightarrow|-5/2\rangle$ ($P=-1, D=4$)

```python
method2 = Method1D(
    name="Satellite Spectrum",
    channels=["27Al"],
    magnetic_flux_density=21.14,  # in T
    rotor_frequency=1e9,  # in Hz
    spectral_dimensions=[
        {
            "count": 1024,
            "spectral_width": 1e4,  # in Hz
            "reference_offset": 1e4,  # in Hz
            "events": [
                {
                    "transition_query": [
                        {"P": [-1], "D": [2]},  # <-- select inter satellite transitions
                        {"P": [-1], "D": [4]},  # <-- select outer satellite transitions
                    ]
                }
            ],
        }
    ],
)

# A graphical representation of the method object.
plt.figure(figsize=(5, 3))
method2.plot()
plt.show()
```

Update the method object in the Simulator object.

```python
sim.methods[0] = method2  # add the method
```

Simulate the spectrum.

```python
sim.run()

# The plot of the simulation before signal processing.
plt.figure(figsize=(4.25, 3.0))
ax = plt.subplot(projection="csdm")
ax.plot(sim.methods[0].simulation.real, color="black", linewidth=1)
ax.invert_xaxis()
plt.tight_layout()
plt.show()
```


```
import sympy
from phasor.utilities.ipynb.displays import *
from phasor.utilities.ipynb.ipy_sympy import *
import scipy.linalg

import numpy.testing as np_test
import declarative as decl
from declarative.bunch import (
    DeepBunch,
)

#import numpy as np

from phasor import system
from phasor import readouts
from phasor import optics
from phasor.optics.nonlinear_crystal import NonlinearCrystal
from phasor.utilities.print import pprint

from phasor.optics.models.KTP_test_stand import KTPTestStand
```

    Populating the interactive namespace from numpy and matplotlib
    Sympy version:  1.0

```
db = DeepBunch()
db.test.ktp.length.val = 20#np.linspace(0, 20, 100)
db.test.ktp.N_ode = 100
db.test.include_AC = False
sys = system.BGSystem(
    ctree = db,
)
from phasor.optics.models.KTP_test_stand import KTPTestStand
sys.own.test = KTPTestStand()
db = sys.ctree_shadow()
print(sys.test.ktp.ctree_as_yaml())
sys.test.DC_R.DC_readout
```

    N_ode: 100
    length: {units: millimeter, val: 20}
    nlg: 0.1

    Number of states:  4
    Number of states:  4
    PERTURB:  1
    COUPLING_SIZE:  108
    PRE-PURGING
    FRAC REMOVED:  0.06896551724137931 8
    PERTURB:  2
    COUPLING_SIZE:  124
    PRE-PURGING
    FRAC REMOVED:  0.03125 4
    PERTURB:  3
    COUPLING_SIZE:  124
    PRE-PURGING
    FRAC REMOVED:  0.03125 4
    PRE-PURGING
    FRAC REMOVED:  0.1447811447811448 86

    array(0.06923723470180326)

```
print(sys.test.DC_R.DC_readout)
print(sys.test.DC_G.DC_readout)
```

    0.06923723470180326
    0.9295115580241431

```
db = DeepBunch()
db.test.ktp.length.val = np.linspace(0, 25, 100)
db.test.ktp.N_ode = 200
db.test.include_AC = False
sys = system.BGSystem(
    ctree = db,
)
from phasor.optics.models.KTP_test_stand import KTPTestStand
sys.own.test = KTPTestStand()
db = sys.ctree_shadow()
print(sys.test.ktp.ctree_as_yaml())
print(sys.test.DC_R.DC_readout)
```

    N_ode: 200
    length:
      units: millimeter
      val: !!python/object/apply:numpy.core.multiarray._reconstruct
        args:
        - !!python/name:numpy.ndarray ''
        - !!python/tuple [0]
        - !!binary |
          Yg==
        state: !!python/tuple
        - 1
        - !!python/tuple [100]
        - !!python/object/apply:numpy.dtype
          args: [f8, 0, 1]
          state: !!python/tuple [3, <, null, null, null, -1, -1, 0]
        - false
        - !!binary |
          AAAAAAAAAAB/pUCtXynQP3+lQK1fKeA/Pvjggw8+6D9/pUCtXynwP9/OkJi3M/Q/Pvjggw8++D+e
          ITFvZ0j8P3+lQK1fKQBAL7rooosuAkDfzpCYtzMEQI/jOI7jOAZAPvjggw8+CEDuDIl5O0MKQJ4h
          MW9nSAxATjbZZJNNDkB/pUCtXykQQNevFKj1KxFAL7rooosuEkCHxLydITETQN/OkJi3MxRAN9lk
          k002FUCP4ziO4zgWQOftDIl5OxdAPvjggw8+GECWArV+pUAZQO4MiXk7QxpARhdddNFFG0CeITFv
          Z0gcQPYrBWr9Sh1ATjbZZJNNHkCmQK1fKVAfQH+lQK1fKSBAq6qqqqqqIEDXrxSo9SshQAO1fqVA
          rSFAL7rooosuIkBbv1Kg1q8iQIfEvJ0hMSNAs8kmm2yyI0DfzpCYtzMkQAvU+pUCtSRAN9lkk002
          JUBj3s6QmLclQI/jOI7jOCZAu+iiiy66JkDn7QyJeTsnQBPzdobEvCdAPvjggw8+KEBq/UqBWr8o
          QJYCtX6lQClAwgcffPDBKUDuDIl5O0MqQBoS83aGxCpARhdddNFFK0ByHMdxHMcrQJ4hMW9nSCxA
          yiabbLLJLED2KwVq/UotQCIxb2dIzC1ATjbZZJNNLkB6O0Ni3s4uQKZArV8pUC9A0kUXXXTRL0B/
          pUCtXykwQBWo9SsFajBAq6qqqqqqMEBBrV8pUOswQNevFKj1KzFAbbLJJptsMUADtX6lQK0xQJm3
          MyTm7TFAL7rooosuMkDFvJ0hMW8yQFu/UqDWrzJA8cEHH3zwMkCHxLydITEzQB3HcRzHcTNAs8km
          m2yyM0BJzNsZEvMzQN/OkJi3MzRAddFFF110NEAL1PqVArU0QKHWrxSo9TRAN9lkk002NUDN2xkS
          83Y1QGPezpCYtzVA+eCDDz74NUCP4ziO4zg2QCXm7QyJeTZAu+iiiy66NkBR61cK1Po2QOftDIl5
          OzdAffDBBx98N0AT83aGxLw3QKn1KwVq/TdAPvjggw8+OEDU+pUCtX44QGr9SoFavzhAAAAAAAAA
          OUA=
    nlg: 0.1

    /usr/lib64/python3.5/site-packages/yaml/representer.py:135: FutureWarning: comparison to `None` will result in an elementwise object comparison in the future.
      if data in [None, ()]:

    Number of states:  4
    Number of states:  4
    PERTURB:  1
    COUPLING_SIZE:  108
    PRE-PURGING
    FRAC REMOVED:  0.06896551724137931 8
    PERTURB:  2
    COUPLING_SIZE:  124
    PRE-PURGING
    FRAC REMOVED:  0.03125 4
    PERTURB:  3
    COUPLING_SIZE:  124
    PRE-PURGING
    FRAC REMOVED:  0.03125 4
    PRE-PURGING
    FRAC REMOVED:  0.1447811447811448 86
    [ 1.          0.9993594   0.99744086  0.9942542   0.98981564  0.98414763
      0.97727865  0.96924287  0.9600798   0.94983388  0.93855407  0.9262933
      0.91310803  0.89905766  0.88420404  0.86861091  0.85234333  0.83546722
      0.8180488   0.80015414  0.78184866  0.76319678  0.74426145  0.72510388
      0.70578316  0.68635607  0.66687678  0.64739671  0.62796434  0.60862517
      0.58942157  0.57039279  0.55157489  0.53300084  0.51470049  0.49670066
      0.47902521  0.46169517  0.4447288   0.42814177  0.41194722  0.39615598
      0.38077665  0.36581575  0.35127789  0.33716592  0.32348101  0.31022287
      0.29738982  0.28497896  0.27298628  0.26140677  0.25023454  0.23946296
      0.22908468  0.21909182  0.20947599  0.20022837  0.19133985  0.18280102
      0.17460227  0.16673387  0.15918598  0.15194869  0.14501212  0.13836641
      0.13200177  0.12590849  0.12007699  0.11449785  0.10916178  0.10405971
      0.09918273  0.09452214  0.09006947  0.08581646  0.08175508  0.07787753
      0.07417626  0.07064392  0.06727343  0.06405792  0.06099078  0.05806561
      0.05527625  0.05261675  0.05008139  0.04766466  0.04536128  0.04316616
      0.0410744   0.03908131  0.03718239  0.03537332  0.03364995  0.03200831
      0.0304446   0.02895518  0.02753655  0.02618537]

```
axB = mplfigB(Nrows=1)
test = sys.test
axB.ax0.plot(test.ktp.length_mm.val, test.DC_R.DC_readout, color = 'red')
axB.ax0.plot(test.ktp.length_mm.val, test.DC_G.DC_readout, color = 'green')
axB.ax0.plot(test.ktp.length_mm.val, test.DC_R.DC_readout + test.DC_G.DC_readout, color = 'black')
axB.ax0.plot(test.ktp.length_mm.val, 1 * np.tanh(.100 * test.ktp.length_mm.val)**2, ls = '--', color = 'blue')
#axB.ax0.set_ylim(0, 1.1)
```

```
db = DeepBunch()
db.test.ktp.length.val = np.linspace(0, 2.5, 100)
db.test.ktp.nlg = 1
db.test.ktp.N_ode = 100
db.test.include_AC = True
sys = system.BGSystem(
    ctree = db,
)
sys.own.test = KTPTestStand()
db = sys.ctree_shadow()
#print(sys.test.ktp.ctree_as_yaml())
```

```
axB = mplfigB(Nrows=3)
test = sys.test
X_NLG = test.ktp.length_mm.val
axB.ax0.plot(
    X_NLG,
    test.DC_R.DC_readout,
    color = 'red',
    label = '1064 power',
)
axB.ax0.plot(
    X_NLG,
    test.DC_G.DC_readout,
    color = 'green',
    label = '532 power',
)
axB.ax0.plot(
    X_NLG,
    test.DC_R.DC_readout + test.DC_G.DC_readout,
    color = 'black',
    label = 'total power [W]',
)
axB.ax0.plot(
    X_NLG,
    1 * np.tanh(1 * test.ktp.length_mm.val)**2,
    ls = '--',
    color = 'purple',
    label = r'tanh$^2$(1 * NLG)',
)
axB.ax0.set_ylim(0, 1.1)
axB.ax0.set_ylabel('Readout Power [W]')
axB.ax0.legend(
    fontsize = 8,
    loc = 'center left'
)
axB.ax1.plot(
    X_NLG,
    test.AC_R.AC_CSD_IQ[0, 0],
    color = 'red',
    label = 'amplitude quadrature',
)
axB.ax1.plot(
    X_NLG,
    test.AC_R.AC_CSD_IQ[1, 1],
    color = 'orange',
    label = 'phase quadrature',
)
axB.ax1.plot(
    X_NLG,
    test.AC_R.AC_CSD_ellipse.max,
    color = 'blue',
    label = 'ellipse max',
    ls = '--'
)
axB.ax1.plot(
    X_NLG,
    test.AC_R.AC_CSD_ellipse.min,
    color = 'purple',
    label = 'ellipse min',
    ls = '--',
)
axB.ax1.plot(
    X_NLG,
    test.AC_R.AC_CSD_ellipse.min**.25 * test.AC_R.AC_CSD_ellipse.max**.25,
    color = 'black',
    ls = '--',
    label = 'geometric mean',
)
axB.ax1.set_ylabel('1064 PSD/ShotN\n[quanta/Hz]')
axB.ax1.set_yscale('log')
axB.ax1.legend(
    fontsize = 8,
    loc = 'center left'
)
axB.ax2.set_ylabel('532  PSD/ShotN\n[quanta/Hz]')
axB.ax2.plot(
    X_NLG,
    test.AC_G.AC_CSD_IQ[0, 0],
    color = 'green',
    label = 'amplitude quadrature',
)
axB.ax2.plot(
    X_NLG,
    test.AC_G.AC_CSD_IQ[1, 1],
    color = 'blue',
    label = 'phase quadrature',
)
axB.ax2.plot(
    X_NLG,
    test.AC_G.AC_CSD_ellipse.max,
    color = 'cyan',
    label = 'ellipse max',
    ls = '--',
)
axB.ax2.plot(
    X_NLG,
    test.AC_G.AC_CSD_ellipse.min,
    color = 'purple',
    label = 'ellipse min',
    ls = '--',
)
axB.ax2.plot(
    X_NLG,
    test.AC_G.AC_CSD_ellipse.min**.5 * test.AC_G.AC_CSD_ellipse.max**.5,
    color = 'black',
    label = 'geometric mean',
    ls = '--',
)
axB.ax2.set_xlabel('Crystal Total Nonlinear Gain [rtW / W]')
axB.ax2.legend(
    fontsize = 8,
    loc = 'center left'
)

```

```
axB = mplfigB()
for src, CSD in sys.test.AC_R.noise.CSD_by_source.items():
    if not np.all(CSD['ps_In', 'ps_In'] == 0):
        axB.ax0.plot(
            sys.test.ktp.length_mm.val,
            np.ones_like(sys.test.ktp.length_mm.val) * CSD['ps_In', 'ps_In'].real,
            label = str(src),
        )
axB.ax0.legend(
    fontsize = 8,
)

```

```
db = DeepBunch()
db.test.ktp.length.val = 1
db.test.ktp.nlg = 1
db.test.ktp.N_ode = 100
db.test.include_AC = True
sys = system.BGSystem(
    ctree = db,
)
sys.own.test = KTPTestStand()
db = sys.ctree_shadow()

LSTARR = sys.test.full_noise_matrix()
#print(sys.test.ktp.ctree_as_yaml())
```

    Number of states:  20
    Number of states:  20
    PERTURB:  1
    COUPLING_SIZE:  636
    PRE-PURGING
    FRAC REMOVED:  0.28054298642533937 248
    PERTURB:  2
    COUPLING_SIZE:  700
    PRE-PURGING
    FRAC REMOVED:  0.2584745762711864 244
    PERTURB:  3
    COUPLING_SIZE:  716
    PRE-PURGING
    FRAC REMOVED:  0.24152542372881355 228
    PRE-PURGING
    FRAC REMOVED:  0.4016045304388863 1702
              RI        RQ       GI        GQ
    --  --------  --------  -------  --------
    RI  0.309651  0         0        0
    RQ  0         0.947028  0        0.243494
    GI  0         0         1.58019  0
    GQ  0         0.243494  0        2.59602

```
print(sys.AC_R.AC_CSD_IQ[:,:].real)
print(sys.AC_G.AC_CSD_IQ[:,:].real)
print(sys.AC_RGI.AC_CSD_IQ[:,:].real)
print(sys.AC_R.AC_CSD_IQ[:,:].imag)
print(sys.AC_G.AC_CSD_IQ[:,:].imag)
print(sys.AC_RGI.AC_CSD_IQ[:,:].imag)
#print(sys.AC_G.AC_CSD_IQ)
```

```
sys = system.BGSystem()
sys.own.PSLR = optics.Laser(
    F = sys.system.F_carrier_1064,
    power_W = 1.,
)
sys.own.PSLG = optics.Laser(
    F = sys.system.F_carrier_1064,
    power_W = 1.,
    multiple = 2,
)
sys.own.dither = optics.AM()

sys.own.ktp = NonlinearCrystal(
    nlg = .1,
    length_mm = np.linspace(0, 10, 100),
    N_ode = 100,
)

sys.own.mDC2 = optics.HarmonicMirror(
    mirror_H1 = optics.Mirror(
        T_hr = 1,
    ),
    mirror_H2 = optics.Mirror(
        T_hr = 0,
    ),
    AOI_deg = 45,
)
sys.own.PD_R = optics.MagicPD()
sys.own.PD_G = optics.MagicPD()
sys.own.hPD_R = optics.HiddenVariableHomodynePD(
    source_port = sys.PSLR.po_Fr.o,
)
sys.own.hPD_G = optics.HiddenVariableHomodynePD(
    source_port = sys.PSLG.po_Fr.o,
)

sys.system.bond_sequence(
    sys.PSLR.po_Fr,
    sys.dither.po_Fr,
    sys.ktp.po_Fr,
    sys.mDC2.po_FrA,
    sys.PD_R.po_Fr,
    sys.hPD_R.po_Fr,
)
sys.system.bond_sequence(
    sys.mDC2.po_FrB,
    sys.PD_G.po_Fr,
    sys.hPD_G.po_Fr,
)
sys.own.DC_R = readouts.DCReadout(
    port = sys.PD_R.Wpd.o,
)
sys.own.DC_G = readouts.DCReadout(
    port = sys.PD_G.Wpd.o,
)
sys.own.AC_G = readouts.HomodyneACReadout(
    portNI = sys.hPD_G.rtWpdI.o,
    portNQ = sys.hPD_G.rtWpdQ.o,
    portD  = sys.dither.Drv.i,
)
sys.own.AC_R = readouts.HomodyneACReadout(
    portNI = sys.hPD_R.rtWpdI.o,
    portNQ = sys.hPD_R.rtWpdQ.o,
    portD  = sys.dither.Drv.i,
)
sys.own.AC_RGI = readouts.HomodyneACReadout(
    portNI = sys.hPD_R.rtWpdI.o,
    portNQ = sys.hPD_G.rtWpdI.o,
    portD  = sys.dither.Drv.i,
)
sys.own.AC_N = readouts.NoiseReadout(
    port_map = dict(
        RI = sys.hPD_R.rtWpdI.o,
        RQ = sys.hPD_R.rtWpdQ.o,
        GI = sys.hPD_G.rtWpdI.o,
        GQ = sys.hPD_G.rtWpdQ.o,

)
#print("A")
#pprint(sys.ctree.test.PSL)
#print("sys.DC_R.DC_readout", sys.DC_R.DC_readout, 2)
#print("sys.DC_G.DC_readout", sys.DC_G.DC_readout, 1)

```

```
1.802e-19 - 2*2.36e-20
```

```
sys.AC_RGI.AC_CSD_IQ[0,0]
```

```
sys.AC_RGI.AC_CSD_ellipse
```

```
sys = system.BGSystem()
sys.own.PSLG = optics.Laser(
    F = sys.system.F_carrier_1064,
    power_W = 1.,
    multiple = 2,
)
sys.own.PSLR = optics.Laser(
    F = sys.system.F_carrier_1064,
    power_W = 0.001,
    multiple = 1,
)
sys.own.dither = optics.AM()

sys.own.ktp = NonlinearCrystal(
    nlg = .1,
    length_mm = 10, #np.linspace(0, 20, 2),
    N_ode = 20,
)

sys.own.mDC1 = optics.HarmonicMirror(
    mirror_H1 = optics.Mirror(
        T_hr = 0,
    ),
    mirror_H2 = optics.Mirror(
        T_hr = 1,
    ),
    AOI_deg = 45,
)
sys.own.mDC2 = optics.HarmonicMirror(
    mirror_H1 = optics.Mirror(
        T_hr = 1,
    ),
    mirror_H2 = optics.Mirror(
        T_hr = 0,
    ),
    AOI_deg = 45,
)
sys.own.PD_R = optics.MagicPD()
sys.own.PD_G = optics.MagicPD()
sys.own.hPD_R = optics.HiddenVariableHomodynePD(
    source_port = sys.PSLR.po_Fr.o,
)
sys.own.hPD_G = optics.HiddenVariableHomodynePD()

sys.system.bond_sequence(
    sys.PSLG.po_Fr,
    sys.mDC1.po_FrA,
    sys.dither.po_Fr,
    sys.ktp.po_Fr,
    sys.mDC2.po_FrA,
    sys.PD_R.po_Fr,
    sys.hPD_R.po_Fr,
)
#sys.system.bond_sequence(
#    sys.PSLR.po_Fr,
#    sys.mDC1.po_BkB,
#)
sys.system.bond_sequence(
    sys.mDC2.po_FrB,
    sys.PD_G.po_Fr,
    sys.hPD_G.po_Fr,
)
sys.own.DC_R = readouts.DCReadout(
    port = sys.PD_R.Wpd.o,
)
sys.own.DC_G = readouts.DCReadout(
    port = sys.PD_G.Wpd.o,
)
sys.own.AC_G = readouts.HomodyneACReadout(
    portNI = sys.hPD_G.rtWpdI.o,
    portNQ = sys.hPD_G.rtWpdQ.o,
    portD  = sys.dither.Drv.i,
)
sys.own.AC_R = readouts.HomodyneACReadout(
    portNI = sys.hPD_R.rtWpdI.o,
    portNQ = sys.hPD_R.rtWpdQ.o,
    portD  = sys.dither.Drv.i,
)
sys.own.AC_RGI = readouts.HomodyneACReadout(
    portNI = sys.hPD_R.rtWpdI.o,
    portNQ = sys.hPD_G.rtWpdI.o,
    portD  = sys.dither.Drv.i,
)
sys.own.AC_N = readouts.NoiseReadout(
    port_map = dict(
        RI = sys.hPD_R.rtWpdI.o,
        RQ = sys.hPD_R.rtWpdQ.o,
        GI = sys.hPD_G.rtWpdI.o,
        GQ = sys.hPD_G.rtWpdQ.o,
    )
)
#print("A")
#pprint(sys.ctree.test.PSL)
#print("sys.DC_R.DC_readout", sys.DC_R.DC_readout, 2)
#print("sys.DC_G.DC_readout", sys.DC_G.DC_readout, 1)

```

```
print(sys.AC_R.AC_CSD_IQ[:,:])
print(sys.AC_G.AC_CSD_IQ[:,:])
print(sys.AC_RGI.AC_CSD_IQ[:,:])
print((sys.AC_R.AC_CSD_ellipse.min * sys.AC_R.AC_CSD_ellipse.max)**.5)
print(sys.AC_R.AC_CSD_ellipse.min / (sys.AC_R.AC_CSD_ellipse.min * sys.AC_R.AC_CSD_ellipse.max)**.5)
sys.AC_R.AC_CSD_ellipse
```

```
axB = mplfigB(Nrows=1)
axB.ax0.plot(sys.ktp.length_mm.val, sys.DC_R.DC_readout, color = 'red')
axB.ax0.plot(sys.ktp.length_mm.val, sys.DC_G.DC_readout, color = 'green')
axB.ax0.plot(sys.ktp.length_mm.val, sys.DC_R.DC_readout + sys.DC_G.DC_readout, color = 'black')
axB.ax0.set_ylim(0, 1.1)
```

```
sys = system.BGSystem()
sys.own.PSLG = optics.Laser(
    F = sys.system.F_carrier_1064,
    power_W = 1.,
    multiple = 2,
)
sys.own.PSLR = optics.Laser(
    F = sys.system.F_carrier_1064,
    power_W = 0.001,
    multiple = 1,
)
sys.own.PD_R = optics.MagicPD()
sys.own.PD_G = optics.MagicPD()
sys.own.dither = optics.AM()

sys.own.hPD_R = optics.HiddenVariableHomodynePD(
    source_port = sys.PSLR.po_Fr.o,
)
sys.own.hPD_G = optics.HiddenVariableHomodynePD()

sys.system.bond_sequence(
    sys.PSLG.po_Fr,
    sys.PD_G.po_Fr,
    sys.hPD_G.po_Fr,
)
sys.system.bond_sequence(
    sys.PSLR.po_Fr,
    sys.PD_R.po_Fr,
    sys.hPD_R.po_Fr,
)
sys.own.DC_R = readouts.DCReadout(
    port = sys.PD_R.Wpd.o,
)
sys.own.DC_G = readouts.DCReadout(
    port = sys.PD_G.Wpd.o,
)
sys.own.AC_G = readouts.HomodyneACReadout(
    portNI = sys.hPD_G.rtWpdI.o,
    portNQ = sys.hPD_G.rtWpdQ.o,
    portD  = sys.dither.Drv.i,
)
sys.own.AC_R = readouts.HomodyneACReadout(
    portNI = sys.hPD_R.rtWpdI.o,
    portNQ = sys.hPD_R.rtWpdQ.o,
    portD  = sys.dither.Drv.i,
)
sys.own.AC_RGI = readouts.HomodyneACReadout(
    portNI = sys.hPD_R.rtWpdI.o,
    portNQ = sys.hPD_G.rtWpdI.o,
    portD  = sys.dither.Drv.i,
)
sys.own.AC_N = readouts.NoiseReadout(
    port_map = dict(
        RI = sys.hPD_R.rtWpdI.o,
        RQ = sys.hPD_R.rtWpdQ.o,
        GI = sys.hPD_G.rtWpdI.o,
        GQ = sys.hPD_G.rtWpdQ.o,
    )
)
#print("A")
#pprint(sys.ctree.test.PSL)
#print("sys.DC_R.DC_readout", sys.DC_R.DC_readout, 2)
#print("sys.DC_G.DC_readout", sys.DC_G.DC_readout, 1)

```

```
print(sys.AC_R.AC_CSD_IQ[:,:])
print(sys.AC_G.AC_CSD_IQ[:,:])
print(sys.AC_RGI.AC_CSD_IQ[:,:])
print((sys.AC_R.AC_CSD_ellipse.min * sys.AC_R.AC_CSD_ellipse.max)**.5)
print(sys.AC_R.AC_CSD_ellipse.min / (sys.AC_R.AC_CSD_ellipse.min * sys.AC_R.AC_CSD_ellipse.max)**.5)
sys.AC_R.AC_CSD_ellipse
```

```
from phasor.optics.models.KTP_test_stand import KTPTestStand
sys = system.BGSystem()
sys.own.test = KTPTestStand()
LSTARR = sys.test.full_noise_matrix()
```

```

```

```

```


# Causality and supervised machine learning
*Andreas Bjerre-Nielsen*

## Introduction

What is the objective of empirical policy research?

1. *causation*: what is the effect of a particular variable on an outcome?
2. *prediction*: find some function that provides a good prediction of $y$ as a function of $x$

## Intution

$$ y = \alpha + \beta x + \varepsilon $$

- *causation*: interested in $\hat{\beta}$

- *prediction*: interested in $\hat{y}$

## Preparation

```python
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns
import statsmodels.formula.api as smf # module for various regression models

sns.set()

%matplotlib inline
```

# Causal Inference

## Introduction

Most statistical methods in social science theory is focused on estimating **causal effects**

Causal effect: how does the factor A affect B?

Examples of causal questions:

- what is the effect of being assigned to a shared group on communication?
- what is the effect of sterotypical names on job interview success?

## Intuition

Variable of interest (often called *treatment*): $D_i$

Outcome of interest: $Y_i$

**Potential outcome framework**
$$
Y_i = \left\{
\begin{array}{rl}
Y_{1i} & \text{if } D_i = 1,\\
Y_{0i} & \text{if } D_i = 0
\end{array} \right.
$$

The observed outcome $Y_i$ can be written in terms of potential outcomes as
$$ Y_i = Y_{0i} + (Y_{1i}-Y_{0i})D_i$$

$Y_{1i}-Y_{0i}$ is the *causal* effect of $D_i$ on $Y_i$.

But we never observe the same individual $i$ in both states. This is the **fundamental problem of causal inference**.

## Selection Bias I

We need some way of estimating the state we do not observe (the ***counterfactual***)

Usually, our sample contains individuals from both states - treated and untreated.

So why not do a naive comparison of averages by treatment status? i.e. $E[Y_i|D_i = 1] - E[Y_i|D_i = 0]$

## Selection Bias II
We can rewrite into:
\begin{align}
\nonumber E[Y_i|D_i = 1] - E[Y_i|D_i = 0] = &E[Y_{1i}|D_i = 1] - E[Y_{0i}|D_i = 1] + \\
 \nonumber  &E[Y_{0i}|D_i = 1] - E[Y_{0i}|D_i = 0]
\end{align}

The decomposition:

 - $E[Y_{1i}|D_i = 1] - E[Y_{0i}|D_i = 1] = E[Y_{1i} - Y_{0i}|D_i = 1]$: the average *causal* effect of $D_i$ on $Y$.

- $E[Y_{0i}|D_i = 1] - E[Y_{0i}|D_i = 0]$: difference in average $Y_{0i}$ between the two groups. Likely to be different from 0 when individuals are allowed to self-select into treatment. Often referred to as ***selection bias***.

## Random assignment solves the problem

Random assignment of $D_i$ solves the problem because random assignment makes $D_i$ independent of potential outcomes

That means that $E[Y_{0i}|D_i = 1] = E[Y_{0i}|D_i = 0]$ and thus that the selection bias term is zero

Intuition: with random assignment, non-treated individuals can be used as counterfactuals for treated (*what would have happened to individual $i$ had he not received the treatment*?)

This allows us to overcome the fundamental problem of causal inference

## Randomization

Holland and Rubin (1986)

> no causation without manipulation

As mentioned, we need to worry when individuals are allowed to self-select

This means that a lot of thought has to go into the *randomization phase*

Randomization into treatment groups has to be manipulated by someone

But what about effect of *immutable characteristics* such as race, gender, etc.?

## Quasi Experiments

*Quasi-experiments*: randomization happens by "accident"

- Differences in Differences
- Regression Discontinuity Design
- Instrumental variables

## Randomized Controlled Trials

*Randomized controlled trials (RCT)*: randomization done by researcher

- Survey experiments
- Field experiments

Note: difficult to say one is strictly better than the other. Randomization can be impractical and/or unethical.

## External & internal validity

*Internal validity*: Refers to the validity of causal conclusions

*External validity*: Refers to the extent to which the conclusions of a particular study can be generalized beyond a particular setting

RCTs - external and internal validity
- Kosuke Imai (2016): There is tradeoff.
- Cyrus Samii (2016): No such tradeoff.

## Observational study

In many cases, social scientists are unable to randomize treatment assignment for ethical or logistic reasons

*Observational study*: No random manipulation of treatment

Strategy: Statistical control (control variables, fixed effects, matching, etc)

Risks: selection & confounding bias and endogeneity.

## Case: Racial Discrimination in the Labor Market

Does racial discrimination exist in the labor market?

*Experiment*: In response to newspaper ads, researchers send out resumes of fictitious job candidates, varying only the names of the job applicants while leaving all other information in the resumes unchanges

Names were randomized between stereotypically black- and white-sounding names (Lakisha, Jamal, Emily, Greg, etc.)

## Case: Racial Discrimination in the Labor Market (2)

```python
gh_raw = "https://raw.githubusercontent.com/"
user = "kosukeimai/"
repo = 'qss/'
branch = "master/"
filepath = "CAUSALITY/resume.csv"
url = gh_raw + user + repo + branch + filepath

df = pd.read_csv(url,dtype={'race':'category','sex':'category'})
```

## Case: Racial Discrimination in the Labor Market (3)

Can we use a boxplot? No boxplot not good for binary data.

```python
f, ax = plt.subplots(1,2,figsize=(12,4))
sns.barplot(x='race', y='call', data=df, ax=ax[0])
sns.barplot(x='sex', hue='race', y='call', data=df, ax=ax[1])
```

## Case: Racial Discrimination in the Labor Market (4)

```python
model = smf.ols(formula='call~race*sex', data=df)
results = model.fit()
results.summary()
```

OLS Regression Results

  Dep. Variable:	          call	         R-squared:         	    0.004

  Model:	                   OLS	         Adj. R-squared:    	    0.003

  Method:	             Least Squares	    F-statistic:       	    5.973

  Date:	             Mon, 14 Aug 2017	   Prob (F-statistic):	 0.000464

  Time:	                 09:40:34	       Log-Likelihood:    	  -561.75

  No. Observations:	        4870	        AIC:               	    1131.

  Df Residuals:	            4866	        BIC:               	    1157.

  Df Model:	                   3

  Covariance Type:	      nonrobust

              	                 coef	     std err	      t	      P>|t|	  [0.025	    0.975]

  Intercept	                     0.0663	     0.006	    10.595	  0.000	     0.054	     0.079

  race[T.white]	                 0.0326	     0.009	     3.677	  0.000	     0.015	     0.050

  sex[T.male]	                  -0.0080	     0.013	    -0.606	  0.544	    -0.034	     0.018

  race[T.white]:sex[T.male]	    -0.0022	     0.018	    -0.121	  0.904	    -0.038	     0.034

  Omnibus:	       2968.362	   Durbin-Watson:     	    1.441

  Prob(Omnibus):	   0.000	    Jarque-Bera (JB):  	 18913.265

  Skew:	            3.067	    Prob(JB):          	     0.00

  Kurtosis:	       10.455	    Cond. No.          	     6.64

## Case: Racial Discrimination in the Labor Market (5)

```python
summ_pd = results.summary2() # get summary as pandas tables
params = summ_pd.tables[1].iloc[1:] # pick coefficient tables

m = len(params)
err_args = {'fmt':'.', 'capsize':5, 'markeredgewidth':2}
plt.errorbar(y=range(m),
             x=params['Coef.'],
             xerr=params['Std.Err.'],
             **err_args)
plt.yticks(range(m),params.index)
plt.plot((0, 0), (0, m-1), '--', color='0.6')
```

# Machine learning

## Topics in machine learning

*Supervised Learning*: Models designed to infer a relationship between input and **labeled** training data. These models are used for **prediction**. Examples: OLS, logistic reg.

*Unsupervised Learning*:  Models designed to infer a relationship from **unlabeled** training data. This may involve clustering, dimensionality reduction and more.

# Supervised machine learning

## Prediction

Many policy problems are not about causality but rather about prediction

Sometimes called *prediction policy problems*

- How many people will sign up for Obamacare?
- Who will win the U.S general election in November?
- Who should the Department of Economics hire in the future?

## Who predicts?

* Local governments -> crime, childcare usage, pension payments etc.
* FB/GOOG/AMZN/NTFL/etc. > estimating 'preferences' to improve customer experience / sales
    * ads, status updates, music, movies, news
* Insurance companies -> what your risk of death is
* Stock traders > to trade
* Robots -> understanding their environment (e.g. self-driving cars)
* You? -> will *Social Data Science* be a fun/rewarding/interesting course to follow?

## Why predict? Glory!

## The Netflix Contest

Competition started in October 2006. Training data is ratings for 18K movies by 400K Netflix customers, each rating between 1 and 5

Training data is very sparse - about 98% missing

Objective is to predict the rating for a set of 1 million customer-movie pairs that are missing in the training data

Winner: Averaged 800 models

## Why predict? Riches!

[http://www.heritagehealthprize.com/c/hhp](http://www.heritagehealthprize.com/c/hhp)

## Prediction problem types

Predicition problems are often described in terms of the data types they are predicting.
- The first is **regression** which uses numeric (continuous) variables.
- The second is **classification** which uses categorical (discrete) variables.

Examples?

## Case: Predicting gender from body measures

What type of prediction problem is this? Classification.

- Classification!

## Case: Predicting gender from body measures (2)

```python
print(body.head())
```

      Gender      Height      Weight  Male
    0   Male  189.048364  109.819678   1.0
    1   Male  176.081674   73.688955   1.0
    2   Male  189.721870   96.584348   1.0
    3   Male  183.631305   99.899282   1.0
    4   Male  178.897397   93.682809   1.0

```python
gh_raw = "https://raw.githubusercontent.com/"
user = "johnmyleswhite/"
repo = 'ML_for_Hackers/'
branch = "master/"
filepath = "02-Exploration/data/01_heights_weights_genders.csv"
url = gh_raw + user + repo + branch + filepath

body = pd.read_csv(url)
body['Male'] = (body.Gender=='Male').astype(float) # Binary variable
body.Height = body.Height*2.56 # convert inches to cm
body.Weight = body.Weight*0.454 # convert pounds to kg
```

## Case: Predicting gender from body measures (3)
*Do we already know any machine learning models?*

```python
from sklearn.linear_model import LogisticRegression

X = body[['Weight', 'Height']] # features
y = body.Male # labels

# make classifier and fit model
clf = LogisticRegression().fit(X, y)

# parameter estimates
pd.Series(clf.coef_[0], index=['Weight', 'Height'])
```

    Weight    0.433517
    Height   -0.186886
    dtype: float64

## Case: Predicting gender from body measures (4)

Logit estimates

$$ P(Y_i = 1 |X_i = x_i) = \frac{1}{1 + e^{-x_i\beta}}$$

This probability is .5 when $x_i\beta = 0$. Thus we can classify predicted gender based on height and weight with the following rule:

$$
\hat{y} = \left\{
\begin{array}{rl}
1 & \text{if } x_i\beta \geq 0,\\
0 & \text{otherwise}
\end{array} \right.
$$

Classifier threshold:
$$ H = \frac{-\alpha - \beta_W W}{\beta_H} $$

## Case: Predicting gender from body measures (5)

```python
xx, yy = np.mgrid[25:125:.1, 130:210:.08]
grid = np.c_[xx.ravel(), yy.ravel()]
probs = clf.predict_proba(grid)[:, 1].reshape(xx.shape)

f, ax = plt.subplots(figsize=(13, 7))
contour = ax.contourf(xx, yy, probs, 25, cmap="RdBu",
                      vmin=0, vmax=1)
ax_c = f.colorbar(contour)
ax_c.set_label("$P(y = 1)$")
ax_c.set_ticks([0, .25, .5, .75, 1])

plt.scatter(X.iloc[:,0], X.iloc[:,1], c=y, s=10,
            cmap="RdBu", vmin=-.2, vmax=1.2,
            edgecolor="white", linewidth=.5, alpha=.3)

ax.set(aspect="equal",
       xlim=(25, 125), ylim=(130, 210),
       xlabel="Weight", ylabel="Height")
```

## Case: Predicting gender from body measures (6)

**Decision boundary**

```python
f
```

## Case: Predicting gender from body measures (7)
Another model: nearest neighbor

```python
from sklearn.ensemble import RandomForestClassifier
from sklearn.neighbors import  KNeighborsClassifier

X = body[['Weight', 'Height']]
y = body.Male

# make model on data - fifty nearest neighbors
clf = KNeighborsClassifier(n_neighbors=50).fit(X, y)
```

## Case: Predicting gender from body measures (8)

Computing nearest neighbor decision boundary

```python
xx, yy = np.mgrid[25:125:.1, 130:210:.08]
grid = np.c_[xx.ravel(), yy.ravel()]
probs = clf.predict_proba(grid)[:, 1].reshape(xx.shape)

f, ax = plt.subplots(figsize=(13, 7))
contour = ax.contourf(xx, yy, probs, 25, cmap="RdBu",
                      vmin=0, vmax=1)
ax_c = f.colorbar(contour)
ax_c.set_label("$P(y = 1)$")
ax_c.set_ticks([0, .25, .5, .75, 1])

plt.scatter(X.iloc[:,0], X.iloc[:,1], c=y, s=10,
            cmap="RdBu", vmin=-.2, vmax=1.2,
            edgecolor="white", linewidth=.5, alpha=.1)

ax.set(aspect="equal",
       xlim=(25, 125), ylim=(130, 210),
       xlabel="Weight", ylabel="Height")
```

## Case: Predicting gender from body measures (9)

Decision boundary: ***non-linear***

```python
f
```

# Bias and variance

Are OLS and logistic regression good for prediction?

## The bias-variance tradeoff

OLS is designed to minimize *in sample error*: the error rate you get on the same data set you used to build your predictor.

$$ \text{arg min}_{\beta} \sum_{i = 1}^{n} (y_i - \hat{y}_i)^2  = \text{arg min}_{\beta} (V(\hat{f}(x_0)) + \sigma^2) $$

But for prediction we are interested in minimizing *out of sample error*: the error rate you get on a new data set

## Prediction

Too see this, consider a prediction at a new point (out-of-sample), $x_0$. Our prediction for $y_0$ is then $\hat{f}(x_0)$ and the mean squared error (MSE ) can be decomposed as

$$ \mathbb{E}[(y_0 - \hat{f}(x_0))^2] =
\underset{Bias(\hat{f}(x))}{\underbrace{\mathbb{E}[\hat{f}(x_0)-f(x_0)]}}^2 +
\underset{Var(\hat{f}(x))}{\underbrace{\mathbb{E}[\hat{f}(x_0)^2]-\mathbb{E}[\hat{f}(x_0)]^2}} +
\sigma^2$$

By ensuring zero bias within sample, OLS picks a solution which not be optimal for prediction
- in many cases we can lower variance while increasing bias a little.

## Bias and variance

What do we mean by the *variance* and *bias* of an estimator?

- *Bias*: Comes from using erroneous model assumptions, e.g. fitting non-linear fct. $f$ with linear fct. $\hat{f}$.
    - Can lead to missing relevant patterns in data, i.e. *underfitting*.

- *Variance*: Refers to model complexity. If the model is too complex then small changes to the data will cause the solution to change a lot.
    - Can lead to finding spurious patterns in data, i.e. *overfitting*.

Machine learning techniques were developed specifically to maximize prediction performance by providing an empirical way to make this bias-variance trade off

## Bias and variance (2)

*So why do we care about bias?*

- By not modelling bias: allows *inference*, i.e. testing hypotheses! (model parameters converge to true parameters) ~ interested in $\hat{\beta}$

- By modelling bias: allows better predictive models as they trade off bias and variance. Interested in $\hat{y}$

# Out-of-sample measures

## Key concepts

- Training data: where we estimate our model
- Test data: where we evaluate the model's accuracy

## Error

Statistical learning models are designed to minimize *out of sample error*: the error rate you get on a new data set

Key ideas

- Out of sample error is what you care about
- In sample error $<$ out of sample error
- The reason is overfitting (matching your algorithm to the data you have)

## Error measures (continuous variables)

**Mean absolute error (MAE)**:

$$MAE = \frac{1}{n} \sum_{i=1}^{n} |\hat{y}_i - y_i|$$

**Mean squared error (MSE)**:

$$MSE = \frac{1}{n} \sum_{i=1}^{n} (\hat{y}_i - y_i)^2$$

**Root mean squared error (RMSE)**:

$$\sqrt{MSE}$$

**Question:** what is the difference?

```python
from sklearn.metrics import mean_absolute_error as mae, mean_squared_error as mse
```

## Case: Longevity

```python
gh_raw = "https://raw.githubusercontent.com/"
user = "johnmyleswhite/"
repo = "ML_for_Hackers/"
branch = "master/"
filepath = "05-Regression/data/longevity.csv"
url = gh_raw + user + repo + branch + filepath

longevity = pd.read_csv(url)

print(longevity.head())
```

       Smokes  AgeAtDeath
    0       1          75
    1       1          72
    2       1          66
    3       1          74
    4       1          69

## Case: Longevity (2)

```python
y_true = longevity.AgeAtDeath

def rmse_year(y):
    '''
    Takes as input: year (y)
    Outputs: root mean squared of guessing y
    '''
    y_pred = [y]*len(longevity)
    return np.sqrt(mse(y_true, y_pred))

# normal for loop
rmse_years = []

for y in range(60,91):
    rmse_years.append((y, rmse_year(y)))

# list comprehenseion:
rmse_years = [(y, rmse_year(y)) for y in range(60,91)]

rmse_data = pd.DataFrame(data = rmse_years,
                         columns = ['age', 'rmse'])

# print ('Best guess:', rmse_data.set_index('year').rmse.idxmin())
#rmse_data.plot.scatter('age', 'rmse')
```

## Case: Longevity (3)

```python
f,ax = plt.subplots(figsize=(12,6))
print ('Best guess:', rmse_data.set_index('age').rmse.idxmin())
rmse_data.plot.scatter('age', 'rmse', ax=ax)
```

# Cross validation (CV)

## Test and training data

Accuracy on the training set (resubstitution accuracy) does not capture bias and therefore is optimistic. A better estimate comes from an independent set (test set accuracy). Strategy:

- Use share of observations for training
- Use other of observations for testing model (out-of-sample)

So we estimate the test data accuracy with the model calibrated on training data.

## Data split

Why not just divide data randomly the data into a test and training set?

Two drawbacks

1. RMSE is very sensitive to which observations are used for test and training.
2. RMSE is artificially large as not all observations are used for training model (model becomes more accurate for more observations)

One very useful refinement of the test-training data approach is ***cross-validation*** use multiple splits.

## K-fold Cross Validation

1. Divide the data into $k$ roughly equal subsets and label them $s = 1, ..., k$.
2. Fit your model using the $k-1$ subsets other than subset $s$
3. Predict for subset $s$ and calculate RMSE
4. Stop if $s = k$, otherwise increment $s$ by $1$ and continue

The $k$ fold CV estimate is computed by averaging the mean squared errors ($\text{MSE}_1, ..., \text{MSE}_k$)

$$\text{CV}_k = \frac{1}{k}\sum_{i = 1}^{k} \text{MSE}_i$$

Common choices for $k$ are 3, 5 and 10.

CV can (and should) be used both to decide hyperparameters and to report goodness-of-fit measures.

## K-fold Cross Validation (2)

## Fitting polynomial
Polyonomial: $f(x) = 2+8*x^4$

Try models of higher and higher polynomials. Iteration n: $y = \sum_{k=0}^{n}(\beta_k\cdot x^k)+\varepsilon$.

```python
def my_polynomial(x):
    'Polyonomial: f(x) = 2+8*x^4'
    return 2+8*x**4

np.random.seed(1234)

x_range = pd.Series(np.arange(0,1,.02))

y_s = []
for x in x_range:
    y = my_polynomial(x)
    y_s.append(y)

fct_values = pd.Series(y_s) # true values
errors =  pd.Series(np.random.normal(size=(50))) # measurement error

y = fct_values + errors # observed outcome data
```

## Fitting polynomial (2)

*Compute within sample error for OLS*

```python
from sklearn.linear_model import LinearRegression

X_s = {}
rmse_in_sample = []

for i in range(1,10): # for each order 1-9
    X_s['order '+str(i)]= x_range**i  # add polynomial of order i
    X = pd.concat(X_s,axis=1) # concatename polynomials of order 1,..,i

    model = LinearRegression().fit(X,y) # fit linear regression model
    y_pred =  model.predict(X) # make predictions within-sample

    rmse_in_sample.append(np.sqrt(mse(y, y_pred)))

print(X.iloc[:,:5].head(2))
```

       order 1  order 2   order 3       order 4       order 5
    0     0.00   0.0000  0.000000  0.000000e+00  0.000000e+00
    1     0.02   0.0004  0.000008  1.600000e-07  3.200000e-09

## Fitting polynomial (3)

*Compute out-of-sample errors for OLS*

```python
from sklearn.model_selection import KFold

kf = KFold(n_splits=10, random_state=123)

X_s = {}
rmse_CV = []
coef_CV = []

for i in range(1,10):
    X_s['order '+str(i)] = x_range**i
    X = pd.concat(X_s, axis=1)

    rmse_fold = []
    coef_fold = []

    # cross validation loop
    for train_index, test_index in kf.split(X):
        X_train, X_test = X.iloc[train_index], X.iloc[test_index]
        y_train, y_test = y.iloc[train_index], y.iloc[test_index]

        model = LinearRegression().fit(X_train,y_train)
        y_pred =  model.predict(X_test)

        rmse = np.sqrt(mse(y_test, y_pred))
        rmse_fold.append(rmse)

        coef_fold.append(pd.Series(model.coef_, index=range(1,i+1)))

    rmse_CV.append(np.mean(rmse_fold))
    coef_CV.append(pd.concat(coef_fold,1).T.mean().rename(i))
```

## Fitting polynomial (4)

How does in-sample and out-of-sample estimates of RMSE diverse?

```python
f,ax = plt.subplots(figsize=(12,6))
rmse_df = pd.DataFrame(data = [rmse_in_sample, rmse_CV],
                       index = ['OLS in-sample', 'OLS out-of-sample (CV)'],
                       columns = range(1,10)).T

rmse_df.columns.name = 'RMSE'
rmse_df.index.name = 'polynomial order'
rmse_df.plot(ax=ax)
ax.set_yscale('log')
```

## Fitting polynomial (5)

Why does in-sample and out-of-sample estimates of RMSE diverse? Well, the coefficient of OLS are increasingly mis-estimated for higher and higher number of variables.

```python
pd.concat(coef_CV,1).T
```

      1
      2
      3
      4
      5
      6
      7
      8
      9

      1
      6.234355
      NaN
      NaN
      NaN
      NaN
      NaN
      NaN
      NaN
      NaN

      2
      -6.146667
      12.590554
      NaN
      NaN
      NaN
      NaN
      NaN
      NaN
      NaN

      3
      -0.781354
      -1.104807
      9.249710
      NaN
      NaN
      NaN
      NaN
      NaN
      NaN

      4
      -6.394433
      22.107885
      -25.078074
      16.501495
      NaN
      NaN
      NaN
      NaN
      NaN

      5
      7.678056
      -73.795322
      227.908655
      -266.336210
      112.593443
      NaN
      NaN
      NaN
      NaN

      6
      -24.034620
      197.092328
      -785.577926
      1574.624054
      -1482.858265
      528.364240
      NaN
      NaN
      NaN

      7
      -26.234597
      139.009441
      -235.967168
      -318.984810
      1647.278049
      -1974.405448
      777.934915
      NaN
      NaN

      8
      -2.480596
      -149.025175
      1496.517741
      -6011.772182
      12283.632851
      -13177.716397
      6927.394820
      -1354.677227
      NaN

      9
      -174.311112
      2248.717158
      -16219.517439
      69637.464867
      -182832.881188
      294545.334322
      -282663.341134
      147727.564074
      -32259.887872

## Regularization

*Why do we regularize?*

- To avoid overfitting and thus have better predictions

*How do we regularize?*

We make models which are less complex by reducing the number and/or size of the coefficients.

## Regularization (2)

*What does regularization look like?*

We add a penalty term our optimization procedure:

$$ \text{arg min}_\beta \, \underset{\text{MSE}}{\underbrace{E[(y_0 - \hat{f}(x_0))^2]}} + \underset{\text{penalty}}{\underbrace{\lambda \cdot R(\beta)}}$$

Introduction of penalties implies that increased model complexity has to be met with high increases precision of estimates.

## Regularization (3)

*What are some used penalty functions?*

The two most common penalty functions are L1 and L2 regularization.

- L1 regularization (***Lasso***): $R(\beta)=\sum_{j=1}^{p}|\beta_j|$
    - Makes coefficients sparse, i.e. selects variables by removing some (if $\lambda$ is high)

- L2 regularization (***Ridge***): $R(\beta)=\sum_{j=1}^{p}\beta_j^2$
    - Reduce coefficient size
    - Fast due to analytical solution

*To note:* The *Elastic Net* uses a combination of L1 and L2 regularization.

## Regularization (4)

*How the Lasso (L1 reg.) deviates from OLS*

## Regularization (5)

*How the Ridge regression (L2 reg.) deviates from OLS*

# Models for supervised machine learning

## Model overview

*Parametric models*
- regression models
    - unbiased: OLS
    - biased: ***Lasso*** (L1), ***Ridge*** (L2) - (regulariation)
- classifier
    - unbiased: logistic

*Other models*
- **random forest**
- **nearest neighbor**
- neural networks

## Fitting polynomial (6)

Training the model with Lasso

```python
from sklearn.model_selection import KFold
from sklearn.linear_model import Lasso

kf = KFold(n_splits=10, random_state=123)

X_s = {}
rmse_CV_L = []

for i in range(1,10):
    X_s['order '+str(i)] = x_range**i
    X = pd.concat(X_s, axis=1)

    rmse_fold = []

    # cross validation loop
    for train_index, test_index in kf.split(X):
        X_train, X_test = X.iloc[train_index], X.iloc[test_index]
        y_train, y_test = y.iloc[train_index], y.iloc[test_index]

        # use Lasso with lambda = .05
        y_pred = Lasso(alpha=0.05)
                    .fit(X_train, y_train)
                    .predict(X_test)

        rmse = np.sqrt(mse(y_test, y_pred))
        rmse_fold.append(rmse)

    rmse_CV_L.append(np.mean(rmse_fold))
```

## Fitting polynomial (7)

Lasso vs OLS performance

```python
f,ax = plt.subplots(figsize=(12,6))
rmse_df['Lasso out-of-sample (CV)'] = rmse_CV_L
rmse_df.plot(ax=ax)
ax.set_yscale('log')
```

## Fitting polynomial (8)

Tuning The Lasso

```python
X = pd.concat(X_s, axis=1)

rmse_CV_lambda = []
coef_CV_lambda = []

for l in np.arange(.0001,.01,.0001): # loop over different values of lambda

    rmse_fold = []

    for train_index, test_index in kf.split(X):
        X_train, X_test = X.iloc[train_index], X.iloc[test_index]
        y_train, y_test = y.iloc[train_index], y.iloc[test_index]

        model = Lasso(alpha=l).fit(X_train,y_train)
        y_pred = model.predict(X_test)

        rmse = np.sqrt(mse(y_test, y_pred))
        rmse_fold.append(rmse)

    coef_CV_lambda.append(model.coef_)
    rmse_CV_lambda.append([l, np.mean(rmse_fold)])
```

## Fitting polynomial (9)

We search the $\lambda$ grid : 0.0001-0.01 to find the optimal $\lambda$ parameter

```python
rmse_lambda_df = pd.DataFrame(rmse_CV_lambda,columns=['lambda', 'RMSE']).set_index('lambda')

rmse_lambda_df.plot()
```

## Fitting polynomial (10)

Model for optimal lambda

```python
lambda_opt = rmse_lambda_df.idxmin()
lambda_opt
```

    RMSE    0.0022
    dtype: float64

Parameters for optimal lambda

```python
coefs = pd.DataFrame(coef_CV_lambda,columns=range(1,10),index=np.arange(.0001,.01,.0001))
print(coefs.loc[lambda_opt])
```

                   1    2         3    4    5    6    7    8    9
    0.0022 -0.077694  0.0  6.793418  0.0  0.0 -0.0 -0.0 -0.0 -0.0

## Summary causality

*Selection bias*: Issue for observational studies where treatment is correlated with baseline outcome.

*Randomization*: Enactment of treatments that are assigned randomly and thus do not suff selection biases.

## Summary supervised learning

*Prediction/postdiction*: Application of  model to estimate response/output associated with input.

*Bias-variance tradeoff*: There exist models beside OLS which can improve better at out-of-sample predictions, however, they have biased parameter estimates.

*Prediction types*:
- when the response is numeric (continuous) the problem is called *regression*
- when response is categorical (discrete) the problem is called *classification*

*MAE/RMSE*: measures of prediction accuracy for regression problems

*Cross validation*: Split data in test and training data. Train model on training data, test it on test data.

*Regularization*: A technique used to model bias in an attempt to solve overfitting problems


```python
from IPython.display import Latex
from IPython.display import Markdown as md
```

```latex
%%latex

Some words
\begin{align}\label{eq:1}
x & = y\\
z & = \beta
\end{align}

Some more words

$$\frac{1}{2}$$
```

Some words
\begin{align}\label{eq:1}
x & = y\\
z & = \beta
\end{align}

Some more words

$$\frac{1}{2}$$

```python

```


Copyright (c) 2015, 2016
[Sebastian Raschka](http://sebastianraschka.com/)
[Li-Yi Wei](http://www.liyiwei.org/)

https://github.com/1iyiwei/pyml

[MIT License](https://github.com/1iyiwei/pyml/blob/master/LICENSE.txt)

# Introduction
* What is machine learning
* Why machine learning
* Types of machine learning
* Machine learning pipeline
* Relations to other fields
* Historical perspective

# What is machine learning?
The construction and study of algorithms/programs that can  learn from data .

# Traditional programming

Steps: formulate problem $\rightarrow$ design algorithm $\rightarrow$ write program $\rightarrow$ test

The program remains invariant with different input data, unless the programmer manually changes it.

# Example: minimum finding
Problem
* Given a sequence of numbers, find the smallest one

Algorithm
* Record the currently minimum, initialized to $\infty$
* Loop through the numbers one by one
 * If this number $<$ minimum, minimum $\leftarrow$ this number

Analysis
* The time complexity of the above algorithm is $O(N)$ where $N$ is the sequence size.

```python
# a simple python program to find minimum numbers
# note that it remains the same regardless of the input data

import math

# function
def findmin(numbers):
    answer = math.inf
    for value in numbers:
        if value < answer:
            answer = value
    return answer

# main
test = [3.14, 2.2, 8, -9.2, 100000, 0]

print(findmin(test))

```

    -9.2

# Example: sorting

Problem
* Given a sequence of numbers, order them from small to large

Algorithm
* Pick one number (randomly) as anchor
* Go through each other number
 * If this number $\leq$ anchor, append it to the left sequence
 * Otherwise, append it to the right sequence
* Apply the same method recursively to the left and right sequences
* Concatenate left sequence, anchor, right sequence

```python
# code is left as an exercise
```

# Other examples
Think about the programs you have written in the past; how many of them can learn from data?

# Machine learning

The program can learn from data and change structure/behavior

The programmer still writes (part of) the program, but it is not fixed
* Models with parameters that can change with input data, like brains
* Programmer selects and initializes the model; the model parameters change with data (training via optimization)
* The trained model deals with future situations

[](http://www.wired.com/2016/03/final-game-alphago-lee-sedol-big-deal-humanity/)

# Why learning from data

Some algorithms/programs are hard/impossible to design/code manually/explicitly

The algorithms/programs might need to deal with unforseeable situations (generalization)

# Example: handwriting digit recognition

Problem
* Input: a digit represented by a $28 \times 28$ image (MNIST)
* Output: one of the digits in [0 .. 9]

Traditional programming?
* Give it a try :-)

Machine learning
* Collect data - pairs of images and labels
* Select and initialize a model; train the model (parameters) with the data
* The model, if properly trained, can recognize handwritings not in the original dataset for training

Sometimes it is much easier to say what (example data) instead of how (algorithm)
* [Soon We Won’t Program Computers. We’ll Train Them Like Dogs](http://www.wired.com/2016/05/the-end-of-code/)

# Other applications

* Self-driving cars
* Language translation
* Speech analysis & synthesis
* Spam filtering
* Recommendation systems
* Fraud detection
* Market prediction

# Types of machine learning

# Supervised learning

Given examples of inputs and corresponding desired outputs, predict outputs for future inputs
* classification, regression, time series prediction

# Classification vs. regression

## Classification: discrete output
Class labels: spam or not spam, positive or negative for disease, types of animals, etc.

## Classifying flowers based on various features

## Regression: continous output
function fitting

# Unsupervised learning

Given only inputs (without desired output examples), automatically discover representations, features, structure, etc.
* clustering, outlier detection, density estimation, dimensionality reduction

# Clustering

Put data samples into different groups

# Dimensionality reduction

Project data from a higher dimensional space into a lower dimensional space
* compression, visualization

# Reinforcement learning

Given sequences of actions of an agent and feedbacks from an environment, learn to select action sequences in a way that maximises the expected reward

* playing games
 * state: game board configuration
 * reward: expected winning chance

* self driving cars
 * state: position, direction, speed, sensors, etc.
 * reward: not hitting anything, remaining time for destination, fuel consumption, etc.

# Summary of types

Types of learning

* Supervised learning
 * Given sample inputs and outputs, learn how to predict outputs for future inputs

* Unsupervised learning
 * Given only sample inputs, learn to transform or organize them in some way

* Reinforcement learning
 * No direct inputs or outputs, learn best behavior from environment feedbacks (states, rewards)

Types of data
* Discrete/continuous $\times$ input/output
 * Applies to different types of learning above

* Discrete output
 * Classification for supervised learning
 * Clustering for unsupervised learning

* Continuous output
 * Regression for supervised learning
 * Dimensionality reduction for unsupervised learning

# Representation

## Model
We can represent the model as a function $f$, with a set of parameters $\Theta$.

Given a set of inputs $\mathbf{X}$, the model computes outcomes $\mathbf{Y}$.
$$\mathbf{Y} = f(\mathbf{X}, \Theta)$$

For example, in digit recognition, $\mathbf{X}$ and $\mathbf{Y}$ are the digit images and digit labels (0 to 9), respectively.

The parameters $\Theta$ consist of those optimized automatically and those manually picked by humans.
The latter are called hyper-parameters.

## Loss
Every machine learning task as a goal, which can be formalized as a loss function:
$$L(\mathbf{X}, \mathbf{T}, \mathbf{Y})$$
, where $\mathbf{T}$ is some form of target or auxiliary information, such as:
* labels for supervised classification
* number of clusters for unsupervised clustering
* environment for reinforcement learning

## Regularization
In addition to the objective, we often care about the simplicity of the model, for better efficiency and generalization (avoiding over-fitting).
The complexity of the model can be measured by another penalty function:
$$P(\Theta)$$
Some common penalty functions include number and/or magnitude of parameters.

## Objective
We can sum up both the loss and regularization terms as the total objective:
$$\Phi(\mathbf{X}, \mathbf{T}, \Theta) = L\left(\mathbf{X}, \mathbf{T}, \mathbf{Y}=f(\mathbf{X}, \Theta)\right) + P(\Theta)$$

During training, the goal is to optimize the parameters $\Theta$ with respect to the given training data $\mathbf{X}$ and $\mathbf{T}$:
$$argmin_\Theta \; \Phi(\mathbf{X}, \mathbf{T}, \Theta)$$
And hope the trained model with generalize well to future data.

## Example: curve fitting

Given a set of data points $\left(\mathbf{X}, \mathbf{Y}\right)$, fit a model curve to describe their relationship.

This is actually a regression problem, but we have all seen this in prior math/coding classes to serve as a good example for machine learning.

Recall $\mathbf{Y} = f(\mathbf{X}, \Theta)$ is our model.

For 2D linear curve fitting, the model is a straight line:
$y = w_1 x + w_0$, so the parameters $\Theta = \{w_0, w_1\}$.

The loss function is $L\left(\mathbf{X}, \mathbf{T}, \mathbf{Y}\right) = \sum_i \left( T^{(i)} - Y^{(i)}\right)^2 = \sum_i \left( T^{(i)} - w_1 X^{(i)} - w_0 \right)^2$.

($\mathbf{X}$ is a matrix/tensor, and each data sample is a row. We denote the ith sample/row as $\mathbf{X}^{(i)}$.)

For this simple example we don't care about regularization, thus $P(\Theta) = 0$.

The goal is to optimize $\Theta = \{w_0, w_1 \}$ with given $\left(\mathbf{X}, \mathbf{Y}\right)$ to minimize $L$.
For simple cases like this, we can directly optimize via calculus:
$$
\begin{align}
\frac{\partial L}{\partial w_0} & = 0 \\
\frac{\partial L}{\partial w_1} & = 0
\end{align}
$$

The math and coding will be left as an exercise.

# Steps for building a machine learning system

We will talk about individual steps for the rest of this course.

# Machine learning and other related fields

Data mining
* discovering (i.e. mining) useful information from large data sets

Pattern recognition
* originated from engineering, more on hand-crafted algorithms
* machine learning originated from computer science

Artificial intelligence
* machine learning is a subset
* traditional AI can be rule-based, deterministic
* machine learnign tends to be data-driven, probabilistic
 * statistics

Cognitive science
* reverse engineers the brain
* machine learning focuses on the forward process
 * understanding biology can help, but not the goal

# Deep learning

Deep learning $\subset$ neural networks $\subset$ machine learning

Algorithms existed since the 80's, but lacked sufficient computing power

Moore law enabled simple algorithms to process deep architecture and large data

Geoffrey Hinton, the godfather of ‘deep learning’—which helped Google’s AlphaGo beat a grandmaster—on the past, present and future of AI

# A DARPA Perspective on Artificial Intelligence

https://youtu.be/-O01G3tSYpU

```python
from IPython.display import YouTubeVideo
YouTubeVideo("-O01G3tSYpU")
```

# Reading
* PML - Chapter 1
* IML - Chapter 1

# Coding
* Install anaconda, git
* Review/learn python, ipynb
* See [README.md](https://github.com/1iyiwei/pyml/blob/master/code/ch01/README.md) for quick instructions

# Assignment
* [ex01](http://nbviewer.jupyter.org/github/1iyiwei/pyml/blob/master/code/ch01/ex01.ipynb)

Fin