# Problem:
Solution of the kinematic chain of the anthropomorphic robot shown below.

The mechanism has 2 links and 3 gaskets.

## Coordinate System
First, we have to determine frames location. The inertial reference, `B0`, is fixed and will be placed in the intercession of the first two joints axes. The second reference, `B1`, is in pure rotation of $\theta_1$ with respect to the $y$ axis of the `B0` reference, and their origins coincide. The origin of the `B2` reference is the same as `B1` and it follows the movement of the second joint, that is, it is in pure rotation, $\theta_2$, in relation to the $z$ axis of ` B1`. Finally, the origin of the `B3` reference coincides with the intercession of the $x$ axis of `B2` and the axis of rotation of the third joint, besides being a pure rotation of $\theta_3$ with respect to the axis $z$ of `B2`. The links are the rigid parts that connect the joints; so in this case we have three links. Since the first frame is coincident with the next two, we only need to define the length of two links. By defining the $A$ point at joint 2, the point $B$ at joint 3 and the point $C$ at the tip of the last link, we can define the distance between points A and B, and B and C as $l_1$ and $l_2$, respectively.
Thus, we can begin to implement the solution of the problem.

```python
# Funções das Bibliotecas Utilizadas
from sympy import symbols
from sympy.physics.mechanics import dynamicsymbols
from sympy.physics.vector import ReferenceFrame

# Variáveis Simbólicas
theta_1, theta_2, theta_3 = dynamicsymbols('theta_1 theta_2 theta_3')
l_1, l_2 = symbols('l_1 l_2', positive = True)

# Referenciais
B0 = ReferenceFrame('B0')                 # Referencial Parado
B1 = ReferenceFrame('B1')
B1.orient(B0, 'Axis', [theta_1, B0.y])    # Referencial móvel: theta_1 em relação a B0.y
B2 = ReferenceFrame('B2')
B2.orient(B1, 'Axis', [theta_2, B1.z])    # Referencial móvel: theta_2 em relação a B1.z
B3 = ReferenceFrame('B3')
B3.orient(B2, 'Axis', [theta_3, B2.z])    # Referencial móvel: theta_3 em relação a B2.z
```

This code creates the symbolic and referential variables that will be used by `sympy`. $\theta_1, \theta_2$ and $\theta_3$ were defined using `dynammicsymbols` to show that these variables are time functions, and the `symbols` method was used together with the `positive = True` argument to define that the lengths of the links are positive values.
`ReferenceFrame` class is used to create inertial references. All references were defined as a rotation from the previous reference frame, excepting the fixed reference `B0`.

### Rotation Matrices
With these definitions, it is easy to represent the rotation matrices between any of the defined frames. For this, we invoke the `.dcm` method (Direction Cosine Matrix) that creates the matrix between the frames used. Here, are some examples, along with the representation of the results in LaTeX.

```python
# Matrizes de Rotação
T_B0B1 = B0.dcm(B1)    # Matriz de rotação de B1 para B0
T_B1B2 = B1.dcm(B2)    # Matriz de rotação de B2 para B1
T_B2B3 = B2.dcm(B3)    # Matriz de rotação de B3 para B2

T_B0B3 = (B0.dcm(B3)).simplify()    # Matriz de rotação de B0 para B3
T_B0B2 = (B0.dcm(B2)).simplify()

# Resultados em LaTeX
from IPython.display import Latex
from sympy import latex

Latex("\\begin{eqnarray}" +
     "T_{\\theta_1}&=" + "&" +str(latex(T_B0B1)) + " T_{\\theta_2}="
     + str(latex(T_B1B2)) + "T_{\\theta_3}=" + str(latex(T_B2B3)) +
     "\\\\T_{\\theta_1, \\theta_3}&=" + "&" + str(latex(T_B0B3))
     + "\\end{eqnarray}")
```

\begin{eqnarray}T_{\theta_1}&=&\left[\begin{matrix}\cos{\left (\theta_{1}{\left (t \right )} \right )} & 0 & \sin{\left (\theta_{1}{\left (t \right )} \right )}\\0 & 1 & 0\\- \sin{\left (\theta_{1}{\left (t \right )} \right )} & 0 & \cos{\left (\theta_{1}{\left (t \right )} \right )}\end{matrix}\right] T_{\theta_2}=\left[\begin{matrix}\cos{\left (\theta_{2}{\left (t \right )} \right )} & - \sin{\left (\theta_{2}{\left (t \right )} \right )} & 0\\\sin{\left (\theta_{2}{\left (t \right )} \right )} & \cos{\left (\theta_{2}{\left (t \right )} \right )} & 0\\0 & 0 & 1\end{matrix}\right]T_{\theta_3}=\left[\begin{matrix}\cos{\left (\theta_{3}{\left (t \right )} \right )} & - \sin{\left (\theta_{3}{\left (t \right )} \right )} & 0\\\sin{\left (\theta_{3}{\left (t \right )} \right )} & \cos{\left (\theta_{3}{\left (t \right )} \right )} & 0\\0 & 0 & 1\end{matrix}\right]\\T_{\theta_1, \theta_3}&=&\left[\begin{matrix}\cos{\left (\theta_{2}{\left (t \right )} + \theta_{3}{\left (t \right )} \right )} \cos{\left (\theta_{1}{\left (t \right )} \right )} & - \sin{\left (\theta_{2}{\left (t \right )} + \theta_{3}{\left (t \right )} \right )} \cos{\left (\theta_{1}{\left (t \right )} \right )} & \sin{\left (\theta_{1}{\left (t \right )} \right )}\\\sin{\left (\theta_{2}{\left (t \right )} + \theta_{3}{\left (t \right )} \right )} & \cos{\left (\theta_{2}{\left (t \right )} + \theta_{3}{\left (t \right )} \right )} & 0\\- \sin{\left (\theta_{1}{\left (t \right )} \right )} \cos{\left (\theta_{2}{\left (t \right )} + \theta_{3}{\left (t \right )} \right )} & \sin{\left (\theta_{2}{\left (t \right )} + \theta_{3}{\left (t \right )} \right )} \sin{\left (\theta_{1}{\left (t \right )} \right )} & \cos{\left (\theta_{1}{\left (t \right )} \right )}\end{matrix}\right]\end{eqnarray}

### Relative positions of $A$, $B$, and $C$
As we defined a reference frame for each degree of freedom in the mechanism, the position of the points $A$, $B$ and $C$ can be easily represented. The $A$ point coincides with the origin of the fixed referential, that is, $\vec{r_{OA}}=\vec{0}$. The $B$ point is easily expressed relative to the `B2` reference, in which case we have $\vec{r_{AB}}={l_1 \vec{{i}_{B2}}}$. Finally, the $C$ point is represented by $\vec{r_{BC}}={l_2 \vec{{i}_{B3}}}$. $\vec{{i}_{B2}}$ and $\vec{{i}_{B3}}$ being the versors in the $x$ direction of the references `B2` and `B3` respectively. Next, should be noticed that the definition of these positional vectors is simple to implement in `sympy`. The main advantage is that we do not have to worry about which referential we are defining the vectors, because the relations between the frames were previously created in their definitions. That is, we can rewrite a vector in relation to different references immediately, as shown below.

```python
from sympy.physics.vector import Vector

# Vetores Posição entre os Pontos
r_OA = Vector(0)      # Vetor Nulo
r_AB = l_1 * B2.x     # Vetor que liga os pontos A e B expresso no referencial móvel B2
r_BC = l_2 * B3.x     # Vetor que liga os pontos B e C expresso no referencial móvel B3

R_AB = r_AB.express(B0) # Vetor que liga os pontos A e B expresso no referencial fixo B0
R_BC = r_BC.express(B0) # Vetor que liga os pontos B e C expresso no referencial fixo B0

# Resultado em LaTeX na Forma Vetorial ou Vetor Coluna
Latex("\\begin{eqnarray}" +
     "R_{AB}&=" + "&" +str(latex(R_AB)) + "\\\\" +
     "R_{AB}&=" + "&" +str(latex(R_AB.to_matrix(B0))) +
     "\\end{eqnarray}")
```

\begin{eqnarray}R_{AB}&=&l_{1} \operatorname{cos}\left(\theta_{1}\right) \operatorname{cos}\left(\theta_{2}\right)\mathbf{\hat{b0}_x} + l_{1} \operatorname{sin}\left(\theta_{2}\right)\mathbf{\hat{b0}_y} -  l_{1} \operatorname{sin}\left(\theta_{1}\right) \operatorname{cos}\left(\theta_{2}\right)\mathbf{\hat{b0}_z}\\R_{AB}&=&\left[\begin{matrix}l_{1} \cos{\left (\theta_{1}{\left (t \right )} \right )} \cos{\left (\theta_{2}{\left (t \right )} \right )}\\l_{1} \sin{\left (\theta_{2}{\left (t \right )} \right )}\\- l_{1} \sin{\left (\theta_{1}{\left (t \right )} \right )} \cos{\left (\theta_{2}{\left (t \right )} \right )}\end{matrix}\right]\end{eqnarray}

## Results
Thus, we are already able to find the resulting kinematics of point $C$ with respect to `` `B0``. For this, we only need to calculate the position vectors of points $A$, $B$ and $C$, and their first and second time derivatives, since the variables $\theta_1$, $\theta_2$ and $\theta_3$ were defined as time functions. For best efficiency, we could calculate the vectors using the Kinematics formulas. In addition, the `trigsimp` function was used on each element of the resulting column vectors to simplify them. The result is shown bellow.

```python
from sympy.physics.vector import time_derivative, vlatex
from sympy import trigsimp

# Cinemática do ponto A em relação ao referencial B0
r_A  = r_OA.express(B0)
v_A = time_derivative(r_A, B0)
a_A = time_derivative(v_A, B0)

# Cinemática do ponto B em relação ao referencial B0
r_B  = r_A.express(B0) + r_AB.express(B0)
v_B = time_derivative(r_B, B0)
a_B = time_derivative(v_B, B0)

# Cinemática do ponto C em relação ao referencial B0
r_C  = r_B.express(B0) + r_BC.express(B0)
v_C = (time_derivative(r_C, B0))
a_C = (time_derivative(v_C, B0))

# Simplifcação dos Resultados
r_A = (r_A.to_matrix(B0)).applyfunc(trigsimp)
v_A = (v_A.to_matrix(B0)).applyfunc(trigsimp)
a_A = (a_A.to_matrix(B0)).applyfunc(trigsimp)
r_B = (r_B.to_matrix(B0)).applyfunc(trigsimp)
v_B = (v_B.to_matrix(B0)).applyfunc(trigsimp)
a_B = (a_B.to_matrix(B0)).applyfunc(trigsimp)
r_C = (r_C.to_matrix(B0)).applyfunc(trigsimp)
v_C = (v_C.to_matrix(B0)).applyfunc(trigsimp)
a_C = (a_C.to_matrix(B0)).applyfunc(trigsimp)
```

```python
# Resultados em LaTeX na forma de Vetores Coluna
Latex("\\begin{align}"
     "r_{C}=&" + str(vlatex(r_C)) + "\\\\" + "\\\\"
     "v_{C}=&" + str(vlatex(v_C)) + "\\\\" + "\\\\"
     "a_{C}=& " + str(vlatex(a_C)) + "\\end{align}")
```

\begin{align}
_{C}=&\left[\begin{matrix}\left(l_{1} \operatorname{cos}\left(\theta_{2}\right) + l_{2} \operatorname{cos}\left(\theta_{2} + \theta_{3}\right)\right) \operatorname{cos}\left(\theta_{1}\right)\\l_{1} \operatorname{sin}\left(\theta_{2}\right) + l_{2} \operatorname{sin}\left(\theta_{2} + \theta_{3}\right)\\- \left(l_{1} \operatorname{cos}\left(\theta_{2}\right) + l_{2} \operatorname{cos}\left(\theta_{2} + \theta_{3}\right)\right) \operatorname{sin}\left(\theta_{1}\right)\end{matrix}\right]\\\\v_{C}=&\left[\begin{matrix}- l_{1} \operatorname{sin}\left(\theta_{1}\right) \operatorname{cos}\left(\theta_{2}\right) \dot{\theta}_{1} - l_{1} \operatorname{sin}\left(\theta_{2}\right) \operatorname{cos}\left(\theta_{1}\right) \dot{\theta}_{2} - l_{2} \operatorname{sin}\left(\theta_{2} + \theta_{3}\right) \operatorname{cos}\left(\theta_{1}\right) \dot{\theta}_{2} - l_{2} \operatorname{sin}\left(\theta_{2} + \theta_{3}\right) \operatorname{cos}\left(\theta_{1}\right) \dot{\theta}_{3} - l_{2} \operatorname{sin}\left(\theta_{1}\right) \operatorname{cos}\left(\theta_{2} + \theta_{3}\right) \dot{\theta}_{1}\\l_{1} \operatorname{cos}\left(\theta_{2}\right) \dot{\theta}_{2} + l_{2} \left(\dot{\theta}_{2} + \dot{\theta}_{3}\right) \operatorname{cos}\left(\theta_{2} + \theta_{3}\right)\\l_{1} \operatorname{sin}\left(\theta_{1}\right) \operatorname{sin}\left(\theta_{2}\right) \dot{\theta}_{2} - l_{1} \operatorname{cos}\left(\theta_{1}\right) \operatorname{cos}\left(\theta_{2}\right) \dot{\theta}_{1} + l_{2} \left(\operatorname{sin}\left(\theta_{2} + \theta_{3}\right) \operatorname{sin}\left(\theta_{1}\right) \dot{\theta}_{2} + \operatorname{sin}\left(\theta_{2} + \theta_{3}\right) \operatorname{sin}\left(\theta_{1}\right) \dot{\theta}_{3} - \operatorname{cos}\left(\theta_{2} + \theta_{3}\right) \operatorname{cos}\left(\theta_{1}\right) \dot{\theta}_{1}\right)\end{matrix}\right]\\\\a_{C}=& \left[\begin{matrix}2 l_{1} \operatorname{sin}\left(\theta_{1}\right) \operatorname{sin}\left(\theta_{2}\right) \dot{\theta}_{1} \dot{\theta}_{2} - l_{1} \operatorname{sin}\left(\theta_{1}\right) \operatorname{cos}\left(\theta_{2}\right) \ddot{\theta}_{1} - l_{1} \operatorname{sin}\left(\theta_{2}\right) \operatorname{cos}\left(\theta_{1}\right) \ddot{\theta}_{2} - l_{1} \operatorname{cos}\left(\theta_{1}\right) \operatorname{cos}\left(\theta_{2}\right) \dot{\theta}_{1}^{2} - l_{1} \operatorname{cos}\left(\theta_{1}\right) \operatorname{cos}\left(\theta_{2}\right) \dot{\theta}_{2}^{2} + l_{2} \left(2 \operatorname{sin}\left(\theta_{2} + \theta_{3}\right) \operatorname{sin}\left(\theta_{1}\right) \dot{\theta}_{1} \dot{\theta}_{2} + 2 \operatorname{sin}\left(\theta_{2} + \theta_{3}\right) \operatorname{sin}\left(\theta_{1}\right) \dot{\theta}_{1} \dot{\theta}_{3} - \operatorname{sin}\left(\theta_{2} + \theta_{3}\right) \operatorname{cos}\left(\theta_{1}\right) \ddot{\theta}_{2} - \operatorname{sin}\left(\theta_{2} + \theta_{3}\right) \operatorname{cos}\left(\theta_{1}\right) \ddot{\theta}_{3} - \operatorname{sin}\left(\theta_{1}\right) \operatorname{cos}\left(\theta_{2} + \theta_{3}\right) \ddot{\theta}_{1} - \operatorname{cos}\left(\theta_{2} + \theta_{3}\right) \operatorname{cos}\left(\theta_{1}\right) \dot{\theta}_{1}^{2} - \operatorname{cos}\left(\theta_{2} + \theta_{3}\right) \operatorname{cos}\left(\theta_{1}\right) \dot{\theta}_{2}^{2} - 2 \operatorname{cos}\left(\theta_{2} + \theta_{3}\right) \operatorname{cos}\left(\theta_{1}\right) \dot{\theta}_{2} \dot{\theta}_{3} - \operatorname{cos}\left(\theta_{2} + \theta_{3}\right) \operatorname{cos}\left(\theta_{1}\right) \dot{\theta}_{3}^{2}\right)\\- l_{1} \operatorname{sin}\left(\theta_{2}\right) \dot{\theta}_{2}^{2} + l_{1} \operatorname{cos}\left(\theta_{2}\right) \ddot{\theta}_{2} + l_{2} \left(- \operatorname{sin}\left(\theta_{2} + \theta_{3}\right) \dot{\theta}_{2}^{2} - 2 \operatorname{sin}\left(\theta_{2} + \theta_{3}\right) \dot{\theta}_{2} \dot{\theta}_{3} - \operatorname{sin}\left(\theta_{2} + \theta_{3}\right) \dot{\theta}_{3}^{2} + \operatorname{cos}\left(\theta_{2} + \theta_{3}\right) \ddot{\theta}_{2} + \operatorname{cos}\left(\theta_{2} + \theta_{3}\right) \ddot{\theta}_{3}\right)\\l_{1} \operatorname{sin}\left(\theta_{1}\right) \operatorname{sin}\left(\theta_{2}\right) \ddot{\theta}_{2} + l_{1} \operatorname{sin}\left(\theta_{1}\right) \operatorname{cos}\left(\theta_{2}\right) \dot{\theta}_{1}^{2} + l_{1} \operatorname{sin}\left(\theta_{1}\right) \operatorname{cos}\left(\theta_{2}\right) \dot{\theta}_{2}^{2} + 2 l_{1} \operatorname{sin}\left(\theta_{2}\right) \operatorname{cos}\left(\theta_{1}\right) \dot{\theta}_{1} \dot{\theta}_{2} - l_{1} \operatorname{cos}\left(\theta_{1}\right) \operatorname{cos}\left(\theta_{2}\right) \ddot{\theta}_{1} + l_{2} \left(\operatorname{sin}\left(\theta_{2} + \theta_{3}\right) \operatorname{sin}\left(\theta_{1}\right) \ddot{\theta}_{2} + \operatorname{sin}\left(\theta_{2} + \theta_{3}\right) \operatorname{sin}\left(\theta_{1}\right) \ddot{\theta}_{3} + 2 \operatorname{sin}\left(\theta_{2} + \theta_{3}\right) \operatorname{cos}\left(\theta_{1}\right) \dot{\theta}_{1} \dot{\theta}_{2} + 2 \operatorname{sin}\left(\theta_{2} + \theta_{3}\right) \operatorname{cos}\left(\theta_{1}\right) \dot{\theta}_{1} \dot{\theta}_{3} + \operatorname{sin}\left(\theta_{1}\right) \operatorname{cos}\left(\theta_{2} + \theta_{3}\right) \dot{\theta}_{1}^{2} + \operatorname{sin}\left(\theta_{1}\right) \operatorname{cos}\left(\theta_{2} + \theta_{3}\right) \dot{\theta}_{2}^{2} + 2 \operatorname{sin}\left(\theta_{1}\right) \operatorname{cos}\left(\theta_{2} + \theta_{3}\right) \dot{\theta}_{2} \dot{\theta}_{3} + \operatorname{sin}\left(\theta_{1}\right) \operatorname{cos}\left(\theta_{2} + \theta_{3}\right) \dot{\theta}_{3}^{2} - \operatorname{cos}\left(\theta_{2} + \theta_{3}\right) \operatorname{cos}\left(\theta_{1}\right) \ddot{\theta}_{1}\right)\end{matrix}\right]\end{align}


# An Intro to DifferentialEquations.jl
### Chris Rackauckas

## Basic Introduction Via Ordinary Differential Equations

This notebook will get you started with DifferentialEquations.jl by introducing you to the functionality for solving ordinary differential equations (ODEs). The corresponding documentation page is the [ODE tutorial](http://docs.juliadiffeq.org/latest/tutorials/ode_example.html). While some of the syntax may be different for other types of equations, the same general principles hold in each case. Our goal is to give a gentle and thorough introduction that highlights these principles in a way that will help you generalize what you have learned.

### Background

If you are new to the study of differential equations, it can be helpful to do a quick background read on [the definition of ordinary differential equations](https://en.wikipedia.org/wiki/Ordinary_differential_equation). We define an ordinary differential equation as an equation which describes the way that a variable $u$ changes, that is

$$u' = f(u,p,t)$$

where $p$ are the parameters of the model, $t$ is the time variable, and $f$ is the nonlinear model of how $u$ changes. The initial value problem also includes the information about the starting value:

$$u(t_0) = u_0$$

Together, if you know the starting value and you know how the value will change with time, then you know what the value will be at any time point in the future. This is the intuitive definition of a differential equation.

### First Model: Exponential Growth

Our first model will be the canonical exponential growth model. This model says that the rate of change is proportional to the current value, and is this:

$$u' = au$$

where we have a starting value $u(0)=u_0$. Let's say we put 1 dollar into Bitcoin which is increasing at a rate of $98\%$ per year. Then calling now $t=0$ and measuring time in years, our model is:

$$u' = 0.98u$$

and $u(0) = 1.0$. We encode this into Julia by noticing that, in this setup, we match the general form when

```julia
f(u,p,t) = 0.98u
```

with $ u_0 = 1.0 $. If we want to solve this model on a time span from `t=0.0` to `t=1.0`, then we define an `ODEProblem` by specifying this function `f`, this initial condition `u0`, and this time span as follows:

```julia
using DifferentialEquations
f(u,p,t) = 0.98u
u0 = 1.0
tspan = (0.0,1.0)
prob = ODEProblem(f,u0,tspan)
```

To solve our `ODEProblem` we use the command `solve`.

```julia
sol = solve(prob)
```

and that's it: we have succesfully solved our first ODE!

#### Analyzing the Solution

Of course, the solution type is not interesting in and of itself. We want to understand the solution! The documentation page which explains in detail the functions for analyzing the solution is the [Solution Handling](http://docs.juliadiffeq.org/latest/basics/solution.html) page. Here we will describe some of the basics. You can plot the solution using the plot recipe provided by [Plots.jl](http://docs.juliaplots.org/latest/):

```julia
using Plots; gr()
plot(sol)
```

From the picture we see that the solution is an exponential curve, which matches our intuition. As a plot recipe, we can annotate the result using any of the [Plots.jl attributes](http://docs.juliaplots.org/latest/attributes/). For example:

```julia
plot(sol,linewidth=5,title="Solution to the linear ODE with a thick line",
     xaxis="Time (t)",yaxis="u(t) (in μm)",label="My Thick Line!") # legend=false
```

Using the mutating `plot!` command we can add other pieces to our plot. For this ODE we know that the true solution is $u(t) = u_0 exp(at)$, so let's add some of the true solution to our plot:

```julia
plot!(sol.t, t->1.0*exp(0.98t),lw=3,ls=:dash,label="True Solution!")
```

In the previous command I demonstrated `sol.t`, which grabs the array of time points that the solution was saved at:

```julia
sol.t
```

We can get the array of solution values using `sol.u`:

```julia
sol.u
```

`sol.u[i]` is the value of the solution at time `sol.t[i]`. We can compute arrays of functions of the solution values using standard comprehensions, like:

```julia
[t+u for (u,t) in tuples(sol)]
```

However, one interesting feature is that, by default, the solution is a continuous function. If we check the print out again:

```julia
sol
```

you see that it says that the solution has a order changing interpolation. The default algorithm automatically switches between methods in order to handle all types of problems. For non-stiff equations (like the one we are solving), it is a continuous function of 4th order accuracy. We can call the solution as a function of time `sol(t)`. For example, to get the value at `t=0.45`, we can use the command:

```julia
sol(0.45)
```

#### Controlling the Solver

DifferentialEquations.jl has a common set of solver controls among its algorithms which can be found [at the Common Solver Options](http://docs.juliadiffeq.org/latest/basics/common_solver_opts.html) page. We will detail some of the most widely used options.

The most useful options are the tolerances `abstol` and `reltol`. These tell the internal adaptive time stepping engine how precise of a solution you want. Generally, `reltol` is the relative accuracy while `abstol` is the accuracy when `u` is near zero. These tolerances are local tolerances and thus are not global guarantees. However, a good rule of thumb is that the total solution accuracy is 1-2 digits less than the relative tolerances. Thus for the defaults `abstol=1e-6` and `reltol=1e-3`, you can expect a global accuracy of about 1-2 digits. If we want to get around 6 digits of accuracy, we can use the commands:

```julia
sol = solve(prob,abstol=1e-8,reltol=1e-8)
```

Now we can see no visible difference against the true solution:

```julia
plot(sol)
plot!(sol.t, t->1.0*exp(0.98t),lw=3,ls=:dash,label="True Solution!")
```

Notice that by decreasing the tolerance, the number of steps the solver had to take was `9` instead of the previous `5`. There is a trade off between accuracy and speed, and it is up to you to determine what is the right balance for your problem.

Another common option is to use `saveat` to make the solver save at specific time points. For example, if we want the solution at an even grid of `t=0.1k` for integers `k`, we would use the command:

```julia
sol = solve(prob,saveat=0.1)
```

Notice that when `saveat` is used the continuous output variables are no longer saved and thus `sol(t)`, the interpolation, is only first order. We can save at an uneven grid of points by passing a collection of values to `saveat`. For example:

```julia
sol = solve(prob,saveat=[0.2,0.7,0.9])
```

If we need to reduce the amount of saving, we can also turn off the continuous output directly via `dense=false`:

```julia
sol = solve(prob,dense=false)
```

and to turn off all intermediate saving we can use `save_everystep=false`:

```julia
sol = solve(prob,save_everystep=false)
```

If we want to solve and only save the final value, we can even set `save_start=false`.

```julia
sol = solve(prob,save_everystep=false,save_start = false)
```

Note that similarly on the other side there is `save_end=false`.

More advanced saving behaviors, such as saving functionals of the solution, are handled via the `SavingCallback` in the [Callback Library](http://docs.juliadiffeq.org/latest/features/callback_library.html#SavingCallback-1) which will be addressed later in the tutorial.

#### Choosing Solver Algorithms

There is no best algorithm for numerically solving a differential equation. When you call `solve(prob)`, DifferentialEquations.jl makes a guess at a good algorithm for your problem, given the properties that you ask for (the tolerances, the saving information, etc.). However, in many cases you may want more direct control. A later notebook will help introduce the various *algorithms* in DifferentialEquations.jl, but for now let's introduce the *syntax*.

The most crucial determining factor in choosing a numerical method is the stiffness of the model. Stiffness is roughly characterized by a Jacobian `f` with large eigenvalues. That's quite mathematical, and we can think of it more intuitively: if you have big numbers in `f` (like parameters of order `1e5`), then it's probably stiff. Or, as the creator of the MATLAB ODE Suite, Lawrence Shampine, likes to define it, if the standard algorithms are slow, then it's stiff. We will go into more depth about diagnosing stiffness in a later tutorial, but for now note that if you believe your model may be stiff, you can hint this to the algorithm chooser via `alg_hints = [:stiff]`.

```julia
sol = solve(prob,alg_hints=[:stiff])
```

Stiff algorithms have to solve implicit equations and linear systems at each step so they should only be used when required.

If we want to choose an algorithm directly, you can pass the algorithm type after the problem as `solve(prob,alg)`. For example, let's solve this problem using the `Tsit5()` algorithm, and just for show let's change the relative tolerance to `1e-6` at the same time:

```julia
sol = solve(prob,Tsit5(),reltol=1e-6)
```

### Systems of ODEs: The Lorenz Equation

Now let's move to a system of ODEs. The [Lorenz equation](https://en.wikipedia.org/wiki/Lorenz_system) is the famous "butterfly attractor" that spawned chaos theory. It is defined by the system of ODEs:

$$
\begin{align}
\frac{dx}{dt} &= \sigma (y - x)\\
\frac{dy}{dt} &= x (\rho - z) -y\\
\frac{dz}{dt} &= xy - \beta z
\end{align}
$$

To define a system of differential equations in DifferentialEquations.jl, we define our `f` as a vector function with a vector initial condition. Thus, for the vector `u = [x,y,z]'`, we have the derivative function:

```julia
function lorenz!(du,u,p,t)
    σ,ρ,β = p
    du[1] = σ*(u[2]-u[1])
    du[2] = u[1]*(ρ-u[3]) - u[2]
    du[3] = u[1]*u[2] - β*u[3]
end
```

Notice here we used the in-place format which writes the output to the preallocated vector `du`. For systems of equations the in-place format is faster. We use the initial condition $u_0 = [1.0,0.0,0.0]$ as follows:

```julia
u0 = [1.0,0.0,0.0]
```

Lastly, for this model we made use of the parameters `p`. We need to set this value in the `ODEProblem` as well. For our model we want to solve using the parameters $\sigma = 10$, $\rho = 28$, and $\beta = 8/3$, and thus we build the parameter collection:

```julia
p = (10,28,8/3) # we could also make this an array, or any other type!
```

Now we generate the `ODEProblem` type. In this case, since we have parameters, we add the parameter values to the end of the constructor call. Let's solve this on a time span of `t=0` to `t=100`:

```julia
tspan = (0.0,100.0)
prob = ODEProblem(lorenz!,u0,tspan,p)
```

Now, just as before, we solve the problem:

```julia
sol = solve(prob)
```

The same solution handling features apply to this case. Thus `sol.t` stores the time points and `sol.u` is an array storing the solution at the corresponding time points.

However, there are a few extra features which are good to know when dealing with systems of equations. First of all, `sol` also acts like an array. `sol[i]` returns the solution at the `i`th time point.

```julia
sol.t[10],sol[10]
```

Additionally, the solution acts like a matrix where `sol[j,i]` is the value of the `j`th variable at time `i`:

```julia
sol[2,10]
```

We can get a real matrix by performing a conversion:

```julia
A = Array(sol)
```

This is the same as sol, i.e. `sol[i,j] = A[i,j]`, but now it's a true matrix. Plotting will by default show the time series for each variable:

```julia
plot(sol)
```

If we instead want to plot values against each other, we can use the `vars` command. Let's plot variable `1` against variable `2` against variable `3`:

```julia
plot(sol,vars=(1,2,3))
```

This is the classic Lorenz attractor plot, where the `x` axis is `u[1]`, the `y` axis is `u[2]`, and the `z` axis is `u[3]`. Note that the plot recipe by default uses the interpolation, but we can turn this off:

```julia
plot(sol,vars=(1,2,3),denseplot=false)
```

Yikes! This shows how calculating the continuous solution has saved a lot of computational effort by computing only a sparse solution and filling in the values! Note that in vars, `0=time`, and thus we can plot the time series of a single component like:

```julia
plot(sol,vars=(0,2))
```

### A DSL for Parameterized Functions

In many cases you may be defining a lot of functions with parameters. There exists the domain-specific language (DSL) defined by the `@ode_def` macro for helping with this common problem. For example, we can define the Lotka-Volterra equation:

$$
\begin{align}
\frac{dx}{dt} &= ax - bxy\\
\frac{dy}{dt} &= -cy + dxy
\end{align}
$$

as follows:

```julia
function lotka_volterra!(du,u,p,t)
  du[1] = p[1]*u[1] - p[2]*u[1]*u[2]
  du[2] = -p[3]*u[2] + p[4]*u[1]*u[2]
end
```

However, that can be hard to follow since there's a lot of "programming" getting in the way. Instead, you can use the `@ode_def` macro from ParameterizedFunctions.jl:

```julia
using ParameterizedFunctions
lv! = @ode_def LotkaVolterra begin
  dx = a*x - b*x*y
  dy = -c*y + d*x*y
end a b c d
```

We can then use the result just like an ODE function from before:

```julia
u0 = [1.0,1.0]
p = (1.5,1.0,3.0,1.0)
tspan = (0.0,10.0)
prob = ODEProblem(lv!,u0,tspan,p)
sol = solve(prob)
plot(sol)
```

Not only is the DSL convenient syntax, but it does some magic behind the scenes. For example, further parts of the tutorial will describe how solvers for stiff differential equations have to make use of the Jacobian in calculations. Here, the DSL uses symbolic differentiation to automatically derive that function:

```julia
lv!.Jex
```

The DSL can derive many other functions; this ability is used to speed up the solvers. An extension to DifferentialEquations.jl, [Latexify.jl](https://korsbo.github.io/Latexify.jl/latest/tutorials/parameterizedfunctions.html), allows you to extract these pieces as LaTeX expressions.

## Internal Types

The last basic user-interface feature to explore is the choice of types. DifferentialEquations.jl respects your input types to determine the internal types that are used. Thus since in the previous cases, when we used `Float64` values for the initial condition, this meant that the internal values would be solved using `Float64`. We made sure that time was specified via `Float64` values, meaning that time steps would utilize 64-bit floats as well. But, by simply changing these types we can change what is used internally.

As a quick example, let's say we want to solve an ODE defined by a matrix. To do this, we can simply use a matrix as input.

```julia
A  = [1. 0  0 -5
      4 -2  4 -3
     -4  0  0  1
      5 -2  2  3]
u0 = rand(4,2)
tspan = (0.0,1.0)
f(u,p,t) = A*u
prob = ODEProblem(f,u0,tspan)
sol = solve(prob)
```

There is no real difference from what we did before, but now in this case `u0` is a `4x2` matrix. Because of that, the solution at each time point is matrix:

```julia
sol[3]
```

In DifferentialEquations.jl, you can use any type that defines `+`, `-`, `*`, `/`, and has an appropriate `norm`. For example, if we want arbitrary precision floating point numbers, we can change the input to be a matrix of `BigFloat`:

```julia
big_u0 = big.(u0)
```

and we can solve the `ODEProblem` with arbitrary precision numbers by using that initial condition:

```julia
prob = ODEProblem(f,big_u0,tspan)
sol = solve(prob)
```

```julia
sol[1,3]
```

To really make use of this, we would want to change `abstol` and `reltol` to be small! Notice that the type for "time" is different than the type for the dependent variables, and this can be used to optimize the algorithm via keeping multiple precisions. We can convert time to be arbitrary precision as well by defining our time span with `BigFloat` variables:

```julia
prob = ODEProblem(f,big_u0,big.(tspan))
sol = solve(prob)
```

Let's end by showing a more complicated use of types. For small arrays, it's usually faster to do operations on static arrays via the package [StaticArrays.jl](https://github.com/JuliaArrays/StaticArrays.jl). The syntax is similar to that of normal arrays, but for these special arrays we utilize the `@SMatrix` macro to indicate we want to create a static array.

```julia
using StaticArrays
A  = @SMatrix [ 1.0  0.0 0.0 -5.0
                4.0 -2.0 4.0 -3.0
               -4.0  0.0 0.0  1.0
                5.0 -2.0 2.0  3.0]
u0 = @SMatrix rand(4,2)
tspan = (0.0,1.0)
f(u,p,t) = A*u
prob = ODEProblem(f,u0,tspan)
sol = solve(prob)
```

```julia
sol[3]
```

## Conclusion

These are the basic controls in DifferentialEquations.jl. All equations are defined via a problem type, and the `solve` command is used with an algorithm choice (or the default) to get a solution. Every solution acts the same, like an array `sol[i]` with `sol.t[i]`, and also like a continuous function `sol(t)` with a nice plot command `plot(sol)`. The Common Solver Options can be used to control the solver for any equation type. Lastly, the types used in the numerical solving are determined by the input types, and this can be used to solve with arbitrary precision and add additional optimizations (this can be used to solve via GPUs for example!). While this was shown on ODEs, these techniques generalize to other types of equations as well.


# Polynomial Optimization

## Introduction

Consider the polynomial optimization problem of
minimizing the polynomial $x^3 - x^2 + 2xy -y^2 + y^3$
over the polyhedron defined by the inequalities $x \ge 0, y \ge 0$ and $x + y \geq 1$.

```julia
using DynamicPolynomials
@polyvar x y
p = x^3 - x^2 + 2x*y -y^2 + y^3
using SemialgebraicSets
S = @set x >= 0 && y >= 0 && x + y >= 1
p(x=>1, y=>0), p(x=>1//2, y=>1//2), p(x=>0, y=>1)
```

    (0, 1//4, 0)

The optimal solutions are $(x, y) = (1, 0)$ and $(x, y) = (0, 1)$ with objective value $0$ but [Ipopt](https://github.com/JuliaOpt/Ipopt.jl/) only finds the local minimum $(1/2, 1/2)$ with objective value $1/4$.

```julia
using JuMP
using Ipopt
model = Model(optimizer_with_attributes(Ipopt.Optimizer, "print_level" => 0))
@variable(model, a >= 0)
@variable(model, b >= 0)
@constraint(model, a + b >= 1)
@NLobjective(model, Min, a^3 - a^2 + 2a*b - b^2 + b^3)
optimize!(model)

@show termination_status(model)
@show value(a)
@show value(b)
@show objective_value(model)
```

    ******************************************************************************
    This program contains Ipopt, a library for large-scale nonlinear optimization.
     Ipopt is released as open source code under the Eclipse Public License (EPL).
             For more information visit http://projects.coin-or.org/Ipopt
    ******************************************************************************

    termination_status(model) = LOCALLY_SOLVED::TerminationStatusCode = 4
    value(a) = 0.4999999966705758
    value(b) = 0.49999999667062867
    objective_value(model) = 0.24999999500590342

    0.24999999500590342

Note that the problem can be written equivalently as follows using [registered functions](http://www.juliaopt.org/JuMP.jl/v0.19.0/nlp/#User-defined-Functions-1).

```julia
using JuMP
using Ipopt
model = Model(optimizer_with_attributes(Ipopt.Optimizer, "print_level" => 0))
@variable(model, a >= 0)
@variable(model, b >= 0)
@constraint(model, a + b >= 1)
peval(a, b) = p(x=>a, y=>b)
register(model, :peval, 2, peval, autodiff=true)
@NLobjective(model, Min, peval(a, b))
optimize!(model)

@show termination_status(model)
@show value(a)
@show value(b)
@show objective_value(model)
```

    termination_status(model) = LOCALLY_SOLVED::TerminationStatusCode = 4
    value(a) = 0.499999995002878
    value(b) = 0.4999999950028773
    objective_value(model) = 0.24999999250431656

    0.24999999250431656

## Sum-of-Squares approach

We will now see how to find the optimal solution using Sum of Squares Programming.

```julia
using SumOfSquares
```

We first need to pick an SDP solver, see [here](http://www.juliaopt.org/JuMP.jl/dev/installation/#Getting-Solvers-1) for a list of the available choices.

```julia
using CSDP
optimizer = optimizer_with_attributes(CSDP.Optimizer, "printlevel" => 0);
```

```julia
using MosekTools
optimizer = optimizer_with_attributes(Mosek.Optimizer, "QUIET" => true);
```

A Sum-of-Squares certificate that $p \ge \alpha$ over the domain `S`, ensures that $\alpha$ is a lower bound to the polynomial optimization problem.
The following program searches for the largest upper bound and finds zero.

```julia
model = SOSModel(optimizer)
@variable(model, α)
@objective(model, Max, α)
@constraint(model, c3, p >= α, domain = S)

optimize!(model)
@show termination_status(model)
@show objective_value(model)
```

    termination_status(model) = OPTIMAL::TerminationStatusCode = 1
    objective_value(model) = -2.0092710828478744e-10

    -2.0092710828478744e-10

Using the solution $(1/2, 1/2)$ found by Ipopt of objective value $1/4$
and this certificate of lower bound $0$ we know that the optimal objective value is in the interval $[0, 1/4]$
but we still do not know what it is (if we consider that we did not try the solutions $(1, 0)$ and $(0, 1)$ as done in the introduction).
If the dual of the constraint `c3` was atomic, its atoms would have given optimal solutions of objective value $0$ but that is not the case.

```julia
using MultivariateMoments
ν3 = moment_matrix(c3)
extractatoms(ν3, 1e-3) # Returns nothing as the dual is not atomic
```

Fortunately, there is a hierarchy of programs with increasingly better bounds that can be solved until we get one with atom dual variables.
This comes from the way the Sum-of-Squares constraint with domain `S` is formulated.
The polynomial $p - \alpha$ is guaranteed to be nonnegative over the domain `S` if there exists Sum-of-Squares polynomials $s_0$, $s_1$, $s_2$, $s_3$ such that
$$ p - \alpha = s_0 + s_1 x + s_2 y + s_3 (x + y - 1). $$
Indeed, in the domain `S`, $x$, $y$ and $x + y - 1$ are nonnegative so the right-hand side is a sum of squares hence is nonnegative.
Once the degrees of $s_1$, $s_2$ and $s_3$ have been decided, the degree needed for $s_0$ will be determined but we have a freedom in choosing the degrees of $s_1$, $s_2$ and $s_3$.
By default, they are chosen so that the degrees of $s_1 x$, $s_2 y$ and $s_3 (x + y - 1)$ match those of $p - \alpha$ but this can be overwritten using the $maxdegree$ keyword argument.

### The maxdegree keyword argument

The maximum total degree (i.e. maximum sum of the exponents of $x$ and $y$) of the monomials of $p$ is 3 so the constraint in the program above is equivalent to `@constraint(model, p >= α, domain = S, maxdegree = 3)`.
That is, since $x$, $y$ and $x + y - 1$ have total degree 1, the sum of squares polynomials $s_1$, $s_2$ and $s_3$ have been chosen with maximum total degree $2$.
Since these polynomials are sums of squares, their degree must be even so the next maximum total degree to try is 4.
For this reason, the keywords `maxdegree = 4` and `maxdegree = 5` have the same effect in this example.
In general, if the polynomials in the domain are not all odd or all even, each value of `maxdegree` has different effect in the choice of the maximum total degree of $s_i$.

```julia
model = SOSModel(optimizer)
@variable(model, α)
@objective(model, Max, α)
@constraint(model, c5, p >= α, domain = S, maxdegree = 5)

optimize!(model)
@show termination_status(model)
@show objective_value(model)
```

    termination_status(model) = OPTIMAL::TerminationStatusCode = 1
    objective_value(model) = 8.700006062680539e-8

    8.700006062680539e-8

This time, the dual variable is atomic as it is the moments of the measure
$$0.5 \delta(x-1, y) + 0.5 \delta(x, y-1)$$
where $\delta(x, y)$ is the dirac measure centered at $(0, 0)$.
Therefore the program provides both a certificate that $0$ is a lower bound and a certificate that it is also an upper bound since it is attained at the global minimizers $(1, 0)$ and $(0, 1)$.

```julia
using MultivariateMoments
ν5 = moment_matrix(c5)
extractatoms(ν5, 1e-3)
```

    Atomic measure on the variables x, y with 2 atoms:
     at [-0.000242397, 1.00024] with weight 0.4936307922624579
     at [0.999972, 2.76679e-5] with weight 0.5063248377957446

## A deeper look into atom extraction

The `extractatoms` function requires a `ranktol` argument that we have set to `1e-3` in the preceding section.
This argument is used to transform the dual variable into a system of polynomials equations whose solutions give the atoms.
This transformation uses the SVD decomposition of the moment matrix and discards the equations corresponding to a singular value lower than `ranktol`.
When this system of equation has an infinite number of solutions, `extractatoms` concludes that the measure is not atomic.
For instance, with `maxdegree = 3`, we obtain the system
$$x + y = 1$$
which contains a whole line of solution.
This explains `extractatoms` returned `nothing`.

```julia
ν3 = moment_matrix(c3)
MultivariateMoments.computesupport!(ν3, 1e-3)
```

    Algebraic Set defined by 1 equality
     -x - 0.9999999999999998*y + 1.0000000000569824 == 0

With `maxdegree = 5`, we obtain the system
\begin{align}
  x + y & = 1\\
  y^2 & = y\\
  xy & = 0\\
  x^2 + y & = 1
\end{align}

```julia
ν5 = moment_matrix(c5)
MultivariateMoments.computesupport!(ν5, 1e-3)
```

    Algebraic Set defined by 4 equalities
     -x - 0.9999999962812296*y + 1.0000000580930948 == 0
     -y^2 + 1.0002701267987304*y - 2.7674560303336815e-5 == 0
     -x*y - 1.293349230893601e-6*y + 3.3938404378219955e-5 == 0
     -x^2 - 1.0002601608230963*y + 1.0002330609868753 == 0

This system can be reduced to the equivalent system
\begin{align}
  x + y & = 1\\
  y^2 & = y
\end{align}
which has the solutions $(0, 1)$ and $(1, 0)$.

```julia
SemialgebraicSets.computegröbnerbasis!(ideal(ν5.support))
ν5.support
```

    Algebraic Set defined by 2 equalities
     x + 0.9999999962812296*y - 1.0000000580930948 == 0
     y^2 - 1.0002701267987304*y + 2.7674560303336815e-5 == 0

The function `extractatoms` then reuse the matrix of moments to find the weights $1/2$, $1/2$ corresponding to the diracs centered respectively at $(0, 1)$ and $(1, 0)$.
This details the how the function obtained the result
$$0.5 \delta(x-1, y) + 0.5 \delta(x, y-1)$$
given in the previous section.


```python
import sympy as sym
sym.init_printing()
%matplotlib inline
```

**Ejercicio**

A partir de este circuito encuentre las expresiones matemáticas para el voltaje en cada resistencia en función de $Vi$.

\begin{align}
V1 &= expresión\\
V2 &= expresión\\
V3 &= expresión\\
V4 &= expresión\\
V5 &= expresión\\
V6 &= expresión\\
V7 &= expresión\\
V8 &= expresión\\
\end{align}

Considerando que todas las resistencias son de $1 k\Omega$, encuentre los voltajes para cuando $Vi = 15V$

```python
# Celda para código
```

Considerando que todas las resistencias son de $5 k\Omega$, encuentre los voltajes para cuando $Vi = 10V$

```python
# Celda para código
```

Considerando que $ R1 = R2 = R4 = 5 k\Omega$, $ R3 = R5 = R8 = 2 k\Omega$ y $ R6 = R7 = 100 \Omega$, encuentre los voltajes para cuando $Vi = 12V$

```python
# Celda para código
```


```python
# Notebook imports and packages
import numpy as np
from sympy import symbols, diff, lambdify
```

# Please lambdify your derivatives

$$f(x, y)=\frac{1}{3^{-x^2-y^2}+1}$$

$$\frac{\partial f(x, y)}{\partial x}=\frac{2x\ln \left(3\right)\cdot \:3^{-x^2-y^2}}{\left(3^{-x^2-y^2}+1\right)^2}$$

$$\frac{\partial f(x, y)}{\partial y}=\frac{2y\ln \left(3\right)\cdot \:3^{-y^2-x^2}}{\left(3^{-x^2-y^2}+1\right)^2}$$

```python
def f(x, y):
    return 1/(3**(-(x**2)-(y**2)) + 1)
```

```python
a, b = symbols('x, y')
```

Make sure to lambdify your functions:

If you do GD with `diff(f(a,b),a).evalf(subs={a:params[0],b:params[1]})` differentiating every time, it will be slow.

10k iterations with `dfx(x,y)` is way faster than 1k iterations differentiating every time.

```python
dfx=lambdify([a,b], diff(f(a,b), a))
dfy=lambdify([a,b], diff(f(a,b), b))
```

```python
params = np.array([1.8, 1.0])
```

```python
dfx(x=1.8,y=1.0), dfx(1.8,1.0), dfx(*params) # All three result in the same thing
```

    (0.036808971619750504, 0.036808971619750504, 0.036808971619750504)

# Bath Gradient Descent with SymPy

```python
multiplier = .1
max_iter = 10000
params = np.array([1.8, 1.0])

for n in range(max_iter):
    gradient_x, gradient_y = dfx(*params), dfy(*params)
    gradients = np.array([gradient_x,gradient_y]) # These two first steps could be combined into one;
    params = params - multiplier * gradients

print("Values in gradient array", gradients)
print("Minimum occurs at (x,y):", tuple(params))
print("The cost is:\t\t", f(*params))
```

    Values in gradient array [1.61557085e-244 8.97539364e-245]
    Minimum occurs at (x,y): (2.7795548456563392e-244, 1.544197136475744e-244)
    The cost is:		 0.5


```python
import matplotlib.pyplot as plt
import scipy as sp
import scipy.special
import pandas as pd
import scipy.stats
import numpy as np
```

#  Point Estimator

To estimate the population parameters (e.g. average income of man with a master degree), we compute corresponding characteristics of the sample, referred to as **sample statistic**.

Any types of single value sample statistic is called **point estimator**, previously we have seen  **Sample mean, standard deviation, variance**, which all are point estimators of populations. For instance, $\bar{x}$ is the point estimator of $\mu$ and $s^2$ the point estimator of $\sigma^2$.

#  Sampling Distribution

The pointer estimators themselves are random variables. For instance we have 10 samples of household income, so each sample has a mean value, which are most probably different than each other, therefore the sample mean is essentially a random variable.|

The distribution of point estimator is called the  **sampling distribution**, i.e. the distribution of sample statistics. The knowledge of its properties will enable us to make statements about how close the point estimates (e.g. sample mean) to the population parameters.

##  Sampling Distribution of $\bar{X}$

If point estimators are unbiased, then  **mean of sampling distribution** and  **standard error of the mean** are:
$$
E(\bar{X})=\mu\\
\sigma_{\bar{X}}=\frac{\sigma}{\sqrt{n}}\sqrt{\frac{N-n}{N-1}}
$$

The name of _standard error_ generally is used when referring to the standard deviation of a sampling distribution.

To illustrate the sampling distribution of $\bar{X}$, we will generate a population of $100000$ people's height with standard deviation of $3$, mean of $170$cm.

```python
mu = 170
sigma = 3
pop_size = 100000
pop_height = sp.stats.norm.rvs(mu, sigma, size = pop_size)
```

Now pretend that we know nothing of the population, but we are able to draw as many samples as possible with zero cost (this will never be possible in real world, but perfect illustration to sampling distribution). After drawing each sample we calculate the sample mean then append onto the list ```sample_mean```, finally we plot the histogram.

Here we plot six sampling distributions with increasing sample sizes, the larger the sample size $n$, the smaller the standard errors are. The red dashed line is a normal distribution $z\sim N(170, 3)$, in contrast, the histogram of sample mean is much concentrated.

```python
sample_sizes = [2**i for i in range(1, 7)]
number_of_sample = 1000
sample_means = []

norm_pdf_x = np.linspace(160, 180, 100)
norm_pdf_y = sp.stats.norm.pdf(norm_pdf_x, mu, sigma)
fig, axs = plt.subplots(nrows=3, ncols=2, figsize=(18, 18))
for indx, ax in enumerate(axs.flatten()):
    for i in range(number_of_sample):
        sample_means.append(np.mean(np.random.choice(pop_height, size = sample_sizes[indx])))
    n, bins, patches = ax.hist(sample_means, bins = 100, label = 'Sampling Distribution of Mean of Height', density= True)
    ax.axvline(x = mu, color = 'tomato', lw = 3)
    ax.plot(norm_pdf_x, norm_pdf_y, 'r--', label="$z\sim N(170, 3)$")
    ax.set_xlim(160, 180)
    ax.annotate('number of sample: {}\nsample size: {}\nstandard error: {}'
                .format(10000, sample_sizes[indx], round(np.std(sample_means, ddof=1), 2)), (160.5, np.max(n)*.90), size = 11)
fig.suptitle('Sampling Distribution Of Means With Various Sample Size', y = 0.9, size = 18)
plt.show()
```

From $\sigma_{\bar{x}}=\frac{\sigma}{\sqrt{n}}\sqrt{\frac{N-n}{N-1}}$, we can see as $n\rightarrow N$, then $\sigma_{\bar{x}}\rightarrow 0$.

Though raising sample size can contract the variation of sampling distribution,  but efficiency drops relatively fast. Take a look at graph of $\sqrt{n}$, the slope is steep in the beginning, then flattens as the sample size increases.

```python
x = np.linspace(0, 10, 1000)
y = np.sqrt(x)
fig, ax = plt.subplots(figsize=(12,5))
ax.plot(x, y, color = 'tomato', lw = 3)
ax.set_ylim(0, 4)
ax.grid()
ax.set_title('$y=\sqrt{n}$', size = 18)
plt.show()
```

There are also sampling distribution of sample variances.

```python
sample_sizes = [2**i for i in range(1, 18,3)]
number_of_sample = 1000
sample_vars = []

fig, axs = plt.subplots(nrows=3, ncols=2, figsize=(18, 18))
for indx, ax in enumerate(axs.flatten()):
    for i in range(number_of_sample):
        sample_vars.append(np.var(np.random.choice(pop_height, size = sample_sizes[indx]), ddof = 1))
    n, bins, patches = ax.hist(sample_vars, bins = 100, label = 'Sampling Distribution of Variance of Height', density= True)
    ax.axvline(x = np.mean(sample_vars), color = 'tomato', lw = 3)
    ax.annotate('number of sample: {}\nsample size: {}\nstandard error: {:.2f}\nmean: {:.2f}'
                .format(10000, sample_sizes[indx], np.std(sample_vars, ddof=1), np.mean(sample_vars)), (30, np.max(n)*.80), size = 11)
fig.suptitle('Sampling Distribution Of Variances With Various Sample Size', y = 0.9, size = 18)
plt.show()
```

#  Central Limit Theorem

We mentioned above that properties of sampling distribution can help us making meaningful inferences about population. The most important property is **Central Limit Theorem** which guarantees that with large sample size, we can safely assume the sample mean follows a normal distribution. With this information, we can make statistical inferences such as how far away the sample mean is from the population mean?

Here is the classical definition:

$\{\bar{X}_1,...\bar{X}_2\}$ is a random i.i.d. sequence drawn from an unknown distribution whose $E(X_i)=\mu$ and $\text{Var}(X_i)=\sigma^2$, as $n\rightarrow \infty$, we have
$$
\sqrt{n}(\bar{X}_n-\mu)\xrightarrow[]{d} N(0, \sigma^2)
$$

Simply speaking, sampling distribution of sample mean will converge to a normal distribution when sample size is large enough. The figure below shows three different distributions other than normal distribution, as the sample size increases their sampling distributions converge to normal distribution.

The code is in module ```plot_material```.

```python
from plot_material import central_limit_theorem_plot
central_limit_theorem_plot()
```

    Font 'default' does not have a glyph for '\uff0c' [U+ff0c], substituting with a dummy symbol.

#  Sample Size and Estimation Accuracy

Here is a concrete example that demonstrates how sample sizes influence the estimation accuracy.

To estimate the average height of female in the city of Helsinki, we measure the height of $n = 30$ female adults, we have the sample mean but we also would like to know if the sample mean is within $\pm 1.5cm$ of population mean. And we need to have a key but unrealistic assumption: we know the population standard deviation of the height: $\sigma = 6 cm$.

Step 1: Because we know the $\sigma$, we can calculate the standard error of the mean by using

\begin{equation}
\sigma_{\bar{x}}=\frac{\sigma}{\sqrt{n}}= \frac{6}{\sqrt{30}}
\end{equation}

```python
sigma = 6
std_err = sigma/np.sqrt(30)
std_err
```

Step 2: Calculate of range in terms of standard errors.

```python
z_right = 1.5/std_err # plus or minus zr/zl units of std errors
z_left = -1.5/std_err
```

```python
p = sp.stats.norm.cdf(z_right)-sp.stats.norm.cdf(z_left)
print('The probability of sample mean (sample size = 30) being within 1.5cm of pop mean is {0:.2f}%.'.format(p*100))
```

Now let's increase the sample size to 60 persons.

```python
sigma = 6
ss = 60

std_err = sigma/np.sqrt(ss)
zr = 1.5/std_err # plus or minus zr/zl units of std errors
zl = -1.5/std_err
p = sp.stats.norm.cdf(zr)-sp.stats.norm.cdf(zl)
print('The probability of sample mean (sample size = {0}) being within 1.5cm of pop mean is {1:.2f}%.'.format(ss, p*100))
```

And this figure can show how the sample size can influence the estimation accuracy. We will come back to similar topics in interval estimations.

```python
sigma = 6
p = np.zeros(100)
for i in range(2, 100):
    std_err = sigma/np.sqrt(i)
    zr = 1.5/std_err # plus or minus zr/zl units of std errors
    zl = -1.5/std_err
    p[i] = sp.stats.norm.cdf(zr)-sp.stats.norm.cdf(zl)
fig, ax = plt.subplots(figsize = (12, 7))
ax.plot(np.arange(100), p, lw = 3, color = 'r')
ax.set_xlabel('Sample Size', size = 16)
ax.set_ylabel('Probability', size = 16)
ax.set_title('The probability of sample mean being within 1.5cm of pop mean',size = 18)
plt.show()
```

#  Interval Estimation With Known $\sigma$

The example above is exactly an **interval estimation** of population mean with known $\sigma$. We will dive deeper here, once you understand the logic of interval estimation, you will have much easier time in later chapters.

We have talked about _Central Limit Theorem_ which guarantees that $\bar{X}$ is normally distributed with sufficiently large samples. But we almost always convert $\bar{X}$ into a standard normal distribution before making any inferences.
$$
Z=\frac{\bar{x}-\mu}{\sigma/\sqrt{n}}
$$

Essentially, it is a $z$-score of $\bar{X}$ as we mentioned in chapter 1. To make any meaningful interval estimation, we need a notation $z_{\alpha/2}$, such as $z_{0.025}=1.96$. It means the area to the right of $1.96$ is $0.025$.

To establish an interval
$$
P\bigg(-z_{\alpha/2} < \frac{\bar{x}-\mu}{\sigma/\sqrt{n}} < z_{\alpha/2}\bigg)=.95
$$
With a bit rearrangement, which means that with $1-\alpha$ probability the $X$ will fall in this range.
$$
P\bigg(\mu-z_{\alpha/2}\frac{\sigma}{\sqrt{n}} < \bar{x}< \mu+z_{\alpha/2}\frac{\sigma}{\sqrt{n}}\bigg)=1-\alpha
$$

The sample mean of women's height in Helsinki will fall in
$$
P\bigg(170-1.96\frac{6}{\sqrt{30}} < \bar{x}< 170+1.96\frac{6}{\sqrt{30}}\bigg)=.95
$$

```python
print(170-1.96*6/np.sqrt(30))
print(170+1.96*6/np.sqrt(30))
```

However expression above is less common in practice, more commonly $\mu$ is set as the centre of the interval, a slight rearrangement gives us
$$
P\bigg(\bar{x}-Z_{\alpha/2}\frac{\sigma}{\sqrt{n}}<\mu<\bar{x}+Z_{\alpha/2}\frac{\sigma}{\sqrt{n}}\bigg)
$$

this is officially called **confidence interval estimator of population mean**. It states that there is $1-\alpha$ probability that $\bar{X}$ will equal to a value such that the interval will include the population mean.

##  Example of Sleeping Hours

Suppose we have a sample of ten old person (>70 years old) whose average sleeping time recorded as below

```python
sleep_time = [8.3, 6.9, 4.3, 10.8, 7.9, 9.6, 6.8, 5.6, 7.7]
```

The standard deviation of the population sleeping time is $2$, enough information to calculate the confidence interval of $95\%$.

```python
LCL = np.mean(sleep_time)-1.96*2/np.sqrt(len(sleep_time))
UCL = np.mean(sleep_time)+1.96*2/np.sqrt(len(sleep_time))
print('Confidence interval of 95% is ({:.2f}, {:.2f}).'.format(LCL, UCL))
```

Because the sample size is considerably small, the confidence interval doesn't provide much info either.

Here we've created a class for population of old people's sleeping time with some methods for easy computation of interval estimators, i.e. ```conf_interval``` for _known $\sigma$_ and ```conf_interval_t``` for _unknown $\sigma$_.

```python
class OldPeopleSleep:
    def __init__(self, mean, std, pop_size):
        self.std = std
        self.population = sp.stats.norm.rvs(loc=mean, scale=std, size=pop_size)

    def sampling(self, size):
        self.sample = np.random.choice(self.population, size)

    def conf_interval(self, confidence_level, two_tail = True):
        '''For simplicity, we only show a two tail confidence interval.'''
        if two_tail == True:
            ppf = 1 - (1 - confidence_level)/2
            z = round(sp.stats.norm.ppf(ppf), 2)
            LCL = np.mean(self.sample)-z*self.std/np.sqrt(len(self.sample))
            UCL = np.mean(self.sample)+z*self.std/np.sqrt(len(self.sample))
            return LCL, UCL
    def conf_interval_t(self, confidence_level, two_tail = True):
        '''For simplicity, we only show a two tail confidence interval.'''
        if two_tail == True:
            ppf = 1 - (1 - confidence_level)/2
            t = round(sp.stats.t.ppf(ppf, df = len(self.sample)-1), 2)
            LCL_t = np.mean(self.sample)-t*np.std(self.sample, ddof = 1)/np.sqrt(len(self.sample))
            UCL_t = np.mean(self.sample)+t*np.std(self.sample, ddof = 1)/np.sqrt(len(self.sample))
            return LCL_t, UCL_t
```

Generate a population and take a sample of 100 persons, calculate the confidence interval

```python
population_mean = 7.5
old_ppl = OldPeopleSleep(population_mean, std=2, pop_size=100000)
old_ppl.sampling(100)
old_ppl.conf_interval(.95, two_tail=True)
```

We will simulate repetitive sampling and show that $95\%$ of time the confidence interval can include the population mean. We draw sample ```simu_time``` times, each time calculate its confidence interval based on sampling distribution, if the population mean is included in confidence interval, we append a $1$ onto a list named ```conf_inclusion```, otherwise append a $0$.

It shows that round $95\%$ of times the confidence interval will indeed include population mean.

```python
conf_inclusion =[]
simu_time = 10000
for i in range(simu_time):
    old_ppl.sampling(100)
    conf_interval = old_ppl.conf_interval(.95, two_tail=True)
    if conf_interval[0] <= population_mean and population_mean <= conf_interval[1]:
        conf_inclusion.append(1)
    else:
        conf_inclusion.append(0)
print('Total number of simulation: {}'.format(simu_time))
print('Number of inclusion: {}'.format(np.sum(conf_inclusion)))
print('Inclusion rate: {}'.format(np.sum(conf_inclusion)/len(conf_inclusion)))
```

#  Interval Estimation With Unknown $\sigma$

In practice, we barely have the knowledge of the $\sigma$, then $s$ is used instead. Because of unknown $\sigma$, the sampling distribution of mean will be based on $t$-distribution. A $t$-distribution with large degree of freedom is very close to a normal distribution, while smaller degree of freedom will cause fatter tails in $t$-distribution than normal distribution.

The notation of interval estimator based on $t$-distribution is similar to the one based on the normal distribution
$$
P\bigg(\bar{x}-t_{\alpha/2}\frac{s}{\sqrt{n}}<\mu<\bar{x}+t_{\alpha/2}\frac{s}{\sqrt{n}}\bigg)
$$
where $s$ is the sample standard deviation
$$
s = \sqrt{\frac{\sum(x_i - \bar{x})^2}{n-1}}
$$

We can compare the confidence based on normal and $t$-distribution, very similar.

```python
population_mean = 7.5
old_ppl = OldPeopleSleep(population_mean, std=2, pop_size=100000)
old_ppl.sampling(10)
print(old_ppl.conf_interval(.95, two_tail=True))
print(old_ppl.conf_interval_t(.95, two_tail=True))
```

```python
conf_inclusion_t =[]
simu_time = 10000
for i in range(simu_time):
    old_ppl.sampling(100)
    conf_interval_t = old_ppl.conf_interval_t(.95, two_tail=True)
    if conf_interval_t[0] <= population_mean and population_mean <= conf_interval_t[1]:
        conf_inclusion_t.append(1)
    else:
        conf_inclusion_t.append(0)
print('Total number of simulation: {}'.format(simu_time))
print('Number of inclusion: {}'.format(np.sum(conf_inclusion)))
print('Inclusion rate: {}'.format(np.sum(conf_inclusion)/len(conf_inclusion)))
```

Chapter 3 - Point and Interval Estimation.ipynb


# Vehicle Tracking with Noisy Sensors and Missing Data

We will try to pinpoint the location of a moving vehicle with high accuracy using only noisy (and sometimes missing) sensor data. We'll do this with a simple model for the physics of the vehicle, and an assumption that the vehicle's input acceleration is piecewise constant, with only occasional changes. That is, changes to the acceleration are sparse.

## Model

We'll model the vehicle with a discrete-time linear dynamical system with state vector $x_t \in \mathbf{R}^4$ at time $t$, where $(x_{t,0}, x_{t,1})$ is the position of the vehicle in two dimensions, and $(x_{t,2}, x_{t,3})$ is the vehicle velocity. The system has a control input $u_t \in \mathbf{R}^2$, which is the input acceleration in two dimensions. We only observe noisy measurements, $y_t \in \mathbf{R}^2$, of the vehicle's position at each time step.

The system evolves according to the equations

$$
\begin{align}
x_{t+1} &= A x_t + B u_t \\
y_{t} &= C x_t + \nu_t,
\end{align}
$$
with matrices
$$
A = \begin{bmatrix}
1 & 0 & \left(1-\frac{\gamma}{2}\Delta t\right) \Delta t & 0 \\
0 & 1 & 0 & \left(1-\frac{\gamma}{2} \Delta t\right) \Delta t\\
0 & 0 & 1-\gamma \Delta t & 0 \\
0 & 0 & 0 & 1-\gamma \Delta t
\end{bmatrix},
$$

$$
B = \begin{bmatrix}
\frac{1}{2}\Delta t^2 & 0 \\
0 & \frac{1}{2}\Delta t^2 \\
\Delta t & 0 \\
0 & \Delta t \\
\end{bmatrix},
$$

$$
C = \begin{bmatrix}
1 & 0 & 0 & 0 \\
0 & 1 & 0 & 0
\end{bmatrix},
$$

where $\nu_t$ is measurement noise for the vehicle position, and $\gamma$ is a damping parameter.

## 1D Model
The recurrence is derived from the following relations in a single dimension. For this subsection, let $x_t, v_t, a_t$ be the vehicle position, velocity, and input acceleration. The true acceleration of the vehicle is $a_t - \gamma v_t$, with $\gamma$ a damping parameter.

The discretized dynamics are then
$$
\begin{align}
x_{t+1} &= x_t + \left(1-\frac{\gamma \Delta t}{2}\right)v_t \Delta t + \frac{1}{2}a_{t} \Delta t^2\\
v_{t+1} &= \left(1-\gamma\right)v_t + a_t \Delta t.
\end{align}
$$

## Contents
- Exploiting Sparsity: $\ell_1$ Regularization
- Handling Outliers: $\ell_1$ Loss for Robust Kalman Filtering
- Missing Data
- Do we need convex optimization?: Comparison with Least-Squares Approaches
    - $\ell_2$ Regularization
    - $\ell_2$ Smoothing: Acceleration
    - $\ell_2$ Smoothing: Velocity
    - $\ell_2$ Smoothing: Position
- (possible) Noisy dynamics

## Helper Functions

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

def accel_sched(vectors, times, n):
    times = np.array(times, dtype=float)
    times /= times.sum()
    times = np.int_(np.floor(times*n))
    times[-1] += n - times.sum()

    count = 0
    u = np.zeros((2,n))
    for v, k in zip(vectors,times):
        v = np.array(v).reshape(2,1)
        u[:,count:count+k] = np.tile(v,(1,k))
        count += k

    return u

def plot_state(actual, estimated=None):
    '''
    plot position, speed, and acceleration in the x and y coordinates for
    the actual data, and optionally for the estimated data
    '''
    trajectories = [actual]
    if estimated is not None:
        trajectories.append(estimated)

    fig, ax = plt.subplots(3, 2, sharex='col', sharey='row', figsize=(8,8))
    for x, u in trajectories:
        ax[0,0].plot(t,x[0,:])
        ax[0,1].plot(t,x[1,:])
        ax[1,0].plot(t,x[2,:])
        ax[1,1].plot(t,x[3,:])
        ax[2,0].plot(t,u[0,:])
        ax[2,1].plot(t,u[1,:])

    ax[0,0].set_ylabel('x position')
    ax[1,0].set_ylabel('x speed')
    ax[2,0].set_ylabel('x acceleration')

    ax[0,1].set_ylabel('y position')
    ax[1,1].set_ylabel('y speed')
    ax[2,1].set_ylabel('y acceleration')

    ax[0,1].yaxis.tick_right()
    ax[1,1].yaxis.tick_right()
    ax[2,1].yaxis.tick_right()

    ax[0,1].yaxis.set_label_position("right")
    ax[1,1].yaxis.set_label_position("right")
    ax[2,1].yaxis.set_label_position("right")

    ax[2,0].set_xlabel('time')
    ax[2,1].set_xlabel('time')

def plot_positions(xtrue, y=None, recovered=None):
    '''
    show point clouds for true, observed, and recovered positions
    '''
    titles = ['True', 'Noisy', 'Recovered']
    trajectories = [xtrue]
    if y is not None:
        trajectories += [y]
    if recovered is not None:
        trajectories += [recovered]

    n = len(trajectories)

    fig, ax = plt.subplots(1, n, sharex=True, sharey=True,figsize=(12, 4))
    if n == 1:
        ax = [ax]

    for i,x in enumerate(trajectories):
        ax[i].plot(x[0,:], x[1,:], 'ro', alpha=.1)
        ax[i].set_title(titles[i])
        #ax[i].set_aspect('equal')
        #ax[i].axis('equal')

    #x0,x1 = ax[0].get_xlim()
    #y0,y1 = ax[0].get_ylim()
    #ax[0].set_aspect(abs(x1-x0)/abs(y1-y0))
```

## Problem Data
- set problem parameters
- form dynamics matrices
- simulate vehicle movement
- form noisy observations of just the vehicle position

```
n = 1000 # number of timesteps
T = 10 # time will vary from 0 to T with step delt
t, delt = np.linspace(0,T,n,endpoint=True, retstep=True)
gamma = 1 # damping, 0 is no damping
noise_level = .15 # sensing errors

A = np.zeros((4,4))
B = np.zeros((4,2))

A[0,0] = 1
A[1,1] = 1
A[0,2] = (1-gamma*delt/2)*delt
A[1,3] = (1-gamma*delt/2)*delt
A[2,2] = 1 - gamma*delt
A[3,3] = 1 - gamma*delt

B[0,0] = delt**2/2
B[1,1] = delt**2/2
B[2,0] = delt
B[3,1] = delt

x = np.zeros((4,n))
x[:,0] = [0,0,0,0]

# form the piecewise constant acceleration schedule
u = accel_sched([(1,1.2),(-1,.6),(0,-1.3), (-1.2,.6),], [300,400,500,700], n)

# simulate the system forward in time
for i in range(n-1):
    x[:,i+1] = A.dot(x[:,i]) + B.dot(u[:,i])

xtrue = x.copy()
utrue = u.copy()

# form noisy observations
y = noise_level*np.random.randn(2,n) + xtrue[0:2,:]

# plot system state
plot_state((xtrue,utrue))

# plot true and noisy position observations
plot_positions(xtrue,y)
```

## Exploiting Sparsity: Total Variation Regularization

The noise in the position measurements makes it difficult to pinpoint the vehicle's location. However, we can exploit the fact that the measurments don't *look* like how a vehicle would actually move. The measurments have the vehicle jumping around erratically. Thus, we'll require our recovered vehicle's movements to follow the known dynamics exactly as

$$
x_{t+1} = A x_t + B u_t,
$$

which will constrain the way in which the model vehicle can move.

This isn't enough, however, beacause no matter what the observed positions $y_t$ are, there is some control schedule $u_t$, which hits those points exactly. But for noisy data like this, that control schedule will probably vary erratically. We can use exploit the fact that we know the changes in the acceleration schedule are sparse. We can try to induce such sparsity by adding total variation regularization to our model:

$$
\begin{array}{ll}
  \mbox{minimize} & \sum_{t=0}^{n} \|y_t - x_{t,0:1}\|_2^2 + \rho\sum_{t=1}^{n} \|u_t - u_{t-1}\|_1\\
  \mbox{subject to} & x_{t+1} = A x_t + B u_t
\end{array}
$$

The first term in the objective penalizes differences between the model and observed positions. The second term penalizes changes in the control schedule. The parameter $\rho$ trades off between the two. For example, setting $\rho = 0$ would match the observed positions exactly, but the acceleration schedule wouln't match our expecation.

## CVXPY Implementation
The code below uses CVXPY to implement and solve the model. The plots compare the true and recovered system state, which end up being very close!

```
import cvxpy as cvx

x = cvx.Variable(4,n)
u = cvx.Variable(2,n)
rho = .1

objective = cvx.norm(x[0,:].T - y[0,:])/n + cvx.norm(x[1,:].T - y[1,:])/n
objective += rho*cvx.norm1(u[0,0:n-1] - u[0,1:n])/n + rho*cvx.norm1(u[1,0:n-1] - u[1,1:n])/n
objective = cvx.Minimize(objective)

constraints = []
for i in range(n-1):
    constraints += [x[:,i+1] == A*x[:,i] + B*u[:,i]]

prob = cvx.Problem(objective, constraints)

result = prob.solve(solver=cvx.ECOS, verbose=True)

x = np.array(x.value)
u = np.array(u.value)

plot_state((xtrue,utrue),(x,u))
plot_positions(xtrue,y,x)
```

# Robust Kalman Filtering

```
noise_level = .1
outlier_percentage = .10

outlier_inds = np.random.permutation(n)[:int(np.ceil(outlier_percentage*n))]
# noisy observations
y = noise_level*np.random.randn(2,n) + xtrue[0:2,:]
y[:,outlier_inds] += 10*noise_level*np.random.randn(2,len(outlier_inds))

plot_state((xtrue,utrue))
plot_positions(xtrue,y)
```

```
import cvxpy as cvx

x = cvx.Variable(4,n)
u = cvx.Variable(2,n)
rho = 1

objective = cvx.norm1(x[0,:].T - y[0,:])/n + cvx.norm1(x[1,:].T - y[1,:])/n
objective += rho*cvx.norm1(u[0,0:n-1] - u[0,1:n])/n + rho*cvx.norm1(u[1,0:n-1] - u[1,1:n])/n
objective = cvx.Minimize(objective)

constraints = []
for i in range(n-1):
    constraints += [x[:,i+1] == A*x[:,i] + B*u[:,i]]

prob = cvx.Problem(objective, constraints)

result = prob.solve(solver=cvx.ECOS, verbose=True)

x = np.array(x.value)
u = np.array(u.value)

plot_state((xtrue,utrue),(x,u))
plot_positions(xtrue,y)
```

# Missing Data

We randomly observe only 40% of the data points. These observations are still noisy.

```
noise_level = .1
present_percentage = .40

inds = np.random.permutation(n)[:int(np.ceil(present_percentage*n))]

# noisy observations
y = noise_level*np.random.randn(2,n) + xtrue[0:2,:]
y = y[:,inds]

plot_state((xtrue,utrue))
plot_positions(xtrue,y)
```

```
import cvxpy as cvx

x = cvx.Variable(4,n)
u = cvx.Variable(2,n)
rho = 1

m = len(inds)
y2 = np.zeros((2,n))
y2[:,inds] = y

objective = sum(cvx.square(x[0,ind] - y2[0,ind])/m + cvx.square(x[1,ind] - y2[1,ind])/m for ind in inds )
objective += rho*cvx.norm1(u[0,0:n-1] - u[0,1:n])/n + rho*cvx.norm1(u[1,0:n-1] - u[1,1:n])/n
objective = cvx.Minimize(objective)

constraints = []
for i in range(n-1):
    constraints += [x[:,i+1] == A*x[:,i] + B*u[:,i]]

prob = cvx.Problem(objective, constraints)

result = prob.solve(solver=cvx.ECOS, verbose=True)

x = np.array(x.value)
u = np.array(u.value)

plot_state((xtrue,utrue),(x,u))
plot_positions(xtrue,y,x)
```

```

```


# Change points

*Joseph Hall (October 2019)*

This notebook demonstrates the use of the `ChangePoints` kernel, which can be used to describe one-dimensional functions that contain a number of change-points, or regime changes. The kernel makes use of sigmoids ($\sigma$) to blend smoothly between different kernels. For example, a single change-point kernel is defined by:

\begin{equation}
\textrm{cov}(f(x), f(y)) = k_1(x, y)\cdot\bar{\sigma}(x, y) + k_2(x, y)\cdot\sigma(x, y)
\end{equation}

where $\sigma(x, y) = \sigma(x)\cdot\sigma(y)$ and $\bar{\sigma}(x, y) = (1 - \sigma(x))\cdot(1 - \sigma(y))$. The sigmoid ($\sigma$) is parameterized by a location ($l$) and a width ($w$).

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

np.random.seed(123)  # for reproducibility of this notebook

plt.style.use("ggplot")
%matplotlib inline

def plotkernelsample(k, ax, xmin=-3, xmax=3, title=None):
    xx = np.linspace(xmin, xmax, 100)[:, None]
    ax.plot(xx, np.random.multivariate_normal(np.zeros(100), k(xx), 3).T)
    ax.set_title(title)
```

We demonstrate the use of the kernel by drawing a number of samples from different parameterizations. Firstly, a simple single change-point between two kernels of differing lengthscales.

```python
np.random.seed(1)

base_k1 = gpflow.kernels.Matern32(lengthscales=0.2)
base_k2 = gpflow.kernels.Matern32(lengthscales=2.0)
k = gpflow.kernels.ChangePoints([base_k1, base_k2], [0.0], steepness=5.0)

f, ax = plt.subplots(1, 1, figsize=(10, 3))
plotkernelsample(k, ax)
```

Secondly, an implementation of a "change window" in which we change from one kernel to another, then back to the original.

```python
np.random.seed(3)

base_k1 = gpflow.kernels.Matern32(lengthscales=0.3)
base_k2 = gpflow.kernels.Constant()
k = gpflow.kernels.ChangePoints([base_k1, base_k2, base_k1], locations=[-1, 1], steepness=10.0)

f, ax = plt.subplots(1, 1, figsize=(10, 3))
plotkernelsample(k, ax)
```

And finally, allowing different change-points to occur more or less abruptly by defining different steepness parameters.

```python
np.random.seed(2)

base_k1 = gpflow.kernels.Matern32(lengthscales=0.3)
base_k2 = gpflow.kernels.Constant()
k = gpflow.kernels.ChangePoints(
    [base_k1, base_k2, base_k1], locations=[-1, 1], steepness=[5.0, 50.0]
)

f, ax = plt.subplots(1, 1, figsize=(10, 3))
plotkernelsample(k, ax)
```


# Symbolic Mathematics in Python

There are times when you need to solve a difficult problem symbollically or analytically. If you have ever used Wolfram Alpha, then you have already done this. Sympy is a python library that allows you to do symbolic mathematics in python.

```python
import sympy as sym
```

## 1. Introduction

### Example 1.1

If you try to write the follwing in python by itself, you will get an error telling you x is undefined:
$$x-x$$

```python
x-x
```

(The error above is on purpose). Variables in python need to be defined before you can say something specific about them

```python
x=102
x-x
```

If you are trying to show that $x-x=0$ is true for any $x$, the above answer would not be valid. Instead you can use a symbolic expression to show that it is true

**First we define the variable as a symmbolic expression**

```python
x = sym.symbols('x')
```

**Now we can use the variable in a symbolic expression**

```python
x-x
```

### Example 1.2

Sympy can be used to perform algebreic operations (among other things). Consider the following expression: $$(3a-4b)^3$$

We can use symppy to expand the expression algebraically.

**First we need to define the variables as symbolic expressions**

```python
a,b = sym.symbols('a,b')
```

**Side note** Notice that the left hand side of the epression has two variables being defined. Python can define more than one variable at a time:

```python
x1,y1 =10,20
print(x1)
print(y1)
```

    10
    20

**Back to the ion** We can define an expression using the variables $a$ and $b$.

```python
expr = (3*a-4*b)**3
print(expr)
```

    (3*a - 4*b)**3

We can also make it look nicer in our notebook. This doesn't affect the math, but it makes our notebook more readable.

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

```python
expr
```

**Now we expand the function algebreically**

```python
expr.expand()
```

Sympy can also factor the equation

```python
sym.factor(26*a**3-108*a**2*b+144*a*b**2-64*b**3)
```

If you want to copy and paste a result, you print the result.

```python
print(sym.factor(26*a**3-108*a**2*b+144*a*b**2-64*b**3))
```

    2*(a - 2*b)*(13*a**2 - 28*a*b + 16*b**2)

You can also chain together functions

```python
expr.expand().factor()
```

### Exercise 1.1

Show that the following two expressions are true.
$$(2w-3z)(2w+3z)=4w^2-9z^2$$
$$(2w-3z)^2\ne4w^2-9z^2$$

```python
w,z = sym.symbols('w,z')
```

```python
expr = (2*w-3*z)*(2*w+3*z)
```

```python
print(expr)
```

    (2*w - 3*z)*(2*w + 3*z)

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

```python
expr
```

```python
expr.expand()
```

```python
w,z = sym.symbols('w,z')
expr = (2*w-3*z)**2
print(expr)
sym.init_printing()
expr
```

```python
expr.expand()
```

## 2. Solving Equations

Sympy can be used to symbolilically solve equations. As before, you need to define which variables are symbols

### Example 2.1

Use sympy to solve the following equation
$$ax^3+bx^2+cx+d=0$$

```python
# Define the variables
a,b,c,d,x = sym.symbols('a,b,c,d,x')
```

```python
# Define the expression
expr=a*x**3+b*x**2+c*x+d
expr
```

We can use the `solvset` function to solve this equation

```python
solutions=sym.solveset(expr,x)
```

```python
print(solutions)
```

    {-(-3*c/a + b**2/a**2)/(3*(sqrt(-4*(-3*c/a + b**2/a**2)**3 + (27*d/a - 9*b*c/a**2 + 2*b**3/a**3)**2)/2 + 27*d/(2*a) - 9*b*c/(2*a**2) + b**3/a**3)**(1/3)) - (sqrt(-4*(-3*c/a + b**2/a**2)**3 + (27*d/a - 9*b*c/a**2 + 2*b**3/a**3)**2)/2 + 27*d/(2*a) - 9*b*c/(2*a**2) + b**3/a**3)**(1/3)/3 - b/(3*a), -(-3*c/a + b**2/a**2)/(3*(-1/2 - sqrt(3)*I/2)*(sqrt(-4*(-3*c/a + b**2/a**2)**3 + (27*d/a - 9*b*c/a**2 + 2*b**3/a**3)**2)/2 + 27*d/(2*a) - 9*b*c/(2*a**2) + b**3/a**3)**(1/3)) - (-1/2 - sqrt(3)*I/2)*(sqrt(-4*(-3*c/a + b**2/a**2)**3 + (27*d/a - 9*b*c/a**2 + 2*b**3/a**3)**2)/2 + 27*d/(2*a) - 9*b*c/(2*a**2) + b**3/a**3)**(1/3)/3 - b/(3*a), -(-3*c/a + b**2/a**2)/(3*(-1/2 + sqrt(3)*I/2)*(sqrt(-4*(-3*c/a + b**2/a**2)**3 + (27*d/a - 9*b*c/a**2 + 2*b**3/a**3)**2)/2 + 27*d/(2*a) - 9*b*c/(2*a**2) + b**3/a**3)**(1/3)) - (-1/2 + sqrt(3)*I/2)*(sqrt(-4*(-3*c/a + b**2/a**2)**3 + (27*d/a - 9*b*c/a**2 + 2*b**3/a**3)**2)/2 + 27*d/(2*a) - 9*b*c/(2*a**2) + b**3/a**3)**(1/3)/3 - b/(3*a)}

```python
solutions
```

What if I need help. You can do this with any python function. `function?`

```python
# Run this command to see a help box
sym.solveset?
```

### Exercise 2.1

Use the `solveset` function to solve the following chemical problem.

Phosgene gas, $\text{COCl}_2$, dissociates at high temperatures according to the following equilibrium:

$$ \text{COCl}_2 \rightleftharpoons  \text{CO} + \text{Cl}_2 $$

At $\text{400 C}$, the equilibrium constant $K_c=8.05$.

If you start with a  $\text{0.250 M}$ phosgene sample at $\text{400 C}$, determine the concentrations of all species at equilibrium.

```python
x=sym.symbols('x')
```

```python
expr=(((x**2)/(0.25-x))-8.05)
```

```python
print(expr)
sym.init_printing()
expr
```

```python
solutions=sym.solveset(expr,x)
print(solutions)
```

    {-8.29268379803378, 0.242683798033777}

Why did you pick your answer?

```python
#have to ignore negative value, concentration cannot be negative
```

## 3. Calculus

We can use also Sympy to differentiate and integrate. Let us experiment with differentiating the following expression:

$$x ^ 2 - \cos(x)$$

```python
sym.diff(x ** 2 - sym.cos(x), x)
```

Similarly we can integrate:

```python
sym.integrate(x ** 2 - sym.cos(x), x)
```

We can also carry out definite integrals:

```python
sym.integrate(x ** 2 - sym.cos(x), (x, 0, 5))
```

### Exercise 3.1

Use Sympy to calculate the following:

1. $\frac{d(x ^2 + xy - \ln(y))}{dy}$
1. $\int_0^5 e^{2x}\;dx$

```python
x,y=sym.symbols('x,y')
sym.diff((x**2+x*y-sym.ln(y)),y)
```

```python
sym.integrate(sym.E**(2*x), (x,0,5))
```

### Exercise 3.2
Solve the following definate integral
$$\int\limits_{ - \infty }^\infty  {\frac{1}{{\sigma \sqrt {2\pi } }}{e^{ - \frac{1}{2}{{\left( {\frac{{x - \mu }}{\sigma }} \right)}^2}}}}$$

Hint, the sympy symbol for infinity is `oo`

```python
oo,x,m,s=sym.symbols('oo,x,m,s')
sym.integrate((1/(s*sym.sqrt(2*sym.pi))*sym.E**(-0.5*(x-m/s)**2)), (x, -oo, oo))
```

Lookup Gaussian functions: https://en.wikipedia.org/wiki/Gaussian_function
Does your answer maake sense?

## 4. Plotting with Sympy

Finally Sympy can be used to plot functions. Note that this makes use of [matplotlib](http://matplotlib.org/).

Let us plot $x^2$:

```python
expr = x ** 2
p = sym.plot(expr)
```

### Exercise 4.1 Plot the following function:

1. $y=x + cos(x)$
1. ${\frac{1}{{ \sqrt {2\pi } }}{e^{ - \frac{x^2}{2}}}}$

```python
x,y=sym.symbols('x,y')
expr=x+sym.cos(x)
p=sym.plot(expr)
```

```python
x,y=sym.symbols('x,y')
expr=(1/sym.sqrt(2*sym.pi))*((sym.E**(-x**2/2)))
p=sym.plot(expr)
```

# Lecture

## L1. Hydrogen Atom

Sympy has built in modules for the eigenfunctions of the hydrogen atom.

```python
import sympy.physics.hydrogen
import numpy as np
```

You can caluclate the eigenvalues ($E$) in Hartrees

`sym.physics.hydrogen.E_nl(n,Z)`

```python
sym.physics.hydrogen.E_nl(1,1)
```

We can use a loop to print out many energies

```python
for n in range(1,5):
    print(sym.physics.hydrogen.E_nl(n,1))
```

    -1/2
    -1/8
    -1/18
    -1/32

We can plot the hydrogen radial wavefunction (1s orbital)

```python
r=sym.symbols('r')
sympy.symbols('r')
sympy.physics.hydrogen.R_nl(1, 0, r, 1)
```

```python
sym.plot(sympy.physics.hydrogen.R_nl(1, 0, r, 1),(r,0,10.50))
```

And the probablity distribution function

```python
sympy.symbols('r')
prob_1s=sympy.physics.hydrogen.R_nl(1, 0, r, 1)*sympy.physics.hydrogen.R_nl(1, 0, r, 1)
prob_1s
```

```python
sym.plot(prob_1s,(r,0,10))
```

Plot a 2s orbital

```python
sympy.symbols('r')
prob_2s=sympy.physics.hydrogen.R_nl(2, 0, r, 1)*sympy.physics.hydrogen.R_nl(2, 0, r, 1)
prob_2s
```

```python
sym.plot(prob_2s,(r,0,10))
```

We can change the range to see the node better.

```python
sym.plot(prob_2s,(r,1,8))
```

Notice the node!

### Exercise L1.1

Plot the radial distriubution function for a 2p, 3s, 4s, and 3d orbital.

```python
sympy.symbols('r')
prob_2p=sympy.physics.hydrogen.R_nl(2, 1, r, 1)*sympy.physics.hydrogen.R_nl(2, 1, r, 1)
prob_2p
```

```python
sym.plot(prob_2p,(r,0,10))
```

```python
sympy.symbols('r')
prob_3s=sympy.physics.hydrogen.R_nl(2, 0, r, 1)*sympy.physics.hydrogen.R_nl(2, 0, r, 1)
prob_3s
```

```python
sym.plot(prob_3s,(r,1,8))
```

```python
sympy.symbols('r')
prob_4s=sympy.physics.hydrogen.R_nl(2, 0, r, 1)*sympy.physics.hydrogen.R_nl(2, 0, r, 1)
prob_4s
```

```python
sym.plot(prob_4s,(r,1,8))
```

```python
sympy.symbols('r')
prob_3d=sympy.physics.hydrogen.R_nl(3, 2, r, 1)*sympy.physics.hydrogen.R_nl(3, 2, r, 1)
prob_3d
```

```python
sym.plot(prob_3d,(r,0,20))
```

```python

```


```python
from sympy.physics.mechanics import *
import sympy as sp
mechanics_printing(pretty_print=True)
```

```python
m1,m2,l0,k,l1,l2 = sp.symbols(r'm_1,m_2,l_0,k,l_1,l_2')
t,g = sp.symbols('t,g')
v1, v2, p1, p2 = dynamicsymbols(r'\theta_1 \theta_2 \phi_1 \phi_2')
dv1, dv2, dp1, dp2 = dynamicsymbols(r'\theta_1 \theta_2 \phi_1 \phi_2', 1)
```

```python
x1 = l1*sp.sin(p1)*sp.cos(v1)
y1 = l1*sp.sin(p1)*sp.sin(v1)
z1 = l1*sp.cos(p1)

x2 = x1 + l2*sp.sin(p2)*sp.cos(v2)
y2 = y1 + l2*sp.sin(p2)*sp.sin(v2)
z2 = z1 + l2*sp.cos(p2)

dx1 = x1.diff(t)
dy1 = y1.diff(t)
dz1 = z1.diff(t)

dx2 = x2.diff(t)
dy2 = y2.diff(t)
dz2 = z2.diff(t)
```

```python
V = m1*g*y1+m2*g*y2+sp.Rational(1,2)*k*(sp.sqrt((x2-x1)**2+(y2-y1)**2+(z2-z1)**2)-l0)**2
T = sp.Rational(1,2)*m1*(dx1**2+dy1**2+dz1**2)+sp.Rational(1,2)*m2*(dx2**2+dy2**2+dz2**2)
L = sp.simplify(T - V)
```

```python
LM = LagrangesMethod(L, [v1, v2, p1, p2])
```

```python
soln = LM.form_lagranges_equations()
```

```python
soln
```

```python
solvedsoln = sp.solve((sp.Eq(soln[0]),sp.Eq(soln[1]),sp.Eq(soln[2]),sp.Eq(soln[3])),(v1.diff(t,t),v2.diff(t,t),p1.diff(t,t),p2.diff(t,t)))
```

```python
solvedsoln
```

```python

```


# Lecture 4: Conditional Probability

## Stat 110, Prof. Joe Blitzstein, Harvard University

----

## Definitions

We continue with some basic definitions of _independence_ and _disjointness_:

#### Definition: independence & disjointness

> Events A and B are __independent__ if $P(A \cap B) = P(A)P(B)$.  Knowing that event A occurs tells us nothing about event B.
>
> In contrast, events A and B are __disjoint__ if A occurring means that B cannot occur.

What about the case of events A, B and c?

> Events A, B and C are __independent__ if
>
> \begin\{align\}
>     P(A \cap B) &= P(A)P(B), ~~  P(A \cap C) = P(A)P(C), ~~ P(B \cap C) = P(B)P(C) \\\\
>     P(A \cap B \cap C) &= P(A)P(B)P(C)
> \end\{align\}
>
> So you need both _pair-wise independence and three-way independence_.

## Newton-Pepys Problem (1693)

Yet another famous example of probability that comes from a [gambling question](https://en.wikipedia.org/wiki/Newton%E2%80%93Pepys_problem).

We have fair dice. Which of the following events is most likely?

- $A$ ... at least one 6 with 6 dice
- $B$ ... at least two 6's with 12 dice
- $C$ ... at least three 6's with 18 dice

Let's solve for the probability of each event using independence.

\begin{align}
    P(A) &= 1 - P(A^c) ~~~~ &\text{since the complement of at least one 6 is no 6's at all}      \\
         &= 1 - \left(\frac{5}{6}\right)^6 &\text{the 6 dice are independent, so we just multiply them all} \\
         &\approx 0.665 \\
         \\
    P(B) &= 1 - P(\text{no 6's}) - P(\text{one 6}) \\
         &= 1 - \left(\frac{5}{6}\right)^{12} - 12 \left(\frac{1}{6}\right)\left(\frac{5}{6}\right)^{11} &\text{... does this look familiar?}\\
         &\approx 0.619 \\
         \\
    P(C) &= 1 - P(\text{no 6's}) - P(\text{one 6}) - P(\text{two 6's})  \\
         &= 1 - \sum_{k=0}^{2} \binom{18}{k} \left(\frac{1}{6}\right)^k \left(\frac{5}{6}\right)^{18-k} &\text{... it's Binomial probability!} \\
         &\approx 0.597
\end{align}

## Conditional Probability

> Conditioning is the soul of probability.

How do you update your beliefs when presented with new information? That's the question here.

Consider 2 events $A$ and $B$. We defined _conditional probability_ a $P(A|B)$, read _the probability of A given B_.

Suppose we just observed that $B$ occurred. Now if $A$ and $B$ are independent, then $P(A|B)$ is irrelevant. But if $A$ and $B$ are not independent, then the fact that $B$ happened is important information and we need to update our uncertainty about $A$ accordingly.

#### Definition: conditional probability

> \begin\{align\}
>     \text{conditional probability } P(A|B) &= \frac{P(A \cap B)}{P(B)} &\text{if }P(B)\gt0 \\
> \end\{align\}

Prof. Blitzstein gives examples of _Pebble World_ and _Frequentist World_ to help explain conditional probability, but I find that [Legos make things simple](https://www.countbayesie.com/blog/2015/2/18/bayes-theorem-with-lego).

## Theorem 1

The intersection of events $A$ and $B$ can be given by

\begin{align}
    P(A \cap B) = P(B) P(A|B) = P(A) P(B|A)
\end{align}

Note that if $A$ and $B$ are independent, then conditioning on $B$ means nothing (and vice-versa) so $P(A|B) = P(A)$, and $P(A \cap B) = P(A) P(B)$.

## Theorem 2

\begin{align}
   P(A_1, A_2, ... A_n) = P(A_1)P(A_2|A_1)P(A_3|A_1,A_2)...P(A_n|A_1,A_2,...,A_{n-1})
\end{align}

## Theorem 3: Bayes' Theorem

\begin{align}
    P(A|B) = \frac{P(B|A)P(A)}{P(B)} ~~~~ \text{this follows from Theorem 1}
\end{align}

----

## Appendix A: Bayes' Rule Expressed in Terms of Odds

The _odds_ of an event with probability $p$ is $\frac{p}{1-p}$.

An event with probability $\frac{3}{4}$ can be described as having odds _3 to 1 in favor_, or _1 to 3 against_.

Let $H$ be the hypothesis, or the event we are interested in.

Let $D$ be the evidence (event) we gather in order to study $H$.

The _prior_ probability $P(H)$ is that for which $H$ is true __before__ we observe any new evidence $D$.

The _posterior_ probability $P(H|D)$ is, of course, that which is __after__ we observed new evidence.

The _likelihood ratio_ is defined as $\frac{P(D|H)}{P(D^c|H^c)}$

Applying Bayes' Rule, we can see how the _posterior odds_, _prior odds_ and _likelihood odds_ are related:

\begin{align}
    P(H|D) &= \frac{P(D|H)P(H)}{P(D)} \\
    \\
    P(H^c|D) &= \frac{P(D|H^c)P(H^c)}{P(D)} \\
    \\
    \Rightarrow \underbrace{\frac{P(H|D)}{P(H^c|D)}}_{\text{posterior odds of H}} &= \underbrace{\frac{P(H)}{P(H^c)}}_{\text{prior odds of H}} \times \underbrace{\frac{P(D|H)}{P(D|H^c)}}_{\text{likelihood ratio}}
\end{align}

----

## Appendix B: Translating Odds into Probability

To go from _odds_ back to _probability_

\begin{align}
    p = \frac{p/q}{1 + p/q} & &\text{ for } q = 1-p
\end{align}

----

View [Lecture 4: Conditional Probability | Statistics 110](http://bit.ly/2Mwpk11) on YouTube.


# Estimating a scalar constant using a Kalman filter

```
using Kalman
```

This is the example given in [Welch and Bishop, 2006](http://www.cs.unc.edu/~welch/kalman/kalmanIntro.html).

We want to estimate a constant voltage (set to $x=-0.37727\ \mathrm{V}$ in this example), but our measurements are corrupted by $0.1\ \mathrm{V}$ white, additive measurement noise. The basic Kalman Filter signal and measurement models are

\begin{align}
\mathbf{x}_{k+1} &= \mathbf{Ax}_k+\mathbf{Gw}_k \\
\mathbf{z}_{k+1} &= \mathbf{Hx}_k+v_k
\end{align}

since our signal doesn't change from step to step, and we are directly measuring the voltage, $\mathbf{A} = \mathbf{H} = 1$. Our system noise directly corrupts the signal, so $\mathbf{G}=1$ as well.

These are scalars, but for the Kalman.jl interface, we need these to be two dimensional matrices (thus the transposes below).

```
a = [1]'
h = [1]'
g = [1]'
```

    1x1 Array{Int64,2}:
     1

$\mathbf{w}_k$ is normally-distributed with a scalar variance $\mathbf{Q} = Q$ while $\mathbf{v}_k$ is normally-distributed with a scalar variance $\mathbf{R}=R$. We set $R=0.1^2\ \mathrm{V} = 0.01\ \mathrm{V}$. It helps if our system noise is non-zero (which is realistic in any case), so we'll set $Q=10^{-10}\ \mathrm{V}$

```
r = [0.01]'
q = [1e-10]'
```

    1x1 Array{Float64,2}:
     1.0e-10

All we need now is our initial conditions. We'll estimate our initial voltage as $\hat{x}_0 = 0.0\ \mathrm{V}$ and our initial covariance matrix as $\hat{P}_0 = 1$. For Kalman.jl, the state is contained within a `State` type which holds a vector for the system state and a matrix for the state covariance matrix.

```
x0 = State([0.0],[1.0]')
```

    State{Float64}([0.0],1x1 Array{Float64,2}:
     1.0)

We now set up our Kalman filter. We'll use the `BasicKalmanFilter` type which holds a `State`, a `LinearModel` representing the signal model, and a `LinearObservationModel` representing the observation model.

```
f = LinearModel(a,g,q)
z = LinearObservationModel(h,r)
kf0 = BasicKalmanFilter(x0,f,z)
kf = kf0
```

    BasicKalmanFilter(State{Float64}([0.0],1x1 Array{Float64,2}:
     1.0),LinearModel(1x1 Array{Int64,2}:
     1,1x1 Array{Int64,2}:
     1,1x1 Array{Float64,2}:
     1.0e-10),LinearObservationModel(1x1 Array{Int64,2}:
     1,1x1 Array{Float64,2}:
     0.01))

We'll generate a random set of 50 measurements of our voltage. Measurements are fed to the Kalman filter as an `Observation` type.

```
x_true = -0.37727 # We need to know our true voltage to generate the measurements
y = map(i->Observation([i]),x_true+sqrt(r[1])*randn(50))
```

    50-element Array{Observation{Float64},1}:
     Observation{Float64}([-0.211002])
     Observation{Float64}([-0.394751])
     Observation{Float64}([-0.377961])
     Observation{Float64}([-0.406222])
     Observation{Float64}([-0.44631])
     Observation{Float64}([-0.49396])
     Observation{Float64}([-0.390077])
     Observation{Float64}([-0.33505])
     Observation{Float64}([-0.321221])
     Observation{Float64}([-0.476213])
     Observation{Float64}([-0.339561])
     Observation{Float64}([-0.394962])
     Observation{Float64}([-0.358227])
     ⋮
     Observation{Float64}([-0.32531])
     Observation{Float64}([-0.390645])
     Observation{Float64}([-0.325979])
     Observation{Float64}([-0.334369])
     Observation{Float64}([-0.396387])
     Observation{Float64}([-0.298485])
     Observation{Float64}([-0.495664])
     Observation{Float64}([-0.368824])
     Observation{Float64}([-0.453343])
     Observation{Float64}([-0.30535])
     Observation{Float64}([-0.187863])
     Observation{Float64}([-0.374981])

Now we run the Kalman filter by iterating over a "predict" step and an "update" step.

```
x = zeros(Float64,50) # Store our state estimates for analysis later
p = ones(Float64,50) # Store our state covariance for analysis later

for i = 1:49
    kf2 = predict(kf)
    kf = update(kf2,y[i])
    x[i+1] = kf.x.x[1]
    p[i+1] = kf.x.p[1]
end
```

Let's plot up some results.

Here is our state estimate over the 50 measurements which we made.

```
using Winston
plot(x_true*ones(50),"k")
oplot(map(i->i.y[1],y),"r.")
oplot(x,"b")
xlim(1,50)
xlabel("Time step")
ylabel("Voltage estimate (V)")
```

That converges to $x$ very nicely!

Now let's see how the state covariance changes as we add more measurements.

```
plot(p,"g")
xlim(2,50)
xlabel("Time step")
ylabel("Variance (V^2)")
```

The variance of our estimate has decreased from our guess of $1\ \mathrm{V^2}$ to $0.0002\ \mathrm{V^2}$