# Estimating text loss in The Old Norse fornaldarsögur

This Python notebook is a derivative of the one which accompanies the following publication:

> Mike Kestemont and Folgert Karsdorp, "Het Atlantis van de Middelnederlandse ridderepiek. Een schatting van het tekstverlies met methodes uit de ecodiversiteit". *Spiegel der letteren* (2020).

Adaptation to the current study is still a work in progress.

All figures and numbers were prepared using the code below. Future updates of the code and data will be managed in an open [Github repository](https://github.com/mikekestemont/chivalric_diversity). The code block below loads all (third-party) packages and modules necessary to run the module. These can be installed from the file `requirements.txt`:

    pip install -r requirements.txt

```python
from functools import partial
from itertools import product

import numpy as np
np.random.seed(12345)
from scipy.special import erfinv
import pandas as pd

import matplotlib.pyplot as plt
plt.style.use("tufte.mplstyle")
plt.rcParams["text.usetex"] = False
%matplotlib inline

import scipy.stats as stats
from scipy.special import gammaln
```

## Data

We load the data from the spreadsheet file `mnl.xlsx`:

```python
mnl = pd.read_excel('mnl.xlsx', header=None, names=('text', 'witness'))
mnl.head(10)
```

      text
      witness

      0
      Af Upplendinga konungum
      Den Arnamagnæanske Samling, Copenhagen, AM 544...

      1
      Áns saga bogsveigis
      Stofnun Árna Magnússonar í íslenskum fræðum, R...

      2
      Ásmundar saga kappabana
      Stofnun Árna Magnússonar í íslenskum fræðum, R...

      3
      Ásmundar saga kappabana
      Kungliga biblioteket, Stockholm, Perg. 4to nr 7

      4
      Bósa saga ok Herrauðs
      Stofnun Árna Magnússonar í íslenskum fræðum, R...

      5
      Bósa saga ok Herrauðs
      Stofnun Árna Magnússonar í íslenskum fræðum, R...

      6
      Bósa saga ok Herrauðs
      Stofnun Árna Magnússonar í íslenskum fræðum, R...

      7
      Bósa saga ok Herrauðs
      Stofnun Árna Magnússonar í íslenskum fræðum, R...

      8
      Egils saga einhenda ok Ásmundar berserkjabana
      Stofnun Árna Magnússonar í íslenskum fræðum, R...

      9
      Egils saga einhenda ok Ásmundar berserkjabana
      Stofnun Árna Magnússonar í íslenskum fræðum, R...

We are only interested in the count data, i.e. the number of witnesses per text (the technical term is "abundance data").

```python
mnl.groupby('text').size().sort_values(ascending=False).head()
```

    text
    Hrólfs saga Gautrekssonar     8
    Örvar-Odds saga               6
    Norna-Gests þáttr             6
    Þorsteins þáttr bæjarmagns    6
    Eiríks saga víðförla          5
    dtype: int64

The counts per text can be plotted as follows:

```python
fig, ax = plt.subplots(figsize=(10,18))
mnl.groupby('text').size().sort_values(ascending=True).plot.barh(ax=ax);
ax.set(xlabel='number of manuscripts', ylabel='',
       title='Distribution of texts of ON legendary sagas (known to me)')
ax.spines['right'].set_visible(False)
ax.spines['top'].set_visible(False)
plt.savefig('output/Fig1.jpeg', dpi=300, transparent=True)
```

Yet a different perspective is to list the size of the frequency bins that we can distinguish within the manuscript counts:

```python
types = mnl.groupby('text').size().sort_values(ascending=False).value_counts().sort_index()
types = types.to_frame(name='number of texts')
types['number of manuscripts'] = types.index
types.to_excel('output/Tab1.xlsx')
types
```

      number of texts
      number of manuscripts

      1
      11
      1

      2
      8
      2

      3
      4
      3

      4
      3
      4

      5
      2
      5

      6
      3
      6

      8
      1
      8

Finally, we define the auxiliary function `species_richness` to count the number of unique texts in the data (i.e. the number of non-zero counts):

```python
def species_richness(counts):
    return np.sum(counts > 0)

print('# unique texts:', species_richness(mnl.groupby('text').size()))
print('# witnesses:', len(mnl))
```

    # unique texts: 32
    # witnesses: 87

## Jackknife

The following function computes the first-order Jackknife estimate, on the basis of the abundance data in our data frame, as well as a confidence interval (.95 be default). This approach is detailed in the following paper:

> K. Burnham & W. Overton, "Robust Estimation of Population Size When Capture Probabilities Vary Among Animals". *Ecology* (1979), 927-936.

```python
def jackknife(data, conf_lvl=0.95):
    jack_stat = species_richness(data)

    x = np.array(sum([[i] * c for i, c in enumerate(data, 1)], []))
    index = np.arange(x.shape[0])

    vals = []
    for i in range(x.shape[0]):
        t = x[index != i]
        vals.append(species_richness(np.bincount(t)))

    mean_jack_stat = np.mean(vals)
    bias = (x.shape[0] - 1) * (mean_jack_stat - jack_stat)

    estimate = jack_stat - bias

    std_err = np.sqrt(
        (x.shape[0] - 1) *
        np.mean((mean_jack_stat - vals) *
                (mean_jack_stat - vals), axis=0)
    )

    z_score = np.sqrt(2.0) * erfinv(conf_lvl)
    conf_interval = estimate + z_score * np.array((-std_err, std_err))

    return estimate, std_err, conf_interval

results = jackknife(mnl.groupby('text').size())
print('jackknife-estimate (order=1):', results[0], results[-1])
```

    jackknife-estimate (order=1): 42.873563218390935 [36.83294758 48.91417886]

This implementation is verbose and uses an explicit `for`-loop, which iteratively leaves out observations and tracks the drops in diversity that follow from this operation. In the code blocks below we show that the same estimate can also be obtained in a fully analytical fashion. First we calculate the frequency counts for each unique text:

```python
num_per_text = mnl.groupby('text').size()
num_per_text
```

    text
    Af Upplendinga konungum                                 1
    Bósa saga ok Herrauðs                                   4
    Egils saga einhenda ok Ásmundar berserkjabana           3
    Eiríks saga víðförla                                    5
    Friðþjófs saga ins frækna                               2
    Frá Fornljóti og ættmönnum hans                         2
    Gautreks saga                                           2
    Gríms saga loðinkinna                                   3
    Göngu-Hrólfs saga                                       5
    Helga þáttr Þórissonar                                  4
    Hervarar saga ok Heiðreks                               2
    Hrólfs saga Gautrekssonar                               8
    Hálfdanar saga Brönufóstra                              3
    Hálfdanar saga Eysteinssonar                            2
    Hálfs saga ok Hálfsrekka                                1
    Illuga saga Gríðarfóstra                                1
    Ketils saga hængs                                       2
    Norna-Gests þáttr                                       6
    Ragnars saga loðbrókar                                  1
    Ragnarssona þáttr                                       1
    Sturlaugs saga starfsama                                3
    Sögubrot af nokkrum fornkonungum í Dana og Svíaveldi    1
    Sörla þáttr                                             1
    Tóka þáttr Tókasonar                                    1
    Völsunga saga                                           1
    Yngvars saga víðförla                                   2
    Áns saga bogsveigis                                     1
    Ásmundar saga kappabana                                 2
    Örvar-Odds saga                                         6
    Þjalar-Jóns saga                                        1
    Þorsteins saga Víkingssonar                             4
    Þorsteins þáttr bæjarmagns                              6
    dtype: int64

Next, we store the species richness (the number of unique texts) in $t$:

```python
t = species_richness(num_per_text)
t
```

    32

Then we set $s$ to the number of texts that are only attested in a single witness:

```python
s = sum(num_per_text == 1)
s
```

    11

Only the $s$ texts that occur once will affect the species richness during the iterative Jackknife procedure. We can therefore predict that we will obtain the following deviations when applying the bootstrap:

```python
mu = (((t - s) * t) + (s * (t - 1))) / t
mu
```

    31.65625

That means that we can calculate the bias as follows:

```python
bias = (t - 1) * (mu - t)
bias
```

    -10.65625

To account for this bias, we can subtract it from the original species richness in the observed data:

```python
t - bias
```

    42.65625

## Simple example

```python
counts = [5, 4, 3, 3, 1, 1, 1, 1, 1]
names = 'ABCDEFGHI'
data = zip(counts, names)
df = pd.DataFrame(zip(names, counts), columns=('name', 'mss'))
df.to_excel('output/Tab2.xlsx')
df
```

      name
      mss

      0
      A
      5

      1
      B
      4

      2
      C
      3

      3
      D
      3

      4
      E
      1

      5
      F
      1

      6
      G
      1

      7
      H
      1

      8
      I
      1

```python
print('total # of witnesses:', df['mss'].sum())
```

    total # of witnesses: 20

```python
species_richness(df['mss'])
```

    9

```python
jackknife(df['mss'])
```

    (13.75, 1.8874586088176875, array([10.0506491, 17.4493509]))

```python
data = np.array(df['mss'])
x = np.array(sum([[i]*c for i, c in enumerate(data, 1)], []))
tradition = [names[i - 1] for i in x]
print(tradition)
```

    ['A', 'A', 'A', 'A', 'A', 'B', 'B', 'B', 'B', 'C', 'C', 'C', 'D', 'D', 'D', 'E', 'F', 'G', 'H', 'I']

```python
bootstrap = []
for i in range(len(tradition)):
    tradition_ = [tradition[j] for j in range(len(tradition)) if i != j]

    bootstrap.append((
        (i + 1), tradition[i], ''.join(tradition_),
        len(set(tradition_)), len(set(tradition_)) - len(set(tradition))))
```

```python
df = pd.DataFrame(bootstrap, columns=('iteration', 'leftout', 'imputed tradition', 'richness', 'error'))
df.to_excel('output/Tab3.xlsx')
df
```

      iteration
      leftout
      imputed tradition
      richness
      error

      0
      1
      A
      AAAABBBBCCCDDDEFGHI
      9
      0

      1
      2
      A
      AAAABBBBCCCDDDEFGHI
      9
      0

      2
      3
      A
      AAAABBBBCCCDDDEFGHI
      9
      0

      3
      4
      A
      AAAABBBBCCCDDDEFGHI
      9
      0

      4
      5
      A
      AAAABBBBCCCDDDEFGHI
      9
      0

      5
      6
      B
      AAAAABBBCCCDDDEFGHI
      9
      0

      6
      7
      B
      AAAAABBBCCCDDDEFGHI
      9
      0

      7
      8
      B
      AAAAABBBCCCDDDEFGHI
      9
      0

      8
      9
      B
      AAAAABBBCCCDDDEFGHI
      9
      0

      9
      10
      C
      AAAAABBBBCCDDDEFGHI
      9
      0

      10
      11
      C
      AAAAABBBBCCDDDEFGHI
      9
      0

      11
      12
      C
      AAAAABBBBCCDDDEFGHI
      9
      0

      12
      13
      D
      AAAAABBBBCCCDDEFGHI
      9
      0

      13
      14
      D
      AAAAABBBBCCCDDEFGHI
      9
      0

      14
      15
      D
      AAAAABBBBCCCDDEFGHI
      9
      0

      15
      16
      E
      AAAAABBBBCCCDDDFGHI
      8
      -1

      16
      17
      F
      AAAAABBBBCCCDDDEGHI
      8
      -1

      17
      18
      G
      AAAAABBBBCCCDDDEFHI
      8
      -1

      18
      19
      H
      AAAAABBBBCCCDDDEFGI
      8
      -1

      19
      20
      I
      AAAAABBBBCCCDDDEFGH
      8
      -1

```python
mean_estimate = np.mean(df['richness'])
print('Average estimate:', mean_estimate)
print('Bias:', mean_estimate - 9)
```

    Average estimate: 8.75
    Bias: -0.25

```python
bias = 19 * (mean_estimate - 9)
bias
```

    -4.75

```python
corrected = 9 - bias
corrected
```

    13.75

```python
conf_lvl = .95

std_err = np.sqrt(
    19 * np.mean((mean_estimate - df['richness']) *
                 (mean_estimate - df['richness']), axis=0))

z_score = np.sqrt(2.0) * erfinv(conf_lvl)
conf_interval = corrected + z_score * np.array((-std_err, std_err))
conf_interval
```

    array([10.0506491, 17.4493509])

## Chao1

In the paper we eventually opt for the more recent, non-parametric formula "Chao1", which is described in this paper:

> A. Chao & L. Jost, ‘Estimating diversity and entropy profiles via discovery rates of new species". *Methods in Ecology and Evolution* (2015), 873-882.

Because we have "doubletons" in our data, we use can the following formula, where:
- $\hat{f_0}$ is the (theoretical) number of non-observed species/texts;
- $f_1$ is the number of species/texts attested exactly once ("singletons");
- $f_2$ is the number of species/texts attested exactly twice ("doubletons");
- $n$ is the total number of individuals/manuscripts in the observed data.

\begin{equation}
\hat{f_0} = \frac{(n - 1)}{n} \frac{f_1^2}{2f_2}
\end{equation}

The code block below returns the full, theoretical species richness as etimated by Chao1, i.e. it adds the estimated $\hat{f_0}$ to the species richness that was observed in the sample:

```python
def chao_richness(x):
    x, n = x[x > 0], x.sum()
    t = x.shape[0]
    f1, f2 = (x == 1).sum(), (x == 2).sum()
    return t + (n - 1) / n * ((f1 ** 2 / 2 / f2) if f2 > 0 else (f1 * (f1 - 1) / 2))
```

If we apply this function to our data, we obtain an even higher (but arguably more realistic) estimate of the loss in textual diversity for this literature. Note, however, that this estimate is still a theoretical *minimum estimate*, since the original loss could still be higher.

```python
chao_richness(num_per_text)
```

    39.47557471264368

Instead of reporting just this number, we apply a bootstrapped procedure in which we sample from the material using a multinomial distribution (see the Appendix Chao and Jost, 2015) and apply Chao1 to the resulting samples. This procedure allows us to calculate a .95 confidence interval for this value.

```python
def bt_prob(x):
    x, n = x[x > 0], x.sum()
    f1, f2 = (x == 1).sum(), (x == 2).sum()
    C = 1 - f1 / n * (((n - 1) * f1 / ((n - 1) * f1 + 2 * f2)) if f2 > 0 else
                      ((n - 1) * (f1 - 1) / ((n - 1) * (f1 - 1) + 2)) if f1 > 0 else
                      0)
    W = (1 - C) / np.sum(x / n * (1 - x / n) ** n)
    p = x / n * (1 - W * (1 - x / n) ** n)
    f0 = np.ceil(((n - 1) / n * f1 ** 2 / (2 * f2)) if f2 > 0 else
                 ((n - 1) / n * f1 * (f1 - 1) / 2))
    p0 = (1 - C) / f0
    p = np.hstack((p, np.array([p0 for i in np.arange(f0)])))
    return p

def bootstrap(x, n_iter=1000, conf=.95):
    # define a multinomial probability distribution
    # for the bootstrap procedure to sample from:
    p, n = bt_prob(x), x.sum()
    data_bt = np.random.multinomial(n, p, n_iter)

    pro = np.array([chao_richness(row) for row in data_bt])

    pro_mean = pro.mean(0)

    lci_pro = -np.quantile(pro, (1 - conf) / 2, axis=0) + pro_mean
    uci_pro = np.quantile(pro, 1 - (1 - conf) / 2, axis=0) - pro_mean

    sd_pro = np.std(pro, axis=0)

    pro = pro_mean - pro
    return (lci_pro, uci_pro, sd_pro, pro)
```

```python
def chao_estimate(x, n_iter=1000, conf=0.95):
    pro = chao_richness(x)
    (lci_pro, uci_pro, sd_pro, bt_pro) = bootstrap(x, n_iter=n_iter, conf=conf)
    lci_pro, uci_pro = pro - lci_pro, pro + uci_pro
    bt_pro = pro - bt_pro
    return (lci_pro, uci_pro, bt_pro, pro)
```

The following block applies this bootstrapped procedure to obtain the final estimates:

```python
lci_pro, uci_pro, bt_pro, pro = chao_estimate(num_per_text, n_iter=10000)
print('pro:', pro)
print('lci_pro:', lci_pro)
print('uci_pro:', uci_pro)
```

    pro: 39.47557471264368
    lci_pro: 28.554685801360193
    uci_pro: 63.66962833009582

The array `bt_pro` contains the estimates that were collected during the bootstrap (1,000 iterations by default). Below, we plot the distribution of these numbers using a rainplot: [removing rain_alpha =.3 argument on pt.RainCloud() because it is showing as invalid]

```python
import ptitprince as pt

fig, ax = plt.subplots(figsize=(8, 6))

d = list([(x, 'bootstrap') for x in bt_pro])
bt = pd.DataFrame(d, columns=('bootstrap', 'type'))

pt.RainCloud(
    data=bt, x="type", y="bootstrap", ax=ax,
    orient="h", alpha=.8, bw=.2, rain_alpha=.3, palette="Greys"
)
ax.axvline(pro, c='black', ls='--')
ax.axvline(uci_pro, c='darkgrey', ls='--')
ax.axvline(lci_pro, c='darkgrey', ls='--')
ax.spines['right'].set_visible(False)
ax.spines['top'].set_visible(False)
ax.spines['left'].set_visible(False)
ax.set_yticks([])
ax.set_ylabel('')
plt.savefig('output/Fig2.png', dpi=300, transparent=True)
```

[These findings refer to the original study of Dutch romances and have not yet been analyzed for the FAS] The idea that there were at least 100 texts is not completely unlikely, but it is a very
conservative estimate, at the very bottom of the probability continuum. The estimate of ~148 manuscripts (or more) is much more plausible, which would mean that *at least half of
the chivalric texts have been lost*.  Just as 100 is an extremely optimistic
estimate, ~219 is the most pessimistic estimate: in that
case, only a third of the ever available chivalric epics would have been persisted through
time, which is quite a dramatic, but not entirely unrealistic figure.

## Species accumulation curve

In what preceded, we have investigated how many unique texts may have been lost, or, more positively, how many unique texts we may have not yet seen. In this concluding section, we investigate how many texts should be retrieved before we arrive at this diversity estimate. This new estimate provides us with information about the total population size, i.e. the total number of text witnesses. We follow Hsieh, Ma and Chao (2016) to compute this estimate using "Rarefaction Extrapolation". For details about this method, see:

> Hsieh, Ma and Chao (2016): iNEXT: an R package for rarefaction and extrapolation ofspecies diversity. *Methods in Ecology and Evolution*, 7, 1451–1456.

```python
def bootstrap_re(x, fn=chao_richness, n_iter=1000, conf=.95):
    # define a multinomial probability distribution
    # for the bootstrap procedure to sample from:
    p, n = bt_prob(x), x.sum()
    data_bt = np.random.multinomial(n, p, n_iter)

    Dq = fn(x)

    pro = np.array([fn(row) for row in data_bt])

    error = stats.norm.ppf(1 - (1 - conf) / 2) * np.std(pro, 0)
    lci_pro = Dq - error
    uci_pro = Dq + error

    sd_pro = np.std(pro, axis=0)

    return (lci_pro, uci_pro, sd_pro, Dq, )

def rarefaction_extrapolation(x, max_steps):
    x, n = x[x > 0], x.sum()
    def _sub(m):
        if m <= n:
            return np.sum(1 - np.array(
                [np.exp(gammaln(n - i + 1) + gammaln(n - m + 1) -
                        gammaln(n - i - m + 1) - gammaln(n + 1)) if i <= (n - m) else
                 0 for i in x]))
        else:
            S = (x > 0).sum()
            f1, f2 = (x == 1).sum(), (x == 2).sum()
            f0 = ((n - 1) / n * f1 * (f1 - 1) / 2) if f2 == 0 else ((n - 1) / n * f1**2 / 2 / f2)
            A = n * f0 / (n * f0 + f1)
            return S if f1 == 0 else (S + f0 * (1 - A**(m - n)))
    return np.array([_sub(mi) for mi in range(1, max_steps)])
```

```python
counts = np.bincount(mnl.groupby('text').size())[1:] # ignore zero
x = np.array(sum([[i] * c for i, c in enumerate(counts, 1)], []))
```

Here too we use a bootstrap method with 100 samples:

```python
max_steps = 1000
lci_pro, uci_pro, sd_pro, Dq = bootstrap_re(
    x,
    fn=partial(rarefaction_extrapolation, max_steps=max_steps),
    n_iter=100
)
```

```python
steps = np.arange(1, max_steps)
interpolated = np.arange(1, max_steps) < x.sum()

fig, ax = plt.subplots(figsize=(8, 6))
ax.plot(steps[interpolated], Dq[interpolated], color='C0')
ax.plot(x.sum(), Dq[x.sum() - 1], 'o')
ax.plot(steps[~interpolated], Dq[~interpolated], '--', color='C0')
ax.fill_between(steps, lci_pro, uci_pro, alpha=0.3)
ax.grid()
ax.set(xlabel='# of manuscripts', ylabel='# texts', title='Species Accumulation Curve')
ax.spines['right'].set_visible(False)
ax.spines['top'].set_visible(False)

plt.savefig('output/Fig3.png', dpi=300, transparent=True)
```

This graph would suggest a rough estimate of 39 works and 200-300 witnesses. However the graph forms a significantly different shape than the original study and requires closer scrutiny.

```python

```


```python
from IPython.display import HTML

HTML('''
<form action="javascript:code_toggle()"><input type="submit" value="Code Toggle"></form>''')
```

<form action="javascript:code_toggle()"><input type="submit" value="Code Toggle"></form>

```javascript
%%javascript
    MathJax.Hub.Config({
      TeX: { equationNumbers: { autoNumber: "AMS" } }
    });
```

    <IPython.core.display.Javascript object>

```python
from IPython.display import HTML

HTML('''
<a href="https://raw.githubusercontent.com/{{ site.links.repo }}/master/benchmarks/benchmark6-hackathon.ipynb"
   download="benchmark6-hackathon.ipynb">
<button type="submit">Download Notebook</button>
</a>''')
```

<a href="https://raw.githubusercontent.com/{{ site.links.repo }}/master/benchmarks/benchmark6-hackathon.ipynb"
   download="benchmark6-hackathon.ipynb">
<button type="submit">Download Notebook</button>
</a>

# Benchmark Problem 6: Electrostatics

```python
from IPython.display import HTML

HTML('''{% include jupyter_benchmark_table.html num="[6]" revision=0 %}''')
```

{% include jupyter_benchmark_table.html num="[6]" revision=0 %}

See the Overleaf document entitled ["Phase Field Benchmark Problems for Dendritic Growth and Linear Elasticity"][overleaf] for more details about the benchmark problems. Furthermore, read [the extended essay][benchmarks] for a discussion about the need for benchmark problems.

[benchmarks]: ../
[overleaf]: https://www.overleaf.com/read/nqjkdwyybvdz

# Overview

Diffusion of a charged species is often modeled with the phase field approach, such as for  batteries, electrodeposition, and electromigration.  This benchmark problem incorporates the first benchmark problem for spinodal decomposition [Jokisaari 2017][jokisaari2017benchmark] and extends it to incorporate coupling with electrostatics.

## Governing Equations

In this problem, two variables are used: $c$, the concentration field of our charged species, and $\Phi$, the electric potential field.  The free energy of the system is given as

\begin{equation}
F=\int_{V} \left[\frac{\kappa}{2} |\nabla c|^2+f_{chem}(c)+f_{elec}(c,\Phi)\right]\,dV,
\end{equation}

where $\kappa$ is the gradient energy coefficient, $f_{chem}$ is the chemical free energy, and $f_{elec}$ is the electrostatic coupling energy.  Here, $f_{chem}$ is a symmetric double-well function with minima between $0<c<1$,

\begin{equation}
f_{chem} = \rho (c-c^{\alpha})^2(c^{\beta}-c)^2,
\end{equation}

where $\rho$ controls the height of the double-well and $c^{\alpha}$ and $c^{\beta}$ are the compositions at which the energy is minimum.  In addition, the electrostatic coupling energy is given as

\begin{equation}
f_{elec}=\frac{k\, c \, \Phi}{2},
\end{equation}

where $k$ is a constant.

The time evolution of the system is described by the Cahn-Hilliard equation with the additional constraint that the Poisson equation must be satisfied at every point in the system at each time step ([Guyer 2004a][guyer2004phase], [Guyer 2004b][guyer2004phaseII]).  To avoid a lengthy derivation, we simply present the equations below and suggest the readers study the given references.  The Cahn-Hillard equation is given as

\begin{equation}
\frac{\partial c}{\partial t} = \nabla \cdot \left\lbrace M \nabla \left( 2 \rho(c-c^{\alpha})(c^{\beta}-c)(c^{\alpha}+c^{\beta}-2c) -\kappa \nabla^2 c + k \, \Phi \right) \right\rbrace,
\end{equation}

where $M$ is the mobility. The dynamics of electric relaxation occur at a much faster time scale than the diffusion of the charged species.  In this case, the Poisson equation is solved at each time step,

\begin{equation}
\nabla^2 \Phi = \frac{-k\, c}{\epsilon},
\end{equation}

where $\epsilon$ is the permittivity.

As in the [spinodal benchmark problem]({{ site.baseurl }}/benchmarks/benchmark1.ipynb), $c^{\alpha}=0.3$, $c^{\beta}=0.7$, $\kappa=2$, $\rho=5$, and $M=5$.  In addition, $k=0.09$ and $\epsilon=90$.

## Domain

In this problem, the system consists of a domain which is grounded on one side and with a voltage applied on the other.  Two different 2D computational domains are given, shown below (the boundary conditions for $\Phi$ are schematically illustrated).  The first consists of a square domain, while the second consists of a half-circle with a radius of 50 units and centered at (50, 50), which is attached to a rectangle of 50 units wide by 100 units tall.  The curved boundary is the right-hand boundary, while the straight domain edges from (0, 0) to (50, 0) and (0, 100) to (50, 100) are the bottom and top boundaries, respectively. Schematic illustrations of the 2D computational domains and boundary conditions of $\Phi$ (indicated by hatching) for the electrochemical benchmark problem are shown in Figures 1 and 2.

### Figure 1: Domain (a)

### Figure 2: Domain (b)

## Boundary Conditions

We impose no-flux boundary conditions on all boundaries, while $\Phi=0$ for the left boundary, $\nabla \Phi \cdot \hat{\textbf{n}}=0$ for the top and bottom boundaries,  and the right boundary is given by

\begin{equation}
\Phi(x|_{boundary},y) = \sin(y/7),
\end{equation}

where $x|_{boundary}=100$ for domain (a), and varies from 50 to 100 in domain (b).
The initial condition for $c$ is specified by

\begin{eqnarray}
c\left(x,y\right) & = & c_{0}+c_{1}\left\{\cos\left(0.2x\right)\cos\left(0.11y\right)+\left[\cos\left(0.13x\right)\cos\left(0.087y\right)\right]^{2}\right. \\
 &  & \left.+\cos\left(0.025x-0.15y\right)\cos\left(0.07x-0.02y\right)\right\},
\end{eqnarray}

where $c_{0}=0.5$ and $c_{1}=0.04$ (you may recognize this as the initial conditions for the [spinodal decomposition benchmark]({{ site.baseurl }}/benchmarks/benchmark1.ipynb) problem with a slightly different parameterization).

# Submission Guidelines

Both variations (a) and (b) should be run for 400 time units. The following data should be collected from each simulation,

 - the free energy integrated across the entire domain, $F$, collected as frequently as is feasible.

 - the $c$ and $\Phi$ at $t=400$ along the $x=50$ cut plane

 - the $c$ and $\Phi$ at $t=400$ along the $y=50$ cut plane

Thus each submission requires three data files for each variation,

 - a CSV or JSON upload labeled as `free_energy` with column titles of `time` and `free_energy`

 - a CSV or JSON upload labeled as `x_cut_plane` with column titles of `y`, `concentration` and `potential`

 - a CSV or JSON upload labeled as `y_cut_plane` with column titles of `x`, `concentration` and `potential`

Further data to upload can include images of $c$ and $\Phi$ at different times or youtube videos of the evolution. These are not required, but will help others view your work.

[jokisaari2017benchmark]: https://doi.org/10.1016/j.commatsci.2016.09.022
[guyer2004phase]: https://doi.org/10.1103/PhysRevE.69.021603
[guyer2004phaseII]: https://doi.org/10.1103/PhysRevE.69.021604

```python

```


# Deep learning sweet spots

As of 2020, these are the types of problems DL is good at:

## Computer vision

Caveat: models dont perform well for an image which is not similar to those used in the training set. For example, trying to make a prediction on a black and white image, but your training data consisted only of color images.

Data augmentation might help with the problem by artificially creating new training instances (ie: rotating images, changing them to black and white, etc)

# Data preparation

## Structuring your experiment data ##

Always divide your data into the following blocks:

* Split your data into a _training set_ and a _test set_
* Split the training set further into _training set_ and a _validation_ set

When evaluating the correctness of your results, always validate with the **validation** set. Do not use the test set until you think your model is ready for prime time.

## Considerations for splitting data

### Set aside a validation set and a test set

The idea is for the validation set to me used to tune the model. However, the tunning process must never see the test set you set apart. This is to ensure that you are not indirectly overfitting the model by looking too often at this last bastion of data. For example: if you have a vendor doing a model for you, set aside a test set for yourself, which the vendor never gets to see. Once the vendor's model is finished, use this test set to control the performance according to your own metrics.

### Random shuffling

If you have a data set ordered in classes, ensure you do random shuffling. I.e: if you have a dataset representing single digits (classes 0-9) and you split your data 80/20 without random shuffling, your training set will not have instances of classes 8-9.

### Time series

Do not do random shuffling if you have time series data. Otherwise, the progression of time will be lost

### Data redundancy

Remove data dups. If you shuffle your data and it das dups, these dups might end up in both the test, validation and training sets.

### Data representation

Think about creative ways in which your data can be re-represented, so that it is easier for a model to analyze. For example, sound can be transformed into an image by creating its spectrogram. So your sound analysis problem suddenly becomes a computer vision problem.

## K-fold Cross-validation

If your data set is too small, splitting the data set might cause your sets to loose statistical significance. In this case, use the K-fold approach to split the data into _x_ folds, each of which will be evaluated individually and the result of those runs averaged to produce a more solid result.

\begin{align}
CV_{(k)} = \frac{1}{k} \sum_{i=1}^k Loss_{i}
\end{align}

* _k_ is the number of folds, typically 10
* _Loss_ function typically is mean squared errors, log-loss or accuracy

## Epochs

This is how many times the model will see each data point.

# Data processing

## Vectorization

All your data must be expressed as tensors (either floating point numbers, or ints).

## Data normalization

Features must have values which have similar ranges. Typically, you will try to have all input and output values to have a range of 0-1. As a recommendations, values should have mean 0 and standard deviation 1:

For a 2D matrix:
```
x -= x.mean(axis=0)
x /= x.std(axis=0)
```

# Handling overfitting

A model should _generalize_ well. After a certain number of training iterations, the model will start learning things which are particular to the training set but not general. To deal with overfitting:

* Get more training data
* Invest in regularization

## Regularization

Impose constraints to the network. These constraints will make it more difficult for the network to learn particularities of the training set.

### Reduce the size of the network

Reduce number of layers and number of units per layer. Recommendation: start with fewer layers and parameters, and start making your architecture more complex until you get to a point of diminishing returns.

### Weight regularization

Impose a penalty for having large weights in the network, thus forcing the weights across the network to be more regular. This is done by adding to the loss function so that it penalizes larger weights.

### Dropout

Set some of the values in the output vector to zero, at random.

```python

```


\title{Memories in myHDL}
\author{Steven K Armour}
\maketitle

```python
from myhdl import *
from myhdlpeek import Peeker
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline

from sympy import *
init_printing()

import random

#https://github.com/jrjohansson/version_information
%load_ext version_information
%version_information myhdl, myhdlpeek, numpy, pandas, matplotlib, sympy, random
```

Software	Version

Python	3.6.4 64bit [GCC 7.2.0]

IPython	6.2.1

OS	Linux 4.13.0 45 generic x86_64 with debian stretch sid

myhdl	0.10

myhdlpeek	0.0.6

numpy	1.13.3

pandas	0.21.1

matplotlib	2.1.1

sympy	1.1.1

random	The 'random' distribution was not found and is required by the application

Fri Jun 29 23:35:28 2018 MDT

```python
#helper  functions to read in the .v and .vhd generated files into python
def VerilogTextReader(loc, printresult=True):
    with open(f'{loc}.v', 'r') as vText:
        VerilogText=vText.read()
    if printresult:
        print(f'***Verilog modual from {loc}.v***\n\n', VerilogText)
    return VerilogText

def VHDLTextReader(loc, printresult=True):
    with open(f'{loc}.vhd', 'r') as vText:
        VerilogText=vText.read()
    if printresult:
        print(f'***VHDL modual from {loc}.vhd***\n\n', VerilogText)
    return VerilogText
```

RTL and Implimentation Schamatics are from Xilinx Vivado 2016.1

# Read Only Memory (ROM)
ROM is a memory structure that holds static information that can only be read from. In other words, these are hard-coded instruction memory. That *should* never change. Furthermore, this data is held in a sort of array; for example, we can think of a python tuple as a sort of read-only memory since the content of a tuple is static and we use array indexing to access a certain portions of the memory.

```python
#use type casting on list genrator to store 0-9 in 8bit binary
TupleROM=tuple([bin(i, 8) for i in range(10)])
TupleROM
```

    ('00000000',
     '00000001',
     '00000010',
     '00000011',
     '00000100',
     '00000101',
     '00000110',
     '00000111',
     '00001000',
     '00001001')

```python
f'accesss location 6: {TupleROM[6]}, read contents of location 6 to dec:{int(TupleROM[6], 2)}'
```

    'accesss location 6: 00000110, read contents of location 6 to dec:6'

And if we try writing to the tuple we will get an error

```python
#TupleROM[6]=bin(16,2)
```

# Random and Sequntial Access Memory
So to start off with the Random in RAM does not mean Random in a proplositc sence. It refares to Random as in you can randomly access any part of the data array opposed to the now specility sequantil only memory wich are typicly made with a counter or stat machine to sequance that acation

# HDL Memeorys
in HDL ROM the data is stored a form of a D flip flop that are structerd in a sort of two diminal array where one axis is the address and the other is the content and we use a mux to contorl wich address "row" we are trying to read. There fore we have two signals: address and content. Where the address contorls the mux.

## ROM Preloaded

```python
@block
def ROMLoaded(addr, dout):
    """
    A ROM laoded with data already incoded in the structer
    insted of using myHDL inchanced parmter loading

    I/O:
        addr(Signal>4): addres; range is from 0-3
        dout(Signal>4): data at each address
    """

    @always_comb
    def readAction():
        if addr==0:
            dout.next=3
        elif addr==1:
            dout.next=2
        elif addr==2:
            dout.next=1

        elif addr==3:
            dout.next=0

    return instances()

```

### myHDL Testing

```python
Peeker.clear()
addr=Signal(intbv(0)[4:]); Peeker(addr, 'addr')
dout=Signal(intbv(0)[4:]); Peeker(dout, 'dout')

DUT=ROMLoaded(addr, dout)

def ROMLoaded_TB():
    """Python Only Testbench  for `ROMLoaded`"""
    @instance
    def stimules():
        for i in range(3+1):
            addr.next=i
            yield delay(1)
        raise StopSimulation()

    return instances()

sim = Simulation(DUT, ROMLoaded_TB(), *Peeker.instances()).run()
```

```python
Peeker.to_wavedrom()
```

```python
Peeker.to_dataframe()
```

      addr
      dout

      0
      0
      3

      1
      1
      2

      2
      2
      1

      3
      3
      0

### Verilog Code

```python
DUT.convert()
VerilogTextReader('ROMLoaded');
```

    ***Verilog modual from ROMLoaded.v***

     // File: ROMLoaded.v
    // Generated by MyHDL 0.10
    // Date: Wed Jun 27 16:44:05 2018

    `timescale 1ns/10ps

    module ROMLoaded (
        addr,
        dout
    );
    // A ROM laoded with data already incoded in the structer
    // insted of using myHDL inchanced parmter loading
    //
    // I/O:
    //     addr(Signal>4): addres; range is from 0-3
    //     dout(Signal>4): data at each address

    input [3:0] addr;
    output [3:0] dout;
    reg [3:0] dout;

    always @(addr) begin: ROMLOADED_READACTION
        case (addr)
            'h0: begin
                dout = 3;
            end
            'h1: begin
                dout = 2;
            end
            'h2: begin
                dout = 1;
            end
            'h3: begin
                dout = 0;
            end
        endcase
    end

    endmodule

**ROMLoaded RTL**

**ROMLoaded Synthesis**

### Verilog Testbench

```python
@block
def ROMLoaded_TBV():
    """Verilog Only Testbench  for `ROMLoaded`"""
    clk = Signal(bool(0))
    addr=Signal(intbv(0)[4:])
    dout=Signal(intbv(0)[4:])

    DUT=ROMLoaded(addr, dout)

    @instance
    def clk_signal():
        while True:
            clk.next = not clk
            yield delay(10)

    @instance
    def stimules():
        for i in range(3+1):
            addr.next=i
            #yield delay(1)
            yield clk.posedge
        raise StopSimulation

    @always(clk.posedge)
    def print_data():
        print(addr,  dout)

    return instances()

#create instaince of TB
TB=ROMLoaded_TBV()
#convert to verilog with reintilzed values
TB.convert(hdl="Verilog", initial_values=True)
#readback the testbench results
VerilogTextReader('ROMLoaded_TBV');
```

    <class 'myhdl._Signal._Signal'> <class '_ast.Name'>
    <class 'myhdl._Signal._Signal'> <class '_ast.Name'>
    ***Verilog modual from ROMLoaded_TBV.v***

     // File: ROMLoaded_TBV.v
    // Generated by MyHDL 0.10
    // Date: Wed Jun 27 16:44:08 2018

    `timescale 1ns/10ps

    module ROMLoaded_TBV (

    );
    // Verilog Only Testbench  for `ROMLoaded`

    reg [3:0] addr = 0;
    reg [3:0] dout = 0;
    reg clk = 0;

    always @(addr) begin: ROMLOADED_TBV_ROMLOADED0_0_READACTION
        case (addr)
            'h0: begin
                dout = 3;
            end
            'h1: begin
                dout = 2;
            end
            'h2: begin
                dout = 1;
            end
            'h3: begin
                dout = 0;
            end
        endcase
    end

    initial begin: ROMLOADED_TBV_CLK_SIGNAL
        while (1'b1) begin
            clk <= (!clk);
            # 10;
        end
    end

    initial begin: ROMLOADED_TBV_STIMULES
        integer i;
        for (i=0; i<(3 + 1); i=i+1) begin
            addr <= i;
            @(posedge clk);
        end
        $finish;
    end

    always @(posedge clk) begin: ROMLOADED_TBV_PRINT_DATA
        $write("%h", addr);
        $write(" ");
        $write("%h", dout);
        $write("\n");
    end

    endmodule

## ROM Preloaded via Parmter

With myHDL we can dynamicaly load the contents that will be hard coded in the convertion to verilog/VHDL wich is an ammazing benfict for devlopment as is sean here

```python
@block
def ROMParmLoad(addr, dout, CONTENT):
    """
    A ROM laoded with data from CONTENT input tuple

    I/O:
        addr(Signal>4): addres; range is from 0-3
        dout(Signal>4): data at each address
    Parm:
        CONTENT: tuple size 4 with contende must be no larger then 4bit
    """
    @always_comb
    def readAction():
        dout.next=CONTENT[int(addr)]

    return instances()

```

### myHDL Testing

```python
Peeker.clear()
addr=Signal(intbv(0)[4:]); Peeker(addr, 'addr')
dout=Signal(intbv(0)[4:]); Peeker(dout, 'dout')
CONTENT=tuple([i for i in range(4)][::-1])

DUT=ROMParmLoad(addr, dout, CONTENT)

def ROMParmLoad_TB():
    """Python Only Testbench  for `ROMParmLoad`"""
    @instance
    def stimules():
        for i in range(3+1):
            addr.next=i
            yield delay(1)
        raise StopSimulation()

    return instances()

sim = Simulation(DUT, ROMParmLoad_TB(), *Peeker.instances()).run()
```

```python
Peeker.to_wavedrom()
```

```python
Peeker.to_dataframe()
```

      addr
      dout

      0
      0
      3

      1
      1
      2

      2
      2
      1

      3
      3
      0

### Verilog Code

```python
DUT.convert()
VerilogTextReader('ROMParmLoad');
```

    ***Verilog modual from ROMParmLoad.v***

     // File: ROMParmLoad.v
    // Generated by MyHDL 0.10
    // Date: Tue Jun 26 23:15:16 2018

    `timescale 1ns/10ps

    module ROMParmLoad (
        addr,
        dout
    );
    // A ROM laoded with data from CONTENT input tuple
    //
    // I/O:
    //     addr(Signal>4): addres; range is from 0-3
    //     dout(Signal>4): data at each address
    // Parm:
    //     CONTENT: tuple size 4 with contende must be no larger then 4bit

    input [3:0] addr;
    output [3:0] dout;
    reg [3:0] dout;

    always @(addr) begin: ROMPARMLOAD_READACTION
        case (addr)
            0: dout = 3;
            1: dout = 2;
            2: dout = 1;
            default: dout = 0;
        endcase
    end

    endmodule

**ROMParmLoad RTL**

**ROMParmLoad Synthesis**

### Verilog Testbench

```python
@block
def ROMParmLoad_TBV():
    """Verilog Only Testbench  for `ROMParmLoad`"""
    clk=Signal(bool(0))
    addr=Signal(intbv(0)[4:])
    dout=Signal(intbv(0)[4:])
    CONTENT=tuple([i for i in range(4)][::-1])

    DUT=ROMParmLoad(addr, dout, CONTENT)

    @instance
    def clk_signal():
        while True:
            clk.next = not clk
            yield delay(1)

    @instance
    def stimules():
        for i in range(3+1):
            addr.next=i
            yield clk.posedge
        raise StopSimulation

    @always(clk.posedge)
    def print_data():
        print(addr,  dout)

    return instances()

#create instaince of TB
TB=ROMParmLoad_TBV()
#convert to verilog with reintilzed values
TB.convert(hdl="Verilog", initial_values=True)
#readback the testbench results
VerilogTextReader('ROMParmLoad_TBV');
```

    <class 'myhdl._Signal._Signal'> <class '_ast.Name'>
    <class 'myhdl._Signal._Signal'> <class '_ast.Name'>
    ***Verilog modual from ROMParmLoad_TBV.v***

     // File: ROMParmLoad_TBV.v
    // Generated by MyHDL 0.10
    // Date: Tue Jun 26 23:15:24 2018

    `timescale 1ns/10ps

    module ROMParmLoad_TBV (

    );

    reg [3:0] addr = 0;
    reg [3:0] dout = 0;
    reg clk = 0;

    always @(addr) begin: ROMPARMLOAD_TBV_ROMPARMLOAD0_0_READACTION
        case (addr)
            0: dout = 3;
            1: dout = 2;
            2: dout = 1;
            default: dout = 0;
        endcase
    end

    initial begin: ROMPARMLOAD_TBV_CLK_SIGNAL
        while (1'b1) begin
            clk <= (!clk);
            # 1;
        end
    end

    initial begin: ROMPARMLOAD_TBV_STIMULES
        integer i;
        for (i=0; i<(3 + 1); i=i+1) begin
            addr <= i;
            @(posedge clk);
        end
        $finish;
    end

    always @(posedge clk) begin: ROMPARMLOAD_TBV_PRINT_DATA
        $write("%h", addr);
        $write(" ");
        $write("%h", dout);
        $write("\n");
    end

    endmodule

we can also create rom that insted of being asynchronous is synchronous

```python
@block
def ROMParmLoadSync(addr, dout, clk, rst, CONTENT):
    """
    A ROM laoded with data from CONTENT input tuple

    I/O:
        addr(Signal>4): addres; range is from 0-3
        dout(Signal>4): data at each address
        clk (bool): clock feed
        rst (bool): reset
    Parm:
        CONTENT: tuple size 4 with contende must be no larger then 4bit
    """
    @always(clk.posedge)
    def readAction():
        if rst:
            dout.next=0
        else:
            dout.next=CONTENT[int(addr)]

    return instances()

```

```python
Peeker.clear()
addr=Signal(intbv(0)[4:]); Peeker(addr, 'addr')
dout=Signal(intbv(0)[4:]); Peeker(dout, 'dout')
clk=Signal(bool(0)); Peeker(clk, 'clk')
rst=Signal(bool(0)); Peeker(rst, 'rst')
CONTENT=tuple([i for i in range(4)][::-1])

DUT=ROMParmLoadSync(addr, dout, clk, rst, CONTENT)

def ROMParmLoadSync_TB():
    """Python Only Testbench  for `ROMParmLoadSync`"""

    @always(delay(1))
    def ClkGen():
        clk.next=not clk

    @instance
    def stimules():
        for i in range(3+1):
            yield clk.posedge
            addr.next=i

        for i in range(4):
            yield clk.posedge
            rst.next=1
            addr.next=i

        raise StopSimulation()

    return instances()

sim = Simulation(DUT, ROMParmLoadSync_TB(), *Peeker.instances()).run()
```

```python
Peeker.to_wavedrom()
```

```python
ROMData=Peeker.to_dataframe()
#keep only clock high
ROMData=ROMData[ROMData['clk']==1]
ROMData.drop(columns='clk', inplace=True)
ROMData.reset_index(drop=True, inplace=True)
ROMData
```

      addr
      dout
      rst

      0
      0
      3
      0

      1
      1
      3
      0

      2
      2
      2
      0

      3
      3
      1
      0

      4
      0
      0
      1

      5
      1
      0
      1

      6
      2
      0
      1

```python
DUT.convert()
VerilogTextReader('ROMParmLoadSync');
```

    ***Verilog modual from ROMParmLoadSync.v***

     // File: ROMParmLoadSync.v
    // Generated by MyHDL 0.10
    // Date: Tue Jun 26 23:45:38 2018

    `timescale 1ns/10ps

    module ROMParmLoadSync (
        addr,
        dout,
        clk,
        rst
    );
    // A ROM laoded with data from CONTENT input tuple
    //
    // I/O:
    //     addr(Signal>4): addres; range is from 0-3
    //     dout(Signal>4): data at each address
    //     clk (bool): clock feed
    //     rst (bool): reset
    // Parm:
    //     CONTENT: tuple size 4 with contende must be no larger then 4bit

    input [3:0] addr;
    output [3:0] dout;
    reg [3:0] dout;
    input clk;
    input rst;

    always @(posedge clk) begin: ROMPARMLOADSYNC_READACTION
        if (rst) begin
            dout <= 0;
        end
        else begin
            case (addr)
                0: dout <= 3;
                1: dout <= 2;
                2: dout <= 1;
                default: dout <= 0;
            endcase
        end
    end

    endmodule

**ROMParmLoadSync RTL**

**ROMParmLoadSync Synthesis**

```python
@block
def ROMParmLoadSync_TBV():
    """Python Only Testbench  for `ROMParmLoadSync`"""

    addr=Signal(intbv(0)[4:])
    dout=Signal(intbv(0)[4:])
    clk=Signal(bool(0))
    rst=Signal(bool(0))
    CONTENT=tuple([i for i in range(4)][::-1])

    DUT=ROMParmLoadSync(addr, dout, clk, rst, CONTENT)

    @instance
    def clk_signal():
        while True:
            clk.next = not clk
            yield delay(1)

    @instance
    def stimules():
        for i in range(3+1):
            yield clk.posedge
            addr.next=i

        for i in range(4):
            yield clk.posedge
            rst.next=1
            addr.next=i
        raise StopSimulation

    @always(clk.posedge)
    def print_data():
        print(addr,  dout, rst)

    return instances()

#create instaince of TB
TB=ROMParmLoadSync_TBV()
#convert to verilog with reintilzed values
TB.convert(hdl="Verilog", initial_values=True)
#readback the testbench results
VerilogTextReader('ROMParmLoadSync_TBV');
```

    <class 'myhdl._Signal._Signal'> <class '_ast.Name'>
    <class 'myhdl._Signal._Signal'> <class '_ast.Name'>
    <class 'myhdl._Signal._Signal'> <class '_ast.Name'>
    ***Verilog modual from ROMParmLoadSync_TBV.v***

     // File: ROMParmLoadSync_TBV.v
    // Generated by MyHDL 0.10
    // Date: Tue Jun 26 23:46:29 2018

    `timescale 1ns/10ps

    module ROMParmLoadSync_TBV (

    );
    // Python Only Testbench  for `ROMParmLoadSync`

    reg [3:0] addr = 0;
    reg [3:0] dout = 0;
    reg clk = 0;
    reg rst = 0;

    always @(posedge clk) begin: ROMPARMLOADSYNC_TBV_ROMPARMLOADSYNC0_0_READACTION
        if (rst) begin
            dout <= 0;
        end
        else begin
            case (addr)
                0: dout <= 3;
                1: dout <= 2;
                2: dout <= 1;
                default: dout <= 0;
            endcase
        end
    end

    initial begin: ROMPARMLOADSYNC_TBV_CLK_SIGNAL
        while (1'b1) begin
            clk <= (!clk);
            # 1;
        end
    end

    initial begin: ROMPARMLOADSYNC_TBV_STIMULES
        integer i;
        for (i=0; i<(3 + 1); i=i+1) begin
            @(posedge clk);
            addr <= i;
        end
        for (i=0; i<4; i=i+1) begin
            @(posedge clk);
            rst <= 1;
            addr <= i;
        end
        $finish;
    end

    always @(posedge clk) begin: ROMPARMLOADSYNC_TBV_PRINT_DATA
        $write("%h", addr);
        $write(" ");
        $write("%h", dout);
        $write(" ");
        $write("%h", rst);
        $write("\n");
    end

    endmodule

```python
@block
def SeqROMEx(clk, rst, dout):
    """
    Seq Read Only Memory Ex
    I/O:
        clk (bool): clock
        rst (bool): rst on counter
        dout (signal >4): data out
    """
    Count=Signal(intbv(0)[3:])

    @always(clk.posedge)
    def counter():
        if rst:
            Count.next=0
        elif Count==3:
            Count.next=0

        else:
            Count.next=Count+1

    @always(clk.posedge)
    def Memory():
        if Count==0:
            dout.next=3
        elif Count==1:
            dout.next=2
        elif Count==2:
            dout.next=1
        elif Count==3:
            dout.next=0

    return instances()
```

```python
Peeker.clear()
dout=Signal(intbv(0)[4:]); Peeker(dout, 'dout')
clk=Signal(bool(0)); Peeker(clk, 'clk')
rst=Signal(bool(0)); Peeker(rst, 'rst')

DUT=SeqROMEx(clk, rst, dout)

def SeqROMEx_TB():
    """Python Only Testbench  for `SeqROMEx`"""

    @always(delay(1))
    def ClkGen():
        clk.next=not clk

    @instance
    def stimules():
        for i in range(5+1):
            yield clk.posedge

        for i in range(4):
            yield clk.posedge
            rst.next=1

        raise StopSimulation()

    return instances()

sim = Simulation(DUT, SeqROMEx_TB(), *Peeker.instances()).run()
```

```python
Peeker.to_wavedrom()
```

```python
SROMData=Peeker.to_dataframe()
#keep only clock high
SROMData=SROMData[SROMData['clk']==1]
SROMData.drop(columns='clk', inplace=True)
SROMData.reset_index(drop=True, inplace=True)
SROMData
```

      dout
      rst

      0
      3
      0

      1
      2
      0

      2
      1
      0

      3
      0
      0

      4
      3
      0

      5
      2
      0

      6
      1
      1

      7
      0
      1

      8
      3
      1

```python
DUT.convert()
VerilogTextReader('SeqROMEx');
```

    ***Verilog modual from SeqROMEx.v***

     // File: SeqROMEx.v
    // Generated by MyHDL 0.10
    // Date: Wed Jun 27 00:03:18 2018

    `timescale 1ns/10ps

    module SeqROMEx (
        clk,
        rst,
        dout
    );
    // Seq Read Only Memory Ex
    // I/O:
    //     clk (bool): clock
    //     rst (bool): rst on counter
    //     dout (signal >4): data out

    input clk;
    input rst;
    output [3:0] dout;
    reg [3:0] dout;

    reg [2:0] Count = 0;

    always @(posedge clk) begin: SEQROMEX_COUNTER
        if (rst) begin
            Count <= 0;
        end
        else if ((Count == 3)) begin
            Count <= 0;
        end
        else begin
            Count <= (Count + 1);
        end
    end

    always @(posedge clk) begin: SEQROMEX_MEMORY
        case (Count)
            'h0: begin
                dout <= 3;
            end
            'h1: begin
                dout <= 2;
            end
            'h2: begin
                dout <= 1;
            end
            'h3: begin
                dout <= 0;
            end
        endcase
    end

    endmodule

**SeqROMEx RTL**

**SeqROMEx Synthesis**

```python
@block
def SeqROMEx_TBV():
    """Verilog Only Testbench  for `SeqROMEx`"""

    dout=Signal(intbv(0)[4:])
    clk=Signal(bool(0))
    rst=Signal(bool(0))

    DUT=SeqROMEx(clk, rst, dout)

    @instance
    def clk_signal():
        while True:
            clk.next = not clk
            yield delay(1)

    @instance
    def stimules():
        for i in range(5+1):
            yield clk.posedge

        for i in range(4):
            yield clk.posedge
            rst.next=1

        raise StopSimulation()

    @always(clk.posedge)
    def print_data():
        print(clk, rst, dout)

    return instances()

#create instaince of TB
TB=SeqROMEx_TBV()
#convert to verilog with reintilzed values
TB.convert(hdl="Verilog", initial_values=True)
#readback the testbench results
VerilogTextReader('SeqROMEx_TBV');
```

    <class 'myhdl._Signal._Signal'> <class '_ast.Name'>
    <class 'myhdl._Signal._Signal'> <class '_ast.Name'>
    <class 'myhdl._Signal._Signal'> <class '_ast.Name'>
    ***Verilog modual from SeqROMEx_TBV.v***

     // File: SeqROMEx_TBV.v
    // Generated by MyHDL 0.10
    // Date: Wed Jun 27 01:09:28 2018

    `timescale 1ns/10ps

    module SeqROMEx_TBV (

    );
    // Verilog Only Testbench  for `SeqROMEx`

    reg [3:0] dout = 0;
    reg clk = 0;
    wire rst;
    reg [2:0] SeqROMEx0_0_1_2_3_Count = 0;

    assign rst = 1'd0;

    always @(posedge clk) begin: SEQROMEX_TBV_SEQROMEX0_0_1_2_3_COUNTER
        if (rst) begin
            SeqROMEx0_0_1_2_3_Count <= 0;
        end
        else if ((SeqROMEx0_0_1_2_3_Count == 3)) begin
            SeqROMEx0_0_1_2_3_Count <= 0;
        end
        else begin
            SeqROMEx0_0_1_2_3_Count <= (SeqROMEx0_0_1_2_3_Count + 1);
        end
    end

    always @(posedge clk) begin: SEQROMEX_TBV_SEQROMEX0_0_1_2_3_MEMORY
        case (SeqROMEx0_0_1_2_3_Count)
            'h0: begin
                dout <= 3;
            end
            'h1: begin
                dout <= 2;
            end
            'h2: begin
                dout <= 1;
            end
            'h3: begin
                dout <= 0;
            end
        endcase
    end

    initial begin: SEQROMEX_TBV_CLK_SIGNAL
        while (1'b1) begin
            clk <= (!clk);
            # 1;
        end
    end

    initial begin: SEQROMEX_TBV_STIMULES
        integer i;
        for (i=0; i<(5 + 1); i=i+1) begin
            @(posedge clk);
        end
        for (i=0; i<4; i=i+1) begin
            @(posedge clk);
        end
        $finish;
    end

    always @(posedge clk) begin: SEQROMEX_TBV_PRINT_DATA
        $write("%h", clk);
        $write(" ");
        $write("%h", rst);
        $write(" ");
        $write("%h", dout);
        $write("\n");
    end

    endmodule

    /home/iridium/anaconda3/lib/python3.6/site-packages/myhdl/conversion/_toVerilog.py:349: ToVerilogWarning: Signal is not driven: rst
      category=ToVerilogWarning

# read and write memory

```python
@block
def RAMConcur(addr, din, writeE, dout, clk):
    """
    Random access read write memeory
    I/O:
        addr(signal>4): the memory cell arrdress
        din (signal>4): data to write into memeory
        writeE (bool): write enable contorl; false is read only
        dout (signal>4): the data out
        clk (bool): clock

    Note:
        this is only a 4 byte memory
    """
    #create the memeory list (1D array)
    memory=[Signal(intbv(0)[4:]) for i in range(4)]

    @always(clk.posedge)
    def writeAction():
        if writeE:
            memory[addr].next=din

    @always_comb
    def readAction():
        dout.next=memory[addr]

    return instances()

```

```python
Peeker.clear()
addr=Signal(intbv(0)[4:]); Peeker(addr, 'addr')
din=Signal(intbv(0)[4:]); Peeker(din, 'din')
writeE=Signal(bool(0)); Peeker(writeE, 'writeE')
dout=Signal(intbv(0)[4:]); Peeker(dout, 'dout')
clk=Signal(bool(0)); Peeker(clk, 'clk')
CONTENT=tuple([i for i in range(4)][::-1])

DUT=RAMConcur(addr, din, writeE, dout, clk)

def RAMConcur_TB():
    """Python Only Testbench  for `RAMConcur`"""

    @always(delay(1))
    def ClkGen():
        clk.next=not clk

    @instance
    def stimules():
        # do nothing
        for i in range(1):
            yield clk.posedge

        #write memory
        for i in range(4):
            yield clk.posedge
            writeE.next=True
            addr.next=i
            din.next=CONTENT[i]

        #do nothing
        for i in range(1):
            yield clk.posedge
            writeE.next=False

        #read memory
        for i in range(4):
            yield clk.posedge
            addr.next=i

        # rewrite memory
        for i in range(4):
            yield clk.posedge
            writeE.next=True
            addr.next=i
            din.next=CONTENT[-i]

        #do nothing
        for i in range(1):
            yield clk.posedge
            writeE.next=False

        #read memory
        for i in range(4):
            yield clk.posedge
            addr.next=i

        raise StopSimulation()

    return instances()

sim = Simulation(DUT, RAMConcur_TB(), *Peeker.instances()).run()
```

```python
Peeker.to_wavedrom()
```

```python
RAMData=Peeker.to_dataframe()
RAMData=RAMData[RAMData['clk']==1]
RAMData.drop(columns='clk', inplace=True)
RAMData.reset_index(drop=True, inplace=True)
RAMData
```

      addr
      din
      dout
      writeE

      0
      0
      0
      0
      0

      1
      0
      3
      0
      1

      2
      1
      2
      0
      1

      3
      2
      1
      0
      1

      4
      3
      0
      0
      1

      5
      3
      0
      0
      0

      6
      0
      0
      3
      0

      7
      1
      0
      2
      0

      8
      2
      0
      1
      0

      9
      3
      0
      0
      0

      10
      0
      3
      3
      1

      11
      1
      0
      2
      1

      12
      2
      1
      1
      1

      13
      3
      2
      0
      1

      14
      3
      2
      2
      0

      15
      0
      2
      3
      0

      16
      1
      2
      0
      0

      17
      2
      2
      1
      0

```python
RAMData[RAMData['writeE']==1]
```

      addr
      din
      dout
      writeE

      1
      0
      3
      0
      1

      2
      1
      2
      0
      1

      3
      2
      1
      0
      1

      4
      3
      0
      0
      1

      10
      0
      3
      3
      1

      11
      1
      0
      2
      1

      12
      2
      1
      1
      1

      13
      3
      2
      0
      1

```python
RAMData[RAMData['writeE']==0]
```

      addr
      din
      dout
      writeE

      0
      0
      0
      0
      0

      5
      3
      0
      0
      0

      6
      0
      0
      3
      0

      7
      1
      0
      2
      0

      8
      2
      0
      1
      0

      9
      3
      0
      0
      0

      14
      3
      2
      2
      0

      15
      0
      2
      3
      0

      16
      1
      2
      0
      0

      17
      2
      2
      1
      0

```python
DUT.convert()
VerilogTextReader('RAMConcur');
```

    ***Verilog modual from RAMConcur.v***

     // File: RAMConcur.v
    // Generated by MyHDL 0.10
    // Date: Wed Jun 27 00:31:42 2018

    `timescale 1ns/10ps

    module RAMConcur (
        addr,
        din,
        writeE,
        dout,
        clk
    );
    // Random access read write memeory
    // I/O:
    //     addr(signal>4): the memory cell arrdress
    //     din (signal>4): data to write into memeory
    //     writeE (bool): write enable contorl; false is read only
    //     dout (signal>4): the data out
    //     clk (bool): clock
    //
    // Note:
    //     this is only a 4 byte memory

    input [3:0] addr;
    input [3:0] din;
    input writeE;
    output [3:0] dout;
    wire [3:0] dout;
    input clk;

    reg [3:0] memory [0:4-1];

    initial begin: INITIALIZE_MEMORY
        integer i;
        for(i=0; i<4; i=i+1) begin
            memory[i] = 0;
        end
    end

    always @(posedge clk) begin: RAMCONCUR_WRITEACTION
        if (writeE) begin
            memory[addr] <= din;
        end
    end

    assign dout = memory[addr];

    endmodule

**RAMConcur RTL**

**RAMConcur Synthesis**

```python
@block
def RAMConcur_TBV():
    """Verilog Only Testbench  for `RAMConcur`"""
    addr=Signal(intbv(0)[4:])
    din=Signal(intbv(0)[4:])
    writeE=Signal(bool(0))
    dout=Signal(intbv(0)[4:])
    clk=Signal(bool(0))
    CONTENT=tuple([i for i in range(4)][::-1])

    DUT=RAMConcur(addr, din, writeE, dout, clk)

    @instance
    def clk_signal():
        while True:
            clk.next = not clk
            yield delay(1)

    @instance
    def stimules():
        # do nothing
        for i in range(1):
            yield clk.posedge

        #write memory
        for i in range(4):
            yield clk.posedge
            writeE.next=True
            addr.next=i
            din.next=CONTENT[i]

        #do nothing
        for i in range(1):
            yield clk.posedge
            writeE.next=False

        #read memory
        for i in range(4):
            yield clk.posedge
            addr.next=i

        # rewrite memory
        for i in range(4):
            yield clk.posedge
            writeE.next=True
            addr.next=i
            din.next=CONTENT[-i]

        #do nothing
        for i in range(1):
            yield clk.posedge
            writeE.next=False

        #read memory
        for i in range(4):
            yield clk.posedge
            addr.next=i

        raise StopSimulation()

    @always(clk.posedge)
    def print_data():
        print(addr, din, writeE, dout, clk)

    return instances()

#create instaince of TB
TB=RAMConcur_TBV()
#convert to verilog with reintilzed values
TB.convert(hdl="Verilog", initial_values=True)
#readback the testbench results
VerilogTextReader('RAMConcur_TBV');
```

    <class 'myhdl._Signal._Signal'> <class '_ast.Name'>
    <class 'myhdl._Signal._Signal'> <class '_ast.Name'>
    <class 'myhdl._Signal._Signal'> <class '_ast.Name'>
    <class 'myhdl._Signal._Signal'> <class '_ast.Name'>
    <class 'myhdl._Signal._Signal'> <class '_ast.Name'>
    ***Verilog modual from RAMConcur_TBV.v***

     // File: RAMConcur_TBV.v
    // Generated by MyHDL 0.10
    // Date: Wed Jun 27 01:12:16 2018

    `timescale 1ns/10ps

    module RAMConcur_TBV (

    );
    // Verilog Only Testbench  for `RAMConcur`

    reg [3:0] addr = 0;
    wire [3:0] dout;
    reg clk = 0;
    reg [3:0] din = 0;
    reg writeE = 0;
    reg [3:0] RAMConcur0_0_1_2_memory [0:4-1];

    initial begin: INITIALIZE_RAMCONCUR0_0_1_2_MEMORY
        integer i;
        for(i=0; i<4; i=i+1) begin
            RAMConcur0_0_1_2_memory[i] = 0;
        end
    end

    always @(posedge clk) begin: RAMCONCUR_TBV_RAMCONCUR0_0_1_2_WRITEACTION
        if (writeE) begin
            RAMConcur0_0_1_2_memory[addr] <= din;
        end
    end

    assign dout = RAMConcur0_0_1_2_memory[addr];

    initial begin: RAMCONCUR_TBV_CLK_SIGNAL
        while (1'b1) begin
            clk <= (!clk);
            # 1;
        end
    end

    initial begin: RAMCONCUR_TBV_STIMULES
        integer i;
        for (i=0; i<1; i=i+1) begin
            @(negedge clk);
        end
        for (i=0; i<4; i=i+1) begin
            @(posedge clk);
            writeE <= 1'b1;
            addr <= i;
            case (i)
                0: din <= 3;
                1: din <= 2;
                2: din <= 1;
                default: din <= 0;
            endcase
        end
        for (i=0; i<1; i=i+1) begin
            @(posedge clk);
            writeE <= 1'b0;
        end
        for (i=0; i<4; i=i+1) begin
            @(posedge clk);
            addr <= i;
        end
        for (i=0; i<4; i=i+1) begin
            @(posedge clk);
            writeE <= 1'b1;
            addr <= i;
            case ((-i))
                0: din <= 3;
                1: din <= 2;
                2: din <= 1;
                default: din <= 0;
            endcase
        end
        for (i=0; i<1; i=i+1) begin
            @(posedge clk);
            writeE <= 1'b0;
        end
        for (i=0; i<4; i=i+1) begin
            @(posedge clk);
            addr <= i;
        end
        $finish;
    end

    always @(posedge clk) begin: RAMCONCUR_TBV_PRINT_DATA
        $write("%h", addr);
        $write(" ");
        $write("%h", din);
        $write(" ");
        $write("%h", writeE);
        $write(" ");
        $write("%h", dout);
        $write(" ");
        $write("%h", clk);
        $write("\n");
    end

    endmodule

```python

```


## Classical Mechanics - Week 11

### Last Week:
- Applied Fourier series in analyzing harmonic oscillators
- Saw how different variables affect the physical system
- Gained experience with plotting more complex systems

### This Week:
- Practice the steps for Lagrangian Mechanics
- Develop concepts of how it can be applied to physical systems
- Further practice with symbolic calculators

It seems we've hit the second round of midterm this week, so let us take it easy once again. This week we will revisit the simple pendulum; however, now we are going to utilize Lagrangian mechanics on this system. From there we will create an Euler-Lagrangian equation (Euler is back, get ready to see him everywhere).

###### Due to the advanced nature of this topic we recommend you go through this Notebook as if it is just that, a Notebook you read through. However you can interact with this Notebook, giving us a leg up over the traditional approach.

```python
# Let's import some important packages, as usual
import numpy as np
import matplotlib.pyplot as plt
import sympy as sym
sym.init_printing(use_unicode=True)
```

# Discussion:

As we were introduced through lecture and the literature, [Lagrangian mechanics](https://en.wikipedia.org/wiki/Lagrangian_mechanics) analyzes a system by looking at its Kinetic and Potential Energy in a new combination:

eq 1.)$$L = T - V$$

where $L$ is called the "Lagrangian". Here, $T$ is our kinetic energy, and $V$ is our potential energy.
We construct the Lagrangian $L$ by determining $T$ and $V$ in terms of whatever variables are most convenient.

(Note the minus sign! $L$ is **NOT** the total energy!)

Lagrangians are a key component of physics, the concepts helping to lay the foundations of more modern topics such as Quantum Field Theory.  They are also incredibly useful in practice.  Since energies are scalar quantities, they are often much easier to construct than vector quantities like force and acceleration, especially when using non-Cartesian coordinates or when there are constraints on the system.

This week we will practice Lagrangians with the simple pendulum.

#### Ok, let's get started!

## The humble pendulum:

 Picture courtesy of https://study.com/academy/lesson/pendulums-in-physics-energy-exchange-calculations.html

Assume we are in a frictionless environment with a mass $m$ hanging at the end of a massless string of length $l$. At any given time it can have an angular displacement of $\theta$ from the vertical.

### First Consider:

To construct the Lagrangian, we need to express its ***kinetic*** and ***potential*** energies for any given state of the system.

We know that:

eq 2.) $T = \dfrac{1}{2}mv^2$, where $m$ is the mass and $v$ is the velocity.

We also know that:

eq 3.) $V = mgh$, where $g$ is the acceleration due to gravity and $h$ is the height of our pendulum.

We could easily express these in terms of Cartesian coordinates, $x$ and $y$, and their derivatives. But it is much simpler to write everything in terms of the single angle $\theta$.  (The motion of the pendulum is **constrained** by the string so that $x$ and $y$ are not independent.)

Let's begin by determining the coordinates $x$ and $y$ of the position of the mass for any given $\theta$.  For definiteness, let the origin (0,0) be the fixed point at the top of the string.

# Q1.) Given $l$ and $\theta$, what are the equations for $x$ and $y$ at any given $\theta$? i.e. $x(\theta)$, $y(\theta)$

Note: If the pendulum rests below the origin, what sign should the y equation have?

✅ Double click this cell, erase its content, and put your answer to the above question here.

#### Based on your answer above, use sympy to set your x and y equations.

```python
# let's define our variables first. These will be useful for the Notebook. Feel free to add more as you please.
x, y, theta, m, l, g, t = sym.symbols('x, t, theta, m, l, g, t')
theta = sym.Function('theta')(t) # This is our theta as a function of time
```

```python
# Create your x and y equations in this cell using SymPy
x =
y =
```

We now want to write the kinetic energy in terms of $\theta$.

Given that:

eq 4a.) $\vec{v} = \dfrac{d\vec{r}}{dt}$ and

eq 4b.) $v = \sqrt{v_x^2 + v_y^2}$,

we can use our expressions for $x(\theta)$ and $y(\theta)$ to obtain $v$ in terms of $\theta$ and $\dot{\theta}$.
For this we need to calculate $\dfrac{dx}{dt}$ and $\dfrac{dy}{dt}$.

This is not that hard to do with paper and pencil, but we will instead show the power of SymPy by letting it do the calculus for us.

SymPy has an operation `diff` which allows us to take the derivative of a SymPy-defined function.
Because we defined theta as a function of time, and since $x$ and $y$ are functions of theta, we can take the time derivative of both $x$ and $y$ using our symbolic calculator.

Finish the code below to obtain expressions for $v_x$ and $v_y$. We have given you the code for how to get $v_x=\dfrac{dx}{dt}$.

```python
# Find vx and vy in this cell
vx = sym.diff(x, t)
vy =
```

Now that we have our $v_x$ and $v_y$, we can find our speed $v$. However, notice that what we will really end up using is $v^2$, so you can define this quantity instead, as long as you properly use the value you define.

```python
## Define v or v_squared in this cell with the values we have calculated so far using eq 4b.

# v = ???
# v_squared = ???
```

We now have all the pieces of our puzzle. Using the quantities we have calculated and defined thus far, in the cell below construct our kinetic energy $T$ and potential energy $V$.

```python
T = # Use eq 2 to calculate kinetic energy
V = # Use eq 3 to calculate potential energy
```

##### Finally, we can construct our Lagrangian! Use eq 1.

```python
L = # What should go here?

L # This line will print out our Lagrangian
```

The equation above looks a bit complicated, huh? Luckily, SymPy has simplifying functions to help us out. Once you feel confident that your Lagrangian is correctly assembled, run the cell below to simplify it.

```python
from sympy.simplify import trigsimp # Import one of many simplifying functions from SimPy
L = trigsimp(L) # Simplifies our equation
L # Prints out our equation
```

##### But why did we go through all of that? This equation looks absolutely confusing!
Great question, keep on asking them! Following the wisdom of Euler and Lagrange, we will now construct our [Euler-Lagrange Equation!](https://en.wikipedia.org/wiki/Euler%E2%80%93Lagrange_equation) This will be our equation of motion.

As a quick summary, the Euler-Lagrange equation takes our Lagrangian, $L$ and performs the following operations:

eq 5a.) $\dfrac{d}{dt}\dfrac{dL}{d\dot{q}} = \dfrac{dL}{dq}$, where $q$ is the generalized variable that describes the state of the system.  In our case, $q=\theta$, so we can write the E-L equation as

eq 5b.) $\dfrac{d}{dt}\dfrac{dL}{d\dot{\theta}} = \dfrac{dL}{d\theta}$.

In the cell below we have defined $\dot{\theta}$ for you. Use what we have learned so far to calculate $\dfrac{d}{dt}\dfrac{dL}{d\dot{\theta}}$ (Left Hand Side - LHS) and $\dfrac{dL}{d\theta}$ (Right Hand Side - RHS). Then combine them into equation 5b.

```python
dtheta = sym.diff(theta,t)
```

```python
LHS = # How would you calculate LHS of the equation?
RHS = # How would you calculate RHS of the equation?
```

```python
# Once you have created the two sides of the equation, use this cell to print the Euler-Lagrange equation
EOM = sym.Eq(LHS,RHS)
EOM # This is the equation of motion for the pendulum
```

You should have found that the LHS is proportional to $\dfrac{d^2}{dt^2}\theta(t)$ and the RHS is proportional to $\sin(\theta(t))$.  In the small angle approximation, we can replace $\sin\theta$ by $\theta$. As a paper-pencil exercise, replace $\dfrac{d^2}{dt^2}\theta(t)$ with $\ddot{\theta}$ and solve for $\ddot{\theta}$ in the small angle approximation.

# Q2.) What value did you get for $\ddot{\theta}$?

✅ Double click this cell, erase its content, and put your answer to the above question here.

Does this equation look familiar yet?

Let's finish up by doing some dimensional analysis.

# Q3.) Now rearrange the equation so that you have just $\dfrac{\ddot{\theta}}{\theta}$ on the LHS.

✅ Double click this cell, erase its content, and put your answer to the above question here.

# Q4.) What are the dimensions of $\dfrac{\ddot{\theta}}{\theta}$?
Hint: $\dot{\theta} = \dfrac{[\theta]}{[s]}$

✅ Double click this cell, erase its content, and put your answer to the above question here.

# Q5.) Final Step: Take the square root of the RHS (ignoring the sign).  What are the dimensions of this quantity.  What is the meaning of this quantity?
Hint: You should recognize that we have dealt with an equation of this form before.

✅ Double click this cell, erase its content, and put your answer to the above question here.

# Notebook Wrap-up.
Run the cell below and copy-paste your answers into their corresponding cells.

```python
from IPython.display import HTML
HTML(
"""

"""
)
```

# You've now hit a huge milestone in your career getting experience with Lagrangians.

This time you got to see just how useful symbolic calulators can be. Although these equations weren't too bad, theoretical physicists often deal with equations that are too impossibly long and complicated to work by hand. Hopefully you also see how to properly construct a Lagrangian now. [Here's a nice introduction to Lagrangians if you are still confused as to what magic happened before your eyes](https://www.youtube.com/watch?v=pVVMNsidI0g)

```python

```