```python
%matplotlib inline
```

```python
import numpy as np
from scipy.integrate import odeint, solve_ivp

import matplotlib.pyplot as plt
```

## Double pendulums
There are two pendulums. The second pendulum is hanged on the first pendulum. The potential for the first pendulum is $U_1=-m_1 gL_1\cos\theta_1$. The potential for the second pendulum is $U_2=-m_2 g(L_1\cos\theta_1+L_2\cos\theta_2)$.

The total potential for the system is
\\[
U=-(m_1+m_2) gL_1\cos\theta_1-m_2 gL_2\cos\theta_2
\\]

The kinetic energy for the first pendulum is $T_1=\frac{1}{2}m_1 L_1^2\dot\theta_1^2$. The kinetic energy for the second pendulum is $T_2=\frac{1}{2}m_2L_1^2\dot\theta_1^2+m_2L_1L_2\dot\theta_1\dot\theta_2\cos(\theta_1-\theta_2)+\frac{1}{2}m_2L_2^2\dot\theta_2^2$.

The total kinetic energy is
\\[
T=\frac{1}{2}(m_1+m_2)L_1^2\dot\theta_1^2+m_2L_1L_2\dot\theta_1\dot\theta_2+\frac{1}{2}m_2L_2^2\dot\theta_2^2
\\]

The Lagrangian is
\\[
\mathcal{L}=T-U=\frac{1}{2}(m_1+m_2)L_1^2\dot\theta_1^2+m_2L_1L_2\dot\theta_1\dot\theta_2+\frac{1}{2}m_2L_2^2\dot\theta_2^2\\\quad\quad\quad\quad+(m_1+m_2) gL_1\cos\theta_1+m_2 gL_2\cos\theta_2
\\]
The Euler-Langrange equation is
\begin{align}
\frac{\partial \mathcal{L}}{\partial \theta_1}&=\frac{d}{dt}\frac{\partial \mathcal{L}}{\partial \dot\theta_1}\\\rightarrow 0=
(m_1+m_2)L_1^2\ddot\theta_1+m_2L_1L_2\cos(\theta_1-\theta_2)\ddot\theta_2&+(m_1+m_2)gL_1\sin\theta_1+m_2L_1L_2\sin(\theta_1-\theta_2)\dot\theta_2^2\\
\frac{\partial \mathcal{L}}{\partial \theta_2}&=\frac{d}{dt}\frac{\partial \mathcal{L}}{\partial \dot\theta_2}\\\rightarrow 0=m_2L_1L_2\cos(\theta_1-\theta_2)\ddot\theta_1+m_2L_2^2\ddot\theta_2&+m_2gL_2\sin\theta_2-m_2L_1L_2\sin(\theta_1-\theta_2)\dot\theta_1^2
\end{align}

In order to solve the equation using solve_ivp, extra steps need to be done. The final result is below

\begin{align}
\ddot\theta_1=\frac{m_2 g\sin\theta_2\cos(\theta_1-\theta_2)- m_2\sin(\theta_1-\theta_2)(L_1\dot\theta_1^2\cos(\theta_1-\theta_2)+L_2\dot\theta_2^2)-(m_1+m_2)g\sin\theta_1}{L_1(m_1+m_2\sin(\theta_1-\theta_2)^2}
\end{align}

\begin{align}
\ddot\theta_2 = \frac{(m_1+m_2)(L_1\dot\theta_1^2\sin(\theta_1-\theta_2)-g\sin\theta_2+g\sin\theta_1\cos(\theta_1-\theta_2))+m_2L_2\dot\theta_2^2\sin(\theta_1-\theta_2)\cos(\theta_1-\theta_2))}{L_2(m_1+m_2\sin(\theta_1-\theta_2)^2}
\end{align}

```python
class Double_Pendulum():
    def __init__(self, L1=1., L2=1., m1=1., m2=1., g=1.):
        self.L1 = L1
        self.L2 = L2
        self.m1 = m1
        self.m2 = m2
        self.g = g

    def dy_dt(self, t, y):
        """
        This function returns the right-hand side of the diffeq:
        [dtheta_1/dt d^2theta_1/dt^2 dtheta_2/dt d^2theta_2/dt^2]

        Parameters
        ----------
        t : float
            time
        y : float
            A 2-component vector with y[0] = theta1(t) and y[1] = dtheta1/dt
                                      y[2] = theta2(t) and y[3] = dtheta2/dt

        Returns
        -------
        """
        theta1, theta1_dot, theta2, theta2_dot = y
        theta1_dot = theta1_dot
        theta1_ddot = (self.m2*self.g*np.sin(theta2)*np.cos(theta1-theta2)\
                    - self.m2*np.sin(theta1-theta2)*(self.L1*theta1_dot**2*np.cos(theta1-theta2)+self.L2*theta2_dot**2)\
                    - (self.m1+self.m2)*self.g*np.sin(theta1))\
                    /(self.L1*(self.m1+self.m2*np.sin(theta1-theta2)**2))

        theta2_dot = theta2_dot
        theta2_ddot = ((self.m1+self.m2)*(self.L1*theta1_dot**2*np.sin(theta1-theta2)\
                    -self.g*np.sin(theta2)+self.g*np.sin(theta1)*np.cos(theta1-theta2))
                    +self.m2*self.L2*theta2_dot**2*np.sin(theta1-theta2)*np.cos(theta1-theta2))\
                    /(self.L2*(self.m1+self.m2*np.sin(theta1-theta2)**2))

        return [theta1_dot, theta1_ddot, theta2_dot, theta2_ddot]

    def solve_ode(self, t_pts, theta1_0, theta1_dot_0, theta2_0, theta2_dot_0,
                  abserr=1.0e-9, relerr=1.0e-9):
        """
        Solve the ODE given initial conditions.
        Specify smaller abserr and relerr to get more precision.
        """
        y = [theta1_0, theta1_dot_0, theta2_0, theta2_dot_0]
        solution = solve_ivp(self.dy_dt, (t_pts[0], t_pts[-1]),
                             y, t_eval=t_pts,
                             atol=abserr, rtol=relerr)
        theta1, theta1_dot, theta2, theta2_dot = solution.y

        return theta1, theta1_dot, theta2, theta2_dot
```

```python
def plot_y_vs_x(x, y, axis_labels=None, label=None, title=None,
                color=None, linestyle=None, semilogy=False, loglog=False,
                ax=None):
    """
    Generic plotting function: return a figure axis with a plot of y vs. x,
    with line color and style, title, axis labels, and line label
    """
    if ax is None:        # if the axis object doesn't exist, make one
        ax = plt.gca()

    if (semilogy):
        line, = ax.semilogy(x, y, label=label,
                            color=color, linestyle=linestyle)
    elif (loglog):
        line, = ax.loglog(x, y, label=label,
                          color=color, linestyle=linestyle)
    else:
        line, = ax.plot(x, y, label=label,
                    color=color, linestyle=linestyle)

    if label is not None:    # if a label if passed, show the legend
        ax.legend()
    if title is not None:    # set a title if one if passed
        ax.set_title(title)
    if axis_labels is not None:  # set x-axis and y-axis labels if passed
        ax.set_xlabel(axis_labels[0])
        ax.set_ylabel(axis_labels[1])

    return ax, line
```

```python
def start_stop_indices(t_pts, plot_start, plot_stop):
    start_index = (np.fabs(t_pts-plot_start)).argmin()  # index in t_pts array
    stop_index = (np.fabs(t_pts-plot_stop)).argmin()  # index in t_pts array
    return start_index, stop_index
```

```python
# Common plotting time (generate the full time then use slices)
t_start = 0.
t_end = 100.
delta_t = 0.001

t_pts = np.arange(t_start, t_end+delta_t, delta_t)

L1 = 1.
L2 = 1.
m1 = 1.
m2 = 1.
g = 1.

# Instantiate a pendulum
p1 = Double_Pendulum(L1=L1, L2=L2, m1=m1, m2=m2, g=g)
```

```python
# initial conditions
theta1_0 = 1/2*np.pi
theta1_dot_0 = 0.
theta2_0 = np.pi
theta2_dot_0 = 0.
theta1, theta1_dot, theta2, theta2_dot = p1.solve_ode(t_pts, theta1_0, theta1_dot_0, theta2_0, theta2_dot_0)

# start the plot!
fig = plt.figure(figsize=(10,5))
overall_title = 'Double pendulum from Lagrangian:  ' + \
                rf'  $\theta_1 = {theta1_0:.2f},$' + \
                rf' $\dot\theta_1 = {theta1_dot_0:.2f}$' + \
                rf'  $\theta_2 = {theta2_0:.2f},$' + \
                rf' $\dot\theta_2 = {theta2_dot_0:.2f}$'
fig.suptitle(overall_title, va='baseline')

# theta plot
ax_a = fig.add_subplot(1,1,1)

start, stop = start_stop_indices(t_pts, 0., 50.)
plot_y_vs_x(t_pts[start : stop], theta1[start : stop],
            axis_labels=(r'$t$', r'$\theta(t)$'),
            color='blue',
            label=r'$\theta_1(t)$',
            title=r'$\theta(t)$',
            ax=ax_a)
plot_y_vs_x(t_pts[start : stop], theta2[start : stop],
            color='red',
            label=r'$\theta_2(t)$',
            ax=ax_a)

fig.tight_layout()
fig.savefig('double_pendulum.png', bbox_inches='tight')
```

To show that the system is chaotic, compare the solution of $\theta_1$ and $\theta_2$ using two slightly different initial condition

```python
# one plot with multiple initial conditions
theta1_0_1 = 1/2*np.pi
theta1_dot_0_1 = 0.
theta2_0_1 = np.pi
theta2_dot_0_1 = 0.
theta1_1, theta1_dot_1, theta2_1, theta2_dot_1 = p1.solve_ode(t_pts, theta1_0_1, \
                                                      theta1_dot_0_1, theta2_0_1, \
                                                      theta2_dot_0_1)

theta1_0_2 = 1/2*np.pi
theta1_dot_0_2 = 0.
theta2_0_2 = np.pi+0.0001
theta2_dot_0_2 = 0.
theta1_2, theta1_dot_2, theta2_2, theta2_dot_2 = p1.solve_ode(t_pts, theta1_0_2, \
                                                      theta1_dot_0_2, theta2_0_2, \
                                                      theta2_dot_0_2)

# Calculate the absolute value of \theta_2 - \theta_1
Delta_theta1 = np.fabs(theta1_2-theta1_1)
Delta_theta2 = np.fabs(theta2_2-theta2_1)

# start the plot!
fig = plt.figure(figsize=(10,5))
overall_title = f'$\Delta\theta$ of Double pendulum:  ' + \
                rf'  $\theta_1 = {theta1_0:.2f},$' + \
                rf' $\dot\theta_1 = {theta1_dot_0:.2f}$' + \
                rf'  $\theta_2 = {theta2_0:.2f},$' + \
                rf' $\dot\theta_2 = {theta2_dot_0:.2f}$'
fig.suptitle(overall_title, va='baseline')

# plot from t=0 to t=50
ax_a = fig.add_subplot(1,1,1)

start, stop = start_stop_indices(t_pts, 0., 50.)
plot_y_vs_x(t_pts[start : stop], Delta_theta1[start : stop],
            color='blue', label=r'$|\Delta\theta_1(t)|$', semilogy=True)

plot_y_vs_x(t_pts[start : stop], Delta_theta2[start : stop],
            color='red', label=r'$|\Delta\theta_2(t)|$', semilogy=True)
ax_a.set_ylim(1.e-7,2.)
ax_a.set_xlabel('t')
ax_a.set_ylabel(r'$|\Delta\theta|$')

fig.tight_layout()

# always bbox_inches='tight' for best results.  Further adjustments also.
fig.savefig('delta_theta.png', bbox_inches='tight')
```

As shown in the plot above, $\Delta\theta$ increases over time. The system is chaostic.

```python

```


# Bayesian Coin Flip

## Introduction

This notebook gives an introduction to Bayesian statistics, using mostly elementary probability theory and mathematics.

```
from sympy import *
from sympy.stats import *
```

## Coin Flipping

Imagine we have a coin $c$ with two sides (heads $\mathrm{H}$ and tails $\mathrm{T}$).  The coin may be fair, or it may biased, we don't know.  We will say that $p(c=\mathrm{H})$ is the probability of the coin toss facing heads, and $p(c=\mathrm{T}) = 1 - p(c=\mathrm{H})$ is the probability of tails.  The value of $h := p(c=\mathrm{H})$ itself is uncertain, and can take any value from 0 to 1.  We represent this uncertainty by a probability density function $f(h)$.  $f$ is a distribution of distributions: The value of $f(h)$ determines the bias of the coin, which itself determines the distribution of the coin tosses.  The following image illustrates this distribution of distributions for a factory of fair coins.  Any particular coin may have a bias (although coins with a large bias are rare), but most coins are fair within a margin of error.

```
h = Symbol("h")
f = Beta(h, 10, 10)
fig = plot(density(f)(h), (h, 0, 1), title="Probability density function of the probability of heads", xlabel="$h$", ylabel="$f(h)$")
```

After sampling a specific coin from $f(h)$, we can then represent the coin toss with a discrete random variable.

```
c = Symbol("c")
bias = sample(f)
C = Coin(c, bias)
print density(C)
print [sample(C) for i in range(10)]
```

    {H: 0.511884213785308, T: 0.488115786214692}
    [H, T, T, T, H, H, H, T, T, H]

## Bayesian Inference

Our goal is to understand the consequences of observing tosses of a coin with unknown bias $\hat{h}$.  Ultimately, we want to know the bias of the coin itself, but to answer that, we look at a much bigger question: What is the probability density function $f(h)$ of a coin factory that produces coins with this bias?  Of course, any factory that assigns a non-zero probability mass to $\hat{h}$ is a possible answer, but as we will see in the next section, some are more reasonable than others.  Let's first assume that by some kind of trick we came up with a guess of such a *prior distribution* $f_n$ for our factory.  Then the Bayesian inference process allows us to improve this guess, based on an observed coin toss $c_n$ (heads or tails).  The resulting *posterior distribution* $f_{n+1}$ is the best estimate for the probability density function of our hypothetical factory, given the prior distribution and the observed data.  It is derived by Bayes' theorem for conditional propabilities:

$$
f_{n+1}(\hat{h}) := f_n(h=\hat{h}|c=c_n) = \frac{p(c=c_n|h=\hat{h}) f_n(h=\hat{h})}{p(c=c_n)}, \hat{h}\in [0, 1]
$$

The left hand side is the posterior distribution after one Bayesian update at step $n$ (there are as many steps as there are observed data points).  It represents the updated probability of the coin bias being $\hat{h}$ after observing $c_n$.  The parameter $\hat{h}$ varies over the whole range $[0,1]$, giving us a probability mass for each possible bias of the coin.  On the right hand side, we have the *likelihood* $p(c=c_n|h=\hat{h})$ of the observed data $c_n$, assuming that the bias of the coin is $\hat{h}$, and the prior distribution for $h$.  To get a proper probability density function, we have to make sure that the right hand side sums to $1$ over all possible values for $\hat{h}$.  For this, we divide by the *normalizing constant*, which is easily expressed by the sum over all possible numerators:

$$
p(c=c_n) = \int_0^1 p(c=c_n|h) f_n(h) \thinspace dh
$$

Don't panic.  We will do these calculations several times for specific values of $f_n$ and $c_n$, which will give you plenty of time to review how the Bayesian update process works and what its effects are on the probability density function of the coin bias.

## Prior Distribution

To get started with Bayesian inference, we need a prior distribution of the probability function of our coin factory.  If we had complete knowledge of the bias of the coin, this would be a simple Dirac delta function:

```
f_exact = DiracDelta(h-bias)
# Note: DiracDelta is not a Sympy distribution, so we can not plot the density function or sample it.
integrate(f_exact, (h, 0, 1))
```

    1

If we lack any information, we can assume that the coin comes from a uniform distribution:

```
f_uniform = Uniform(h, 0, 1)
sample(f_uniform)
# This craps out on me.
# plot(density(f_uniform)(h), (h, 0, 1))
```

    0.583601108813623

Choosing the uniform distribution is not as random as it may appear.  The uniform distribution has maximum entropy.  In this sense it ensures that the prior does not include any hidden assumptions that are not justified by the model.  However, sometimes it can be useful to choose other priors, and in general it may be a good idea to verify that the inference process is not overly sensitive to different reasonable choices for the prior.  We will revisit this question at the very end.

## Step 1: The Uniform Prior

Because we assume no knowledge about the bias of the coin in question, we use a uniform prior:

$$
f_1(h) := 1
$$

```
f1 = 1
```

## Step 2: The First Coin Toss

### Heads

We toss the coin and get heads:

$$
c_1 = H
$$

We now want to perform the first Bayesian update.  For this, let's look at the individual components of Bayes Theorem.  Clearly, the prior probability of any bias $\hat{h}$ is $1$, because of the chosen uniform distribution:

$$
f_1(\hat{h}) = 1 \text{ (uniform prior)}
$$

The likelihood of observing heads, given the bias $\hat{h}$, is simply $\hat{h}$, because this is how we defined the bias.  Note that the likelihood of tails would be $1-\hat{h}$.  These are the only two possible outcomes for the likelihood in this example.

$$
p(c=H|h=\hat{h}) = \hat{h} \text{ (by definition)}
$$

Now we only need to calculate the normalization constant.  Luckily, we already did most of the work above:

$$
\int_0^1 p(c=H|h) \cdot f_1(h) \thinspace dh = \int_0^1 h \cdot 1 \thinspace dh = \left. \frac{1}{2} h^2 \right |_0^1 = \frac{1}{2}
$$

Putting it all together, we get the posterior distribution:

$$
f_2(\hat{h}) := f_1(h=\hat{h}|c=H) = \frac{\hat{h} \cdot 1}{\frac{1}{2}} = 2\hat{h}
$$

or simply: $f_2(h) = 2h$

```
f2 = h * f1 / integrate(h * f1, (h, 0, 1))
plot(f2, (h, 0, 1), title="Posterior distribution after observing $c=(\mathrm{H})$.", xlabel="$h$", ylabel="$f_2(h)$")
f2
```

We can see that the posterior distribution favors heads-biased coins over tails-biased coins, in stark contrast to the uniform distribution.  However, even extremely tails-biased coins are still possible, with one exception: Coins that exclusively show tails are now excluded ($f_2(0) = 0$).  This makes sense, because we already observed heads once.

### Tails

Because of the symmetry of the problem, we would get the same result if we had seen tails, but flipped horizontally (due to the transformation $p(c=\mathrm{T}) = 1 - h$).

```
plot(f2.subs(h, 1-h), (h, 0, 1), title="Posterior distribution after observing $c=(\mathrm{T})$.", xlabel="$h$", ylabel="$f_2(h)$")
f2.subs(h,1-h)
```

## Step 3: The Second Coin Toss

### Heads again

We toss the coin a second time, and we get heads again.  Now we repeat the process.  The previous posterior distribution now becomes the new prior, which requires recalculating the normalization constant.  However, the likelihood is the same as in the previous step.

$$
\int_0^1 p(c=H|h) \cdot f_2(h) \thinspace dh = \int_0^1 h \cdot 2\hat{h} \thinspace dh = \left. \frac{2}{3} h^3 \right |_0^1 = \frac{2}{3}
$$

Which yields:

$$
f_3(\hat{h}) := f_2(h=\hat{h}|c=H) = \frac{\hat{h} \cdot 2\hat{h}}{\frac{2}{3}} = 3\hat{h}^2
$$

or simply: $f_3(h) = 3h^2$

```
f3 = h * f2 / integrate(h * f2, (h, 0, 1))
fig = plot(f3, (h, 0, 1), title="Posterior distribution after observing $c=(\mathrm{HH})$.", xlabel="$h$", ylabel="$f_3(h)$")
f3
```

We can see that the distribution now favors heads-biased coins even more strongly.  Considering that two heads in a row is a common event even for fair coins, this may seem strange.  But recall that we used a uniform prior at the very beginning, and did not favor fair coins over strongly biased coins at all.  Consequently, the Bayesian inference process choses the most favorable distribution based on the uniform prior and the observed data.  Had we chosen a prior that preferred fair coins over biased ones (incorporating external knowledge about the world, coin tossing, and gravity), we would see a more moderate result here.  We will see in the folowing sections that, as more data comes in, the choice of the prior becomes less dominant on the overall result.

### ... or Tails

Had we observed tails in the second toss, we would get a very different distribution.  The integrals are easy to solve, so we will just give the result here as computed with Sympy.  Now that tails is observed, the distribution misses the strong leaning towards heads-biased coins, as it must vanish at $h=1$ (i.e. all-heads).

```
f3_ = (1-h) * f2 / integrate((1-h) * f2, (h, 0, 1))
plot(f3_, (h, 0, 1), title="Posterior distribution after observing $c=(\mathrm{HT})$.", xlabel="$h$", ylabel="$f_3(h)$")
f3_
```

## Step 4: The Third Coin Toss

We give the results for three coin tosses (apart from identities and symmetries):

```
f4 = h * f3 / integrate(h * f3, (h, 0, 1))
fig = plot(f4, (h, 0, 1), title="Posterior distribution after observing $c=(\mathrm{HHH})$.", xlabel="$h$", ylabel="$f_4(h)$")
f4
```

```
f4_ = (1-h) * f3 / integrate((1-h) * f3, (h, 0, 1))
fig = plot(f4_, (h, 0, 1), title="Posterior distribution after observing $c=(\mathrm{HHT})$.", xlabel="$h$", ylabel="$f_4(h)$")
f4_
```

## Arbitrary Number of Coin Tosses

The final posterior distribution of a Bayesian update process does not depend on the order of the updates, or even if we do the updates incrementally one after another, or all at once.  We can use this to calculate the posterior after any number of coin tosses $c_1, \..., c_n$.  Assume that the number of heads is $H$ and the number of tails is $T$, then, choosing the uniform prior and any order of the data, we have:

$$
f_1(\hat{h}) = 1
$$

$$
p(c_{1,\...,n}|h=\hat{h}) = h^H (1-h)^T
$$

$$
\int_0^1 h^H (1-h)^T \cdot 1 \thinspace dh = \text{ ?}
$$

Unfortunately, we now have to calculate the integral, which is a bit tricky using only elementary methods (try it, if you are up for a challenge!).  Luckily, Sympy can spit out an acceptable answer in a couple of minutes.  Note that we simplify the expression, and also move the expression to the denominator, where it will end up anyway:

```
H = Symbol("H", positive=True, integer=True)
T = Symbol("T", positive=True, integer=True)
# Warning, slow!
#expr = integrate(h**H*(1-h)**T, (h, 0, 1))
expr = gamma(H + 1)*hyper((-T, H + 1), (H + 2,), exp_polar(2*I*pi))/gamma(H + 2)
beta = expr.simplify()
beta
```

    gamma(H + 1)*gamma(T + 1)/gamma(H + T + 2)

So, we end up with:

$$
\int_0^1 h^H (1-h)^T \cdot 1 \thinspace dh = \frac{1}{B(H+1,T+1)}
$$

with

$$
B(p, q) := \frac{\Gamma(p) \Gamma(q)}{\Gamma(p+q)}
$$

and consequently:

$$
f(h) = \frac{1}{B(H+1,T+1)}h^H(1-h)^T
$$

Please say hello to the [Beta-Distribution](http://en.wikipedia.org/wiki/Beta_distribution)!

Let's look at this function for a couple of examples:

```
f = (1/beta) * h**H * (1-h)**T
fig = plot(f.subs({H:2,T:1}), (h, 0, 1), title="Posterior distribution after observing $c=(\mathrm{HHT})$.", xlabel="$h$", ylabel="$f(h)$")
fig = plot(f.subs({H:10,T:1}), (h, 0, 1), title="Posterior distribution after observing $c=(H=10,T)$.", xlabel="$h$", ylabel="$f(h)$")
fig = plot(f.subs({H:10,T:10}), (h, 0, 1), title="Posterior distribution after observing $c=(H=10,T=10)$.", xlabel="$h$", ylabel="$f(h)$")
fig = plot(f.subs({H:100,T:50}), (h, 0, 1), title="Posterior distribution after observing $c=(H=100,T=50)$.", xlabel="$h$", ylabel="$f(h)$")

```

## Prior Distribution, revisited

To illustrate what can go wrong if an unreasonable prior distribution is chosen, imagine we were to use the Dirac delta function $f(h) = \delta(h_0 - h)$ as prior distribution.  In this case, the integral in the denominator would collaps to $p(c|h=\hat{h})$, which would cancel out with the likelihood, leaving the prior distribution unchanged in the posterior.  We can understand this intuitively: The delta function represents an absolute prejudice in the value of $h$, and this belief is so strong that no factual data can change it.  In this sense, the Bayesian inference process works as expected.  To avoid such a situation, the prior should give at least some probability mass to any reasonable outcome, and let the data speak for itself.  We could see above that after a coin toss facing heads, the posterior vanishes at $0$, which means that the Bayesian inference process excluded the possibility for an all-tails coin.  Likewise, after a coin toss facing tails, all-heads coins are excluded.  No harm was done by including those possibilities in the prior, and the inference process quickly adapted to the data and excluded impossible choices.

## What just happened

We saw that starting with a uniform prior, the Baysian inference process can give us a lot of information about the certainty of a random variable, even after just a couple of observations.  The result can be improved incrementally when new data arrives, or in batches.  For a coin flip, the result is a Beta distribution, where the parameters indicate the number of heads and tails in the data.  The choice of the prior is significant, but as more data arrives, it fades into the background.  A strong bias in the prior that is not justified by the model may be hard to overcome.

A limitation of the process is that it assumes i.i.d. (independent and identically distributed) random variables.  If there was some correlation in the data, or the bias of the coin changes over time, we would prefer to look at some other method (for example Markov Chain).

Bayesian inference is one of the foundations of machine learning, where it has many applications, for example [testing emails for spam](http://en.wikipedia.org/wiki/Bayesian_spam_filtering).

```

```


```python
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import sympy
sympy.init_printing(use_latex='mathjax')
%matplotlib inline
```

## 적분

- 적분(integral)은 미분과 반대되는 개념이다.
- 부정적분(indefinite integral)
- 정적분(definite integral)

#### 부정적분(indefinite integral)

- 부정적분은 정확하게 미분과 반대되는 개념, 즉 반-미분(anti-derivative)이다. 함수 f(x)가 어떤 함수를 미분하여 나온 결과인 도함수라고 가정하고 이 도함수 f(x)에 대한 미분되기 전의 원래의 함수를 찾는 과정(integration), 또는 그 결과 (integral)을 말한다.

- 부정적분으로 찾은 원래의 함수를 표기할 때는 도함수를 대문자화하여 표기할 때도 있지만, 다음처럼 Integral기호로 나타내는 것이 일반적이다. 여기에서 도함수가 f(x)이므로 미분하기 전의 함수를 F(x)또는 Sf(x)dx로 쓴다. dx는 x라는 변수로 적분했다는 것을 나타내는 기호로 편미분에 대응하는 적분을 표기할 때 필요하다.
- dF(x)/dx = f(x) <-> F(x) = Sf(x)dx + C # C는 상수값

```python
# 연습문제1
# 부정적분을 구하라
# 1. S3x^2dx
# 2. S(3x^2-6x+1)dx
# 3. S(2+6x+4exp(x) + 5/x)dx
# 4. S((2x)/x^2-1)dx
```

#### 편미분의 도함수

- 편미분을 한 도함수에서 원래의 함수를 찾을 수도 있다. 다음 식은 f(x,y)가 원래의 함수를 어떻게 미분했는지에 따라 원래의 함수를 표기하는 방법이 달라진다는 것을 나타낸다.
- 만약 f(x,y)가 함수 F1(x,y)를 x로 편미분한 함수였다면 이 함수를 찾는 식은 다음과 같다.
- roundF1(x,y)/round(x) = f(x,y) <-> F1(x,y) = Sf(x,y)dx + C(y)
- 주의할 점은 상수항 C(y)가 y의 함수일 수 있다는 점이다. C(y)는 x없이 y만으로 이루어진 함수를 뜻한다. y만의 함수는 x로 편미분하면 0이 되기 때문이다. 물론 반드시 y의 함수이어야 하는 것은 아니고 단순한 숫자 상수일 수도 있다.
- 마찬가지로 만약 f(x, y)가 함수 F2(x,y)를 y로 편미분한 함수였다면 이 함수를 찾는 식은 다음과 같다
- round(f2(x,y)/round(y) = f(x,y) <-> F2(x,y) = Sf(x,y)dy + C(x)

```python
# 연습문제2
# 다음 부정적분을 구하라
# 1. S(1+xy)dx
# 2. S(xyexp(x^2 + y^2)dx)
```

#### 다차 도함수와 다중 적분

- 미분을 여러번 한 결과로 나온 다차 도함수로부터 원래의 함수를 찾아내려면 여러번 적분을 하는 다중 적분(multiple integration)이 필요함.
- 예를 들어 f(x,y)가 함수 F3(x,y)를 x로 한번 편미분한 후, y로 다시 편미분하여 나온 이차 도함수였다면 이 함수를 찾는 식은 다음과 같다.
- round^2(F3(x))/round(x)round(y) = f(x,y) <-> F3(x,y) = SSf(x,y)dxdy

```python
# 연습문제3
# 다음 부정적분을 구하라
# SSxyexp(x^2 + y^2)dxdy
```

## Sympy를 이용한 부정적분

#### 다음은 SymPy의 integrate 명령으로 부정적분을 하는 예제이다. 상수항은 반환하지 않는다.

```python
import sympy
sympy.init_printing(use_latex='mathjax')
```

```python
x = sympy.symbols('x')
f = x * sympy.exp(x) + sympy.exp(x)
f
```

$$x e^{x} + e^{x}$$

```python
sympy.integrate(f)
```

$$x e^{x}$$

```python
x, y = sympy.symbols('x y')
f = 2 * x + y
sympy.integrate(f, x)
```

$$x^{2} + x y$$

```python
# 연습문제4
# 지금까지 구한 연습 문제의 답을 Sympy를 사용하여 구하라
```

```python
import sympy
sympy.init_printing(use_latex='mathjax')

# 1번
x, y = sympy.symbols('x y')
f = 3* x ** 2
sympy.integrate(f, x)
```

$$x^{3}$$

```python
# 1.2번
f = 3*x**2 - 6*x + 1
sympy.integrate(f, x)
```

$$x^{3} - 3 x^{2} + x$$

```python
f = 2+6*x + 4*sympy.exp(x) + 5/x
sympy.integrate(f, x)
```

$$3 x^{2} + 2 x + 4 e^{x} + 5 \log{\left (x \right )}$$

```python
f = 2*x / x ** 2 - 1
sympy.integrate(f, x)
```

$$- x + 2 \log{\left (x \right )}$$

```python
f = 1 + x*y
sympy.integrate(f, x)
```

$$\frac{x^{2} y}{2} + x$$

```python
f = x*y*sympy.exp(x**2 + y**2)
sympy.integrate(f, x)
```

$$\frac{y}{2} e^{x^{2} + y^{2}}$$

```python
import sympy
sympy.init_printing(use_latex='mathjax')

f = x*y*sympy.exp(x**2 + y**2)
sympy.integrate(f, x, y)
```

$$\frac{1}{4} e^{x^{2} + y^{2}}$$

## 정적분(Definite integral)

- 정적분은 독립변수x가 어떤 구간 [a,b]사이일 때 그 구간에서 함수 f(x)의 값과 수평선(x축)이 이루는 면적을 구하는 행위 혹은 그 값을 말한다. 수학 기호로는 다음과 같이 표기한다.
- S위에 b, a * f(x)dx

```python
# 정적분
```

```python
from matplotlib.patches import Polygon

def f(x):
    return x**3 - 3*x ** 2 + x + 6

a, b = 0, 2

x = np.linspace(a - 0.5, b + 0.5, 50)
y = f(x)

ax = plt.subplot(111)

plt.plot(x, y, 'r', linewidth=2)
plt.ylim(ymin=0)

ix = np.linspace(a,b)
iy = f(ix)

verts = [(a,0)] + list(zip(ix,iy)) + [(b,0)]
poly = Polygon(verts, facecolor = '0.9', edgecolor='0.5')
ax.add_patch(poly)

plt.text(0.5 * (a+b), 0.2 * (f(a) + f(b)), r"$\int_a^b f(x)dx$", horizontalalignment='center', fontsize=20)

plt.figtext(0.9, 0.05, '$x$')
plt.figtext(0.1, 0.9, '$y$')

ax.spines['right'].set_visible(False)
ax.spines['top'].set_visible(False)
ax.xaxis.set_ticks_position('bottom')

ax.set_xticks((a,b))
ax.set_xticklabels(('$a$', '$b$'))
ax.set_yticks([])

plt.show()
```

- 정적분은 얼핏 미분과 아무런 상관이 없어 보이지만 부정적분으로 구한 함수 F(x)를 이용하면 다음처럼 정적분의 값을 구할 수 있다. 이를 **미적분학의 기본 정리(Fundamental Theorem of Calculus)**라고 부른다.
$\int_a^b f(x)dx$
- $\int_a^b f(x)dx$ = F(b) - F(a)

- 정적분은 SymPy등으로 부정적분을 한 뒤 미적분학의 기본 정리를 사용하여 푸는 방법과 원래 함수의 면적 부분을 실제로 잘개 쪼개어 면적을 근사하게 구하는 수치적 부정적분방법으로 구할 수 있다.
- 예를 들어 다음 정적분을 구하는 문제를 생각하자.
$\int_2^0 (x^3 - 3x^2+x+6)dx$

#### 부정 적분

```python
x, y = sympy.symbols('x y')
f = x**3 - 3*x**2+x+6
F = sympy.integrate(f)
F
```

$$\frac{x^{4}}{4} - x^{3} + \frac{x^{2}}{2} + 6 x$$

#### 정적분

```python
(F.subs(x, 2) - F.subs(x, 0)).evalf()
```

$$10.0$$

## 수치적분

- 수치 적분은 함수를 아주 작은 구간으로 나누어 실제 면적을 계산함으로써 정적분의 값을 구하는 방법이다. Scipy의 integrate서브 패키지의 quad(적분), dblquad(이중적분), tplquad(삼중 적분) 함수 등은 수치 적분을 이용하여 정적분의 값을 계산한다.

```python
def f(x):
    return x ** 3 - 3 * x ** 2 + x + 6

sp.integrate.quad(f, 0, 2) # 수치 적분
```

$$\left ( 10.0, \quad 1.1102230246251565e-13\right )$$

-  수치적 정적분 결과값의 두번째 숫자는 오차의 상한값을 뜻한다. 수치적분으로 구한 값과 정적분으로 구한 값이 같다는 것을 알 수 있다.

- 연습 문제 5
- 다음 정적분의 값을 부정적분과 수치적분 두 가지 방법으로 구하라.
- $\int_0^1 (3x^2-6x+1)dx$

```python
# 부정적분 방법
import sympy
sympy.init_printing(use_latex='mathjax')

x = sympy.symbols('x')
f = 3*x**2 - 6*x + 1
F = sympy.integrate(f, x)
F

# F(x)까지는 찾음. 그 다음으로 대입

(F.subs(x, 1) - F.subs(x, 0)).evalf()
```

$$-1.0$$

```python
# 수치적분 방법
import sympy
sympy.init_printing(use_latex='mathjax')

def f(x):
    return 3*x**2 - 6*x + 1

sp.integrate.quad(f, 0, 1) # 함수
```

$$\left ( -1.0, \quad 1.3085085171449517e-14\right )$$

- 연습문제 5.2
- 부정적분과 수치적분 두 가지 방법으로 구하라.
- $\int_1^10 (2+6x+4e(x)+5/x)dx$

```python
# 부정적분
```

```python
import sympy
sympy.init_printing(use_latex='mathjax')

x = sympy.symbols('x')
f = 2 + 6*x + 4*sympy.exp(x) + 5 / x
F = sympy.integrate(f, x)

(F.subs(x, 10) - (F.subs(x, 1))).evalf(), f
```

$$\left ( 88421.502977378, \quad 6 x + 4 e^{x} + 2 + \frac{5}{x}\right )$$

```python
# 수치적분
def f(x):
    return 2 + 6*x + 4*sympy.exp(x) + 5 / x

sp.integrate.quad(f, 1, 10)
```

$$\left ( 88421.50297737827, \quad 1.5276890734473408e-06\right )$$

## 다변수 정적분

- 입력 변수가 2개인 2차원 함수 f(x,y)의 경우에는 정적분을 다양한 방법으로 정의 할 수 있다.

####  두 변수로 이중 적분하는 경우

- 두 변수로 모두 적분하는 것은 다음과 같이 2차원 평면에서 주어진 사각형 영역 아래의 부피를 구하는 것과 같다.

- 수치 이중 적분을 하려면 Scipy의 integrate 서브패키지의 dblquad명령을 사용한다.
- 함수 사용법은 다음과 같다.
- dblquad(func, a, b, gfun, hfun)

```python
def f(x,y):
    return np.exp(-x*y) / y**2

sp.integrate.dblquad(f, 1, np.Inf, lambda x: 0, lambda x: np.inf)
```

$$\left ( 0.4999999999999961, \quad 1.068453874338024e-08\right )$$

#### 하나의 변수로 단일 적분하는 경우
- f(x,y)가 2차원 함수이지만 하나의 변수만 진짜 입력 변수로 보고 나머지 하나는 상수로 보는 방법이다.
- $\int_a^b f(x,y)dx$
- 예를 들어 다음과 같은 함수를 생각하자.
- f(x, y) = 4x^2 + 4xy + y^2
- 여기에서 변수 x만 진짜 입력 변수로 보고 y는 단순히 정해지지 않은 상수로 보면, 이 함수는 다음과 같은 1차원 함수이다.
- 2차원 함수 f(x,y)를 y를 고정시켜 절단한 단면 함수로 볼 수 있다.
- y가 고정되어 있다는 것을 강조하기 위해 다음처럼 표기하기도 한다.
- y가 변수가 아니라는 점을 강조하기 위해 함수 표기에서 쉼표가 아니라 세미콜런이 씌여졌다는 점에 주의하라.
- f(x;y) = 4x^2 + (4y)*x + (y^2)

#### 연습문제 6
다음 정적분의 값을 구하라
S1,-1S1,-1 (1 + xy) dxdy

```python
import sympy
sympy.init_printing(use_latex = 'mathjax')

x, y = sympy.symbols('x y')

def f(x,y):
    return 1 + x*y

sp.integrate.dblquad(f, -1, 1, lambda x: -1, lambda x: 1)
```

$$\left ( 4.0, \quad 4.440892098500626e-14\right )$$

```python

```


# Introduction to the Harmonic Oscillator

*Note:* Much of this is adapted/copied from https://flothesof.github.io/harmonic-oscillator-three-methods-solution.html

This week week we are going to begin studying molecular dynamics, which uses classical mechanics to study molecular systems. Our "hydrogen atom" in this section will be the 1D harmomic oscillator.

The harmonic oscillator is a system that, when displaced from its equilibrium position, experiences a restoring force F proportional to the displacement x:

$$F=-kx$$

The potential energy of this system is

$$V = {1 \over 2}k{x^2}$$

These are sometime rewritten as

$$ F=- \omega_0^2 m x, \text{ }  V(x) = {1 \over 2}  \omega_0^2 m {x^2}$$

Where $\omega_0  = \sqrt {{k \over m}} $

In classical mechanics, our goal is to determine the equations of motion, $x(t),y(t)$, that describe our system.

In this notebook we will use sympy to solve an second order, ordinary differential equation.

## 1. Solving differential equations with sympy

Soliving differential equations can be tough, and there is not always a set plan on how to proceed. Luckily for us, the harmonic osscillator is the classic second order diffferential eqations.

Consider the following second order differential equation

$$ay(t)''+by(t)'=c$$

where $y(t)'' = {{{d^2}y} \over {dt^2}}$, and $y(t)' = {{{d}y} \over {dt}}$

We can rewrite this as a homogeneous linear differential equations

$$ay(t)''+by(t)'-c=0$$

The goal here is to find $y(t)$, similar to our classical mechanics problems. Lets use sympy to solve this equation

### Second order ordinary differential equation

First we import the sympy library

```python
import sympy as sym
```

Next we initialize pretty printing

```python
sym.init_printing()
```

Next we will set our symbols

```python
t,a,b,c=sym.symbols("t,a,b,c")
```

Now for somehting new. We can define functions using `sym.Function("f")`

```python
y=sym.Function("y")
y(t)
```

Now, If I want to define a first or second derivative, I can use `sym.diff`

```python
sym.diff(y(t),(t,1)),sym.diff(y(t),(t,2))
```

My differential equation can be written as follows

```python
dfeq=a*sym.diff(y(t),(t,2))+b*sym.diff(y(t),(t,1))-c
dfeq
```

```python
sol = sym.dsolve(dfeq)
sol
```

The two constants $C_1$ and $C_2$ can be determined by setting boundry conditions.
First, we can set the condition $y(t=0)=y_0$

The next intial condition we will set is $y'(t=0)=v_0$

To setup the equality we want to solve, we are using `sym.Eq`. This function sets up an equaility between a lhs aand rhs of an equation

```python
# sym.Eq example
alpha,beta=sym.symbols("alpha,beta")
sym.Eq(alpha+2,beta)
```

Back to the actual problem

```python
y0,v0=sym.symbols("y_0,v_0")
ics=[sym.Eq(sol.args[1].subs(t, 0), y0),
     sym.Eq(sol.args[1].diff(t).subs(t, 0), v0)]
ics
```

We can use this result to first solve for $C_2$ and then solve for $C_1$.
Or we can use sympy to solve this for us.

```python
solved_ics=sym.solve(ics)
solved_ics
```

Substitute the result back into $y(t)$

```python
full_sol = sol.subs(solved_ics[0])
full_sol
```

We can plot this result too. Assume that $a,b,c=1$ and that the starting conditions are $y_0=0,v_0=0$

We will use two sample problems:

* case 1 : initial position is nonzero and initial velocity is zero
* case 2 : initial position is zero and initialvelocity is nonzero

```python
# Print plots
%matplotlib inline
```

#### Initial velocity set to zero

```python
case1 = sym.simplify(full_sol.subs({y0:0, v0:0, a:1, b:1, c:1}))
case1
```

```python
sym.plot(case1.rhs)
sym.plot(case1.rhs,(t,-2,2))
```

#### Initial velocity set to one

```python
case2 = sym.simplify(full_sol.subs({y0:0, v0:1, a:1, b:1, c:1}))
case2
```

```python
sym.plot(case2.lhs,(t,-2,2))
```

## Calculate the phase space

As we will see in lecture, the state of our classical systems are defined as points in phase space, a hyperspace defined by ${{\bf{r}}^N},{{\bf{p}}^N}$. We will convert our sympy expression into a numerical function so that we can plot the path of $y(t)$ in phase space $y,y'$.

```python
case1
```

```python
# Import numpy library
import numpy as np

# Make numerical functions out of symbolic expressions
yfunc=sym.lambdify(t,case1.rhs,'numpy')
vfunc=sym.lambdify(t,case1.rhs.diff(t),'numpy')

# Make list of numbers
tlst=np.linspace(-2,2,100)

# Import pyplot
import matplotlib
import matplotlib.pyplot as plt
# Make plot
plt.plot(yfunc(tlst),vfunc(tlst))
plt.xlabel('$y$')
plt.ylabel("$y'$")
plt.show()
```

### Exercise 1.1

Change the initial starting conditions and see how that changes the plots. Make three different plots with different starting conditions

```python
# Your code here
```

## 2. Harmonic oscillator

Applying the harmonic oscillator force to Newton's second law leads to the following second order differential equation

$$ F = m a $$

$$ F= - \omega_0^2 m x $$

$$ a = - \omega_0^2 x $$

$$ x(t)'' = - \omega_0^2 x $$

The final expression can be rearranged into a second order homogenous differential equation, and can be solved using the methods we used above

Your goal is determine and plot the equations of motion of a 1D harmomnic oscillator

### Exercise 2.1

1. Use the methodology above to determine the equations of motion $x(t), v(t)$ for a harmonic ocillator
1. Solve for any constants by using the following initial conditions: $x(0)=x_0, v(0)=v_0$
1. Show expressions for and plot the equations of motion for the following cases:
    1. $x(0)=0, v(0)=0$
    1. $x(0)=0, v(0)>0$
    1. $x(0)>0, v(0)=0$
    1. $x(0)<0, v(0)=0$
1. Plot the phasespace diagram for the harmonic oscillator

```python
# Your code here
```

```python

```


# Reproducing and extending diffeq_pendulum.cpp - Pass 4

Pass 4: Exploring animation

* Created 27-Dec-2018 by Dick Furnstahl (furnstahl.1@osu.edu)
* Last revised 27-Dec-2018 by Dick Furnstahl (furnstahl.1@osu.edu).

```python
import numpy as np
from scipy.integrate import ode, odeint

import matplotlib.pyplot as plt
```

## Pendulum code

```python
class Pendulum():
    """
    Pendulum class implements the parameters and differential equation from
     the diffeq_pendulum.cpp code.

    Parameters
    ----------
    omega0 : float
        natural frequency of the pendulum (\sqrt{g/l} where l is the pendulum length)
    alpha : float
        coefficient of friction
    f_ext : float
        amplitude of external force
    omega_ext : float
        frequency of external force
    phi_ext : float
        phase angle for external force

    Methods
    -------
    dy_dt(y, t)
        Returns the right side of the differential equation in vector y, given time t
        and the corresponding value of y.
    """
    def __init__(self,
                 omega0=1.,
                 alpha=0.2,
                 f_ext=0.2,
                 omega_ext=0.689,
                 phi_ext=0.
                ):
        self.omega0 = omega0
        self.alpha = alpha
        self.f_ext = f_ext
        self.omega_ext = omega_ext
        self.phi_ext = phi_ext

    def dy_dt(self, y, t):
        """
        This function returns the right-hand side of the diffeq: [dtheta/dt d^2theta/dt^2]

        Parameters
        ----------
        y : float
            A 2-component vector with y[0] = theta(t) and y[1] = dtheta/dt
        t : float
            time

        Returns
        -------

        """
        F_ext = self.f_ext * np.cos(self.omega_ext*t + self.phi_ext)
        return [y[1], -self.omega0**2*np.sin(y[0]) - self.alpha*y[1] + F_ext]
```

```python
def plot_y_vs_x(x, y, axis_labels=None, label=None, title=None, ax=None):
    """
    Return a figure axis with a plot of y vs. x.
    """
    if ax is None:
        ax = plt.gca()

    ax.plot(x, y, label=label)
    if label is not None:
        ax.legend()
    if title is not None:
        ax.set_title(title)
    if axis_labels is not None:
        ax.set_xlabel(axis_labels[0])
        ax.set_ylabel(axis_labels[1])

    return ax

```

## Interface using ipywidgets with interactive_output

```python
# Import the widgets we will use (add more as needed!)
import ipywidgets as widgets
from ipywidgets import HBox, VBox, Layout, Tab, Label, Checkbox
from ipywidgets import FloatSlider, Dropdown, HTMLMath

from IPython.display import display
from time import sleep
```

```python
# This function generates the main output, which is a grid of plots
def pendulum_plots(theta_vs_time_plot=True, theta_dot_vs_time_plot=True,
                   phase_space_plot=True, omega0=1.,
                   alpha=0.2, f_ext=0.2, omega_ext=0.689, phi_ext=0.,
                   theta0=0.8, theta_dot0=0.0,
                   t_start=0, t_end=100, delta_t=0.1, plot_start=0,
                   font_size=18):
    """
    Create plots for interactive_output according to the inputs.

    Based on generating a Pendulum instance and the requested graphs.

    Notes
    -----
        1. We generate a new Pendulum instance every time *and* solved
            the ODE every time, even if the only change is to parameters
            like t_start and t_end.  Should we care or is this just so
            cheap to recalculate that it doesn't matter?
            How could we structure this differently?
    """

    # add delta_t o it goes at least to t_end (probably should use linspace)
    t_pts = np.arange(t_start, t_end+delta_t, delta_t)

    # Instantiate a pendulum with the passed (or default) values of the
    #  natural frequency omega0, damping alpha, driving amplitude, frequency,
    #  and phase (f_ext, omega_ext, phi_ext).
    #  Should we delete p1 at some point?  Is there a memory issue?
    p1 = Pendulum(omega0=omega0, alpha=alpha, f_ext=f_ext, omega_ext=omega_ext,
                  phi_ext=phi_ext)
    y0 = [theta0, theta_dot0]  # initial conditions for the pendulum ODE

    # ODE solver parameters
    abserr = 1.0e-8
    relerr = 1.0e-6

    # For now we solve with odeint; give more options in the future.
    theta, theta_dot = odeint(p1.dy_dt, y0, t_pts,
                              atol=abserr, rtol=relerr).T

    # Update the common font size
    plt.rcParams.update({'font.size': font_size})

    # Labels for individual plot axes
    theta_vs_time_labels = (r'$t$', r'$\theta$')
    theta_dot_vs_time_labels = (r'$t$', r'$d\theta/dt$')
    phase_space_labels = (r'$\theta$', r'$d\theta/dt$')

    # Figure out how many rows and columns [one row for now]
    plot_flags = [theta_vs_time_plot, theta_dot_vs_time_plot, phase_space_plot]
    plot_num = plot_flags.count(True)
    plot_rows = 1
    figsize_rows = plot_rows*4  # 6
    plot_cols = plot_num
    figsize_cols = min(plot_cols*6, 10)  # 8, 16

    # Make the plot!
    fig, axes = plt.subplots(plot_rows, plot_cols,
                             figsize=(figsize_cols,figsize_rows))
                             #num='This is a test')
    axes = np.atleast_1d(axes)  # make it always a 1d array, even if only 1

    start_index = (np.fabs(t_pts-plot_start)).argmin() # finds nearest index

    next_axes = 0
    if theta_vs_time_plot:
        plot_y_vs_x(t_pts, theta, axis_labels=theta_vs_time_labels,
                    label='pendulum', title=r'$\theta$ vs. time',
                    ax=axes[next_axes])
        # add a line where the phase space plot starts
        axes[next_axes].axvline(t_pts[start_index], lw=3, color='red')
        next_axes += 1

    if theta_dot_vs_time_plot:
        plot_y_vs_x(t_pts, theta_dot, axis_labels=theta_dot_vs_time_labels,
                    label='pendulum', title=r'$d\theta/dt$ vs. time',
                    ax=axes[next_axes])
        # add a line where the phase space plot starts
        axes[next_axes].axvline(t_pts[start_index], lw=3, color='red')
        next_axes += 1

    if phase_space_plot:
        plot_y_vs_x(theta[start_index:-1], theta_dot[start_index:-1],
                    axis_labels=phase_space_labels, title='Phase space',
                    ax=axes[next_axes])
        next_axes += 1

    fig.tight_layout()

```

```python

# Widgets for the various inputs.
#   For any widget, we can set continuous_update=False if we don't want the
#    plots to shift until the selection is finished (particularly relevant for
#    sliders).

# Widgets for the plot choice (plus a label out front)
plot_choice_w = Label(value='Which plots: ',layout=Layout(width='100px'))
def plot_choice_widget(on=True, plot_description=None):
    return Checkbox(value=on, description=plot_description,
                  disabled=False, indent=False, layout=Layout(width='150px'))
theta_vs_time_plot_w = plot_choice_widget(True, r'$\theta$ vs. time')
theta_dot_vs_time_plot_w = plot_choice_widget(False, r'$d\theta/dt$ vs. time')
phase_space_plot_w = plot_choice_widget(True, 'phase space')

# Widgets for the pendulum parameters
def float_widget(value, min, max, step, description, format):
    slider_border = Layout(border='solid 1.0px')
    return FloatSlider(value=value,min=min,max=max,step=step,disabled=False,
                       description=description,continuous_update=False,
                       orientation='horizontal',layout=slider_border,
                       readout=True,readout_format=format)
omega0_w = float_widget(value=1.0, min=0.0, max=10., step=0.1,
                        description=r'natural $\omega_0$:', format='.1f')
alpha_w = float_widget(value=0.1, min=0.0, max=2., step=0.1,
                       description=r'damping $\alpha$:', format='.1f')
f_ext_w = float_widget(value=0.2, min=0.0, max=2., step=0.05,
                       description=r'strength $f_{\rm ext}$:', format='.2f')
omega_ext_w = float_widget(value=0.689,min=0.0,max=3.,step=0.1,
                       description=r'freq. $\omega_{\rm ext}$:', format='.2f')
phi_ext_w = float_widget(value=0.0, min=0, max=2.*np.pi, step=0.1,
                         description=r'phase $\phi_{\rm ext}$:', format='.1f')

# Widgets for the initial conditions
theta0_w = float_widget(value=0.8, min=0., max=2.*np.pi, step=0.1,
                        description=r'$\theta_0$:', format='.1f')
theta_dot0_w = float_widget(value=0.0, min=-10., max=10., step=0.1,
                            description=r'$(d\theta/dt)_0$:', format='.1f')

# Widgets for the plotting parameters
t_start_w = float_widget(value=0., min=0., max=100., step=10.,
                         description='t start:', format='.1f')
t_end_w = float_widget(value=100., min=0., max=500., step=10.,
                       description='t end:', format='.1f')
delta_t_w = float_widget(value=0.1, min=0.01, max=0.2, step=0.01,
                         description='delta t:', format='.2f')
plot_start_w = float_widget(value=0., min=0., max=300., step=5.,
                            description='start plotting:', format='.1f')

# Widgets for the styling parameters
font_size_w = Dropdown(options=['12', '16', '18', '20', '24'], value='18',
                       description='Font size:',disabled=False,
                       continuous_update=False,layout=Layout(width='140px'))

# Text for the help section
overview_text = \
   r"""<p>Here we explore the dynamics of a damped, driven pendulum. There is help
          available under the other tabs.</p>
          <ul>
            <li>Physics tab: find out about the equations being solved.
            <li>Plotting tab: adjust what is plotted and over what intervals.
                Also about the algorithm used.
            <li>Styling tab: change how the plots look.
            <li>Animate tab: look at animated plots of the time dependence.
          </ul>
    """
physics_text = \
   r"""<p>We have in mind a physical pendulum with moment of inertia $I$ for which
       the dependent variable is the angle $\theta$.  It is subject to gravity,
       a damping force proportional to $d\theta/dt \equiv \dot\theta$, and an external
       periodic driving torque.  Newton's second law for the torque can be rearranged to
       give the differential equation:
       \begin{align}
         \qquad
         \frac{d^2\theta}{dt^2} + \alpha \frac{d\theta}{dt}
           + \omega_0^2 \sin\theta = f_{\rm ext}\cos(\omega_{\rm ext}t)
           \; .
       \end{align}
       The presence of $\sin\theta$ rather than $\theta$ makes this problem
       inherently nonlinear.</p>

       <p>To study this system, we'll look at plots such as $\theta$ versus $t$
       and the phase-space plot $\dot\theta$ vs. $\theta$, given initial
       conditions $\theta_0 \equiv \theta(t=0)$ and
       $\dot\theta_0 \equiv \dot\theta(t=0)$.
       Under what conditions will these look like the results for a
       simple harmonic oscillator?
       Try to decide what range of $\theta_0$ gives harmonic behavior,
       given $\dot\theta_0 = 0$.</p>

       <p>Let's think about <em>chaos</em>.  Consider the following brief
       discussion as a teaser.  Here are some characteristics of chaos:
       <ul>
         <li> The system does not repeat past behavior (cf.\ periodic
         behavior).</li>
         <li> An uncertainty (or variation) in initial conditions grows
         <em>exponentially</em> (rather than linearly) in time.  The consequence
         is that the system is deterministic (as opposed to having a random
         component) but not predictable, since there is a finite precision in
         specifying the initial conditions (e.g., think about round-off
         error).</li>
         <li> The system has a distributed power spectrum.</li>
       </ul>
       The following are necessary conditions for chaotic behavior:
       <ol type="a">
         <li> The system has at least \emph{three} independent dynamical
         variables.  That is, the system can be written as
         \begin{align}
            \qquad\frac{dy_0}{dt} &= F_0(y_0,\cdots,y_n) \ , \\
            \qquad\frac{dy_1}{dt} &= F_0(y_0,\cdots,y_n) \ , \\
                 &  \quad \vdots \\
            \qquad\frac{dy_n}{dt} &= F_0(y_0,\cdots,y_n) \ , \\
         \end{align}
         with $n \geq 3$.</li>
         <li>
           The equations of motion contain nonlinear term(s) that couple
           several of the variables.</li>
       </ol>
       You might not think that our pendulum example qualifies, since there
       only seem to be two independent dynamical variables, $\theta$ and
       $\omega \equiv \dot\theta$.  We find the third by introducing $\phi$
       as
       \begin{align}
         \qquad\phi &= \omega_{\rm ext} t \quad \Longrightarrow \quad
           \frac{d\phi}{dt} = \omega_{\rm ext} \ .
       \end{align}
       Thus, the three necessary equations are
       \begin{align}
         \qquad\frac{d\theta}{dt} &= \omega \ ,\\
         \qquad\frac{d\omega}{dt} &= -\alpha\omega - \omega_0^2 \sin\theta
            - f_{\rm ext}\cos\phi \ ,\\
         \qquad\frac{d\phi}{dt} &= \omega_{\rm ext} \ .
       \end{align}
       Now we satisfy a) with $\theta$, $\omega$, and $\phi$, and we satisfy
       b) since the $\sin\theta$ and $\cos\phi$ terms couple the equations
       nonlinearly.  So we should be able to find chaos!
    """

plotting_text = \
    """
    <p>Notes on plotting:</p>
    <ul>
      <li>The <tt>plot_start</tt> variable sets when the phase space plot
       starts plotting.  This enables you to remove the transient behavior
       at early ties.
    </ul>
    """

styling_text = \
    """
    <p>For now you can only change the font size.</p>
    """

# Widgets for the help section
help_max_height = '500px'
help_overview_w = HTMLMath(value=overview_text)
help_physics_w = HTMLMath(value=physics_text)
help_plotting_w = HTMLMath(value=plotting_text)
help_styling_w = HTMLMath(value=styling_text)
help_w = Tab(children=[help_overview_w, help_physics_w,
                       help_plotting_w, help_styling_w],
             layout=Layout(width='95%', max_height=help_max_height))
help_w.set_title(0, 'Overview')
help_w.set_title(1, 'Physics')
help_w.set_title(2, 'Plotting')
help_w.set_title(3, 'Styling')
help_w.set_title(4, 'Animate')

############## Begin: Explicit callback functions #######################

# Make sure that t_end is at least t_start + 50
def update_t_end(*args):
    if t_end_w.value < t_start_w.value:
        t_end_w.value = t_start_w.value + 50
t_end_w.observe(update_t_end, 'value')
t_start_w.observe(update_t_end, 'value')

# Make sure that plot_start is at least t_start and less than t_end
def update_plot_start(*args):
    if plot_start_w.value < t_start_w.value:
        plot_start_w.value = t_start_w.value
    if plot_start_w.value > t_end_w.value:
        plot_start_w.value = t_end_w.value
plot_start_w.observe(update_plot_start, 'value')
t_start_w.observe(update_plot_start, 'value')
t_end_w.observe(update_plot_start, 'value')

############## End: Explicit callback functions #######################

# Set up the interactive_output widget
plot_out = widgets.interactive_output(pendulum_plots,
                          dict(
                          theta_vs_time_plot=theta_vs_time_plot_w,
                          theta_dot_vs_time_plot=theta_dot_vs_time_plot_w,
                          phase_space_plot=phase_space_plot_w,
                          omega0=omega0_w,
                          alpha=alpha_w,
                          f_ext=f_ext_w,
                          omega_ext=omega_ext_w,
                          phi_ext=phi_ext_w,
                          theta0=theta0_w,
                          theta_dot0=theta_dot0_w,
                          t_start=t_start_w,
                          t_end=t_end_w,
                          delta_t=delta_t_w,
                          plot_start=plot_start_w,
                          font_size=font_size_w)
                       )

# Now do some manual layout, where we can put the plot anywhere using plot_out
hbox1 = HBox([plot_choice_w, theta_vs_time_plot_w, theta_dot_vs_time_plot_w,
              phase_space_plot_w]) #  choice of what plots to show
hbox2 = HBox([omega0_w, f_ext_w, omega_ext_w, phi_ext_w])  # external driving parameters
hbox3 = HBox([theta0_w, theta_dot0_w, alpha_w]) # initial conditions and damping
hbox4 = HBox([t_start_w, t_end_w, delta_t_w, plot_start_w]) # time and plot ranges
hbox5 = HBox([font_size_w]) # font size
hbox6 = HBox()  # animate
hbox7 = HBox([help_w])  # help tabs

tab_height = '70px'  # some auto height?
tab0 = VBox([hbox2, hbox3], layout=Layout(min_height=tab_height))
tab1 = VBox([hbox1, hbox4], layout=Layout(min_height=tab_height))
tab2 = VBox([hbox5], layout=Layout(min_height=tab_height))
tab3 = VBox([hbox6], layout=Layout(min_height=tab_height))
tab4 = VBox([hbox7], layout=Layout(min_height=tab_height))

tab = Tab(children=[tab0, tab1, tab2, tab3, tab4])
tab.set_title(0, 'Physics')
tab.set_title(1, 'Plotting')
tab.set_title(2, 'Styling')
tab.set_title(3, 'Animate')
tab.set_title(4, 'Help')

# Release the Kraken!
vbox2 = VBox([tab, plot_out])
display(vbox2)
```

## Animating the time dependence

Questions:
* How do we get the return value of the function called with interactive_output? We would like to return the fig and axes and be able to use these.

One plan is to create a series of frames and then show them.

```python
play = widgets.Play(
                    interval=100,
                    value=10,
                    min=0,
                    max=100,
                    step=1,
                    description='Press play',
                    disabled=False
                )
slider = widgets.IntSlider()

widgets.jslink((play, 'value'), (slider, 'value'))
widgets.HBox([play, slider])
```

```python
%matplotlib notebook

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

fig = plt.figure()

def f(x, y):
    return np.sin(x) + np.cos(y)

x = np.linspace(0, 2 * np.pi, 120)
y = np.linspace(0, 2 * np.pi, 100).reshape(-1, 1)
# ims is a list of lists, each row is a list of artists to draw in the
# current frame; here we are just animating one artist, the image, in
# each frame
ims = []
for i in range(60):
    x += np.pi / 15.
    y += np.pi / 20.
    im = plt.imshow(f(x, y), animated=True)
    ims.append([im])

ani = animation.ArtistAnimation(fig, ims, interval=50, blit=True,
                                repeat_delay=1000)

# ani.save('dynamic_images.mp4')

plt.show()
```

The following example is from https://github.com/jupyter-widgets/ipywidgets/issues/134.

An explanation of the %config line and some other aspects is in https://stackoverflow.com/questions/45341765/matplotlib-inline-causes-error-in-following-code.  We'll quote it here:

By default, figures are closed at the end of a cell. This means that pyplot (plt) has forgotten about the axes to work on in the next cell.  See %config InlineBackend in the next cell.

The solution is thus to set .close_figures to False:

%config InlineBackend.close_figures=False

In order to prevent the automatic output of active figures at the end of a cell, you may then set 'plt.ioff()'.

```python
import matplotlib.pyplot as plt

%matplotlib inline
%config InlineBackend.close_figures=False
plt.ioff()

fig, axes = plt.subplots(ncols=2)

```

```python
ax = axes[0]
ax.set_title("title")
ax.plot([1,3,2])
plt.sca(ax)
plt.xticks([0,1,2],["A","B","C"]);
```

```python
fig
```

```python
%config InlineBackend
```

```python
#%matplotlib inline

# To prevent automatic figure display when execution of the cell ends
%config InlineBackend.close_figures=False

import matplotlib.pyplot as plt
import numpy as np

import ipywidgets as widgets
from IPython.display import display,clear_output

plt.ioff()
ax=plt.gca()
plt.plot(np.random.randn(100),np.random.randn(100),'+')

out=widgets.Output()
button=widgets.Button(description='Next')
vbox=widgets.VBox(children=(out,button))
display(vbox)

def click(b):
    ax.lines[0].set_xdata(np.random.randn(100))
    ax.lines[0].set_ydata(np.random.randn(100))
    with out:
        clear_output(wait=True)
        display(ax.figure)

button.on_click(click)
click(None)
```

### Another type of example

```python
import ipywidgets as widgets
from IPython.display import display

def any_function_returning_value(word1,word2):
    new_word = 'Combining words is easy: %s %s'%(word1,word2)
    print(new_word)
    return new_word

class learn_classes_and_widgets():
    def __init__(self, param1 = 'a word', param2 = 'another word'):
        self.p1_text = widgets.Text(description = 'Word #1',value = param1)
        self.p2_text = widgets.Text(description = 'Word #2',value = param2)
        self.p1_text.on_submit(self.handle_submit)
        self.p2_text.on_submit(self.handle_submit)
        display(self.p1_text, self.p2_text)

    def handle_submit(self, text):
        print("Submitting")
        print( "Text " + str(text.value))
        self.w = any_function_returning_value(self.p1_text.value,self.p2_text.value)
        return self.w

f = learn_classes_and_widgets(param1 = 'try this word')
```

### Basic use of 'display'

```python
# doesn't seem to work with %matplotlib notebook
import matplotlib.pyplot as plt
import matplotlib.animation
import numpy as np
from IPython.display import display, clear_output

t = np.linspace(0,2*np.pi,100)
x = np.sin(t)

fig, ax = plt.subplots(1,2,figsize=(8,4))
#l, = ax.plot([0,2*np.pi],[-1,1])
l, = ax[0].plot(t,x)
l2, = ax[0].plot(t[0],x[0],'ro')
l3, = ax[1].plot(t,x**2)
l4, = ax[1].plot(t[0],x[0]**2,'go')

fig.tight_layout()

# Add text in axes coordinates, with lower left (0,0) and upper right (1,1).
time_text = ax[0].text(0.02, 0.95, '', transform=ax[0].transAxes)

#animate = lambda i: l.set_data(t[:i], x[:i])
#animate = lambda i: l2.set_data(t[i], x[i])
def animate(i):
    time_text.set_text('time = %.1f' % t[i])
    l2.set_data(t[i], x[i])
    l4.set_data(t[i], x[i]**2)

for i in range(len(x)):
    animate(i)
    clear_output(wait=True)
    display(fig)
clear_output(wait=True)

plt.show()

```

```python
%matplotlib notebook

import matplotlib.pyplot as plt
import matplotlib.animation
import numpy as np
from IPython.display import display, clear_output
import time

t = np.linspace(0,2*np.pi,100)
x = np.sin(t)

fig = plt.figure(num='Test of animation',figsize=(6,3))
#, ax = plt.subplots(1,2,figsize=(8,4))
#l, = ax.plot([0,2*np.pi],[-1,1])
ax_left = fig.add_subplot(1,2,1)
ax_right = fig.add_subplot(1,2,2)

l1, = ax_left.plot(t,x)
p1, = ax_left.plot(t[0],x[0],'ro')
l2, = ax_right.plot(t,x**2)
p2, = ax_right.plot(t[0],x[0]**2,'go')

fig.tight_layout()

# Add text in axes coordinates, with lower left (0,0) and upper right (1,1).
time_text = ax_left.text(0.02, 0.95, 'time', transform=ax_left.transAxes)

fig.show()
fig.canvas.draw()

# for i in range(len(t)):
#     p1, = ax_left.plot(t[5],x[5],'ro')
#     #fig.canvas.draw()
#     #fig.canvas.flush_events()
#     time.sleep(.1)

# for i in range(len(t)):
#   p1.remove()
#   p1, = ax_left.plot(t[i],x[i],'ro')
#   p2.remove()
#   p2, = ax_right.plot(t[i],x[i]**2,'go')
#   fig.canvas.draw()
#   time.sleep(.2)

```

```python
# ax_right.clear()
```

```python
for i in range(len(t)):
    p1.remove()
    p1, = ax_left.plot(t[i],x[i],'ro')
    #p2.remove()
    #p2, = ax_right.plot(t[i],x[i]**2,'go')
    ax_right.clear()
    l2, = ax_right.plot(t[:i],x[:i]**2,'go')
    fig.canvas.draw()
    fig.canvas.flush_events()  # seems to keep results cleaner (no strays)
    time.sleep(.1)

```

```python
%matplotlib notebook

import matplotlib.pyplot as plt
#import matplotlib.animation
import numpy as np
# Import the widgets we will use (add more as needed!)
import ipywidgets as widgets
from ipywidgets import HBox, VBox, Layout, Tab, Label, Checkbox
from ipywidgets import FloatSlider, Dropdown, HTMLMath, Button

from IPython.display import display, clear_output
from time import sleep

def plot_test(t_start=0, t_end=2*np.pi, delta_t=0.1):
    t = np.linspace(t_start,t_end,100)
    x = np.sin(t)

    fig, ax = plt.subplots(1,2,figsize=(8,4))
    #fig.figure(num='This is the title')

    #l, = ax.plot([0,2*np.pi],[-1,1])
    l1, = ax[0].plot(t,x)
    p1, = ax[0].plot(t[0],x[0],'ro')
    l2, = ax[1].plot(t,x**2)
    p2, = ax[1].plot(t[0],x[0]**2,'go')

    fig.tight_layout()
    fig.show()
    fig.canvas.draw()

    return p1, p2, fig

# Add text in axes coordinates, with lower left (0,0) and upper right (1,1).
#time_text = ax[0].text(0.02, 0.95, '', transform=ax[0].transAxes)

# def animate(i):
#     time_text.set_text('time = %.1f' % t[i])
#     l2.set_data(t[i], x[i])
#     l4.set_data(t[i], x[i]**2)

# for i in range(len(x)):
#     animate(i)
#     clear_output(wait=True)
#     display(fig)
# clear_output(wait=True)

# plt.show()

def float_widget(value, min, max, step, description, format):
    slider_border = Layout(border='solid 1.0px')
    return FloatSlider(value=value,min=min,max=max,step=step,disabled=False,
                       description=description,continuous_update=False,
                       orientation='horizontal',layout=slider_border,
                       readout=True,readout_format=format)

animate_button = Button(description='animate', disabled=False)

# Widgets for the plotting parameters
t_start_w = float_widget(value=0., min=0., max=2.*np.pi, step=0.1,
                         description='t start:', format='.1f')
t_end_w = float_widget(value=2.*np.pi, min=0., max=500., step=10.,
                       description='t end:', format='.1f')
delta_t_w = float_widget(value=0.1, min=0.01, max=0.2, step=0.01,
                         description='delta t:', format='.2f')

# Set up the interactive_output widget
plot_out = widgets.interactive_output(plot_test,
                          dict(
                          t_start=t_start_w,
                          t_end=t_end_w,
                          delta_t=delta_t_w)
                       )

def on_button_clicked(b):
    p1, p2, fig = plot_out.result
    p1.remove()
    fig.canvas.draw()
    fig.canvas.flush_events()  # seems to keep results cleaner (no strays)
    print("Clicked!")

animate_button.on_click(on_button_clicked)

hbox4 = HBox([t_start_w, t_end_w, delta_t_w]) # time and plot ranges

vbox = VBox([hbox4, animate_button, plot_out])
display(vbox)

```

```python
%config InlineBackend.rc
```

```python
%config NotebookBackend.rc
```

```python

```

```python
% This generates multiple plots, like above

import numpy as np
import matplotlib.pyplot as plt

# The following programme runs on version 3.6.
# Code generates a pair of lines and the line 2 is removed in a for loop which
# simulates a comet effect
pts=100
t2 = np.linspace(0.0,5.0,pts)
t2=(t2/50)
tm=t2*(10**3)
nz=t2.size
tc=np.linspace(0.8,2.5,2)
nz=tc.size
for n in range (nz):
    print(tc[n])
    resp = 1 - np.exp(-tc[n]*tm*10**-3*50) * np.cos(2*np.pi*50*tm*10**-3)
    for m in range(pts):
        plt.xlim(0,100)
        plt.ylim(0,2)
        plt.xlabel('Time,in milliseconds',fontsize=12)
        plt.ylabel('Respose',fontsize=12)
        plt.title('Underdamped Second Order System Step Response',fontsize=14)
        line1,=plt.plot(tm[0:m+1],resp[0:m+1],color='black',linewidth=0.2)
        line2,=plt.plot(tm[m],resp[m],marker='o',color='red',markersize=5)
        ax = plt.gca()
        plt.pause(0.02)
        ax.lines.remove(line2)
        plt.grid(True)
plt.show()
```

```python
%matplotlib notebook
import matplotlib.pyplot as plt
from IPython.display import HTML
import numpy as np
from matplotlib import animation
N = 5
M = 100
points_evo_array = np.random.rand(M,N)

# First set up the figure, the axis, and the plot element we want to animate
fig = plt.figure()
ax = plt.axes(xlim=(0, M), ylim=(0, np.max(points_evo_array)))
lines = []

lines = [ax.plot([], [])[0] for _ in range(N)]

def init():
    for line in lines:
        line.set_data([], [])
    return lines

def animate(i):
    for j,line in enumerate(lines):
        line.set_data(range(i), [points_evo_array[:i,j]])
    return lines

# call the animator.  blit=True means only re-draw the parts that have changed.
anim = animation.FuncAnimation(fig, animate,np.arange(1, M), init_func=init, interval=10, blit=True)

HTML(anim.to_html5_video())
```

```python
# From https://stackoverflow.com/questions/39658717/plot-dynamically-changing-graph-using-matplotlib-in-jupyter-notebook
# Problem: rendering of animation is very small until final figure.
%matplotlib notebook
import numpy as np
import matplotlib.pyplot as plt
import time

#plt.setp(plt.gca(), autoscale_on=False)

m = 100
n = 100
matrix = np.random.normal(0,1,m*n).reshape(m,n)

fig = plt.figure(num='Test animation',figsize=(4,3))
ax = fig.add_subplot(111)
plt.ion()   # Do we need this?  On by default?

fig.show()
fig.canvas.draw()
fig.canvas._dpi_ratio = 2

for i in range(0,100):
    ax.clear()
    ax.plot(matrix[i,:])
    fig.canvas.draw()
    time.sleep(.1)

```

### Getting a function return from interactive and interactive_output

```python
# tryin

%matplotlib inline
from ipywidgets import interactive
import matplotlib.pyplot as plt
import numpy as np

def f(m, b):
    fig = plt.figure(figsize=(4,3))
    ax = fig.add_subplot(111)
    x = np.linspace(-10, 10, num=1000)
    ax.plot(x, m * x + b)
    p1, = ax.plot([0],[0],'ro')
    ax.set_ylim(-5, 5)
    fig.show()
    return fig, ax, p1

interactive_plot = interactive(f, m=(-2.0, 2.0), b=(-3, 3, 0.5))
output = interactive_plot.children[-1]
output.layout.height = '350px'
interactive_plot

```

```python
fig2, ax2, p1 = interactive_plot.result

p1.remove()
p1, = ax2.plot([5],[2],'ro')
display(fig2)
```

```python
# tryin

%matplotlib inline
from ipywidgets import interactive_output
import matplotlib.pyplot as plt
import numpy as np

def f(m, b):
    fig = plt.figure(figsize=(4,3))
    ax = fig.add_subplot(111)
    x = np.linspace(-10, 10, num=1000)
    ax.plot(x, m * x + b)
    p1, = ax.plot([0],[0],'ro')
    ax.set_ylim(-5, 5)
    fig.show()
    return fig, ax, p1

interactive_plot = interactive_output(f, m=(-2.0, 2.0), b=(-3, 3, 0.5))
output = interactive_plot.children[-1]
output.layout.height = '350px'
interactive_plot

```

### Working with a class and interactive_output

```python
%matplotlib notebook

import ipywidgets as widgets
from ipywidgets import HBox, VBox, Layout, Tab, Label, Checkbox
from ipywidgets import FloatSlider, IntSlider, Play, Dropdown, HTMLMath

import matplotlib.pyplot as plt
import numpy as np

from IPython.display import display, clear_output

import time

```

This works, sort of, but only with the notebook backend.  So must need interactive after all.

Also, we can't get the widgets to display above the plot this way.

```python
class TestPersistance():
    """
    Simple test case for having a figure in an Output widget together with
    interactive_output in which the components of the figure (e.g., a marker
    dot on a line) are changed without changing the rest of the plot.

    This will hopefully make real-time animations faster.
    """
    def __init__(self, t_min=0, t_max=1, num_t=100):
        self.t_min = t_min
        self.t_max = t_max
        self.num_t = num_t
        self.t_pts = np.linspace(self.t_min, self.t_max, self.num_t)
        self.x_pts = np.zeros(len(self.t_pts))

        self.fig = plt.figure(figsize=(5,4))
        self.ax = self.fig.add_subplot(1,1,1)
        self.x_vs_time_labels = (r'$t$', r'$x$')
        self.ax.set_xlabel(self.x_vs_time_labels[0])
        self.ax.set_ylabel(self.x_vs_time_labels[1])

        self.line, = self.ax.plot(self.t_pts[0],
                                      self.x_pts[0],
                                      'ro'
                                     )

    def plot_points(self, animate_flag=False, plot_index=0):
        if animate_flag is False:
            self.x_pts = self.t_pts**2
            self.ax.plot(self.t_pts, self.x_pts)
            #self.fig.show()
        elif animate_flag is True:
            self.line.remove()
            self.line, = self.ax.plot(self.t_pts[plot_index],
                                      self.x_pts[plot_index],
                                      'ro'
                                     )

t_min = 0
t_max = 1
num_t = 100
tp1 = TestPersistance(t_min, t_max, num_t)
#tp1.plot_points(-1)

# for index in np.arange(len(tp1.t_pts)):
#     tp1.plot_points(index)
#     tp1.fig.show()
#     display(tp1.fig)
#     time.sleep(.1)

```

    <IPython.core.display.Javascript object>

```python
# Widgets for the animating
animate_flag_w = Checkbox(value=False, description='Animate',
                  disabled=False, indent=False, layout=Layout(width='100px'))
plot_index_w = Play(interval=10, value=0, min=0, max=num_t-1, step=1,
                      disabled=False, continuous_update=True,
                      description='press play',
                      orientation='horizontal')

plot_out = widgets.interactive_output(tp1.plot_points,
                                      dict(
                                           animate_flag=animate_flag_w,
                                           plot_index=plot_index_w
                                          )
                                     )

hbox1 = HBox([animate_flag_w, plot_index_w])  # animate
tab1 = VBox([hbox1])
vbox1 = VBox([tab1, plot_out])
display(vbox1)
```

    VBox(children=(VBox(children=(HBox(children=(Checkbox(value=False, description='Animate', indent=False, layout…

### "Convert animation to javascript" examples

This one still seems pretty slow, with no intensive calculations.  Wait, the interval was set to be too long by its default.  Now it flies along.  There is also the possibility of blit.  (To make blit work, simply add `return l2` to the end of the `animate` function.

```python
%matplotlib inline
import matplotlib.pyplot as plt
plt.rcParams["animation.html"] = "jshtml"
import matplotlib.animation
import numpy as np

t = np.linspace(0,2*np.pi)
x = np.sin(t)

fig, ax = plt.subplots()
l, = ax.plot(t,x)
l2, = ax.plot(t[0],x[0],'ro')

def animate(i):
    l2.set_data(t[i], x[i])

ani = matplotlib.animation.FuncAnimation(fig, animate, frames=len(t),\
                                        interval=20)
plt.show()
```

```python
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation

fig, ax = plt.subplots()
xdata, ydata = [], []
ln, = plt.plot([], [], 'ro', animated=True)

def init():
    ax.set_xlim(0, 2*np.pi)
    ax.set_ylim(-1, 1)
    return ln,

def update(frame):
    xdata.append(frame)
    ydata.append(np.sin(frame))
    ln.set_data(xdata, ydata)
    return ln,

ani = FuncAnimation(fig, update, frames=np.linspace(0, 2*np.pi, 128),
                    init_func=init, blit=True)
plt.show()

```

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

t = np.linspace(0,2*np.pi)
x = np.sin(t)

fig, ax = plt.subplots()
l, = ax.plot([0,2*np.pi],[-1,1])

animate = lambda i: l.set_data(t[:i], x[:i])

ani = matplotlib.animation.FuncAnimation(fig, animate, frames=len(t),
                                        interval=5)

from IPython.display import HTML
HTML(ani.to_jshtml())

```

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

def data_gen(t=0):
    cnt = 0
    while cnt < 1000:
        cnt += 1
        t += 0.1
        yield t, np.sin(2*np.pi*t) * np.exp(-t/10.)

def init():
    ax.set_ylim(-1.1, 1.1)
    ax.set_xlim(0, 10)
    del xdata[:]
    del ydata[:]
    line.set_data(xdata, ydata)
    return line,

fig, ax = plt.subplots()
line, = ax.plot([], [], lw=2)
ax.grid()
xdata, ydata = [], []

def run(data):
    # update the data
    t, y = data
    xdata.append(t)
    ydata.append(y)
    xmin, xmax = ax.get_xlim()

    if t >= xmax:
        ax.set_xlim(xmin, 2*xmax)
        ax.figure.canvas.draw()
    line.set_data(xdata, ydata)

    return line,

ani = animation.FuncAnimation(fig, run, data_gen, blit=False, interval=10,
                              repeat=False, init_func=init)
plt.show()
```

```python
import numpy as np
from matplotlib.lines import Line2D
import matplotlib.pyplot as plt
import matplotlib.animation as animation

class Scope(object):
    def __init__(self, ax, maxt=2, dt=0.02):
        self.ax = ax
        self.dt = dt
        self.maxt = maxt
        self.tdata = [0]
        self.ydata = [0]
        self.line = Line2D(self.tdata, self.ydata)
        self.ax.add_line(self.line)
        self.ax.set_ylim(-.1, 1.1)
        self.ax.set_xlim(0, self.maxt)

    def update(self, y):
        lastt = self.tdata[-1]
        if lastt > self.tdata[0] + self.maxt:  # reset the arrays
            self.tdata = [self.tdata[-1]]
            self.ydata = [self.ydata[-1]]
            self.ax.set_xlim(self.tdata[0], self.tdata[0] + self.maxt)
            self.ax.figure.canvas.draw()

        t = self.tdata[-1] + self.dt
        self.tdata.append(t)
        self.ydata.append(y)
        self.line.set_data(self.tdata, self.ydata)
        return self.line,

def emitter(p=0.03):
    'return a random value with probability p, else 0'
    while True:
        v = np.random.rand(1)
        if v > p:
            yield 0.
        else:
            yield np.random.rand(1)

# Fixing random state for reproducibility
np.random.seed(19680801)

fig, ax = plt.subplots()
scope = Scope(ax)

# pass a generator in "emitter" to produce data for the update func
ani = animation.FuncAnimation(fig, scope.update, emitter, interval=10,
                              blit=True)

plt.show()
```

```python

```