## Maximización de agua en un canal
###### Nombres
- Ricardo Shizuo Téllez Urakami
- Dulce Carolina Martínez Moeales

**  Simulacion matemática **
######   Email:    user@algo
___

___
I. Introducción

>
I will be justified. Proporcionar una breve descripción de los problemas a mostrar en este reporte. Blabla bla bla bla...

II.   Planteamiento, problema

III.   Resultados

```python
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
from sympy import *
```

```python
#def A(h,b,x):
    #h = 10sin(x)
    #return 10h + 2((1/2)*bh)
```

```python
var("x")
```

    x

```python
A = (100*sin(x)) + (100*sin(x)*cos(x))
```

```python
Derivative(A,x).doit()
```

    -100*sin(x)**2 + 100*cos(x)**2 + 100*cos(x)

```python
solve(Derivative(A,x).doit(),x)
```

    [-pi/3, pi/3]

```python
# x = pi/3
```

```python
# b = 10cos(x)
```

```python
b = 10*cos(pi/3)
b
```

    5

```python
h = 10*sin(pi/3)
h
```

    5*sqrt(3)

```python
# Sustituyendo en A
```

```python
A = (100*sin(pi/3)) + (100*sin(pi/3)*cos(pi/3))
A
```

    75*sqrt(3)

```python
x2 = -0.57735x+8.6602
x3 = 0.57735x
b2 = 0
```

```python
x2 = np.arange(0,5)
```

```python
b2 = np.arange(5,15)
```

```python
x3 = np.arange(15,20)
```

```python
fig = plt.figure(figsize = (100,100))
ax = fig.add_subplot(10, 10, 10)
x2 = (-0.57735*(pi/3))+8.6602
x3 = 0.57735*(pi/3)
b2 = 0
ax.plot(x2, 'ko')
ax.plot(x3, 'ko')
ax.plot(b2, 'ko')
#ax.plot([0], [0], 'rD')
#ax.set_xlim(xmin = -2.2, xmax = 2.2)
#ax.set_ylim(ymin = -2.2, ymax = .2)
plt.show()
```

IV.   Conclusiones

```python

```

... Por favor en este espacio expresar de manera muy general sus conclusiones...

V.   Referencias

```python

```

```python
import seaborn as sns
sns.set(style='ticks', palette='Set2')
from IPython.core.display import HTML
def css_styling():
    styles = open("numericalmoocstyle.css", "r").read()
    return HTML(styles)
css_styling()
```

<link href='http://fonts.googleapis.com/css?family=Alegreya+Sans:100,300,400,500,700,800,900,100italic,300italic,400italic,500italic,700italic,800italic,900italic' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Arvo:400,700,400italic' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=PT+Mono' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Shadows+Into+Light' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Nixie+One' rel='stylesheet' type='text/css'>
<link href='https://fonts.googleapis.com/css?family=Source+Code+Pro' rel='stylesheet' type='text/css'>


```python
import numpy as np
import scipy.stats as stats
import pymc3 as pm
import arviz as az
```

```python
az.style.use('arviz-white')
```

# Probabilistic Programming

This post is based on an excerpt from the second chapter of the book that I have slightly adapted so it's easier to read without having read the first chapter.

## Bayesian Inference

Bayesian statistics is conceptually very simple; we have _the knowns_ and _the unknowns_; we use Bayes' theorem to condition the latter on the former. If we are lucky, this process will reduce the uncertainty about _the unknowns_.

Generally, we refer to _the knowns_ as **data** and treat it like a constant, and __the unknowns__ as **parameters** and treat them as probability distributions. In more formal terms, we assign probability distributions to unknown quantities. Then, we use Bayes' theorem to combine the prior probability distribution and the data to get the posterior distribution.

$$p(\theta \mid y) = \frac{p(y \mid \theta) p(\theta)}{p(y)}$$

Here $\theta$ represents the parameters in our model, generally this are the quantities we want to learn, $y$ represents the data.

Each term in the Bayes Theorem has it's own name:

* $p(\theta \mid y)$: posterior
* $p(y \mid \theta)$: likelihood
* $p(\theta)$: prior
* $p(y)$: marginal likelihood

The prior distribution should reflect what we know about the value of the parameter before seeing the data $y$. If we know nothing, like Jon Snow, we could use _flat_ priors that do not convey too much information. In general, we can do better than flat priors.

The likelihood is how we introduce data in our analysis. It is an expression of the plausibility of the data given the parameters.

The posterior distribution is the result of the Bayesian analysis and reflects all that we know about a problem (given our data and model). The posterior is a probability distribution for the parameters in our model and not a single value.  Conceptually, we can think of the posterior as the updated prior in the light of (new) data. In fact, the posterior from one analysis can be used as the prior for a new analysis.

The last term is the marginal likelihood, also known as evidence. For the moment we will think of it as a simple normalization factor.

## Probabilistic Programming: Inference-Button

Although conceptually simple, fully probabilistic models often lead to analytically intractable expressions. For many years, this was a real problem and was probably one of the main issues that hindered the wide adoption of Bayesian methods. The arrival of the computational era and the development of numerical methods that, at least in principle, can be used to solve any inference problem, has dramatically transformed the Bayesian data analysis practice. We can think of these numerical methods as universal inference engines, or as Thomas Wiecki, a core developer of PyMC3, likes to call it, the inference-button. The possibility of automating the inference process has led to the development of **probabilistic programming languages (PPL)**, which allow for a clear separation between model creation and inference.

In the PPL framework, users specify a full probabilistic model by writing a few lines of code, and then inference follows automatically. It is expected that probabilistic programming will have a major impact on data science and other disciplines by enabling practitioners to build complex probabilistic models in a less time-consuming and less error-prone way.

I think one good analogy for the impact that programming languages can have on scientific computing is the introduction of the Fortran programming language more than six decades ago. While Fortran has lost its shine nowadays, at one time, it was considered to be very revolutionary. For the first time, scientists moved away from computational details and began focusing on building numerical methods, models, and simulations in a more natural way. In a similar fashion, we now have PPL, which hides details on how probabilities are manipulated and how the inference is performed from users, allowing users to focus on model specification and the analysis of the results.

In this post, you will learn how to use PyMC3 to define and solve a simple model. We will treat the inference-button as a black box that gives us proper **samples** from the posterior distribution. The methods we will be using are stochastic, and so the samples will vary every time we run them. However, if the inference process works as expected, the samples will be representative of the posterior distribution and thus we will obtain the same conclusion from any of those samples. The details of what happens under the hood when we push the inference-button and how to check if the samples are indeed trustworthy is explained in Chapter 8, _Inference Engines_.

## PyMC3 primer

PyMC3 is a Python library for probabilistic programming. The latest version at the moment of writing is 3.6. PyMC3 provides a very simple and intuitive syntax that is easy to read and close to the syntax used in statistical literature to describe probabilistic models. PyMC3's base code is written using Python, and the computationally demanding parts are written using NumPy and Theano.

**Theano** is a Python library that was originally developed for deep learning and allows us to define, optimize, and evaluate mathematical expressions involving multidimensional arrays efficiently. The main reason PyMC3 uses Theano is because some of the sampling methods, such as NUTS, need gradients to be computed, and Theano knows how to compute gradients using what is known as automatic differentiation. Also, Theano compiles Python code to C code, and hence PyMC3 is really fast. This is all the information about Theano we need to have to use PyMC3. If you still want to learn more about it, start reading the official Theano tutorial at http://deeplearning.net/software/theano/tutorial/index.html#tutorial.

> Note: You may have heard that Theano is no longer developed, but that's no reason to worry. PyMC devs will take over Theano maintenance, ensuring that Theano will keep serving PyMC3 for several years to come. At the same time, PyMC devs are moving quickly to create the successor to PyMC3. This will probably be based on TensorFlow as a backend, although other options are being analyzed as well. You can read more about this at this [blog post](https://medium.com/@pymc_devs/theano-tensorflow-and-the-future-of-pymc-6c9987bb19d5).

### The coin-flipping problem

The coin-flipping problem, or the beta-binomial model if you want to sound fancy at parties, is a classical problem in statistics and goes like this: we toss a coin a number of times and record how many heads and tails we get. Based on this data, we try to answer questions such as, is the coin fair? Or, more generally, how biased is the coin?

While this problem may sound dull, we should not underestimate it. The coin-flipping problem is a great example to learn the basics of Bayesian statistics because it is a simple model that we can solve and compute with ease. Besides, many real problems consist of binary, mutually-exclusive outcomes such as 0 or 1, positive or negative, odds or evens, spam or ham, hotdog or not hotdog, cat or dog, safe or unsafe, and healthy or unhealthy. Thus, even when we are talking about coins, this model applies to any of those problems.

In order to estimate the bias of a coin, and in general to answer any questions in a Bayesian
setting, we will need data and a probabilistic model. We are going to generate the data using Python, but you can also generate the data yourself using a real coin!

```python
np.random.seed(123)
trials = 4
theta_real = 0.35 # unknown value in a real experiment
data = stats.bernoulli.rvs(p=theta_real, size=trials)
```

### Model specification

Now that we have the data, we need to specify the model. This is done by specifying the likelihood and the prior using probability distributions. For the likelihood, we will use the binomial distribution with parameters $n=1$, and $p=\theta$ and for the prior, a beta distribution with parameters $\alpha=\beta=1$:

We can write the model using the following mathematical notation:

\begin{align}
\theta &\sim \mathop{Beta}(\alpha, \beta) \\
y &\sim \mathop{Bern}(n=1, p=\theta)
\end{align}

A justification of this model is discussed in chapter 1. But briefly we can justify it as follows. Coins can take only two values heads and tails, thus we can use a Bernoulli distribution with n=1, as this distributions models the distribution of two mutually exclusive outcomes like heads (1) or tails (0). We use a beta distribution with parameters $\alpha=\beta=1$ as this is equivalent to a uniform distribution in the interval [0, 1]. That is we are totally ignorant of the value $\theta$ can take, besides being some number between 0 and 1. If instead you have reasons to think the value should be around 0.5, you could use a beta distribution with parameters $\alpha=\beta=2$ or $\alpha=\beta=20$, I suggest once you have read this post you try using those priors and see how the inference change.

This statistical model has an almost one-to-one translation to PyMC3:

```python
with pm.Model() as our_first_model:
    θ = pm.Beta('θ', alpha=1., beta=1.)
    y = pm.Bernoulli('y', p=θ, observed=data)
    trace = pm.sample(1000, random_seed=123)
```

    Auto-assigning NUTS sampler...
    Initializing NUTS using jitter+adapt_diag...
    Multiprocess sampling (2 chains in 2 jobs)
    NUTS: [θ]
    Sampling 2 chains: 100%|██████████| 3000/3000 [00:00<00:00, 3017.95draws/s]

* The first line of the code creates a container for our model. Everything inside the `with-block` will be automatically added to `our_first_model`. You can think of this as syntactic sugar to ease model specification as we do not need to manually assign variables to the model.

* The second line specifies the prior. As you can see, the syntax follows the mathematical notation closely.

> Please note that we’ve used the name `θ` twice, first as a Python variable and then as the first argument of the Beta function; using the same name is a good practice to avoid confusion. The `θ` variable is a random variable; it is not a number, but an object representing a probability distribution from which we can compute random numbers and probability densities.

* The third line specifies the likelihood. The syntax is almost the same as for the prior, except that we pass the data using the `observed` argument. This is the way we tell PyMC3 we want to condition the unknowns ($\theta$) on the known (`data`). The observed values can be passed as a Python list, a tuple, a NumPy array, or a pandas DataFrame.

Now, we are finished with the model's specification! Pretty neat, right?

### Pushing the inference button

The last line is the inference button. We are asking for 1,000 samples from the posterior and will store them in the `trace` object. Behind this innocent line, PyMC3 has hundreds of _oompa loompas_, singing and baking a delicious Bayesian inference just for you! Well, not exactly, but PyMC3 is automating a lot of tasks. If you run the code, you will get a message like this:

    Auto-assigning NUTS sampler...
    Initializing NUTS using jitter+adapt_diag...
    Multiprocess sampling (2 chains in 2 jobs)
    NUTS: [θ]
    Sampling 2 chains: 100%|██████████| 3000/3000 [00:00<00:00, 3017.95draws/s]

The first and second lines tell us that PyMC3 has automatically assigned the NUTS sampler (one inference engine that works very well for continuous variables), and has used a method to initialize that sampler.

The third line says that PyMC3 will run two chains in parallel, so we can get two independent samples from the posterior for the price of one. The exact number of chains is computed taking into account the number of processors in your machine; you can change it using the `chains` argument for the `sample` function.

The next line is telling us which variables are being sampled by which sampler. For this particular case, this line is not adding any new information because NUTS is used to sample the only variable we have `θ`. However, this is not always the case as PyMC3 can assign different samplers to different variables. This is done automatically by PyMC3 based on the properties of the variables, which ensures that the best possible sampler is used for each variable. Users can manually assign samplers using the step argument of the sample function.

Finally, the last line is a progress bar, with several related metrics indicating how fast the sampler is working, including the number of iterations per second. If you run the code, you will see the progress-bar get updated really fast. Here, we are seeing the last stage when the sampler has finished its work. The numbers are 3000/3000, where the first number is the running sampler number (this starts at 1), and the last is the total number of samples. You will notice that we have asked for 1,000 samples, but PyMC3 is computing 3,000 samples. We have 500 samples per chain to auto-tune the sampling algorithm (NUTS, in this example).
These samples will be discarded by default. We also have 1,000 productive draws per-chain, thus a total of 3,000 samples are generated. The tuning phase helps PyMC3 provide a reliable sample from the posterior. We can change the number of tuning steps with the `tune` argument of the sample function.

### Summarizing the posterior

Generally, the first task we will perform after sampling from the posterior is check what the results look like. The `plot_trace` function is one of many ArviZ functions we can use for this task:

```python
az.plot_trace(trace);
```

By using `az.plot_trace`, we get two subplots for each unobserved variable. The only unobserved variable in `our_first_model` is $\theta$. Notice that $y$ is an observed variable representing the data; we do not need to sample that because we already know those values.

In the above figure, we have two subplots. On the left, we have a Kernel Density Estimation (KDE) plot; this is like the smooth version of an histogram. On the right, we get the individual sampled values at each step during the sampling. From the trace plot, we can visually get the plausible values of the parameters according to the posterior distribution.

Another plot we can make with ArviZ is a posterior plot.

```python
az.plot_posterior(trace, round_to=2);
```

We can interpret this result as follows, on average the value of $\theta$ is 0.33, this means the coin is most likely biased towards tails, as we used 0 to represent tails and 1 to represents heads. This estimation is pretty close to the real value of $\theta=0.35$, the value we used to generate the synthetic data. We can see that PyMC3 and our model has provided a reasonable answer. Of course for real examples we do not know the _true_ value of the parameters, that's the whole point of doing inferences in the first place.

As we can see from this example we did not got a single number for $\theta$ we got a distribution of plausible values. This distribution represents the uncertainty in our estimate. There are many ways to express the uncertainty of a Bayesian Analysis one is to use a **Highest Posterior Density** (HPD) Interval, as in the previous plot. For this particular example the hpd interval says that the 94% of the plausible values are contained within the 0.02-0.64 range.

Getting the posterior is not the end on a Bayesian Analysis. As with other forms of modeling a Bayesian analysis is an iterative process and is motivated by a particular context and set of questions. But I hope this simple example has make you learn more about Bayesian Analysis


# Intergal using Line Equation from Stock Histocial Data

```python
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
from sympy import *

import warnings
warnings.filterwarnings("ignore")

# yfinance is used to fetch data
import yfinance as yf
yf.pdr_override()
```

```python
# input
symbol = 'AMD'
start = '2017-01-01'
end = '2019-01-01'

# Read data
dataset = yf.download(symbol,start,end)['Adj Close']

# View Columns
dataset.head()
```

    [*********************100%***********************]  1 of 1 completed

    Date
    2017-01-03    11.43
    2017-01-04    11.43
    2017-01-05    11.24
    2017-01-06    11.32
    2017-01-09    11.49
    Name: Adj Close, dtype: float64

```python
df = dataset.reset_index()
```

```python
df.head()
```

      Date
      Adj Close

      0
      2017-01-03
      11.43

      1
      2017-01-04
      11.43

      2
      2017-01-05
      11.24

      3
      2017-01-06
      11.32

      4
      2017-01-09
      11.49

```python
df.tail()
```

      Date
      Adj Close

      497
      2018-12-24
      16.650000

      498
      2018-12-26
      17.900000

      499
      2018-12-27
      17.490000

      500
      2018-12-28
      17.820000

      501
      2018-12-31
      18.459999

```python
max_p = df['Adj Close'].max()
min_p = df['Adj Close'].min()
avg_p = df['Adj Close'].mean()
```

```python
data = df.drop(['Date'], axis=1)
data
```

      Adj Close

      0
      11.430000

      1
      11.430000

      2
      11.240000

      3
      11.320000

      4
      11.490000

      ...
      ...

      497
      16.650000

      498
      17.900000

      499
      17.490000

      500
      17.820000

      501
      18.459999

502 rows × 1 columns

```python
data = data.reset_index()
```

```python
data.values
```

    array([[  0.        ,  11.43000031],
           [  1.        ,  11.43000031],
           [  2.        ,  11.23999977],
           ...,
           [499.        ,  17.48999977],
           [500.        ,  17.81999969],
           [501.        ,  18.45999908]])

```python
from numpy import ones,vstack
from numpy.linalg import lstsq
```

```python
points = data.values
```

```python
x_coords, y_coords = zip(*points)
A = vstack([x_coords,ones(len(x_coords))]).T
m, c = lstsq(A, y_coords)[0]

```

```python
print("Line Equation is y = {m}x + {c}".format(m=m,c=c))

```

    Line Equation is y = 0.021718614923358828x + 9.372574584656501

```python
equation_of_line = print("y = {m}x + {c}".format(m=m,c=c))

```

    y = 0.021718614923358828x + 9.372574584656501

```python
equation = print("{m}*x + {c}".format(m=m,c=c))
```

    0.021718614923358828*x + 9.372574584656501

```python
x = Symbol('x')
```

```python
integrate(0.021718614923358828*x+9.372574584656501, x)
```

$\displaystyle 0.0108593074616794 x^{2} + 9.3725745846565 x$

```python
integrate(0.0108593074616794*x**2 + 9.3725745846565 * x, x)
```

$\displaystyle 0.00361976915389313 x^{3} + 4.68628729232825 x^{2}$

# Univariate roots and fixed points

```python
def f(x):
    return 0.00361976915389313*x**3 + 4.68628729232825 * x**2
```

```python
x = df['Adj Close']
```

```python
plt.axhline((f(x)).mean(), c='red')
plt.plot(x, f(x))
```


```python
%matplotlib inline

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

import torch
from torch.autograd import Variable

from tqdm import tqdm, trange

import copy

from sklearn.metrics import r2_score
```

```python
sns.set_context("paper")
sns.set_style("ticks")

np.random.seed(101217209)
torch.manual_seed(101217209)
```

    <torch._C.Generator at 0x7f9d553305e8>

```python
def get_data(N, min_x, max_x):
    w, b = np.random.randn(2)
    def true_model(X):
        lin_factor = w * X + b
        y = np.sin(lin_factor*10)*(lin_factor**2)
        return y
    X_true = np.arange(min_x, max_x, 0.01)
    y_true = true_model(X_true)
    span = (max_x - min_x)
    scale = 0.2
    X_obs = min_x + span*scale + np.random.rand(N)*(span - 2*scale*span)
    y_obs = true_model(X_obs) + np.random.randn(N)*0.1

    y_mean, y_std = y_obs.mean(), y_obs.std()
    y_obs = (y_obs - y_mean)/y_std
    y_true = (y_true - y_mean)/y_std
    return (X_obs, y_obs, X_true, y_true), (w, b, true_model)
```

```python
N = 40
min_x, max_x = -1, 1
(X_obs, y_obs, X_true, y_true), (w, b, true_model) = get_data(N, min_x, max_x)
```

```python
class SimpleModel(torch.nn.Module):
    def __init__(self, p=0.05, decay=0.001, non_linearity=torch.nn.ReLU):
        super(SimpleModel, self).__init__()
        self.dropout_p = p
        self.decay = decay
        self.f = torch.nn.Sequential(
            torch.nn.Linear(1,20),
            torch.nn.ReLU(),
            torch.nn.Dropout(p=self.dropout_p),
            torch.nn.Linear(20, 20),
            non_linearity(),
            torch.nn.Dropout(p=self.dropout_p),
            torch.nn.Linear(20,1)
        )
    def forward(self, X):
        X = Variable(torch.Tensor(X), requires_grad=False)
        return self.f(X)

def ewc_criterion(model, fisher_matrix, prior_params, alpha=1):
    ewc_loss = Variable(torch.zeros(1))
    for p, fm, prior in zip(model.parameters(), fisher_matrix, prior_params):
        param_loss = torch.pow(p - Variable(prior, requires_grad=False), 2)
        fisher_scaled_loss = alpha*Variable(fm, requires_grad=False).mul(param_loss)
        ewc_loss += fisher_scaled_loss.sum()
    return ewc_loss

def get_criterion_optimizer(model):
    criterion = torch.nn.MSELoss()
    optimizer = torch.optim.Adam(
        model.parameters(),
        #lr=0.001,
        #momentum=0.5,
        weight_decay=model.decay)
    return criterion, optimizer

def plot_predictions(model, model_simple, ax=None, uncertainty=0, N=0, l2=0.005):
    if ax is None:
        fig, ax = plt.subplots(1,1)
    ax.plot(X_obs[:10], y_obs[:10], ls="none", marker="o", color="b", label="task 1")
    ax.plot(X_obs[10:20], y_obs[10:20], ls="none", marker="d", color="cyan", label="task 2")
    ax.plot(X_obs[20:], y_obs[20:], ls="none", marker="s", color="magenta", label="task 3")
    ax.plot(X_true, y_true, ls="-", lw=1, color="0.5", label="true")

    for model_t, model_name, color in [
        (model, "ewc", "r"),
        (model_simple, "simple", "g")
    ]:
        if uncertainty > 0:
            model_t.train()
            y_preds = []
            for i in trange(uncertainty):
                y_pred = model_t(X_true[:, np.newaxis]).data.numpy()
                y_preds.append(y_pred)
            y_preds = np.hstack(y_preds)
            y_pred = y_preds.mean(axis=1)
            y_var = y_preds.var(axis=1)
            tau = l2 * (model_t.dropout_p) / (2*N*model_t.decay)
            y_var += (1/tau)
            y_std = np.sqrt(y_var) #+ (1/tau)
            for i in range(4):
                ax.fill_between(
                    X_true,
                    y_pred - y_std * ((i+1)/2),
                    y_pred + y_std * ((i+1)/2),
                    color=color,
                    alpha=0.1**(i+1)
                )
        else:
            model_t.eval()
            y_pred = model_t(X_true[:, np.newaxis]).data.numpy()
        r_squared = r2_score(y_true, y_pred)
        ax.plot(X_true, y_pred, ls="--", lw=1, color=color,
                label="{} ({:.3f})".format(model_name, r_squared))
    ax.legend()
    ax.set_xlabel("X")
    ax.set_ylabel("Y")
    sns.despine(offset=10)
    return ax

def plot_losses(losses, losses_simple, ax=None):
    if ax is None:
        fig, ax = plt.subplots(1,1,figsize=(10,4))
    ax.plot(losses, lw=0.1, color='r', label='ewc')
    ax.plot(losses_simple, lw=0.1, color='0.5', label='simple')
    ax.legend()
    ax.set_xlabel("Epochs")
    ax.set_ylabel("Loss")
    sns.despine(offset=10)
    return ax

def get_fisher_matrix(model, criterion, X, Y, old_fisher_matrix=None):
    fisher_matrix = [torch.zeros(p.data.shape) for p in model.parameters()]
    counts = 0
    #print(X.shape, Y.shape)
    for x,y in tqdm(zip(X[:, np.newaxis, np.newaxis], Y[:, np.newaxis])):
        criterion.zero_grad()
        #print(x.shape, y.shape)
        y_pred = model(x)
        #print(y_pred.data.shape)
        loss = criterion(y_pred, Variable(torch.Tensor(y), requires_grad=False))
        loss.backward()
        for i, p in enumerate(model.parameters()):
            grad_squared = torch.pow(p.grad.data, 2)
            #print(grad_squared.shape, fisher_matrix[i].shape)
            fisher_matrix[i] += grad_squared
        counts += 1
    #print(grad_squared.shape, fisher_matrix[i].shape)
    ## Calculate the means
    fisher_matrix = [k/counts for k in fisher_matrix]
    if old_fisher_matrix:
        fisher_matrix = [k + o for k,o in zip(fisher_matrix, old_fisher_matrix)]
    return fisher_matrix

def train_step(
    model, X, Y, criterion, optimizer,
    fisher_matrix=None, model_params=None,
    ecw_alpha=0, clip=10):
    y_pred = model(X[:, np.newaxis])
    loss = criterion(y_pred, Variable(torch.Tensor(Y), requires_grad=False))
    train_loss = loss.data.numpy()[0]
    if ecw_alpha > 0:
        ewc_loss = ewc_criterion(model, fisher_matrix, model_params, alpha=ecw_alpha)
        loss += ewc_loss
    loss.backward()
    torch.nn.utils.clip_grad_norm(model.parameters(),clip)
    optimizer.step()
    return train_loss
```

```python
def train_plot_compare(X_obs, y_obs, prefix="IID", clip=10, uncertainty=0, l2=0.01, **kwargs):
    model = SimpleModel(**kwargs)
    model_simple = copy.deepcopy(model)

    fig, ax = plt.subplots(1,3, sharex=True, sharey=True, figsize=(18, 6))
    fig_loss, ax_loss = plt.subplots(3,1, sharex=True, sharey=False, figsize=(10, 12))

    ## Task 1

    fisher_matrix = None
    model_params = None
    N = 0
    for i, X, Y, alpha in zip(
        range(3),
        [X_obs[:10], X_obs[10:20], X_obs[20:]],
        [y_obs[:10], y_obs[10:20], y_obs[20:]],
        [0, 1e6, 1e6] # Alpha should increase with tasks
    ):
        title = "Task {}".format(" + ".join(str(k+1) for k in range(i+1)))
        model.train()
        criterion, optimizer = get_criterion_optimizer(model)

        model_simple.train()
        criterion_simple, optimizer_simple = get_criterion_optimizer(model_simple)

        losses = []
        losses_simple = []
        for epoch in trange(10000):
            loss = train_step(
                model, X, Y,
                criterion, optimizer,
                fisher_matrix=fisher_matrix,
                model_params=model_params,
                ecw_alpha=alpha, clip=clip
            )
            losses.append(loss)

            loss = train_step(
                model_simple, X, Y, criterion_simple, optimizer_simple, clip=clip)
            losses_simple.append(loss)
        plot_losses(losses, losses_simple, ax=ax_loss[i])
        N = X.shape[0]
        plot_predictions(model, model_simple, ax=ax[i], uncertainty=uncertainty, N=N, l2=l2)
        ax_loss[i].set_title(title)
        ax[i].set_title(title)
        print(title)

        model.eval()
        fisher_matrix = get_fisher_matrix(model, criterion, X, Y, old_fisher_matrix=fisher_matrix)
        model_params = [copy.deepcopy(p.data) for p in model.parameters()]

    fig.tight_layout()
    fig_loss.tight_layout()

    sns.despine(offset=10, fig=fig)
    sns.despine(offset=10, fig=fig_loss)

    fig.savefig("{}_EWC_predictions.pdf".format(prefix), bbox_inches="tight")
    fig.savefig("{}_EWC_losses.pdf".format(prefix), bbox_inches="tight")

    return model, model_simple
```

## IID samples

```python
plt.plot(X_obs[:10], y_obs[:10], ls="none", marker="o", color="b", label="task 1")
plt.plot(X_obs[10:20], y_obs[10:20], ls="none", marker="d", color="cyan", label="task 2")
plt.plot(X_obs[20:], y_obs[20:], ls="none", marker="s", color="magenta", label="task 3")
plt.plot(X_true, y_true, ls="-", lw=1, color="0.5", label="true")
plt.legend()
sns.despine(offset=10)
```

```python
model, model_simple = train_plot_compare(
    X_obs, y_obs, prefix="IID",
    p=0.1, decay=1e-6, non_linearity=torch.nn.ReLU)
```

```python
"""
model.eval()
fisher_matrix = get_fisher_matrix(model, criterion, X_obs[:10], y_obs[:10])
model_params = [copy.deepcopy(p.data) for p in model.parameters()]

fig = plt.figure(0)
ax = fig.add_subplot(111)

el = plt.matplotlib.patches.Ellipse(
    xy=[
        model_params[0].squeeze().numpy()[0],
        model_params[1].numpy()[0]
    ],
    width=2./fisher_matrix[0].squeeze().numpy()[0],
    height=2./fisher_matrix[1].numpy()[0])
ax.add_artist(el)
#el.set_clip_box(ax.bbox)
el.set_color('0.5')
el.set_alpha(0.1)
ax.scatter(model_params[0].squeeze().numpy(), model_params[1].numpy(), s=1)
"""
```

    "\nmodel.eval()\nfisher_matrix = get_fisher_matrix(model, criterion, X_obs[:10], y_obs[:10])\nmodel_params = [copy.deepcopy(p.data) for p in model.parameters()]\n\nfig = plt.figure(0)\nax = fig.add_subplot(111)\n\n\nel = plt.matplotlib.patches.Ellipse(\n    xy=[\n        model_params[0].squeeze().numpy()[0],\n        model_params[1].numpy()[0]\n    ],\n    width=2./fisher_matrix[0].squeeze().numpy()[0],\n    height=2./fisher_matrix[1].numpy()[0])\nax.add_artist(el)\n#el.set_clip_box(ax.bbox)\nel.set_color('0.5')\nel.set_alpha(0.1)\nax.scatter(model_params[0].squeeze().numpy(), model_params[1].numpy(), s=1)\n"

## Sequential samples

```python
sorted_idx = np.argsort(X_obs)
X_obs = X_obs[sorted_idx]
y_obs = y_obs[sorted_idx]
```

```python
plt.plot(X_obs[:10], y_obs[:10], ls="none", marker="o", color="b", label="task 1")
plt.plot(X_obs[10:20], y_obs[10:20], ls="none", marker="d", color="cyan", label="task 2")
plt.plot(X_obs[20:], y_obs[20:], ls="none", marker="s", color="magenta", label="task 3")
plt.plot(X_true, y_true, ls="-", lw=1, color="0.5", label="true")
plt.legend()
sns.despine(offset=10)
```

```python
model, model_simple = train_plot_compare(
    X_obs, y_obs, prefix="Seq",
    p=0.1, decay=1e-6, non_linearity=torch.nn.ReLU)
```

## Improvements

Before each forward pass

$$
\begin{equation}
\sigma_{\theta} = F_{\theta}^{-1/2}\\
\epsilon \sim \mathcal{N}(0, 1)\\
\theta = \theta + \sigma*\epsilon
\end{equation}
$$

```python
model, model_simple = train_plot_compare(
    X_obs, y_obs, prefix="Seq", uncertainty=100, l2=0.01,
    p=0.1, decay=1e-6, non_linearity=torch.nn.ReLU
)
```

```python

```


## Data Science Basics in Python

### Bootstrap for Uncertainty Models

#### Michael Pyrcz, Associate Professor, University of Texas at Austin

##### [Twitter](https://twitter.com/geostatsguy) | [GitHub](https://github.com/GeostatsGuy) | [Website](http://michaelpyrcz.com) | [GoogleScholar](https://scholar.google.com/citations?user=QVZ20eQAAAAJ&hl=en&oi=ao) | [Book](https://www.amazon.com/Geostatistical-Reservoir-Modeling-Michael-Pyrcz/dp/0199731446) | [YouTube](https://www.youtube.com/channel/UCLqEr-xV-ceHdXXXrTId5ig)  | [LinkedIn](https://www.linkedin.com/in/michael-pyrcz-61a648a1)

### Bootstrap in Python

Here's a simple workflow, demonstration of bootstrap for modeling workflows. This should help you get started with this important data analytics method to evaluate and integrate uncertainty for any statistic or model.

#### Bootstrap

Uncertainty in statistics
* one source of uncertainty is the paucity of data.
* do 200 or even less samples provide a precise (and accurate estimate) of the mean? standard deviation? skew? P13?

Would it be useful to know the uncertainty in these statistics due to limited sampling?
* what is the impact of uncertainty in the mean porosity e.g. 20%+/-2%?
* empirically calculate standard errors, confidence intervals and to conduct hypothesis testing

**Bootstrap** is a method to assess the uncertainty in a statistic by repeated random sampling with replacement to create multiple simulated data sets.

#### The Bootstrap Workflow (Efron, 1982)

Statistical resampling procedure to calculate uncertainty in a calculated statistic from the data itself.
* Does this work?  Prove it to yourself, for uncertainty in the mean solution is standard error:

\begin{equation}
\sigma^2_\overline{x} = \frac{\sigma^2_s}{n}
\end{equation}

Extremely powerful - could calculate uncertainty in any statistic!  e.g. P13, skew etc.
* Would not be possible access general uncertainty in any statistic without bootstrap.
* Advanced forms account for spatial information and sampling strategy (game theory and Journel’s spatial bootstrap (1993).

Steps:

  **1.** assemble a representative sample set or build a cumulative distribution function (CDF) using analog data and/or declustering weights

  **2.** For $\ell = 1, \ldots, L$ realizations, do the following:

          For $i = \alpha, \ldots, n$ samples, do the following:

              Draw a random sample with replacement from the sample set or Monte Carlo simulate from the CDF.

      Calculate a realization of the statistic from the $n$ samples, e.g. $m^\ell$, $\sigma^2_{\ell}$.

  **3.** Compile and summarize the $L$ realizations of the statistic of interest.

This is a very powerful method.  Let's try it out. But first...

#### Bootstrap Assumptions and Limitations

* **representativity** - samples are sufficient, representative
* **stationarity** - metric of interest is invariant under translation (identically distributed)
* **no spatial context** - independent samples (no between sample correlation), does not account for boundary of area of interest and local information sources
* **uncertainty source** - only accounts for uncertainty due to too few samples (no uncertainty due nonstationarity, multiple populations or measurement errors)

#### Project Goal

Learn the basics for working with bootstrap for Uncertainty Modeling in Python to build practical spatial data analytics, geostatistics and machine learning workflows.

* Focus on customization and not a survey of available plot times

#### Caveats

I included methods that I have found useful for building my science and engineering workflows for subsurface modeling.

* Accessibility for most scientists and engineers is my goal.
* There are more advanced, more compact, more efficient methods to accomplish the same tasks.
* I appreciate feedback and I will use it to improve these walk-throughs.

#### Load and Configure the Required Libraries

The following code loads the required libraries and sets a plotting default.

```python
import numpy as np                        # ndarrys for gridded data
import pandas as pd                       # DataFrames for tabular data
import os                                 # set working directory, run executables
import matplotlib.pyplot as plt           # for plotting
import matplotlib
from scipy import stats                   # summary statistics
import math                               # trig etc.
import scipy.signal as signal             # kernel for moving window calculation
import random                             # random sampling
from scipy.stats import gaussian_kde      # for PDF calculation
from scipy.stats import t                 # Student's t distribution for analytical solution
import seaborn as sns                     # for advanced plotting
```

#### Convenience Functions for Plotting

These are convenience functions to visualize the data and bootstrap results.
* **custom_histogram** - data histogram with summary statistics
* **custom_histogram_with_uncert** - data histogram with boostrap uncertainty model
* **display_bootstrap** - bootstrap realizations' histogram with analytical or empirical PDF

I include these for concise, readable workflows.

```python
def custom_histogram(zdata,zmin,zmax,xlabel,title):
    offset = (zmax-zmin)/30.0
    freq = plt.hist(zdata,color = 'red',alpha = 0.3,edgecolor='black',bins = np.linspace(zmin,zmax,int(len(zdata)/3)))[0]
    ht = np.max(freq)
    plt.axvline(x=np.average(zdata),linestyle="--",c='black')
    plt.text(np.average(zdata)+offset,ht*0.95, r'Average = ' + str(round(np.average(zdata),1)), fontsize=12)
    plt.text(np.average(zdata)+offset,ht*0.90, r'St.Dev. = ' + str(round(np.std(zdata),1)), fontsize=12)
    plt.text(np.average(zdata)+offset,ht*0.85, r'P10 = ' + str(round(np.percentile(zdata,10),1)), fontsize=12)
    plt.text(np.average(zdata)+offset,ht*0.80, r'P90 = ' + str(round(np.percentile(zdata,90),1)), fontsize=12)
    plt.xlabel(xlabel); plt.ylabel('Frequency'); plt.title(title)

def custom_histogram_with_uncert(zreal,zmin,zmax,zdata,xlabel,title):
    offset = (zmax-zmin)/30.0
    freq = plt.hist(zdata,color = 'red',alpha = 0.2,edgecolor='grey',bins = np.linspace(zmin,zmax,int(len(zdata)/3)))[0]
    ht = np.max(freq)
    plt.axvline(x=np.average(zdata),c='black')
    plt.axvline(x=np.percentile(zreal,90),linestyle="--",c='black')
    plt.axvline(x=np.percentile(zreal,10),linestyle="--",c='black')
    plt.text(np.percentile(zreal,90)+offset,ht*0.90, r'Average = ' + str(round(np.average(zreal),1)), fontsize=12)
    plt.text(np.percentile(zreal,90)+offset,ht*0.85, r'Average P90 = ' + str(round(np.percentile(zreal,90),1)), fontsize=12)
    plt.text(np.percentile(zreal,90)+offset,ht*0.80, r'Average P10 = ' + str(round(np.percentile(zreal,10),1)), fontsize=12)
    plt.xlabel(xlabel); plt.ylabel('Frequency'); plt.title(title)

def display_bootstrap(zreal,zmin,zmax,zdata,title,abin,analytical,stat):
    offset = (zmax-zmin)/30.0
    freq = plt.hist(zreal,color = 'red',alpha = 0.2,edgecolor = 'black',bins=np.linspace(zmin,zmax,100),label = 'bootstrap',density = True)[0] # plot the distribution, could also calculate any summary statistics
    ht = np.max(freq)
    if analytical is None:
        sns.kdeplot(x=zreal,color = 'grey',alpha = 0.1,levels = 1,bw_adjust = 1,label='bootstrap PDF')
        plt.xlabel('Boostrap Realizations and Kernel Density Estimate')
    else:
        plt.plot(abin,analytical,color = 'black',label = 'analytical',alpha=0.4)
        plt.fill_between(abin, 0, analytical, where = abin <= np.percentile(zreal,10), facecolor='red', interpolate=True, alpha = 0.5)
        plt.fill_between(abin, 0, analytical, where = abin >= np.percentile(zreal,90), facecolor='red', interpolate=True, alpha = 0.5)
        plt.xlabel('Boostrap Realizations and Analytical Sampling Distributions')
    plt.axvline(x=stat(zdata),linestyle="--",c='black')
    plt.text(stat(zdata)+offset,ht*0.95, r'Average = ' + str(round(np.average(zreal),1)), fontsize=12)
    plt.text(stat(zdata)+offset,ht*0.90, r'St.Dev. = ' + str(round(np.std(zreal),1)), fontsize=12)
    plt.text(stat(zdata)+offset,ht*0.85, r'P90 = ' + str(round(np.percentile(zreal,90),1)), fontsize=12)
    plt.text(stat(zdata)+offset,ht*0.8, r'P10 = ' + str(round(np.percentile(zreal,10),1)), fontsize=12)
    plt.ylabel('Frequency'); plt.title(title)
    plt.legend(loc = 'upper left')
```

#### Set the Random Number Seed

Since we will be random sampling, Monte Carlo simulation for sampling with replacement, we set the random number seed for worklfow repeatability

* note, if you rerun any bootstrap code blocks the results will change unless the seed is set in each code block

```python
seed = 13                                 # random number generator seed
random.seed(a=seed)                       # initialize the random number generator
```

#### Set the working directory

I always like to do this so I don't lose files and to simplify subsequent read and writes (avoid including the full address each time).

```python
os.chdir("c:/PGE383")                     # set the working directory
```

#### Load the Tabular Data

Here's the command to load our comma delimited data file in to a Pandas' DataFrame object.

```python
df = pd.read_csv(r'https://raw.githubusercontent.com/GeostatsGuy/GeoDataSets/master/sample_data_biased.csv') # load the data from Dr. Pyrcz's github repository
#df = pd.read_csv('sample_data_biased.csv') # load our data table from the working directory
```

#### Feature Engineering

We do just a couple basic things:

* let's drop some samples so that we increase the variations in bootstrap samples for our demonstration below.

* let's convert porosity from fraction to percentage (100x)

```python
fraction = 0.1                             # set proportion of data to retain and observe the uncertainty
df['Porosity'] = df['Porosity']*100        # convert fraction to percentage
df = df.sample(frac = fraction,random_state=seed) # extract a random subset
print('Using ' + str(len(df)) + ' number of samples')
```

    Using 29 number of samples

#### Visualize the DataFrame

Visualizing the DataFrame would be useful and we already learned about these methods in this demo (https://git.io/fNgRW).

```python
df.head()                                 # display first 4 samples in the table as a preview
```

      X
      Y
      Facies
      Porosity
      Perm

      107
      390
      779
      1
      19.328012
      495.525997

      209
      530
      789
      1
      15.487071
      51.991055

      30
      800
      800
      1
      12.783107
      3.294085

      207
      450
      689
      1
      15.476265
      82.943101

      221
      490
      959
      1
      19.656596
      403.008633

#### Calculate Summary Statistics

The data includes **X** and **Y** coordinates (meters), **Facies** 1 and 0 (1 is sandstone and 0 interbedded sand and mudstone), **Porosity** (fraction), and **Permeability** as Perm (mDarcy).

```python
df.describe().transpose()                 # summary statistics
```

      count
      mean
      std
      min
      25%
      50%
      75%
      max

      X
      29.0
      463.103448
      246.520116
      20.000000
      300.000000
      430.000000
      550.000000
      900.000000

      Y
      29.0
      563.137931
      325.808524
      89.000000
      199.000000
      619.000000
      839.000000
      959.000000

      Facies
      29.0
      0.896552
      0.309934
      0.000000
      1.000000
      1.000000
      1.000000
      1.000000

      Porosity
      29.0
      13.943832
      3.584357
      7.996248
      11.684271
      12.740454
      15.487071
      21.821727

      Perm
      29.0
      155.973329
      376.973510
      0.111985
      3.967875
      11.806496
      98.144295
      1954.288198

#### Basic Data Visualization Parameters

Let's set the areal extents (X and Y), feature extents and color map.

```python
xmin = 0.0; xmax = 1000.0                 # limits for X coordinate
ymin = 0.0; ymax = 1000.0                 # limits for Y coordinate
pormin = 5.0; pormax = 25.0;              # limits for Porosity feature
permmin = 0.01; permmax = 10000           # limits for Permeability feature
cmap = plt.cm.inferno                     # color map
```

#### Basic Data Visualization

Let's try out location maps, histograms and scatter plots. See my other live code walk-throughs for more information.

```python
plt.subplot(231)
plt.scatter(df['X'],df['Y'],s=20,c=df['Porosity'],cmap=cmap,linewidths=0.3,edgecolor='black',alpha=0.8,vmin=pormin,vmax=pormax)
plt.colorbar(); plt.xlabel('X (m)'); plt.ylabel('Y (m)'); plt.title('Porosity Location Map')

plt.subplot(234)
plt.hist(df['Porosity'],color='red',alpha=0.3,edgecolor='black',bins=np.linspace(pormin,pormax,int(len(df)/3)))
plt.xlabel('Porosity (fraction)'); plt.ylabel('Frequency'); plt.title('Porosity Histogram')

plt.subplot(232)
plt.scatter(df['X'],df['Y'],s = 20,c = df['Perm'],cmap = cmap,linewidths = 0.3,edgecolor ='black',alpha =0.8,vmin=permmin,vmax=permmax,norm=matplotlib.colors.LogNorm())
plt.colorbar(); plt.xlabel('X (m)'); plt.ylabel('Y (m)'); plt.title('Permeability Location Map')

plt.subplot(235)
plt.hist(df['Perm'],color='red',alpha=0.3,edgecolor='black',bins=np.logspace(np.log10(permmin),np.log10(permmax),int(len(df)/3)))
plt.xlabel('Permeability (mD)'); plt.ylabel('Frequency'); plt.title('Permeability Histogram'); plt.xscale('log')

plt.subplot(233)
plt.scatter(df['Porosity'],df['Perm'],s=20,color='red',alpha=0.3,edgecolor='black')
plt.ylabel('Permeability (mD)'); plt.xlabel('Porosity (fraction)'); plt.title('Permeability-Porosity Scatter Plot')
plt.yscale('log')
sns.kdeplot(x=df['Porosity'],y=df['Perm'],color = 'black',alpha=0.2,levels=4)

plt.subplots_adjust(left=0.0, bottom=0.0, right=3.0, top=2.2, wspace=0.2, hspace=0.2)
plt.show()
```

### Bootstrap Method in Python

If you are new to bootstrap and Python, here's a simple pseudocode workflow for bootstrap.

* specify the number of bootstrap realizations, $L$
* declare a list to store the bootstrap realizations of the statistic of interest
* loop over $L$ bootstrap realizations
    * $n$ MCS, random samples with replacement for a new realization of the data
    * calculate the realization of the statistic from the realization of the data
* summarize the resulting uncertainty model, histogram, summary statistics etc.

#### Bootstrap of the Sample Mean, Arithmetic Average

Let's make a simple bootstrap function where:
* **zdata** is a 1D ndarray of the original data
* **nreal** is the number of bootstrap realizations to calculate
* **stat** is a function that calculates the statistic

```python
def bootstrap(zdata,nreal,stat):
    zreal = []                            # declare an empty list to store the bootstrap realizations
    for l in range(0,nreal):              # loop over the L bootstrap realizations
        samples = random.choices(zdata, k=len(zdata)) # n Monte Carlo simulations, sample with replacement
        zreal.append(stat(samples))       # calculate the realization of the statistic and append to list
    return zreal                          # return the list of realizations of the statistic
```

#### Additional Declarations for Read-able Code

Let's add a couple more declarations for concise, read-able code

```python
por = df['Porosity'].values               # copy DataFrame series, Porosity feature, to 1D ndarray
lpor = np.linspace(pormin,pormax,100)     # calculate bins for plotting
```

#### Demonstration of Bootstrap for Uncertainty in the Mean

Let's bootstrap to calculate the uncertainty in the mean. We will compare the bootstrap results with the analytical expression:

\begin{equation}
\mu \sim \overline{x} \pm t_{\frac{\alpha}{2},n-1}\sqrt{\frac{s^2}{n}}
\end{equation}

```python
L = 1000; random.seed(a=seed)             # set the number and seed for the bootstrap realizations
por_avg_real = bootstrap(por,L,np.average) # bootstrap of the arithmetic average

plt.subplot(131)                          # plot the original data
custom_histogram(por,pormin,pormax,'Porosity (fraction)','Original Porosity Histogram')
plt.subplot(132)                          # plot the bootstrap results
mod = t.pdf(lpor,len(df)-1,loc=np.average(por),scale=math.sqrt(np.var(por)/len(df))) # analytical PDF
display_bootstrap(por_avg_real,pormin,pormax,por,'Bootstrap Average Realizations',lpor,mod,np.average)
plt.subplot(133)                          # plot the original data and bootstrap results
custom_histogram_with_uncert(por_avg_real,pormin,pormax,por,'Porosity (%)','Bootstrap Uncertainty Average')
plt.subplots_adjust(left=0.0, bottom=0.0, right=3.0, top=1.2, wspace=0.2, hspace=0.2); plt.show()
```

#### Optional: Write out the Bootstrap Samples for Uncertainty in the Mean

Set the file name and feature name and write your bootstrap realizations as a .csv file.

* file will be written in the current working directory

```python
filename = 'avg_por_boot.csv'             # specify file name and feature name for writing out
featurename = 'avg_por_boot'

df_boot = pd.DataFrame(data=por_avg_real,columns=[featurename]) # DataFrame with the bootstrap realizations
df_boot.to_csv(filename,index=False)
df_boot.head()
```

      avg_por_boot

      0
      13.505598

      1
      13.896181

      2
      13.771810

      3
      13.872079

      4
      14.120102

#### Demonstration of Bootstrap for Uncertainty in the Standard Deviation

Let's perform bootstrap of a measure of dispersion, the standard deviation, $\sigma_x$.

```python
L = 1000; random.seed(a=seed)             # set the number and seed for the bootstrap realizations
por_stdev_real = bootstrap(por,L,np.std)  # bootstrap of the standard deviation

plt.subplot(131)                          # plot the bootstrap results
display_bootstrap(por_stdev_real,0,7,por,'Bootstrap Standard Deviation',lpor,None,np.std)
plt.subplots_adjust(left=0.0, bottom=0.0, right=3.0, top=1.1, wspace=0.2, hspace=0.2); plt.show()
```

#### Bootstrap of the Coefficient of Variation

Here's a statistic that is derived from multiple statistics for each bootstrap realization of the data.

* $CV_x^{\ell} = \frac{s_x^{\ell}}{\overline{x}_x^{\ell}}$

* this reinforces that we bootstrap dataset realizations and then calculate the statistic on this dataset realization

For the coefficient of variation we will:

* calculate a bootstrap realization of the dataset with $n$ samples with replacement
* calculate the mean and standard deviation from this bootstrapped realization of the dataset
* calculate a boostrap realization of the coefficient of variation as the standard deviation realizaiton divided by the mean realization

Repeat this $L$ time on $L$ realizations of the data and then evaluate the resulting distribution.

#### Demonstration of Bootstrap for Uncertainty in the Coefficient of Variation

Now bootstrap uncertainty of coefficient of variation, $CV_x^{\ell} = \frac{s_x^{\ell}}{\overline{x}_x^{\ell}}$

```python
cv = lambda x: np.std(x) / np.mean(x)     # define lambda function to calculate coefficient of variation
L = 1000; random.seed(a=seed)             # set the number and seed for the bootstrap realizations
por_cv_real = bootstrap(por,L,cv)         # bootstrap of the interquartile range

plt.subplot(131)                          # plot the bootstrap results
display_bootstrap(por_cv_real,0,0.5,por,'Bootstrap Coefficient of Variation',lpor,None,cv)
plt.subplots_adjust(left=0.0, bottom=0.0, right=3.0, top=1.1, wspace=0.2, hspace=0.2); plt.show()
```

#### Demonstration of Bootstrap for Uncertainty in the Interquartile Range

Here's another statistic that is derived from a combination of statistics, interquartile range, $IQR_x^{\ell} = F_x^{\ell}(0.75) - F_x^{\ell}(0.25)$

```python
L = 1000; random.seed(a=seed)             # set the number and seed for the bootstrap realizations
por_iqr_real = bootstrap(por,L,stats.iqr) # bootstrap of the interquartile range

plt.subplot(131)                          # plot the bootstrap results
display_bootstrap(por_iqr_real,0,10,por,'Bootstrap IQR',lpor,None,stats.iqr)
plt.subplots_adjust(left=0.0, bottom=0.0, right=3.0, top=1.1, wspace=0.2, hspace=0.2); plt.show()
```

#### Demonstration of Bootstrap for Uncertainty in the Percentile

Now let's select any percentile and bootstrap it.

```python
per = 50                                  # percentile to bootstrap (in percentage)
def my_percentile(data):                  # define a function to calculate this specific percentile
    return np.percentile(data,percentile)

L = 1000; random.seed(a=seed)             # set the number and seed for the bootstrap realizations
por_per_real = bootstrap(por,L,my_percentile) # bootstrap of a specified percentile

plt.subplot(131)                          # plot the bootstrap results
display_bootstrap(por_per_real,0,20,por,'Bootstrap ' + str(per) + 'th Percentile',lpor,None,my_percentile)
plt.subplots_adjust(left=0.0, bottom=0.0, right=3.0, top=1.1, wspace=0.2, hspace=0.2); plt.show()
```

#### Demonstration of Bootstrap for Uncertainty in the Proportion

Let's demonstrate another case for which we have the analytical solution to check our bootstrap results.

\begin{equation}
P \sim \hat{p} \pm t_{\frac{\alpha}{2},n-1}\sqrt{\frac{\hat{p}(1-\hat{p})}{n}}
\end{equation}

We will a big, ad hoc function to assist with this presentation. More could be done to factor, generalize the code.

```python
def proportion_bootstrap_viz(zdata,nreal,nbin):
    sand_prop_real = []                       # declare an empty list to store the bootstrap realizations of the statistic
    shale_prop_real = []

    for k in range(0,L):                      # loop over the L bootstrap realizations
        samples = random.choices(zdata, k=len(zdata)) # n Monte Carlo simulations
        sand_prop_real.append(samples.count(1)/len(zdata)) # calculate the statistic of interest from the new bootstrap dataset
        shale_prop_real.append(samples.count(0)/len(zdata)) # calculate the statistic of interest from the new bootstrap dataset
    sand_prop = np.sum(zdata == 1)/len(zdata); shale_prop = np.sum(zdata == 0)/len(df)

    fig = plt.subplot(121)
    barlist = plt.bar(x=['shale','sand'],height = [1-sand_prop,sand_prop],color = 'red',alpha = 0.3,edgecolor='black')
    barlist[0].set_color('blue')
    plt.text(-.3, shale_prop+0.02, r'Prop. Shale = ' + str(round(shale_prop,2)), fontsize=12)
    plt.text(0.7, sand_prop+0.02, r'Prop. Sand = ' + str(round(sand_prop,2)), fontsize=12)
    plt.xlabel('Rock Type / Facies'); plt.ylabel('Frequency'); plt.title('Facies Histogram')
    plt.ylim([0,1]); plt.yticks(np.arange(0, 1.1, 0.1))

    plt.subplot(122)
    plt.hist(sand_prop_real,color = 'red',alpha = 0.2,edgecolor = 'black',bins=np.linspace(0.0,1.0,nbin),label = 'sand',density = True) # plot the distribution, could also calculate any summary statistics
    analytical = t.pdf(np.linspace(0.0,1.0,200), len(df)-1,loc=sand_prop,scale=math.sqrt(sand_prop*(1.0-sand_prop)/len(df)))
    plt.plot(np.linspace(0.0,1.0,200),analytical,color = 'black',label = 'analytical',alpha=0.4)
    plt.axvline(x=sand_prop,linestyle="--",c='black')
    plt.fill_between(np.linspace(0.0,1.0,200), 0, analytical, where = np.linspace(0.0,1.0,200) <= np.percentile(sand_prop_real,10), facecolor='red', interpolate=True, alpha = 0.5)
    plt.fill_between(np.linspace(0.0,1.0,200), 0, analytical, where = np.linspace(0.0,1.0,200) >= np.percentile(sand_prop_real,90), facecolor='red', interpolate=True, alpha = 0.5)
    plt.text(np.average(sand_prop_real)-0.3, 7.0, r'E{Prop.} = ' + str(round(sand_prop,2)), fontsize=12)
    plt.text(np.average(sand_prop_real)-0.3, 6.6, r'P90 = ' + str(round(np.percentile(sand_prop_real,90),2)), fontsize=12)
    plt.text(np.average(sand_prop_real)-0.3, 6.2, r'P10 = ' + str(round(np.percentile(sand_prop_real,10),2)), fontsize=12)

    plt.hist(shale_prop_real,color = 'blue',alpha = 0.2,edgecolor = 'black',bins=np.linspace(0.0,1.0,nbin),label = 'shale',density = True) # plot the distribution, could also calculate any summary statistics
    analytical_shale = t.pdf(np.linspace(0.0,1.0,200), len(df)-1,loc=shale_prop,scale=math.sqrt(shale_prop*(1.0-shale_prop)/len(df)))
    plt.plot(np.linspace(0.0,1.0,200),analytical_shale,color = 'black',alpha=0.4)
    plt.axvline(x=shale_prop,linestyle="--",c='black')
    plt.fill_between(np.linspace(0.0,1.0,200), 0, analytical_shale, where = np.linspace(0.0,1.0,200) <= np.percentile(shale_prop_real,10), facecolor='blue', interpolate=True, alpha = 0.5)
    plt.fill_between(np.linspace(0.0,1.0,200), 0, analytical_shale, where = np.linspace(0.0,1.0,200) >= np.percentile(shale_prop_real,90), facecolor='blue', interpolate=True, alpha = 0.5)
    plt.text(np.average(shale_prop_real)+0.07, 7.0, r'E{Prop.} = ' + str(round(shale_prop,2)), fontsize=12)
    plt.text(np.average(shale_prop_real)+0.07, 6.6, r'P90 = ' + str(round(np.percentile(shale_prop_real,90),2)), fontsize=12)
    plt.text(np.average(shale_prop_real)+0.07, 6.2, r'P10 = ' + str(round(np.percentile(shale_prop_real,10),2)), fontsize=12)

    plt.xlabel('Boostrap Realizations and Analytical Sampling Distributions'); plt.ylabel('Frequency'); plt.title('Distribution of Bootstrap Proportions')
    plt.legend(loc = 'upper left')

    return sand_prop_real, shale_prop_real
```

#### Demonstration of Bootstrap for Uncertainty in the Proportion

Here's the original facies, categorical feature normalized histrogram and bootstrap uncertainty in proportions.

* given enough bootstrap realizations, we have a good match with the analytical prediction

```python
sand_prop_real,shale_prop_real = proportion_bootstrap_viz(zdata=df['Facies'].values,nreal=10000,nbin=20)
plt.subplots_adjust(left=0.0, bottom=0.0, right=2.0, top=1.2, wspace=0.2, hspace=0.2); plt.show()
```

#### Optional: Write out the Bootstrap Samples for an Uncertainty in the Proportions Model

Set the file name and feature name and write your bootstrap realizations as a .csv file.

* file will be written in the current working directory

```python
filename2 = 'prop_facies_boot.csv'        # specify file and feature name for writing out
featurename2 = ['sand_prop_boot','shale_prop_boot']

df_prop_boot = pd.DataFrame(data=np.column_stack((sand_prop_real,shale_prop_real)),columns=[featurename2]) # make a DataFrame with the bootstrap realizations
df_prop_boot.to_csv(filename2,index=False)
```

#### Comments

This was a basic demonstration of bootstrap.Remember:

* you can use bootstrap to calculate uncertainty for any statistic!

* you are calculating realizations of the statistic, representing uncertainty due to small sample size

* note the assumptions of bootstrap, including stationarity and representativity

* remember, get a dataset realization by bootstrap and then calculate the realization of the statistic from the dataset realization

* if your statistic has multiple inputs (e.g. P25 and P75), calculate each from the same bootstrap realization of the dataset.

I have other demonstrations on the basics of working with DataFrames, ndarrays, univariate statistics, plotting data, declustering, data transformations, trend modeling and many other workflows available at https://github.com/GeostatsGuy/PythonNumericalDemos and https://github.com/GeostatsGuy/GeostatsPy.

I hope this was helpful,

*Michael*

#### The Author:

### Michael Pyrcz, Associate Professor, University of Texas at Austin
*Novel Data Analytics, Geostatistics and Machine Learning Subsurface Solutions*

With over 17 years of experience in subsurface consulting, research and development, Michael has returned to academia driven by his passion for teaching and enthusiasm for enhancing engineers' and geoscientists' impact in subsurface resource development.

For more about Michael check out these links:

#### [Twitter](https://twitter.com/geostatsguy) | [GitHub](https://github.com/GeostatsGuy) | [Website](http://michaelpyrcz.com) | [GoogleScholar](https://scholar.google.com/citations?user=QVZ20eQAAAAJ&hl=en&oi=ao) | [Book](https://www.amazon.com/Geostatistical-Reservoir-Modeling-Michael-Pyrcz/dp/0199731446) | [YouTube](https://www.youtube.com/channel/UCLqEr-xV-ceHdXXXrTId5ig)  | [LinkedIn](https://www.linkedin.com/in/michael-pyrcz-61a648a1)

#### Want to Work Together?

I hope this content is helpful to those that want to learn more about subsurface modeling, data analytics and machine learning. Students and working professionals are welcome to participate.

* Want to invite me to visit your company for training, mentoring, project review, workflow design and / or consulting? I'd be happy to drop by and work with you!

* Interested in partnering, supporting my graduate student research or my Subsurface Data Analytics and Machine Learning consortium (co-PIs including Profs. Foster, Torres-Verdin and van Oort)? My research combines data analytics, stochastic modeling and machine learning theory with practice to develop novel methods and workflows to add value. We are solving challenging subsurface problems!

* I can be reached at mpyrcz@austin.utexas.edu.

I'm always happy to discuss,

*Michael*

Michael Pyrcz, Ph.D., P.Eng. Associate Professor The Hildebrand Department of Petroleum and Geosystems Engineering, Bureau of Economic Geology, The Jackson School of Geosciences, The University of Texas at Austin

#### More Resources Available at: [Twitter](https://twitter.com/geostatsguy) | [GitHub](https://github.com/GeostatsGuy) | [Website](http://michaelpyrcz.com) | [GoogleScholar](https://scholar.google.com/citations?user=QVZ20eQAAAAJ&hl=en&oi=ao) | [Book](https://www.amazon.com/Geostatistical-Reservoir-Modeling-Michael-Pyrcz/dp/0199731446) | [YouTube](https://www.youtube.com/channel/UCLqEr-xV-ceHdXXXrTId5ig)  | [LinkedIn](https://www.linkedin.com/in/michael-pyrcz-61a648a1)

```python

```


# Tutorial 4 - Pressure vs Temperature

Surfinpy has the functionality to generate phase diagrams as a function of pressure vs temperature based upon the methodology used in Molinari et al. (J. Phys. Chem. C  116, 12, 7073-7082) according to

\begin{align}
\gamma_{adsorbed, T, P} & = \gamma_{bare} + ( C ( E_{ads, T} - RTln(\frac{p}{p^o})
\end{align}

where $\gamma_{adsorbed, T, p}$ is the surface energy of the surface with adsorbed species at temperature (T) and pressure (P), $\gamma_{bare}$ is the suface energy of the bare surface, C is the coverage of adsorbed species, $E_{ads}$ is the adsorption energy,

\begin{align}
E_{ads, T} & =  E_{slab, adsorbant} - (E_{slab, bare} + n_{H_2O} E_{H_2O, T}) / n_{H_2O}
\end{align}

where $E_{slab, adsorbant}$ is the energy of the surface and the adsorbed species, $n_{H_2O}$ is he number of adsorbed species,

\begin{align}
E_{H_2O, (T)} & = E_{H_2O, (g)} - TS_{(T)}
\end{align}

where $S_{(T)}$ is the experimental entropy of gaseous water in the standard state.

So let's look at this in action. The module used is called p_vs_t

```python
import matplotlib.pyplot as plt
from surfinpy import utils as ut
from surfinpy import p_vs_t
from surfinpy import data
```

There are a number of user inputs that are required, the inputs are similiar to mu_vs_mu but with some small differences. First we need the energy from DFT of the adsorbant (eV), the energy of the surface ($j_m^2$) and the data for each surface. For clarity, in the `surfinpy.data.DataSet` objects for each surface it is always assumed that the adsorbing species is 'Y'.

```python
adsorbant = -14.00
```

```python
SE = 1.40
```

```python
stoich = data.DataSet(cation = 24, x = 48, y = 0, area = 60.22, energy = -575.00, label = 'Bare')
H2O =    data.DataSet(cation = 24, x = 48, y = 2, area = 60.22, energy = -605.00, label = '1 Water')
H2O_2 =  data.DataSet(cation = 24, x = 48, y = 8, area = 60.22, energy = -695.00, label = '2 Water')
```

```python
data = [H2O, H2O_2]
```

The coverage of the adsorbing species (in this case water) is also needed. surfinpy has a built in function within the utils module to do this for you, it takes the number of adsorbing species and the surface area and calculates the coverage for you.

```python
cov = ut.calculate_coverage(data)
```

We need the thermochemical data for the adsorbed species in order to make the DFT energy of our adsorbing species a temperature dependent term. This data has been taken directly from the NIST Janaf database and has been cut to just include the temperature and S values within the temperature range that interests us (In this case 0 - 1000 K).

```python
thermochem = ut.read_nist("H2O.txt")
```

Now we can generate our phase diagram .

```python
system = p_vs_t.calculate(stoich, data, SE, adsorbant, thermochem, coverage=cov)
ax = system.plot(colourmap='Spectral', figsize=(6, 4), ylabel='$log_{10} (P_{H_2O})$')
plt.savefig("../../../docs/source/Figures/Surfaces_7.png", dpi=600)
plt.show()
```

```python

```


# 12 Temperature Measurement

- Mechanical
- Thermoresitive (RTD, thermistor)
- Thermoelectric (thermocouple-TC)
- Radiative

## Mechanical

### liquid-in-glass thermometer
calibrated 0-ftp.mak#EIfiqdnd.mdahdnion them. motu
'L immerse to here.
### pressure thermometer
gas
bashing.G7#¥④s
are T
### bi-metallic strip thermometer
straight aksfx-t-gmeklA.BA
The metal B B
T--Trey 1 B
material D=-Iff linear coff.
of thermal expansion
density if PB> BA, T> Tref
Tag
geek

## Thermoresistive:

### RTD: Resistance Temperature Device
same than a strain gage

\begin{align}
R & = \frac{\rho L}{A} \\
\frac{dR}{R} & = \frac{d\rho}{\rho} + \frac{dL}{L} - \frac{dA}{A}
\end{align}
$R (T)$ is linear: $R(T) =R_0 + a (T-T_0)$

### Thermistor
semi-conductor material
\begin{align}
R(T) = R_0 \exp \left( \beta \left( \frac{1}{T} - \frac{1}{T_0} \right) \right)
\end{align}
Note $\beta$ is not the coefficient of linear therma expansion

need to be determine experimentally: $\beta$, $R_0$, $T_0$.

$R_0 ~ 5,000-10,000 \Omega$

→ sensing $dT$ → $dR >> dR_{lead}$

Use Wheatstone bridge

Thermistors have broad Temperature range than RTD's.  Resistance decreases with increasing temperature.

## Thermoelectric effects: thermocouples

When two dissimilar metals are connected together a net electromotrive force (emf), which is primarily dependent on the temperature of the junction, is created.  This is the Seebeck effect.  Furthermore, if the two dissimilar metals are connected to a circuit that draws current, another emf is created.  This is the Peltier effect.  Finally, if there is a temperature gradient along one (or both) of the wires, then another emf is created: Thomson effect.  Therefore, there are 3 emfs present in the circuit:
- Seebeck effect: junction of dissimilar materials
- Peltier effect: current flow in the circuit
- Thomson effect: temperature gradient in the materials

For properly designed thermoelectric devices, we are only concerned with emf from Seebeck effect.  However, when we want to measure this emf, we need to connect the dissimilar metal wires to a measuring device (DMM or DAQ) and we are created another thermal junction at each of the wire connection.  This new emf will depend on the temperature of the junction.

The temperature dependence of emf is complex, see fig below.

Values are typically reported in tables with respect to a reference temperature, $T_R$.  By convention $T_R = 0^\circ$C, the thermocouple tables give emf relative to 0$^\circ$C.  Also in the table emf is given positive for a temperature greater than 0$^\circ$C. So, with this convention if a thermocouple measures the temperature between $T_1$ & $T_2$, then $E_{1-2} = E_{1-R} - E_{2-R}$.  $E_{i-R}$ is the value reported in the thermocouple tables.

### Rules for analysis of thermoelectric circuits
- __Law of intermediate metals__

A third (intermediate) metal can be inserted in series with one of the wires, it won't affect the net emf as long as the new connections are at the same temperature.

- __Law of intermediate temperature__

If identical thermocouple wires are used, but connected between different temperatures.  The circuit between the temperatures $T_1$ & $T_2$ will develop an emf $E_{1-2}$, while the one between $T_2$ & $T_3$ will develop $E_{2-3}$.  The circuit between $T_1$ and $T_3$ will develop an emf $E_{1-3}$ which is the sum of $E_{1-2}$ & $E_{1-3}$:
\begin{align}
E_{1-3} = E_{1-2} + E_{2-3}
\end{align}

__Examples__

- Three constant temperature bath are at at temperature $T_1=155^\circ\text{C, } T_2 = 100^\circ\text{C, } T_3 = 20^\circ\text{C}$. A type E thermocouple & DMM are used to measure $E_{1-2}, E_{2-3}, E_{1-3}$.  Predict the measured emfs $E_{1-2}, E_{2-3}, E_{1-3}$.

Take TC tables for E-type TC:

```python
E_1R = 10.145; E_2R = 6.319; E_3R = 1.192 # mV
E_12 = E_1R - E_2R; E_23 = E_2R-E_3R; E_13 = E_1R-E_3R

print('E_12 = ',E_12 , 'E_23 =', E_23 , ', E_13 = ',E_13 )
print(E_12+E_23-E_13)
```

    E_12 =  3.8259999999999996 E_23 = 5.127 , E_13 =  8.953
    0.0

- A type E thermocouple is connected to DAQ whose terminals are at 25$^\circ$C and which does not have temperature compensation.  The DAQ reads 3.685 mV.  What is the temperature of the thermocouple junction?

```python

```

- A type J thermocouple is used to measure the temperature of an oven. An ice bath is used as a reference temperature junction.  The output voltage is $E = 10.779$ mV.

1- Calculate temperature of the oven.

$T_{oven} = 200^\circ$C

2- If ambient temperature $T = 20^\circ$C is used instead of the ice bath for the reference temperature, and the oven was still at the same temperature, what would be the voltage output?

$E_{1-2} = 9.760$ mV

### Real effects/potential errors

- Faulty wire replacement
- Spurious connection (broken electrical insulation)
- Temperature gradient effects

### Dynamic response

First order system with time constant:
\begin{align}
\tau = \frac{mc}{hA}
\end{align}

### (Dynamic) thermocouple compensation

\begin{align}
\left| \frac{E_o}{E_i} \right| & = \alpha \sqrt{ \frac{1+\omega^2 \tau_c^2}{1+\alpha^2 \omega^2 \tau_c^2} }\\
\text{with} & \; \alpha  = \frac{R}{R+R_c}, \; \tau_c = R_c C
\end{align}
$\omega$ frequency of the signal.

At steady-state:
\begin{align}
\left| \frac{E_o}{E_i} \right|_{\text{steady-state}} & = \frac{R}{R+R_c} = \alpha
\end{align}

```python

```

```python

```


# The $z$-Transform

*This Jupyter notebook is part of a [collection of notebooks](../index.ipynb) in the bachelors module Signals and Systems, Comunications Engineering, Universität Rostock. Please direct questions and suggestions to [Sascha.Spors@uni-rostock.de](mailto:Sascha.Spors@uni-rostock.de).*

## Summary of Properties, Theorems and Transforms

The [properties](properties.ipynb), [theorems](theorems.ipynb) and transforms of the two-sided $z$-transform as derived in the previous sections are summarized in the following. The corresponding tables serve as a reference for the application of the $z$-transform in the theory of signals and systems. Please refer to the respective sections for details.

### Definition

The $z$-transform and its inverse are defined as

\begin{align}
X(z) &= \sum_{k = -\infty}^{\infty} x[k] \, z^{-k} \\
x[k] &= \frac{1}{2 \pi j} \oint_{C} X(z) \, z^{k - 1} \; dz
\end{align}

where $z \in \text{ROC} \{ x[k] \}$.

### Properties and Theorems

The properties and theorems of the two-sided $z$-transform are given as

|  | $x[k]$ | $X(z) = \mathcal{Z} \{ x[k] \}$ | ROC |
|:---|:---:|:---:|:---|
| [Linearity](properties.ipynb#Linearity) | $A \, x_1[k] + B \, x_2[k]$ | $A \, X_1(z) + B \, X_2(z)$ | $\supseteq \text{ROC}\{x_1[k]\} \cap \text{ROC}\{x_2[k]\}$ |
| [Conjugation](properties.ipynb#Symmetry-for-Real-Valued-Signals) | $x^*[k]$ | $X^*(z^*)$ | $\text{ROC}\{ x[k] \}$ |
| [Real-valued signal](properties.ipynb#Symmetry-for-Real-Valued-Signals) | $x[k] = x^*[k]$ | $X(z) = X^*(z^*)$ | |
| [Linear Convolution](theorems.ipynb#Convolution-Theorem) | $x[k] * h[k]$ | $X(z) \cdot H(z)$ | $\supseteq \text{ROC}\{x[k]\} \cap \text{ROC}\{h[k]\}$ |
| [Shift](theorems.ipynb#Shift-Theorem) | $x[k - \kappa]$ | $z^{- \kappa} \cdot X(z)$ | $\supseteq \text{ROC}\{x[k]\} \setminus \{0, \infty \}$ |
| [Modulation](theorems.ipynb#Modulation-Theorem) | $z_0^k \cdot x[k]$ | $X\left( \frac{z}{z_0} \right)$ | $\{z: \frac{z}{z_0} \in \text{ROC} \{ x[k] \} \}$ |
| [Reversal](theorems.ipynb#Reversal) | $x[-k]$ | $X \left( \frac{1}{z} \right)$ | $\{z: \frac{1}{z} \in \text{ROC} \{ x[k] \} \}$ |

where $A, B, z_0 \in \mathbb{C}$ and $\kappa \in \mathbb{Z}$

### Selected Transforms

Two-sided $z$-transforms which are frequently used are given as

| $x[k]$ | $X(z) = \mathcal{Z} \{ x[k] \}$ | ROC |
|:---:|:---:|:---|
| $\delta[k]$ | $1$ | $\mathbb{C}$ |
| $\epsilon[k]$ | $\frac{z}{z-1}$ | $|z| > 1$ |
| $k \epsilon[k]$ | $\frac{z}{(z-1)^2}$ | $|z| > 1$ |
| $z_0^{k} \epsilon[k]$ | $\frac{z}{z - z_0}$ | $|z| > |z_0|$ |
| $-z_0^{k} \epsilon[-k-1]$ | $\frac{z}{z - z_0}$ | $|z| < |z_0|$ |
| $\sin(\Omega_0 k) \epsilon[k]$ | $\frac{z \sin(\Omega_0)}{z^2 - 2 z \cos(\Omega_0) + 1}$ | $|z| > 1$ |
| $\cos(\Omega_0 k) \epsilon[k]$ | $\frac{z ( z - \cos(\Omega_0))}{z^2 - 2 z \cos(\Omega_0) + 1}$ | $|z| > 1$ |

where $z_0 \in \mathbb{C}$, $\Omega_0 \in \mathbb{R}$ and $n \in \mathbb{N}$. More one- and two-sided transforms may be found in the literature or [online](https://en.wikipedia.org/wiki/Z-transform#Table_of_common_Z-transform_pairs).

**Copyright**

This notebook is provided as [Open Educational Resource](https://en.wikipedia.org/wiki/Open_educational_resources). Feel free to use the notebook for your own purposes. The text is licensed under [Creative Commons Attribution 4.0](https://creativecommons.org/licenses/by/4.0/), the code of the IPython examples under the [MIT license](https://opensource.org/licenses/MIT). Please attribute the work as follows: *Sascha Spors, Continuous- and Discrete-Time Signals and Systems - Theory and Computational Examples*.


# The Extended Kalman Filter

선형 칼만 필터 (Linear Kalman Filter)에 대한 이론을 바탕으로 비선형 문제에 칼만 필터를 적용해 보겠습니다. 확장칼만필터 (EKF)는 예측단계와 추정단계의 데이터를 비선형으로 가정하고 현재의 추정값에 대해 시스템을 선형화 한뒤 선형 칼만 필터를 사용하는 기법입니다.

비선형 문제에 적용되는 성능이 더 좋은 알고리즘들 (UKF, H_infinity)이 있지만 EKF 는 아직도 널리 사용되서 관련성이 높습니다.

```python
%matplotlib inline

from IPython.core.display import HTML
HTML("""
<style>
.output_png {
    display: table-cell;
    text-align: center;
    vertical-align: middle;
}
</style>
""")
```

<style>
.output_png {
    display: table-cell;
    text-align: center;
    vertical-align: middle;
}
</style>

## Linearizing the Kalman Filter

### Non-linear models
칼만 필터는 시스템이 선형일것이라는 가정을 하기 때문에 비선형 문제에는 직접적으로 사용하지 못합니다. 비선형성은 두가지 원인에서 기인될수 있는데 첫째는 프로세스 모델의 비선형성 그리고 둘째 측정 모델의 비선형성입니다. 예를 들어, 떨어지는 물체의 가속도는 속도의 제곱에 비례하는 공기저항에 의해 결정되기 때문에 비선형적인 프로세스 모델을 가지고, 레이더로 목표물의 범위와 방위 (bearing) 를 측정할때 비선형함수인 삼각함수를 사용하여 표적의 위치를 계산하기 때문에 비선형적인 측정 모델을 가지게 됩니다.

비선형문제에 기존의 칼만필터 방정식을 적용하지 못하는 이유는 비선형함수에 정규분포 (Gaussian)를 입력하면 아래와 같이 Gaussian 이 아닌 분포를 가지게 되기 때문입니다.

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

mu, sigma = 0, 0.1
x = np.linspace(mu - 3*sigma, mu + 3*sigma, 10000)
gaussian = stats.norm.pdf(x, mu, sigma)

def nonlinearFunction(x):
    return np.sin(x)

def linearFunction(x):
    return 0.5*x

nonlinearOutput = nonlinearFunction(gaussian)
linearOutput = linearFunction(gaussian)

# print(x)

plt.plot(x, gaussian, label = 'Gaussian Input')
plt.plot(x, linearOutput, label = 'Linear Output')
plt.plot(x, nonlinearOutput, label = 'Nonlinear Output')

plt.grid(linestyle='dotted', linewidth=0.8)
plt.legend()
plt.show()
```

### System Equations
선형 칼만 필터의 경우 프로세스 및 측정 모델은 다음과 같이 나타낼수 있습니다.

$$\begin{aligned}\dot{\mathbf x} &= \mathbf{Ax} + w_x\\
\mathbf z &= \mathbf{Hx} + w_z
\end{aligned}$$

이때 $\mathbf A$ 는 (연속시간에서) 시스템의 역학을 묘사하는 dynamic matrix 입니다. 위의 식을 이산화(discretize)시키면 아래와 같이 나타내줄 수 있습니다.

$$\begin{aligned}\bar{\mathbf x}_k &= \mathbf{F} \mathbf{x}_{k-1} \\
\bar{\mathbf z} &= \mathbf{H} \mathbf{x}_{k-1}
\end{aligned}$$

이때 $\mathbf F$ 는 이산시간 $\Delta t$ 에 걸쳐 $\mathbf x_{k-1}$을 $\mathbf x_{k}$ 로 전환하는 상태변환행렬 또는 상태전달함수 (state transition matrix) 이고, 위에서의 $w_x$ 와 $w_z$는 각각 프로세스 노이즈 공분산 행렬 $\mathbf Q$ 과 측정 노이즈 공분산 행렬 $\mathbf R$ 에 포함됩니다.

선형 시스템에서의 $\mathbf F \mathbf x- \mathbf B \mathbf u$ 와 $\mathbf H \mathbf x$ 는 비선형 시스템에서 함수 $f(\mathbf x, \mathbf u)$ 와 $h(\mathbf x)$ 로 대체됩니다.

$$\begin{aligned}\dot{\mathbf x} &= f(\mathbf x, \mathbf u) + w_x\\
\mathbf z &= h(\mathbf x) + w_z
\end{aligned}$$

### Linearisation
선형화란 말그대로 하나의 시점에 대하여 비선형함수에 가장 가까운 선 (선형시스템) 을 찾는것이라고 볼수 있습니다. 여러가지 방법으로 선형화를 할수 있겠지만 흔히 일차 테일러 급수를 사용합니다. ($ c_0$ 과 $c_1 x$)

$$f(x) = \sum_{k=0}^\infty c_k x^k = c_0 + c_1 x + c_2 x^2 + \dotsb$$
$$c_k = \frac{f^{\left(k\right)}(0)}{k!} = \frac{1}{k!} \cdot \frac{d^k f}{dx^k}\bigg|_0 $$

행렬의 미분값을 Jacobian 이라고 하는데 이를 통해서 위와 같이 $\mathbf F$ 와 $\mathbf H$ 를 나타낼 수 있습니다.

$$
\begin{aligned}
\mathbf F
= {\frac{\partial{f(\mathbf x_t, \mathbf u_t)}}{\partial{\mathbf x}}}\biggr|_{{\mathbf x_t},{\mathbf u_t}} \;\;\;\;
\mathbf H = \frac{\partial{h(\bar{\mathbf x}_t)}}{\partial{\bar{\mathbf x}}}\biggr|_{\bar{\mathbf x}_t}
\end{aligned}
$$

$$\mathbf F = \frac{\partial f(\mathbf x, \mathbf u)}{\partial x} =\begin{bmatrix}
\frac{\partial f_1}{\partial x_1} & \frac{\partial f_1}{\partial x_2} & \dots & \frac{\partial f_1}{\partial x_n}\\
\frac{\partial f_2}{\partial x_1} & \frac{\partial f_2}{\partial x_2} & \dots & \frac{\partial f_2}{\partial x_n} \\
\\ \vdots & \vdots & \ddots & \vdots
\\
\frac{\partial f_n}{\partial x_1} & \frac{\partial f_n}{\partial x_2} & \dots & \frac{\partial f_n}{\partial x_n}
\end{bmatrix}
$$

Linear Kalman Filter 와 Extended Kalman Filter 의 식들을 아래와 같이 비교할수 있습니다.

$$\begin{array}{l|l}
\text{Linear Kalman filter} & \text{EKF} \\
\hline
& \boxed{\mathbf F = {\frac{\partial{f(\mathbf x_t, \mathbf u_t)}}{\partial{\mathbf x}}}\biggr|_{{\mathbf x_t},{\mathbf u_t}}} \\
\mathbf{\bar x} = \mathbf{Fx} + \mathbf{Bu} & \boxed{\mathbf{\bar x} = f(\mathbf x, \mathbf u)}  \\
\mathbf{\bar P} = \mathbf{FPF}^\mathsf{T}+\mathbf Q  & \mathbf{\bar P} = \mathbf{FPF}^\mathsf{T}+\mathbf Q \\
\hline
& \boxed{\mathbf H = \frac{\partial{h(\bar{\mathbf x}_t)}}{\partial{\bar{\mathbf x}}}\biggr|_{\bar{\mathbf x}_t}} \\
\textbf{y} = \mathbf z - \mathbf{H \bar{x}} & \textbf{y} = \mathbf z - \boxed{h(\bar{x})}\\
\mathbf{K} = \mathbf{\bar{P}H}^\mathsf{T} (\mathbf{H\bar{P}H}^\mathsf{T} + \mathbf R)^{-1} & \mathbf{K} = \mathbf{\bar{P}H}^\mathsf{T} (\mathbf{H\bar{P}H}^\mathsf{T} + \mathbf R)^{-1} \\
\mathbf x=\mathbf{\bar{x}} +\mathbf{K\textbf{y}} & \mathbf x=\mathbf{\bar{x}} +\mathbf{K\textbf{y}} \\
\mathbf P= (\mathbf{I}-\mathbf{KH})\mathbf{\bar{P}} & \mathbf P= (\mathbf{I}-\mathbf{KH})\mathbf{\bar{P}}
\end{array}$$

$\mathbf F \mathbf x_{k-1}$ 을 사용하여 $\mathbf x_{k}$의 값을 추정할수 있겠지만, 선형화 과정에서 오차가 생길수 있기 때문에 Euler 또는 Runge Kutta 수치 적분 (numerical integration) 을 통해서 사전추정값 $\mathbf{\bar{x}}$ 를 구합니다. 같은 이유로 $\mathbf y$ (innovation vector 또는 잔차(residual)) 를 구할때도 $\mathbf H \mathbf x$ 대신에 수치적인 방법으로 계산하게 됩니다.

## Example: Robot Localization

### Prediction Model (예측모델)
EKF를 4륜 로봇에 적용시켜 보겠습니다. 간단한 bicycle steering model 을 통해 아래의 시스템 모델을 나타낼 수 있습니다.

```python
import kf_book.ekf_internal as ekf_internal
ekf_internal.plot_bicycle()
```

$$\begin{aligned}
\beta &= \frac d w \tan(\alpha) \\
\bar x_k &= x_{k-1} - R\sin(\theta) + R\sin(\theta + \beta) \\
\bar y_k &= y_{k-1} + R\cos(\theta) - R\cos(\theta + \beta) \\
\bar \theta_k &= \theta_{k-1} + \beta
\end{aligned}
$$

위의 식들을 토대로 상태벡터를 $\mathbf{x}=[x, y, \theta]^T$ 그리고 입력벡터를 $\mathbf{u}=[v, \alpha]^T$ 라고 정의 해주면 아래와 같이 $f(\mathbf x, \mathbf u)$ 나타내줄수 있고 $f$ 의 Jacobian $\mathbf F$를 미분하여 아래의 행렬을 구해줄수 있습니다.

$$\bar x = f(x, u) + \mathcal{N}(0, Q)$$

$$f = \begin{bmatrix}x\\y\\\theta\end{bmatrix} +
\begin{bmatrix}- R\sin(\theta) + R\sin(\theta + \beta) \\
R\cos(\theta) - R\cos(\theta + \beta) \\
\beta\end{bmatrix}$$

$$\mathbf F = \frac{\partial f(\mathbf x, \mathbf u)}{\partial \mathbf x} = \begin{bmatrix}
1 & 0 & -R\cos(\theta) + R\cos(\theta+\beta) \\
0 & 1 & -R\sin(\theta) + R\sin(\theta+\beta) \\
0 & 0 & 1
\end{bmatrix}$$

$\bar{\mathbf P}$ 을 구하기 위해 입력($\mathbf u$)에서 비롯되는 프로세스 노이즈 $\mathbf Q$ 를 아래와 같이 정의합니다.

$$\mathbf{M} = \begin{bmatrix}\sigma_{vel}^2 & 0 \\ 0 & \sigma_\alpha^2\end{bmatrix}
\;\;\;\;
\mathbf{V} = \frac{\partial f(x, u)}{\partial u} \begin{bmatrix}
\frac{\partial f_1}{\partial v} & \frac{\partial f_1}{\partial \alpha} \\
\frac{\partial f_2}{\partial v} & \frac{\partial f_2}{\partial \alpha} \\
\frac{\partial f_3}{\partial v} & \frac{\partial f_3}{\partial \alpha}
\end{bmatrix}$$

$$\mathbf{\bar P} =\mathbf{FPF}^{\mathsf T} + \mathbf{VMV}^{\mathsf T}$$

```python
import sympy
from sympy.abc import alpha, x, y, v, w, R, theta
from sympy import symbols, Matrix
sympy.init_printing(use_latex="mathjax", fontsize='16pt')
time = symbols('t')
d = v*time
beta = (d/w)*sympy.tan(alpha)
r = w/sympy.tan(alpha)

fxu = Matrix([[x-r*sympy.sin(theta) + r*sympy.sin(theta+beta)],
              [y+r*sympy.cos(theta)- r*sympy.cos(theta+beta)],
              [theta+beta]])
F = fxu.jacobian(Matrix([x, y, theta]))
F
```

$\displaystyle \left[\begin{matrix}1 & 0 & - \frac{w \cos{\left(\theta \right)}}{\tan{\left(\alpha \right)}} + \frac{w \cos{\left(\frac{t v \tan{\left(\alpha \right)}}{w} + \theta \right)}}{\tan{\left(\alpha \right)}}\\0 & 1 & - \frac{w \sin{\left(\theta \right)}}{\tan{\left(\alpha \right)}} + \frac{w \sin{\left(\frac{t v \tan{\left(\alpha \right)}}{w} + \theta \right)}}{\tan{\left(\alpha \right)}}\\0 & 0 & 1\end{matrix}\right]$

```python
# reduce common expressions
B, R = symbols('beta, R')
F = F.subs((d/w)*sympy.tan(alpha), B)
F.subs(w/sympy.tan(alpha), R)
```

$\displaystyle \left[\begin{matrix}1 & 0 & - R \cos{\left(\theta \right)} + R \cos{\left(\beta + \theta \right)}\\0 & 1 & - R \sin{\left(\theta \right)} + R \sin{\left(\beta + \theta \right)}\\0 & 0 & 1\end{matrix}\right]$

```python
V = fxu.jacobian(Matrix([v, alpha]))
V = V.subs(sympy.tan(alpha)/w, 1/R)
V = V.subs(time*v/R, B)
V = V.subs(time*v, 'd')
V
```

$\displaystyle \left[\begin{matrix}t \cos{\left(\beta + \theta \right)} & \frac{d \left(\tan^{2}{\left(\alpha \right)} + 1\right) \cos{\left(\beta + \theta \right)}}{\tan{\left(\alpha \right)}} - \frac{w \left(- \tan^{2}{\left(\alpha \right)} - 1\right) \sin{\left(\theta \right)}}{\tan^{2}{\left(\alpha \right)}} + \frac{w \left(- \tan^{2}{\left(\alpha \right)} - 1\right) \sin{\left(\beta + \theta \right)}}{\tan^{2}{\left(\alpha \right)}}\\t \sin{\left(\beta + \theta \right)} & \frac{d \left(\tan^{2}{\left(\alpha \right)} + 1\right) \sin{\left(\beta + \theta \right)}}{\tan{\left(\alpha \right)}} + \frac{w \left(- \tan^{2}{\left(\alpha \right)} - 1\right) \cos{\left(\theta \right)}}{\tan^{2}{\left(\alpha \right)}} - \frac{w \left(- \tan^{2}{\left(\alpha \right)} - 1\right) \cos{\left(\beta + \theta \right)}}{\tan^{2}{\left(\alpha \right)}}\\\frac{t}{R} & \frac{d \left(\tan^{2}{\left(\alpha \right)} + 1\right)}{w}\end{matrix}\right]$

### Measurement Model (측정모델)

레이더로 범위$(r)$와 방위($\phi$)를 측정할때 다음과 같은 센서모델을 사용합니다. 이때 $\mathbf p$ 는 landmark의 위치를 나타내줍니다.

$$r = \sqrt{(p_x - x)^2 + (p_y - y)^2}
\;\;\;\;
\phi = \arctan(\frac{p_y - y}{p_x - x}) - \theta
$$

$$\begin{aligned}
\mathbf z& = h(\bar{\mathbf x}, \mathbf p) &+ \mathcal{N}(0, R)\\
&= \begin{bmatrix}
\sqrt{(p_x - x)^2 + (p_y - y)^2} \\
\arctan(\frac{p_y - y}{p_x - x}) - \theta
\end{bmatrix} &+ \mathcal{N}(0, R)
\end{aligned}$$

$h$ 의 Jacobian $\mathbf H$를 미분하여 아래의 행렬을 구해줄수 있습니다.
$$\mathbf H = \frac{\partial h(\mathbf x, \mathbf u)}{\partial \mathbf x} =
\left[\begin{matrix}\frac{- p_{x} + x}{\sqrt{\left(p_{x} - x\right)^{2} + \left(p_{y} - y\right)^{2}}} & \frac{- p_{y} + y}{\sqrt{\left(p_{x} - x\right)^{2} + \left(p_{y} - y\right)^{2}}} & 0\\- \frac{- p_{y} + y}{\left(p_{x} - x\right)^{2} + \left(p_{y} - y\right)^{2}} & - \frac{p_{x} - x}{\left(p_{x} - x\right)^{2} + \left(p_{y} - y\right)^{2}} & -1\end{matrix}\right]
$$

```python
import sympy
from sympy.abc import alpha, x, y, v, w, R, theta

px, py = sympy.symbols('p_x, p_y')
z = sympy.Matrix([[sympy.sqrt((px-x)**2 + (py-y)**2)],
            [sympy.atan2(py-y, px-x) - theta]])
z.jacobian(sympy.Matrix([x, y, theta]))

# print(sympy.latex(z.jacobian(sympy.Matrix([x, y, theta])))
```

$\displaystyle \left[\begin{matrix}\frac{- p_{x} + x}{\sqrt{\left(p_{x} - x\right)^{2} + \left(p_{y} - y\right)^{2}}} & \frac{- p_{y} + y}{\sqrt{\left(p_{x} - x\right)^{2} + \left(p_{y} - y\right)^{2}}} & 0\\- \frac{- p_{y} + y}{\left(p_{x} - x\right)^{2} + \left(p_{y} - y\right)^{2}} & - \frac{p_{x} - x}{\left(p_{x} - x\right)^{2} + \left(p_{y} - y\right)^{2}} & -1\end{matrix}\right]$

```python
from math import sqrt

def H_of(x, landmark_pos):
    """ compute Jacobian of H matrix where h(x) computes
    the range and bearing to a landmark for state x """

    px = landmark_pos[0]
    py = landmark_pos[1]
    hyp = (px - x[0, 0])**2 + (py - x[1, 0])**2
    dist = sqrt(hyp)

    H = array(
        [[-(px - x[0, 0]) / dist, -(py - x[1, 0]) / dist, 0],
         [ (py - x[1, 0]) / hyp,  -(px - x[0, 0]) / hyp, -1]])
    return H
```

```python
from math import atan2

def Hx(x, landmark_pos):
    """ takes a state variable and returns the measurement
    that would correspond to that state.
    """
    px = landmark_pos[0]
    py = landmark_pos[1]
    dist = sqrt((px - x[0, 0])**2 + (py - x[1, 0])**2)

    Hx = array([[dist],
                [atan2(py - x[1, 0], px - x[0, 0]) - x[2, 0]]])
    return Hx
```

측정 노이즈는 다음과 같이 나타내줍니다.
$$\mathbf R=\begin{bmatrix}\sigma_{range}^2 & 0 \\ 0 & \sigma_{bearing}^2\end{bmatrix}$$

### Implementation

`FilterPy` 의 `ExtendedKalmanFilter` class 를 활용해서 EKF 를 구현해보도록 하겠습니다.

```python
from filterpy.kalman import ExtendedKalmanFilter as EKF
from numpy import array, sqrt, random
import sympy

class RobotEKF(EKF):
    def __init__(self, dt, wheelbase, std_vel, std_steer):
        EKF.__init__(self, 3, 2, 2)
        self.dt = dt
        self.wheelbase = wheelbase
        self.std_vel = std_vel
        self.std_steer = std_steer

        a, x, y, v, w, theta, time = sympy.symbols(
            'a, x, y, v, w, theta, t')
        d = v*time
        beta = (d/w)*sympy.tan(a)
        r = w/sympy.tan(a)

        self.fxu = sympy.Matrix(
            [[x-r*sympy.sin(theta)+r*sympy.sin(theta+beta)],
             [y+r*sympy.cos(theta)-r*sympy.cos(theta+beta)],
             [theta+beta]])

        self.F_j = self.fxu.jacobian(sympy.Matrix([x, y, theta]))
        self.V_j = self.fxu.jacobian(sympy.Matrix([v, a]))

        # save dictionary and it's variables for later use
        self.subs = {x: 0, y: 0, v:0, a:0,
                     time:dt, w:wheelbase, theta:0}
        self.x_x, self.x_y, = x, y
        self.v, self.a, self.theta = v, a, theta

    def predict(self, u):
        self.x = self.move(self.x, u, self.dt)

        self.subs[self.theta] = self.x[2, 0]
        self.subs[self.v] = u[0]
        self.subs[self.a] = u[1]

        F = array(self.F_j.evalf(subs=self.subs)).astype(float)
        V = array(self.V_j.evalf(subs=self.subs)).astype(float)

        # covariance of motion noise in control space
        M = array([[self.std_vel*u[0]**2, 0],
                   [0, self.std_steer**2]])

        self.P = F @ self.P @ F.T + V @ M @ V.T

    def move(self, x, u, dt):
        hdg = x[2, 0]
        vel = u[0]
        steering_angle = u[1]
        dist = vel * dt

        if abs(steering_angle) > 0.001: # is robot turning?
            beta = (dist / self.wheelbase) * tan(steering_angle)
            r = self.wheelbase / tan(steering_angle) # radius

            dx = np.array([[-r*sin(hdg) + r*sin(hdg + beta)],
                           [r*cos(hdg) - r*cos(hdg + beta)],
                           [beta]])
        else: # moving in straight line
            dx = np.array([[dist*cos(hdg)],
                           [dist*sin(hdg)],
                           [0]])
        return x + dx
```

정확한 잔차값 $y$을 구하기 방위값이 $0 \leq \phi \leq 2\pi$ 이도록 고쳐줍니다.

```python
def residual(a, b):
    """ compute residual (a-b) between measurements containing
    [range, bearing]. Bearing is normalized to [-pi, pi)"""
    y = a - b
    y[1] = y[1] % (2 * np.pi)    # force in range [0, 2 pi)
    if y[1] > np.pi:             # move to [-pi, pi)
        y[1] -= 2 * np.pi
    return y
```

```python
from filterpy.stats import plot_covariance_ellipse
from math import sqrt, tan, cos, sin, atan2
import matplotlib.pyplot as plt

dt = 1.0

def z_landmark(lmark, sim_pos, std_rng, std_brg):
    x, y = sim_pos[0, 0], sim_pos[1, 0]
    d = np.sqrt((lmark[0] - x)**2 + (lmark[1] - y)**2)
    a = atan2(lmark[1] - y, lmark[0] - x) - sim_pos[2, 0]
    z = np.array([[d + random.randn()*std_rng],
                  [a + random.randn()*std_brg]])
    return z

def ekf_update(ekf, z, landmark):
    ekf.update(z, HJacobian = H_of, Hx = Hx,
               residual=residual,
               args=(landmark), hx_args=(landmark))

def run_localization(landmarks, std_vel, std_steer,
                     std_range, std_bearing,
                     step=10, ellipse_step=20, ylim=None):
    ekf = RobotEKF(dt, wheelbase=0.5, std_vel=std_vel,
                   std_steer=std_steer)
    ekf.x = array([[2, 6, .3]]).T # x, y, steer angle
    ekf.P = np.diag([.1, .1, .1])
    ekf.R = np.diag([std_range**2, std_bearing**2])

    sim_pos = ekf.x.copy() # simulated position
    # steering command (vel, steering angle radians)
    u = array([1.1, .01])

    plt.figure()
    plt.scatter(landmarks[:, 0], landmarks[:, 1],
                marker='s', s=60)

    track = []
    for i in range(200):
        sim_pos = ekf.move(sim_pos, u, dt/10.) # simulate robot
        track.append(sim_pos)

        if i % step == 0:
            ekf.predict(u=u)

            if i % ellipse_step == 0:
                plot_covariance_ellipse(
                    (ekf.x[0,0], ekf.x[1,0]), ekf.P[0:2, 0:2],
                     std=6, facecolor='k', alpha=0.3)

            x, y = sim_pos[0, 0], sim_pos[1, 0]
            for lmark in landmarks:
                z = z_landmark(lmark, sim_pos,
                               std_range, std_bearing)
                ekf_update(ekf, z, lmark)

            if i % ellipse_step == 0:
                plot_covariance_ellipse(
                    (ekf.x[0,0], ekf.x[1,0]), ekf.P[0:2, 0:2],
                    std=6, facecolor='g', alpha=0.8)
    track = np.array(track)
    plt.plot(track[:, 0], track[:,1], color='k', lw=2)
    plt.axis('equal')
    plt.title("EKF Robot localization")
    if ylim is not None: plt.ylim(*ylim)
    plt.show()
    return ekf
```

```python
landmarks = array([[5, 10], [10, 5], [15, 15]])

ekf = run_localization(
    landmarks, std_vel=0.1, std_steer=np.radians(1),
    std_range=0.3, std_bearing=0.1)
print('Final P:', ekf.P.diagonal())
```

## References

* Roger R Labbe, Kalman and Bayesian Filters in Python
(https://github.com/rlabbe/Kalman-and-Bayesian-Filters-in-Python/blob/master/11-Extended-Kalman-Filters.ipynb)
* https://blog.naver.com/jewdsa813/222200570774