```python
!pip install coremltools
```

    Collecting coremltools
      Downloading coremltools-5.1.0-cp37-none-manylinux1_x86_64.whl (1.6 MB)
         |████████████████████████████████| 1.6 MB 4.1 MB/s
    Requirement already satisfied: protobuf>=3.1.0 in /usr/local/lib/python3.7/dist-packages (from coremltools) (3.17.3)
    Requirement already satisfied: packaging in /usr/local/lib/python3.7/dist-packages (from coremltools) (21.2)
    Requirement already satisfied: tqdm in /usr/local/lib/python3.7/dist-packages (from coremltools) (4.62.3)
    Requirement already satisfied: numpy>=1.14.5 in /usr/local/lib/python3.7/dist-packages (from coremltools) (1.19.5)
    Requirement already satisfied: sympy in /usr/local/lib/python3.7/dist-packages (from coremltools) (1.7.1)
    Requirement already satisfied: six>=1.9 in /usr/local/lib/python3.7/dist-packages (from protobuf>=3.1.0->coremltools) (1.15.0)
    Requirement already satisfied: pyparsing<3,>=2.0.2 in /usr/local/lib/python3.7/dist-packages (from packaging->coremltools) (2.4.7)
    Requirement already satisfied: mpmath>=0.19 in /usr/local/lib/python3.7/dist-packages (from sympy->coremltools) (1.2.1)
    Installing collected packages: coremltools
    Successfully installed coremltools-5.1.0

```python
!pip install --force-reinstall 'scikit-learn==0.19.2'
```

    Collecting scikit-learn==0.19.2
      Downloading scikit_learn-0.19.2-cp37-cp37m-manylinux1_x86_64.whl (4.9 MB)
         |████████████████████████████████| 4.9 MB 4.1 MB/s
    Installing collected packages: scikit-learn
      Attempting uninstall: scikit-learn
        Found existing installation: scikit-learn 0.22.2.post1
        Uninstalling scikit-learn-0.22.2.post1:
          Successfully uninstalled scikit-learn-0.22.2.post1
    ERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.
    yellowbrick 0.9.1 requires scikit-learn>=0.20, but you have scikit-learn 0.19.2 which is incompatible.
    imbalanced-learn 0.4.3 requires scikit-learn>=0.20, but you have scikit-learn 0.19.2 which is incompatible.
    Successfully installed scikit-learn-0.19.2

```python
# 使用するデータのダウンロード
!git clone https://github.com/jackasser/FileMaker-coreML.git
```

    Cloning into 'FileMaker-coreML'...
    remote: Enumerating objects: 7, done.
    remote: Counting objects: 100% (7/7), done.
    remote: Compressing objects: 100% (7/7), done.
    remote: Total 7 (delta 1), reused 0 (delta 0), pack-reused 0
    Unpacking objects: 100% (7/7), done.

```python
import pandas as pd
from sklearn.linear_model import LinearRegression
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score
import coremltools
```

    /usr/local/lib/python3.7/dist-packages/sklearn/ensemble/weight_boosting.py:29: DeprecationWarning: numpy.core.umath_tests is an internal NumPy module and should not be imported. It will be removed in a future NumPy release.
      from numpy.core.umath_tests import inner1d
    WARNING:root:TensorFlow version 2.7.0 has not been tested with coremltools. You may run into unexpected errors. TensorFlow 2.5.0 is the most recent version that has been tested.
    WARNING:root:Keras version 2.7.0 has not been tested with coremltools. You may run into unexpected errors. Keras 2.2.4 is the most recent version that has been tested.

```python
%matplotlib inline

# データの読み込み
df_data = pd.read_csv("/content/FileMaker-coreML/取り込み用.csv")

```

```python
# データ確認
df_data.head()
```

      記録日
      作物PK
      気温
      湿度
      土中湿度
      元気

      0
      2021/06/28 8:50:30
      FAF90C9F-794A-46F2-A538-AA8B2897848D
      26.6
      51.68
      66
      2

      1
      2021/06/28 11:49:34
      FAF90C9F-794A-46F2-A538-AA8B2897848D
      25.0
      56.25
      54
      0

      2
      2021/06/28 12:19:40
      FAF90C9F-794A-46F2-A538-AA8B2897848D
      25.0
      55.98
      64
      1

      3
      2021/06/28 12:49:47
      FAF90C9F-794A-46F2-A538-AA8B2897848D
      25.0
      55.81
      64
      1

      4
      2021/06/28 13:19:53
      FAF90C9F-794A-46F2-A538-AA8B2897848D
      24.8
      55.47
      64
      1

```python
# データ情報
df_data.info()
```

    <class 'pandas.core.frame.DataFrame'>
    RangeIndex: 1391 entries, 0 to 1390
    Data columns (total 5 columns):
     #   Column  Non-Null Count  Dtype
    ---  ------  --------------  -----
     0   記録日     1391 non-null   object
     1   作物PK    1391 non-null   object
     2   気温      1391 non-null   float64
     3   湿度      1391 non-null   float64
     4   土中湿度    1391 non-null   int64
    dtypes: float64(2), int64(1), object(2)
    memory usage: 54.5+ KB

```python
# データの統計情報
df_data.describe()
```

      気温
      湿度
      土中湿度

      count
      1391.000000
      1391.000000
      1391.000000

      mean
      26.315457
      50.335953
      80.907261

      std
      1.278353
      25.600224
      9.738710

      min
      22.600000
      -87.000000
      53.000000

      25%
      25.300000
      38.820000
      74.000000

      50%
      26.400000
      49.000000
      80.000000

      75%
      27.200000
      62.000000
      90.000000

      max
      29.400000
      153.000000
      95.000000

```python
# ↑湿度の最小値(min）と最大値（max）がおかしい

# データを折線グラフで可視化
plt.plot(df_data.記録日, df_data.気温, color = 'red', marker = 'o')
plt.plot(df_data.記録日, df_data.湿度, color = 'blue', marker = 'o')
plt.plot(df_data.記録日, df_data.土中湿度, color = 'black', marker = 'o')
plt.show()

```

```python
# ↑湿度に外れ値があるため外れ値を削除する

# 値を大きい順にならべたときに小さい方から１０％の順番の値をqに代入
q=df_data.湿度.quantile(0.1)
# qより小さい値は外れ値として除外
new_df = df_data.query('湿度>@q')
# 湿度が100を超えて記録されているものは外れ値として除外
new_df = new_df.query('湿度<100')
```

```python
# 整形後のデータの統計情報を表示
new_df.describe()
```

      気温
      湿度
      土中湿度
      元気

      count
      1184.000000
      1184.000000
      1184.000000
      1184.000000

      mean
      26.355236
      52.078454
      81.447635
      2.833615

      std
      1.252193
      15.220931
      9.700884
      0.508775

      min
      23.300000
      22.340000
      54.000000
      0.000000

      25%
      25.400000
      42.820000
      74.000000
      3.000000

      50%
      26.400000
      50.280000
      81.000000
      3.000000

      75%
      27.300000
      61.000000
      90.000000
      3.000000

      max
      29.400000
      99.000000
      95.000000
      3.000000

```python
# ↑湿度の標準偏差が減少し、最大値と最小値が正常になった

# 整形後のデータを可視化
plt.plot(new_df.記録日, new_df.気温, color = 'red', marker = 'o')
plt.plot(new_df.記録日, new_df.湿度, color = 'blue', marker = 'o')
plt.plot(new_df.記録日, new_df.土中湿度, color = 'black', marker = 'o')
plt.show()
```

```python
# 土中湿度のデータ型を学習に使用するためにfloat６４に変更
new_df["土中湿度"]  =new_df["土中湿度"] .astype('float64')

# 訓練データとテストデータにデータを分割｜気温、湿度、土中湿度データを入力し、元気を予測する
X_train, X_test, y_train, y_test = train_test_split(new_df[['気温','湿度','土中湿度']], new_df.元気, random_state=0)

```

```python
# 線形回帰モデルで学習する（本当は分類系のモデルを使用したかったがFIleMakerでは使用できないため）
clf = LinearRegression()
clf.fit(X_train, y_train)
```

    LinearRegression(copy_X=True, fit_intercept=True, n_jobs=1, normalize=False)

```python
# 試しに気温２５度、湿度８０%、土中湿度90%の時の植物の元気を予測してみる
print(clf.predict([[25, 80,  90]]))
```

    [3.17557134]

```python
# テストデータを入力し、予測してみる
y_pred =clf.predict(X_test)
y_pred
```

    array([3.08369971, 2.53819092, 2.85235553, 3.30658811, 2.31784949,
           2.78301776, 2.8041364 , 2.37361214, 2.89116579, 2.70624794,
           2.54347322, 2.66186844, 2.58297894, 2.68634892, 2.56730945,
           3.09874473, 3.08194009, 3.17890297, 2.75861496, 2.7565602 ,
           3.2047413 , 2.42450762, 3.13674649, 2.94001471, 2.52478461,
           2.39143393, 2.78748251, 2.37689716, 3.11546434, 3.12513417,
           2.70894537, 3.33828927, 2.26505853, 2.650438  , 3.01660171,
           2.73120652, 3.11635059, 3.13512157, 2.876496  , 2.97202193,
           2.7827029 , 2.51379775, 2.54228941, 2.34587831, 2.94084804,
           2.56023287, 3.29458965, 3.15660561, 3.22271898, 2.24858055,
           2.7736607 , 3.1784765 , 3.21135196, 2.21779109, 2.86976182,
           2.6310171 , 3.23394416, 2.46893321, 2.48273333, 3.12171838,
           2.46941106, 3.3622862 , 2.73430713, 2.68756868, 3.24969047,
           2.81117948, 3.20327653, 2.74473181, 2.70881902, 2.66590262,
           2.41051725, 2.97781049, 3.00357912, 3.24226152, 2.69437395,
           3.07909188, 2.76195039, 2.79040883, 3.17326035, 2.13144368,
           3.32574779, 3.05550663, 2.69156919, 2.46888892, 2.89354058,
           3.13005491, 3.20384791, 3.20775093, 2.69708645, 2.79664286,
           3.14607192, 2.44888442, 2.88379308, 2.44030159, 2.61170981,
           3.21224535, 3.01762809, 2.28373397, 2.88330855, 2.73383101,
           2.46793547, 2.66726051, 3.32629081, 3.1836522 , 2.70862878,
           2.68085011, 3.06484074, 3.22580868, 2.75487877, 3.20327653,
           2.29337002, 3.19293759, 2.97070236, 2.79196188, 3.16557443,
           2.82031752, 2.38677428, 2.8200982 , 2.74395269, 2.11952529,
           2.43391665, 2.22012924, 3.11722396, 2.74460586, 2.52857004,
           3.19274284, 3.13045169, 2.59269549, 2.67160659, 2.72209801,
           2.65459565, 2.8737595 , 2.11578584, 3.17470511, 2.97681731,
           3.18636698, 2.5881727 , 3.33828927, 2.83427453, 2.8654944 ,
           3.2345573 , 2.41950495, 2.48665037, 2.7973335 , 2.89766362,
           2.96806937, 3.05075938, 3.31419225, 2.50396637, 2.95193253,
           2.39769511, 2.67121534, 2.80235489, 3.18676363, 3.20656098,
           2.68723762, 2.88750641, 2.92771046, 1.95464981, 2.17236068,
           2.74501672, 2.48477576, 3.11835215, 3.25026726, 2.75153681,
           2.90649045, 2.82763048, 2.92346006, 2.16793581, 2.76886303,
           2.90546322, 2.2277199 , 3.05063558, 3.08980075, 2.69160029,
           2.77019676, 3.17010918, 2.32948009, 3.33979408, 3.07069489,
           2.7035045 , 2.24519429, 2.65477753, 2.73198362, 2.52526557,
           3.07993854, 3.02790697, 2.83549825, 3.29908407, 2.59583161,
           3.23321263, 2.98605232, 2.86376259, 2.6707338 , 2.73313765,
           2.65169564, 2.67591943, 2.13302856, 3.20630617, 3.13923936,
           2.78979027, 2.67643165, 2.80327283, 2.75949927, 3.14383387,
           2.5531848 , 3.17925958, 3.03562578, 2.83042192, 3.33229004,
           2.71595737, 3.24980561, 2.90466907, 3.09989946, 2.68653729,
           2.96327983, 2.40477198, 3.11120471, 2.69760052, 3.20773091,
           3.07604266, 2.30874352, 2.57448329, 2.86987285, 3.19575246,
           2.95448602, 3.10573756, 3.01166889, 2.96117819, 2.24115657,
           3.20927576, 3.07287118, 3.23760696, 2.92292784, 3.18676363,
           3.16923722, 2.62252316, 3.2070333 , 3.00860092, 3.20024688,
           2.70718346, 3.10183925, 3.12601193, 3.17326035, 2.31270787,
           3.1643351 , 3.15284273, 3.15165449, 3.05749754, 2.72981813,
           3.3442885 , 2.31628773, 3.13242852, 3.18593029, 3.06281421,
           2.83822947, 2.54118591, 2.34696646, 3.0525971 , 2.40900207,
           2.35100432, 3.21381022, 2.93043446, 2.63259999, 2.70033314,
           2.77765228, 2.64427819, 2.82058299, 3.13991254, 3.22750825,
           2.88926433, 2.18362135, 3.16557443, 2.79070951, 3.02114908,
           3.36678062, 2.49791646, 2.589075  , 2.96669589, 2.91311832,
           2.73190838, 3.20325651, 2.7041541 , 2.30412856, 3.24283846,
           3.12387636, 2.5294884 , 3.11964714, 2.3370496 , 2.9573331 ,
           2.35417565, 2.28356786, 2.63499394, 2.43784521, 3.13062177,
           2.89022005])

```python
# 精度を検証する（予測された値を四捨五入したものと正解データを比較する）
accuracy_score(y_pred.round().astype(int), y_test)
```

    0.9054054054054054

```python
# ↑95%の精度を誇る機械学習モデル作成！
```

```python
# CoreMLに変換｜入力変数名を["temp","humid","modhumid"]とします
coreml_model = coremltools.converters.sklearn.convert(clf,["temp","humid","modhumid"])
coreml_model.save( 'my_model.mlmodel')
```

```python

```

```python
# ファイルをダウンロード
from google.colab import files
files.download('my_model.mlmodel')
```

    <IPython.core.display.Javascript object>

    <IPython.core.display.Javascript object>

```python

```


```python
from IPython.display import HTML

# Cell visibility - COMPLETE:
#tag = HTML('''<style>
#div.input {
#    display:none;
#}
#</style>''')
#display(tag)

#Cell visibility - TOGGLE:
tag = HTML('''
<p style="text-align:right">
Toggle cell visibility <a href="javascript:code_toggle()">here</a>.</p>''')
display(tag)
```

Toggle cell visibility here.

## Integriranje polinomov

Primer je namenjen predstavitvi nekaterih zanimivih vidikov, ki zadevajo integral polinomske funkcije.

Za izbrano polinomsko funkcijo $f(x)$, ki jo definiramo z uporabo drsnikov, ki pripadajo posameznim koeficientom, se izračuna nedoločen integral. Le-ta se nato predstavi v grafični obliki in v obliki matematičnega zapisa.

Dodatno lahko z nastavljanjem spodnje in zgornje meje integrala (z uporabo namenskih drsnikov) določimo površino pod krivuljo, ki se nato prikaže na grafu; hkrati se izračuna tudi njena vrednost. Prosim upoštevajte, da mora biti spodnja meja manjša od zgornje, da je določeni integral veljaven.

```python
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Polygon
import sympy as sym
from IPython.display import Latex, display, clear_output, Markdown # For displaying Markdown and LaTeX code
from ipywidgets import widgets
from ipywidgets import interactive
import matplotlib.patches as mpatches
from scipy.integrate import quad
from IPython.display import HTML

red_patch = mpatches.Patch(color='red', label='$f(x)$')
blue_patch = mpatches.Patch(color='blue', label='nedoločeni integral $f(x)$')
gray_patch = mpatches.Patch(color='lightgray', label='površina pod krivuljo')
XLIM = 10
YLIM = 30

x = sym.symbols('x')

# Polynomial coeficients
a = 0
b = 0
c = 0
d = 0
e = 0
C = 0

style = {'description_width': 'initial'}

# Sliders
fs_a = widgets.FloatSlider(description='$a$', min=-10.0, max=10.0, step=0.5, continuous_update=False)
fs_b = widgets.FloatSlider(description='$b$', min=-10.0, max=10.0, step=0.5, continuous_update=False)
fs_c = widgets.FloatSlider(description='$c$',min=-10.0, max=10.0, step=0.5, continuous_update=False)
fs_d = widgets.FloatSlider(description='$d$',min=-10.0, max=10.0, step=0.5, continuous_update=False)
fs_e = widgets.FloatSlider(description='$e$',min=-10.0, max=10.0, step=0.5, continuous_update=False)
w_C = widgets.FloatSlider(description='$C$',min=-10.0, max=10.0, step=0.5, continuous_update=False)
lower_limit = widgets.FloatSlider(description='Spodnja meja integrala:',min=-10.0, max=10.0, step=0.5, continuous_update=False,style=style)
upper_limit = widgets.FloatSlider(description='Zgornja meja integrala:',min=-10.0, max=10.0, step=0.5, continuous_update=False,style=style)

# Mathematical notation of a specific (user-defined) polynomial, shown as Markdown
fourth_order = "e + d * x + c * x ** 2 + b * x ** 3 + a * x ** 4"
third_order = "d + c * x + b * x ** 2 + a * x ** 3"
second_order = "c + b * x + a * x ** 2"
first_order = "b + a * x"
zero_order = "a"
tf = sym.sympify(fourth_order)
w_mark = Markdown('$%s$' %sym.latex(tf))

# General mathematical notation of a polynomial (shown in Label widget)
fourth_order_html = "$f(x)=ax^4$ + $bx^3$ + $cx^2$ + $dx$ + $e$"
third_order_html = "$f(x)=ax^3$ + $bx^2$ + $cx$ + $d$"
second_order_html = "$f(x)=ax^2$ + $bx$ + $c$"
first_order_html = "$f(x)=ax$ + $b$"
zero_order_html = "$f(x)=a$"
w_funLabel = widgets.Label(layout=widgets.Layout(width='40%', margin='0px 0px 0px 50px'),)

dd_order = widgets.Dropdown(
    options=['4', '3', '2', '1', '0'],
    value='4',
    description='Izberi stopnjo polinoma [0-4]:',
    disabled=False,
    style = {'description_width': 'initial'},
)

def dropdown_eventhandler(change):
    fs_a.layout.visibility = 'hidden'
    fs_b.layout.visibility = 'hidden'
    fs_c.layout.visibility = 'hidden'
    fs_d.layout.visibility = 'hidden'
    fs_e.layout.visibility = 'hidden'

    if (dd_order.value == '4'):
        fs_a.layout.visibility = 'visible'
        fs_a.description = '$a$'
        fs_b.layout.visibility = 'visible'
        fs_b.description = '$b$'
        fs_c.layout.visibility = 'visible'
        fs_c.description = '$c$'
        fs_d.layout.visibility = 'visible'
        fs_d.description = '$d$'
        fs_e.layout.visibility = 'visible'
        fs_e.description = '$e$'
        w_funLabel.value=fourth_order_html
    if (dd_order.value == '3'):
        fs_a.value = 0
        fs_b.layout.visibility = 'visible'
        fs_b.description = '$a$'
        fs_c.layout.visibility = 'visible'
        fs_c.description = '$b$'
        fs_d.layout.visibility = 'visible'
        fs_d.description = '$c$'
        fs_e.layout.visibility = 'visible'
        fs_e.description = '$d$'
        w_funLabel.value=third_order_html
    if (dd_order.value == '2'):
        fs_a.value = 0
        fs_b.value = 0
        fs_c.layout.visibility = 'visible'
        fs_c.description = '$a$'
        fs_d.layout.visibility = 'visible'
        fs_d.description = '$b$'
        fs_e.layout.visibility = 'visible'
        fs_e.description = '$c$'
        w_funLabel.value=second_order_html
    if (dd_order.value == '1'):
        fs_a.value = 0
        fs_b.value = 0
        fs_c.value = 0
        fs_d.layout.visibility = 'visible'
        fs_d.description = '$a$'
        fs_e.layout.visibility = 'visible'
        fs_e.description = '$b$'
        w_funLabel.value=first_order_html
    if (dd_order.value == '0'):
        fs_a.value = 0
        fs_b.value = 0
        fs_c.value = 0
        fs_d.value = 0
        fs_e.layout.visibility = 'visible'
        fs_e.description = '$a$'
        w_funLabel.value=zero_order_html

dd_order.observe(dropdown_eventhandler, names='value')

# Functions
def polynomial_function(X_quad, X_cubed, X_squared, X, const, x):
    return const + X * x + X_squared * x ** 2 + X_cubed * x ** 3 + X_quad * x ** 4

def fun(x):
    global a, b, c, d, e
    return e + d * x + c * x ** 2 + b * x ** 3 + a * x ** 4

def f_integral(fx):
    if not fx.is_zero:
        return sym.integrate(fx, x)
    else:
        return ""

def convert(base_text, ss):
    if ss != "":
        tf = sym.sympify(ss)
        display(Markdown(base_text + '$%s$' %sym.latex(tf)))

# Plotting
def plot_limits(X_quad, X_cubed, X_squared, X, const, ax, a_limit, b_limit):
    ix = np.linspace(a_limit, b_limit)
    iy = polynomial_function(X_quad, X_cubed, X_squared, X, const, ix)
    verts = [(a_limit, 0), *zip(ix, iy), (b_limit, 0)]
    poly = Polygon(verts, facecolor='0.9', edgecolor='0.5')
    ax.add_patch(poly)

def plot_function(X_quad, X_cubed, X_squared, X, const, C, llimit, ulimit):
    global a, b, c, d, e, output, x
    a = X_quad
    b = X_cubed
    c = X_squared
    d = X
    e = const
    fig = plt.figure(figsize=(12,6))
    ax = fig.add_subplot(1, 1, 1)
    # Draw x and y axes
    plt.axhline(y=0,color='k',linewidth=0.5)
    plt.axvline(x=0,color='k',linewidth=0.5)
    # Plot input function
    x_p = np.linspace(-XLIM, XLIM, num=1000)
    y_p = polynomial_function(X_quad, X_cubed, X_squared, X, const, x_p)
    plt.plot(x_p, y_p, 'r-')

    # Plot indefinite integral of the input function
    integ = f_integral(fun(x))
    #integ = integ + str(C)
    if integ != "":
        if C < 0:
            integ = str(integ) + "-" + str(abs(C))
        else:
            integ = str(integ)+ "+" + str(C)
        f_integrate = sym.lambdify(x, integ) # from str to function
        x_p = np.linspace(-XLIM, XLIM, num=1000)
        y_p = f_integrate(x_p)
        ax.plot(x_p, y_p, 'b-', linewidth=2)

    # Plot integral limits (area under curve)
    if ulimit < llimit:
        display(Markdown('Izbrani vrednosti zgornje in spodnje meje intervala nista ustrezni.'))
        res = "";
    else:
        plot_limits(X_quad, X_cubed, X_squared, X, const, ax, llimit, ulimit)
        res, err = quad(fun, llimit, ulimit)

    plt.grid(True)
    plt.xlim(-XLIM, XLIM)
    plt.ylim(-YLIM, YLIM)
    plt.xlabel('$x$')
    plt.ylabel('$f(x)$, nedoločeni integral $f(x)$')
    plt.legend(handles=[red_patch, blue_patch, gray_patch])
    plt.show()

    convert("Vhodna funkcija $f(x)$: ", fun(x))
    if integ != "":
        if C == 0:
            integ_str = str(integ) + "+ C"
        else:
            integ_str = str(integ)
        convert("Nedoločeni integral $f(x)$: ", integ_str)
    if res != "":
        display(Markdown('Površina pod krivuljo: ' + str(res)))

w_funLabel.value=fourth_order_html
control_widgets = widgets.HBox()
control_widgets.children=[dd_order, w_funLabel]
display(control_widgets)
interactive(plot_function, const=fs_e, X=fs_d, X_squared=fs_c, X_cubed=fs_b, X_quad = fs_a, C = w_C, llimit=lower_limit, ulimit=upper_limit)

```

    HBox(children=(Dropdown(description='Izberi stopnjo polinoma [0-4]:', options=('4', '3', '2', '1', '0'), style…

    interactive(children=(FloatSlider(value=0.0, continuous_update=False, description='$a$', max=10.0, min=-10.0, …

```python

```


```python
%matplotlib inline

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

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

# Enrichment

Enrichment is the stage at which natural uranium is processed to acheive higher concentrations of fissile $^{235}U$ isotopes.

## Learning Objectives

- Recall the isotopic content of natural uranium.
- Identify target enrichments for various applications.
- Distinguish among enrichment technologies.
- Discuss the history of enrichment technology development.
- Calculate separation potentials.
- Quantify the relationship between feed, product, and tails during enrichment.
- Calculate SWU, Feed, and Waste Factors.
- Explain the physical and chemical mechanisms of various enrichment technologies.
- Discuss the nonproliferation implications of enrichment technology export control.

## Natural Uranium

Natural uranium, $U_{nat}$ is uranium found in nature. By weight it contains :

- 0.711\% $^{235}U$
- 99.284\% $^{238}U$
- trace amount (0.0055\%) of $^{234}U$

The halflife of 238U is different (longer) than 235U, so the ratio changes over time. (Where time is measured in millions of years.)

## Enrichment Level

_Commercial LWR Fuel_: Commercial nuclear fuel for light water reactors is typically in the range of 3-5%

_Research Reactor Fuel_: Research reactor fuel (such as TRIGA reactors in many universities) was previously often as high as 40%. However, a downblending effort was undertaken nationally and the fuel is now much less enriched.

_Small Modular Reactor Fuel_: Small modular reactors often boast long running times, such that the fuel rarely needs to be replaced. Though these typically rely on breeding for long term sustainability, the initial enrichment is usually relatively high in these designs.

_Navy Fuel_: Classified, but high.

_Weapons_: Very high. This varies by weapon type. Does anyone recall the $^{325}U$ enrichment of the Godiva device? (_Hint_: It was 30cm in diameter.)

```python
enr_sources = {'natural U':0.007,
               'recycled U': 0.01}

enr_uses = {'CANDU':0.007,
            'LWR': 0.045,
            'Research':0.20,
            'Weapons': 0.9}

import collections
od_enr_uses = collections.OrderedDict(sorted(enr_uses.items()))
```

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

x=[]
y=[]
delta_x=0.02
delta_y=-0.02

for k,v in od_enr_uses.items():
    x.append(v)
    y.append(v)
    plt.text(v + delta_x, v + delta_y, k)
t = x
area = 60*np.pi
plt.scatter(x, y, s=area, c=t, cmap='viridis', alpha=0.6)
plt.axis([0,1,0,1])
plt.xlabel('Enrichment $(\% ^{235}U)$')
plt.ylabel('Effort $(SWU)$')
plt.title('Enrichment for various uses')
plt.plot(0.007*np.ones(2), 'g--')
plt.text(0.5, 0.007, 'Natural U', color='g',)

plt.show()
```

## Gaseous Diffusion

$$kT = \frac{1}{2}mv^2$$

```python
from scipy import constants as sc
sc.Boltzmann
import scipy
scipy.constants.unit('Boltzmann constant')

import numpy as np
k= sc.Boltzmann

def f_to_k(f):
    return (f + 459.67)*5./9.

T = np.arange(300.0, 700.)

def vel(temp, m):
    import math
    k= sc.Boltzmann
    return math.sqrt(2*k*temp/m)

v_235 = np.arange(0.0, 400.0)
v_238 = np.arange(0.0, 400.0)
ratio = np.arange(0.0, 400.0)

m_235 = 235
m_238 = 238

for i in range(0, 400):
    v_235[i] = vel(T[i], m_235)
    v_238[i] = vel(T[i], m_238)
    ratio[i] = v_235[i]/v_238[i]

plt.plot(v_235, 'g--')
plt.plot(v_238, 'r--')
```

```python
f_to_k(700.)
```

    644.2611111111112

```python
plt.plot(ratio)
```

$^{238}UF_6$ and $^{235}UF_6$ have different effusive rates. This is used to separate them through a porous barrier.

$$\frac{V_L}{V_H} = \sqrt{\frac{M_H}{M_L}} = \alpha$$

In the case of $^{238}UF_6$ and $^{235}UF_6$:

$$M_H = M_{^{238}U} + 6\times M_F$$
$$\Rightarrow M_H = 238 + 6\times 19$$

$$M_L = M_{^{235}U} + 6\times M_F$$
$$\Rightarrow M_H = 235 + 6\times 19$$

So, $\alpha$ is the separative capacity of a single gaseous diffusion stage.

### Math of Enrichment

\begin{align}
F &= \mbox{rate of feed material (per time)}\\
P &= \mbox{rate of enriched product material (per time)}\\
W &= \mbox{rate of waste material (tails)}\\
x_f &= \mbox{weight percent of the target isotope in feed }\\
x_p &= \mbox{weight percent of the target isotope in product}\\
x_w &= \mbox{weight percent of the target isotope remains in waste}\\
\end{align}

The core equations in the math of enrichment are these two:

\begin{align}
F &= P + W\\
x_fF &= x_pP + x_wW\\
\end{align}

We try to make these more useful by defining the following derived equations.

\begin{align}
FF &= \frac{F}{P}\\
   &= \frac{x_p - x_w}{x_f - x_w}\\
WF &= \frac{W}{P}\\
   &= \frac{x_p - x_f}{x_f - x_w}
\end{align}

```python
def feed(P, W):
    return P + W

def prod(F, W):
    return F - W

def tail(P, F):
    return F - P

def ff(x_p, x_w, x_f):
    num = x_p - x_w
    denom = x_f - x_w
    return num/denom

def wf(x_p, x_w, x_f):
    num = x_p - x_f
    denom = x_f - x_w
    return num/denom

def x_p(f, p, x_f, x_w):
    return (f/p)*(x_f - x_w) + x_w

def x_w(f, p, x_f, x_p):
    num = x_p - (f/p)*x_f
    denom = 1-(f/p)
    return num/denom

x_p = np.arange(0.1, 0.5, 0.1)
to_plot = np.arange(1.,5.)
for i in range(0,4):
    to_plot[i] = ff(x_p[i], 0.001, 0.00711)

plt.plot(x_p, to_plot, label="FF")
plt.plot(x_p, to_plot - 1, label="WF")
plt.xlabel("Desired enrichment (%)")
plt.legend(loc=2)
```

### Separation Potentials

\begin{align}
V(x_i) &= (2x_i -1)ln\left(\frac{x_i}{1-x_i}\right)\\
\Rightarrow V(x_f) &= (2x_f -1)ln\left(\frac{x_f}{1-x_f}\right)\\
\end{align}

```python
from math import log
def v(x):
    """separation potential"""
    return 2*x - 1 * log(x/(1-x))

x_i = np.arange(0.001, 0.9, 0.001)
to_plot = [v(x) for x in x_i]
plt.plot(x_i, to_plot)
plt.xlabel(r'$x_i$', fontsize=24)
plt.ylabel(r'$v(x_i)$', fontsize=24)
```

$$SWU = \tau(PV(x_p) + WV(x_w) - FV(x_f)))$$

```python
def swu(x_p, x_w, x_f, p, t):
    w = p*wf(x_p, x_w, x_f)
    f = p*ff(x_p, x_w, x_f)
    return t*(p*v(x_p) + w*v(x_w) - f*v(x_f))

x_i = np.arange(0.001, 0.1, 0.001)

swu_p = [swu(0.03, 0.001, 0.007, x, 1) for x in range(1, 100)]
swu_x_p = [swu(x, 0.001, 0.007, 50, 1) for x in x_i]
swu_x_w = [swu(0.03, x, 0.007, 50, 1) for x in x_i]
swu_x_f = [swu(0.03, 0.001, x, 50, 1) for x in x_i]

fig, ax1 = plt.subplots()

ax1.plot(x_i, swu_x_p, label= "SWU(x_p)")
ax1.plot(x_i, swu_x_w, label= "SWU(x_w)")
ax1.plot(x_i, swu_x_f, label= "SWU(x_f)")
ax1.set_xlabel(r'$x_i$', color='k', fontsize=20)

ax1.legend(loc=2)

ax2 = ax1.twiny()
ax2.plot(swu_p, label="SWU(P)", color='c')
ax2.set_xlabel(r'$P$', color='c', fontsize=20)
for tl in ax2.get_xticklabels():
    tl.set_color('c')

ax2.legend()

```

### SWU Factor

The SWU factor is the number of SWUs required per unit product (in our case, kg of enriched U).

\begin{align}
SF &= \frac{SWU}{P\tau}\\
   &= V(x_p) + \frac{W}{P}V(x_w) - \frac{F}{P}V(x_f)
\end{align}

```python
plt.clf()
def swu_factor(x_p, x_w, x_f):
    sf = v(x_p) + wf(x_p, x_w, x_f)*v(x_w) - ff(x_p, x_w, x_f)*v(x_f)
    return sf

x_i = np.arange(0.001, 0.1, 0.001)
x_p = 0.03
x_w = 0.002
x_f = 0.00711

sf_x_p = [swu_factor(x,   x_w, x_f) for x in x_i]
sf_x_w = [swu_factor(x_p, x,   x_f) for x in x_i]
sf_x_f = [swu_factor(x_p, x_w, x) for x in x_i]

plt.plot(x_i, sf_x_p, label= "SF(x_p)")
plt.plot(x_i, sf_x_w, label= "SF(x_w)")
plt.plot(x_i, sf_x_f, label= "SF(x_f)")
plt.legend(loc=2)
```

```python
plt.clf()
def swu_factor(x_p, x_w, x_f):
    sf = v(x_p) + wf(x_p, x_w, x_f)*v(x_w) - ff(x_p, x_w, x_f)*v(x_f)
    return sf

x_i = np.arange(0.001, 0.9, 0.001)
x_p = 0.03
x_w = 0.002
x_f = 0.00711

sf_x_p = [swu_factor(x, x_w, x_f) for x in x_i]
ff_x_p = [ff(x, x_w, x_f) for x in x_i]

plt.plot(x_i, sf_x_p, label= "SF(x_p)")
plt.plot(x_i, ff_x_p, label= "FF(x_p)")
plt.legend(loc=2)
```

## Centrifugal Enrichment

### Centrifugal Force

Centrifugal enrichment relies on the __centrifugal force__. The centrifugal force is only apparent in the rotating reference frame. In the inertial reference frame, it is not apparent, so it is a 'fictitious' force. Fictitious or not, though, in the stationary reference frame, things move.

#### Velocity
Velocity in a rotating frame of reference, with the absolute angular velocity, $\omega$, gives

$$\frac{\operatorname{d}\boldsymbol{r}}{\operatorname{d}t} = \left[\frac{\operatorname{d}\boldsymbol{r}}{\operatorname{d}t}\right] + \boldsymbol{\omega} \times \boldsymbol{r}$$

Note that the square brackets in this notation indicate that the argumet is evaluated in So, the velocity in the rotating reference frame is dependent on the rotation, $\omega$.

#### Acceleration

Acceleration in a rotating reference frame simply undergoes the same derivative transformation a second time.

$$\begin{align}
\boldsymbol{a} &=\frac{\operatorname{d}^2\boldsymbol{r}}{\operatorname{d}t^2} = \frac{\operatorname{d}}{\operatorname{d}t}\frac{\operatorname{d}\boldsymbol{r}}{\operatorname{d}t} = \frac{\operatorname{d}}{\operatorname{d}t} \left( \left[\frac{\operatorname{d}\boldsymbol{r}}{\operatorname{d}t}\right] + \boldsymbol{\omega} \times \boldsymbol{r}\ \right) \\
 &= \left[ \frac{\operatorname{d}^2 \boldsymbol{r}}{\operatorname{d}t^2} \right] + \frac{\operatorname{d} \boldsymbol{\omega}}{\operatorname{d}t}\times\boldsymbol{r} + 2 \boldsymbol{\omega}\times \left[ \frac{\operatorname{d} \boldsymbol{r}}{\operatorname{d}t} \right] + \boldsymbol{\omega}\times ( \boldsymbol{\omega} \times \boldsymbol{r}) \ .
\end{align}
$$

But, that's all in the rotating frame. When an observer views this, the forces must appear to cancel. So, we arrive at the observed form of the equation of motion, where :

$$\boldsymbol{F} - m\frac{\operatorname{d} \boldsymbol{\omega}}{\operatorname{d}t}\times\boldsymbol{r} - 2m \boldsymbol{\omega}\times \left[ \frac{\operatorname{d} \boldsymbol{r}}{\operatorname{d}t} \right] - m\boldsymbol{\omega}\times (\boldsymbol{\omega}\times \boldsymbol{r}) = m\left[ \frac{\operatorname{d}^2 \boldsymbol{r}}{\operatorname{d}t^2} \right]$$

#### Questions:

- What direction is represented by the vector $\boldsymbol{r}$?
- What direction is represented by the vector $\boldsymbol{\omega}$?
- Combining these facts with the equation above, what can be said about the direction of the velocity vector $\vec{\omega}\times\vec{r}$?
- What about the direction of the final term of the observed force $-\vec{\omega}\times(\vec{\omega}\times\vec{r})$?

```python
# Let's see a classic image of a bank of centrifuges
from IPython.display import Image
Image("http://world-nuclear.org/uploadedImages/org/info/Nuclear_Fuel_Cycle/Enrichment_and_Conversion/centrfge.jpg")
```

### Separation Factor per Centrifuge Stage

$$\alpha = 1+ \frac{M_H - M_L}{2RT}\omega^2a^2$$

In the case of $^{238}UF_6$ and $^{235}UF_6$:

$$M_H = M_{^{238}U} + 6\times M_F$$
$$\Rightarrow M_H = 238 + 6\times 19$$
$$\Rightarrow M_H =

$$M_L = M_{^{235}U} + 6\times M_F$$
$$\Rightarrow M_H = 235 + 6\times 19$$

```python
from scipy import constants as sc
import math
def alpha(m_h, m_l, omega, a, t):
    """
    :param omega: angular speed of rotating bowls (rad/s)
    :param a: inside radius of rotor [m]
    :param m_h: molecular weight of \[^{238}U^{19}F_6\]
    :param m_l: molecular weight of \[^{235}U^{19}F_6\]
    :param r: R, molar gas constant
    :param t: temperature in Kelvin
    """
    return 1 + ((m_h-m_l)*(omega**2)*(a**2))/(2*sc.R*t)

def sep_cap(l, m_h, m_l, omega, a, t):
    """
    :param l: length of the bowl [m]
    :param omega: angular speed of rotating bowls (rad/s)
    :param a: inside radius of rotor [m?]
    :param m_h: molecular weight of \[^{238}U^{19}F_6\]
    :param m_l: molecular weight of \[^{235}U^{19}F_6\]
    :param r: R, molar gas constant
    :param t: temperature in Kelvin
    """
    return math.exp((l*(m_h-m_l)**2*(omega*a)**4))/(t**2)

m_h = (238 + 6*19)
m_l = (235 + 6*19)
a = 0.3048
t = 300
#l = 4
omega = np.arange(2300., 4000.)

alpha_plot = [alpha(m_h, m_l, o, a, t) for o in omega]
#plt.plot(alpha_plot, label=r'$\alpha$ (separation factor)')

#sep_cap_plot = [sep_cap(l, m_h, m_l, o, a, t) for o in omega]

plt.plot(omega, alpha_plot, label="alpha")
#plt.plot(sep_cap_plot, label="sep cap")
plt.xlabel(r'$\omega$', fontsize=24)
plt.legend(loc=2)
```

## The Zippe Centrifuge

> A Zippe-type centrifuge has a hollow, cylindrical rotor filled with gaseous uranium in the form of its hexafluoride. A pulsating magnetic field at the bottom of the rotor, similar to that used in an electric motor, is able to spin it quickly enough that the U-238 is thrown towards the edge. The lighter U-235 collects near the center. The bottom of the gaseous mix is heated, producing convection currents that move the U-238 down. The U-235 moves up, where scoops collect it. Each centrifuge has one inlet and two output lines (corresponding to the heavy and the light fractions).

> At the high speed of rotation, the gas is compressed close to the wall of the rotor. The rotor can be almost a meter in length (diameter is likely to be less than 10 cm) and a temperature gradient of 300 °C between the top and bottom of the rotor produces a very strong convection current. In addition, the very strong coriolis forces produced add to the separation efficiency.

> To reduce friction, the rotor spins in a vacuum. A magnetic bearing holds the top of the rotor steady, and the only physical contact is the needle-like bearing that the rotor sits on. The three gas lines must be concentric with the fixed axis as the outer rim is spinning very quickly, and the seal is very important.

## History

### Calutron Electromagnetic Separation

1940s schematic of Calutron separation physics.

Electromagnetic separation cartoon.

Alpha I racetrack, Y-12 Uranium 235 electromagnetic separation plant, Manhattan Project, Clinton Engineer Works, Oak Ridge

Y-12, Oak Ridge.

Calutron operators.

## Separation Nozzle Method

The nozzle method is another type of enrichment technology that relies on mass.

In particular, lighter elements are deflected upward more easily and are separated physically from their heavier counterparts.

## Laser Enrichment

Recall the hyperfine structure of the atom.

### AVLIS
Atomic vapor laser isotope separation.

### SILEX
In Australia, a combination of the two methods, abbreviated to SILEX (separation of isotopes via laser excitation). Cold gas, pulsed $CO_2$ laser with an amplified $16 \mu m$ wavelength laser which preferentially excites the $^{235}UF_6$.

## Economics of Enrichment

The enrichment technologies used by industry must be energy effecient and economically feasible. Cost of enrichment is typically given in $\$/SWU$, so to find the cost of enrichment, one must multiply the price of separation with the number of SWUs to be purchased.

$$\$ = \frac{\$}{SWU}SWU$$

### Discussion: If a $\$/kg$ unit is needed, one must multiply the price of separation in $\$/SWU$ with what value?

## World Enrichment Capacity

## Aside: Enrichment of Heavy Water
$^2H$ is also known as D (deuterium).

### Discussion: Are $D$ (deuterium) and $H$ (protium) easier or harder to separate than isotopes of U?

### Why?


     Histograms of Oriented Gradients (HOG)

Introduction

As we saw with the ORB algorithm, we can use keypoints in images to do keypoint-based matching to detect objects in images. These type of algorithms work great when you want to detect objects that have a lot of consistent internal features that are not affected by the background. For example, these algorithms work well for facial detection because faces have a lot of consistent internal features that don’t get affected by the image background, such as the eyes, nose, and mouth. However, these type of algorithms don’t work so well when attempting to do more general object recognition, say for example, pedestrian detection in images. The reason is that people don’t have consistent internal features, like faces do, because the body shape and style of every person is different (see Fig. 1). This means that every person is going to have a different set of internal features, and so we need something that can more generally describe a person.

Fig. 1. - Pedestrians.

One option is to try to detect pedestrians by their contours instead.  Detecting objects in images by their contours (boundaries) is very challenging because we have to deal with the difficulties brought about by the contrast between the background and the foreground. For example, suppose you wanted to detect a pedestrian in an image that is walking in front of a white building and she is wearing a white coat and black pants (see Fig. 2). We can see in Fig. 2, that since the background of the image is mostly white, the black pants are going to have a very high contrast, but the coat, since it is white as well, is going to have very low contrast. In this case, detecting the edges of pants is going to be easy but detecting the edges of the coat is going to be very difficult. This is where **HOG** comes in. HOG stands for **Histograms of Oriented Gradients** and it was first introduced by Navneet Dalal and Bill Triggs in 2005.

Fig. 2. - High and Low Contrast.

The HOG algorithm works by creating histograms of the distribution of gradient orientations in an image and then normalizing them in a very special way. This special normalization is what makes HOG so effective at detecting the edges of objects even in cases where the contrast is very low. These normalized histograms are put together into a feature vector, known as the HOG descriptor, that can be used to train a machine learning algorithm, such as a Support Vector Machine (SVM), to detect objects in images based on their boundaries (edges). Due to its great success and reliability, HOG has become one of the most widely used algorithms in computer vison for object detection.

In this notebook, you will learn:

* How the HOG algorithm works
* How to use OpenCV to create a HOG descriptor
* How to visualize the HOG descriptor.

# The HOG Algorithm

As its name suggests, the HOG algorithm, is based on creating histograms from the orientation of image gradients. The HOG algorithm is implemented in a series of steps:

1. Given the image of particular object, set a detection window (region of interest) that covers the entire object in the image (see Fig. 3).

2. Calculate the magnitude and direction of the gradient for each individual pixel in the detection window.

3. Divide the detection window into connected *cells* of pixels, with all cells being of the same size (see Fig. 3). The size of the cells is a free parameter and it is usually chosen so as to match the scale of the features that want to be detected. For example, in a 64 x 128 pixel detection window, square cells 6 to 8 pixels wide are suitable for detecting human limbs.

4. Create a Histogram for each cell, by first grouping the gradient directions of all pixels in each cell into a particular number of orientation (angular) bins; and then adding up the gradient magnitudes of the gradients in each angular bin (see Fig. 3). The number of bins in the histogram is a free parameter and it is usually set to 9 angular bins.

5. Group adjacent cells into *blocks* (see Fig. 3). The number of cells in each block is a free parameter and all blocks must be of the same size. The distance between each block (known as the stride) is a free parameter but it is usually set to half the block size, in which case you will get overlapping blocks (*see video below*).  The HOG algorithm has been shown empirically to work better with overlapping blocks.

6. Use the cells contained within each block to normalize the cell histograms in that block (see Fig. 3). If you have overlapping blocks this means that most cells will be normalized with respect to different blocks (*see video below*). Therefore, the same cell may have several different normalizations.

7. Collect all the normalized histograms from all the blocks into a single feature vector called the HOG descriptor.

8. Use the resulting HOG descriptors from many images of the same type of object to train a machine learning algorithm, such as an SVM, to detect those type of objects in images. For example, you could use the HOG descriptors from many images of pedestrians to train an SVM to detect pedestrians in images. The training is done with both positive a negative examples of the object you want detect in the image.

9. Once the SVM has been trained, a sliding window approach is used to try to detect and locate objects in images.  Detecting an object in the image entails finding the part of the image that looks similar to the HOG pattern learned by the SVM.

Fig. 3. - HOG Diagram.

Vid. 1. - HOG Animation.

# Why The HOG Algorithm Works

As we learned above, HOG creates histograms by adding the magnitude of the gradients in particular orientations in localized portions of the image called *cells*.  By doing this we guarantee that stronger gradients will contribute more to the magnitude of their respective angular bin, while the effects of weak and randomly oriented gradients resulting from noise are minimized. In this manner the histograms tell us the dominant gradient orientation of each cell.

### Dealing with contrast

Now, the magnitude of the dominant orientation can vary widely due to variations in local illumination and the contrast between the background and the foreground.

To account for the background-foreground contrast differences, the HOG algorithm tries to detect edges locally.  In order to do this, it defines groups of cells, called **blocks**, and normalizes the histograms using this local group of cells. By normalizing locally, the HOG algorithm can detect the edges in each block very reliably; this is called **block normalization**.

In addition to using block normalization, the HOG algorithm also uses overlapping blocks to increase its performance. By using overlapping blocks, each cell contributes several independent components to the final HOG descriptor, where each component corresponds to a cell being normalized with respect to a different block. This may seem redundant but, it has been shown empirically that by normalizing each cell several times with respect to different local blocks, the performance of the HOG algorithm increases dramatically.

### Loading Images and Importing Resources

The first step in building our HOG descriptor is to load the required packages into Python and to load our image.

We start by using OpenCV to load an image of a triangle tile. Since, the `cv2.imread()` function loads images as BGR we will convert our image to RGB so we can display it with the correct colors. As usual we will convert our BGR image to Gray Scale for analysis.

```python
import cv2
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline

# Set the default figure size
plt.rcParams['figure.figsize'] = [17.0, 7.0]

# Load the image
image = cv2.imread('./images/triangle_tile.jpeg')

# Convert the original image to RGB
original_image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)

# Convert the original image to gray scale
gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

# Print the shape of the original and gray scale images
print('The original image has shape: ', original_image.shape)
print('The gray scale image has shape: ', gray_image.shape)

# Display the images
plt.subplot(121)
plt.imshow(original_image)
plt.title('Original Image')
plt.subplot(122)
plt.imshow(gray_image, cmap='gray')
plt.title('Gray Scale Image')
plt.show()
```

# Creating The HOG Descriptor

We will be using OpenCV’s `HOGDescriptor` class to create the HOG descriptor. The parameters of the HOG descriptor are setup using the `HOGDescriptor()` function. The parameters of the `HOGDescriptor()` function and their default values are given below:

`cv2.HOGDescriptor(win_size = (64, 128),
                  block_size = (16, 16),
                  block_stride = (8, 8),
                  cell_size = (8, 8),
                  nbins = 9,
                  win_sigma = DEFAULT_WIN_SIGMA,
                  threshold_L2hys = 0.2,
                  gamma_correction = true,
                  nlevels = DEFAULT_NLEVELS)`

Parameters:

* **win_size** – *Size*
Size of detection window in pixels (*width, height*). Defines the region of interest. Must be an integer multiple of cell size.

* **block_size** – *Size*
Block size in pixels (*width, height*). Defines how many cells are in each block. Must be an integer multiple of cell size and it must be smaller than the detection window. The smaller the block the finer detail you will get.

* **block_stride** – *Size*
Block stride in pixels (*horizontal, vertical*). It must be an integer multiple of cell size. The `block_stride` defines the distance between adjecent blocks, for example, 8 pixels horizontally and 8 pixels vertically. Longer `block_strides` makes the algorithm run faster (because less blocks are evaluated) but the algorithm may not perform as well.

* **cell_size** – *Size*
Cell size in pixels (*width, height*). Determines the size fo your cell. The smaller the cell the finer detail you will get.

* **nbins** – *int*
Number of bins for the histograms. Determines the number of angular bins used to make the histograms. With more bins you capture more gradient directions. HOG uses unsigned gradients, so the angular bins will have values between 0 and 180 degrees.

* **win_sigma** – *double*
Gaussian smoothing window parameter. The performance of the HOG algorithm can be improved by smoothing the pixels near the edges of the blocks by applying a Gaussian spatial window to each pixel before computing the histograms.

* **threshold_L2hys** – *double*
L2-Hys (Lowe-style clipped L2 norm) normalization method shrinkage. The L2-Hys method is used to normalize the blocks and it consists of an L2-norm followed by clipping and a renormalization. The clipping limits the maximum value of the descriptor vector for each block to have the value of the given threshold (0.2 by default). After the clipping the descriptor vector is renormalized as described in *IJCV*, 60(2):91-110, 2004.

* **gamma_correction** – *bool*
Flag to specify whether the gamma correction preprocessing is required or not. Performing gamma correction slightly increases the performance of the HOG algorithm.

* **nlevels** – *int*
Maximum number of detection window increases.

As we can see, the  `cv2.HOGDescriptor()`function supports a wide range of parameters. The first few arguments (`block_size, block_stride, cell_size`, and `nbins`) are probably the ones you are most likely to change. The other parameters can be safely left at their default values and you will get good results.

In the code below, we will use the `cv2.HOGDescriptor()`function to set the cell size, block size, block stride, and the number of bins for the histograms of the HOG descriptor. We will then use `.compute(image)`method to compute the HOG descriptor (feature vector) for the given `image`.

```python
# Specify the parameters for our HOG descriptor

# Cell Size in pixels (width, height). Must be smaller than the size of the detection window
# and must be chosen so that the resulting Block Size is smaller than the detection window.
cell_size = (6, 6)

# Number of cells per block in each direction (x, y). Must be chosen so that the resulting
# Block Size is smaller than the detection window
num_cells_per_block = (2, 2)

# Block Size in pixels (width, height). Must be an integer multiple of Cell Size.
# The Block Size must be smaller than the detection window
block_size = (num_cells_per_block[0] * cell_size[0],
              num_cells_per_block[1] * cell_size[1])

# Calculate the number of cells that fit in our image in the x and y directions
x_cells = gray_image.shape[1] // cell_size[0]
y_cells = gray_image.shape[0] // cell_size[1]

# Horizontal distance between blocks in units of Cell Size. Must be an integer and it must
# be set such that (x_cells - num_cells_per_block[0]) / h_stride = integer.
h_stride = 1

# Vertical distance between blocks in units of Cell Size. Must be an integer and it must
# be set such that (y_cells - num_cells_per_block[1]) / v_stride = integer.
v_stride = 1

# Block Stride in pixels (horizantal, vertical). Must be an integer multiple of Cell Size
block_stride = (cell_size[0] * h_stride, cell_size[1] * v_stride)

# Number of gradient orientation bins
num_bins = 9

# Specify the size of the detection window (Region of Interest) in pixels (width, height).
# It must be an integer multiple of Cell Size and it must cover the entire image. Because
# the detection window must be an integer multiple of cell size, depending on the size of
# your cells, the resulting detection window might be slightly smaller than the image.
# This is perfectly ok.
win_size = (x_cells * cell_size[0] , y_cells * cell_size[1])

# Print the shape of the gray scale image for reference
print('\nThe gray scale image has shape: ', gray_image.shape)
print()

# Print the parameters of our HOG descriptor
print('HOG Descriptor Parameters:\n')
print('Window Size:', win_size)
print('Cell Size:', cell_size)
print('Block Size:', block_size)
print('Block Stride:', block_stride)
print('Number of Bins:', num_bins)
print()

# Set the parameters of the HOG descriptor using the variables defined above
hog = cv2.HOGDescriptor(win_size, block_size, block_stride, cell_size, num_bins)

# Compute the HOG Descriptor for the gray scale image
hog_descriptor = hog.compute(gray_image)
```

    The gray scale image has shape:  (250, 250)

    HOG Descriptor Parameters:

    Window Size: (246, 246)
    Cell Size: (6, 6)
    Block Size: (12, 12)
    Block Stride: (6, 6)
    Number of Bins: 9

# Number of Elements In The HOG Descriptor

The resulting HOG Descriptor (feature vector), contains the normalized histograms from all cells from all blocks in the detection window concatenated in one long vector. Therefore, the size of the HOG feature vector will be given by the total number of blocks in the detection window, multiplied by the number of cells per block, times the number of orientation bins:

\begin{equation}
\mbox{total_elements} = (\mbox{total_number_of_blocks})\mbox{ } \times \mbox{ } (\mbox{number_cells_per_block})\mbox{ } \times \mbox{ } (\mbox{number_of_bins})
\end{equation}

If we don’t have overlapping blocks (*i.e.* the `block_stride`equals the `block_size`), the total number of blocks can be easily calculated by dividing the size of the detection window by the block size. However, in the general case we have to take into account the fact that we have overlapping blocks. To find the total number of blocks in the general case (*i.e.* for any `block_stride` and `block_size`), we can use the formula given below:

\begin{equation}
\mbox{Total}_i = \left( \frac{\mbox{block_size}_i}{\mbox{block_stride}_i} \right)\left( \frac{\mbox{window_size}_i}{\mbox{block_size}_i} \right) - \left [\left( \frac{\mbox{block_size}_i}{\mbox{block_stride}_i} \right) - 1 \right]; \mbox{  for  } i = x,y
\end{equation}

Where Total$_x$, is the total number of blocks along the width of the detection window, and Total$_y$, is the total number of blocks along the height of the detection window. This formula for Total$_x$ and Total$_y$, takes into account the extra blocks that result from overlapping. After calculating Total$_x$ and Total$_y$, we can get the total number of blocks in the detection window by multiplying Total$_x$  $\times$ Total$_y$. The above formula can be simplified considerably because the `block_size`, `block_stride`, and `window_size`are all defined in terms of the `cell_size`. By making all the appropriate substitutions and cancelations the above formula reduces to:

\begin{equation}
\mbox{Total}_i = \left(\frac{\mbox{cells}_i - \mbox{num_cells_per_block}_i}{N_i}\right) + 1\mbox{  }; \mbox{  for  } i = x,y
\end{equation}

Where cells$_x$ is the total number of cells along the width of the detection window, and cells$_y$, is the total number of cells along the height of the detection window. And $N_x$ is the horizontal block stride in units of `cell_size` and $N_y$ is the vertical block stride in units of `cell_size`.

Let's calculate what the number of elements for the HOG feature vector should be and check that it matches the shape of the HOG Descriptor calculated above.

```python
# Calculate the total number of blocks along the width of the detection window
tot_bx = np.uint32(((x_cells - num_cells_per_block[0]) / h_stride) + 1)

# Calculate the total number of blocks along the height of the detection window
tot_by = np.uint32(((y_cells - num_cells_per_block[1]) / v_stride) + 1)

# Calculate the total number of elements in the feature vector
tot_els = (tot_bx) * (tot_by) * num_cells_per_block[0] * num_cells_per_block[1] * num_bins

# Print the total number of elements the HOG feature vector should have
print('\nThe total number of elements in the HOG Feature Vector should be: ',
      tot_bx, 'x',
      tot_by, 'x',
      num_cells_per_block[0], 'x',
      num_cells_per_block[1], 'x',
      num_bins, '=',
      tot_els)

# Print the shape of the HOG Descriptor to see that it matches the above
print('\nThe HOG Descriptor has shape:', hog_descriptor.shape)
print()
```

    The total number of elements in the HOG Feature Vector should be:  40 x 40 x 2 x 2 x 9 = 57600

    The HOG Descriptor has shape: (57600, 1)

# Visualizing The HOG Descriptor

We can visualize the HOG Descriptor by plotting the histogram associated with each cell as a collection of vectors. To do this, we will plot each bin in the histogram as a single vector whose magnitude is given by the height of the bin and its orientation is given by the angular bin that its associated with. Since any given cell might have multiple histograms associated with it, due to the overlapping blocks, we will choose to average all the histograms for each cell to produce a single histogram for each cell.

OpenCV has no easy way to visualize the HOG Descriptor, so we have to do some manipulation first in order to visualize it. We will start by reshaping the HOG Descriptor in order to make our calculations easier.  We will then compute the average histogram of each cell and finally we will convert the histogram bins into vectors. Once we have the vectors, we plot the corresponding vectors for each cell in an image.

The code below produces an interactive plot so that you can interact with the figure. The figure contains:
* the grayscale image,
* the HOG Descriptor (feature vector),
* a zoomed-in portion of the HOG Descriptor, and
* the histogram of the selected cell.

**You can click anywhere on the gray scale image or the HOG Descriptor image to select a particular cell**. Once you click on either image a *magenta* rectangle will appear showing the cell you selected. The Zoom Window will show you a zoomed in version of the HOG descriptor around the selected cell; and the histogram plot will show you the corresponding histogram for the selected cell. The interactive window also has buttons at the bottom that allow for other functionality, such as panning, and giving you the option to save the figure if desired. The home button returns the figure to its default value.

**NOTE**: If you are running this notebook in the Udacity workspace, there is around a 2 second lag in the interactive plot. This means that if you click in the image to zoom in, it will take about 2 seconds for the plot to refresh.

```python
%matplotlib notebook

import copy
import matplotlib.patches as patches

# Set the default figure size
plt.rcParams['figure.figsize'] = [9.8, 9]

# Reshape the feature vector to [blocks_y, blocks_x, num_cells_per_block_x, num_cells_per_block_y, num_bins].
# The blocks_x and blocks_y will be transposed so that the first index (blocks_y) referes to the row number
# and the second index to the column number. This will be useful later when we plot the feature vector, so
# that the feature vector indexing matches the image indexing.
hog_descriptor_reshaped = hog_descriptor.reshape(tot_bx,
                                                 tot_by,
                                                 num_cells_per_block[0],
                                                 num_cells_per_block[1],
                                                 num_bins).transpose((1, 0, 2, 3, 4))

# Print the shape of the feature vector for reference
print('The feature vector has shape:', hog_descriptor.shape)

# Print the reshaped feature vector
print('The reshaped feature vector has shape:', hog_descriptor_reshaped.shape)

# Create an array that will hold the average gradients for each cell
ave_grad = np.zeros((y_cells, x_cells, num_bins))

# Print the shape of the ave_grad array for reference
print('The average gradient array has shape: ', ave_grad.shape)

# Create an array that will count the number of histograms per cell
hist_counter = np.zeros((y_cells, x_cells, 1))

# Add up all the histograms for each cell and count the number of histograms per cell
for i in range (num_cells_per_block[0]):
    for j in range(num_cells_per_block[1]):
        ave_grad[i:tot_by + i, j:tot_bx + j] += hog_descriptor_reshaped[:, :, i, j, :]
        hist_counter[i:tot_by + i, j:tot_bx + j] += 1

# Calculate the average gradient for each cell
ave_grad /= hist_counter

# Calculate the total number of vectors we have in all the cells.
len_vecs = ave_grad.shape[0] * ave_grad.shape[1] * ave_grad.shape[2]

# Create an array that has num_bins equally spaced between 0 and 180 degress in radians.
deg = np.linspace(0, np.pi, num_bins, endpoint = False)

# Each cell will have a histogram with num_bins. For each cell, plot each bin as a vector (with its magnitude
# equal to the height of the bin in the histogram, and its angle corresponding to the bin in the histogram).
# To do this, create rank 1 arrays that will hold the (x,y)-coordinate of all the vectors in all the cells in the
# image. Also, create the rank 1 arrays that will hold all the (U,V)-components of all the vectors in all the
# cells in the image. Create the arrays that will hold all the vector positons and components.
U = np.zeros((len_vecs))
V = np.zeros((len_vecs))
X = np.zeros((len_vecs))
Y = np.zeros((len_vecs))

# Set the counter to zero
counter = 0

# Use the cosine and sine functions to calculate the vector components (U,V) from their maginitudes. Remember the
# cosine and sine functions take angles in radians. Calculate the vector positions and magnitudes from the
# average gradient array
for i in range(ave_grad.shape[0]):
    for j in range(ave_grad.shape[1]):
        for k in range(ave_grad.shape[2]):
            U[counter] = ave_grad[i,j,k] * np.cos(deg[k])
            V[counter] = ave_grad[i,j,k] * np.sin(deg[k])

            X[counter] = (cell_size[0] / 2) + (cell_size[0] * i)
            Y[counter] = (cell_size[1] / 2) + (cell_size[1] * j)

            counter = counter + 1

# Create the bins in degress to plot our histogram.
angle_axis = np.linspace(0, 180, num_bins, endpoint = False)
angle_axis += ((angle_axis[1] - angle_axis[0]) / 2)

# Create a figure with 4 subplots arranged in 2 x 2
fig, ((a,b),(c,d)) = plt.subplots(2,2)

# Set the title of each subplot
a.set(title = 'Gray Scale Image\n(Click to Zoom)')
b.set(title = 'HOG Descriptor\n(Click to Zoom)')
c.set(title = 'Zoom Window', xlim = (0, 18), ylim = (0, 18), autoscale_on = False)
d.set(title = 'Histogram of Gradients')

# Plot the gray scale image
a.imshow(gray_image, cmap = 'gray')
a.set_aspect(aspect = 1)

# Plot the feature vector (HOG Descriptor)
b.quiver(Y, X, U, V, color = 'white', headwidth = 0, headlength = 0, scale_units = 'inches', scale = 5)
b.invert_yaxis()
b.set_aspect(aspect = 1)
b.set_facecolor('black')

# Define function for interactive zoom
def onpress(event):

    #Unless the left mouse button is pressed do nothing
    if event.button != 1:
        return

    # Only accept clicks for subplots a and b
    if event.inaxes in [a, b]:

        # Get mouse click coordinates
        x, y = event.xdata, event.ydata

        # Select the cell closest to the mouse click coordinates
        cell_num_x = np.uint32(x / cell_size[0])
        cell_num_y = np.uint32(y / cell_size[1])

        # Set the edge coordinates of the rectangle patch
        edgex = x - (x % cell_size[0])
        edgey = y - (y % cell_size[1])

        # Create a rectangle patch that matches the the cell selected above
        rect = patches.Rectangle((edgex, edgey),
                                  cell_size[0], cell_size[1],
                                  linewidth = 1,
                                  edgecolor = 'magenta',
                                  facecolor='none')

        # A single patch can only be used in a single plot. Create copies
        # of the patch to use in the other subplots
        rect2 = copy.copy(rect)
        rect3 = copy.copy(rect)

        # Update all subplots
        a.clear()
        a.set(title = 'Gray Scale Image\n(Click to Zoom)')
        a.imshow(gray_image, cmap = 'gray')
        a.set_aspect(aspect = 1)
        a.add_patch(rect)

        b.clear()
        b.set(title = 'HOG Descriptor\n(Click to Zoom)')
        b.quiver(Y, X, U, V, color = 'white', headwidth = 0, headlength = 0, scale_units = 'inches', scale = 5)
        b.invert_yaxis()
        b.set_aspect(aspect = 1)
        b.set_facecolor('black')
        b.add_patch(rect2)

        c.clear()
        c.set(title = 'Zoom Window')
        c.quiver(Y, X, U, V, color = 'white', headwidth = 0, headlength = 0, scale_units = 'inches', scale = 1)
        c.set_xlim(edgex - cell_size[0], edgex + (2 * cell_size[0]))
        c.set_ylim(edgey - cell_size[1], edgey + (2 * cell_size[1]))
        c.invert_yaxis()
        c.set_aspect(aspect = 1)
        c.set_facecolor('black')
        c.add_patch(rect3)

        d.clear()
        d.set(title = 'Histogram of Gradients')
        d.grid()
        d.set_xlim(0, 180)
        d.set_xticks(angle_axis)
        d.set_xlabel('Angle')
        d.bar(angle_axis,
              ave_grad[cell_num_y, cell_num_x, :],
              180 // num_bins,
              align = 'center',
              alpha = 0.5,
              linewidth = 1.2,
              edgecolor = 'k')

        fig.canvas.draw()

# Create a connection between the figure and the mouse click
fig.canvas.mpl_connect('button_press_event', onpress)
plt.show()
```

    The feature vector has shape: (57600, 1)
    The reshaped feature vector has shape: (40, 40, 2, 2, 9)
    The average gradient array has shape:  (41, 41, 9)

    <IPython.core.display.Javascript object>

    <IPython.core.display.Javascript object>

# Understanding The Histograms

Let's take a look at a couple of snapshots of the above figure to see if the histograms for the selected cell make sense. Let's start looking at a cell that is inside a triangle and not near an edge:

Fig. 4. - Histograms Inside a Triangle.

In this case, since the triangle is nearly all of the same color there shouldn't be any dominant gradient in the selected cell. As we can clearly see in the Zoom Window and the histogram, this is indeed the case. We have many gradients but none of them clearly dominates over the other.

Now let’s take a look at a cell that is near a horizontal edge:

Fig. 5. - Histograms Near a Horizontal Edge.

Remember that edges are areas of an image where the intensity changes abruptly. In these cases, we will have a high intensity gradient in some particular direction. This is exactly what we see in the corresponding histogram and Zoom Window for the selected cell.  In the Zoom Window, we can see that the dominant gradient is pointing up, almost at 90 degrees, since that’s the direction in which there is a sharp change in intensity. Therefore, we should expect to see the 90-degree bin in the histogram to dominate strongly over the others. This is in fact what we see.

Now let’s take a look at a cell that is near a vertical edge:

Fig. 6. - Histograms Near a Vertical Edge.

In this case we expect the dominant gradient in the cell to be horizontal, close to 180 degrees, since that’s the direction in which there is a sharp change in intensity. Therefore, we should expect to see the 170-degree bin in the histogram to dominate strongly over the others. This is what we see in the histogram but we also see that there is another dominant gradient in the cell, namely the one in the 10-degree bin. The reason for this, is because the HOG algorithm is using unsigned gradients, which means 0 degrees and 180 degrees are considered the same. Therefore, when the histograms are being created, angles between 160 and 180 degrees, contribute proportionally to both the 10-degree bin and the 170-degree bin. This results in there being two dominant gradients in the cell near the vertical edge instead of just one.

To conclude let’s take a look at a cell that is near a diagonal edge.

Fig. 7. - Histograms Near a Diagonal Edge.

To understand what we are seeing, let’s first remember that gradients have an *x*-component, and a *y*-component, just like vectors. Therefore, the resulting orientation of a gradient is going to be given by the vector sum of its components. For this reason, on vertical edges the gradients are horizontal, because they only have an x-component, as we saw in Figure 4. While on horizontal edges the gradients are vertical, because they only have a y-component, as we saw in Figure 3. Consequently, on diagonal edges, the gradients are also going to be diagonal because both the *x* and *y* components are non-zero.  Since the diagonal edges in the image are close to 45 degrees, we should expect to see a dominant gradient orientation in the 50-degree bin. This is in fact what we see in the histogram but, just like in Figure 4., we see there are two dominant gradients instead of just one. The reason for this is that when the histograms are being created, angles that are near the boundaries of bins, contribute proportionally to the adjacent bins.  For example, a gradient with an angle of 40 degrees, is right in the middle of the 30-degree and 50-degree bin. Therefore, the magnitude of the gradient is split evenly into the 30-degree and 50-degree bin. This results in there being two dominant gradients in the cell near the diagonal edge instead of just one.

Now that you know how HOG is implemented, in the workspace you will find a notebook named *Examples*. In there, you will be able set your own paramters for the HOG descriptor for various images. Have fun!


# Experiment 2: Airtrack I---Kinematics
### Objectives
- To learn good lab practice
- To learn how to estimate and propagate uncertainties
- To learn how position, velocity, and acceleration are related
- To learn how to use the Airtrack
- To learn how to use PASCO Capstone Data Acquisition and Analysis software

### Equipment
- One Airtrack, Blower, Cart, and Acoustic Sensor
- One wooden reflector
- One 2x4 “shim”
- One 30 cm plastic ruler
- One two-meter stick

### Safety
- **Do not attempt to push the carts faster than 1 m/s.**
- **Be careful placing the carts on the track.**
- **Do not damage the track.** Please place paper underneath the carts when they are resting on the track without the air turned on.
- **Do not slide the carts on the track without the air turned on.**

# Introduction

Before one can study the action of forces on objects, one must be able to describe motion. This description of motion is called $kinematics$. We will be learning how to use two tools to investigate the meanings of the three kinematic equations for motion in one dimension:
The definition of average velocity:

\begin{equation}
v_{avg} \equiv \frac{\Delta x}{\Delta t} \tag{1}
\label{eq:v_avg}
\end{equation}

The definition of average acceleration:

\begin{equation}
a_{avg} \equiv \frac{\Delta v}{\Delta t} \tag{2}
\label{eq:a_avg}
\end{equation}

And the position of an object undergoing constant acceleration:

\begin{equation}
x(t) = \frac{1}{2} a t^2 + v_o t + x_o \tag{3}
\label{eq:x_of_t}
\end{equation}

In this lab, your job will be to compare different measurements of velocity and acceleration for an air-cart experiencing both no acceleration and constant acceleration.

# Theory

### Measurement of Velocity and Acceleration
An acoustic sensor will measure the position of a cart on the airtrack at specific intervals of time. By use of the definition of velocity (Equation $\eqref{eq:v_avg}$), we can compute the average velocity between two measurements. Notice that if we plot the position versus time, the fraction is the same as the rise divided by the run-- or the slope of the graph. By plotting *x* vs. *t*, we then find the slope of the graph and determine the velocity.

This same technique works for determining acceleration. However, notice that the variables to plot are now *velocity* versus time.

### Analysis of Uncertainties
It will be important to know what the uncertainties are in our computed velocities and accelerations.

First, recall that a difference in position or time is a subtraction of two quantities:
\begin{equation}
\Delta x = x_2 - x_1 \nonumber
\end{equation}

Therefore, we can use (R1) to compute the uncertainty in the difference:
\begin{equation}
(\delta(\Delta x))^2 = (\delta x_2)^2 - (\delta x_1)^2 \nonumber
\end{equation}

However, in this case, the uncertainties $\delta x_1$ and $\delta x_2$ should be the same, so we can say that the uncertainty in our computed difference is:
\begin{equation}
\delta(\Delta x) = \sqrt{2} (\delta x) \tag{4}
\label{eq:deltax}
\end{equation}

where $\delta x$ is the uncertainty in a single position measurement. We can say the same for time differences:
\begin{equation}
\delta(\Delta t) = \sqrt{2} (\delta t) \tag{5}
\label{eq:deltat}
\end{equation}

where $\delta t$ is the uncertainty in a single time measurement. Notice that the uncertainty in the difference is larger than the uncertainty in each individual measurement.

Computing the uncertainty in velocity, $\delta v$, is a straightforward application of (R2). Looking at Equation $\eqref{eq:v_avg}$ 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 \nonumber
\end{equation}

This equation needs some work to become useful. Capstone will take data points at constant $\Delta t$. But because $\Delta x$ may be different for each measurement, we should try to remove it from our equation. We do this by using the definition of velocity to replace $\Delta x$ with $v \Delta t$:
\begin{equation}
\left(\frac{\delta v}{v}\right)^2 = \left(\frac{\delta (\Delta x)}{v \Delta t}\right)^2 + \left(\frac{\delta (\Delta t)}{\Delta t}\right)^2 \nonumber
\end{equation}

Factoring out $\Delta t$ so that we need only use it once:
\begin{equation}
\left(\frac{\delta v}{v}\right)^2 = \frac{1}{(\Delta t)^2} \left(\left(\frac{\delta (\Delta x)}{v}\right)^2 + \left(\delta (\Delta t)\right)^2 \right) \nonumber
\end{equation}

We may also multiply through by $v^2$:
\begin{equation}
\left(\delta v\right)^2 = \frac{1}{(\Delta t)^2} \left(\left(\delta (\Delta x)\right)^2 + v^2 \left(\delta (\Delta t)\right)^2 \right) \tag{6}
\label{eq:delta_v}
\end{equation}

Now we have an equation to find our uncertainty in velocity based on things we know. Notice that the uncertainty in the velocity *increases* with the velocity. Also notice that the uncertainty is *not zero* when the velocity is zero.
Does this make sense?

# Experimental Procedure

### Leveling the Airtrack
In order to study unaccelerated motion, we must ensure that gravity does not interfere. To do this, we must level the airtrack. The best method for leveling the airtrack is to use the cart itself to verify that it is not pulled one way or the other by gravity.

1. Turn on the blower (you should be able to feel the air coming out of the drilled holes) and place a cart at the 100 cm mark.
2. Place your fingers at the front and rear of the cart so that it remains at rest.
3. Release the cart without pushing it.

If the cart tends to move to one side, you will need to raise that side (or lower the other side). This can be accomplished by rotating the feet on the two-foot support. If this is still not sufficient, you may need to place a folded sheet of paper under one of the two supports until the cart favors neither side. It’s OK if the cart slowly oscillates.

### Configuring the Acoustic Sensor
Place a wooden reflector into the center top hole in the cart. You will now need to place the sensor so that it properly reads the position of the reflector.

1. Place the sensor at the opposite end from where the air hose is placed.
2. Place a cart at that end of the airtrack and set the sensor at the same height as the center of the wooden reflector.
3. Ensure that the sensor is centered over the track and pointed straight down the track.
4. Ensure that the top switch is in “cart” mode (*not* “stick figure” mode).

There are two plugs which must be inserted into the PASCO 850 interface. The yellow plug goes into Digital Input 1 and the black plug goes into Digital Input 2.

### Using PASCO Capstone DAA
Capstone will be used throughout the physics laboratories to collect data, so you should spend some time now learning how to use it. To set it up for the first time, the PASCO 850 interface must be turned on (press and hold the power button until it beeps and the green light is lit) and connected to the computer.

1. Start Capstone.
2. Click “Hardware Setup” on the left-side “Tools” palette. A picture of the PASCO 850 interface will appear.
3. Click on the Digital Input 1 port and
4. Click on the Settings Gear Icon in the lower left. You will need to enter the proper speed of sound. The speed of sound is (331+0.6T) m/s where T is the air temperature in Celcius. Click “OK.”
5. Click on “Hardware Setup” to close the window.
6. In the right-side palette, drag “Graph” to the main window. A large graph will appear.

At the very bottom of the window, notice the sample rate (in Hz). This number tells you how many data points are taken every second. You will need this number later.

When you click “Record,” Capstone will start taking data on all the sensors attached to the 850 interface. When you click “Stop,” Capstone will stop taking data. Every Record/Stop sequence is considered a “run.” All of the data runs you gather are listed in the “Data Summary” tab on the left-side palette. Clicking “Data Summary” a second time will close that window.

### Testing the Acoustic Sensor using Capstone
A very quick test of the sensor would be to click “Record” and move your hand towards and away from the sensor a few times and then click “Stop.” By clicking `<Select Measurement>` on the y-axis and selecting “Position (m),” Capstone will plot a graph of that data. If this works, then your sensor works and it’s time to test the alignment with your cart.

Turn on the air for your airtrack and place a cart on the track. Push the cart towards the sensor and click “Record.” Let the cart bounce back and forth between the bumpers a few times before clicking “Stop.” The new data should have replaced the old data on the graph. (If not, then there are two ways to have the new data plotted: (1) In the “Data Summary” tab, drag the desired run icon from under “Position” and drop it onto the graph. (2) The toolbar on the menu itself has a rainbow-colored triangle on it. Click the arrow next to that icon and select the data run you would like to plot.)

The data should look something like this:

### Troubleshooting
If your data does not look like this, you will need to work on the alignment of your sensor with the reflector. Check the following things:

1. The sensor should be at the same height as the center of the reflector.
2. The sensor should be pointed directly at the reflector.
3. The reflector should be fully facing the sensor.
4. The sensor must be centered on the track (look down over the sensor and make sure the center of the gold circle is over the ridge-line of the airtrack).
5. The sensor must be pointed directly down the track.
6. The switch on the sensor must be set to “Cart.”
7. The green light should blink while data is being taken.

We will be using the PASCO Capstone software frequently in all the laboratory courses, so it is important that you become familiar with this software now. Once your data looks pretty good, perform the following functions:

1. In the “Data Summary” tab, double-click on the data tag and rename it to “Calibration Run #1.”
2. Print your graph in landscape mode (File -> Print, then click the “Preferences” button).
3. Have your instructor sign off on your graph.
4. Grab the right side of your graph and make some space for another display.
5. Drag “Meter” or “Digits” or “Table” display into the open area, click on that display’s `<Select Measurement>` button and select a data source, then click Record/Stop.
6. Click each button on the graph to see what it does.
7. Learn how to pan and zoom your graph.
8. Drag the legend around the graph.
9. Delete all your graphs/tables (select the display by clicking on it, then click on the “Delete Selected Display” icon in the upper Capstone toolbar).
10. Delete every data run and start over by clicking the arrow next to “Delete Last Run” in the bottom tool palette and selecting “Delete All Runs.”

```python
# Sensor test (Sawtooth plot)

import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
from P201_Functions import *

# Reads the name of the csv file and gets the data
df = pd.read_csv("./Lab2_Trace1.csv")

# Prints information about the file
df.info()

print("")

# Defines the x and y values
time = df.filter(['Time (s) Run #1'], axis=1).dropna()
position = df.filter(['Position (m) Run #1'], axis=1).dropna()

# Create a figure of reasonable size and resolution, white background, black edge color
fig=plt.figure(figsize=(7,5), dpi= 100, facecolor='w', edgecolor='k')

# Gets the data values for x and y
x_data = time.values.reshape(-1, 1)
y_data = position.values.reshape(-1, 1)

# Creates the base plot with titles
# df.plot(x='Time (s)', y='Position (m)', kind ='scatter')
plt.plot(x_data,y_data,'b.')
plt.ylabel('Position (m)')
plt.xlabel('Time (s)')
plt.title('Troubleshooting the Capstone Software')

# Add a dotted line through the data points
plt.plot(x_data,y_data,'b--')

# Displays the plot
plt.show()

print("")
```

### Acoustic Sensor Uncertainty
The acoustic sensor can only measure distance so well. How well? You’ll need to determine that. The best way to do this is to take many distance measurements at a fixed location and find the standard deviation of those measurements. Here is one method of doing this:

Turn the air off so that the cart doesn’t move. At three locations on your airtrack (near the sensor, at the 100 cm mark, and at the far end from the sensor):
1. $Carefully$ place the cart at its location on a sheet of paper and press “Record.”
2. After 30 seconds, press “Stop.”
3. Drag a “Table” from the right tool palette onto the main window.
4. In one column, click the `<Select Measurement>` button and select “Time (s).”
5. In the other column, click the `<Select Measurement>` button and select “Position (m).”
6. The cell above should show your last data run. If not, click it and select your last data run.
7. On the table, click the arrow next to the summation sign ($\Sigma$) and make sure “Mean” and “Standard Deviation” are selected. Then click the summation sign ($\Sigma$) itself.
8. Read the standard deviation off the table at the bottom. This is your $\delta x$.

If your standard deviation is greater than 0.001 m (1.0 mm) then you should try to fine-tune the alignment of your sensor. It is very possible to get less than one millimeter uncertainty at the far end of the track. By looking at the
displayed significant digits, your table also tells you what the uncertainty is for a single time measurement. Once you are done aligning your set-up, record your new worst (highest) position uncertainty (this will probably be at
your far position) as well as your uncertainty in time, and the uncertainties for any differences the computer would calculate between two adjacent points.

```python
# Raw Data 1

import pandas as pd
import numpy as np
import ipywidgets as widgets

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

Delta_x = 7.223E-5
delta_Delta_x = np.sqrt(2)*Delta_x # From Equation 4

###########################################
print("Δx =", Delta_x)
print("𝛿(Δx) =", delta_Delta_x)
```

    Δx = 7.223e-05
    𝛿(Δx) = 0.00010214864561020867

What is the difference in time between two $successive$ data points? (You can either look at your table or recall the Sample Rate from above).  The uncertainty in time, $\delta t$, will be $half$ of this number.

```python
# Raw Data 2

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

Delta_t = 0.025
delta_Delta_t = np.sqrt(2)*Delta_t # From Equation 5

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

print("Δt =", Delta_t)
print("𝛿(Δt) =", delta_Delta_t)
```

    Δt = 0.025
    𝛿(Δt) = 0.03535533905932738

### Unaccelerated Motion
This first part of the experiment is to compare the position of the cart as a function of time with its velocity and verify equation $\eqref{eq:v_avg}$:

1. Start fresh: delete ALL data sets and close all data windows.
2. Drag a “Graph” from the right tool palette to your data area. Select “Position (m)” for your y-axis.
3. Turn on the air and remove the paper from under the cart.
4. With your cart at the far end of the track, give it a good push towards the sensor.
5. Immediately after the cart bounces off the near bumper, press “Record.”
6. Immediately BEFORE the cart strikes the far bumper, press “Stop.”
7. Turn off the air.
8. Rename this run “Level Airtrack”, and export the data as Lab2_Level_Airtrack.csv

```python
# Position vs. Time Graph

# Reads the name of the csv file and gets the data
df = pd.read_csv("./Lab2_Level_Airtrack.csv")
df.info()
print (df)

# Prints information about the file
#df.info()

print("")

# Defines the x and y values
time = df.filter(['Time (s) Run #1'], axis=1).dropna()
position = df.filter(['Position (m) Run #1'], axis=1).dropna()

# Create a figure of reasonable size and resolution, white background, black edge color
fig=plt.figure(figsize=(7,5), dpi= 100, facecolor='w', edgecolor='k')

# Gets the data values for x and y
x_data = time.values.reshape(-1, 1)
y_data = position.values.reshape(-1, 1)

xi = df['Time (s) Run #1'].to_numpy()
yi = df['Position (m) Run #1'].to_numpy()

# Creates the base plot with titles
# df.plot(x='Time (s)', y='Position (m)', kind ='scatter')
plt.errorbar(x_data,y_data,Delta_x,Delta_t,'b.',label='Raw Data')
plt.ylabel('Position (m)')
plt.xlabel('Time (s)')
plt.title('Position vs. Time')

# Takes the x and y values to make a trendline
#intercept, slope = linear_fit_plot(x_data,y_data)
intercept, slope, dintercept, dslope = linear_fit_plot_errors(xi,yi,x_low=0.5,x_high=2.5,labelstring="Linear Fit",linestring="r-")

# Adds the legend to the plot
plt.legend()

# Displays the plot
plt.show()

print("")
```

```python
# Velocity vs. Time Graph (unaccelerated)

# Remove NaN entries from dataframe
df = df.dropna()

# Defines the x and y values
time = df.filter(['Time (s) Run #1'], axis=1).dropna()
position = df.filter(['Velocity (m/s) Run #1'], axis=1).dropna()

fig=plt.figure(figsize=(7,5), dpi= 100, facecolor='w', edgecolor='k')

# Gets the data values for x and y
x_data = time.values.reshape(-1, 1)
y_data = position.values.reshape(-1, 1)
xi = df['Time (s) Run #1'].to_numpy()
yi = df['Velocity (m/s) Run #1'].to_numpy()

Delta_v = delta_Delta_x/Delta_t

# Creates the base plot with titles
# df.plot(x='Time (s)', y='Position (m)', kind ='scatter')
plt.errorbar(x_data,y_data,Delta_v,Delta_t,'b.',label='Raw Data')
plt.ylabel('Velocity (m/s)')
plt.xlabel('Time (s)')
plt.title('Velocity vs. Time')

# Takes the x and y values to make a trendline
#intercept, slope = linear_fit_plot(x_data,y_data)
intercept2, slope2, dintercept2, dslope2 = linear_fit_plot_errors(xi,yi,x_low=0.2,x_high=2.0,labelstring="Linear Fit",linestring="r-")

# Adds the legend to the plot
plt.legend()

# Displays the plot
plt.show()

print("")
```

## Results and Conclusions
**Unaccelerated Motion Experiment**
- Why does the time between peaks in the saw-tooth pattern increase? Will this affect your results?
- Compare the slope of the Position vs. Time graph to the average value of the velocities in the velocity vs. time graph. Do the two numbers overlap if you include your uncertainties?

```python
print()
print ("Velocity, from x vs. t graph = ",slope," +/- ",dslope,"m/s")

print()
print ("Velocity, from v vs. t graph = ",intercept2," +/- ",dintercept2,"m/s")

print()
print()

x = ['x vs. t','v vs. t']
dx = ['','','','']
y = np.array([slope,intercept2])
dy = np.array([dslope,dintercept2])

fig=plt.figure(figsize=(7,5), dpi= 100, facecolor='w', edgecolor='k')

plt.errorbar(x,y,dy,fmt='o')
plt.ylabel('Velocity (m/s)')
plt.title('Comparision of Measured Velocities - Unacclerated Motion')
```

### Accelerated Motion
Now that you’ve gotten some data to investigate unaccelerated motion, you need to collect data on accelerated motion. To do this, you will need to allow gravity to provide the acceleration by tilting the track.

1. Measure the distance between the supports on the airtrack. This will be your base length.
2. Measure the thickness of a 2x4 “shim.” This will be your height change.
3. Place the 2x4 “shim” under the foot nearest the acoustic sensor.
4. You will need to re-align the acoustic sensor using the methods described earlier.
5. Re-measure the uncertainty in position at the end of the track furthest from the sensor and try to reduce that uncertainty below 1 mm. Record this uncertainty in your Data Sheet.

When you are ready to take data:

1. Remove everything except one graph from the data area and make sure the y-axis is set to “Acceleration ($m / s^2$).”
2. Turn on the air, remove the paper, and allow the cart to slide down the airtrack and bounce off the far bumper.
3. **START** taking data ***immediately after*** the cart strikes the bumper.
4. **STOP** taking data ***just before*** the cart strikes the bumper again.
5. Turn off the air.
6. Rename this data set as “Tilted Airtrack.”

This time, let’s allow the Capstone software to analyze our data:

1. Click the arrow beside the summation symbol ($\Sigma$) and make sure that “Mean” and “Standard Deviation” are selected. Then click the summation symbol ($\Sigma$).
2. Move the legend so that it does not obscure the data points.
3. Type in an appropriate title and print this graph in Landscape Mode.
4. Click the summation symbol ($\Sigma$) again to remove the fit.
5. Click the axis button labeled “Acceleration ($m / s^2$)” and select “Velocity (m/s)”
6. Click the arrow next to to the “Fit” button and select “Linear.” Then click the “Fit” button.
7. Move the legend and linear fit windows so that they do not obscure the data points.
8. Type in an appropriate title and print this graph in Landscape Mode.
9. Click the “Fit” button to remove the fit.
10. Click the axis button labeled “Velocity (m/s)” and select “Position (m).”
11. Click the arrow next to to the “Fit” button, unselect “Linear” and select “Quadratic.” Then click the “Fit” button.
12. Move the legend and quadratic fit windows so that they do not obscure the data points.
13. Type in an appropriate title and print this graph in Landscape Mode.

```python
# Acceleration vs Time Graph (Tilted Airtrack)

# Reads the name of the csv file and gets the data
df = pd.read_csv("./Lab2_Inclined_Airtrack.csv")

# Prints information about the file
df.info()
df = df.dropna()

Delta_a = delta_Delta_x/(Delta_t**2)

print("")

# Defines the x and y values
time = df.filter(['Time (s) Run #1'], axis=1).dropna()
position = df.filter(['Acceleration (m/s²) Run #1'], axis=1).dropna()

fig=plt.figure(figsize=(7,5), dpi= 100, facecolor='w', edgecolor='k')

# Gets the data values for x and y
x_data = time.values.reshape(-1, 1)
y_data = position.values.reshape(-1, 1)
xi = df['Time (s) Run #1'].to_numpy()
yi = df['Acceleration (m/s²) Run #1'].to_numpy()

# Creates the base plot with titles
# df.plot(x='Time (s)', y='Position (m)', kind ='scatter')
plt.errorbar(x_data,y_data,Delta_a,Delta_t,'b.',label='Raw Data')
plt.ylabel('Acceleration (m/s²)')
plt.xlabel('Time (s)')
plt.title('Acceleration vs. Time')

# Takes the x and y values to make a trendline
#intercept1, slope1 = linear_fit_plot(x_data,y_data,0.0,20.0)
#intercept2, slope2 = linear_fit_plot(x_data,y_data,20.1,40.0)
#intercept3, slope3 = linear_fit_plot(x_data,y_data,40.1,50.0)
intercept1, slope1, dintercept1, dslope1 = linear_fit_plot_errors(xi,yi,x_low=0.3,x_high=4.0,labelstring="Linear Fit",linestring="r-")
# Adds the legend to the plot
plt.legend()

# Displays the plot
plt.show()

print("")
```

```python
# Position vs. Time Graph (accelerated)

# Defines the x and y values
time = df.filter(['Time (s) Run #1'], axis=1).dropna()
position = df.filter(['Position (m) Run #1'], axis=1).dropna()

# Create a figure of reasonable size and resolution, white background, black edge color
fig=plt.figure(figsize=(7,5), dpi= 100, facecolor='w', edgecolor='k')

# Gets the data values for x and y
x_data = time.values.reshape(-1, 1)
y_data = position.values.reshape(-1, 1)
xi = df['Time (s) Run #1'].to_numpy()
yi = df['Position (m) Run #1'].to_numpy()

# Creates the base plot with titles
# df.plot(x='Time (s)', y='Position (m)', kind ='scatter')
plt.errorbar(x_data,y_data,Delta_x,Delta_t,'b.',label='Raw Data')
plt.ylabel('Position (m)')
plt.xlabel('Time (s)')
plt.title('Position vs. Time')

# Takes the x and y values to make a trendline
#intercept, slope = linear_fit_plot(x_data,y_data)
a,b,c,da,db,dc = quadratic_fit_plot_errors(xi,yi,linestring='g-')

# Adds the legend to the plot
plt.legend()

# Displays the plot
plt.show()

print("")
```

```python
# Velocity vs. Time Graph (Accelerated Motion)

# Defines the x and y values
time = df.filter(['Time (s) Run #1'], axis=1).dropna()
velocity = df.filter(['Velocity (m/s) Run #1'], axis=1).dropna()

fig=plt.figure(figsize=(7,5), dpi= 100, facecolor='w', edgecolor='k')

# Gets the data values for x and y
x_data = time.values.reshape(-1, 1)
y_data = velocity.values.reshape(-1, 1)
xi = df['Time (s) Run #1'].to_numpy()
yi = df['Velocity (m/s) Run #1'].to_numpy()

# Creates the base plot with titles
# df.plot(x='Time (s)', y='Position (m)', kind ='scatter')
plt.errorbar(x_data,y_data,Delta_v,Delta_t,'b.',label='Raw Data')
plt.ylabel('Velocity (m/s)')
plt.xlabel('Time (s)')
plt.title('Velocity vs. Time')

# Takes the x and y values to make a trendline
#intercept1, slope1 = linear_fit_plot(x_data,y_data,0.0,3.0)
#intercept2, slope2 = linear_fit_plot(x_data,y_data,3.0,5.0)
#intercept3, slope3 = linear_fit_plot(x_data,y_data,5.0,10.0)
intercept2, slope2, dintercept2, dslope2 = linear_fit_plot_errors(xi,yi,x_low=0.3,x_high=4.0,labelstring="Linear Fit",linestring="r-")

#Adds the legend to the plot
plt.legend()

# Displays the plot
plt.show()

print("")
```

## Results and Conclusions
**Accelerated Motion Experiment**

Report the following data (with units and uncertainties):

- Velocity of cart without acceleration
- Angle that you tilted the airtrack
- Average Acceleration from the Acceleration vs. Time graph
- Average Acceleration calculated form the slope of the Velocity vs. Time graph
- Average Acceleration computed from the parabolic fit to your Position vs. Time graph

To compare the “A” value to the acceleration, you will need to know that the A, B, and C values are the constants for the following fit to the data:

\begin{equation}
x(t) = At^2 + Bt + C \nonumber
\end{equation}

Compare this equation to equation $\eqref{eq:x_of_t}$ to determine the acceleration, *a*.

```python
# Raw Data 4

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

# The distance between the two supports and the uncertainty in that distance
d = 1.000
uncertainty_d = 0.001

# The thickness of the 2x4 and the uncertainty in that thickness
h = .029
uncertainty_h = 0.001

# Your new uncertainty in cart position
uncertainty_x = 0.002

# The angle your track was tilted
theta_min = np.arctan((h-uncertainty_h)/(d+uncertainty_d))
theta_max = np.arctan((h+uncertainty_h)/(d-uncertainty_d))
theta = (theta_max+theta_min)/2.0
dtheta = (theta_max-theta_min)/2.0

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

print("d =", d, "m, 𝛿d =", uncertainty_d,"m")
print("")
print("h =", h, "m, 𝛿h =", uncertainty_h,"m")
print("")
print("𝛿x =", uncertainty_x,"m")
print("")
print("θ =", theta," +/- ",dtheta,"radians")

gravity = 9.80

a_max = gravity*np.sin(theta_max)
a_min = gravity*np.sin(theta_min)

a_theory = (a_max+a_min)/2.0
da_theory = (a_max-a_min)/2.0

print()
print ("Expected Acceleration = ",a_theory," +/- ",da_theory,"m/s^2")

a_experiment_1 = intercept1
da_experiment_1 = dintercept1

print()
print ("Average Acceleration from a vs. t graph = ",a_experiment_1," +/- ",da_experiment_1,"m/s^2")

a_experiment_2 = 2.0*a
da_experiment_2 = 2.0*da

print()
print ("Average Acceleration from x vs. t graph = ",a_experiment_2," +/- ",da_experiment_2,"m/s^2")

a_experiment_3 = slope2
da_experiment_3 = dslope2

print()
print ("Average Acceleration from v vs. t graph = ",a_experiment_3," +/- ",da_experiment_3,"m/s^2")

print()
print()

x = ['Theory','a vs. t','x vs. t','v vs. t']
dx = ['','','','']
y = np.array([a_theory,a_experiment_1,a_experiment_2,a_experiment_3])
dy = np.array([da_theory,da_experiment_1,da_experiment_2,da_experiment_3])

fig=plt.figure(figsize=(7,5), dpi= 100, facecolor='w', edgecolor='k')

plt.errorbar(x,y,dy,fmt='o')
plt.ylabel('Acceleration (m/s^2)')
plt.title('Comparision of Measured Accelerations to Theoretical Prediction')

```

# Lab Report

## EXPERIMENTAL PROCEDURE Section
You will need to write a few paragraphs to carefully describe how you set up your experiment. Include the following specific items:
- What did you do to ensure that your zero-acceleration and constant-acceleration experiments were free from unwanted sources of acceleration?
- How did you configure your measurement equipment so that you got the most accuracy?
- How did you set the track and determine the angle for the constant-acceleration experiment?
- How did you re-calibrate your measurement equipment after changing experiments?

**Remember:** This section is a description of what you did---not a set of instructions. Include a description of any problems you encountered and how you solved them.

**Errors/Uncertainties**
Did you have any systematic errors (i.e. were there sources of acceleration in either experiment that you could not remove)?

How could you reduce your errors/uncertainties?

## Appendix
- Include your signed raw data sheet

```python

```

```python

```