# Linear Support Vector Machine (SVM)

We've seen how to frame a problem as a differentiable program in the optimising functions example.
Now we can take a look a more usable example; a linear Support Vector Machine (SVM). Note that the model and loss used
in this guide are based on the code found [here](https://github.com/kazuto1011/svm-pytorch).

**Note**: The easiest way to use this tutorial is as a colab notebook, which allows you to dive in with no setup. We recommend you enable a free GPU with

> **Runtime**   →   **Change runtime type**   →   **Hardware Accelerator: GPU**

## Install Torchbearer

First we install torchbearer if needed.

```python
try:
    import torchbearer
except:
    !pip install -q torchbearer
    import torchbearer

print(torchbearer.__version__)
```

    0.3.2

## SVM Recap

Recall that an SVM tries to find the maximum margin hyperplane which separates the data classes. For a soft margin SVM
where $\textbf{x}$ is our data, we minimize:

\begin{equation}
\left[\frac 1 n \sum_{i=1}^n \max\left(0, 1 - y_i(\textbf{w}\cdot \textbf{x}_i - b)\right) \right] + \lambda\lVert \textbf{w} \rVert^2
\end{equation}

We can formulate this as an optimization over our weights $\textbf{w}$ and bias $b$, where we minimize the
hinge loss subject to a level 2 weight decay term. The hinge loss for some model outputs
$z = \textbf{w}\textbf{x} + b$ with targets $y$ is given by:

\begin{equation}
\ell(y,z) = \max\left(0, 1 - yz \right)
\end{equation}

## Defining the Model

Let's put this into code. First we can define our module which will project the data through our weights and offset by
a bias. Note that this is identical to the function of a linear layer.

```python
import torch.nn as nn

class LinearSVM(nn.Module):
    """Support Vector Machine"""

    def __init__(self):
        super(LinearSVM, self).__init__()
        self.w = nn.Parameter(torch.randn(1, 2), requires_grad=True)
        self.b = nn.Parameter(torch.randn(1), requires_grad=True)

    def forward(self, x):
        h = x.matmul(self.w.t()) + self.b
        return h
```

Next, we define the hinge loss function:

```python
import torch

def hinge_loss(y_pred, y_true):
    return torch.mean(torch.clamp(1 - y_pred.t() * y_true, min=0))
```

Creating Synthetic Data
-----------------------------------------------

Now for some data, 1024 samples should do the trick. We normalise here so that our random init is in the same space as
the data:

```python
import numpy as np
from sklearn.datasets.samples_generator import make_blobs

X, Y = make_blobs(n_samples=1024, centers=2, cluster_std=1.2, random_state=1)
X = (X - X.mean()) / X.std()
Y[np.where(Y == 0)] = -1
X, Y = torch.FloatTensor(X), torch.FloatTensor(Y)
```

Visualizing the Training
----------------------------------------

We now aim to create a nice visualisation, such as the one below.

The code for the visualisation (using [pyplot](https://matplotlib.org/api/pyplot_api.html)) is a bit ugly but we'll
try to explain it to some degree. First, we need a mesh grid `xy` over the range of our data:

```python
delta = 0.01
x = np.arange(X[:, 0].min(), X[:, 0].max(), delta)
y = np.arange(X[:, 1].min(), X[:, 1].max(), delta)
x, y = np.meshgrid(x, y)
xy = list(map(np.ravel, [x, y]))
```

Now things get a little strange. We start by evaluating our model over the mesh grid from earlier.

For our outputs $z \in \textbf{Z}$, we can make some observations about the decision boundary. First, that we are
outside the margin if $z \lt -1$ or $z \gt 1$. Conversely, we are inside the margine where $z \gt -1$
or $z \lt 1$.

The next bit is a bit of a hack to get the update of the contour plot working. If a reference to the plot is already in state we just remove the old one and add a new one, otherwise we add it and show the plot. Finally, we call mypause to trigger an update. You could just use plt.pause, however, it grabs the mouse focus each time it is called which can be annoying. Instead, mypause is taken from stackoverflow.

This whole process is shown in the callback below:

```python
from torchbearer import callbacks

%matplotlib notebook
import matplotlib
import matplotlib.pyplot as plt

@callbacks.on_step_training
@callbacks.only_if(lambda state: state[torchbearer.BATCH] % 10 == 0)
def draw_margin(state):
    w = state[torchbearer.MODEL].w[0].detach().to('cpu').numpy()
    b = state[torchbearer.MODEL].b[0].detach().to('cpu').numpy()

    z = (w.dot(xy) + b).reshape(x.shape)
    z[np.where(z > 1.)] = 4
    z[np.where((z > 0.) & (z <= 1.))] = 3
    z[np.where((z > -1.) & (z <= 0.))] = 2
    z[np.where(z <= -1.)] = 1

    plt.clf()
    plt.scatter(x=X[:, 0], y=X[:, 1], c="black", s=10)
    plt.contourf(x, y, z, cmap=plt.cm.jet, alpha=0.5)
    fig.canvas.draw()
```

Subgradient Descent
----------------------------------------------

Since we don't know that our data is linearly separable, we would like to use a soft-margin SVM. That is, an SVM for
which the data does not all have to be outside of the margin. This takes the form of a weight decay term,
$\lambda\lVert \textbf{w} \rVert^2$ in the above equation. This term is called weight decay because the gradient
corresponds to subtracting some amount ($2\lambda\textbf{w}$) from our weights at each step. With torchbearer we
can use the `L2WeightDecay` callback to do this. This whole process is known as subgradient descent because we
only use a mini-batch (of size 32 in our example) at each step to approximate the gradient over all of the data. This is
proven to converge to the minimum for convex functions such as our SVM. At this point we are ready to create and train
our model:

```python
from torchbearer import Trial
from torchbearer.callbacks import L2WeightDecay, ExponentialLR

import torch.optim as optim

device = 'cuda' if torch.cuda.is_available() else 'cpu'

fig = plt.figure(figsize=(5, 5))

svm = LinearSVM()
model = Trial(svm, optim.SGD(svm.parameters(), 0.1), hinge_loss, ['loss'],
              callbacks=[draw_margin, ExponentialLR(0.999, step_on_batch=True), L2WeightDecay(0.01, params=[svm.w])]).to(device)
model.with_train_data(X, Y, batch_size=32)
model.run(epochs=50, verbose=1)

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

    <IPython.core.display.Javascript object>

    HBox(children=(IntProgress(value=0, max=50), HTML(value='')))

Final Comments
------------------------------------

So, there you have it, a fun differentiable programming example with a live visualisation in under 100 lines of code
with torchbearer. It's easy to see how this could become more useful, perhaps finding a way to use the kernel trick with
the standard form of an SVM (essentially an RBF network). You could also attempt to write some code that saves the gif
from earlier. We had some but it was beyond a hack, can you do better?


# 2021-11-05 Galerkin methods

## Last time

* Project discussion
* Weak forms

## Today

* Variational forms
* Finite element definition
* From prime to nodal bases
* Galerkin method

```julia
using Plots
using LinearAlgebra
```

# Integration by parts

## One dimension

> it's the product rule backwards

\begin{align} \int_a^b d(uv) &= \int_a^b u dv + \int_a^b v du \\
(uv)_a^b &= \int_a^b u dv + \int_a^b v du \\
\int_a^b u dv &= (uv)_a^b - \int_a^b v du
\end{align}

> you can move the derivative to the other term; it'll cost you a minus sign and a boundary term

## Multiple dimensions

\begin{align}
\int_\Omega v \nabla\cdot \mathbf f = -\int_\Omega \nabla v \cdot \mathbf f + \int_{\partial \Omega} v \mathbf f \cdot \mathbf n
\end{align}

## Strong form

$$ -\nabla\cdot(\kappa \nabla u) = 0 $$

## Weak form
* multiply by a test function and integrate by parts

\begin{align} -\int_\Omega v \nabla\cdot(\kappa \nabla u) = 0, \forall v \\
\int_\Omega \nabla v \cdot \kappa \nabla u - \int_{\partial\Omega} v \underbrace{\kappa \nabla u \cdot \mathbf n}_{\text{boundary condition}} = 0
\end{align}

# Variational: from minimization to weak forms

## Minimization

Find $u\in \mathcal V$ such that

\begin{align}
u &= \arg\min \int_\Omega \frac 1 p \lVert \nabla u \rVert^p \\
&= \arg\min \int_\Omega \frac 1 p (\nabla u \cdot \nabla u)^{p/2}
\end{align}

## Variations

> If $f(x)$ is a minimum, then $df = f'(x) dx = 0$ for all $dx$.

So if $u \in \mathcal V$ is a minimizer, then
\begin{align}
\int_\Omega \underbrace{\lVert \nabla u \rVert^{p-2}}_{\kappa(u)} \nabla u \cdot \nabla du = 0
\end{align}
for all $du \in \mathcal V$.

* It's common to use $v$ in place of $du$.
* Our discretization will reflect this structure

$$ v_h^T F(u_h) \sim \int_\Omega \nabla v \cdot \kappa(u) \cdot \nabla u $$

# Finite elements

## Problem statement

Find $u \in \mathcal V$ such that
\begin{align} \int_\Omega \nabla v \cdot \kappa \nabla u - \int_{\partial\Omega} v \underbrace{\kappa \nabla u \cdot \mathbf n}_{\text{boundary condition}} = 0, \forall v \in \mathcal V
\end{align}

### What do we need?
* A way to integrate
* A space $\mathcal V$
* Boundary conditions

## Ciarlet (1978) definition

A **finite element** is a triple $(K, P, N)$ where
* $K$ is a bounded domain with piecewise smooth boundary
* $P = \{p_j\}_1^k$ is a finite-dimensional function space over $K$ ("prime basis")
* $N = \{n_i\}_1^k$ is a basis for the dual space ("nodes")

It is common for nodes to be pointwise evaluation
$$ \langle n_i, p_j \rangle = p_j(x_i) $$

### First: choose a single element $K = \Omega$

# Quadrature

Our elements will be a line segment and we'll define quadrature on the standard line segment, $\hat K = (-1, 1)$. A quadrature is a set of points $q_i$ and weights $w_i$ such that
$$ \int_{-1}^1 f(x) \approx \sum_{i=1}^n w_i f(q_i) $$

```julia
default(linewidth=4)
function quad_trapezoid(n)
    q = LinRange(-1, 1, n)
    w = 2 * ones(n) / (n - 1)
    w[[1,end]] ./= 2
    q, w
end

function plot_quad_accuracy(f, F, quad)
    ns = 2 .^ (2:7)
    exact = F(1) - F(-1)
    function err(n)
        q, w = quad(n)
        w' * f.(q) - exact
    end
    errors = [abs.(err(n)) for n in ns]
    plot(ns, errors, marker=:auto, label=quad)
    plot!(ns, ns.^(-2), xscale=:log10, yscale=:log10)
end
```

    plot_quad_accuracy (generic function with 1 method)

```julia
plot_quad_accuracy(exp, exp, quad_trapezoid)
plot_quad_accuracy(x -> 3*x^2, x -> x^3, quad_trapezoid)
```

# Integration via polynomial interpolation

* Pick some points $x_i$
* Evaluate the function there $f_i = f(x_i)$
* Find the polynomial that interpolates the data
* Integrate the polynomial

## What order of accuracy can we expect?
* What degree polynomials can be integrate exactly?

# Doing better: Gauss quadrature

Suppose a polynomial on the interval $[-1,1]$ can be written as

$$ P_n(x) q(x) + r(x) $$

where $P_n(x)$ is the $n$th Legendre polnomials and both $q(x)$ and $r(x)$ are polynomials of maximum degree $n-1$.

* Why is $\int_{-1}^1 P_n(x) q(x) = 0$?
* Can every polynomials of degree $2n-1$ be written in the above form?
* How many roots does $P_n(x)$ have on the interval?
* Can we choose points $\{x_i\}$ such that the first term is 0?

If $P_n(x_i) = 0$ for each $x_i$, then we need only integrate $r(x)$, which is done exactly by integrating its interpolating polynomial.  How do we find these roots $x_i$?

# Gauss-Legendre in code

1. Solve for the points, compute the weights
  * Use a Newton solver to find the roots. You can use the recurrence to write a recurrence for the derivatives.
  * Create a Vandermonde matrix and extract the first row of the inverse or (using more structure) the derivatives at the quadrature points.
2. Use duality of polynomial roots and matrix eigenvalues.
  * A fascinating mathematical voyage (topic of graduate linear algebra class).

```julia
function gauss_legendre(n)
    """Gauss-Legendre integration using Golub-Welsch algorithm"""
    beta = @. .5 / sqrt(1 - (2 * (1:n-1))^(-2))
    T = diagm(-1 => beta, 1 => beta)
    D, V = eigen(T)
    w = V[1,:].^2 * 2
    q = D
    q, w
end
```

    gauss_legendre (generic function with 1 method)

```julia
plot_quad_accuracy(exp, exp, gauss_legendre)
plot_quad_accuracy(x -> 11*x^10, x -> x^11, gauss_legendre)
```

# Legendre polynomials

```julia
function vander_legendre(x, k=nothing)
    if isnothing(k)
        k = length(x) # Square by default
    end
    m = length(x)
    Q = ones(m, k)
    Q[:, 2] = x
    for n in 1:k-2
        Q[:, n+2] = ((2*n + 1) * x .* Q[:, n+1] - n * Q[:, n]) / (n + 1)
    end
    Q
end
```

    vander_legendre (generic function with 2 methods)

```julia
x = LinRange(-1, 1, 50)
P = vander_legendre(x, 6)
plot(x, P)
```

# Prime to nodal basis

A **nodal basis** $\{ \phi_j(x) \}$ is one that satisfies
$ n_i(\phi_j) = \delta_{ij} . $

We write $\phi_j$ in the prime basis by solving with the generalized Vandermonde matrix $V_{ij} = \langle n_i, p_j \rangle$,
$$ \phi_j(x) = \sum_k p_k(x) (V^{-1})_{k,j} . $$

```julia
k = 10
xn = LinRange(-1, 1, k)
V = vander_legendre(xn)
xx = LinRange(-1, 1, 50)
Pxx = vander_legendre(xx, k)
Pn = Pxx / V
plot(xx, Pn)
scatter!(xn, one.(xn))
```

```julia
using FastGaussQuadrature
xn, _ = gausslobatto(k)
V = vander_legendre(xn)
Pn = Pxx / V
plot(xx, Pn)
scatter!(xn, one.(xn))
```

# Galerkin method: $L^2$ projection

A nice test problem that doesn't require derivatives or boundary conditions:
Find $u \in \mathcal V_h$ such that
$$ \int_{-1}^1 v(x) \big[ u(x) - f(x) \big] = 0, \quad \forall v \in \mathcal V_h$$

```julia
function febasis(P, Q)
    x, _ = gausslobatto(P)
    q, w = gausslegendre(Q)
    Pk = vander_legendre(x)
    Pq = vander_legendre(q, P)
    B = Pq / Pk
    x, q, w, B
end

function L2_galerkin(P, Q, f)
    x, q, w, B = febasis(P, Q)
    A = B' * diagm(w) * B
    rhs = B' * diagm(w) * f.(q)
    u = A \ rhs
    x, u
end
```

    L2_galerkin (generic function with 1 method)

```julia
f(x) = tanh(5*x)
x, u = L2_galerkin(20, 20, f)
error = u - f.(x)
plot(x, u, marker=:auto, legend=:bottomright)
plot!(f, title="Galerkin error: $(norm(error))")
```

# Convergence of the Galerkin method

```julia
ns = 2 .^ (2:8)
function L2_error(n)
    x, u = L2_galerkin(n, n, f)
    norm(u - f.(x))
end
plot(ns, L2_error.(ns), marker=:auto, yscale=:log10)
```


# Aiyagari (1994) in Continuous Time

#### By [SeHyoun Ahn](http://www.princeton.edu/~sehyouna/) and [Benjamin Moll](http://www.princeton.edu/~moll/)

The material in this notebook is based on [Achdou et al. (2015) "Heterogeneous Agent Models in Continuous Time"](http://www.princeton.edu/~moll/HACT.pdf) and follows closely the material in the paper's [online Appendix](http://www.princeton.edu/~moll/HACTproject/HACT_Numerical_Appendix.pdf). Additional codes (mainly in MATLAB) can be found [here](http://www.princeton.edu/~moll/HACTproject.htm). The code and its performance should be compared to [QuantEcon's discrete-time version of the Aiyagari model](http://quant-econ.net/py/aiyagari.html). The structure of the code in the present notebook is purposely kept as close as possible to that of the discrete-time code. The continuous-time code is considerably faster.

We begin by importing some packages

```python
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
from scipy import sparse
from scipy.sparse.linalg import spsolve
```

## Aiyagari Economy
The economy can be represented by the following system of equations which we aim to solve numerically:
$$
\begin{align*}
\rho v_1(a) &=  \max_c \ u(c) + v_1'(a)(wz_1 + ra - c) + \lambda_1(v_2(a) - v_1(a))\\
\rho v_2(a) &=  \max_c \ u(c) + v_2'(a)(wz_2 + ra - c) + \lambda_2(v_1(a) - v_2(a))\\
0 &= - \frac{d}{da}[s_1(a)g_1(a)] - \lambda_1 g_1(a) + \lambda_2 g_2(a)\\
0 &= - \frac{d}{da}[s_2(a)g_2(a)] - \lambda_2 g_2(a) + \lambda_1 g_1(a)\\
1 &= \int_{\underline{a}}^\infty g_1(a)da + \int_{\underline{a}}^\infty g_2(a)da\\
K &= \int_{\underline{a}}^\infty a g_1(a)da + \int_{\underline{a}}^\infty a g_2(a)da\\
r &= \alpha K^{\alpha-1} - \delta, \quad w=(1-\alpha)K^\alpha
\end{align*}
$$

where $z_1$ < $z_2$ and $s_j(a)=wz_j + ra -c_j(a)$ and $c_j(a) = (u')^{-1}(v_j(a))$ are optimal savings and consumption. Finally, there is a state constraint $a\geq \underline{a}$. The first order condition $u'(c_j(\underline{a}))=v'_j(\underline{a})$ still holds at the borrowing constraint. However, in order to respect the constraint we need $s_j(\underline{a}) = z_j + ra - c_j(\underline{a}) \geq 0$. Combining this with the FOC, the state constraint motivates a boundary condition
$$
\begin{align} v_j'(\underline{a}) \geq u'(z_j + r \underline{a}), \quad j=1,2 \end{align}
$$

We use a finite difference method, an in particular an "implicit upwind scheme." The details are explained in the paper's [online Appendix](http://www.princeton.edu/~moll/HACTproject/HACT_Numerical_Appendix.pdf). We here provide a brief summary. We approximate the functions $(v_1,v_2,g_1,g_2)$ at $I$ discrete points in the space dimension, $a_i,i=1,...,I$. We use equispaced grids, denote by $\Delta a$ the distance between grid points, and use the short-hand notation $v_{i,j} \equiv v_j(a_i)$ and so on. The derivative $v_{i,j}'=v_j'(a_i)$ is approximated with either a forward or a backward difference approximation
$$
\begin{align*}
v_j'(a_i) \approx \frac{v_{i+1,j} - v_{i,j}}{\Delta a} \equiv v_{i,j,F}' \\
v_j'(a_i) \approx \frac{v_{i-1,j} - v_{i,j}}{\Delta a} \equiv v_{i,j,B}'
\end{align*}
$$
An upwind scheme means that we approximate the derivative $v_j'(a_i)$ with a forward difference approximation whenever the drift of the state variable is positive and the backward difference approximation whenever it is negative. An implicit scheme is a particular way of iterating on the value function.

In a nutshell, the discretized HJB equation can be written as
$$\rho v = u + \mathbf{A} v$$
where $\mathbf{A}$ is $N \times N$ transition matrix with $N = 2 \times I$ and where $I$ is the number of wealth grid points. The matrix $\mathbf{A}$ depends on $v$, i.e. this is a nonlinear problem and we therefore need to iterate (this is where the implicit scheme comes in). The matrix $\mathbf{A}$ has the interpretation of a Poisson transition matrix (or "intensity matrix") on the discretized state space $(a_i,z_j)$.

Similarly, one can show that the discretized Kolmogorov Forward equation is
$$0 = \mathbf{A}^T g$$
which is an eigenvalue problem. That is, the discretized stationary distribution $g$ is the eigenvector corresponding to a zero eigenvalue of the transpose of the Poisson transition matrix $\mathbf{A}$. The transpose comes from the fact that the differential operator in the KF equation is the "adjoint" of the operator in the HJB equation. And an "adjoint" is the infinite-dimensional analogue of matrix transpose.

The matrix $\mathbf{A}$ is found from the discretized HJB equation. Skipping a number of steps it can be written as
$$
\begin{align*}
&\frac{v^{n+1}_{i,j} - v^{n}_{i,j}}{\Delta} + \rho v_{i,j}^{n+1} = u(c_{i,j}^n) + v_{i-1,j}^{n+1}x_{i,j}   +  v^{n+1}_{i,j} y_{i,j} + v_{i+1,j}^{n+1} z_{i,j} + v_{i,-j}^{n+1}\lambda_j \quad \mbox{where}\\
&x_{i,j} = -\frac{(s^n_{i,j,B})^-}{\Delta a},\\
&y_{i,j} = - \frac{(s^n_{i,j,F})^+}{\Delta a}  +  \frac{ (s^n_{i,j,B})^-}{\Delta a} - \lambda_j,\\
&z_{i,j} = \frac{(s^n_{i,j,F})^+}{\Delta a}
\end{align*}
$$
where $s^n_{i,j,F}$ and $s^n_{i,j,B}$ are the discretized optimal household savings at grid points $(a_i,z_j)$ using forward and backward approximations, and where for any number $x$, the notation $x^+$ means "the positive part of $x$", i.e. $x^+ = \max\{x,0\}$ and analogously $x^{-} = \min\{x,0\}$. This part is what makes it an upwind scheme.

This is a system of $2 \times I$ linear equations which can be written in matrix notation as:
\begin{equation}\frac{1}{\Delta}(v^{n+1} - v^n) + \rho v^{n+1} = u^n + \mathbf{A}^n v^{n+1} \end{equation}
where
$$\mathbf{A}^n = \left[\begin{matrix}y_{1,1} & z_{1,1} & 0 & \cdots & 0 & \lambda_1 & 0 & 0 & \cdots & 0 \\ x_{2,1} & y_{2,1} & z_{2,1} & 0 & \cdots & 0 & \lambda_1 & 0 & 0 & \cdots \\ 0 & x_{3,1} & y_{3,1} & z_{3,1} & 0 & \cdots & 0 & \lambda_1 & 0 & 0 \\ \vdots & \ddots & \ddots & \ddots & \ddots & \ddots & \ddots & \ddots & \ddots & \vdots \\ 0 & \ddots & \ddots & x_{I,1} & y_{I,1} & 0 & 0 & 0 & 0 & \lambda_1\\  \lambda_2 & 0 & 0 & 0 & 0 & y_{1,2} & z_{1,2} & 0 & 0 & 0 \\ 0 & \lambda_2 & 0 & 0 & 0 & x_{2,2} & y_{2,2} & z_{2,2} & 0 & 0  \\ 0 & 0 & \lambda_2 & 0 & 0 & 0 & x_{3,2}  & y_{3,2} & z_{3,2} & 0 \\ 0 & 0 & \ddots & \ddots & \ddots & \ddots & \ddots  & \ddots & \ddots & \ddots \\ 0 & \cdots & \cdots & 0 & \lambda_2 & 0 & \cdots  & 0 & x_{I,2} & y_{I,2}  \end{matrix}\right], \quad u^n = \left[\begin{matrix} u(c_{1,1}^n)\\ \vdots \\ \vdots \\ u(c_{I,1}^n)\\  u(c_{1,2}^n) \\ \vdots \\ \vdots \\ u(c_{I,2}^n)\end{matrix}\right]$$
This system can in turn be written as
$$
\begin{align*}\mathbf{B}^n v^{n+1} = b^n, \quad \quad \mathbf{B}^n = \left(\frac{1}{\Delta} + \rho\right)\mathbf{I} - \mathbf{A}^n, \quad  b^n = u^n + \frac{1}{\Delta}v^n \end{align*}
$$
This system of linear equations can be solved very efficiently using sparse matrix routines. In Python this is implemented with the function "spsolve()."

#### Summary of Algorithm
First consider the algorithm for solving the HJB equations. Guess $v^0_{i,j},i=1,...,I,j=1,2$ and for $n=0,1,2,...$ follow
1. Compute $(v^n_{i,j})'$ using the current guess of the value function and the upwind scheme (forward difference when drift is positive, backward difference when drift is negative)
2. Compute $c^n$ from $c_{i,j}^n = (u')^{-1}[(v_{i,j}^n)']$
3. Find $v^{n+1}$ by solving the system of linear equations involving the matrix $\mathbf{A}$ described above (implicit scheme)
4. If $v^{n+1}$ is close enough to $v^n$: stop. Otherwise, go to step 1.

After solving the HJB equations, solving the Kolmogorov Forward equation: simply solve the eigenvalue problem $0=\mathbf{A}^T g$ described above. That is, once the HJB equation is solved, we basically get the Kolmogorov Forward equation "for free."

#### Overview of Code
Given this overview of the algorithm, we now briefly describe the code. As in the discrete-time version, we define a "household" class. Household objects contain all the data relevant to solving a household's decision problem. The household class  contain:
* economic parameters (e.g., w, r)
* utility paramters (e.g., discount factor $\rho$)
* asset and skill level represented on a grid

The household's decision problem is solved by invoking the function solve_bellman() which solves the HJB equation given the relevant parameters saving the value-function as v.

We also include the stationary wealth distribution of households in the household object. This is a natural thing to do since this stationary distribution is computed using the household's decision rule. Hence, after computing the decision problem of households, the stationary distribution can be found by invoking compute_stationary_distribution()

The definition of the household class is given below.

```python
class Household(object):
    def __init__(self,
                 r=0.03,         # interest rate
                 w=1,            # wages
                 rho=0.04,       # discount factor
                 a_min=1e-10,    # minimum asset amount
                 pi=[[-0.1, 0.1], [0.1, -0.1]],  # poisson Jumps
                 z_vals=[0.1, 1.0],                # exogenous income states
                 a_max=40,
                 a_size=1000,    # number of asset grid points
                 delta=1000.0):
        # Initialize values, and set up grids over a and z
        self.r, self.w, self.rho = r, w, rho
        self.a_min, self.a_max, self.a_size = a_min, a_max, a_size
        self.da = (self.a_max-self.a_min)/(self.a_size-1)
        self.k = 10
        self.pi = np.asarray(pi)
        self.z_vals = np.asarray(z_vals)
        self.z_size = len(z_vals)
        self.a_vals = np.linspace(self.a_min, self.a_max, self.a_size)
        self.n = self.a_size * self.z_size
        self.delta = delta

        # Initial Guess of Value Function
        self.v = np.log(np.tile(self.a_vals,(self.z_size,1))*self.r
                        +self.w*np.tile(self.z_vals,(self.a_size,1)).transpose())/self.rho

        # Build skill_transition, the matrix summarizing transitions due to the Poisson income shocks
        # This is analogous to the Q matrix in the discrete time version of the QuantEcon Aiyagari model
        self.z_transition = sparse.kron(self.pi,sparse.eye(self.a_size))

        # Preallocation
        self.v_old = np.zeros((self.z_size,self.a_size))
        self.g = np.zeros((self.z_size,self.a_size))
        self.dv = np.zeros((self.z_size,self.a_size-1))
        self.cf = np.zeros((self.z_size,self.a_size-1))
        self.c0 = np.zeros((self.z_size,self.a_size))
        self.ssf = np.zeros((self.z_size,self.a_size))
        self.ssb = np.zeros((self.z_size,self.a_size))
        self.is_forward = np.zeros((self.z_size,self.a_size),'bool')
        self.is_backward = np.zeros((self.z_size,self.a_size),'bool')
        self.diag_helper = np.zeros((self.z_size,self.a_size))
        self.A = self.z_transition.copy()
        self.B = self.z_transition.copy()
        self.AT = self.z_transition.copy()

    def set_prices(self, r, w):
        """
        Resets prices
        Calling the method will resolves the Bellman Equation.

        Parameters:
        -----------------
        r : Interest rate
        w : wage
        """
        self.r, self.w = r, w
        self.solve_bellman()

    def reinitialize_v(self):
        """
        Reinitializes the value function if the value function
        became NaN
        """
        self.v = np.log(np.tile(self.a_vals,(self.z_size,1))*self.r
                        +self.w*np.tile(self.z_vals,(self.a_size,1)).transpose())/self.rho

    def solve_bellman(self,maxiter=100,crit=1e-6):
        """
        This function solves the decision problem with the given parameters

        Parameters:
        -----------------
        maxiter : maximum number of iteration before haulting value function iteration

        crit : convergence metric, stops if value function does not change more than crit
        """
        dist=100.0
        for i in range(maxiter):
            # compute saving and consumption implied by current guess for value function, using upwind method
            self.dv = (self.v[:,1:]-self.v[:,:-1])/self.da
            self.cf = 1.0/self.dv
            self.c0 = np.tile(self.a_vals,(self.z_size,1))*self.r \
                            +self.w*np.tile(self.z_vals,(self.a_size,1)).transpose()
            # computes savings with forward forward difference and backward difference
            self.ssf[:,:-1] = self.c0[:,:-1]-self.cf
            self.ssb[:,1:] = self.c0[:,1:]-self.cf
            # Note that the boundary conditions are handled implicitly as ssf will be zero at a_max and ssb at a_min
            self.is_forward = self.ssf>0
            self.is_backward = self.ssb<0
            # Update consumption based on forward or backward difference based on direction of drift
            self.c0[:,:-1] += (self.cf-self.c0[:,:-1])*self.is_forward[:,:-1]
            self.c0[:,1:] += (self.cf-self.c0[:,1:])*self.is_backward[:,1:]
            self.c0 = np.log(self.c0)

            # Build the matrix A that summarizes the evolution of the process for (a,z)
            # This is a Poisson transition matrix (aka intensity matrix) with rows adding up to zero
            self.A = self.z_transition.copy()
            self.diag_helper = (-self.ssf*self.is_forward/self.da \
                               + self.ssb*self.is_backward/self.da).reshape(self.n)
            self.A += sparse.spdiags(self.diag_helper,0,self.n,self.n)
            self.diag_helper = (-self.ssb*self.is_backward/self.da).reshape(self.n)
            self.A += sparse.spdiags(self.diag_helper[1:],-1,self.n,self.n)
            self.diag_helper = (self.ssf*self.is_forward/self.da).reshape(self.n)
            self.A += sparse.spdiags(np.hstack((0,self.diag_helper)),1,self.n,self.n)

            # Solve the system of linear equations corresponding to implicit finite difference scheme
            self.B = sparse.eye(self.n)*(1/self.delta + self.rho) - self.A
            self.b = self.c0.reshape(self.n,1) + self.v.reshape(self.n,1)/self.delta
            self.v_old = self.v.copy()
            self.v = spsolve(self.B,self.b).reshape(self.z_size,self.a_size)

            # Compute convergence metric and stop if it satisfies the convergence criterion
            dist = np.amax(np.absolute(self.v_old-self.v).reshape(self.n))
            if dist < crit:
                break

    def compute_stationary_distribution(self):
        """
        Solves for the stationary distribution given household decision rules

        Output:
        Capital level from the stationary distribution
        """
        self.AT = self.A.transpose().tocsr()

        # The discretized Kolmogorov Forward equation AT*g=0 is an eigenvalue problem
        # AT is singular because one of the equation is the distribution adding
        # up to 1. Here we solve the eigenvalue problem by setting g(1,1)=0.1
        # and the equation is solved relative to that value.
        # Alternatively, one could use a routine for solving eigenvalue problems.
        b = np.zeros((self.n,1))
        b[0] = 0.1
        self.AT.data[1:self.AT.indptr[1]] = 0
        self.AT.data[0] = 1.0
        self.AT.indices[0] = 0
        self.AT.eliminate_zeros()
        self.g = spsolve(self.AT,b).reshape(self.z_size,self.a_size)

        # Since g was solved taking one of g(1,1) as given, g needs to be
        # renormalized to add up to 1
        self.g = self.g/np.sum(self.g)
        return np.sum(self.g*(np.tile(self.a_vals,(self.z_size,1))))
```

For example, if interest rate is 0.05 and wage is 1, we can initialize a household, solve it decision problem, and find the stationary distribution by running

```python
am=Household(r=0.05,w=1)
am.solve_bellman()
am.compute_stationary_distribution()
```

    39.104796555465526

Once, a household object is created, the object can be reused to solve a different problem by changing parameters. For example, if the interest rate were to change to 0.03 and wage to 0.9, the new problem can be solved by setting parameters directly by

```python
am.r=0.03
am.w=0.9
```

and running

```python
am.solve_bellman()
am.compute_stationary_distribution()
```

    5.9052049733275469

Alternately, a helper function can be created to automate this process. For example, set_prices(r,w) function resets parameters, and solves the decision problem again. For example, you can run the following to the same effect.

```python
am.set_prices(r=0.03,w=0.9)
am.compute_stationary_distribution()
```

    5.9052051500667417

Given the household class, solving for the steady state becomes really simple. To find the equilibrium, we solve for the capital level that is consistent with household's decisions.

Before solving for the actual steady state, we can visualize the capital demand and capital supply.

```python
A = 2.5
N = 0.05
alpha = 0.33

def r_to_w(r):
    return A * (1 - alpha) * (alpha / (1 + r))**(alpha / (1 - alpha))

def rd(K):
    return A * alpha * (N / K)**(1 - alpha)

def prices_to_capital_stock(am, r):
    """
    Map prices to the induced level of capital stock.

    Parameters:
    ----------
    am : Household
        An instance of the Household class
    r : float
        The interest rate
    """
    w = r_to_w(r)

    # Set new prices and solve the Bellman equation
    am.set_prices(r, w)

    # Compute the stationary distribution and capital
    return am.compute_stationary_distribution()
```

We make a grid of interest rate points, and plot the resulting capital.

```python
num_points = 20
r_vals = np.linspace(0.0, 0.04, num_points)

# Compute supply of capital
k_vals = np.empty(num_points)
for i, r in enumerate(r_vals):
    k_vals[i] = prices_to_capital_stock(am,r)

# Plot supply and demand of capital
fig, ax = plt.subplots(figsize=(11, 8))
ax.plot(k_vals, r_vals, lw=2, alpha=0.6, label='supply of capital')
ax.plot(k_vals, rd(k_vals), lw=2, alpha=0.6, label='demand for capital')
ax.grid()
ax.set_xlabel('capital')
ax.set_ylabel('interest rate')
ax.legend(loc='upper right')
plt.show()
```

Finally, the equilibrium interest rate can be found by using the bisection method, and we can see the equilibrium distribution of assets.

```python
# Set parameters for bisection method
crit = 1e-6
r_min = 0.02
r_max = 0.04
r = 0.03

# Bisection loop
for i in range(100):
    am.set_prices(r,r_to_w(r))
    r_new = rd(am.compute_stationary_distribution())
    if np.absolute(r_new-r)<crit:
        break
    elif r_new > r:
        r_min = r
        r = (r_max+r_min)/2.
    else:
        r_max = r
        r = (r_max+r_min)/2.

# Plot stationary distribution at the equilibrium
fig, ax = plt.subplots(figsize=(11, 8))
n=500                      # Determine the max asset level to show in the plot
ax.plot(am.a_vals[0:n], am.g[0,0:n], lw=2, alpha=0.6, label='low income')
ax.plot(am.a_vals[0:n], am.g[1,0:n], lw=2, alpha=0.6, label='high income')
ax.grid()
ax.set_xlabel('asset position')
ax.set_ylabel('distribution')
ax.legend(loc='upper right')
plt.show()
```

```python

```


# Defining Custom Hamiltonians

In this tutorial, we will see how to define custom Hamiltonians in NetKet.

NetKet provides two ```operator``` classes for defining custom Hamiltonians.

1. ```netket.operator.GraphOperator```
2. ```netket.operator.LocalOperator```

We will explore both of these methods in this tutorial.

Let's start.

```python
# Import netket library
import netket as nk

# Helper libraries
import numpy as np
import matplotlib.pyplot as plt
```

## 1) GraphOperator

We shall use the ```GraphOperator``` to define the spin half $J_1$-$J_2$ model in one dimension. The Hamiltonian

$$ H = \sum_{i=1}^{L} J_{1}\vec{\sigma}_{i} \cdot \vec{\sigma}_{i+1} + J_{2} \vec{\sigma}_{i} \cdot \vec{\sigma}_{i+2} $$

Here $\vec{\sigma}=(\sigma^x,\sigma^y,\sigma^z)$ stand for the vector of Pauli matrices. Each term is the sum is an operator defined on the bonds of a graph. For example, we can define a graph with two types of edges: nearest-neighbour and next-nearest-neighbour. We can then specify a bond operator for each type of edge. This is exactly what we need to define the model above.

First, we need a custom graph ``nk.graph.CustomGraph``. To initialise the class we simply provide a list of edges in the ``[[site_i, site_j, edge_color], ...]``. In our example, we give the ```edge_color``` ```1``` for nearest-neighbour edges and the ```2``` for next-nearest-neighbour edges.

```python
# J1-J2 Model Parameters
J = [1, 0.2]
L = 20

# Define custom graph
edge_colors = []
for i in range(L):
    edge_colors.append([i, (i+1)%L, 1])
    edge_colors.append([i, (i+2)%L, 2])

# Define the netket graph object
g = nk.graph.CustomGraph(edge_colors)

# Printing out the graph information
print('This graph has', g.n_sites, 'sites')
print('with the following set of edges:', g.edges)
```

    This graph has 20 sites
    with the following set of edges: [[17, 19], [16, 18], [15, 17], [14, 16], [14, 15], [13, 15], [13, 14], [12, 14], [12, 13], [11, 13], [11, 12], [10, 12], [1, 19], [10, 11], [0, 19], [9, 11], [0, 18], [9, 10], [8, 10], [8, 9], [7, 9], [7, 8], [6, 8], [6, 7], [5, 7], [5, 6], [4, 6], [4, 5], [18, 19], [3, 5], [3, 4], [17, 18], [2, 4], [2, 3], [16, 17], [1, 3], [1, 2], [15, 16], [0, 2], [0, 1]]

Next, let's define the Hilbert space of our model. Note that we impose here to select only configurations with zero total magnetization:

```python
# Spin 1/2 based Hilbert Space
hi = nk.hilbert.Spin(s=0.5, total_sz=0.0, graph=g)
```

Now, we need to create the relevant bond operators. Since we have two type of terms in our Hamiltonian, we need two different bond operators. To do this, we simply express the interaction term as a matrix in the computational basis of the relevant local Hilbert space. In our case, since we are dealing with spin-half degrees of freedom, the local basis of bond is simply $\lvert \uparrow \uparrow \rangle$, $\lvert \uparrow \downarrow \rangle$, $\lvert \downarrow \uparrow \rangle$, $\lvert \downarrow \downarrow \rangle$. Since

\begin{equation}
\vec{\sigma}_{i} \cdot \vec{\sigma}_{i+1} = {\sigma}_{i}^{z}{\sigma}_{i+1}^{z} + {\sigma}_{i}^{x}{\sigma}_{i+1}^{x} + {\sigma}_{i}^{y}{\sigma}_{i+1}^{y}
\end{equation}

we just need to represent each term as kronecker product of the standard Pauli matrix.

```python
# Pauli Matrices
sigmaz = [[1, 0], [0, -1]]
sigmax = [[0, 1], [1, 0]]
sigmay = [[0, -1j], [1j, 0]]

# Bond Operator
interaction = np.kron(sigmaz, sigmaz) + np.kron(sigmax, sigmax) + np.kron(sigmay, sigmay)

bond_operator = [
    (J[0] * interaction).tolist(),
    (J[1] * interaction).tolist(),
]
bond_color = [1, 2]
```

The ```GraphOperator``` can now be defined simply by doing providing the list of bond operators and the corresponding list of bond colors.

```python
# Custom Graph Hamiltonian operator
op = nk.operator.GraphOperator(hi, bondops=bond_operator, bondops_colors=bond_color)
```

The Hamiltonian is now defined and one can proceed to perform variational Monte Carlo or supervised learning or exact diagonalisation. The code snippet below gives us the exact diagonalisation result for the ground state energy of our Hamiltonian.

```python
res = nk.exact.lanczos_ed(op, first_n=1, compute_eigenvectors=False)
print("Exact J1J2 ground state energy = {0:.3f}".format(res.eigenvalues[0]))
```

    Exact J1J2 ground state energy = -32.812

## 2) LocalOperator

The second method we can use is through the ```LocalOperator``` class. Let's use this method to define a one-dimensional spin half transverse field Ising model given by the Hamiltonian:

$$ {H} = -\sum_{i=1}^{L} {\sigma}_{i}^{z} {\sigma}_{i+1}^{z} + h \sum_{i=1}^{L} {\sigma}_{i}^{x}  $$.

As before, we start by defining the graph on which our degrees of freedom sit.

Once again, we use the ```CustomGraph```, but this time we do not need to provide our edges with colors. We simply provide it with the list of edges.

```python
h = 0.5
L = 16

edges = []
for i in range(L):
    edges.append([i, (i+1)%L])

g = nk.graph.CustomGraph(edges)
```

Next, we define the Hilbert space as earlier, except that this time we do not impose the constraint on total magnetization ```total_sz``` (as it is not a quantity conserved by this Hamiltonian this time)

```python
# Spin based Hilbert Space
hi = nk.hilbert.Spin(s=0.5, graph=g)
```

Now, we simply define a list of local operators corresponding to the different terms in the Hamiltonian and also  a list containing the sites on which the respective local operators acts on.

```python
# Pauli Matrices
sigmaz = np.array([[1, 0], [0, -1]])
sigmax = np.array([[0, 1], [1, 0]])

operators = []
sites = []

# Local Field term
for i in range(L):
    operators.append((h*sigmax).tolist())
    sites.append([i])

# Ising iteraction
for i in range(L):
    operators.append((-np.kron(sigmaz,sigmaz)).tolist())
    sites.append([i, (i+1)%L])
```

Then, we create the ```LocalOperator``` by proving the list of operators and the list of sites.

```python
op = nk.operator.LocalOperator(hi, operators=operators, acting_on=sites)
```

And finally we again compute the ground-state energy

```python
res = nk.exact.lanczos_ed(op, first_n=1, compute_eigenvectors=False)
print("Exact transverse Ising ground state energy = {0:.3f}".format(res.eigenvalues[0]))
```

    Exact transverse Ising ground state energy = -17.017


# Lecture 24 - Sequential Monte Carlo in `PyMC3`

```python
import numpy as np
import pymc3 as pm
import theano as T
from theano import shared, function, tensor as tt
from sample_smc import sample_smc

try:
    import sympy
except:
    _=!pip install sympy
    import sympy

import matplotlib.pyplot as plt
import seaborn as sns
sns.set()

import logging

%matplotlib inline
```

## Objectives

+ Compute the model evidence using `PyMC3`.
+ Do model selection with `PyMC3`.

## Sanity check - Does the calculation of the evidence with PySMC work?

Let
$$
p(\theta) = \mathcal{N}(\theta|0, 1),
$$
and
$$
p(y|\theta) = \mathcal{N}(y|\theta,0).
$$
The posterior of $\theta$ given $y$ is:
$$
p(\theta|y) = \frac{p(y|\theta)p(\theta)}{Z},
$$
where
$$
Z = \int_{-\infty}^{\infty} p(y|\theta)p(\theta)d\theta.
$$
Let's first calculate $Z$ analytically.

```python
import sympy.stats
sympy.init_printing()
y, t = sympy.symbols('y \\theta')
q = 1. / sympy.sqrt(2. * sympy.pi) * sympy.exp(-0.5 * (y - t) ** 2) * \
    1. / sympy.sqrt(2. * sympy.pi) * sympy.exp(-0.5 * t ** 2)
sympy.simplify(sympy.integrate(q, (t, -sympy.oo, sympy.oo)))
```

So, if the observed $y$ was zero, then the Z should be:
$$
Z = \frac{1}{2\sqrt{\pi}}.
$$

```python
Z = 1 /  2. / np.sqrt(np.pi)
print('log Z = {0:.3f}'.format(np.log(Z)))
```

    log Z = -1.266

All, right. Now, let's program this thing in pysmc and compare the results.

We start with the model:

```python
model = pm.Model()
yobs = 0.
with model:
    # prior over theta
    theta = pm.Normal('theta', mu=0., sigma=1.,testval=0.)

    # log likelihood
    llk = pm.Potential('llk', pm.Normal.dist(theta, 1.).logp(yobs))

    trace, smcres = sample_smc(1000)
```

    Sample initial stage: ...
    /Users/krishnan/Documents/uq-course/.venv/lib/python3.9/site-packages/pymc3/sampling.py:1925: UserWarning: The effect of Potentials on other parameters is ignored during prior predictive sampling. This is likely to lead to invalid or biased predictive samples.
      warnings.warn(
    Stage:   0 Beta: 1.000 Steps:  25 Acce: 1.000

```python
print(vars(smcres.model))
```

    {'_parent': None, '_theano_config': {'compute_test_value': 'raise'}, '_config_context': <theano.configparser._ChangeFlagsDecorator object at 0x10c7cdbb0>, 'name': '', 'coords': {}, 'RV_dims': {}, 'check_bounds': True, 'named_vars': {'theta': theta ~ Normal, 'llk': llk}, 'free_RVs': [theta ~ Normal], 'observed_RVs': [], 'deterministics': [], 'potentials': [llk], 'missing_values': [], 'theta': theta ~ Normal, 'llk': llk, 'marginal_log_likelihood': -1.2642191580115534, '_bijection_cache': LRUCache([((), <pymc3.blocking.DictToArrayBijection object at 0x12e6b2640>)], maxsize=128, currsize=1)}

```python
# get the model evidence
log_evidence_smc = smcres.model.marginal_log_likelihood
print('True log evidence: %.4f \nSMC log evidence: %.4f'%(np.log(Z), log_evidence_smc))
```

    True log evidence: -1.2655
    SMC log evidence: -1.2642

Which is close to the truth.

### Questions

+ Repeat the calculations above for a varying number of SMC particles. Start from 10 and go up to 10,000.

## Polynomial Regression

```python
def compute_design_matrix(X, phi):
    """
    Arguments:

    X   -  The observed inputs (1D array)
    phi -  The basis functions.
    """
    num_observations = X.shape[0]
    num_basis = phi.num_basis
    Phi = np.ndarray((num_observations, num_basis))
    for i in range(num_observations):
        Phi[i, :] = phi(X[i, :])
    return Phi

class PolynomialBasis(object):
    """
    A set of linear basis functions.

    Arguments:
    degree  -  The degree of the polynomial.
    """
    def __init__(self, degree):
        self.degree = degree
        self.num_basis = degree + 1
    def __call__(self, x):
        return np.array([x[0] ** i for i in range(self.degree + 1)])

class FourierBasis(object):
    """
    A set of linear basis functions.

    Arguments:
    num_terms  -  The number of Fourier terms.
    L          -  The period of the function.
    """
    def __init__(self, num_terms, L):
        self.num_terms = num_terms
        self.L = L
        self.num_basis = 2 * num_terms
    def __call__(self, x):
        res = np.ndarray((self.num_basis,))
        for i in range(num_terms):
            res[2 * i] = np.cos(2 * i * np.pi / self.L * x[0])
            res[2 * i + 1] = np.sin(2 * (i+1) * np.pi / self.L * x[0])
        return res

class RadialBasisFunctions(object):
    """
    A set of linear basis functions.

    Arguments:
    X   -  The centers of the radial basis functions.
    ell -  The assumed lengthscale.
    """
    def __init__(self, X, ell):
        self.X = X
        self.ell = ell
        self.num_basis = X.shape[0]
    def __call__(self, x):
        return np.exp(-.5 * (x - self.X) ** 2 / self.ell ** 2).flatten()
```

Let's generate some fake data.

```python
np.random.seed(12345)
def getdata(N, sigma2):
    X = 2 * np.random.rand(N) - 1.
    y = 0.5 * X  ** 3 - 0.3 * X ** 2 + np.sqrt(sigma2) * np.random.rand(N)
    return X, y

num_samples = 50
sigma2 = 1e-3
X, y = getdata(num_samples, sigma2)
plt.figure(figsize=(8, 6))
plt.plot(X, y, 'o', markeredgewidth=2, label='Data')
plt.xlabel('$x$', fontsize=20)
plt.ylabel('$y$', fontsize=20)
plt.legend(loc='best', fontsize=20)
plt.tight_layout()
```

We are going to implement a standard Bayesian linear regression and train it with `PyMC3`.
We will compute the evidence in order to select the best class of basis functions.
The model is as follows:

The output $y$ conditioned on the input $x$, the weights of the basis functions $w$ and
the noise variance $\sigma^2$ has likelihood:
$$
p(y|x,w,\sigma, \mathcal{M}) = \mathcal{N}(y|w^T\phi_{\mathcal{M}}(x), \sigma^2),
$$
where $\phi_{\mathcal{M},1}(\cdot), \dots, \phi_{\mathcal{M},m_{\mathcal{M}}}(\cdot)$ are the
$m_{\mathcal{M}}$ basis functions of the model $\mathcal{M}$.
We put a normal prior on the weights:
$$
p(w|\alpha) = \mathcal{N}(w|0, \alpha I_{m_{\mathcal{M}}}),
$$
and an inverse Gamma prior for $\sigma$ and $\alpha$:
$$
p(\sigma^2) = \mathrm{IG}(\sigma^2|1, 1),
$$
and
$$
p(\alpha) = \mathrm{IG}(\alpha|1,1).
$$

Assume that the data we have observed are:
$$
x_{1:n} = \{x_1,\dots,x_n\},\;\mathrm{and}\;y_{1:n} = \{y_1,\dots,y_n\}.
$$
Consider the design matrix $\Phi_{\mathcal{M}}\in\mathbb{R}^{n\times m}$:
$$
\Phi_{\mathcal{M},ij} = \phi_{\mathcal{M},j}(x_i).
$$
The likelihood of the data is:
$$
p(y_{1:n} | x_{1:n}, w, \sigma, \mathcal{M}) = \mathcal{N}(y_{1:n}|\Phi_{\mathcal{M}}w, \sigma^2I_n).
$$
Let's turn this into `PyMC3` code.

```python
def make_model(Phi, y):
    """
    INPUTS:
        Phi -> Design matrix.
        y   -> Target vector.

    RETURNS:
        model -> `pymc3.model` context.
    """
    num_data, num_features = Phi.shape

    # define the model
    with pm.Model() as model:
        # prior on the weights
        alpha = pm.InverseGamma('alpha', alpha=1., beta=1.)
        w = pm.Normal('w', mu=0., tau=alpha, shape=num_features)

        # prior on the likelihood noise variance
        sigma2 = pm.InverseGamma('sigma2', alpha=5., beta=0.1)

        # the data likelihood mean
        ymean = pm.Deterministic('ymean', tt.dot(Phi, w))

        # likelihood
        y = pm.Normal('y', ymean, sigma2, shape=num_data, observed=y)
        #llk = pm.Potential('llk', pm.Normal.dist(ymean, tt.sqrt(sigma2)).logp_sum(y))
    return model
```

Now, let's create a function that trains the model using pysmc for a polynomial basis with a given order.

```python
def fit_poly(phi, X, y, num_particles=100):
    """

    RETURNS:
        1. An instance of pymc3.Model for the SMC model.
        2. The SMC trace.
        3. An instance of pymc3.smc.SMC containing sampling information.
    """
    Phi = compute_design_matrix(X[:, None], phi)
    smcmodel = make_model(Phi, y)
    trace, res = sample_smc(draws=num_particles,
                         model=smcmodel,
                         progressbar=True,
                         threshold=0.8)
    return smcmodel, trace, res

phi = PolynomialBasis(3)
model, trace, res = fit_poly(phi, X, y)
```

    Sample initial stage: ...
    Stage:   0 Beta: 0.000 Steps:  25 Acce: 1.000

  <progress value='100' class='' max='100' style='width:300px; height:20px; vertical-align: middle;'></progress>
  100.00% [100/100 00:00<00:00]

    Stage:   1 Beta: 0.000 Steps:  25 Acce: 0.155

  <progress value='100' class='' max='100' style='width:300px; height:20px; vertical-align: middle;'></progress>
  100.00% [100/100 00:00<00:00]

    Stage:   2 Beta: 0.000 Steps:  25 Acce: 0.237

  <progress value='100' class='' max='100' style='width:300px; height:20px; vertical-align: middle;'></progress>
  100.00% [100/100 00:00<00:00]

    Stage:   3 Beta: 0.000 Steps:  17 Acce: 0.195

  <progress value='100' class='' max='100' style='width:300px; height:20px; vertical-align: middle;'></progress>
  100.00% [100/100 00:00<00:00]

    Stage:   4 Beta: 0.000 Steps:  21 Acce: 0.206

  <progress value='100' class='' max='100' style='width:300px; height:20px; vertical-align: middle;'></progress>
  100.00% [100/100 00:00<00:00]

    Stage:   5 Beta: 0.001 Steps:  19 Acce: 0.247

  <progress value='100' class='' max='100' style='width:300px; height:20px; vertical-align: middle;'></progress>
  100.00% [100/100 00:00<00:00]

    Stage:   6 Beta: 0.001 Steps:  16 Acce: 0.246

  <progress value='100' class='' max='100' style='width:300px; height:20px; vertical-align: middle;'></progress>
  100.00% [100/100 00:00<00:00]

    Stage:   7 Beta: 0.003 Steps:  16 Acce: 0.263

  <progress value='100' class='' max='100' style='width:300px; height:20px; vertical-align: middle;'></progress>
  100.00% [100/100 00:00<00:00]

    Stage:   8 Beta: 0.004 Steps:  15 Acce: 0.329

  <progress value='100' class='' max='100' style='width:300px; height:20px; vertical-align: middle;'></progress>
  100.00% [100/100 00:00<00:00]

    Stage:   9 Beta: 0.007 Steps:  11 Acce: 0.252

  <progress value='100' class='' max='100' style='width:300px; height:20px; vertical-align: middle;'></progress>
  100.00% [100/100 00:00<00:00]

    Stage:  10 Beta: 0.013 Steps:  15 Acce: 0.209

  <progress value='100' class='' max='100' style='width:300px; height:20px; vertical-align: middle;'></progress>
  100.00% [100/100 00:00<00:00]

    Stage:  11 Beta: 0.020 Steps:  19 Acce: 0.196

  <progress value='100' class='' max='100' style='width:300px; height:20px; vertical-align: middle;'></progress>
  100.00% [100/100 00:00<00:00]

    Stage:  12 Beta: 0.029 Steps:  21 Acce: 0.238

  <progress value='100' class='' max='100' style='width:300px; height:20px; vertical-align: middle;'></progress>
  100.00% [100/100 00:00<00:00]

    Stage:  13 Beta: 0.042 Steps:  16 Acce: 0.263

  <progress value='100' class='' max='100' style='width:300px; height:20px; vertical-align: middle;'></progress>
  100.00% [100/100 00:00<00:00]

    Stage:  14 Beta: 0.057 Steps:  15 Acce: 0.254

  <progress value='100' class='' max='100' style='width:300px; height:20px; vertical-align: middle;'></progress>
  100.00% [100/100 00:00<00:00]

    Stage:  15 Beta: 0.075 Steps:  15 Acce: 0.256

  <progress value='100' class='' max='100' style='width:300px; height:20px; vertical-align: middle;'></progress>
  100.00% [100/100 00:00<00:00]

    Stage:  16 Beta: 0.095 Steps:  15 Acce: 0.227

  <progress value='100' class='' max='100' style='width:300px; height:20px; vertical-align: middle;'></progress>
  100.00% [100/100 00:00<00:00]

    Stage:  17 Beta: 0.118 Steps:  17 Acce: 0.239

  <progress value='100' class='' max='100' style='width:300px; height:20px; vertical-align: middle;'></progress>
  100.00% [100/100 00:00<00:00]

    Stage:  18 Beta: 0.147 Steps:  16 Acce: 0.286

  <progress value='100' class='' max='100' style='width:300px; height:20px; vertical-align: middle;'></progress>
  100.00% [100/100 00:00<00:00]

    Stage:  19 Beta: 0.182 Steps:  13 Acce: 0.311

  <progress value='100' class='' max='100' style='width:300px; height:20px; vertical-align: middle;'></progress>
  100.00% [100/100 00:00<00:00]

    Stage:  20 Beta: 0.230 Steps:  12 Acce: 0.267

  <progress value='100' class='' max='100' style='width:300px; height:20px; vertical-align: middle;'></progress>
  100.00% [100/100 00:00<00:00]

    Stage:  21 Beta: 0.299 Steps:  14 Acce: 0.287

  <progress value='100' class='' max='100' style='width:300px; height:20px; vertical-align: middle;'></progress>
  100.00% [100/100 00:00<00:00]

    Stage:  22 Beta: 0.389 Steps:  13 Acce: 0.264

  <progress value='100' class='' max='100' style='width:300px; height:20px; vertical-align: middle;'></progress>
  100.00% [100/100 00:00<00:00]

    Stage:  23 Beta: 0.511 Steps:  15 Acce: 0.270

  <progress value='100' class='' max='100' style='width:300px; height:20px; vertical-align: middle;'></progress>
  100.00% [100/100 00:00<00:00]

    Stage:  24 Beta: 0.645 Steps:  14 Acce: 0.281

  <progress value='100' class='' max='100' style='width:300px; height:20px; vertical-align: middle;'></progress>
  100.00% [100/100 00:00<00:00]

    Stage:  25 Beta: 0.825 Steps:  13 Acce: 0.212

  <progress value='100' class='' max='100' style='width:300px; height:20px; vertical-align: middle;'></progress>
  100.00% [100/100 00:00<00:00]

    Stage:  26 Beta: 1.000 Steps:  19 Acce: 0.245

  <progress value='100' class='' max='100' style='width:300px; height:20px; vertical-align: middle;'></progress>
  100.00% [100/100 00:00<00:00]

### Postprocessing

Once you have the `trace` object for the SMC simulation you can apply all the standard postprocessing tools from `PyMC3` as usual.

Here's the posterior distribution over the weights precision and the the likelihood noise, $\alpha$ and $\sigma^2$ respectively:

```python
_=pm.plot_posterior(trace, var_names=['alpha', 'sigma2'])
```

Here's the posterior predictive mean of the output $y$, i.e., $\mathbb{E}[y|x, w, \sigma]$:

```python
ppsamples = pm.sample_posterior_predictive(model=model,
                               trace=trace, var_names=['ymean'])['ymean']
```

  <progress value='100' class='' max='100' style='width:300px; height:20px; vertical-align: middle;'></progress>
  100.00% [100/100 00:00<00:00]

```python
idx = np.argsort(X)
plt.figure(figsize=(10, 8))
plt.plot(X[idx], ppsamples.mean(0)[idx], linewidth=2.5, label='Posterior Predictive Mean' )
plt.plot(X, y, 'x', markeredgewidth=2.5, markersize=10, label='Observed data')

plt.legend(loc='best', fontsize=20)
plt.tight_layout()
```

SMC does a particle approximation of the posterior distribution. The particles themselves can be obtained from the `trace` object and the particle weights can be obtained from the  `res` object.

Recall that the approximate posterior distribution is of the form $p(\theta|\mathcal{D}) = \sum_{j=1}^{N} w_j \delta(\theta - \theta_j)$.

```python
particles_w = trace.w
particles_alpha = trace.alpha
particle_weights = res.weights   # <- these are the ws from the above equation
```

## Model comparison

Since SMC can approximate the model evidence it provides a principled way of comparing models. Let's compare 5 different polynomial regression models where we change the degree of the polynomial from 1 to 5.

```python
# Evaluate the evidence for the various degrees
log_Zs = []
D = [1, 2, 3, 4, 5]
for d in D:
    phi = PolynomialBasis(d)
    _, _, res = fit_poly(phi, X, y, num_particles=500)
    log_Z = np.log(res.model.marginal_likelihood)
    log_Zs.append(log_Z)
```

```python
for d, log_Z in zip(D, log_Zs):
    print('degree %d gives %.4f'%(d, log_Z))
```

    degree 1 gives 25.0212
    degree 2 gives 41.0753
    degree 3 gives 142.4574
    degree 4 gives 139.3597
    degree 5 gives 137.5729

```python
plt.figure(figsize=(10, 5))
_=plt.bar(D, log_Zs, width=0.3)
_=plt.xticks(D)
plt.xticks(fontsize=20)
plt.yticks(fontsize=20)
plt.xlabel('Polynomial degree', fontsize=20)
plt.ylabel('Model Evidence', fontsize=20)
plt.tight_layout()
```

## Questions
+ The model with degree 3 polynomials has the gradest evidence. However, the degree 4 and 5 seem also very plausible. Is this a problem for the theory of Bayesian model selection? What complicates things here, is that model 3 is included in model 4 which is included in model 5. This requires us to design special priors for the models being right. They have to be consistent in some sense. For example, if model 3 is right then model 4 must be right, etc.

+ Revisit the motorcycle dataset problem. Evaluate the model evidence for a 1) Polynomial basis; 2) a Fourier basis; and 3) a Radial basis function basis.

## Revisiting Challenger Disaster Problem (Model Selection)

```python
challenger_data = np.genfromtxt("challenger_data.csv", skip_header=1,
                                usecols=[1, 2], missing_values="NA",
                                delimiter=",")
# drop the NA values
challenger_data = challenger_data[~np.isnan(challenger_data[:, 1])]

# plot it, as a function of temperature (the first column)
print("Temp (F), O-Ring failure?")
print(challenger_data)
```

    Temp (F), O-Ring failure?
    [[66.  0.]
     [70.  1.]
     [69.  0.]
     [68.  0.]
     [67.  0.]
     [72.  0.]
     [73.  0.]
     [70.  0.]
     [57.  1.]
     [63.  1.]
     [70.  1.]
     [78.  0.]
     [67.  0.]
     [53.  1.]
     [67.  0.]
     [75.  0.]
     [70.  0.]
     [81.  0.]
     [76.  0.]
     [79.  0.]
     [75.  1.]
     [76.  0.]
     [58.  1.]]

```python
# plot it, as a function of temperature (the first column)
plt.figure(figsize=(12, 5))
plt.plot(challenger_data[:, 0], challenger_data[:, 1], 'ro',
         markersize=15)
plt.ylabel("Damage Incident?",fontsize=20)
plt.xlabel("Outside temperature (Fahrenheit)",fontsize=20)
plt.title("Defects of the Space Shuttle O-Rings vs temperature",
          fontsize=20)
plt.yticks([0, 1], fontsize=15)
plt.xticks(fontsize=15)
plt.tight_layout()
```

```python
# gather the data and apply preprocessing if any
temp = challenger_data[:, 0]
temp_scaled = (temp - np.mean(temp))/np.std(temp)
data = challenger_data[:, 1]

# instantiate the pymc3 model
challenger_model = pm.Model()

# define the graph
with challenger_model:
    # define the prior
    alpha = pm.Normal('alpha', mu=0., sigma=10.)
    beta = pm.Normal('beta', mu=0., sigma=10.)

    # get the probabilities of failure at each observed temp
    p = pm.Deterministic('p', 1./(1. + tt.exp(alpha + beta*temp_scaled)))

    # define the likelihood
    x = pm.Bernoulli('x', p=p, observed=data)
print("Challenger space shuttle disaster model:")
challenger_model
```

    Challenger space shuttle disaster model:

$$
            \begin{array}{rcl}
            \text{alpha} &\sim & \text{Normal}(\mathit{mu}=0.0,~\mathit{sigma}=10.0)\\\text{beta} &\sim & \text{Normal}(\mathit{mu}=0.0,~\mathit{sigma}=10.0)\\\text{p} &\sim & \text{Deterministic}(\text{Constant},~\text{Constant},~\text{alpha},~\text{beta},~\text{Constant})\\\text{x} &\sim & \text{Bernoulli}(\mathit{p}=\text{p})
            \end{array}
            $$

```python
num_particles = 500
trace, smc = sample_smc(model=challenger_model,
                           draws=num_particles,
                          threshold=0.8,
                          progressbar=True)
```

    Sample initial stage: ...
    Stage:   0 Beta: 0.000 Steps:  25 Acce: 1.000

      0%|          | 0/500 [00:00<?, ?it/s]
     24%|██▍       | 121/500 [00:00<00:00, 1204.65it/s]
     48%|████▊     | 239/500 [00:00<00:00, 1195.81it/s]
     72%|███████▏  | 360/500 [00:00<00:00, 1199.98it/s]
    100%|██████████| 500/500 [00:00<00:00, 1192.56it/s]
    Stage:   1 Beta: 0.011 Steps:  25 Acce: 0.549

    100%|██████████| 500/500 [00:00<00:00, 5654.91it/s]
    Stage:   2 Beta: 0.028 Steps:   5 Acce: 0.406

      0%|          | 0/500 [00:00<?, ?it/s]
    100%|██████████| 500/500 [00:00<00:00, 3532.90it/s]
    Stage:   3 Beta: 0.056 Steps:   8 Acce: 0.346

      0%|          | 0/500 [00:00<?, ?it/s]
    100%|██████████| 500/500 [00:00<00:00, 2808.64it/s]
    Stage:   4 Beta: 0.106 Steps:  10 Acce: 0.301

      0%|          | 0/500 [00:00<?, ?it/s]
     48%|████▊     | 240/500 [00:00<00:00, 2394.73it/s]
    100%|██████████| 500/500 [00:00<00:00, 2361.99it/s]
    Stage:   5 Beta: 0.182 Steps:  12 Acce: 0.275

      0%|          | 0/500 [00:00<?, ?it/s]
     41%|████      | 205/500 [00:00<00:00, 2048.77it/s]
    100%|██████████| 500/500 [00:00<00:00, 2041.58it/s]
    Stage:   6 Beta: 0.313 Steps:  14 Acce: 0.266

      0%|          | 0/500 [00:00<?, ?it/s]
     42%|████▏     | 212/500 [00:00<00:00, 2117.50it/s]
    100%|██████████| 500/500 [00:00<00:00, 2076.29it/s]
    Stage:   7 Beta: 0.529 Steps:  14 Acce: 0.250

      0%|          | 0/500 [00:00<?, ?it/s]
     39%|███▉      | 196/500 [00:00<00:00, 1959.82it/s]
    100%|██████████| 500/500 [00:00<00:00, 1943.86it/s]
    Stage:   8 Beta: 0.897 Steps:  15 Acce: 0.270

      0%|          | 0/500 [00:00<?, ?it/s]
     42%|████▏     | 209/500 [00:00<00:00, 2085.22it/s]
    100%|██████████| 500/500 [00:00<00:00, 2060.88it/s]
    Stage:   9 Beta: 1.000 Steps:  14 Acce: 0.261

      0%|          | 0/500 [00:00<?, ?it/s]
     38%|███▊      | 192/500 [00:00<00:00, 1912.39it/s]
    100%|██████████| 500/500 [00:00<00:00, 1860.62it/s]

```python
ppsamples = pm.sample_posterior_predictive(model=challenger_model,
                                           trace=trace,
                                           var_names=['p'])['p']
```

      0%|          | 0/500 [00:00<?, ?it/s]
    100%|██████████| 500/500 [00:00<00:00, 3612.98it/s]

```python
ppmean = ppsamples.mean(0)
pp_lower, pp_upper = np.percentile(ppsamples, axis=0, q=[2.5, 97.5])

plt.figure(figsize=(15, 8))
plt.plot(temp, data, 'ro', markersize=12, label='Observed data')
idx=np.argsort(temp)
plt.plot(temp[idx], ppmean[idx], linestyle='--', linewidth=2.5,
         label='Post. pred. mean prob.')
plt.fill_between(temp[idx], pp_lower[idx], pp_upper[idx],
                color='purple', alpha=0.25, label='95% Confidence')
plt.ylabel("Probability estimate",fontsize=20)
plt.xlabel("Outside temperature (Fahrenheit)",fontsize=20)
plt.title("Defects of the Space Shuttle O-Rings vs temperature",
          fontsize=20)
plt.yticks(np.arange(0., 1.01, 0.2), fontsize=20)
plt.xticks(fontsize=20)
plt.legend(loc='best', fontsize=20)
plt.tight_layout()
```

```python
logZ_temp = np.log(smc.model.marginal_likelihood)
```


# Aula 2

Importando matplotlib

```python
from matplotlib import pyplot as plt
```

```python
nyc_temp = [53.9, 56.3, 56.4, 53.4, 54.5, 56.8, 55.0, 55.3, 54.0, 56.7, 56.0, 56.1, 56.7]
years = range(2000, 2013)
plt.plot(years, nyc_temp, marker = "o")
```

```python
x1 = [38, 34, 39, 30, 45, 31]
plt.plot(x1, marker = "o")
```

```python
years = range(2000, 2013)
print(years [-1])
```

    2012

```python
# valores
x2 = [38, 34, 39, 30, 45, 31]

# series
label = [1999, 2000, 2001, 2002, 2003, 2004]

#Gerar gráfico
plt.plot(label, x2, marker = "o") ##,'o','p','x'
```

```python
list(np.random.randint(100, size = 10))
```

    [0, 53, 34, 23, 82, 85, 60, 44, 76, 90]

```python
np.random.randint(100, size = 100)
```

    array([ 3, 10, 68, 97, 75, 96, 19, 51, 33,  4, 37, 77, 51, 16, 11, 68, 14,
           95, 75, 72, 96, 19, 34, 84, 25, 34, 68,  1, 35, 46, 89, 12, 43, 31,
           77, 66, 47, 95, 95, 11,  9, 14, 27, 35, 13, 24, 87, 86, 47, 56,  1,
           47, 64, 57, 98, 36, 32, 53, 47, 29, 93, 98, 93,  4, 93, 70, 13, 35,
           41, 62, 14, 57, 78,  7, 33, 86, 51, 25, 41, 54,  6, 20, 83, 72, 61,
           28, 34, 63, 97, 97, 77, 37,  0, 52, 16, 19,  7,  5, 46, 65])

```python
#Fazendo uma função para calculo de valor presente liquido:
def npv_f(rate, cashflows):
    total = 0.0
    for i, cashflow in enumerate(cashflows):
        total += cashflow / (1 + rate) ** i
    print(total)
    plt.plot(cashflows, marker = 'o')
    return (total)
```

```python
#Calcular quantidade de parcelas, 50, vezes o valor da parcela, 1500, multiplicando pela taxa de 0.05 .
npv_f(0.05, [1500] * 50)
```

```python
#Fazendo um calculo para retornar o valor de parcela
def pmt_f(cashflows, rate):
    total = 0.0
    for i, cashflow in enumerate(cashflows):
        total += cashflow * ((1 + rate/100) ** i)
    plt.plot(cashflows, marker = 'o')
    return (total)
```

```python
pmt_f([1000, 2000, 3000], 5)
```

```python
#Utilizando operador while

cashflows = [550, -500, -500, -500, 1000]
r = 0.0
while (r < 1.0):
    r += 0.0001
    npv = npv_f(r, cashflows)
    if(abs(npv) <= 0.001):
        print(r)

```

```python
grade = 74
if grade >= 90:
    print('A')

elif grade >= 85:
    print('A-')

```

```python
import sys
```

```python
#Função que valida notas e classifica de acordo com parametro de entrada.
def notas(nota):
    if nota > 100 or nota <0:
        print("Error!\nNota inválida!\n")
    else:
        if nota >= 90:
            print('A+')
        elif nota >= 85:
            print('A-')
        elif nota >= 75:
            print('B+')
        elif nota >= 65:
            print('B-')
        elif nota >= 55:
            print('C+')
        else:
            print('D-')

```

```python
#Função que valida presença
def presencaAluno(total, presenca):
    if presenca > total:
        print('Presença maior que o permitido.')
    else:
        if presenca < ((total * 75) / 100 ):
            print("Aluno Reprovado!")
        else:
            print("Aluno aprovado!")
```

```python
#Verificando nota e presença
sair = False
while sair == False:
    n = int(input("Digite a nota: "))
    notas(n)
    p = int(input("Presença do aluno: "))
    presencaAluno(15, p)
    s = int(input("Deseja sair?\n1. Sim\n2. Não\n"))
    if s == 1 :
        sair = True
print("Você saiu do sistema.")
```

    Digite a nota: 90
    A+
    Presença do aluno: 8
    Aluno Reprovado!
    Deseja sair?
    1. Sim
    2. Não
    2
    Digite a nota: 102
    Error!
    Nota inválida!

    Presença do aluno: 15
    Aluno aprovado!
    Deseja sair?
    1. Sim
    2. Não
    1
    Você saiu do sistema.

```python
presencaAluno(15, 9)
```

    Aluno Reprovado!

# 2 parte

Aula de operadores

```python
#operador de igualdade
2 == 2
```

    True

```python
#operador de diferente
2 != 3
```

    True

```python
a = 3
b = 2
```

```python
a > 2 & b == 2
```

    True

```python
def curso(nota, presenca):
    if nota < 65 and presenca < 75:
        print('Aluno Reprovado por nota e falta.')

    elif nota <65 or presenca < 75:
        print('Aluno reprovado por nota ou falta')

    else:
        print("Aluno não reprovou")
```

```python
curso(nota = 60, presenca = 79)
```

    Aluno reprovado por nota ou falta

Criando Anti Join com python

```python
import pandas as pd
```

```python
def anti_join(x, y, on):
    #Retorna linhas em x que não estão presentes em y
    ans = pd.merge(left=x, right=y, how='left', indicator=True, on=on)
    ans = ans.loc[ans._merge == 'left_only', :].drop(columns='_merge')
    return ans

```

```python
def anti_join_all_cols(x, y):
    #Retorna colunas em x que não estão presentes em y
    assert set(x.columns.values) == set(y.columns.values)
    return anti_join(x, y, x.columns.tolist())
```

Introdução ao Sympy

```python
#Verificando se ja está instalado a biblioteca
from sympy.solvers import solve
```

```python
#Importando a biblioteca de Symbol
from sympy import Symbol
```

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

```python
solve(x ** 2 -1 + x, x)
```

    [-1/2 + sqrt(5)/2, -sqrt(5)/2 - 1/2]

```python
from sympy import symbols
from sympy.plotting import plot
x = symbols('x')
#Para alterar as curvas, modifique pelas variaveis p1 e p2, dentro plot.
p1 = plot(x**2, show=False)
p2 = plot(x, show=False)
p1.append(p2[0])
p1
# Plot object containing:
# [0]: cartesian line: x**2 for x over (-10.0, 10.0)
# [1]: cartesian line: x for x over (-10.0, 10.0)
p1.show()
print(p1)

```

Fazendo um gráfico 3D

```python
from sympy import symbols
from sympy.plotting import plot3d
x, y = symbols('x y')
```

```python
plot3d(x*y, (x, -5, 5), (y, -5, 5))
# Plot object containing:
# [0]: cartesian surface: x*y for x over (-5.0, 5.0) and y over (-5.0, 5.0)
```

```python
plot3d(x*y, -x*y, (x, -5, 5), (y, -5, 5))
# Plot object containing:
# [0]: cartesian surface: x*y for x over (-5.0, 5.0) and y over (-5.0, 5.0)
# [1]: cartesian surface: -x*y for x over (-5.0, 5.0) and y over (-5.0, 5.0)
```

```python
plot3d(x**4, -y+1, (x, -5, 5), (y, -5, 5))
```

```python
plot3d((x**2 + y**2, (x, -5, 5), (y, -5, 5)),
    (x*y, (x, -3, 3), (y, -3, 3)))
# Plot object containing:
# [0]: cartesian surface: x**2 + y**2 for x over (-5.0, 5.0) and y over (-5.0, 5.0)
# [1]: cartesian surface: x*y for x over (-3.0, 3.0) and y over (-3.0, 3.0)
```

```python
from sympy import symbols, cos, sin
from sympy.plotting import plot3d_parametric_surface
u, v = symbols('u v')
```

```python
plot3d_parametric_surface(cos(u + v), sin(u - v), u - v,
    (u, -5, 5), (v, -5, 5))
# Plot object containing:
# [0]: parametric cartesian surface: (cos(u + v), sin(u - v), u - v) for u over (-5.0, 5.0) and v over (-5.0, 5.0)
```


# TTI pure qP-wave equation implementation

The aim of this notebook is to show how to solve the pure qP-wave equation using the finite-difference (FD) scheme. The 2D TTI pure qP-wave equation can be written as ([Mu et al., 2020](https://library.seg.org/doi/10.1190/geo2019-0320.1))

where $q(\textbf{x},t)$ is an auxiliary wavefield, which is introduced for implementing the FD scheme.

First of all, it is necessary to import some Devito modules and other packages that will be used in the implementation.

```python
import numpy as np
from devito import (Function, TimeFunction, cos, sin, solve,
                    Eq, Operator, configuration, norm)
from examples.seismic import TimeAxis, RickerSource, Receiver, demo_model
from matplotlib import pyplot as plt
```

We will start with the definitions of the grid and the physical parameters $v_{p}, \theta, \epsilon, \delta$. For simplicity, we won't use any absorbing boundary conditions to avoid reflections of outgoing waves at the boundaries of the computational domain, but we will have boundary conditions (zero Dirichlet) at $x=0,nx$ and $z=0,nz$ for the solution of the Poisson equation. We use a homogeneous model. The model is discretized with a grid of $101 \times 101$ and spacing of 10 m. The $v_{p}, \epsilon, \delta$ and $\theta$ parameters of this model are 3600 m∕s, 0.23, 0.17, and 45°, respectively.

```python
# NBVAL_IGNORE_OUTPUT

shape   = (101,101) # 101x101 grid
spacing = (10.,10.) # spacing of 10 meters
origin  = (0.,0.)
nbl = 0  # number of pad points

model = demo_model('constant-tti', spacing=spacing, space_order=8,
                   shape=shape, nbl=nbl)

# initialize Thomsem parameters to those used in Mu et al., (2020)
model.update('vp', np.ones(shape)*3.6) # km/s
model.update('epsilon', np.ones(shape)*0.23)
model.update('delta', np.ones(shape)*0.17)
model.update('theta', np.ones(shape)*(45.*(np.pi/180.))) # radians
```

    Operator `pad_vp` ran in 0.01 s
    Operator `pad_epsilon` ran in 0.01 s
    Operator `pad_delta` ran in 0.01 s
    Operator `pad_theta` ran in 0.01 s

In cell below, symbols used in the PDE definition are obtained from the `model` object. Note that trigonometric functions proper of Devito are exploited.

```python
# Get symbols from model
theta = model.theta
delta = model.delta
epsilon = model.epsilon
m = model.m

# Use trigonometric functions from Devito
costheta  = cos(theta)
sintheta  = sin(theta)
cos2theta = cos(2*theta)
sin2theta = sin(2*theta)
sin4theta = sin(4*theta)
```

Accordingly to [Mu et al., (2020)](https://library.seg.org/doi/10.1190/geo2019-0320.1), the time sampling can be chosen as
$$
\Delta t < \frac{\Delta d}{\pi \cdot (v_{p})_{max}}\sqrt{\dfrac{1}{(1+\eta_{max}|\cos\theta-\sin\theta|_{max}^{2})}}
$$,

where $\eta_{max}$ denotes the maximum value between $|\epsilon|_{max}$ and $|\delta|_{max}$, $|cos\theta-sin\theta|_{max}$ is the maximum value of $|cos\theta-sin\theta|$.

```python
# NBVAL_IGNORE_OUTPUT

# Values used to compute the time sampling
epsilonmax = np.max(np.abs(epsilon.data[:]))
deltamax = np.max(np.abs(delta.data[:]))
etamax = max(epsilonmax, deltamax)
vmax = model._max_vp
max_cos_sin = np.amax(np.abs(np.cos(theta.data[:]) - np.sin(theta.data[:])))
dvalue = min(spacing)
```

The next step is to define the simulation time. It has to be small enough to avoid reflections from borders. Note we will use the `dt` computed below rather than the one provided by the property() function `critical_dt` in the `SeismicModel` class, as the latter only works for the coupled pseudoacoustic equation.

```python
# Compute the dt and set time range
t0 = 0.   #  Simulation time start
tn = 150. #  Simulation time end (0.15 second = 150 msec)
dt = (dvalue/(np.pi*vmax))*np.sqrt(1/(1+etamax*(max_cos_sin)**2)) # eq. above (cell 3)
time_range = TimeAxis(start=t0,stop=tn,step=dt)
print("time_range; ", time_range)
```

    time_range;  TimeAxis: start=0, stop=150.313, step=0.884194, num=171

In exactly the same form as in the [Cavity flow with Navier-Stokes]() tutorial, we will use two operators, one for solving the Poisson equation in pseudotime and one for advancing in time. But unlike what was done in such tutorial, in this case, we write the FD solution of the poisson equation in a manually way, without using the `laplace` shortcut and `solve` functionality (just to break up the routine and try to vary). The internal time loop can be controlled by supplying the number of pseudotime steps (`niter_poisson` iterations) as a `time` argument to the operator. A Ricker wavelet source with peak frequency of 20 Hz is located at center of the model.

```python
# NBVAL_IGNORE_OUTPUT

# time stepping
p = TimeFunction(name="p", grid=model.grid, time_order=2, space_order=2)
q = Function(name="q", grid=model.grid, space_order=8)

# Main equations
term1_p = (1 + 2*delta*(sintheta**2)*(costheta**2) + 2*epsilon*costheta**4)*q.dx4
term2_p = (1 + 2*delta*(sintheta**2)*(costheta**2) + 2*epsilon*sintheta**4)*q.dy4
term3_p = (2-delta*(sin2theta)**2 + 3*epsilon*(sin2theta)**2 + 2*delta*(cos2theta)**2)*((q.dy2).dx2)
term4_p = ( delta*sin4theta - 4*epsilon*sin2theta*costheta**2)*((q.dy).dx3)
term5_p = (-delta*sin4theta - 4*epsilon*sin2theta*sintheta**2)*((q.dy3).dx)

stencil_p = solve(m*p.dt2 - (term1_p + term2_p + term3_p + term4_p + term5_p), p.forward)
update_p = Eq(p.forward, stencil_p)

# Poisson eq. (following notebook 6 from CFD examples)
b = Function(name='b', grid=model.grid, space_order=2)
pp = TimeFunction(name='pp', grid=model.grid, space_order=2)

# Create stencil and boundary condition expressions
x, z = model.grid.dimensions
t = model.grid.stepping_dim

update_q = Eq( pp[t+1,x,z],((pp[t,x+1,z] + pp[t,x-1,z])*z.spacing**2 + (pp[t,x,z+1] + pp[t,x,z-1])*x.spacing**2 -
         b[x,z]*x.spacing**2*z.spacing**2) / (2*(x.spacing**2 + z.spacing**2)))

bc = [Eq(pp[t+1,x, 0], 0.)]
bc += [Eq(pp[t+1,x, shape[1]+2*nbl-1], 0.)]
bc += [Eq(pp[t+1,0, z], 0.)]
bc += [Eq(pp[t+1,shape[0]-1+2*nbl, z], 0.)]

# set source and receivers
src = RickerSource(name='src',grid=model.grid,f0=0.02,npoint=1,time_range=time_range)
src.coordinates.data[:,0] = model.domain_size[0]* .5
src.coordinates.data[:,1] = model.domain_size[0]* .5
# Define the source injection
src_term = src.inject(field=p.forward,expr=src * dt**2 / m)

rec  = Receiver(name='rec',grid=model.grid,npoint=shape[0],time_range=time_range)
rec.coordinates.data[:, 0] = np.linspace(model.origin[0],model.domain_size[0], num=model.shape[0])
rec.coordinates.data[:, 1] = 2*spacing[1]
# Create interpolation expression for receivers
rec_term = rec.interpolate(expr=p.forward)

# Operators
optime=Operator([update_p] + src_term + rec_term)
oppres=Operator([update_q] + bc)

# you can print the generated code for both operators by typing print(optime) and print(oppres)
```

The time steps are advanced through a Python loop where both operators `optime` and `oppres`are called. Note the use of module indices to get proper buffers. We set the number of iteration `niter_poisson` for approximating the solution to Poisson equation as 1200.

```python
# NBVAL_IGNORE_OUTPUT
psave =np.empty ((time_range.num,model.grid.shape[0],model.grid.shape[1]))
niter_poisson = 1200

# This is the time loop.
for step in range(0,time_range.num-2):
    q.data[:,:]=pp.data[(niter_poisson+1)%2,:,:]
    optime(time_m=step, time_M=step, dt=dt)
    pp.data[:,:]=0.
    b.data[:,:]=p.data[(step+1)%3,:,:]
    oppres(time_M = niter_poisson)
    psave[step,:,:]=p.data[(step+1)%3,:,:]
```

    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s
    Operator `Kernel` ran in 0.01 s

```python
# Some useful definitions for plotting if nbl is set to any other value than zero
nxpad,nzpad = shape[0] + 2 * nbl, shape[1] + 2 * nbl
shape_pad   = np.array(shape) + 2 * nbl
origin_pad  = tuple([o - s*nbl for o, s in zip(origin, spacing)])
extent_pad  = tuple([s*(n-1) for s, n in zip(spacing, shape_pad)])
```

We can plot equally spaced snaps (by `factor`) from the full history saved in `psave` using matplotlib.

```python
# NBVAL_IGNORE_OUTPUT

# Note: flip sense of second dimension to make the plot positive downwards
plt_extent = [origin_pad[0], origin_pad[0] + extent_pad[0],
              origin_pad[1] + extent_pad[1], origin_pad[1]]

# Plot the wavefields, each normalized to scaled maximum of last time step
kt = (time_range.num - 2) - 1
amax = 0.05 * np.max(np.abs(psave[kt,:,:]))

nsnaps = 10
factor = round(time_range.num/nsnaps)

fig, axes = plt.subplots(2, 5, figsize=(18, 7), sharex=True)
fig.suptitle("Snapshots", size=14)
for count, ax in enumerate(axes.ravel()):
    snapshot = factor*count
    ax.imshow(np.transpose(psave[snapshot,:,:]), cmap="seismic",
               vmin=-amax, vmax=+amax, extent=plt_extent)
    ax.plot(model.domain_size[0]* .5, model.domain_size[1]* .5, \
         'red', linestyle='None', marker='*', markersize=8, label="Source")
    ax.grid()
    ax.tick_params('both', length=2, width=0.5, which='major',labelsize=10)
    ax.set_title("Wavefield at t=%.2fms" % (factor*count*dt),fontsize=10)
for ax in axes[1, :]:
    ax.set_xlabel("X Coordinate (m)",fontsize=10)
for ax in axes[:, 0]:
    ax.set_ylabel("Z Coordinate (m)",fontsize=10)
```

## References

- **Least-squares reverse time migration in TTI media using a pure qP-wave equation** (2020)
<br> Xinru Mu, Jianping Huang, Jidong Yang, Xu Guo, and Yundong Guo
<br> Geophysics, Vol. 85, No. 4
<br> https://doi.org/10.1190/geo2019-0320.1


```python
import numpy as np
import sympy as sp
import pandas as pd
import math
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split

data=pd.read_csv('C:\\Users\\Utsav\\Desktop\\pulsar_prediction\\pulsar_stars.csv', sep=',',header=0)

pulsarData=data.values
pulsarData=np.array(pulsarData)
```

```python
#split the data

train, test = train_test_split(data, test_size=0.2)
pulsar=np.array(train)
train=np.array(train)
test=np.array(test)

print(len(test))
print(len(train))
#print(train)
```

    3580
    14318

```python
#copy labels of test & train data elsewhere
y_train=np.zeros(len(train))
y_test=np.zeros(len(test))
for i in range(len(test)):
    y_test[i]=test[i][8]
    test[i][8]=-1
#replace all test and train labels by -1
for i in range(len(train)):
    y_train[i]=train[i][8]
    train[i][8]=-1

```

```python
from sklearn.naive_bayes import GaussianNB
gnb = GaussianNB()
naiveBayes = gnb.fit(train, y_train)
y_pred = naiveBayes.predict(test)

```

```python
# calculate score

score=0
for i in range(len(test)):
    if y_pred[i]==y_test[i]:
        score=score+1
score=score*100/len(test)

print("Accuracy = " + "%.2f" % score + "%")

#calculate no of flase negatives
score=0

for i in range(len(test)):
    if y_pred[i]==0 and y_test[i]==1:
        score=score+1

#score=score*100/len(test)
percent = score/len(test)

print("No of false negatives = %d" % score)
print("No of positives in test data = %d" % sum(y_test))
print("No of positives predicted : %d" % sum(y_pred))
```

    Accuracy = 94.55%
    No of false negatives = 50
    No of positives in test data = 314
    No of positives predicted : 409

Own Implementation

```python
train=np.array(train)
NegativeCount=0
PositiveCount=0
for i in range(14318):
    if pulsar[i][8]==0:
        NegativeCount=NegativeCount+1
    if pulsar[i][8]==1:
        PositiveCount=PositiveCount+1
print(NegativeCount)
print(PositiveCount)
print(PositiveCount+NegativeCount)

positives=np.zeros((PositiveCount,9))
negatives=np.zeros((NegativeCount,9))

j=0
k=0

for i in range(14318):

    if(pulsar[i,8] == 1):
        positives[j,:] = pulsar[i,:]
        j=j+1
    if(pulsar[i,8] == 0):
        negatives[k,:] = pulsar[i,:]
        k=k+1

meanMatrixP = np.zeros(8)

for i in range(8):
    meanMatrixP[i] = np.mean(positives[:,i])

varMatrixP = np.zeros(8)

for i in range(8):
    varMatrixP[i] = np.var(positives[:,i])

meanMatrixNP = np.zeros(8)

for i in range(8):
    meanMatrixNP[i] = np.mean(negatives[:,i])

varMatrixNP = np.zeros(8)

for i in range(8):
    varMatrixNP[i] = np.var(negatives[:,i])

```

    12993
    1325
    14318

```python
print(meanMatrixP)
print(varMatrixP)
print(meanMatrixNP)
print(varMatrixNP)
```

    [56.46443396 38.68962562  3.15917187 15.78174757 50.15509686 56.67265959
      2.72705344 17.43102427]
    [ 902.9684607    65.5988982     3.56288583  201.32374846 2045.3054129
      390.33028281    9.28765632 2112.73912716]
    [116.59501991  47.34497034   0.21020221   0.38037675   8.87940773
      23.24627826   8.85398831 113.23625212]
    [3.06486622e+02 3.81434314e+01 1.12921431e-01 1.08675296e+00
     6.02750879e+02 2.74519094e+02 1.77832335e+01 1.11451932e+04]

```python
#make predictions:

y_pred1 = np.zeros(len(test))
print(sum(y_pred))

p_prob = PositiveCount/(PositiveCount+NegativeCount)
np_prob = NegativeCount/(PositiveCount+NegativeCount)
print(p_prob)
print(np_prob)

for i in range(len(test)):
    prod_pulsar=1
    for p in range(8):
        prod_pulsar = prod_pulsar*np.exp((-(test[i,p]-meanMatrixP[p])**2)/(2*varMatrixP[p]))/(np.sqrt(2*3.14*varMatrixP[p]))
    pulsar_prob = p_prob * (prod_pulsar)

    prod_nonpulsar=1
    for nonp in range(8):
        prod_nonpulsar = prod_nonpulsar*np.exp((-(test[i,nonp]-meanMatrixNP[nonp])**2)/(2*varMatrixNP[nonp]))/(np.sqrt(2*3.14*varMatrixNP[nonp]))
    nonpulsar_prob = np_prob * (prod_nonpulsar)

    if (pulsar_prob>=nonpulsar_prob):
        y_pred1[i] = 1
    else:
        y_pred1[i] = 0

```

    409.0
    0.09254085766168459
    0.9074591423383154

```python
# calculate score

score=0
for i in range(len(test)):
    if y_pred1[i]==y_test[i]:
        score=score+1
score=score*100/len(test)

print("Accuracy = " + "%.2f" % score + "%")

#calculate no of flase negatives
score=0

for i in range(len(test)):
    if y_pred1[i]==0 and y_test[i]==1:
        score=score+1

#score=score*100/len(test)
percent = score/len(test)

print("No of false negatives = %d" % score)
print("No of positives in test data = %d" % sum(y_test))
print("No of positives predicted : %d" % sum(y_pred1))
```

    Accuracy = 94.55%
    No of false negatives = 50
    No of positives in test data = 314
    No of positives predicted : 409

```python

```

```python

```