# **Norm-Conserving Pseudopotentials**

**Authors:** Dou Du, Taylor James Baird and Giovanni Pizzi

 Go back to index

**Source code:** https://github.com/osscar-org/quantum-mechanics/blob/master/notebook/band-theory/pseudopotential.ipynb

The pseudopotential method is a technique employed to simplify the description of a system of interacting electrons and nuclei. It is used to construct an effective potential
that includes both the effects of a nucleus and of its core electrons,
allowing one to consider explicitly only the valence electrons.
This notebook illustrates a method of constructing norm-conserving pseudopotentials
and to display them interactively, together with the resulting pseudowavefunctions.

## **Goals**

* Understand why pseudopotentials are needed.
* Learn how to construct pseudopotentials using Kerker's method.
* Examine the results for various values of the principal quantum number n and of the angular quantum number l.
* Examine the effect of changing the cutoff radius.

## **Background theory**

[More on the background theory.](./theory/theory_pseudopotential.ipynb)

## **Tasks and exercises**

1. Investigate the role of the cutoff radius by varying the $R_c$ slider.

    Solution
        Move the slider for $R_c$ and press the button "Compute pseudopotential" to obtain
        the results. Check if there are values for which no solutions can be found.
        Inspect how different the pseudopotential is from the Coulomb potential.

2. Investigate how the pseudopotential changes for different values of the quantum numbers

    Solution
        Try to construct the pseudopotential for various values of n and l.
        Check what happens when constructing a pseudopotential for a nodeless wavefunction
        (e.g. $n=1$ and $l=0$, or $n=2$ and $l=1$).

3. Why do we need pseudopotentials?

    Solution
        Wavefunction oscillates rapidly in the core region. In a plane-wave approach,
        this would require a huge basis set (i.e., a huge number of plane waves) to
        be described accurately. What is most relevant, however, is that while the
        largest part of the contribution to the total energy of the system comes from
        the core electrons, these electrons are essentially frozen and do not participate
        in chemistry and the creation of bonds; the electronic structure is instead
        determined by the valence electrons. Avoiding to treat explicitly core
        electrons avoids that small relative errors on the core electrons completely
        spoil the calculation of the energy of the full system and, in particular,
        of the (small, but crucial) energy differences between different atomic
        configurations or crystalline phases.

4. What is the meaning of the norm-conservation condition?

    Solution
        The condition ensures that the total charge inside the cutoff radius $R_c$ is
        correct. However, there are more profound consequences that are implied by
        this condition: it turns out that imposing norm conservation implies also
        that the first energy derivative of the logarithmic derivatives of the
        all-electron wavefunction and the pseduowafevuntion agree at $R_c$. This
        is a very important condition for the transferability of the pseudopotential.
        It can be shown that this means that the true atom with its electrons and the
        pseudopotential generate the same phase shift when a plane wave is scattered
        into a spherical wave. A detailed discussion can be found in Section 11.4 of
        the book "Electronic Structure:
        Basic Theory and Practical Methods" by Richard M. Martin, Cambridge
        University Press (2004).

5. Are the pseudopotentials constructed with this method local?

    Solution
        As discussed earlier, we obtain a different pseudopotential for different
        values of the quantum numbers n and l. Therefore, the
        pseudopotential is not local (its action is not not just the product of
        the same a single function $V^{PS}(r)$ times the wavefunction).

## Interactive visualization
(be patient, it might take a few seconds to load)

```python
from sympy.physics.hydrogen import R_nl, E_nl
from sympy.abc import r
from sympy.functions import exp
from sympy import lambdify, diff, log
import numpy as np
import matplotlib.pyplot as plt
from sympy.solvers.solveset import linsolve
from ipywidgets import FloatSlider, Button, IntSlider, Layout, HBox, VBox, Label, Tab, Layout, Text
from scipy.optimize import newton
import matplotlib.gridspec as gridspec

%matplotlib widget
```

```python
# sn: the slider to control the quantum number n
# sl: the slider to control the quantum number l
sn = IntSlider(value=3, min=1, max=5, description="$n$")
sl = IntSlider(value=0, min=0, max=sn.value-1, description="$l$")

labeln = Label(value="(principal quantum number)")
labell = Label(value="(angular quantum number)")

#init the quantum number n and l
n = sn.value
l = sl.value
# We consider a H atom, with charge +1
Z = 1

#ho: is the Hydrogen radial wavefunction
#Ea: is the eigenvalue
#rf: make the analytical formula numerically
ho = R_nl(n, l, r, Z=Z)
Ea = E_nl(n, Z=Z)
rf = lambdify(r, ho, "numpy")

def update_functions_and_plot():
    """Update the functions and then the plot.
    """
    global ho, Ea, rf
    ho = R_nl(n, l, r, Z=Z)
    # Check that the value of the radial wavefunction at large radius is positive
    # (i.e., that the wavefunction goes to zero from positive values as r->infinity)
    # If negative: flip it! (This is because due to the formula of the polynomial
    # that we want to use for r < R_c, $\Psi(R_c)R_c$ must be positive at R_c, and thus after R_c as well).
    # Because of the form of R_nl, the sign needs to be swapped if n-l is even.
    if (n - l) % 2 == 0:
        ho = -ho
    Ea = E_nl(n, Z=Z)
    rf = lambdify(r, ho, "numpy")
    update_plot()

def nvalue_change(c):
    """Observe the change of the sn and update plot.
    """
    global n
    n = c["new"]
    sl.value = 0
    sl.max = n - 1
    update_functions_and_plot()

sn.observe(nvalue_change, names="value")

def lvalue_change(c):
    """Observe the change of the sl and upate plot.
    """
    global l
    l = c["new"]
    update_functions_and_plot()

sl.observe(lvalue_change, names="value")
```

```python
## NOTE: the widgets in this cell are currently not shown

text_l0 = Text(description = r"Lt. $\psi^{(0)}(r_c)$:")
text_l1 = Text(description = r"Lt. $\psi^{(1)}(r_c)$:")
text_l2 = Text(description = r"Lt. $\psi^{(2)}(r_c)$:")

text_r0 = Text(description = r"Rt. $\psi^{(0)}(r_c)$:")
text_r1 = Text(description = r"Rt. $\psi^{(1)}(r_c)$:")
text_r2 = Text(description = r"Rt. $\psi^{(2)}(r_c)$:")

cof_0 = Text(description = r"$\lambda_0$:")
cof_2 = Text(description = r"$\lambda_2$:")
cof_3 = Text(description = r"$\lambda_3$:")
cof_4 = Text(description = r"$\lambda_4$:")

def clear_texts():
    text_l0.value = ""
    text_l1.value = ""
    text_l2.value = ""
    text_r0.value = ""
    text_r1.value = ""
    text_r2.value = ""
    cof_0.value = ""
    cof_2.value = ""
    cof_3.value = ""
    cof_4.value = ""

output1 = VBox([HBox([text_l0, text_r0]), HBox([text_l1, text_r1]), HBox([text_l2, text_r2])]);
output2 = VBox([cof_0, cof_2, cof_3, cof_4])

tab = Tab(layout=Layout(width='700px'))
tab.children = [output1, output2]
tab.set_title(0, r"Continuity at Rc")
tab.set_title(1, r"Polynomial coeff.")

## Uncomment this if you want to also see some text boxes with the values of the wavefunction and its derivatives
## at the cutoff radius
#display(tab)
```

```python
s_rc = FloatSlider(value = 21.0, min = 1.0, max = 50, description = "$R_c$",
                   layout={'width':'300px'})

compute = Button(description="Compute pseudopotential", style={'description_width': 'initial'}, layout={'width':'200px'})

display(HBox([sn, labeln]), HBox([sl, labell]))
display(HBox([s_rc, compute]))
```

```python
img = plt.figure(tight_layout=True, figsize=(7,7))
img.canvas.header_visible = False

gs = gridspec.GridSpec(2, 1)

ax1 = img.add_subplot(gs[0, 0])
ax2 = img.add_subplot(gs[1, 0])

x1 = np.arange(0, 50.0, 0.01)
y1 = rf(x1)*x1

line_rho, = ax1.plot(x1, y1, 'r-', label="$R_{"+str(sn.value)+str(sl.value)+"}(r)r$")
ax1.fill_between(x1, y1, 0, where=x1<s_rc.value, facecolor='yellow', alpha=0.5)
ax1.set_xlim([0, 50.0])

ax1.hlines(0, 0, 50, 'k','--')

line_rc1 = ax1.axvline(s_rc.value)
line_pswf, = ax1.plot([],[],'b-', linewidth=1.5, label="$R^{PS}_{"+str(sn.value)+str(sl.value)+"}(r)r$")
ann_rc = ax1.annotate("$R_c$", xy=(s_rc.value + 1.0, rf(s_rc.value)*s_rc.value), fontsize=20)
point, = ax1.plot(s_rc.value, rf(s_rc.value)*s_rc.value, 'ko')

ann_norm1 = ax1.annotate("Yellow (squared):", xy=(250, 30), xycoords='axes points', fontsize=9,
                         bbox=dict(boxstyle='round', facecolor='yellow', alpha=0.5))
ann_norm2 = ax1.annotate("Green (squared):", xy=(250, 10), xycoords='axes points', fontsize=9,
                         bbox=dict(boxstyle='round', facecolor='green', alpha=0.5))

ann_logl = ax1.annotate("Logarithmic deriv. $\psi(r_c)^{PS}$:", xy=(50, 30), xycoords='axes points', fontsize=9,
                         bbox=dict(boxstyle='round', facecolor='blue', alpha=0.5))
ann_logr = ax1.annotate("Logarithmic deriv. $\psi(r_c)$:", xy=(50, 10), xycoords='axes points', fontsize=9,
                         bbox=dict(boxstyle='round', facecolor='red', alpha=0.5))

ax1.set_xlabel("r", fontsize = 15)
ax1.set_ylabel("$R_{nl}(r)r$", fontsize = 15)

ax1.legend(loc=1, fontsize=15)
#ax1.set_ylim([-0.3, 0.5])

x2 = np.linspace(0.001, 50, 500)
y2 = -Z/x2

ax2.plot(x2, y2, 'r--', linewidth=1.0, label="$-Z/r$")

ax2.set_xlim([0, 50.0])
#ax2.set_ylim([-1.0, 0.10])
line_rc2 = ax2.axvline(s_rc.value)
line_psv, = ax2.plot([],[], 'b-', linewidth=1.5, label="$V^{SP}(r)$")
ax2.hlines(0, 0, 50, 'k','--')

ax2.set_xlabel("r", fontsize = 15)
ax2.set_ylabel("$V$", fontsize = 15)

ax2.legend(loc=1, fontsize=15)

def update_plot():
    """ Update the plot when quantum number n and l changing.
    """
    x1 = np.arange(0, 50.0, 0.01)
    y1 = rf(x1)*x1
    line_rho.set_data([x1, y1])
    line_rc1.set_data(s_rc.value, [-1, 1])
    line_rc2.set_data(s_rc.value, [-1, 1])
    ann_rc.set_position((s_rc.value + 1.0, rf(s_rc.value)*s_rc.value))
    ax1.collections.clear()
    ax1.fill_between(x1, y1, 0, where=x1<s_rc.value, facecolor='yellow', alpha=0.5)
    ax1.hlines(0, 0, 50, 'k','--')
    line_pswf.set_data([],[])
    line_psv.set_data([],[])
    point.set_data(s_rc.value, rf(s_rc.value)*s_rc.value)
    ax1.set_ylim([y1.min()-0.04, y1.max()+0.04])
    line_rho.set_label("$R_{"+str(sn.value)+str(sl.value)+"}(r)r$")
    line_pswf.set_label("$R^{PS}_{"+str(sn.value)+str(sl.value)+"}(r)r$")
    ax1.legend(loc=1, fontsize=15)
    ax2.legend(loc=1, fontsize=15)

    #clear_texts()

def on_rc_change(b):
    """ Update the plot when the slider of the Rc changing.
    """
    x1 = np.arange(0, 50.0, 0.01)
    y1 = rf(x1)*x1
    line_rc1.set_data(s_rc.value, [-1, 1])
    line_rc2.set_data(s_rc.value, [-1, 1])
    ann_rc.set_position((s_rc.value + 1.0, rf(s_rc.value)*s_rc.value))
    ax1.collections.clear()
    ax1.fill_between(x1, y1, 0, where=x1<s_rc.value, facecolor='yellow', alpha=0.5)
    ax1.hlines(0, 0, 50, 'k','--')
    line_pswf.set_data([],[])
    line_psv.set_data([],[])
    point.set_data(s_rc.value, rf(s_rc.value)*s_rc.value)
    clear_texts()

s_rc.observe(on_rc_change, names='value')
```

```python
def compute_right_derivative(rc):
    k0 = rf(rc)
    k1 = diff(ho, r).subs(r, rc).evalf()
    k2 = diff(ho, r, 2).subs(r, rc).evalf()
    return np.array([float(k0), float(k1), float(k2)])

def compute_left_derivative(ps, rc):
    psf = lambdify(r, ps, "numpy")
    k0 = psf(rc)
    k1 = diff(ps, r).subs(r, rc).evalf()
    k2 = diff(ps, r, 2).subs(r, rc).evalf()
    return np.array([float(k0), float(k1), float(k2)])

def solver_kernel(devs, rc, l, b):
    A = np.zeros([3,4])
    A[0, :] = np.array([1, rc**2, rc**3, rc**4]);
    A[1, :] = np.array([0, 2*rc, 3*rc**2, 4*rc**3]);
    A[2, :] = np.array([0, 2, 6*rc, 12*rc**2])

    B = np.zeros(3)
    B[0] = log(devs[0]/rc**l);
    B[1] = devs[1]/devs[0] - l/rc;
    B[2] = devs[2]/devs[0] - devs[1]**2/devs[0]**2 + l/rc**2;

    B-=b*A[:, 1];
    A = np.delete(A, (1), axis=1);

    coff = np.linalg.solve(A, B);
    coff = np.insert(coff, 1, b)

    return coff

def diff_norms(b, rc, l):
    devs = compute_right_derivative(rc)
    coff = solver_kernel(devs, rc, l, b)

    ps  = r**l*exp(coff[0] + coff[1]*r**2 + coff[2]*r**3 + coff[3]*r**4)
    psf = lambdify(r, ps, "numpy")
    psr = lambdify(r, ps*ps*r*r, "numpy")
    hor = lambdify(r, ho*ho*r*r, "numpy")

    x1 = np.linspace(0, rc, 800);

    norm1 = np.sum(hor(x1))*(x1[1]-x1[0])
    norm2 = np.sum(psr(x1))*(x1[1]-x1[0])

    return float(norm1 - norm2)

def compute_norms(b, rc, l):
    devs = compute_right_derivative(rc)
    coff = solver_kernel(devs, rc, l, b)

    ps  = r**l*exp(coff[0] + coff[1]*r**2 + coff[2]*r**3 + coff[3]*r**4)
    psf = lambdify(r, ps, "numpy")
    psr = lambdify(r, ps*ps*r*r, "numpy")
    hor = lambdify(r, ho*ho*r*r, "numpy")

    x1 = np.linspace(0, rc, 800);

    norm1 = np.sum(hor(x1))*(x1[1]-x1[0])
    norm2 = np.sum(psr(x1))*(x1[1]-x1[0])

    return norm1, norm2

def compute_potential(l):
    psf = Ea - l*(l+1)/(2*r*r) + 1/(2*ho*r)*diff(ho*r, r, r)
    return lambdify(r, psf, "numpy")

def plot_ps_wavefunction(b, rc, l):
    devs = compute_right_derivative(rc)
    coff = solver_kernel(devs, rc, l, b)

    ps  = r**l*exp(coff[0] + coff[1]*r**2 + coff[2]*r**3 + coff[3]*r**4)
    psf = lambdify(r, ps*r, "numpy")

    devl = compute_left_derivative(ps, rc)

    x1 = np.linspace(0, rc, 800);
    line_pswf.set_data(x1, psf(x1));
    ax1.fill_between(x1, psf(x1), 0, where=x1<s_rc.value, facecolor='green', alpha=0.5)

    logl = diff(ps, r).subs(r, rc).evalf()/ps.subs(r, rc).evalf()
    logr = diff(ho, r).subs(r, rc).evalf()/ho.subs(r, rc).evalf()

    ann_logl.set_text("Logarithmic deriv. $\psi(r_c)^{PS}$:" + str("{:.10f}".format(logl)))
    ann_logr.set_text("Logarithmic deriv. $\psi(r_c)$:" + str("{:.10f}".format(logr)))

    #text_l0.value = str(ps.subs(r, rc).evalf())
    #text_l1.value = str(diff(ps, r).subs(r, rc).evalf())
    #text_l2.value = str(diff(ps, r, r).subs(r, rc).evalf())

    #text_r0.value = str(ho.subs(r, rc).evalf())
    #text_r1.value = str(diff(ho, r).subs(r, rc).evalf())
    #text_r2.value = str(diff(ho, r, r).subs(r, rc).evalf())

    #cof_0.value = str(coff[0])
    #cof_2.value = str(coff[1])
    #cof_3.value = str(coff[2])
    #cof_4.value = str(coff[3])

def plot_ps_potential(b, rc, l):
    devs = compute_right_derivative(rc)
    coff = solver_kernel(devs, rc, l, b)

    pf  = r**(l+1)*exp(coff[0] + coff[1]*r**2 + coff[2]*r**3 + coff[3]*r**4)
    psf = Ea - l*(l+1)/(2*r*r) + 1/(2*pf)*diff(pf, r, r)
    psfnl = lambdify(r, psf, "numpy")
    psfnr = compute_potential(l)

    x1 = np.linspace(0.001, rc, 800);
    x2 = np.linspace(rc, 50, 800);
    line_psv.set_data(np.concatenate((x1,x2)), np.concatenate((psfnl(x1),psfnr(x2))));
    ax2.set_ylim([psfnl(x1).min(axis=0)-0.05, max(psfnl(x1).max(axis=0)+0.1, 0.05)])

def compute_pseudopotential(c):
    global compute
    compute.disabled = True
    old_description = compute.description
    compute.description = "Computing..."

    try:
        try:
            b = newton(lambda x: diff_norms(x, s_rc.value, l), x0 = 0.00001, tol = 1e-10, maxiter=100)

            if abs(diff_norms(b, s_rc.value, l)) > 0.001:
                ann_norm1.set_text("No numerical solution found!");
                ann_norm2.set_text("Please change $R_c$!");
                ann_logl.set_text("");
                ang_logr.set_text("");
                return None
        except Exception:
            ann_norm1.set_text("No numerical solution found!");
            ann_norm2.set_text("Please change $R_c$!");
            ann_logl.set_text("");
            ann_logr.set_text("");
            return None
        update_plot()
        plot_ps_wavefunction(b, s_rc.value, l)
        plot_ps_potential(b, s_rc.value, l)
        norm1, norm2 = compute_norms(b, s_rc.value, l)
        ann_norm1.set_text("Yellow (squared): " + str("{:.10f}".format(norm1)))
        ann_norm2.set_text("Green (squared): " + str("{:.10f}".format(norm2)))
    finally:
        compute.disabled = False
        compute.description = old_description

compute.on_click(compute_pseudopotential)
compute_pseudopotential("init")
```

## **Legend**
(How to use the interactive visualization)

Here, we consider the wavefunctions from the solution of the
Schrödinger equation of one hydrogen atom. One can choose the
state with quantum numbers n and l via the sliders.

The position of the cutoff distance $R_c$ can be tuned with the slider.
Click the "Compute pseudopotentials" button to calculate the pseudowavefunction
and pseudopotential. The top panel shows the calculated radial
pseudowavefunction times $r$: $r R^{PS}_{nl}(r)$ (blue line) and the radial wavefunction
times r: $rR_{nl}(r)$ (red line). To fulfill the norm-conserving
condition, the green and yellow areas should be equal. The square of the area
should be 1 when $r$ goes to $+\infty$. The bottom panel shows the calculated
pseudopotential.

```python

```


# Experiment 3: Projectile Motion
### Objectives
- To learn to take good measurements
- To learn how to estimate and propagate uncertainties
- To learn how to apply the kinematic equations to projectile motion

### Equipment
- One PASCO Ballistic Pendulum apparatus with plastic projectile
- Two (2) C-Clamps
- One stopwatch
- One 30 cm ruler
- One two-meter stick
- One sheet of carbon paper
- Plain white or ruled paper
- Tape

### Safety
1. **DO NOT STAND ON LAB CHAIRS**
2. **Treat the launcher like a weapon:**
    - **NEVER fire the launcher at anyone**---even in jest.
    - **DO NOT “arm” the launcher until you’re ready to fire it.**
3. When “arming” the launcher, place the ball on the pin and press down on the ball. Trying to press down on the bare pin can hurt your hand.

# Introduction
Projectile motion is two-dimensional motion where one direction is under constant acceleration
and the other direction is unaccelerated. In this lab, your job will be to compare the prediction for the range of a projectile given by the kinematic equations with the experimentally determined ranges.

# Theory

### Kinematics
Let us recall the four important equations in kinematics. First, the definitions of velocity and acceleration in the x-direction:

\begin{equation}
v_{x} \equiv \frac{\Delta x}{\Delta t}
\tag{1}
\end{equation}

\begin{equation}
a_{x} \equiv \frac{\Delta v_{x}}{\Delta t}
\tag{2}
\end{equation}

Second, the two equations useful for objects moving under constant accelerations:

\begin{equation}
x(t) = \frac{1}{2} a_{x} t^2 + v_{o,x} t + x_{o}
\tag{3}
\end{equation}

\begin{equation}
v_{f,x}^2 - v_{o,x}^2 = 2 a_{x} \Delta x
\tag{4}
\end{equation}

The same equations can be used in the y-direction by replacing all the x’s with y’s.

In the horizontal direction (the x-direction), there is no acceleration, so that $a_x = 0 m/s^2$. However, gravity contributes a constant acceleration downwards (the –y direction) such that $a_y= -g$ where $g \equiv 9.81 \pm 0.005 m/s^2$ so that $ \delta g = 0.005 m/s^2$.

When you want to predict the distance that the projectile will go, you will need to combine two of the above equations. Because there is no acceleration in the x-direction, you can use equation ([1](#mjx-eqn)) to start. But you will need to know how long the ball is in flight for. This requires solving for time in equation ([3](#mjx-eqn)) for the y-direction. You may need to use the quadratic formula.

### Computing the “Muzzle Velocity”
One of the most important values you will need for this experiment will be the “muzzle
velocity,” $v_o$, of the launcher. One way to compute that velocity is to fire the launcher purely horizontally and use equation ([1](#mjx-eqn)). But we can also fire the launcher straight up and utilize the y version of equation ([4](#mjx-eqn)). Remember that, in our coordinate system, $a_y$ is a negative value. By measuring the height that the ball attains and knowing the value of $a_y$, we can compute the “muzzle velocity” of the launcher.

### Theoretical Range Prediction
Once the muzzle velocity has been determined, the theoretical range of the projectile can be
predicted. Equation ([1](#mjx-eqn)) states that the range is completely determined by the x-component of the launch velocity and the time in the air:

\begin{equation}
\Delta x = v_{x} (\Delta t) = v_{o} \cos{\theta} (\Delta t)
\tag{5}
\end{equation}

What remains is to determine the time, $ \Delta t$, that the ball is in the air. This is completely determined by the vertical motion governed by equation ([3](#mjx-eqn)). This equation can be rearranged as a quadratic equation in standard form:
\begin{equation}
A (\Delta t)^2 + B (\Delta t) + C = 0
\tag{6}
\end{equation}

which has a solution given by the quadratic formula:
\begin{equation}
\Delta t = \frac{-B \pm \sqrt{B^2 - 4 A C}}{2 A}
\tag{7}
\end{equation}

The positive root yeilds the predicted range for the projectile.

## Uncertainty Analysis:
### Uncertainty in Launch Velocity
It will be important to know what the uncertainties are in our computed positions and velocities. First, recall that a difference in position is a subtraction of two quantities:
\begin{equation}
\Delta y = y_{2} - y_{1}
\tag{8}
\end{equation}

Therefore, we can use the addition/subtraction rule to compute the uncertainty in the difference:
\begin{equation}
(\delta (\Delta y)^2 = (\delta (\Delta y_{2})^2 - (\delta (\Delta y_{1})^2
\tag{9}
\end{equation}

This time, the uncertainties $\delta y_{1}$ and $\delta y_{2}$
may not be the same, so we can’t further reduce this uncertainty.

The uncertainty in “muzzle velocity,” $\delta v_{o}$, depends on how it is computed. If one uses a horizontal shot and measures the distance and time it travels, then this is a straightforward application of the product/quotient rule. Looking at equation ([1](#mjx-eqn)) we have
\begin{equation}
\left(\frac{\delta v}{v}\right)^2 = \left(\frac{\delta (\Delta x)}{\Delta x}\right)^2 + \left(\frac{\delta (\Delta t)}{\Delta t}\right)^2
\tag{10}
\end{equation}

This time, the uncertainty in time, $\delta t$ , will be mostly caused by reaction time. This uncertainty is at least 0.1 s, but could be more. Use your best judgment to estimate this. The uncertainty in position will be the standard deviation of a number of trials.

When using equation ([4](#mjx-eqn)) to determine “muzzle velocity,” we arrive at the following equation:
\begin{equation}
v_{o} = \sqrt{2 g \Delta y}
\tag{11}
\end{equation}

To compute the uncertainty, we will have to use both the product/quotient and power rules. First, we rewrite the equation as $v_{o} = \sqrt{q}$ where $q = 2 g \Delta y$

Then the power rule says
\begin{equation}
\frac{\delta v_{o}}{v_{o}} = \frac{1}{2} \frac{\delta q}{q} \nonumber
\end{equation}

So now we work on $\delta q$. The product/quotient rule says
\begin{equation}
\left(\frac{\delta q}{q}\right)^2 = \left(\frac{\delta g}{g}\right)^2 + \left(\frac{\delta (\Delta y)}{\Delta y}\right)^2 \nonumber
\end{equation}

Combining them we find
\begin{equation}
\frac{\delta v_{o}}{v_{o}} = \frac{1}{2} \sqrt{(\frac{\delta g}{g})^2 + (\frac{\delta (\Delta y)}{\Delta y})^2}
\tag{12}
\end{equation}

### Uncertainty in Calculated Range
The uncertainty in the calculated range is one of the more difficult computations in this lab---especially because our standard three rules will actually underestimate this uncertainty. For this lab you may use the following relationship:
\begin{equation}
(\frac{\delta R}{R})^2 \approx 2 (\frac{\delta v_{o}}{v_{o}})^2
\tag{13}
\end{equation}

where $v_{o}$ is your launch velocity.

### Using Standard Deviations
Oftentimes, computing uncertainties can be tedious. Another method that is useful is to collect enough data so that one can trust the standard deviations in the collected data. For the purposes of this course---wherever you are allowed to do so---if you have taken at least five data points, you may use the standard deviation of those data points as your measurement uncertainty. Be aware that this number may be greater than your computed uncertainty; especially if you have few data points.

# Experimental Procedure

## Measuring the “muzzle velocity”
For this part of the experiment, you will determine the muzzle velocity of the launcher by firing the projectile vertically and measuring the maximum height that the ball attains. Equation ([4](#mjx-eqn)) can then be used to find the (initial) muzzle velocity.

1. Mount the projectile launcher to the lab table using a C-clamp (see Figure 1). Set the launcher so the protractor measures an angle of $90^o$. This ensures that the ball will fire vertically.
    - To adjust the launcher angle, loosen the bolts on the back of launcher. Be careful not to remove screws completely.
2. Clamp the two-meter stick to the side of the launcher using the second C-clamp. Ensure that the metric side faces you. Align the side of the two-meter stick to the edge of the launcher frame so that the two-meter stick is as vertical as possible.
3. Note the initial height of the ball, $y_{1}$, (indicated on the front of the launcher). Enter this into the cell below.
4. Set the ball into the launcher and use the ramrod to push ball into launcher. **Stop after the second click**.
5. Fire the ball vertically by pulling the string on the release lever. Determine the final height, $y_{2}$, by locating the center of the ball when it stops rising. (Taking a short video with your cell phone might help with this step). Enter your result in the cell below.
6. Repeat step 5 *at least* five times and enter your data for Table 1 in the cell below.
7. Use Equation ([4](#mjx-eqn)) to determine the initial velocity of the ball, $v_{o}$ when it left the launcher. This is called the ‘**muzzle velocity**’ of the launcher.

**Where does the ball begin its flight?** Is the uncertainty in $y_{1}$ the same as the uncertainty in $y_{2}$? (Translation: Do you know $y_{2}$ as well as you know $y_{1}$? Try it a few times before answering.)

```python
# Raw Data 1
# Table 1: Muzzle Velocity Data

# Import Numpy and Pandas with standard names
import pandas as pd
import numpy as np

# Create an empty numpy array to hold the raw data
raw_data_1 = np.empty((5,4))

# Set the trial number column identifiers for each Trial
raw_data_1[0][0]=1
raw_data_1[1][0]=2
raw_data_1[2][0]=3
raw_data_1[3][0]=4
raw_data_1[4][0]=5

# Create a Pandas dataframe, and convert the Trial number column to integer format
df1 = pd.DataFrame(raw_data_1, columns=["Trial",
                                        "Initial Height (m)",
                                        "Final Height (m)",
                                       "Vertical Displacement (m)"])
df1['Trial'] = df1['Trial'].astype(int)

#### Enter Raw Data Here!!!!!!!!!!!!!! ####

# Enter the values of the initial height (y1) and its uncertainty
df1['Initial Height (m)'] = [0.27,0.27,0.27,0.27,0.27]
uncertainty_y1 = 0.001

# Enter the values of the final height (y2) and its uncertainty
df1['Final Height (m)'] = [1.54,1.53,1.55,1.56,1.54]
uncertainty_y2 = 0.002

###########################################

# Calculate the vertical displacement: Δy = y2 - y1
df1['Vertical Displacement (m)'] = df1['Final Height (m)'] - df1['Initial Height (m)']

# Calculates the mean of the vertical displacement
mean_y = sum(df1['Vertical Displacement (m)'])/len(df1['Vertical Displacement (m)'])

# Calculates the uncertainty of the vertical displacement using standard deviation
uncertainty_Delta_y = np.std(df1['Vertical Displacement (m)'])

# Display the dataframe
from IPython.display import display
print ("Table 1: Muzzle Velocity Data")
display(df1)

print("𝛿y1 = %0.4fm         𝛿y2 = %0.4f" % (uncertainty_y1, uncertainty_y2))
print()
print("Mean Δy = %0.4fm     𝛿(Δy) = %0.4f" % (mean_y, uncertainty_Delta_y))
print()
```

    Table 1: Muzzle Velocity Data

      Trial
      Initial Height (m)
      Final Height (m)
      Vertical Displacement (m)

      0
      1
      0.27
      1.54
      1.27

      1
      2
      0.27
      1.53
      1.26

      2
      3
      0.27
      1.55
      1.28

      3
      4
      0.27
      1.56
      1.29

      4
      5
      0.27
      1.54
      1.27

    𝛿y1 = 0.0010m         𝛿y2 = 0.0020

    Mean Δy = 1.2740m     𝛿(Δy) = 0.0102

With this data, you should now be able to compute:

```python
# Raw Data 2

#### Enter Raw Data Here!!!!!!!!!!!!!! ####

# Initial Velocity (m/s) and its uncertainty
ini_v = 0.001
delta_ini_v = 0.002

###########################################

print(u'v\u2092 =', ini_v, "m/s")
print("𝛿"u'v\u2092 =', delta_ini_v)
print()
```

    vₒ = 0.001 m/s
    𝛿vₒ = 0.002

## The Horizontal Shot: Range Prediction and Verification and Second Measurement of the “Muzzle Velocity”
Using your value for the muzzle velocity, you should now be able to predict the range of the projectile when it is fired horizontally. Use the following procedure to set up the horizontal shot:

1. Remove the two-meter stick.
2. Loosen the two screws and rotate the launcher so that the protractor reads $\theta = 0^o$ (See Figure 2). Re-tighten the two screws.
3. Measure the vertical distance, $\Delta y$, from the bottom of the ball as labeled on your launcher to the floor. Record this value in the cell below.
4. Use Equation ([3](#mjx-eqn)) to determine the time, $\Delta t$, for a ball released from rest, ($v_{oy} = 0 m/s$), to fall the vertical distance, $\Delta y$, to the floor.
    - **Include this calculation in your Sample Calculations section.**
5. Because the horizontal and vertical components for projectile motion are independent, the time for a ball fired horizontally from the table to reach the floor is the same as that calculated in step 4. Use Equation ([5](#mjx-eqn)) to predict the horizontal distance, $\Delta x$, that the ball will travel in this time.
    - Note that the initial horizontal velocity, $v_{ox}$, will be the “muzzle velocity” found earlier. This distance is called the range, R.
    - Show your work in your Sample Calculations section.

Compute your predicted range and enter it into the cell below. **Stop here and have your instructor to check your data before continuing:**

```python
# Raw Data 3

#### Enter Raw Data Here!!!!!!!!!!!!!! ####

# Verticle distance traveled (m) and its uncertainty
Delta_y = 1.027
Unc_Delta_y = 0.005

# Predicted Range (m) and its uncertainty
Delta_x = 5
uncertainty_Delta_x = 0.002

###########################################

print("Δy = %0.03f m               𝛿(Δy) = %0.03f" % (Delta_y,Unc_Delta_y))
print("Predicted Δx = %0.03f m     𝛿(Δx_p) = %0.03f" % (Delta_x,uncertainty_Delta_x))
print("")
```

    Δy = 1.027 m               𝛿(Δy) = 0.005
    Predicted Δx = 5.000 m     𝛿(Δx_p) = 0.002

6. Place the center of the wooden “catcher” at your predicted range and tape a piece of white paper to the bottom. At the center of the paper, you might make a fiducial mark and label it with the horizontal distance from the initial firing location to the mark. This allows you to measure the distance to the actual landing point by adding (or subtracting) the distance from the mark that the ball makes to the fiducial mark.
7. Cover the white paper with a sheet of carbon paper (carbon side down) and lightly tape it down. **When you fire the ball, it should land on the carbon paper and make a black mark on the white paper.**
8. Fire the ball five times and complete Table 2 in the cell below.

**Time each shot** and use the table below to record and analyze your range data:

```python
# Raw Data 4
# Table 2: Horizontal Range Data

# Create an empty numpy array to hold the raw data
raw_data_2 = np.empty((5,4))

# Set the trial number column identifiers for each Trial
raw_data_2[0][0]=1
raw_data_2[1][0]=2
raw_data_2[2][0]=3
raw_data_2[3][0]=4
raw_data_2[4][0]=5

# Create a Pandas dataframe, and convert the Trial number column to integer format
df2 = pd.DataFrame(raw_data_2, columns=["Trial",
                                        "Range Δx (m)",
                                        "Time of Flight Δt (s)",
                                       "Velocity (m/s)"])
df2['Trial'] = df2['Trial'].astype(int)

#### Enter Raw Data Here!!!!!!!!!!!!!! ####

# Horizontal Distance Traveled (m)
df2['Range Δx (m)'] = [2.316,2.317,2.300,2.295,2.285]

# Time for each trial (s)
df2['Time of Flight Δt (s)'] = [0.5,0.5,0.5,0.5,0.5]

###########################################

# From equation 1 in the theory section
df2['Velocity (m/s)'] = df2['Range Δx (m)']/df2['Time of Flight Δt (s)']

# Display the dataframe
from IPython.display import display
display(df2)
print ("Table 2: Horizontal Range Data")
print("")
```

      Trial
      Range Δx (m)
      Time of Flight Δt (s)
      Velocity (m/s)

      0
      1
      2.316
      0.5
      4.632

      1
      2
      2.317
      0.5
      4.634

      2
      3
      2.300
      0.5
      4.600

      3
      4
      2.295
      0.5
      4.590

      4
      5
      2.285
      0.5
      4.570

    Table 2: Horizontal Range Data

## The Angled Shot: Range Prediction and Verification

1. Loosen the screws and adjust the launcher to an angle between $55^o$ and $80^o$ (Your lab instructor may suggest an angle). Retighten the screws and record your chosen angle in the cell below.
2. Measure the new vertical distance, $\Delta y$, from the bottom of the ball in the launcher to the floor. Record this value in the cell below.
3. Using your measured muzzle velocity and chosen angle, predict the new range, $\Delta x$, for the projectile. Place this value in the cell below.

Compute your predicted range and enter it into the cell below. **Stop here and have your instructor to check your data before continuing:**

```python
# Raw Data 5

#### Enter Raw Data Here!!!!!!!!!!!!!! ####

# Chosen angle value
theta = 30

# Verticle distance traveled (m) and its uncertainty
Delta_y = 1.031
Uncertainty_Delta_y = 0.005

# Predicted Range (m) and its uncertainty
Delta_x = 5
uncertainty_Delta_x = 0.002

###########################################

print("θ =", theta)
print("Δy = %0.03fm               𝛿(Δy) = %0.03f" % (Delta_y,Unc_Delta_y))
print("Predicted Δx = %0.03fm     𝛿(Δx_p) = %0.03f" % (Delta_x,uncertainty_Delta_x))
print("")
```

    θ = 30
    Δy = 1.031m               𝛿(Δy) = 0.005
    Predicted Δx = 5.000m     𝛿(Δx_p) = 0.002

4. Place the center of the wooden “catcher” at your predicted range and tape a piece of white paper to the bottom. At the center of the paper, you might make a fiducial mark and label it with the horizontal distance from the initial firing location to the mark. This allows you to measure the distance to the actual landing point by adding (or subtracting) the distance from the mark that the ball makes to the fiducial mark.
5. Cover the white paper with a sheet of carbon paper (carbon side down) and lightly tape it down. **When you fire the ball, it should land on the carbon paper and make a black mark on the white paper.**
6. Fire the ball five times and complete Table 3 in the cell below.

```python
# Raw Data 6
# Table 3. Angled Shot Range

# Create an empty numpy array to hold the raw data
raw_data_3 = np.empty((5,2))

# Set the trial number column identifiers for each Trial
raw_data_3[0][0]=1
raw_data_3[1][0]=2
raw_data_3[2][0]=3
raw_data_3[3][0]=4
raw_data_3[4][0]=5

# Create a Pandas dataframe, and convert the Trial number column to integer format
df3 = pd.DataFrame(raw_data_3, columns=["Trial",
                                        "Range Δx (m)"])
df3['Trial'] = df3['Trial'].astype(int)

#### Enter Raw Data Here!!!!!!!!!!!!!! ####

# Horizontal Distance Traveled (m)
df3['Range Δx (m)'] = [3.43, 3.456, 3.432,3.43, 3.447]

###########################################

# Calculates the average Δx
av_Delta_x = sum(df3['Range Δx (m)'])/len(df3['Range Δx (m)'])

# Calculates 𝛿(Δx) by using the standard deviation
unc_range = np.std(df3['Range Δx (m)'])

# Display the dataframe
from IPython.display import display
display(df3)
print ("Table 3. Angled Shot Range")
print("")
print("Average Δx = %0.5f m  𝛿(Δx) = %0.5f" % (av_Delta_x,unc_range))
```

      Trial
      Range Δx (m)

      0
      1
      3.430

      1
      2
      3.456

      2
      3
      3.432

      3
      4
      3.430

      4
      5
      3.447

    Table 3. Angled Shot Range

    Average Δx = 3.43900 m  𝛿(Δx) = 0.01062

***
# Your Report
### Experimental Procedure
You will need to write a few paragraphs to *carefully* describe the following things:

- Carefully describe how you set up your experiments
- For the vertical shot:
    - Explain your procedure for verifying that the ball was fired purely vertically.
    - Explain your procedure for measuring the height that the ball reached.
- For the horizontal and angled shots:
    - Explain your procedure for measuring the height of the ball.
    - Explain your procedure for measuring the horizontal travel of each shot.
- How did you make each of these measurements as accurate as possible?

Write this section so that someone else could perform the experiment by just reading this section (but do not write instructions). Talk about any problems you encountered and how you handled them.

### Data
*Report the following values and their uncertainties*:
- What was your final determination of your “muzzle velocity?” (Hint: Look at the result for your vertical shot.)
- What was the angle at which you fired the “angled” shot?
- Fill in the values in the cell below:

```python
# Table 4: Theoretical and Experimental Projectile Ranges

# Create an empty numpy array to hold the raw data
raw_data_4 = np.empty((2,4))

# Create a Pandas dataframe, and convert the Trial number column to integer format
df4 = pd.DataFrame(raw_data_4, columns=[" ",
                                        "Theoretical (m)",
                                        "Mean (m)",
                                       "Std. Deviation (m)"])
df4[' '] = ["Horizontal", "Angled"]

#### Enter Raw Data Here!!!!!!!!!!!!!! ####

# Theoretical ranges for horizontal and angled shots
df4['Theoretical (m)'] = [2.327,2.807]

# Mean of ranges for horizontal and angled shots
df4['Mean (m)'] = [2.24638,2.7602]

# Standard Deviation of ranges for horizontal and angled shots
df4['Std. Deviation (m)'] = [0.005,2.468]

###########################################

# Display the dataframe
from IPython.display import display
display(df4)
print ("Table 4: Theoretical and Experimental Projectile Ranges")
print("")
```

      Theoretical (m)
      Mean (m)
      Std. Deviation (m)

      0
      Horizontal
      2.327
      2.24638
      0.005

      1
      Angled
      2.807
      2.76020
      2.468

    Table 4: Theoretical and Experimental Projectile Ranges

### Results and Conclusion
This should be your longest section. The primary goal of this section is to compare your theoretical predictions with your experimental results. You must also explain the uncertainties and errors you encountered and their origins. Be brief, but be complete.

**This is a technical report.** Do not use the words: "about", "almost", "close to", "kind of", "roughly", or "sort of". Instead, use the proper numerical comparison (percent error or percent difference).

Use good paragraph structure to link these results together:
- You computed your muzzle velocity twice. Which way of computing it gave you the least *uncertainty*?
- Which way gave you the closest agreement with the experimentally measured ranges?
- Compare the theoretical range of the horizontal shot with the experimentally measured range. Use the % error formula and cite your errors. Is your experimental range “inside” your theoretical range plus/minus your uncertainty? If not, what does this indicate?
- Do the same for the angled shot.
- Talk about your uncertainties and errors: what caused them and how you might be able to reduce them.

### Appendix
Include your signed raw data sheet and sample calculations.


```python
%matplotlib inline

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

plt.rcParams["figure.figsize"] = (12, 9)
plt.rcParams["font.size"] = 18
```

# In Core Fuel Management

In core fuel management focuses on the study of requirements and operational considerations impacting fuel performance in the reactor core, power history, core loading patterns, and refuelling activities.

## Learning Objectives

At the end of this lesson, you will be equipped to:

- List safety constraints driving in core fuel management decisions.
- Calculate capacity and availability factors.
- Calculate the mass required for each reactor year of operation.
- Calculate core and assembly discharge burnup based on power output.
- Analyze the reactivity evolution of a core based on burnup.
- Apply burnup calculations to multiple batch cores.
- Recognize the relationship between the number of batches and the final burnup.
- Understand the goals driving choices in various fuel loading patterns.
- Apply these lessons to pebble-fuelled and liquid-fueled advanced reactor designs.
- Recognize the impact of extended burnup on fuel utilization, SWU utilization, and fuel cycle cost.
- Understand how isotopic activities can be used to determine fuel burnup.
- Calculate burnup based on key activity ratios.

## Safety Constraints

-  $\frac{P_{peak}}{P_{avg}}$, peak to average power ratio.
- $T_{max}$, maximimum core temperature.
- Departure from Nucleate Boiling Ratio (DNBR)
- $\rho$, reactivity in the core.
- $\alpha_T$, temperature coefficient of reactivity

Primarily, there is a loss of coolant accident (LOCA) peak clad temp (PCT) limit of 1205 $^\circ C$, which limits the maximum pellet linear power density to approx 48 kW/m at Hot Full Power(HFP).

- Critical Heat Flux (CHF), which denotes departure from nuclear boiling (DNB) for a PWR and Dryout for a BWR, not being exceeded during anticipated transients, which limits the maximum average fuel pin linear power density to approximately 29 kW/m at HFP.
- Fuel cladding strain limit not exceeded during anticipated transients

### Safety Variables

- Fuel enrichment
- Re-load batch size & number of assemblies
- Fuel loading pattern of fresh and partially spent fuel assemblies
- Control mechanisms

## Mass Required

The simplest possible representation of the mass of fuel that must be added into a reactor is:

\begin{align}
M(t) &= \frac{Q}{BU}
\end{align}

where
\begin{align}
M &= \mbox{mass of heavy metal (e.g., uranium) in the core }[MTHM/yr]\\
Q &= \mbox{annual thermal energy output }[GWd/yr]\\
BU &= \mbox{burnup }[GWd/MTIHM]
\end{align}

But, Q itself typically needs to be back-calculated from energy produced.

\begin{align}
Q &= \frac{P_0\cdot CF\cdot T}{\eta_{th}}
\end{align}

where
\begin{align}
P_0 &= \mbox{installed electric capacity }[GWe]\\
CF &= \mbox{capacity factor }[-]\\
T &= \mbox{time in core } [days]\\
\eta_{th} &= \mbox{thermal efficiency }[GWe/GWth]\\
\end{align}

```python
def m(q, bu):
    return q/bu

def q(p0, cf, t, eta_th):
    return p0*cf*t/eta_th

p0 = 1000 # installed electric capacity GWe
cf = 0.95 # capacity factor
t = 365 # days per year
eta_th = 0.33 # thermal efficiency GWe/GWth
bu = 50 # burnup GWd/MTIHM

print(m(q(p0, cf, t, eta_th), bu))

```

    21015.151515151512

## Capacity and Availability Factors

The capacity factor is representative of the plant's tendency to acheive its rated power capacity.

\begin{align}
CF &= \frac{\mbox{actual power generated over time T}}{\mbox{rated power potential over time T}}\\
  &=\frac{\int_0^T P(t)dt}{P_0T}\\
P(t) &= \mbox{ thermal power at time t during period T}
\end{align}

The capacity factor, integrated over time, gives Effective Full Power Days (EFPD), the equivalent number of days at full power.

\begin{align}
EFPD &= \int_0^TCF(t)dt\\
     &= \int_0^T \frac{\int_0^T P(t)dt}{P_0T}\\
\end{align}

The availability factor is always greater than the capacity factor.

\begin{align}
AF &= \frac{\mbox{time during which the reactor was operational during time period T}}{T}
\end{align}

```python
# The reactor shuts down:
# for a few days during the 10th month
# for one month during month 18
shutdowns = {10:10.1,
             18.5:19.5}

import numpy as np
def A(t, shutdowns):
    to_ret = 1.0*(t > 0)
    for start,stop in shutdowns.items():
        if start < t and t < stop:
            to_ret = 0
    return to_ret

times = np.arange(0.0, 20.0, 0.01)
hist = np.arange(0.0, 20.0, 0.01)
cf = np.arange(0.0, 20.0, 0.01)

for i in range(0, times.size):
    hist[i] = A(times[i], shutdowns)
    cf[i] = A(times[i], shutdowns)*(1.-0.1*np.random.random())

plt.plot(times, hist, label='Availability')
plt.plot(times, cf, label='Capacity')

plt.ylim([-0.5, 1.5])
plt.title('Capacity and Availabilty')
plt.xlabel('Time (months)')
plt.ylabel('Factor [-]')
plt.legend()

```

We can do a quick numeric integral to get each factor as an integral over the 20 month cycle.

\begin{align}
AF &= \frac{\int_0^{20}A(t)dt}{T}\\
CF &= \frac{\int_0^{20}P(t)dt}{P_0T}\\
\end{align}

```python
print("Availability Factor = ", hist.sum()/hist.shape[0])
print("Capacity Factor = ", cf.sum()/cf.shape[0])
```

    Availability Factor =  0.9455
    Capacity Factor =  0.898188858209

## Simple Reactivity Model
- On each cycle (1/n)th of the fuel is replaced
- Each fuel batch experiences a discharge burnup of Bd
- Each fuel batch on each cycle experiences a burnup of Bd/n
- $k_{reactor}$ is the uncontrolled multiplication factor (excess reactivity)
- $k_i$ is the infinite multiplication factor of a fuel batch (excess reactivity)

Each batch of fuel will have a different burn-up and $k_i(B)$ since each batch has been in the reactor a different length of time. The reactivity of the reactor is found by summing over the reactivities of all the batches of fuel, for n batches:

\begin{align}
k_{reactor} = \frac{1}{n}\sum_{i=1}^{n}k_i(B)
\end{align}

\begin{align}
k_i(B) = k_0 - \alpha B_n
\end{align}

- $k_0$ is the uncontrolled infinite multiplication factor of the fuel batch when it is fresh.
- $B_n$ is the burnup of the batch in a single cycle. The n refers to the number of batches that the reload scheme includes.
- $\alpha$ is a constant of proportionality with units of 1/Bn. Uniform linear depletion.
- $k_F$ is the uncontrolled infinite multiplication factor necessary to sustain a chain reaction at the end of an operating cycle

```python

def ki(k0, alpha, b):
    return k0 - alpha*b

def k(ki, n):
    return (1/n)*np.sum(ki)

n=3
k0 = 4.5
alpha = (k0 - 1)/20000
bu = np.arange(0, 30000., 1000.)
plt.plot(bu, ki(k0, alpha, bu))
plt.plot(bu, np.zeros(bu.shape), color='r')
plt.ylabel(r'$k_i(B)$')
plt.xlabel(r'$B$')
plt.title('Excess Reactivity Using Linear Depletion Model')
```

This approximation is somewhat accurate and gives an intuition for the impact of reloading on excess reactivity in the core.

## Single Cycle Refuelling

\begin{align}
k_{reactor} = k_1(B_1)
\end{align}

\begin{align}
k_1(B_1) = k_0 - \alpha B_1
\end{align}

Therefore the fuel burnup capability is:

\begin{align}
B_1 &= \frac{k_0-k_F}{\alpha}
\end{align}

## Two Cycle Refuelling

At the end of each cycle one batch of fuel has been burned for one cycle and the other batch has been burned for two cycles. Thus:

\begin{align}
k_F &= \frac{k_0 - \alpha B_2}{2} + \frac{k_0 - 2\alpha B_2}{2}\\
  &= k_0 - \frac{3\alpha B_2}{2}\\
B_2 &= \frac{2(k_0 - k_F)}{3\alpha}\\
    &= \frac{2}{3}B_1
\end{align}

- Each batch in the two cycle reload scheme is burned for $2B_2$.

So, in terms of the single cycle reload burnup:

\begin{align}
2B_2 &= 2\left(\frac{2}{3}B_1\right)\\
     &= \frac{4}{3}B_1\\
\end{align}

**This means there is 1/3 more burnup in the two cycle reload, for the same initial and final multiplication factors $k_0$ and $k_F$ (exactly the same fuel.)**

## N Cycle Reload Scheme

The relation between end-of-cycle core multiplication factor kF and the fresh fuel batch infinite multiplication factor k0 and the batch burnup in general is

\begin{align}
k_F &= k_0 - \frac{1}{n}\sum_{i=1}^{n}i\alpha B_n\\
\end{align}

Recall from your geometric series intution:
\begin{align}
\sum_{i=1}^{n}i &= \frac{n(n + 1)}{2}\\
\end{align}

Therefore:

\begin{align}
k_F &= k_0 - \left(\frac{n + 1}{2}\right)\alpha B_n\\
\end{align}

The batch burnup in a single cycle is then the result of solving for $B_n$:

\begin{align}
B_n &= \frac{2(k_0 - k_F)}{\alpha(n + 1)}
\end{align}

The discharge burnup of batch n, is the batch burnup in a cycle times the number of cycles:

\begin{align}
B_n^d &= nB_n\\
&= \frac{2n(k_0 - k_F)}{\alpha(n + 1)}\\
      &= \left(\frac{2n}{n + 1}\right)\frac{k_0 - k_F}{\alpha} \\
 &= \left(\frac{2n}{n + 1}\right)B_1 \\
\end{align}

```python
def bd(n, b1):
    num = 2*n*b1
    denom = n+1
    return num/denom

b1 = 12000
n = np.arange(1,50)
plt.plot(n, bd(n, b1))
```

### Discussion: What is the primary drawback of many batches per core?

## Fuel Loading Patterns

Various fuel loading patterns are used to acheive improved fuel utilization (higher burnup), better core control, and lower leakage to the pressure vessel.

## Many and $\infty$ Batch Reactor Designs

Infinite batch refuelling (a.k.a. online refuelling) is possible in liquid fuelled cores with online reprocessing.

What exactly is a pebble core, then, in terms of batches?

Aufiero, 2016

## Total composition evolution

\begin{align}
\frac{dN_i}{dt} &= \sum_{m=1}^{M}l_{im}\lambda_mN_m + \phi\sum_{m=1}^{M}f_{im}\sigma_mN_m - (\lambda_i + \phi\sigma_i + r_i - c_i)N_i + F_i\Big|_{i\in [1,M]}\\
\end{align}
\begin{align}
N_i &= \mbox{atom density of nuclide i}\\
M &= \mbox{number of nuclides}\\
l_{im} &= \mbox{fraction of decays of nuclide m that result in formation of nuclide i}\\
\lambda_i &= \mbox{radioactive decay constant of nuclide i}\\
\phi &= \mbox{neutron flux, averaged over position and energy}\\
f_{im} &= \mbox{fraction of neutron absorption by nuclide m leading to the formation of nuclide i}\\
\sigma_m &= \mbox{average neutron absorption cross section of nuclide m}\\
r_i &= \mbox{continuous removal rate of nuclide i from the system}\\
c_i &= \mbox{continuous feed rate of nuclide i}\\
F_i &= \mbox{production rate of nuclide i directly from fission}\\
\end{align}

## Fission Product Poisoning

**The main impact of fission product poisons is on thermal utilization**

Thus, the reactivity in the reactor can be found with knowledge of the impact on the thermal utilization factor.

\begin{align}
\rho &= \frac{k-k_0}{k}\\
&= \frac{f-f_0}{f}\\
\mbox{where } &\\
f_0\mbox{, }k_0 &= \mbox{fuel utilization in reactor in the absence of poisons}\\
f\mbox{, }k &= \mbox{fuel utilization in reactor with poisons present}
\end{align}

From our discussion of the two, four, and six-factor formulas, we know:

\begin{align}
f_0 &= \frac{\Sigma_{aF}}{\Sigma_{aF} + \Sigma_{aM}}\\
\end{align}

With poisons present, this becomes:

\begin{align}
f &= \frac{\Sigma_{aF}}{\Sigma_{aF} + \Sigma_{aM} + \Sigma_{aP}}\\
\end{align}

Thus, reactivity decreases with the introduction of a fission product poison having $\Sigma_{aP}$:

\begin{align}
\rho &= \frac{f-f_0}{f}\\
&= \frac{-\Sigma_{aP}}{\Sigma_{aF} + \Sigma_{aM}}\\
\end{align}

```python
def rho(f, f0):
    return (f-f0)/f

def f(sigaf, sigam, sigap):
    return sigaf/(sigaf + sigam + sigap)

def f0(sigaf, sigam):
    return sigaf/(sigaf + sigam)

sigaf = 10
sigam = 10

sigap = np.arange(0,50)
plt.plot(sigap, rho(f(sigaf, sigam, sigap), f0(sigaf, sigam)))

```

### Example: $^{135}Xe$

**Discussion: What is interesting about Xenon?**

$^{135}Xe$ is produced directly by fission and from the decay of iodine.

\begin{align}
\frac{dN_{xe}}{dt} &= \sum_{m=1}^{M}l_{Xem}\lambda_mN_m + \phi\sum_{m=1}^{M}f_{Xem}\sigma_mN_m - (\lambda_{Xe} + \phi\sigma_{Xe} + r_{Xe} - c_{Xe})N_{Xe} + F_{Xe}\\
 &= -\lambda_{Xe}N_{Xe}  - \sigma_{aXe}\phi N_{Xe} + \lambda_IN_I + F_{Xe}\\
 &= -\lambda_{Xe}N_{Xe}  - \sigma_{aXe}\phi N_{Xe} + \lambda_IN_I + \gamma_{Xe}\Sigma_f\phi\\
 \gamma_{Xe} &= 0.003\\
 \gamma_{I} &= 0.061\\
\end{align}

If reactor power (flux) increases, then the higher reaction rate results in a higher concentration of Iodine.

Figure 7.14 in Lamarsh.

Iodine, must then decay into Xe (Image below from Wikimedia Commons.)

Since 135Xe is the strongest thermal absorber known, it reduces reactivity. An increase in reactor power, then, can cause a massive reduction in reactivity shortly thereafter.

From nuclear-power.net

From nuclear-power.net

### Example: $^{239}Pu$

\begin{align}
\frac{dN_{Pu}}{dt} &= \sum_{m=1}^{M}l_{Pum}\lambda_mN_m + \phi\sum_{m=1}^{M}f_{Pum}\sigma_mN_m - (\lambda_{Pu} + \phi\sigma_{Pu} + r_{Pu} - c_{Pu})N_{Pu} + F_{Pu}\\
\end{align}

Let's formulate this equation together.

$$\mathrm{^{238}_{\ 92}U \ + \ ^{1}_{0}n \ \longrightarrow \ ^{239}_{\ 92}U \ \xrightarrow [23.5\ min]{\beta^-} \ ^{239}_{\ 93}Np \ \xrightarrow [2.3565\ d]{\beta^-} \ ^{239}_{\ 94}Pu}$$

- Decay of what nuclides result in the formation of $^{239}Pu$?
- Does $^{239}Pu$ decay?
- Is there a nuclide that becomes  $^{239}Pu$ after it absorbs a neutron?
- Does  $^{239}Pu$ ever absorb neutrons?
- Is  $^{239}Pu$ ever produced directly from fission?

## Burnable Poisons

- Gadolinia ($Gd_2O_3$) or erbia ($Er_2O_3$) common
- Natural Erbium consists of Er166, Er167, Er168  and Er170 primarily.  Er167 has large thermal  cross section.
- Gd is an early life burnable poison, typically gone by 10‐20 GWd
- Boron also used widely.
- Can be mixed with the fuel or a coating on the pellet.

\begin{align}
\frac{dN^P(t)}{dt} &= -g(t)\sigma_{aP}N^P(t)\phi\\
g(t) &= \frac{\mbox{average flux inside BP}}{\mbox{average flux in core}}\\
\sigma_{aP} &=\mbox{neutron absorption cross section of the BP}\\
N^P(t) &= \mbox{number of atoms of the BP at time t}
\end{align}

## Xenon Poisoning

We can define the Xenon buildup with the following two equations.

\begin{align}
\frac{dI(t)}{dt} &= -\lambda_I I(t) + \gamma_I \Sigma_f\phi(t)\\
\frac{dX(t)}{dt} &= \lambda_I I(t) + \gamma_X \Sigma_f\phi(t)-\lambda_X X - \sigma_a^X\phi(t)X(t) \\
\end{align}

The solution of these two, coupled, differential equations is:

\begin{align}
\frac{I(t)}{\Sigma_f} &= \frac{I(0)}{\Sigma_f}e^{-\lambda_I t} + \frac{\gamma_I\phi_o}{\lambda_I}\left[1- e^{-\lambda_It}\right]\\
\frac{X(t)}{\Sigma_f} &= \frac{X(0)}{\Sigma_f}e^{-(\lambda_X  + \sigma_a^X\phi_o)t} + \frac{(\gamma_I + \gamma_x)\phi_o}{\lambda_X  + \sigma_a^X\phi_o}\left[1- e^{-(\lambda_X + \sigma_a^X\phi_o)t}\right]
+ \frac{\lambda_II(0)/\Sigma_f - \gamma_I\phi_o}{\lambda_X - \lambda_I + \sigma_a^X\phi_o}{\lambda_X - \lambda_I + \sigma_a^X\phi_o}\left[e^{-\lambda_It} - e^{-(\lambda_X + \sigma_a^X\phi_o)t}\right]\\
\end{align}

From nuclear-power.net

```python

```


# Exploratory data analysis

# Purpose
Explore the data and extend/transform features.

```python
%matplotlib inline
%load_ext autoreload
%autoreload 2

import warnings
warnings.filterwarnings('ignore')
```

    The autoreload extension is already loaded. To reload it, use:
      %reload_ext autoreload

```python
import os
import matplotlib.pyplot as plt
import pandas as pd
pd.options.display.max_rows = 999
pd.options.display.max_columns = 999
pd.set_option("display.max_columns", None)
import sympy as sp

import pandas as pd
import numpy as np

# Unmark for publish quality plots
#if os.name == 'nt':
#    plt.style.use('paper.mplstyle')  # Windows

import statsmodels.api as sm
from statsmodels.sandbox.regression.predstd import wls_prediction_std
from sklearn.metrics import r2_score, mean_absolute_error
from scipy.integrate import solve_ivp
import seaborn as sns

from copy import deepcopy
import sys
sys.path.append('../../')
from src.notebook_to_latex import Equation, equation_dict
#from src import equations,symbols
import reports.paper_writing
from src.df_to_latex import LateXTable
import src.data.load as load
from src.data.transform import transform, extend
from src.visualization.visualize import plotable_dataframe
from src.visualization.polynom import model_to_string, model_to_sympy

import plotly.express as px
import plotly.graph_objects as go

from sklearn.model_selection import train_test_split
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression
from sklearn.pipeline import Pipeline
from sklearn.model_selection import cross_val_score
from sklearn.model_selection import RepeatedKFold
from sklearn.model_selection import TimeSeriesSplit
from sklearn.preprocessing import StandardScaler
from sklearn.preprocessing import PolynomialFeatures
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import f_regression

import xgboost

## Examples:

```

## Exploratory data analysis
The ship speed $V$, ship draughts $T_{aft}$ and $T_{fwd}$ were all negative in the raw data file. This was imidiatelly corrected, to be more in line with what would be expected from a more general sign convention. The data seems to have been collected in time cronological order, giving a time series of data. For a time series, measurements close to each other in time have a high correlation, as they are experiencing similar envinronmental conditions etc. This is confirmed by looking at the autocorrelation plot in Fig.[(below)](#fig_power_autocorrelation). Dead reckoning (using ship speed and heading) has been used to atempt to describe motion of the ship as seen in Fig.[(below)](#fig_dead_reckoning). The positions are given in an unknown logintude and latitude scale, as the time step between measurements is unknown. The speed of the ship is also indicated as a color gradient in this figure.

```python
raw_data = load.raw()
```

```python
display(raw_data.head())
display(raw_data.describe())
display(raw_data.dtypes)
```

      Power
      V
      T_fwd
      T_aft
      HDG
      Hs
      Tz
      D_wave
      U_wind
      V_wind

      0
      4096.859211
      23.359333
      10.25
      10.2
      225.120423
      0.336355
      4.113880
      58.287071
      -0.251960
      0.598855

      1
      4062.904838
      23.351923
      10.25
      10.2
      225.121609
      0.334025
      4.107303
      61.219389
      -0.205543
      0.537524

      2
      4105.640943
      23.304333
      10.25
      10.2
      213.803859
      0.331768
      4.100033
      64.378446
      -0.167395
      0.480271

      3
      4156.401020
      23.293167
      10.25
      10.2
      225.124531
      0.328743
      4.084423
      69.095584
      -0.153352
      0.410798

      4
      4147.498244
      23.287000
      10.25
      10.2
      225.125682
      0.326883
      4.072262
      73.970302
      -0.125637
      0.360612

      Power
      V
      T_fwd
      T_aft
      HDG
      Hs
      Tz
      D_wave
      U_wind
      V_wind

      count
      8631.000000
      8631.000000
      8631.000000
      8631.000000
      8631.000000
      8631.000000
      8631.000000
      8631.000000
      8631.000000
      8631.000000

      mean
      3188.935287
      24.278879
      11.182685
      11.093998
      254.571157
      1.666358
      7.462152
      204.735041
      2.035780
      0.747150

      std
      1332.289539
      1.001131
      0.715500
      0.701950
      70.481649
      0.813212
      1.955307
      80.514470
      4.931660
      4.261370

      min
      1000.000000
      22.436267
      10.250000
      10.160000
      0.000000
      0.158431
      2.233657
      2.680181
      -11.236499
      -14.531855

      25%
      1815.723938
      23.454343
      10.700000
      10.600000
      245.028127
      1.153389
      6.396379
      169.396098
      -0.985087
      -2.065295

      50%
      3173.939254
      24.132437
      11.000000
      10.800000
      270.018724
      1.540236
      7.497403
      205.961881
      2.740314
      0.088762

      75%
      4385.159249
      24.897164
      11.700000
      11.600000
      292.405334
      2.160576
      8.980331
      268.427660
      5.574664
      3.568319

      max
      5974.050320
      27.955649
      12.400000
      12.200000
      347.212496
      4.330742
      11.513423
      358.097469
      13.623467
      12.415640

    Power     float64
    V         float64
    T_fwd     float64
    T_aft     float64
    HDG       float64
    Hs        float64
    Tz        float64
    D_wave    float64
    U_wind    float64
    V_wind    float64
    dtype: object

## Autocorrelation

```python
from statsmodels.graphics.tsaplots import plot_acf
fig = plot_acf(raw_data['Power'].values, lags=8000);
ax = fig.get_axes()[0]
ax.set_xlabel('Lag')
ax.set_title('Power autocorrelation');

```

```python
from scipy.spatial.transform import Rotation as R
r = R.from_euler('z', raw_data['HDG'], degrees=True)
df = pd.DataFrame(index=raw_data.index)
df['u'] = raw_data['V']  ## Assuming no drift
df['v'] = 0  ## Assuming no drift
df['w'] = 0  ## Assuming no drift

df[['dX','dY','dZ']] = r.apply(df)
df[['X','Y','Z']] = df[['dX','dY','dZ']].cumsum()

```

```python
from matplotlib.collections import LineCollection
from matplotlib.colors import ListedColormap, BoundaryNorm

x = df['Y'].values
y = df['X'].values
dydx = df['u'].values

# Create a set of line segments so that we can color them individually
# This creates the points as a N x 1 x 2 array so that we can stack points
# together easily to get the segments. The segments array for line collection
# needs to be (numlines) x (points per line) x 2 (for x and y)
points = np.array([x, y]).T.reshape(-1, 1, 2)
segments = np.concatenate([points[:-1], points[1:]], axis=1)

fig, ax = plt.subplots()

# Create a continuous norm to map from data points to colors
norm = plt.Normalize(dydx.min(), dydx.max())
lc = LineCollection(segments, cmap='gray', norm=norm)
# Set the values used for colormapping
lc.set_array(dydx)
lc.set_linewidth(2)
line = ax.add_collection(lc)
fig.colorbar(line, ax=ax)
ax.set_xlim(df['Y'].min(),df['Y'].max())
ax.set_ylim(df['X'].min(),df['X'].max())

ax.annotate('start',xy=df.iloc[0][['Y','X']])
ax.annotate('end',xy=df.iloc[-1][['Y','X']])
ax.axis('equal')
ax.set_xlabel('Latitude scale')
ax.set_ylabel('Longitude scale');
```

```python
corr = plotable_dataframe(raw_data.corr().abs())

ax = sns.heatmap(corr, vmin=0, vmax=1, yticklabels=corr.index, cmap='Blues', annot=True)
fig = ax.get_figure()
```

## transform

```python
data = transform(raw_data=raw_data)

corr = plotable_dataframe(data.corr().abs())

ax = sns.heatmap(corr, vmin=0, vmax=1, yticklabels=corr.index, cmap='Blues', annot=True)
fig = ax.get_figure()
```

```python
fig,ax=plt.subplots()

data_ = data.copy()
data_.plot(y='Power', ax=ax)
ax2 = ax.twinx()
data_['-T'] = -data_['T']
data_.plot(y='-T', style='r-', ax=ax2)

```

## Extend data

```python
data_extended = extend(data=data)
```

```python
corr = plotable_dataframe(data_extended.corr().abs())
fig,ax = plt.subplots()
fig.set_size_inches(9,9)
ax = sns.heatmap(corr, vmin=0, vmax=1, yticklabels=corr.index, cmap='Blues', annot=True, ax=ax)

```

```python
data_extended.to_csv('../data/processed/data_extended.csv')
```


```python
from os import walk

import matplotlib.pyplot as plt
import seaborn as sns
sns.set(context='notebook', color_codes=True)

import numpy as np
import pandas as pd

from stats import sample_from_posterior

from pymc3_models import durw_fit, edhmm_fit
```

```python
# get the file names
f = []
for (dirpath, dirnames, filenames) in walk('data'):
    f.extend(filenames)
    break
f = np.sort(f)[1:]

# set parameters
n_subs = len(f)  # number of subjects

T = 160  # total number of trials
last = -35  # set the time point for the post-reversal phase

# input variables for inference
inp = np.zeros((T, n_subs, 3), dtype = np.int64)

# failed trials
nans = np.zeros((T, n_subs), dtype = bool)

# format data
for i,file in enumerate(f):
    sub, _ = file.split(sep='.')
    sub = int(sub[-2:])
    data = pd.read_csv(dirpath + '/' + file)
    start = data['S'][0]
    data = data.loc[:,'A':'R']

    # change choice labels from (1,2) to (0,1)
    if start == 1:
        data['A'] -= 1
    else:
        # reverse labels (1,2) to (1,0) if the initial best choice was 2
        data['A'] = 2 - data['A']

    data['R'][data['R']==-1] = 0  # map choice outcomes from {-1,1} to {0,1}
    data['T'] = 1  # set the switch variable to one (it is set to zero only for nan trials)
    nans[:,i] = data.isnull().any(axis = 1)
    data[nans[:,i]] = 0
    data = data.astype(int)
    inp[:,i,:] = data.values
```

```python
approx_durw = durw_fit(inp, nans, n_subs, last)
```

    /mnt/data/miniconda/envs/ppls/lib/python3.8/site-packages/theano/gpuarray/dnn.py:184: UserWarning: Your cuDNN version is more recent than Theano. If you encounter problems, try updating Theano or downgrading cuDNN to a version >= v5 and <= v7.
      warnings.warn("Your cuDNN version is more recent than "

  <progress value='50000' class='' max='50000' style='width:300px; height:20px; vertical-align: middle;'></progress>
  100.00% [50000/50000 23:18<00:00 Average Loss = 783.42]

    Finished [100%]: Average Loss = 783.42

```python
# sample from posterior and estimate posterior predictive log-model evidence
# for the trials excluded from model fitting
varnames = ['c0', 'beta', 'alpha', 'kappa', 'V0', 'G']
durw_sample, pplme, plike2 = sample_from_posterior(approx_durw,
                                              varnames,
                                              n_subs,
                                              inp[last:,:,0],
                                              last)

# pplme -> provides posterior predictive log-model evidence
# estimated over the 'last' trials for each participant
# from 10000 predictive samples from the posterior

# plike2 -> provides response likelihood per trial over the 'last' trials,
# averaged over 10000 predictive samples from the posterior

# uncomment for storing reults in HDF5 format
# store = pd.HDFStore('behavior_fits.h5')
# store['durw/trace'] = sample
# store['durw/pplme'] = pd.Series(pplme)
# store['durw/plike2'] = pd.DataFrame(plike2)
# store.close()
```

```python
sidx = 0  # subject index - integer value between 0 and 21

fig, axes = plt.subplots(1, 3, figsize=(15, 5));
axes[0].hist(durw_sample['alpha__{}'.format(sidx)], bins=100, histtype='stepfilled');
axes[1].hist(durw_sample['kappa__{}'.format(sidx)], bins=100, histtype='stepfilled');
axes[2].hist(durw_sample['beta__{}'.format(sidx)], bins=100, histtype='stepfilled');
```

```python
approx_edhmm = edhmm_fit(inp, nans, n_subs, last)
```

  <progress value='50000' class='' max='50000' style='width:300px; height:20px; vertical-align: middle;'></progress>
  100.00% [50000/50000 1:53:14<00:00 Average Loss = 840.55]

    Finished [100%]: Average Loss = 840.51

```python
varnames = ['c0', 'beta', 'delta', 'r', 'muA', 'muB', 'G']
edhmm_sample, pplme, plike2 = sample_from_posterior(approx_edhmm,
                                              varnames,
                                              n_subs,
                                              inp[last:,:,0],
                                              last)

# uncomment for storing results in HDF5 format
# store = pd.HDFStore('behavior_fits.h5')
# store['edhmm/trace'] = sample
# store['edhmm/pplme'] = pd.Series(pplme)
# store['edhmm/plike2'] = pd.DataFrame(plike2)
# store.close()
```

```python
sidx = 0  # subject index - integer value between 0 and 21

fig, axes = plt.subplots(1, 3, figsize=(15, 5));
axes[0].hist(edhmm_sample['delta__{}'.format(sidx)], bins=100, histtype='stepfilled');
axes[1].hist(edhmm_sample['r__{}'.format(sidx)], bins=100, histtype='stepfilled');
axes[2].hist(edhmm_sample['beta__{}'.format(sidx)], bins=100, histtype='stepfilled');
```

# Posterior predictive checks and Bayesian chi-square

Given a sequence of responses $(r_1^n, \ldots, r_T^n)$ and a sample $s$ from the posterior over response probabilities $(p_{1, s}^n,\ldots,p_{T,s}^n)$ of the $n$th participant, we will define an approximate chi-square distributed variable as

\begin{equation}
\chi_{s, n}^2(T)= \sum_{t=1}^{T} \frac{(r_t^n - p_{t,s}^n)^2}{p^n_{t,s} (1 - p^n_{t,s})}.
\end{equation}

Given $S$ samples from the posterior we obtained a posterior expected chi square with $T$ degrees of freedom as
\begin{equation}
\bar{\chi}^2_n(T) = \frac{1}{S} \sum_{s=1}^S \chi_{s, n}^2(T)
\end{equation}

In practice, participants do not always provide a valid response. Hence, some of the trials have to be excluded from the estimate of $\bar{\chi}^2_n$. We will denote with $T_n$ the valid number of trails of the $n$th participant. Hence, a p-value of the fit to the behavioural data is obtained as

\begin{equation}
\alpha_n = \Gamma_R\left( \frac{T_n}{2}, \frac{\bar{\chi}^2_n(T_n)}{2}\right)
\end{equation}
where $\Gamma_R(x, y)$ denotes regularised gamma function. Note that the Bayesian p-values are interpreted differently from their counterparts in statistics. If a model provides a good fit to the subject's data then  $\bar{\chi}^2_n(T)$ should be a likely sample from a $\chi^2_T$ distribution. Hence, we want to see p-values distributed around 0.5, as an evidence of good fit on a group level. In other words, the random variable $\bar{\chi}^2_n(T)$ should not be much smaller than a typical sample from the $\chi^2_T$ distribution (p-value < .05) nor much larger from a typical sample (p-value > .95).

```python
from scipy.stats import chi2

def chi_square_stats(responses, probs, nans):

    valid_res = (~nans).astype(np.float)
    T_n = np.sum(~nans, 0)

    chi_2_vals = np.sum(valid_res * (responses - probs[..., -1])**2/np.prod(probs, -1), -2)

    p_values = chi2.cdf(chi_2_vals, T_n)

    return chi_2_vals.mean(0), p_values.mean(0), T_n
```

```python
responses = inp[..., 0]
prob_durw = np.exp(durw_sample.loc[:, 'G__0_0_0':].values.reshape((-1,) + inp.shape[:-1] + (2,)))
prob_durw = prob_durw/prob_durw.sum(-1, keepdims=True)

prob_edhmm = np.exp(edhmm_sample.loc[:, 'G__0_0_0':].values.reshape((-1,) + inp.shape[:-1] + (2,)))
prob_edhmm = prob_edhmm/prob_edhmm.sum(-1, keepdims=True)
```

```python
sidx = 0  # subject index

fig, axes = plt.subplots(1, 2, figsize=(15, 5), sharex=True, sharey=True)

axes[0].plot(prob_durw[..., sidx, -1].T, 'b', alpha=.05);
axes[0].plot(responses[:, sidx], 'ro');
axes[0].set_xlabel('trial');
axes[0].set_title('DU-RW');

axes[1].plot(prob_edhmm[..., sidx, -1].T, 'b', alpha=.05);
axes[1].plot(responses[:, sidx], 'ro');
axes[1].set_xlabel('trial');
axes[1].set_title('ED-HMM');
```

```python
chi_2_durw, p_val_durw, Ts = chi_square_stats(responses, prob_durw, nans)
chi_2_edhmm, p_val_edhmm, Ts = chi_square_stats(responses, prob_edhmm, nans)
```

```python
fig, axes = plt.subplots(1, 2, figsize=(15, 5), sharex=True, sharey=True)

h = axes[0].hist(p_val_durw, bins=20);
axes[0].set_title('DU-RW')
axes[0].set_xlabel('p-value')

axes[1].hist(p_val_edhmm, bins=h[1])
axes[1].set_title('ED-HMM');
axes[1].set_xlabel('p-value');
```


# Lambda School Data Science Module 141
## Statistics, Probability, and Inference

## Prepare - examine what's available in SciPy

As we delve into statistics, we'll be using more libraries - in particular the [stats package from SciPy](https://docs.scipy.org/doc/scipy/reference/tutorial/stats.html).

```
#descriptive statistics - summarize and showing data in meaningful way
#inferential statistics - draw conclusions and claims to population from sample
```

```
from scipy import stats
# dir(stats)
```

```
# As usual, lots of stuff here! There's our friend, the normal distribution
norm = stats.norm()
print(norm.mean())
print(norm.std())
print(norm.var())
```

    0.0
    1.0
    1.0

```
#t oberservation requires you to tell it how many samples of data you have.
#T distribution for small datasets have shorter tails.
# And a new friend - t
t1 = stats.t(100)  # 5 is df "shape" parameter <--- try different numbers here. rule of thumb - 30 observations
print(t1.mean())
print(t1.std())
print(t1.var())
```

    0.0
    1.0101525445522108
    1.0204081632653061

*(Picture from [Wikipedia](https://en.wikipedia.org/wiki/Student's_t-distribution#/media/File:Student_t_pdf.svg))*

The t-distribution is "normal-ish" - the larger the parameter (which reflects its degrees of freedom - more input data/features will increase it), the closer to true normal.

```
t2 = stats.t(30)  # Will be closer to normal
print(t2.mean())
print(t2.std())
print(t2.var())
```

    0.0
    1.0350983390135313
    1.0714285714285714

Why is it different from normal? To better reflect the tendencies of small data and situations with unknown population standard deviation. In other words, the normal distribution is still the nice pure ideal in the limit (thanks to the central limit theorem), but the t-distribution is much more useful in many real-world situations.

History sidenote - this is "Student":

*(Picture from [Wikipedia](https://en.wikipedia.org/wiki/File:William_Sealy_Gosset.jpg))*

His real name is William Sealy Gosset, and he published under the pen name "Student" because he was not an academic. He was a brewer, working at Guinness and using trial and error to determine the best ways to yield barley. He's also proof that, even 100 years ago, you don't need official credentials to do real data science!

## Live Lecture - let's perform and interpret a t-test

We'll generate our own data, so we can know and alter the "ground truth" that the t-test should find. We will learn about p-values and how to interpret "statistical significance" based on the output of a hypothesis test. We will also dig a bit deeper into how the test statistic is calculated based on the sample error, and visually what it looks like to have 1 or 2 "tailed" t-tests.

```
# TODO - during class, but please help!
lambda_heights = [72,72,65]
import pandas as pd
df = pd.DataFrame({'heights': lambda_heights})
df.heights.mean()
```

    69.66666666666667

```
# TODO - your code here!
import random
population = []

for _ in range(1000):
  population.append(random.choice([0, 1]))

print(population)
print(len(population))
```

    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0]
    1000

```
sample = []

for _ in range(100):
  sample.append(random.choice([0, 1]))

print(sample)
print(len(sample))
```

    [0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0]
    100

```
import scipy
scipy.mean(population)
```

    0.471

```
import numpy as np

# def mean(population):
#   average = np.sum(population)/len(population)
#   return average

# print('population mean:',mean(population))
# print('sample mean:',mean(sample))
```

```
df = pd.DataFrame({'likes_coke':sample})
df.head()
df.likes_coke.mean()
```

    0.49

```
import numpy as np
```

```
df.plot.hist()
```

```
df.likes_coke.describe()
```

    count    100.000000
    mean       0.490000
    std        0.502418
    min        0.000000
    25%        0.000000
    50%        0.000000
    75%        1.000000
    max        1.000000
    Name: likes_coke, dtype: float64

```
mean(df.likes_coke)
df.likes_coke.mean()
```

    0.49

```
sample = random.sample(population, 100)
print(sample)
print(len(sample))
```

    [1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0]
    100

```
import scipy
#dir(scipy.stats)
scipy.stats.ttest_1samp(df['likes_coke'], mean(population))
#T statistic or p - value .05 or less to be statistically significant.

#p-value,is the probability for a given statistical model that,
#when the null hypothesis is true, the statistical summary
#(such as the sample mean difference between two compared groups)
#would be greater than or equal to the actual observed results.

#Artin: the t-stat is constructed by substituting the hypothesized
#value of beta1 under the null hypo(zero), the estimated slope, and it’s standard error.
```

    Ttest_1sampResult(statistic=0.3781708678390142, pvalue=0.7061133335973926)

```
import numpy as np
```

```
def mean(list):
  average = np.sum(list)/len(list)
  return average

print("Population Mean:", mean(population))
print("Sample Mean:", mean(sample))
```

    Population Mean: 0.471
    Sample Mean: 0.46

```

def variance(list):#takes each value and subtracting mean
  return(np.sum((list-mean(list))**2)/(len(list)-1))#sample variance is always n-1 degrees of freedom
variance(df.likes_coke)
df.likes_coke.var()
```

    0.2524242424242422

```

def variance(list):#average squared distance from data point and mean
  n=len(list)
  return np.sum((list-mean(list))**2)/(n-1)#-1 degree of freedom, freedom to choose any value,
  #except last one, one observation that is locked down because this dataset has to have a certain mean.

variance(df.likes_coke)
df.likes_coke.var() #there is a difference between our variance function vs dataframe version n-1
```

    0.2524242424242422

```
def stddev(list):
  var = variance(list)
  print(var)
  return np.sqrt(var)

stddev(df.likes_coke)
```

    0.2524242424242424

    0.5024183937956913

```
def stddev(list):
  var = variance(list)
  return np.sqrt(var)

stddev(df.likes_coke)
```

    0.5024183937956913

```
#The T Statistic Equation
#x bar (sample mean) - miu (null hypothesis or population mean)/standard deviation / square root of # of our observations
#sample mean - null hypothesis, might be .5 if we don't know mean, the null hypothesis also called "boring hypothesis" the thing is not interesting if our mean is not different from it. no different between two.
```

```
n=len(df.likes_coke)
t_stat = (mean(df.likes_coke) - mean(population))/(stddev(df.likes_coke)/n**(1/2))
print(t_stat)
```

    0.3781708678390142

```
scipy.stats.ttest_1samp(df['likes_coke'], mean(population))
```

    Ttest_1sampResult(statistic=0.3781708678390142, pvalue=0.7061133335973926)

\begin{align}
t=\frac{\text{variance between groups}}{\text{variance within groups}}
\end{align}

```
#how spread apart are the population from the sample/ standard deviation within our group
```

```
#https://rpsychologist.com/d3/tdist/
#qantile quantile plot shows relationship between normal vs t statistics in order to achieve the same std from mean
#when normal distribution is 2 standard deviations away, the t distribution is about 2.5 left column
```

```
#we decide if we want to do a 1tail or 2 tail t test, we calculate the area of curve under bell curve
# then we get statistical significance number
#sometimes we compare sample to population
#sometimes we compare sample to sample
#t different between two groups variance / spread given sample size, or cummulative spread of samples.
#high t - variance spread out compared
```

```
#t-score good for comparing just mean,
#smaller the sample size, the higher the tstatistic, you need up to 30.
```

```
#when is it appropriate to use 1 or 2 tailed t-tests
```

```
#confidence interval = statistic +- Z* margin of error(z score distance away from mean),
#multiplied by standard deviation of the statistic

#confidence interval = statistic +- population standard deviation of sampling distribution, but we estimate with standard error of statistic
#x + t*(s/sqrt(n)) sample standard deviation/square root of sample size
#sample mean +- z sample standard deviation/ square root of sample size. - z sample underestimates interval
#t table when you don't know standard deviation of population but you have sample
#sample mean x, +- t* sample mean/square root of sample.
#when we compute confidence interval over and over again, we get closer and closer to standard deviation of population mean.

#simple random sample srs
#if your sample is less than 10% of the population, individual observtions are considered independent
```

```
#A TWO TAILED T-TEST - two samples to each other. Difference in means of our two groups
# one variable equation represents sample to null hypothesis
#numerator - variance or spread between group
#denominator (bottom) - variance or spread of a group given the spread of a given group or groups
#mean small - results are close together
```

```
#t statistic - only good for comparing means between 2 groups
#t statistics around 2 for both z scores
```

```
#p-hacking taking only hypothesis that are significant to publish unless p value very small
#take with a grain of salt, look for reproducability
```

## Assignment - apply the t-test to real data

Your assignment is to determine which issues have "statistically significant" differences between political parties in this [1980s congressional voting data](https://archive.ics.uci.edu/ml/datasets/Congressional+Voting+Records). The data consists of 435 instances (one for each congressperson), a class (democrat or republican), and 16 binary attributes (yes or no for voting for or against certain issues). Be aware - there are missing values!

Your goals:

1. Load and clean the data (or determine the best method to drop observations when running tests)
2. Using hypothesis testing, find an issue that democrats support more than republicans with p < 0.01
3. Using hypothesis testing, find an issue that republicans support more than democrats with p < 0.01
4. Using hypothesis testing, find an issue where the difference between republicans and democrats has p > 0.1 (i.e. there may not be much of a difference)

Note that this data will involve *2 sample* t-tests, because you're comparing averages across two groups (republicans and democrats) rather than a single group against a null hypothesis.

Stretch goals:

1. Refactor your code into functions so it's easy to rerun with arbitrary variables
2. Apply hypothesis testing to your personal project data (for the purposes of this notebook you can type a summary of the hypothesis you formed and tested)

```
#null hypothesis - they vote the same.
```

```
#when is it appropriate to use a 1 tailed vs 2 tailed T-tests
```

# Resources

- https://homepage.divms.uiowa.edu/~mbognar/applets/t.html
- https://rpsychologist.com/d3/tdist/
- https://gallery.shinyapps.io/tdist/
- https://en.wikipedia.org/wiki/Standard_deviation#Sample_standard_deviation_of_metabolic_rate_of_northern_fulmars

```
congressional_Voting_R = pd.read_csv('https://archive.ics.uci.edu/ml/machine-learning-databases/voting-records/house-votes-84.data')
```

```
congressional_Voting_R.head(3)
```

      republican
      n
      y
      n.1
      y.1
      y.2
      y.3
      n.2
      n.3
      n.4
      y.4
      ?
      y.5
      y.6
      y.7
      n.5
      y.8

      0
      republican
      n
      y
      n
      y
      y
      y
      n
      n
      n
      n
      n
      y
      y
      y
      n
      ?

      1
      democrat
      ?
      y
      y
      ?
      y
      y
      n
      n
      n
      n
      y
      n
      y
      y
      n
      n

      2
      democrat
      n
      y
      y
      n
      ?
      y
      n
      n
      n
      n
      y
      n
      y
      n
      n
      y

```
import numpy as np
```

```
# congressional = congressional_Voting_R.replace('?', 0)
# congressional.head(3)
congressional_V = congressional_Voting_R.replace('?', np.nan)
congressional_V.head(3)
```

      republican
      n
      y
      n.1
      y.1
      y.2
      y.3
      n.2
      n.3
      n.4
      y.4
      ?
      y.5
      y.6
      y.7
      n.5
      y.8

      0
      republican
      n
      y
      n
      y
      y
      y
      n
      n
      n
      n
      n
      y
      y
      y
      n
      NaN

      1
      democrat
      NaN
      y
      y
      NaN
      y
      y
      n
      n
      n
      n
      y
      n
      y
      y
      n
      n

      2
      democrat
      n
      y
      y
      n
      NaN
      y
      n
      n
      n
      n
      y
      n
      y
      n
      n
      y

```
congressional = congressional_V.dropna()
congressional.head(3)
```

      republican
      n
      y
      n.1
      y.1
      y.2
      y.3
      n.2
      n.3
      n.4
      y.4
      ?
      y.5
      y.6
      y.7
      n.5
      y.8

      4
      democrat
      n
      y
      y
      n
      y
      y
      n
      n
      n
      n
      n
      n
      y
      y
      y
      y

      7
      republican
      n
      y
      n
      y
      y
      y
      n
      n
      n
      n
      n
      y
      y
      y
      n
      y

      18
      democrat
      y
      y
      y
      n
      n
      n
      y
      y
      y
      n
      y
      n
      n
      n
      y
      y

```
congressional.shape
```

    (232, 17)

```
con = congressional.replace({'n': 0, 'y': 1})
con.head(3)
```

      republican
      n
      y
      n.1
      y.1
      y.2
      y.3
      n.2
      n.3
      n.4
      y.4
      ?
      y.5
      y.6
      y.7
      n.5
      y.8

      4
      democrat
      0
      1
      1
      0
      1
      1
      0
      0
      0
      0
      0
      0
      1
      1
      1
      1

      7
      republican
      0
      1
      0
      1
      1
      1
      0
      0
      0
      0
      0
      1
      1
      1
      0
      1

      18
      democrat
      1
      1
      1
      0
      0
      0
      1
      1
      1
      0
      1
      0
      0
      0
      1
      1

```
con.columns = ['party','handicapped-infants','water-project-cost-sharing','adoption-of-the-budget-resolution','physician-fee-freeze','el-salvador-aid', 'religious-groups-in-schools', 'anti-satellite-test-ban', 'aid-to-nicaraguan-contras','mx-missile', 'immigration','synfuels-corporation-cutback','education-spending','superfund-right-to-sue', 'crime', 'duty-free-exports', 'export-administration-act-south-africa']
```

```
con.head(2)
```

      party
      handicapped-infants
      water-project-cost-sharing
      adoption-of-the-budget-resolution
      physician-fee-freeze
      el-salvador-aid
      religious-groups-in-schools
      anti-satellite-test-ban
      aid-to-nicaraguan-contras
      mx-missile
      immigration
      synfuels-corporation-cutback
      education-spending
      superfund-right-to-sue
      crime
      duty-free-exports
      export-administration-act-south-africa

      4
      democrat
      0
      1
      1
      0
      1
      1
      0
      0
      0
      0
      0
      0
      1
      1
      1
      1

      7
      republican
      0
      1
      0
      1
      1
      1
      0
      0
      0
      0
      0
      1
      1
      1
      0
      1

```
issues_by_party = con.groupby(['party']).mean()
issues_by_party = issues_by_party.T
issues_by_party.head(3)
```

      party
      democrat
      republican

      handicapped-infants
      0.588710
      0.212963

      water-project-cost-sharing
      0.451613
      0.472222

      adoption-of-the-budget-resolution
      0.854839
      0.157407

```
issues_by_party['divide'] = issues_by_party['democrat'] - issues_by_party['republican']
issues_by_party.head(3)
```

      party
      democrat
      republican
      divide

      handicapped-infants
      0.588710
      0.212963
      0.375747

      water-project-cost-sharing
      0.451613
      0.472222
      -0.020609

      adoption-of-the-budget-resolution
      0.854839
      0.157407
      0.697431

```
issues_by_party.sort_values(by = ['divide'], ascending=False)
```

      party
      democrat
      republican
      divide

      adoption-of-the-budget-resolution
      0.854839
      0.157407
      0.697431

      aid-to-nicaraguan-contras
      0.830645
      0.148148
      0.682497

      mx-missile
      0.790323
      0.138889
      0.651434

      anti-satellite-test-ban
      0.766129
      0.268519
      0.497611

      duty-free-exports
      0.596774
      0.111111
      0.485663

      handicapped-infants
      0.588710
      0.212963
      0.375747

      synfuels-corporation-cutback
      0.508065
      0.157407
      0.350657

      export-administration-act-south-africa
      0.943548
      0.666667
      0.276882

      water-project-cost-sharing
      0.451613
      0.472222
      -0.020609

      immigration
      0.532258
      0.574074
      -0.041816

      religious-groups-in-schools
      0.443548
      0.870370
      -0.426822

      superfund-right-to-sue
      0.290323
      0.842593
      -0.552270

      crime
      0.346774
      0.981481
      -0.634707

      education-spending
      0.129032
      0.851852
      -0.722820

      el-salvador-aid
      0.201613
      0.953704
      -0.752091

      physician-fee-freeze
      0.048387
      0.990741
      -0.942354

```
dem = con[con['party']=='democrat']
rep = con[con['party']=='republican']
```

```
dem.shape, rep.shape
```

    ((124, 17), (108, 17))

```
dem_f = dem.iloc[0:50]
dem_f.shape
#sample only a selection for each party. each party has the same amount of samples
```

    (50, 17)

```
rep_f = dem.iloc[0:50]
rep_f.shape
```

    (50, 17)

```
dem_f.head(3)
```

      party
      handicapped-infants
      water-project-cost-sharing
      adoption-of-the-budget-resolution
      physician-fee-freeze
      el-salvador-aid
      religious-groups-in-schools
      anti-satellite-test-ban
      aid-to-nicaraguan-contras
      mx-missile
      immigration
      synfuels-corporation-cutback
      education-spending
      superfund-right-to-sue
      crime
      duty-free-exports
      export-administration-act-south-africa

      4
      democrat
      0
      1
      1
      0
      1
      1
      0
      0
      0
      0
      0
      0
      1
      1
      1
      1

      18
      democrat
      1
      1
      1
      0
      0
      0
      1
      1
      1
      0
      1
      0
      0
      0
      1
      1

      22
      democrat
      1
      1
      1
      0
      0
      0
      1
      1
      1
      0
      0
      0
      0
      0
      1
      1

#Answer for Question 3

```
#physician-fee-freeze republicans vs democrats
scipy.stats.ttest_ind(rep_f['physician-fee-freeze'], dem_f['physician-fee-freeze'])
```

    Ttest_indResult(statistic=0.0, pvalue=1.0)

# Answer for Question 4

```
#watersharedem democrats
scipy.stats.ttest_ind(rep_f['water-project-cost-sharing'], dem_f['water-project-cost-sharing'])
```

    Ttest_indResult(statistic=0.0, pvalue=1.0)

#Answer for Question 2

```
#adoption-of-the-budget-resolution republicans
scipy.stats.ttest_ind(rep_f['adoption-of-the-budget-resolution'],dem_f['adoption-of-the-budget-resolution'])
```

    Ttest_indResult(statistic=0.0, pvalue=1.0)

```

```