```python
from sympy.matrices import Matrix
import sympy as sp
import numpy as np
from Exercise import Exercise, MarkdownBlock

from helpers import *

import math
import matplotlib.pyplot as plt
import matplotlib as mpl
plt.rcParams.update({'font.size': 10})

from process_latex import process_sympy

try:
    from config import URL, TOKEN
except:
    None

# TODO: replace with supplied strings
Exercise.URL = URL
Exercise.TOKEN = TOKEN
```

```python
m = r"What is $@a + @b$?"

v = lambda: sp.randMatrix(r=2, c=1, min=0, max=10)

params = {}
params["a"] = v()
params["b"] = v()

e = Exercise(MarkdownBlock(m, params))
e.add_answer(expression=params["a"] + params["b"], correct=True, feedback="Correct!")

e.play()
```

    Published succesfully, preview at: https://www.mscthesis.nl/preview?id=6d55169d-460d-437e-a3b2-c06b73f76da1

```python
def generator():
    m = r"What is $@a + @b$?"

    v = lambda: sp.randMatrix(r=2, c=1, min=0, max=10)

    params = {}
    params["a"] = v()
    params["b"] = v()

    e = Exercise(MarkdownBlock(m, params))
    e.add_answer(params["a"] + params["b"], True, "Correct!")

    return e

Exercise.write_multiple(generator=generator, instances_count=10, id="vector_add")
```

```python
m = r"What is $@trace$?"

params = {}
params["trace"] = sp.Trace(sp.Matrix(np.arange(16).reshape((4,4))))

e = Exercise(MarkdownBlock(m, params))

ans = params["trace"].doit()

e.add_answer(ans, True, MarkdownBlock("Correct! $@trace$", params))
e.display()

e.write("trace")
e.play()
```

```python
m = r"What is $@inverse$?"

params = {}
params["inverse"] = sp.Inverse(sp.Matrix([[-1, 3/2], [1, -1]]))

e = Exercise(MarkdownBlock(m, params))

ans = params["inverse"].doit()
params["ans"] = ans

e.add_answer(ans, True, MarkdownBlock("Correct! $@inverse = @ans$", params))
e.add_default_feedback(MarkdownBlock("Correct! $@inverse = @ans$", params))
e.display()

e.write("inverse")
e.play()
```

```python
def MatrixVectorMultiplication(RandomExercise):
    def query():
        return r"What is ... "

    def random_params():
        return {
            'a': np.random.randint(0, 10)
            ...
        }

    def answer(params):
        return params['a'] + params['b']

    def give_feedback(answer):
        if answer == 16:
            return "idiot"
```

```python
def generator():
    m = r"What is $@a + @b$?"

    params = {}
    params["a"] = np.random.randint(0, 10)
    params["b"] = np.random.randint(10, 20)
    params["ans"] = params["a"] + params["b"]

    e = Exercise(MarkdownBlock(m, params))

    e.add_answer(params["ans"], True, MarkdownBlock("Indeed $@a + @b = @ans$", params))

    return e

Exercise.write_multiple(generator=generator, instances_count=100, id="int_param")
```

```python
from sklearn.datasets import load_digits
digits = load_digits()

sorted_indices = np.argsort(digits.target)
nums = digits.images[sorted_indices]
```

```python
def to_binary(array):
    return np.where(array > 7, 1, 0)

def show(array, filename, binary = True):
    data = to_binary(array) if binary else array
    fig = plt.figure()
    plt.matshow(data, cmap='gray_r')
    plt.axis('off')
    plt.savefig("{}.png".format(filename), dpi=200, bbox_inches='tight')
    plt.close(fig)

# f, axarr = plt.subplots(1,10)
# unit = math.floor(len(nums)/10)
# start_offset = math.floor((1/2)*unit)
# for i in range(0, 10):
#     axarr[i].axis('off')
#     axarr[i].matshow(np.where(nums[start_offset + i*unit + 1] > 7, 1, 0), cmap="gray_r")

# plt.savefig("all_digits.png", dpi=500, bbox_inches='tight')
# show(nums[7], "zero")
# show(nums[1500], "eight")

def matrix_to_image(matrix, filename, grid=True, values_in_cells=False, axis_indices=True, axis_titles=False):
    fig, ax = plt.subplots()

    if grid:
        # Minor ticks
        ax.set_xticks(np.arange(-.5, 10, 1), minor=True)
        ax.set_yticks(np.arange(-.5, 10, 1), minor=True)
        ax.grid(which='minor', color='black', linestyle='-', linewidth=2)
    else:
        plt.axis('off')

    if values_in_cells:
        import matplotlib.patheffects as pe
        for (i, j), z in np.ndenumerate(matrix):
            value = ""
            try:
                value = int(z)
            except:
                value = ""
            ax.text(j, i, '{}'.format(value), ha='center', va='center', path_effects=[pe.withStroke(linewidth=4, foreground="white")])

    if axis_indices:
        ax.set_xticklabels([i for i in range(0, 9)])
        ax.set_yticklabels([i for i in range(0, 9)])

    if axis_titles:
        ax.set_xlabel("column index")
        ax.xaxis.set_label_position('top')

        ax.set_ylabel("row index")
        ax.yaxis.set_label_position('left')

    import copy
    cmap = copy.copy(mpl.cm.get_cmap("gray_r"))
    cmap.set_bad(color='red')
    # was .matshow
    ax.matshow(matrix, cmap=cmap)

    plt.savefig("{}.png".format(filename), dpi=200, bbox_inches='tight')
    plt.close(fig)

```

```python

```

```python
m = """
Given the matrices

<div class="d-flex flex-1 flex-items-center">
$A = $ , $B = $
</div>

#### Task
Frist, determine $D = |A - B|$, then compute $\sum_{i=1}^{} \sum_{j=1}{} d_{i,j}$
"""

f1 = """
Correct!

$D = $

$\sum D = @sum$
"""

f2 = """
<div class="d-flex flex-1 flex-items-center">
Hint: $D = $
</div>
"""
def generator():
    # to vector: .reshape(-1, 1)
    a = to_binary(nums[np.random.randint(1700)])
    b = to_binary(nums[np.random.randint(1700)])
    d = np.abs(a-b)
    matrix_to_image(a, "a", grid=True, values_in_cells=True, axis_indices=True)
    matrix_to_image(b, "b", grid=True, values_in_cells=True, axis_indices=True)
    matrix_to_image(d, "d", grid=True, values_in_cells=True, axis_indices=True)

    e = Exercise(m)

    ans = int(np.sum(d))
    e.add_answer(expression=ans, correct=True, feedback=MarkdownBlock(md=f1, params=dict(sum=ans)))
    e.add_answer(expression=ans-1, correct=False, feedback="You are close, please check your answer")
    e.add_answer(expression=ans+1, correct=False, feedback="You are close, please check your answer")
    e.add_default_feedback(feedback=f2)
    return e

Exercise.write_multiple(generator, 10, "digit_matrices")
```

    <ipython-input-5-5b6071c22429>:45: UserWarning: FixedFormatter should only be used together with FixedLocator
      ax.set_xticklabels([i for i in range(0, 9)])
    <ipython-input-5-5b6071c22429>:46: UserWarning: FixedFormatter should only be used together with FixedLocator
      ax.set_yticklabels([i for i in range(0, 9)])

```python
m = """
Given the matrices

<div class="d-flex flex-1 flex-items-center">
$A = $ , $B = $
</div>

#### Task
Defining $D$ as $D = |A - B|$, compute $\sum_{j=1}{} d_{1,j}$
"""

f1 = """
Correct!

$D = $

$\sum_{j=1}{} d_{1,j} = @sum$
"""

f2 = """
<div class="d-flex flex-1 flex-items-center">
Hint: $D = $
</div>
"""
def generator():
    # to vector: .reshape(-1, 1)
    a = nums[np.random.randint(1700)]
    b = nums[np.random.randint(1700)]
    d = np.abs(a-b)
    matrix_to_image(a, "a", grid=True, values_in_cells=True, axis_indices=True)
    matrix_to_image(b, "b", grid=True, values_in_cells=True, axis_indices=True)
    matrix_to_image(d, "d", grid=True, values_in_cells=True, axis_indices=True)

    e = Exercise(m)

    ans = int(np.sum(d[0,:]))
    e.add_answer(expression=ans, correct=True, feedback=MarkdownBlock(md=f1, params=dict(sum=ans)))
    e.add_default_feedback(feedback=f2)
    return e

Exercise.write_multiple(generator, 5, "digit_vectors_grayscale")
```

    <ipython-input-14-0de57e82f782>:45: UserWarning: FixedFormatter should only be used together with FixedLocator
      ax.set_xticklabels([i for i in range(0, 9)])
    <ipython-input-14-0de57e82f782>:46: UserWarning: FixedFormatter should only be used together with FixedLocator
      ax.set_yticklabels([i for i in range(0, 9)])

```python
import matplotlib.patheffects as pe
def values_in_matrix(ax, array):
    for (i, j), z in np.ndenumerate(array):
        ax.text(j, i, '{}'.format(int(z)), ha='center', va='center', path_effects=[pe.withStroke(linewidth=4, foreground="white")])

from scipy import ndimage, misc
import matplotlib.pyplot as plt
fig = plt.figure(figsize=(10, 10))
# plt.gray()  # show the filtered result in grayscale
ax1 = fig.add_subplot(121)  # left side
ax2 = fig.add_subplot(122)  # right side
ascent = to_binary(nums[516])
result = ndimage.sobel(ascent, mode="constant", cval=0.5)
values_in_matrix(ax1, ascent)
values_in_matrix(ax2, result)
print(type(result))
ax1.imshow(ascent)
ax2.imshow(result)

plt.show()
```

```python
Exercise(r"What is $\begin{bmatrix} 1 \\ 2 \end{bmatrix} + \begin{bmatrix} 3 \\ 4 \end{bmatrix}$").play()
```

    Published succesfully, preview at: https://www.mscthesis.nl/preview?id=954a2a9d-d6c3-4412-b511-89bb47daae41

```python
v1 = sp.randMatrix(r=4, c=1, min=0, max=10)
sp.latex(v1)
f"Hello {v1}"
```

    'Hello Matrix([[2], [5], [8], [3]])'

```python
m = """
Place the following values at the right positions in a vector: $a_{@i2} = @v2, a_{@i3} = @v3, a_{@i4} = @v4, a_{@i1} = @v1$
"""
def generator():
    params = {}
    ans = np.zeros((4, 1), dtype=int)
    print(ans)
    for i in range(1, 5):
        params[f"i{str(i)}"] = i
        value = np.random.randint(0, 20)
        params[f"v{str(i)}"] = value
        ans[i-1,0] = value

    e = Exercise(MarkdownBlock(m, params))
    ans = sp.Matrix(np.array(ans).reshape(-1, 1))
    print(ans)
    e.add_answer(ans, True, "Indeed")
    return e

Exercise.write_multiple(generator, 10, "values_in_vector")

```

    [[0]
     [0]
     [0]
     [0]]
    Matrix([[7], [9], [9], [10]])
    [[0]
     [0]
     [0]
     [0]]
    Matrix([[4], [12], [8], [0]])
    [[0]
     [0]
     [0]
     [0]]
    Matrix([[10], [17], [8], [17]])
    [[0]
     [0]
     [0]
     [0]]
    Matrix([[7], [2], [2], [14]])
    [[0]
     [0]
     [0]
     [0]]
    Matrix([[13], [14], [16], [4]])
    [[0]
     [0]
     [0]
     [0]]
    Matrix([[2], [3], [18], [4]])
    [[0]
     [0]
     [0]
     [0]]
    Matrix([[5], [7], [5], [14]])
    [[0]
     [0]
     [0]
     [0]]
    Matrix([[2], [5], [12], [5]])
    [[0]
     [0]
     [0]
     [0]]
    Matrix([[1], [15], [4], [14]])
    [[0]
     [0]
     [0]
     [0]]
    Matrix([[16], [2], [19], [13]])

```python
# How many rows, how many columns (dimensions), how many total entries,
# selecting a column (vector) or row, from a matrix
# Apply this function to each of the elements
# Value of single entry, constructing a matrix from other matrices, sum of multiple entries, square matrix, identity matrix, trace?
```

```python
m = """
Consider the matrix $A$, what is the value at $a_{@i, @j}$?
<p align="center">
$A = @a$
</p>
"""

def generator():
    params = {}
    rows = np.random.randint(2, 10)
    columns = np.random.randint(2, 10)
    matrix = sp.randMatrix(rows, columns)
    params["a"] = matrix
    i = np.random.randint(0, rows)
    j = np.random.randint(0, columns)
    params["i"] = i + 1
    params["j"] = j + 1

    e = Exercise(MarkdownBlock(m, params))
    e.add_answer(sp.simplify(matrix[i,j]), True, "Correct!")
    if i < columns and j < rows:
        e.add_answer(sp.simplify(matrix[j,i]), False, "🤔 You seem to have swapped the row and column index.")
    return e

Exercise.write_multiple(generator, 100, "matrix_indexing")
```

```python
m = """
Consider the matrix image $A$ below, what is the value at $a_{@i, @j}$?
<p align="center">

</p>
"""

def generator():
    params = {}
    rows = np.random.randint(2, 10)
    columns = np.random.randint(2, 10)
    matrix = sp.randMatrix(rows, columns, min=0, max=15)
    params["a"] = matrix
    i = np.random.randint(0, rows)
    j = np.random.randint(0, columns)
    params["i"] = i + 1
    params["j"] = j + 1

    matrix_to_image(np.array(matrix, dtype=float), "m", grid=True, values_in_cells=True, axis_indices=True, axis_titles=True)

    e = Exercise(MarkdownBlock(m, params))
    e.add_answer(sp.simplify(matrix[i,j]), True, "Correct!")
    if i < columns and j < rows:
        e.add_answer(sp.simplify(matrix[j,i]), False, "🤔 You seem to have swapped the row and column index.")
    params["d"] = symbolic_matrix("a", rows, columns)
    e.add_default_feedback(MarkdownBlock("Remember how values are indexed: $@d$", params))
    return e

Exercise.write_multiple(generator, 10, "matrix_indexing_image")
```

    <ipython-input-2-5b6071c22429>:45: UserWarning: FixedFormatter should only be used together with FixedLocator
      ax.set_xticklabels([i for i in range(0, 9)])
    <ipython-input-2-5b6071c22429>:46: UserWarning: FixedFormatter should only be used together with FixedLocator
      ax.set_yticklabels([i for i in range(0, 9)])

```python
m = """
Consider the matrix $A$, how many rows does $A$ have?
<p align="center">

</p>
"""

def generator():
    params = {}
    rows = np.random.randint(2, 10)
    columns = np.random.randint(2, 10)
    matrix = sp.randMatrix(rows, columns)
    params["a"] = matrix

    e = Exercise(MarkdownBlock(m, params))
    e.add_answer(sp.simplify(rows), True, "Correct!")
    if columns >= rows:
        e.add_answer(sp.simplify(columns), False, "🤔 Nope, that's the amount of columns.")
    return e

Exercise.write_multiple(generator, 100, "matrix_rows")
```

```python
m = """
Consider the matrix $A$, how many columns does $A$ have?
<p align="center">
$A = @a$
</p>
"""

def generator():
    params = {}
    rows = np.random.randint(2, 10)
    columns = np.random.randint(2, 10)
    matrix = sp.randMatrix(rows, columns, min=0, max=15)
    params["a"] = matrix

    e = Exercise(MarkdownBlock(m, params))
    e.add_answer(sp.simplify(columns), True, "Correct!")
    if rows >= columns:
        e.add_answer(sp.simplify(rows), False, "🤔 Nope, that's the amount of rows.")
    return e

Exercise.write_multiple(generator, 100, "matrix_cols")
```

```python
m = """
Consider the matrix randomart image below, how many columns does it contain?
<p align="center">

</p>
"""

def generator():
    params = {}
    rows = np.random.randint(2, 10)
    columns = np.random.randint(2, 10)
    matrix = sp.randMatrix(rows, columns, min=0, max=15)
    params["a"] = matrix
    matrix_to_image(np.array(matrix, dtype=float), "m", grid=False, values_in_cells=False, axis_indices=False, axis_titles=False)

    e = Exercise(MarkdownBlock(m, params))
    e.add_answer(sp.simplify(rows), True, "Correct!")
    if columns >= rows:
        e.add_answer(sp.simplify(columns), False, "🤔 Nope, that's the amount of columns.")
    return e

Exercise.write_multiple(generator, 10, "cols_in_image")
```

```python
m = """
Consider the matrix $A$, how many values does $A$ contain?
<p align="center">
$A = @a$
</p>
"""

def generator():
    params = {}
    rows = np.random.randint(1, 10)
    columns = np.random.randint(1, 10)
    matrix = sp.randMatrix(rows, columns)
    params["a"] = matrix
    ans = rows * columns
    params["ans"] = ans
    params["i"] = rows
    params["j"] = columns

    e = Exercise(MarkdownBlock(m, params))
    e.add_answer(sp.simplify(rows*columns), True, MarkdownBlock("Correct! The matrix has $@i$ rows and $@j$ columns, $@i \cdot @j = @ans$ values", params))
    e.add_default_feedback(MarkdownBlock("Hint: The matrix has $@i$ rows and $@j$ columns.", params))
    return e

Exercise.write_multiple(generator, 100, "matrix_entries")
```

```python
from helpers import *
import random

m = """
Place the following values at the right positions in a matrix:

<p align="center">
    ${values}$
</p>
"""
def generator():
    params = {}
    character = "a"
    rows = np.random.randint(2,4)
    columns = np.random.randint(1,3)
    araw = [[[Symbol(f"{{{character}}}_{{{i+1}, {j+1}}}"), np.random.randint(0, 100)] for j in range(columns)] for i in range(rows)]
    for r in araw:
        random.shuffle(r)
    random.shuffle(araw)
    a = sp.Matrix(araw)

    v = ""
    ans = np.empty((rows, columns), dtype=int)
    for i, row in enumerate(araw):
        for j, col in enumerate(row):
            v = v + f"{latex(col[0])} = {col[1]}, "
            ans[i][j] = col[1]

    # remove trailing space (latex wrapping should be tight e.g. $a,$ instead of $a, $) and comma ($a$ instead of $a,$)
    v = v[:-2]
    e = Exercise(MarkdownBlock(m.format(values=v), params))
    e.add_answer(sp.Matrix(ans), True, "Indeed")
    return e

Exercise.write_multiple(generator, 100, "values_in_matrix")
```

```python
m = r"""
The trace of an $n \times n$ square matrix $A$ is defined as:

$$tr(A) = \sum_{i=1}^{n} a_{i,i} = a_{1,1} + a_{2, 2} + \cdots + a_{n,n}$$

Informally: "the sum of the diagonal elements of A"

$$
A = \begin{bmatrix}
    \bold{a_{1,1}} &  & & \\
     & \bold{a_{2,2}} & & \\
     &  & \ddots & \\
     &        & & \bold{a_{n,n}}
    \end{bmatrix}
$$

Compute $tr(A)$ for $A$

$$A = @a$$
"""
def generator():
    params = {}

    dim = np.random.randint(1, 5)
    a = sp.randMatrix(dim, min=0, max=20)
    params["a"] = a

    e = Exercise(MarkdownBlock(m, params))
    diagonal = np.array(a.diagonal().tolist()).flatten()
    v = " "
    for value in diagonal:
        v = v + str(value) + " + "
    v = v[:-2]
    params["ans"] = a.trace().doit()
    params["d"] = v
    e.add_answer(params["ans"], True, MarkdownBlock("Indeed $tr(A) = {d} = @ans$".format(d=v), params))
    return e

Exercise.write_multiple(generator, 10, "trace")
```

```python

```

```python
m = r"""
The dot product of two vectors $a = \begin{bmatrix} a_{1} & a_{2} & \cdots & a_{n} \end{bmatrix}$ and $b = \begin{bmatrix} b_{1} & b_{2} & \cdots & b_{n} \end{bmatrix}$ is defined as:

$$a \cdot b = \sum_{i=1}^{n} a_{i}b_{i} = a_{1}b_{1} + a_{2}b_{2} + \cdots + a_{n}b_{n}$$

Compute $a \cdot b$ for $a = @a1$ and $b = @b1$.
"""
def generator():
    params = {}
    params["a1"] = sp.randMatrix(1, 4, min=0, max=10)
    params["b1"] = sp.randMatrix(1, 4, min=0, max=10)

    e = Exercise(MarkdownBlock(m, params))
    e.add_answer(0, True, "Yes")
    return e

Exercise.write_multiple(generator, 100, "dot_product")
```

```python
m = r"""
The cosine similarity between two vectors $a = \begin{bmatrix} a_{1} & a_{2} & \cdots & a_{n} \end{bmatrix}$ and $b = \begin{bmatrix} b_{1} & b_{2} & \cdots & b_{n} \end{bmatrix}$ is defined as:

$$\text{similarity} = cos(\theta) = \frac{A \cdot B}{\|A\| \|B\|} = \frac{\sum\limits_{i=1}^{n} A_{i}B_{i}}{\sqrt{\sum\limits_{i=1}^{n} A_{i}^{2}} \sqrt{\sum\limits_{i=1}^{n} B_{i}^{2}}}$$

Compute the cosine similarity between $a = @a1$ and $b = @b1$.
"""
def generator():
    params = {}
    length = np.random.randint(1,4)
    a = sp.randMatrix(1, length, min=0, max=10)
    b = sp.randMatrix(1, length, min=0, max=10)
    params["a1"] = a
    params["b1"] = b
    params["ans"] = np.dot(a, b.transpose())/(np.linalg.norm(np.array(a, dtype=float))*np.linalg.norm(np.array(b, dtype=float)))
#     cos_sim = dot(a, b)/(norm(a)*norm(b))

    e = Exercise(MarkdownBlock(m, params))
    e.add_answer(params["ans"], True, "Yes")
    e.add_default_feedback(MarkdownBlock("Answer = @ans", params))
    return e

Exercise.write_multiple(generator, 100, "cosine_similarity")
```

```python
m = r"""
Below is a partial application of the kernel $K$ to the image $A$, compute
<div class="d-flex flex-1 flex-items-center">
$A = $ , $B = $
</div>
$@c$
"""
def generator():
    a = to_binary(nums[np.random.randint(1700)])

    from scipy.ndimage import convolve
    sharpen_kernel = [[0, -1, 0],
     [-1, 5, -1],
     [0, -1, 0]]
    c = convolve(a, sharpen_kernel)
    c = np.array(c, dtype=float)
    c[5,5] = np.nan

    matrix_to_image(a, "a", grid=True, values_in_cells=True, axis_indices=True)
    matrix_to_image(c, "c", grid=True, values_in_cells=True, axis_indices=True)

    params = {}
    c = np.array(c, dtype=object)
    c[5,5] = sp.Symbol('x')
    params["c"] = sp.Matrix(c)

    e = Exercise(MarkdownBlock(m, params))
    return e

Exercise.write_multiple(generator, 10, "convolution")
```

    <ipython-input-103-4eb5c9fc01c7>:45: UserWarning: FixedFormatter should only be used together with FixedLocator
      ax.set_xticklabels([i for i in range(0, 9)])
    <ipython-input-103-4eb5c9fc01c7>:46: UserWarning: FixedFormatter should only be used together with FixedLocator
      ax.set_yticklabels([i for i in range(0, 9)])

```python
m = r"""
Below is a partial application of the kernel $K$ to the image $A$, compute
<div class="d-flex flex-1 flex-items-center">
$A = $ , $B = $
</div>
"""
def generator():
    from emnist import extract_training_samples
    images, labels = extract_training_samples('digits')

    a = images[np.random.randint(1700)]

    from scipy.ndimage import convolve
    sharpen_kernel = [[0, -1, 0],
     [-1, 5, -1],
     [0, -1, 0]]
    sobel_kernel = [[1, 0, -1],
                   [2, 0, -2],
                   [1, 0, -1]]
    c = convolve(a, sobel_kernel)
    c = np.array(c, dtype=float)
    #c[5,5] = np.nan

    matrix_to_image(a, "a", grid=False, values_in_cells=False, axis_indices=False)
    matrix_to_image(c, "c", grid=False, values_in_cells=False, axis_indices=False)

    params = {}
    #c = np.array(c, dtype=object)
    #c[5,5] = sp.Symbol('x')
    #params["c"] = sp.Matrix(c)

    e = Exercise(MarkdownBlock(m, params))
    return e

Exercise.write_multiple(generator, 20, "sobel_32")
```

```python
from emnist import extract_training_samples
images, labels = extract_training_samples('letters')
```

    Downloading emnist.zip: 536MB [00:52, 10.7MB/s]

```python
for i in range(10):
    plt.imshow(images[i])
    plt.show()
```

```python
import cifar10

img = 0
for image, label in cifar10.data_batch_generator():
    img = image # numpy array of an image, which is of shape 32 x 32 x 3
    label # integer value of the image label

```

```python
def draw(x, y):
    xPlusy = (abs(x[0]+y[0]),abs(x[1]+y[1]))
    array = np.array([[0, 0, x[0], x[1]],
                      [x[0], x[1], y[0], y[1]],
                      [0, 0, xPlusy[0], xPlusy[1]]])
    print(array)
    X, Y, U, V = zip(*array)
    print("X =",X)
    print("Y =",Y)
    print("U =",U)
    print("V =",V)
    plt.figure()
    plt.ylabel('Y-axis')
    plt.xlabel('X-axis')
    ax = plt.gca()
    ax.quiver(X, Y, U, V, angles='xy', scale_units='xy',color=['r','b','g'],scale=1)
    ax.set_xlim([0, 10])
    ax.set_ylim([0, 10])
    plt.draw()
    plt.show()
draw([4,2],[2,4])
```

```python

```


```python
%matplotlib inline

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

plt.rcParams["figure.figsize"] = (12, 9)
plt.rcParams["font.size"] = 18
```

# Application of the Diffusion Equations

This notebook is focused on application of the neutron diffusion equations for one group and multiple energy groups.

## Learning Objectives

- Recall the one group diffusion equation
- Apply the one group diffusion equation to a slab problem

## Recall: The One Group Diffusion Equation

Consider a reactor that is:

- fast
- critical
- the fuel and coolant are a homogeneous mixture
- the reactor has only one region, no reflector ("bare" reactor)

This reactor can be described by the one group diffusion equation:

\begin{align}
D\nabla^2\phi-\Sigma_a\phi + s &= \frac{1}{v}\frac{\partial \phi}{\partial t}\\
D &= \mbox{ the diffusion coefficient}\\
\phi &= \mbox{ flux}\\
v &= \mbox{ neutron speed}
\end{align}

If the neutron source, $s$ does not balance neutron absorption and leakage, then the right hand side of the one-group diffusion equation is nonzero and the power may increase or decrease with time.

For time independent problems, $\frac{1}{v}\frac{\partial \phi}{\partial t} = 0$, so the steady state equation is:

\begin{align}
D\nabla^2\phi-\Sigma_a\phi + s &= 0\\
\end{align}

We can additionally simplify this by defining a new parameter:

\begin{align}
L^2 &= \frac{D}{\Sigma_a}\\
&= \mbox{diffusion area}
\end{align}

Such that:

\begin{align}
&\nabla^2\phi-\frac{1}{L^2}\phi + \frac{s}{D} = 0\\
\implies & \nabla^2\phi-\frac{1}{L^2}\phi = -\frac{s}{D}
\end{align}

## Recall: Transport Cross Section

Recall that D is given approximately as :

\begin{align}
D &= \frac{1}{3\Sigma_{tr}}
\end{align}

where

\begin{align}
\Sigma_{tr} = \mbox{the macroscopic transport cross section}
\end{align}

This can also be stated:

\begin{align}
D &= \frac{\lambda_{tr}}{3}
\end{align}

where

\begin{align}
\lambda_{tr} = \mbox{the transport mean free path}
\end{align}

**In a purely scattering (no absorption) medium,** we can describe the transport mean free path thus:

\begin{align}
\lambda_{tr} &= \frac{1}{\Sigma_{tr}}\\
&= \frac{1}{\Sigma_s\left(1-\overline{\mu}\right)}
\end{align}

**In a medium with both absorption and scattering,** the transport mean free path is:

\begin{align}
\lambda_{tr} &= \frac{1}{\Sigma_{tr}}\\
&= \frac{1}{\left(\Sigma_t-\overline{\mu}\Sigma_s\right)}
\end{align}

## Planar source

Assume an infinite, planar, neutron source (with strength $S$) in an infinite diffusive medium.
The flux is approximately a function of only x, so we can write the diffusion equation in 1D :

\begin{align}
\frac{d^2}{dx^2}\phi(x)-\frac{1}{L^2}\phi(x) = -\frac{s}{D}
\end{align}

How would you describe s(x)?

Everywhere except x=0:

\begin{align}
\frac{d^2\phi(x)}{dx^2}-\frac{\phi(x)}{L^2}= 0\\
\end{align}

This solution has the form:

\begin{align}
\phi(x) &= Ae^{x/L} + Ce^{-x/L}
\end{align}

The flux must go to zero at $\infty$.
What do we learn from this?

If $\phi(\infty)\rightarrow 0$, A must be equal to zero.

The only physically acceptable solution for $x > 0$ is then

\begin{align}
\phi(x) =  Ce^{-x/L}
\end{align}

We need to find C using the source condition as $x\rightarrow0$.

If $S$ is the source strength per unit area of the plane, then the number of neutrons crossing outwards per unit area in the positive x-direction must tend to $S/2$ as $x \rightarrow 0$.

\begin{align}
&\lim_{x->0} J(x) = \frac{S}{2}\\
\implies &\lim_{x->0} \left( -D \frac{d\phi}{dx}\right)= \frac{S}{2}\\
\implies &\lim_{x->0} \left( \frac{CD}{L} e^{-x/L}\right)= \frac{S}{2}\\
\implies & \frac{CD}{L} = \frac{S}{2}\\
\implies & C = \frac{SL}{2D}\\
\end{align}

\begin{align}
\phi(x) =  \frac{SL}{2D}e^{-x/L}
\end{align}

## Extrapolation Distance

Let's do a [gedanken experiment](https://en.wikipedia.org/wiki/Thought_experiment):

- There is a slab in a vacuum
- There is a source of neutrons in the medium
- The medium is diffusive

Are there any neutrons outside the slab?

```python
## This code example was adapted from
## https://github.com/marort91/AlphaEigenvalue/blob/master/RadiationTransportCoding/NeutronDiffusion_Python/NDE_CriticalityEigenvalue.ipynb

def diff(sig_tr):
    return 1.0/(3.0*sig_tr)

def sig_tr(e):
    sig_t(e) - mu*sig_s(e)

D = diff(0.1)
nusigf = 0.70
siga = 0.066

Lx = np.pi*((nusigf-siga)/D)**(-0.5)

N = 50;
h = Lx/(N-1)

x = np.zeros(N)

for i in range(N-1):
    x[i+1] = x[i] + h

L = np.zeros((N,N))
A = np.zeros((N,N))
M = np.zeros((N,N))

for i in range(N):
    L[i][i] = L[i][i] + (-2*(-D/(h**2)))

for i in range(1,N):
    L[i][i-1] = L[i][i-1] + (-D/h**2)

for i in range(N-1):
    L[i][i+1] = L[i][i+1] + (-D/h**2)

for i in range(N):
    A[i][i] = A[i][i] + siga

M = L + A

M[0][0] = 1
M[0][1] = 0
M[N-1][N-1] = 1
M[N-1][N-2] = 0

phi0 = np.ones((N,1))
phi0[0] = 0
phi0[N-1] = 0

tol = 1e-15
k = 1.00

def is_converged(k_old, k, tol):
    return np.abs(k - k_old) <= tol

for i in range(100):
    # update k
    k_old = k
    # solve for psi
    psi = np.linalg.solve(M, nusigf*phi0)

    # solve for k
    k = sum(nusigf*psi)/sum(nusigf*phi0)

    # solve for phi
    phi0 = (1/k)*psi
    phi0[0] = 0
    phi0[N-1] = 0

    # determine convergence
    if is_converged(k_old, k, tol):
        break

plt.plot(x, phi0)
plt.xlabel('Slab (cm)')
plt.ylabel('Neutron Flux')
plt.grid()

print("k-effective = ", k)

```

## Slab

Let's approach solving this equation for a slab in a vacuum. The slab is composed of a diffusing medium with diffusion coeffcient $D$.

- We want a solution for $\phi$
- We know the diffusion coefficient
- We have a differential equation

\begin{align}
\frac{d^2}{dx^2}\phi(x)-\frac{1}{L^2}\phi(x) = -\frac{s}{D}
\end{align}

#### Question: What else do we need?

We need:

- Boundary Conditions (for a 1-D problem, we need two BCs)
- A description of s(x)

Let's imagine there is an infinite planar source at the center of the slab.

### Vacuum Boundary Condition

If we assume no neutrons are reflected back into the vacuum at the ordinary boundary $J_x(x = a) = 0$, then the derivative of $\phi$ at that boundary indicates that $\phi$ is flat across the boundary, as in the transport solution.

\begin{align}
J_x(x=a) = 0 \\
\implies & \left[ -D \frac{d\phi}{dx}\right]_{x=a} = 0\\
\implies & \left[ \frac{-\lambda_{tr}}{3}\frac{d\phi}{dx}\right]_{x=a} = 0\\
\implies & \left[\frac{d\phi}{dx}\right]_{x=a} = 0\\
\end{align}

The best way to approximate this with diffusion is through the extrapolated distance. Fick's law can be slightly corrected by assuming the flux diminishes to zero not at the real boundary ($x=a$), but, rather, at the extrapolated boundary ($x=\tilde{a}=a+d$) where $d=0.71\lambda_{tr}$.

Recall, the solution for the flux still has the form:

\begin{align}
\phi(x) &= Ae^{x/L} + Ce^{-x/L}
\end{align}

Thus:

\begin{align}
&\phi(x=\tilde{a}) = \phi(x=-\tilde{a}) =0 \\
\implies & \left[Ae^{x/L} + Ce^{-x/L}\right]_{x=\pm\tilde{a}} = 0\\
\end{align}

For the right hand side of the slab:

\begin{align}
\implies & \left[Ae^{x/L} + Ce^{-x/L}\right]_{x=\tilde{a}} = 0\\
\implies & C = -Ae^{-2\tilde{a}/L}\\
\end{align}

Accordingly:

\begin{align}
\phi(x) &= Ae^{x/L} -Ae^{-2\tilde{a}/L}e^{-x/L}\\
&=A\left[e^{x/L} - e^{(x -2\tilde{a})/L}\right]\\
\end{align}

### Question: What do we need to find the constant A?

We need a new boundary condition. Let's use the finite source condition, as before, such that, if $S$ is the source strength per unit area of the plane, then the number of neutrons crossing outwards per unit area in the positive x-direction must tend to $S/2$ as $x \rightarrow 0$.

\begin{align}
&\lim_{x->0} J(x) = \frac{S}{2}\\
\implies &\lim_{x->0} \left(-D \frac{d\phi}{dx}\right)= \frac{S}{2}\\
\implies &\lim_{x->0} \left(-D \frac{d}{dx} A\left[e^{x/L} - e^{(x -2\tilde{a})/L}\right]\right)= \frac{S}{2}\\
\implies & A = \frac{SL}{2D\left(1 + e^{-2\tilde{a}/L}\right)}\\
\end{align}

We can plug this solution for A, then:

\begin{align}
\phi(x)\big|_{x>0}&=A\left[e^{x/L} - e^{(x -2\tilde{a})/L}\right]\\
 &=\frac{SL}{2D\left(1 + e^{-2\tilde{a}/L}\right)}\left[e^{x/L} - e^{(x -2\tilde{a})/L}\right]\\
 &=\frac{SL}{2D}\frac{e^{-x/L} - e^{(x - 2\tilde{a})/L}}{1 + e^{-2\tilde{a}/L}}
\end{align}

The above solution is only applicable to the right hand side of $x=0$. To generalize, using symmetry, we substitute $|x|$ for $x$:

\begin{align}
\phi(x)&=\frac{SL}{2D}\frac{e^{-|x|/L} - e^{(|x| - 2\tilde{a})/L}}{1 + e^{-2\tilde{a}/L}}\\
\end{align}

It turns out one can write an equivalent expression with sinh and cosh:

\begin{align}
\phi(x)&=\frac{SL}{2D}\frac{\sinh{\left[(\tilde{a} - |x|)/L\right]}}{\cosh{\left[\tilde{a}/L\right]}}\\
\end{align}

From nuclear-power.net:

# Steady State 2D Multi-Group

Steady-state 2D multi-group neutron diffusion equation

\begin{align}
\nabla\cdot J_g(x,y) &+\Sigma_{r,g}(x,y)\phi_g(x,y) = S_g(x,y)
\end{align}
where

\begin{align}J_g(x,y) &= -D_g(x,y)\nabla\phi_g(x, y)\\
\Sigma_{r,g}(x,y) &= \Sigma_{a,g}(x,y) + \sum_{g'\ne g}\Sigma_{s,g\rightarrow g'}(x,y)\\
S_g(x,y) &= \frac{\chi_g(x,y)}{k}\sum_{g'}\nu\Sigma_{f,g'}\phi_{g'}(x,y) + \sum_{g'\ne g}\Sigma_{s,g'\rightarrow g}(x,y)\phi_{g'}\\
D_g(x,y) &= \frac{1}{3\Sigma_{tr,g}(x,y)}
\end{align}

### Nomenclature

#### Subscripts

 \begin{align}
  g &= \mbox{energy group (discrete energy interval)}
 \end{align}

#### Variables

 \begin{align}
  \phi_g(x,y) &= \mbox{neutron flux}\\
  J_g(x,y) &= \mbox{neutron current (or neutron current density)}
  \end{align}

#### Coefficients (cross-sections, material properties)

 \begin{align}
\Sigma_{r,g}(x,y) &= \mbox{removal cross-section, sum of absorption and out-scattering (scattering from group g to group g’)}\\
D{g}(x,y) &= \mbox{diffusion coefficient (analogous to conductivity in heat conduction)}\\
\Sigma_{a,g}(x,y)&= \mbox{absorption cross-section}\\
\Sigma_{s,g\rightarrow g'}(x,y)&= \mbox{scattering cross-section from group g to group g’}\\
\Sigma_{tr,g}(x,y)&= \mbox{transport cross-sections, approximately equivalent to total cross-section}\\
&\mbox{(sum of absorption, out-scattering and self-scattering) corrected by transport effect}\\
\nu\Sigma_{f,g}(x,y)&= \mbox{fission cross-section multiplied by number of neutrons per fission}\\
\chi_{g}(x,y)&= \mbox{fission spectrum, fraction of all fission neutrons born in group g}\\
\end{align}

In both cases, $\overline{\mu}$ is the average cosine of the neutron scattering angle in the medium.

Without repeating our discussion of elastic scattering angles, note that the cosine of the neutron deflection from a nucleus of mass A is isotropic in the COM system, but **forward scattering is preferred in the laboratory frame of reference.**

\begin{align}
<\cos{\theta}> &= \frac{1}{n}\int_0^\pi \frac{n}{2}\cos{\theta}\sin{\theta}d\theta = 0
\end{align}

We arrive at the **average cosine of the scattering angle** by integrating over all angles $\theta \in [0, \pi]$ and normalizing by degree of anisotropy in COM system (from above, we arrive at n=2).

\begin{align}
\overline{\mu} &= <\cos{\psi}> \\
&= \frac{1}{n}\int_0^\pi \frac{A\cos{\theta} + 1}{\sqrt{A^2 + 2A\cos{\theta} + 1}}\sin{\theta}d\theta\\
&= \frac{1}{2}\int_0^\pi \frac{A\cos{\theta} + 1}{\sqrt{A^2 + 2A\cos{\theta} + 1}}\sin{\theta}d\theta\\ &= \frac{2}{3A}
\end{align}

So, basically, **the transport mean free path ($\lambda_{tr}$) is the average distance a neutron will move in its original direction after infinite number of scattering collisions.**

### Putting it all together

\begin{align}
D &= \frac{1}{3\Sigma_{tr}}\\
\mbox{Purely scattering medium:   } &\\
D &= \frac{1}{3\Sigma_s\left(1-\frac{2}{3A}\right)}\\
\mbox{Scattering medium with absorption:   } &\\
D &= \frac{1}{3\left(\Sigma_t- \frac{2}{3A}\Sigma_s\right)}
\end{align}

## Starting with the Correct Equations

A key step in solving the multigroup equations seems obvious, but takes practice: _writing down the equations properly, based on the assumptions in the problem._

1. We begin with the general form of the multigroup equations
2. We write one equation per group.
3. Then, we delete terms that are zero.

### Begin with the General Form

\begin{align}
-D_g(x,y)\nabla^2\phi_g(x, y) &+\Sigma_{r,g}(x,y)\phi_g(x,y) = S_g(x,y)
\end{align}

Recall that the source in this form includes sources from scattering and sources from fission.

\begin{align}
S_g(x,y) = \sum_{g'\ne g}\Sigma_s^{g'\rightarrow g}\phi_{g'} + \chi_g\sum_{g'=1}^{g'=G}\nu_{g'}\Sigma_{f,g'}\phi_{g'}
\end{align}

### Four groups

Assume:

- four energy groups,
- no upscattering,
- fissions take place in the last two groups only,
- fission neutrons appear in the first two groups only, 79% of them in the 1st group

Write the multigroup diffusion equations.
We write one equation per group.

\begin{align}
-D_1\nabla^2\phi_1 + \Sigma_{R1}\phi_1 &= 0.79\left[(\nu\Sigma_f)_3\phi_3 + (\nu\Sigma_f)_4\phi_4\right] = 0.79S\\
-D_2\nabla^2\phi_2 + \Sigma_{R2}\phi_2 &= \Sigma_s^{1\rightarrow 2}\phi_1 + 0.21\left[((\nu\Sigma_f)_3\phi_3 + (\nu\Sigma_f)_4\phi_4\right] = \Sigma_s^{1\rightarrow 2}\phi_1 + 0.21S\\
-D_3\nabla^2\phi_3 + \Sigma_{R3}\phi_3 &=  \Sigma_s^{1\rightarrow 3}\phi_1 +  \Sigma_s^{2\rightarrow 3}\phi_2\\
-D_4\nabla^2\phi_4 + \Sigma_{R4}\phi_4 &=  \Sigma_s^{1\rightarrow 4}\phi_1 +  \Sigma_s^{2\rightarrow 4}\phi_2 +  \Sigma_s^{3\rightarrow 4}\phi_3
\end{align}

```python

```


```python
import utils
%load_ext autoreload
%autoreload 2

from utils import build_transf, full_homo_transf, prop_velo, prop_force_torque, comp_jacobian
import utils
from sympy import sqrt
import sympy as sy
from IPython.display import display, Math
```

    The autoreload extension is already loaded. To reload it, use:
      %reload_ext autoreload

# Final 20/21 Problem 3

Denavit-Hartenberg Parameters are from problem 3a).

```python
dh_params = [
    [90, 0, "d_1", 0],
    [90, 0, sy.Symbol("l_1")+sy.Symbol("d_3"), "theta_2"],
    [0, "l_2", 0, 0],
    [0, 0, 0, 0]
]

pc1_0 = sy.Matrix([0, -2/3 * sy.Symbol("d_1")])
pc2_0 = sy.Matrix([1/2 * sy.Symbol("l_2") * sy.cos(sy.Symbol("theta_2")),
                   -sy.Symbol("d_1") -1/2 * sy.Symbol("l_2") * sy.sin(sy.Symbol("theta_2")),
                   -sy.Symbol("l_1")])
pc3_0 = sy.Matrix([sy.Symbol("l_2") * sy.cos(sy.Symbol("theta_2")),
                   -sy.Symbol("d_1") - sy.Symbol("l_2") * sy.sin(sy.Symbol("theta_2")),
                   -sy.Symbol("l_1") -1/2 * sy.Symbol("d_3")])
```

```python
transforms = utils.build_transf(dh_params)
full_transform = full_homo_transf(transforms, verbose=False)
```

$\displaystyle {}^0_1T = \left[\begin{matrix}1 & 0 & 0 & 0\\0 & 0 & -1 & - d_{1}\\0 & 1 & 0 & 0\\0 & 0 & 0 & 1\end{matrix}\right]$

$\displaystyle {}^1_2T = \left[\begin{matrix}\cos{\left(\theta_{2} \right)} & - \sin{\left(\theta_{2} \right)} & 0 & 0\\0 & 0 & -1 & - d_{3} - l_{1}\\\sin{\left(\theta_{2} \right)} & \cos{\left(\theta_{2} \right)} & 0 & 0\\0 & 0 & 0 & 1\end{matrix}\right]$

$\displaystyle {}^2_3T = \left[\begin{matrix}1 & 0 & 0 & l_{2}\\0 & 1 & 0 & 0\\0 & 0 & 1 & 0\\0 & 0 & 0 & 1\end{matrix}\right]$

$\displaystyle {}^3_4T = \left[\begin{matrix}1 & 0 & 0 & 0\\0 & 1 & 0 & 0\\0 & 0 & 1 & 0\\0 & 0 & 0 & 1\end{matrix}\right]$

```python
T02 = transforms[0] @ transforms[1]
T03 = T02 @ transforms[2]
T04 = T03 @ transforms[3]
for t in (T02, T03, T04):
    display(Math(sy.latex(t)))
```

$\displaystyle \left[\begin{matrix}\cos{\left(\theta_{2} \right)} & - \sin{\left(\theta_{2} \right)} & 0 & 0\\- \sin{\left(\theta_{2} \right)} & - \cos{\left(\theta_{2} \right)} & 0 & - d_{1}\\0 & 0 & -1 & - d_{3} - l_{1}\\0 & 0 & 0 & 1\end{matrix}\right]$

$\displaystyle \left[\begin{matrix}\cos{\left(\theta_{2} \right)} & - \sin{\left(\theta_{2} \right)} & 0 & l_{2} \cos{\left(\theta_{2} \right)}\\- \sin{\left(\theta_{2} \right)} & - \cos{\left(\theta_{2} \right)} & 0 & - d_{1} - l_{2} \sin{\left(\theta_{2} \right)}\\0 & 0 & -1 & - d_{3} - l_{1}\\0 & 0 & 0 & 1\end{matrix}\right]$

$\displaystyle \left[\begin{matrix}\cos{\left(\theta_{2} \right)} & - \sin{\left(\theta_{2} \right)} & 0 & l_{2} \cos{\left(\theta_{2} \right)}\\- \sin{\left(\theta_{2} \right)} & - \cos{\left(\theta_{2} \right)} & 0 & - d_{1} - l_{2} \sin{\left(\theta_{2} \right)}\\0 & 0 & -1 & - d_{3} - l_{1}\\0 & 0 & 0 & 1\end{matrix}\right]$

```python
for t in (T02, T03, T04):
    display(Math(sy.latex(utils.homo_transpose(t))))
```

$\displaystyle \left[\begin{matrix}\cos{\left(\theta_{2} \right)} & - \sin{\left(\theta_{2} \right)} & 0 & - d_{1} \sin{\left(\theta_{2} \right)}\\- \sin{\left(\theta_{2} \right)} & - \cos{\left(\theta_{2} \right)} & 0 & - d_{1} \cos{\left(\theta_{2} \right)}\\0 & 0 & -1 & - d_{3} - l_{1}\\0 & 0 & 0 & 1\end{matrix}\right]$

$\displaystyle \left[\begin{matrix}\cos{\left(\theta_{2} \right)} & - \sin{\left(\theta_{2} \right)} & 0 & - d_{1} \sin{\left(\theta_{2} \right)} - l_{2}\\- \sin{\left(\theta_{2} \right)} & - \cos{\left(\theta_{2} \right)} & 0 & - d_{1} \cos{\left(\theta_{2} \right)}\\0 & 0 & -1 & - d_{3} - l_{1}\\0 & 0 & 0 & 1\end{matrix}\right]$

$\displaystyle \left[\begin{matrix}\cos{\left(\theta_{2} \right)} & - \sin{\left(\theta_{2} \right)} & 0 & - d_{1} \sin{\left(\theta_{2} \right)} - l_{2}\\- \sin{\left(\theta_{2} \right)} & - \cos{\left(\theta_{2} \right)} & 0 & - d_{1} \cos{\left(\theta_{2} \right)}\\0 & 0 & -1 & - d_{3} - l_{1}\\0 & 0 & 0 & 1\end{matrix}\right]$


###### Content under Creative Commons Attribution license CC-BY 4.0, code under MIT license (c)2014 L.A. Barba, C.D. Cooper, G.F. Forsyth.

# Riding the wave

## Sod's test problems

Sod's test problems are standard benchmarks used to assess the accuracy of numerical solvers. The tests use a classic example of one-dimensional compressible flow: the shock-tube problem. Sod (1978) chose initial conditions and numerical discretization parameters for the shock-tube problem and used these to test several schemes, including Lax-Wendroff and MacCormack's. Since then, many others have followed Sod's example and used the same tests on new numerical methods.

The shock-tube problem is so useful for testing numerical methods because it is one of the few problems that allows an exact solution of the Euler equations for compressible flow.

This notebook complements the previous lessons of the course module [_"Riding the wave: convection problems"_](https://github.com/numerical-mooc/numerical-mooc/tree/master/lessons/03_wave) with Sod's test problems as an independent coding exercise. We'll lay out the problem for you, but leave important bits of code for you to write on your own. Good luck!

### What's a shock tube?

A shock tube is an idealized device that generates a one-dimensional shock wave in a compressible gas. The setting allows an analytical solution of the Euler equations, which is very useful for comparing with the numerical results to assess their accuracy.

Picture a tube with two regions containing gas at different pressures, separated by an infinitely-thin, rigid diaphragm. The gas is initially at rest, and the left region is at a higher pressure than the region to the right of the diaphragm. At time $t = 0.0 s$, the diaphragm is ruptured instantaneously.

What happens?

You get a shock wave.  The gas at high pressure, no longer constrained by the diaphragm, rushes into the lower-pressure area and a one-dimensional unsteady flow is established, consisting of:

* a shock wave traveling to the right
* an expansion wave traveling to the left
* a moving contact discontinuity

The shock-tube problem is an example of a *Riemann problem* and it has an analytical solution, as we said. The situation is illustrated in Figure 1.

#### Figure 1. The shock-tube problem.

### The Euler equations

The Euler equations govern the motion of an inviscid fluid (no viscosity). They consist of the conservation laws of mass and momentum, and often we also need to work with the energy equation.

Let's consider a 1D flow with velocity $u$ in the $x$-direction. The Euler equations for a fluid with density $\rho$ and pressure $p$ are:

\begin{align}
\frac{\partial \rho}{\partial t} + \frac{\partial}{\partial x}(\rho u) &= 0 \\
\frac{\partial}{\partial t}(\rho u) + \frac{\partial}{\partial x} (\rho u^2 + p)&=0
\end{align}

... plus the energy equation, which we can write in this form:

\begin{equation}
\frac{\partial}{\partial t}(\rho e_T) + \frac{\partial}{\partial x} (\rho u e_T +p u)=0
\end{equation}
where $e_T=e+u^2/2$ is the total energy per unit mass, equal to the internal energy plus the potential energy (per unit mass).

Written in vector form, you can see that the Euler equations bear a strong resemblance to the traffic-density equation that has been the focus of this course module so far. Here is the vector representation of the Euler equation:

\begin{equation}
\frac{\partial }{\partial t} \underline{\mathbf{u}} + \frac{\partial }{\partial x} \underline{\mathbf{f}} = 0
\end{equation}

The big difference with our previous work is that the variables $\underline{\mathbf{u}}$ and $\underline{\mathbf{f}}$ are *vectors*.  If you review the [Phugoid Full Model](http://nbviewer.ipython.org/github/numerical-mooc/numerical-mooc/blob/master/lessons/01_phugoid/01_03_PhugoidFullModel.ipynb) lesson, you will recall that we can solve for several values at once using the vector form of an equation.  In the Phugoid Module, it was an ODE—now we apply the same procedure to a PDE.

Let's take a look at what $\underline{\mathbf{u}}$ and $\underline{\mathbf{f}}$ consist of.

### The conservative form

Many works in the early days of computational fluid dynamics in the 1960s showed that using the conservation form of the Euler equations is more accurate for situations with shock waves.  And as you already saw, the shock-tube solutions do contain shocks.

The conserved variables $\underline{\mathbf{u}}$ for Euler's equations are

\begin{equation}\underline{\mathbf{u}} = \left[ \begin{array}{c}
\rho \\
\rho u \\
\rho e_T \\
\end{array} \right]\end{equation}

where $\rho$ is the density of the fluid, $u$ is the velocity of the fluid and $e_T = e + \frac{u^2}{2}$ is the specific total energy; $\underline{\mathbf{f}}$ is the flux vector:

\begin{equation}\underline{\mathbf{f}} = \left[ \begin{array}{c}
\rho u \\
\rho u^2 + p \\
(\rho e_T + p) u \\ \end{array} \right]
\end{equation}

where $p$ is the pressure of the fluid.

If we put together the conserved variables and the flux vector into our PDE, we get the following set of equations:

\begin{equation}
\frac{\partial}{\partial t}
\left[ \begin{array}{c}
\rho \\
\rho u \\
\rho e_T \\
\end{array} \right]
+ \frac{\partial}{\partial x}
\left[ \begin{array}{c}
\rho u \\
\rho u^2 + p \\
(\rho e_T + p) u\\ \end{array}
\right]
=0
\end{equation}

There's one major problem there.  We have 3 equations and 4 unknowns.  But there is a solution!  We can use an equation of state to calculate the pressure—in this case, we'll use the ideal gas law.

### Calculating the pressure

For an ideal gas, the equation of state is

$$e = e(\rho, p) = \frac{p}{(\gamma -1) \rho},$$

where $\gamma = 1.4$ is a reasonable value to model air,

$$\therefore p = (\gamma -1)\rho e. $$

Recall from above that

$$e_T = e+\frac{1}{2} u^2$$

$$\therefore e = e_T - \frac{1}{2}u^2.$$

Putting it all together, we arrive at an equation for the pressure

$$p = (\gamma -1)\left(\rho e_T - \frac{\rho u^2}{2}\right).$$

### Flux in terms of $\underline{\mathbf{u}}$

With the traffic model, the flux was a function of traffic density.  For the Euler equations, the three equations we have are coupled and the flux *vector* is a function of $\underline{\mathbf{u}}$, the vector of conserved variables:

$$\underline{\mathbf{f}} = f(\underline{\mathbf{u}})$$

In order to get everything squared away, we need to represent $\underline{\mathbf{f}}$ in terms of $\underline{\mathbf{u}}$.
We can introduce a little shorthand for the $\underline{\mathbf{u}}$ and $\underline{\mathbf{f}}$ vectors and define:

$$\underline{\mathbf{u}} =
\left[ \begin{array}{c}
u_1 \\
u_2 \\
u_3 \\
\end{array} \right] =
\left[ \begin{array}{c}
\rho \\
\rho u \\
\rho e_T \\
\end{array} \right]$$

$$\underline{\mathbf{f}} =
\left[ \begin{array}{c}
f_1 \\
f_2 \\
f_3 \\ \end{array} \right] =
\left[ \begin{array}{c}
\rho u \\
\rho u^2 + p \\
(\rho e_T + p) u\\ \end{array}
\right]
$$

With a little algebraic trickery, we can represent the pressure vector using quantities from the $\underline{\mathbf{u}}$ vector.

$$p = (\gamma -1)\left(u_3 - \frac{1}{2} \frac{u^2_2}{u_1} \right)$$

Now that pressure can be represented in terms of $\underline{\mathbf{u}}$, the rest of $\underline{\mathbf{f}}$ isn't too difficult to resolve:

$$\underline{\mathbf{f}} = \left[ \begin{array}{c}
f_1 \\
f_2 \\
f_3 \\ \end{array} \right] =
\left[ \begin{array}{c}
u_2\\
\frac{u^2_2}{u_1} + (\gamma -1)\left(u_3 - \frac{1}{2} \frac{u^2_2}{u_1} \right) \\
\left(u_3 + (\gamma -1)\left(u_3 - \frac{1}{2} \frac{u^2_2}{u_1}\right) \right) \frac{u_2}{u_1}\\ \end{array}
\right]$$

## Test conditions

The first test proposed by Sod in his 1978 paper is as follows.

In a tube spanning from $x = -10 \text{m}$ to $x = 10 \text{m}$ with the rigid membrane at $x = 0 \text{m}$, we have the following initial gas states:

$$\underline{IC}_L = \left[ \begin{array}{c}
\rho_L \\ u_L \\ p_L \\ \end{array}\right] =
\left[ \begin{array}{c}
1\ kg/m^3 \\ 0\ m/s \\ 100\ kN/m^2 \\ \end{array}\right]$$

$$\underline{IC}_R = \left[ \begin{array}{c}
\rho_R \\ u_R \\ p_R \\ \end{array}\right] =
\left[ \begin{array}{c}
0.125\ kg/m^3 \\ 0\ m/s \\ 10\ kN/m^2 \\ \end{array}\right]$$

where $\underline{IC}_L$ are the initial density, velocity and pressure on the left side of the tube membrane and $\underline{IC}_R$ are the initial density, velocity and pressure on the right side of the tube membrane.

The analytical solution to this test for the velocity, pressure and density, looks like the plots in Figure 2.

.

#### Figure 2. Analytical solution for Sod's first test.

### The Richtmyer method

For this exercise, you will be using a new scheme called the Richtmyer method.  Like the MacCormack method that we learned in [lesson 2](http://nbviewer.ipython.org/github/numerical-mooc/numerical-mooc/blob/master/lessons/03_wave/03_02_convectionSchemes.ipynb), Richtmyer is a *two-step method*, given by:

\begin{align}
\underline{\mathbf{u}}^{n+\frac{1}{2}}_{i+\frac{1}{2}} &= \frac{1}{2} \left( \underline{\mathbf{u}}^n_{i+1} + \underline{\mathbf{u}}^n_i \right) -
\frac{\Delta t}{2 \Delta x} \left( \underline{\mathbf{f}}^n_{i+1} - \underline{\mathbf{f}}^n_i\right) \\
\underline{\mathbf{u}}^{n+1}_i &= \underline{\mathbf{u}}^n_i - \frac{\Delta t}{\Delta x} \left(\underline{\mathbf{f}}^{n+\frac{1}{2}}_{i+\frac{1}{2}} - \underline{\mathbf{f}}^{n+\frac{1}{2}}_{i-\frac{1}{2}} \right)
\end{align}

The flux vectors used in the second step are obtained by evaluating the flux functions on the output of the first step:

$$\underline{\mathbf{f}}^{n+\frac{1}{2}}_{i+\frac{1}{2}} = \underline{\mathbf{f}}\left(\underline{\mathbf{u}}^{n+\frac{1}{2}}_{i+\frac{1}{2}}\right).$$

The first step is like a *predictor* of the solution: if you look closely, you'll see that we are applying a Lax-Friedrichs scheme here. The second step is a *corrector* that applies a leapfrog update. Figure 3 gives a sketch of the stencil for Richtmyer method, where the "intermediate time" $n+1/2$ will require a temporary variable in your code, just like we had in the MacCormack scheme.

#### Figure 3. Stencil of Richtmyer scheme.

## Coding assignment

Your mission, should you wish to accept it, is to calculate the pressure, density and velocity across the shock tube at time $t = 0.01 s$ using the Richtmyer method. Good luck!

## Reference

* Sod, Gary A. (1978), "A survey of several finite difference methods for systems of nonlinear hyperbolic conservation laws," *J. Comput. Phys.*, Vol. 27, pp. 1–31 DOI: [10.1016/0021-9991(78)90023-2](http://dx.doi.org/10.1016%2F0021-9991%2878%2990023-2) // [PDF from unicamp.br](http://www.fem.unicamp.br/~phoenics/EM974/TG%20PHOENICS/BRUNO%20GALETTI%20TG%202013/a%20survey%20of%20several%20finite%20difference%20methods%20for%20systems%20of%20nonlinear%20hyperbolic%20conservation%20laws%20Sod%201978.pdf), checked Oct. 28, 2014.

---

###### The cell below loads the style of the notebook.

```
from IPython.core.display import HTML
css_file = '../../styles/numericalmoocstyle.css'
HTML(open(css_file, "r").read())
```

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

@font-face {
    font-family: "Computer Modern";
    src: url('http://mirrors.ctan.org/fonts/cm-unicode/fonts/otf/cmunss.otf');
}

#notebook_panel { /* main background */
    background: rgb(245,245,245);
}

div.cell { /* set cell width */
    width: 750px;
}

div #notebook { /* centre the content */
    background: #fff; /* white background for content */
    width: 1000px;
    margin: auto;
    padding-left: 0em;
}

#notebook li { /* More space between bullet points */
margin-top:0.8em;
}

/* draw border around running cells */
div.cell.border-box-sizing.code_cell.running {
    border: 1px solid #111;
}

/* Put a solid color box around each cell and its output, visually linking them*/
div.cell.code_cell {
    background-color: rgb(256,256,256);
    border-radius: 0px;
    padding: 0.5em;
    margin-left:1em;
    margin-top: 1em;
}

div.text_cell_render{
    font-family: 'Alegreya Sans' sans-serif;
    line-height: 140%;
    font-size: 125%;
    font-weight: 400;
    width:600px;
    margin-left:auto;
    margin-right:auto;
}

/* Formatting for header cells */
.text_cell_render h1 {
    font-family: 'Nixie One', serif;
    font-style:regular;
    font-weight: 400;
    font-size: 45pt;
    line-height: 100%;
    color: rgb(0,51,102);
    margin-bottom: 0.5em;
    margin-top: 0.5em;
    display: block;
}
.text_cell_render h2 {
    font-family: 'Nixie One', serif;
    font-weight: 400;
    font-size: 30pt;
    line-height: 100%;
    color: rgb(0,51,102);
    margin-bottom: 0.1em;
    margin-top: 0.3em;
    display: block;
}

.text_cell_render h3 {
    font-family: 'Nixie One', serif;
    margin-top:16px;
	font-size: 22pt;
    font-weight: 600;
    margin-bottom: 3px;
    font-style: regular;
    color: rgb(102,102,0);
}

.text_cell_render h4 {    /*Use this for captions*/
    font-family: 'Nixie One', serif;
    font-size: 14pt;
    text-align: center;
    margin-top: 0em;
    margin-bottom: 2em;
    font-style: regular;
}

.text_cell_render h5 {  /*Use this for small titles*/
    font-family: 'Nixie One', sans-serif;
    font-weight: 400;
    font-size: 16pt;
    color: rgb(163,0,0);
    font-style: italic;
    margin-bottom: .1em;
    margin-top: 0.8em;
    display: block;
}

.text_cell_render h6 { /*use this for copyright note*/
    font-family: 'PT Mono', sans-serif;
    font-weight: 300;
    font-size: 9pt;
    line-height: 100%;
    color: grey;
    margin-bottom: 1px;
    margin-top: 1px;
}

.CodeMirror{
        font-family: "PT Mono";
        font-size: 90%;
}

</style>


| Conceitos matemáticos envolvidos: soma, somatória, média aritmética, desigualdades, intervalo, semi-reta.
| -------:

```python
# Carregando o banco de dados
import pandas as pd #pacote para manipulação do banco de dados
import numpy as np #pacote numérico, matrizes, vetores, somatórios, ...
from matplotlib import pyplot as plt #pacote gráfico
import random

# selecting a set of random register on the file train.csv
records = 42000
n_sample_records = 42000
skip = sorted(random.sample(range(1, records), records - n_sample_records))

# Carregando o banco de dados para a variável df
df = pd.read_csv('train.csv', skiprows = skip)

print(df.info())
```

    <class 'pandas.core.frame.DataFrame'>
    RangeIndex: 42000 entries, 0 to 41999
    Columns: 785 entries, label to pixel783
    dtypes: int64(785)
    memory usage: 251.5 MB
    None

```python
# Agora vamos visualizar as imagens que estão guardadas em cada linha/registro
# do banco de dados. Na página https://www.kaggle.com/c/digit-recognizer/data
# é explicado que cada imagem é constituída de 28x28 pixels. E de fato,
# 28x28 = 784, que é exatamente a quantidade de pixels em cada linha do banco
# de dados.
# Vamos, então transformar a parte dos pixels de cada linha desse banco de dados
# em uma matriz 28x28 que representa, posicionalmente, os pixels da figura
# que o banco de dados representa:

# Carregando as imagens para a matriz img e imprimindo as 10 primeiras matrizes
# (não se preocupe se o resultado não for muito informativo).

df_img = df.iloc[:, 1:] #guardando os pixels das imagens em df_img
size = len(df_img) #size é a quantidade de imagens em df_img
img = df_img.to_numpy().reshape(size, 28, 28) #img guarda todas as matrizes de imagnes no formato 28x28
```

# Instruir Ensinar o computador a reconhecer dígitos numéricos escritos a mão
Como dizíamos, gostaríamos de ensinar o computador a reconhecer o dígito numérico que a imagem alimentada para o computador pretende representar.

Como resolver este problema? Uma abordagem poderia ser aquela da 'programação', que através de procedimentos minunciosamente inseridos no código de execução da tarefa 'instrui' o computador a reconhecer o dígito que a imagem representa. Nesta abordagem, cada detalhe da imagem deve ser explicitamente programada/instruída para o computador.

Por exemplo, se o programador quer instruir o computador a reconhecer o número 1, ele deveria literalmente instruir o computador a reconhcer um traço vertical, as posições em que este traço deveria ocupar na imagem, qual o tamanho desse traço vertical, a posição e inclinação do tracinho que em geral fica posicionado na posição soperior do dígito 1, a expessura dos traços que represetnam o dígito 1,  etc.. etc... etc... E talvez, ainda assim, o computador poderia ter dificuldade em dizer que a primeira imagem do exemplo anterior representa o dígito 1, pois afinal ela não contém um traço vertical, mas sim inclinado. Mas então o programador poderia instruir o computador a reconhecer que o dígito 1 pode ser representado por um traço nem tanto na vertical, etc... etc... e assim por diante.

Estes tantos minuciosos e ainda mais detalhes apenas para reconhecer o dígito 1. Restaria ainda, se bem sucedida, a tarefa de instruir o computador a reconhecer os outros dígitos numéricos. Quantos detalhes inerentes a diferentes caligrafias poderiam escapar à imaginação ou experiência do programador?

Isto, suponho, poderia resultar em um produto final (ou não) extremamente complexo, um código com mais chances de apresentar dificuldades para futuras correções, abertura para mais vulnerabilidades indesejadas e uma maior demanda de recursos humanos especializados. Talvez tantos outros problemas poderiam ser evitados se no lugar de instruir procedimentos para o computador fosse adotada a abordagem do **prendizado de máquina**. Para isso, precisamos saber:

### Como ensinar: Conjunto de Treino e Conjunto de Teste

Para ser justo, seria necessário usar dois conjuntos distintos de imagens de dígitos. Um conjunto, de **treino** seria usado para ensinar o computador a distinguir entre os dígitos. Enquanto outro conjunto de **teste** seria usado para verificar se o computador "aprendeu". Seria ideal que estes dois conjuntos tenham interseção vazia, ou seja, que não compartilhem a mesma imagem em momento algum. Isso porque se você quer testar se o computador aprende a distinguir, por exemplo, entre os dígitos 0 e 1, então seria interessante que os exemplos (imagens) usadas para ensinar o computador não sejam usados na hora da "prova", no conjunto de teste. Assim, seria possível verificar se no contexto de dígitos numéricos o computador "aprendeu" a distinguir entre dígitos, ao invés de ter "decorado" os dígitos associado a cada imagem dadas como exemplos a partir dos quais ele teria aprendido a fazer a tarefa.

```python
from sklearn.model_selection import train_test_split #pacote para separar conjuntos de treino e de teste
Y = df['label'].to_numpy()
X = df.drop(['label'], axis='columns', inplace=False).to_numpy()
X = X.reshape(-1, 28, 28)

X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=0.2, random_state=42)
print('As dimensões dos conjuntos são:')
print('dim(X_train)={}'.format(X_train.shape))
print('dim(Y_train)={}'.format(Y_train.shape))
print('dim(X_test)={}'.format(X_test.shape))
print('dim(Y_test)={}'.format(Y_test.shape))
```

    As dimensões dos conjuntos são:
    dim(X_train)=(33600, 28, 28)
    dim(Y_train)=(33600,)
    dim(X_test)=(8400, 28, 28)
    dim(Y_test)=(8400,)

Os conjuntos que começam com X representam os exemplos de imagens, enquanto que os conjuntos que começam com Y representam os rótulos (labels) das respectivas imagens no conjuntos que começam com X.
Os conjuntos que terminam com train servirão como treino (aprendizado) e os conjuntos que terminam com test servirão como teste (verificação da performance).

Assim, os conjuntos de treino possuem 33600 exemplos, enquanto os conjuntos de teste possuem 8400 exemplos.
Obeserve também que os conjuntos X_train e X_test possuem, para cada exemplo, uma imagem que é constituída por uma matriz de dimensões 28x28.

### O que ensinar: distinguir dígitos a partir de suas intensidades

Para uma primeira forma dessa abordagem, vamos calcular as intensidades das figuras. Observe que cada pixel é um número inteiro 0 e 255. Quanto maior o número que representa determinado pixel, mais branca é a imagem apresentada na posição daquele pixel, e quanto mais próximo de zero é o número representado pelo pixel, mais próximo da cor preta é aquele pixel. Inspecione as matrizes que representam as imagens e as imagens impressas no notebook 00_mnist_1dim_2class.ipynb para comparar essas informações.

Assim, é esperado que quando mais pixels brancos possuir a imagem que representa um número, mais branca é a imagem. Dizemos que a imagem tem mais intensidade. Por exemplo, esperamos que a imagem que representa um típico número 0 tenha mais pixels brancos do que uma imagem que representa o número 1, uma vez que precisaríamos de menos tinta na caneta para escrever 0 do que precisaríamos para escrever 1. Tipicamente, pois podem haver exceções: quanto mais se trata de dígitos escritos à mão, mais ainda escritos por pessoas diferentes. Assim, não está longe do razoável supor que quanto maior a intensidade da figura, mais probabilidade ela tem de representar números como 0, ou 8 do que números como 1 ou 7, por exemplo.

Uma forma de calcular a intensidade seria somar os números (ou itensidades) associados a cada pixel. Quanto mais pixels brancos tiverem a imagem, maior será essa soma.

---

# Cálculo da soma das intensidades dos pixels

Como cada pixel é representado por uma das entradas de uma matriz, A, de dimensão 28x28. Basta somar todos os elementos da matriz que representa a figura (dígito escrito à mão) em questão. Esta soma é:

\begin{align}
    & \biggl( A_{0,0} + A_{0,1} + ... + A_{0, 27} +\\
    & + A_{1,0} + A_{1,1} + ... + A_{1, 27} +\\
     & ... \\
    & + A_{27,0} + A_{27,1} + ... + A_{27, 27} \biggr) \\
\end{align}

Que é igual, usando a notação de *somatório* ($\sum$):

\begin{align}
    & \biggl( A_{0,0} + A_{0,1} + ... + A_{0, 27} +\\
    & + A_{1,0} + A_{1,1} + ... + A_{1, 27} +\\
     & ... \\
    & + A_{27,0} + A_{27,1} + ... + A_{27, 27} \biggr) = \sum_{i=0}^{27}\sum_{j=0}^{27}A_{i,j} \\
\end{align}

```python
# Imprimindo as 10 primeiras imagens do conjunto de treino juntamente com as respectivas somas de intensidades
# de seus pixels.
for i in range(0,5):
    print('Imagem:')
    plt.imshow(X_train[i], interpolation="nearest", cmap='gray')
    plt.show()
    print('Soma das intensidades dos pixels da imagem de rótulo {}: {}'.format(Y_train[i], X_train[i].sum()))
```

# Cálculo de intensidade

Agora que temos uma noção de como usar a soma para ajudar o computador a distinguir entre os dígitos, vamos fazer com que isso faça algum sentido para nós, humanos.

Observe que as somas das intensidades dos pixels são números que variam de $0$ a $255 x 28 x 28$, já que são $28 x 28$ pixels em cada imagem e cada pixel contem um valor inteiro que vai de $0$ a $255$.

Para definir uma intensidade da imagem que vai de $0$ a $255$, basta calcular a *média aritmética* das intensidades de cada pixel da imagem. Meio caminho já está andado uma vez que já calculamos a soma das intensidades de cada pixel. Para calcular a média aritmética das intensidades dos pixels de uma imagem, basta dividir esta soma pela quantidade de pixels, que é $28 x 28 = 784$. Assim, vamos definir a **intensidade** de cada imagem como esta *média aritmética* e denotá-la por $I$:

\begin{align}
    I=\frac{1}{784} & \biggl( A_{0,0} + A_{0,1} + ... + A_{0, 27} +\\
    & + A_{1,0} + A_{1,1} + ... + A_{1, 27} +\\
     & ... \\
    & + A_{27,0} + A_{27,1} + ... + A_{27, 27} \biggr) \\
\end{align}

Ou, usando a notação de somatório para representar a mesma coisa:
    $$I=\frac{1}{784}\sum_{i=0}^{27}\sum_{j=0}^{27}A_{i,j}$$

Você sabe explicar porque a intensidade é um número entre $0$ e $255$? A ideia é usar desigualdades. Aliás, em breve usaremos desigualdades para propor soluções para o problema de distinção entre dígitos numéricos.

Vamos calcular essas intensidades para algumas imagens:

```python
# Função que calcula a intensidade das imagens
def calc_intens(A):
    return A.mean(axis=(1,2))

# Alguns exemplos simples para testar a função calc_intens
# matriz entradas valoradas em 0
ex1 = np.zeros((1,28,28))
# matriz entradas valoradas em 255
ex2 = np.zeros((1,28,28))
ex2[:,:,:] = 255
# matriz entradas da metada direita valoradas em 255 e o restante das entradas valoradas em 0
ex3 = np.zeros((1,28,28))
ex3[:,:,:14] = 255
# matriz entradas da metada esquerda valoradas em 255 e o restante das entradas valoradas em 0
ex4 = np.zeros((1,28,28))
ex4[:,:,14:] = 255

# Imprimindo as imagens representadas pelas matrizes ex1, ex2, ex3 e ex4 em conjunto com suas intensidades
print("Exemplo 1:")
plt.imshow(ex1[0], interpolation="nearest", cmap='gray', vmin=0, vmax=255)
plt.show()
print("Intensidade: ", calc_intens(ex1), '\n')

print("Exemplo 2:")
plt.imshow(ex2[0], interpolation="nearest", cmap='gray', vmin=0, vmax=255)
plt.show()
print("Intensidade: ", calc_intens(ex2), '\n')

print("Exemplo 3:")
plt.imshow(ex3[0], interpolation="nearest", cmap='gray', vmin=0, vmax=255)
plt.show()
print("Intensidade: ", calc_intens(ex3), '\n')

print("Exemplo 4:")
plt.imshow(ex4[0], interpolation="nearest", cmap='gray', vmin=0, vmax=255)
plt.show()
print("Intensidade: ", calc_intens(ex4), '\n')
```

```python
# Calculo de intensidades das imagens com dígitos numéricos
I_train = calc_intens(X_train)

# Imprimindo as 10 primeiras imagens do conjunto de treino juntamente com as respectivas somas de intensidades
# de seus pixels.
for i in range(0,5):
    print('Imagem:')
    plt.imshow(X_train[i], interpolation="nearest", cmap='gray')
    plt.show()
    print('Intensidades da imagem de rótulo {}: {:.2f}'.format(Y_train[i], I_train[i]))
```

---

### Usando desigualdade para distinguir imagens de dígitos numéricos

Algumas duplas de dígitos parecem ser razoavelmente distinguíveis a partir de desigualdades. A ideia é que a representação gráfica das intensidades dos dígitos numéricos parecem, em alguns casos, razoavelmente separáveis de acordo com algum parâmetro entre 0 e 255.

Por exemplo, parece que os dígitos 1 e 0 podem ser razoavelmente separáveis com algum parâmetro na região em torno de 30 de intensidade:

```python
# Esboçando uma amostra de 370 pontos de X_train que representam intensidades para os dígitos 1 e 0.
n_train = 370
digit1=1
digit2=0

#coordenadas dos primeiros dígitos em X_train correspondentes a digit1 e digit2
coord1_train = np.argwhere(Y_train==digit1)[:n_train]
coord2_train = np.argwhere(Y_train==digit2)[:n_train]

#intensidades dos primeiros dígitos em X_train correspondentes a digit1 e digit2
X_train1 = X_train[coord1_train].reshape(-1, 28, 28)
X_train2 = X_train[coord2_train].reshape(-1, 28, 28)
I1_train = calc_intens(X_train1)
I2_train = calc_intens(X_train2)

#figura
plt.scatter(I1_train, 0.5*np.ones(n_train), label=digit1, alpha=1, marker='.', s=0.1)
plt.scatter(I2_train, 0.52*np.ones(n_train), label=digit2, alpha=1, marker='.', s=0.1)
plt.ylim(0.0, 1.0)
plt.legend(loc=2, markerscale=10)
plt.savefig('mnist1dim'+str(digit1)+'_'+str(digit2)+'.eps', format='eps')
plt.show()
```

Agora, vamos escolher um parâmetro de decisão de separação entre as intensidades dos dígitos 1 e 0 em como alpha=30 e observar o que acontece:

```python
alpha = 30

# quantidade de imagens correspondentes aos dígitos 0 e 1 maiores e menores do que alpha
menor1_train = (I1_train < alpha).sum()
maior1_train = (I1_train > alpha).sum()
menor2_train = (I2_train < alpha).sum()
maior2_train = (I2_train > alpha).sum()

print('{} imagens correspondentes ao dígito {} tem intensidade menor do que {}'.format(menor1_train, digit1, alpha))
print('{} imagens correspondentes ao dígito {} tem intensidade Maior do que {}'.format(maior1_train, digit1, alpha))
print('{} imagens correspondentes ao dígito {} tem intensidade menor do que {}'.format(menor2_train, digit2, alpha))
print('{} imagens correspondentes ao dígito {} tem intensidade Maior do que {}'.format(maior2_train, digit2, alpha))

print('Então:')
print('{:.1f}%'.format(100*(menor1_train/(menor1_train+maior1_train))), 'das imagens com dígito ', digit1, 'têm intensidade menor do que ', alpha)
print('{:.1f}%'.format(100*(maior2_train/(menor2_train+maior2_train))), 'das imagens com dígito ', digit2, 'têm intensidade maior do que ', alpha, '\n')

print('Alem disso:')

menor1_train = (I1_train < alpha).sum()
maior2_train = (I2_train > alpha).sum()
maior1_train = (I1_train > alpha).sum()
menor2_train = (I2_train < alpha).sum()

acc_trainA = (menor1_train+maior2_train)/(2*n_train)
acc_trainB = (maior1_train+menor2_train)/(2*n_train)
acc_train = max(acc_trainA, acc_trainB)
#acc_train = (menor1_train+maior2_train)/(2*n_train)
print('Esta proposta tem acurácia de {:.1f}% para distinguir os dígitos {} e {}'.format(acc_train*100, digit1, digit2))
```

    349 imagens correspondentes ao dígito 1 tem intensidade menor do que 30
    21 imagens correspondentes ao dígito 1 tem intensidade Maior do que 30
    29 imagens correspondentes ao dígito 0 tem intensidade menor do que 30
    341 imagens correspondentes ao dígito 0 tem intensidade Maior do que 30
    Então:
    94.3% das imagens com dígito  1 têm intensidade menor do que  30
    92.2% das imagens com dígito  0 têm intensidade maior do que  30

    Alem disso:
    Esta proposta tem acurácia de 93.2% para distinguir os dígitos 1 e 0

# Distinção entre os dígitos 3 e 8 por meio das intensidades:

Para uma primeira aproximação, usando apenas desigualdade e o olho, não está tão ruim. Mas é até esperado que as intensidades entre os dígitos 1 e 0 não sejam tão parecidas. E talvez seja por isso que essa simples proposta possa ter funcionado razoavelmente (bem) para distinguir imagens com dígitos 1 e 0.

No entanto, os dígitos 3 e 8 possuem intensidades não tão distintas quanto aquelas entre os dígitos 1 e 0. Vamos tentar realizar o mesmo procedimento para os dígitos 3 e 8:

```python
# Esboçando uma amostra de 370 pontos de X_train que representam intensidades para os dígitos 3 e 8.
n_train = 370
digit1=3
digit2=8

#coordenadas dos primeiros dígitos em X_train correspondentes a digit1 e digit2
coord1_train = np.argwhere(Y_train==digit1)[:n_train]
coord2_train = np.argwhere(Y_train==digit2)[:n_train]

#intensidades dos primeiros dígitos em X_train correspondentes a digit1 e digit2
X_train1 = X_train[coord1_train].reshape(-1, 28, 28)
X_train2 = X_train[coord2_train].reshape(-1, 28, 28)
I1_train = calc_intens(X_train1)
I2_train = calc_intens(X_train2)

#figura
plt.scatter(I1_train, 0.5*np.ones(n_train), label=digit1, alpha=1, marker='.', s=0.1)
plt.scatter(I2_train, 0.52*np.ones(n_train), label=digit2, alpha=1, marker='.', s=0.1)
plt.ylim(0.0, 1.0)
plt.legend(loc=2, markerscale=10)
plt.savefig('mnist1dim'+str(digit1)+'_'+str(digit2)+'.eps', format='eps')
plt.show()
```

Já parece bem mais difícil estipular um parâmetro que separe razoavelmente as intensidades das imagens dos dígitos 3 e 8. É difícil, inclusive saber se as intensidades associadas às imagens dos dígitos 8 estão mais à esquerda ou à direita daquelas associadas ao dígito 3.

Para saber qual está mais à esquerda ou direita, eu proponho calcular a média das intensidades para as imagens correspondentes aos dígitos 8 e 3.

```python
m1 = I1_train.mean()
m2 = I2_train.mean()

print('A média da intensidade do dígito {} é {:.2f}'.format(digit1, m1))
print('A média da intensidade do dígito {} é {:.2f}'.format(digit2, m2))
```

    A média da intensidade do dígito 3 é 35.01
    A média da intensidade do dígito 8 é 38.21

Vamos escolher a média entre essas duas médias de intensidades para o valor de alpha com o intuito de ver quão mal bem ele separa as intensidades entre os dígitos 3 e 8.

```python
alpha = (m1+m2)/2
print(' A média entre essas duas intensidades é {:.2f}'.format(alpha))
```

     A média entre essas duas intensidades é 36.61

E agora uma representação gráfica com os pontos associados às intensidades dos dígitos 3 e 8, além de três pontos que representam as médias aritméticas comentadas agora a pouco:

```python
# Esboçando uma amostra de 370 pontos que representam intensidades para os dígitos 3 e 8, juntamente com as médias aritméticas das
# intensidades de cada um dos dígitos 3 e 8, além da média aritmética entre estas duas médias aritméticas anteriores.

#figura
plt.scatter(I1_train, 0.5*np.ones(n_train), label=digit1, alpha=1, marker='.', s=0.1)
plt.scatter(I2_train, 0.52*np.ones(n_train), label=digit2, alpha=1, marker='.', s=0.1)
plt.plot([m2, m2], [0.51, 0.53], 'orange', marker='|')
plt.plot([m1, m1], [0.49, 0.51], 'blue', marker='|')
plt.plot([alpha, alpha], [0.49, 0.53], 'black', marker='|')
plt.arrow(alpha, 0.51, 5, 0, head_width=0.005, head_length=1, fc='k', ec='k')
plt.ylim(0.4, 0.6)
plt.legend(loc=3, markerscale=10)
plt.savefig('medias_mnist1dim'+str(digit1)+'_'+str(digit2)+'.eps', format='eps')
plt.show()
```

```python
# quantidade de imagens correspondentes aos dígitos 0 e 1 maiores e menores do que alpha
menor1_train = (I1_train < alpha).sum()
maior1_train = (I1_train > alpha).sum()
menor2_train = (I2_train < alpha).sum()
maior2_train = (I2_train > alpha).sum()

print('{} imagens correspondentes ao dígito {} tem intensidade menor do que {}'.format(menor1_train, digit1, alpha))
print('{} imagens correspondentes ao dígito {} tem intensidade Maior do que {}'.format(maior1_train, digit1, alpha))
print('{} imagens correspondentes ao dígito {} tem intensidade menor do que {}'.format(menor2_train, digit2, alpha))
print('{} imagens correspondentes ao dígito {} tem intensidade Maior do que {}'.format(maior2_train, digit2, alpha))

print('Então:')
print('{:.1f}%'.format(100*(menor1_train/(menor1_train+maior1_train))), 'das imagens com dígito ', digit1, 'têm intensidade menor do que ', alpha)
print('{:.1f}%'.format(100*(maior2_train/(menor2_train+maior2_train))), 'das imagens com dígito ', digit2, 'têm intensidade maior do que ', alpha, '\n')

print('Alem disso:')
menor1_train = (I1_train < alpha).sum()
maior2_train = (I2_train > alpha).sum()
maior1_train = (I1_train > alpha).sum()
menor2_train = (I2_train < alpha).sum()

acc_trainA = (menor1_train+maior2_train)/(2*n_train)
acc_trainB = (maior1_train+menor2_train)/(2*n_train)
acc_train = max(acc_trainA, acc_trainB)
#acc_train = (menor1_train+maior2_train)/(2*n_train)
print('Esta proposta tem acurácia de {:.1f}% para distinguir os dígitos {} e {}'.format(acc_train*100, digit1, digit2))
```

    218 imagens correspondentes ao dígito 3 tem intensidade menor do que 36.60692050468836
    152 imagens correspondentes ao dígito 3 tem intensidade Maior do que 36.60692050468836
    176 imagens correspondentes ao dígito 8 tem intensidade menor do que 36.60692050468836
    194 imagens correspondentes ao dígito 8 tem intensidade Maior do que 36.60692050468836
    Então:
    58.9% das imagens com dígito  3 têm intensidade menor do que  36.60692050468836
    52.4% das imagens com dígito  8 têm intensidade maior do que  36.60692050468836

    Alem disso:
    Esta proposta tem acurácia de 55.7% para distinguir os dígitos 3 e 8

---

# Cálculo de Simetria lateral

Dessa vez não ficou bom. Ficou bem mais difícil de distinguir entre os dígitos 3 e 8. Talvez seja porque eles possuem intensidades parecidas. Se esta proposta não consegue distinguir de modo razoavelmente satisfatório entre os dígitos 3 e 8, mais ainda difícil deve ser dizer qual dígito foi fornecido para o computador, se usarmos proposta semelhante. É preciso fazer algo para melhorar a proposta.

Observe que, aparentemente, os dígitos 3 e 8 podem ser razoavalmente distinguidos a partir da simetria lateral. Enquanto o dígito 3 parece tender a ter intensiade maior na região do direita das imagens, o dígito 8 parece ter intensidades mais balanceadas entre as regiões direita e esquerda da imagem. Vamos definir em seguida a simetria lateral da imagem como a soma das intensidades na região esquerda subtraída pela soma das intensidades na região direita. Para isso delimitamos a região esquerda como os pixels que estão representados entre as colunas 0 e 13 da matriz que representa a imagem do dígito. Similarmente a região direita será composta pelas colunas entre 14 e 27 da matriz que representa o dígito. Assim, propomos a seguinte definição para a simetria lateral das imagens:

\begin{align}
    S= & \biggl( (A_{0,0} + A_{0,1} + A_{0,13}) - ( A_{0,14} + A_{0,15} + ... + A_{0, 27}) +\\
    & (A_{1,0} + A_{1,1} + A_{1,13}) - ( A_{1,14} + A_{1,15} + ... + A_{1, 27}) +\\
     & ... \\
    & (A_{27,0} + A_{27,1} + A_{27,13}) - ( A_{27,14} + A_{27,17} + ... + A_{27, 27} ) \biggr) \\
\end{align}

Ou, novamente, denotando as somas com o somatório:

$$S = \sum_{i=0}^{27} \left( \sum_{j=0}^{13}A_{i,j} - \sum_{j=14}^{27}A_{i,j} \right)$$

Dessa forma, quando mais as intensidades dos pixels de uma imagem estiverem do lado esquerdo, maior será o número, S, definido como a simetria lateral da imagem. Alguns exemplos:

```python
# Função que calcula a Simetria Lateral das imagens

def enquadra_digito(img): # parece que as figuras já estão bem centralizadas?
    #print(img)
    v_cumsum = img.sum(axis=1).cumsum(axis=0)
    v_top = np.argwhere(v_cumsum == v_cumsum.min())[-1,0]
    v_botton = np.argwhere(v_cumsum == v_cumsum.max())[0,0]

    h_cumsum = img.sum(axis=0).cumsum(axis=0)
    h_left = np.argwhere(h_cumsum == h_cumsum.min())[-1,0]
    h_right = np.argwhere(h_cumsum == h_cumsum.max())[0,0]

    #print(img[v_top+1:v_botton+1, h_left+1:h_right+1])
    return img[v_top+1:v_botton+1, h_left+1:h_right+1]

# Função que calcula a Simetria Lateral das imagens
def sim_lat(A):
    result = []
    for n in range(A.shape[0]):
        img = enquadra_digito(A[n,:,:])
        #vtop_half = img.shape[0]//2
        #vdown_half = (img.shape[0]+1)//2
        hleft_half = img.shape[1]//2
        hright_half = (img.shape[1]+1)//2

        result = result + [(img[:,:hleft_half].sum() - img[:,hright_half:].sum())/100]
    #return A[:,:,:14].sum(axis=(1,2)) - A[:,:,14:].sum(axis=(1,2))
    return np.array(result)

# Alguns exemplos simples para testar a função calc_intens
# matriz entradas valoradas em 0
ex1 = np.zeros((1,28,28))
# matriz entradas valoradas em 255
ex2 = np.zeros((1,28,28))
ex2[:,:,:] = 255
# matriz entradas da metada direita valoradas em 255 e o restante das entradas valoradas em 0
ex3 = np.zeros((1,28,28))
ex3[:,:,:14] = 255
ex3[:,:,-1:] = 255
# matriz entradas da metada esquerda valoradas em 255 e o restante das entradas valoradas em 0
ex4 = np.zeros((1,28,28))
ex4[:,:,14:] = 255
ex4[:,:,:1] = 255

# Imprimindo as imagens representadas pelas matrizes ex1, ex2, ex3 e ex4 em conjunto com suas Simetrias Laterais
print("Exemplo 1:")
plt.imshow(ex1[0], interpolation="nearest", cmap='gray', vmin=0, vmax=255)
plt.show()
print("Simetria Lateral: ", sim_lat(ex1), '\n')

print("Exemplo 2:")
plt.imshow(ex2[0], interpolation="nearest", cmap='gray', vmin=0, vmax=255)
plt.show()
print("Simetria Lateral: ", sim_lat(ex2), '\n')

print("Exemplo 3:")
plt.imshow(ex3[0], interpolation="nearest", cmap='gray', vmin=0, vmax=255)
plt.show()
print("Simetria Lateral: ", sim_lat(ex3), '\n')

print("Exemplo 4:")
plt.imshow(ex4[0], interpolation="nearest", cmap='gray', vmin=0, vmax=255)
plt.show()
print("Simetria Lateral: ", sim_lat(ex4), '\n')
```

Obeserve que a simetria lateral das figuras que são lateralmente simétricas é 0. Observe também que a simetria do exemplo 3 é 99960 e que a simetria do exemplo 4 é -99960. A simetria do exemplo 3 é muito maior do que a do exemplo 4 porque região esquerda do exemplo 3 é toda acesa (branca ou com muita intensidade), enquanto a região direita deste mesmo exemplo é toda apagada (preta ou com pouca intensidade); enquanto o contrário ocorre com o exemplo 4.

```python
# Calculo de simetrias das imagens com dígitos numéricos
SL_train = sim_lat(X_train)

# Imprimindo as 10 primeiras imagens do conjunto de treino juntamente com as respectivas Simetrias Laterais
for i in range(0,10):
    print('Imagem:')
    plt.imshow(X_train[i], interpolation="nearest", cmap='gray')
    plt.show()
    print('Simetria Lateral da imagem de rótulo {}: {:.2f}'.format(Y_train[i], SL_train[i]))
```

# Distinção entre os dígitos 3 e 8 por meio da simetria lateral:

Percebe como as simetrias tendem a assumir valores maiores para números como 0 e 1? Ainda está difícil saber se ela distingui bem entre os números 3 e 8. Vamos verificar:

```python
# Esboçando uma amostra de 370 pontos de X_train que representam Simetrias Laterais para os dígitos 3 e 8.
n_train = 370
digit1=3
digit2=8

#coordenadas dos primeiros dígitos em X_train correspondentes a digit1 e digit2
coord1_train = np.argwhere(Y_train==digit1)[:n_train]
coord2_train = np.argwhere(Y_train==digit2)[:n_train]

#Simetrias Laterais dos primeiros dígitos em X_train correspondentes a digit1 e digit2
X_train1 = X_train[coord1_train].reshape(-1, 28, 28)
X_train2 = X_train[coord2_train].reshape(-1, 28, 28)
SL1_train = sim_lat(X_train1)
SL2_train = sim_lat(X_train2)

#figura
plt.scatter(SL1_train, 0.5*np.ones(n_train), label=digit1, alpha=1, marker='.', s=0.1)
plt.scatter(SL2_train, 0.52*np.ones(n_train), label=digit2, alpha=1, marker='.', s=0.1)
plt.ylim(0.0, 1.0)
plt.legend(loc=2, markerscale=10)
plt.savefig('mnist1dim'+str(digit1)+'_'+str(digit2)+'.eps', format='eps')
plt.show()
```

# Distinção entre os dígitos 3 e 8 por meio das simetrias:

Melhorou bastante, não? Percebe como os pontos que representam o dígito 8 tendem a ficar mais à direito do que os pontos que representam o dígito 3? Isto confirma nossa intuição de que as imagens correspondentes ao dígito 8 tendem a ter mais intensidades nos pixels concentrados à esquerda em relação ao dígito 3. E isso porque a simetria lateral, da forma que a definimos, tende a ser maior quanto maior forem os valores dos pixels na metade esquerda da imagem. Lembre-se da definição:

$$S = \sum_{i=0}^{27} \left( \sum_{j=0}^{13}A_{i,j} - \sum_{j=14}^{27}A_{i,j} \right)$$

S é a soma das intensidades na metade esquerda subtraída pela soma das intensidades na metade direita das matrizes que representam as imagens.

Vamos repetir os procedimentos que tinhamos feito para as intensidades dos dígitos 3 e 8, mas dessa vez com a Simetria Lateral no lugar das Intensidades:

```python
m1 = SL1_train.mean()
m2 = SL2_train.mean()

print('A média das Simetrias Laterais do dígito {} é {:.2f}'.format(digit1, m1))
print('A média das Simetrias Laterais do dígito {} é {:.2f}'.format(digit2, m2))

alpha = (m1+m2)/2
print(' A média entre essas médias é {:.2f}'.format(alpha))

```

    A média das Simetrias Laterais do dígito 3 é -61.51
    A média das Simetrias Laterais do dígito 8 é 29.33
     A média entre essas médias é -16.09

Esboçando uma amostra de 370 pontos que representam simetrias laterais para os dígitos 3 e 8, juntamente com as médias aritméticas das simetrias laterais de cada um dos dígitos 3 e 8, além da média aritmética entre estas duas médias aritméticas anteriores.

```python
plt.scatter(SL1_train, 0.5*np.ones(n_train), label=digit1, alpha=1, marker='.', s=0.1)
plt.scatter(SL2_train, 0.52*np.ones(n_train), label=digit2, alpha=1, marker='.', s=0.1)

plt.plot([m2, m2], [0.51, 0.53], 'orange', marker='|')
plt.plot([m1, m1], [0.49, 0.51], 'blue', marker='|')
plt.plot([alpha, alpha], [0.49, 0.53], 'black', marker='|')
plt.arrow(alpha, 0.51, 50, 0, head_width=0.005, head_length=10, fc='k', ec='k')
#plt.scatter(im, 0.51*np.ones(sample_size), label=jdigit, alpha=1, marker='|', s=0.1)
#plt.scatter(jm, 0.51*np.ones(sample_size), label=jdigit, alpha=1, marker='|', s=0.1)
#plt.scatter(jm, 0.51*np.ones(sample_size), label=jdigit, alpha=1, marker='|', s=0.1)
plt.ylim(0.4, 0.6)
plt.legend(loc=3, markerscale=10)
plt.savefig('medias_mnist1dim'+str(digit1)+'_'+str(digit2)+'.eps', format='eps')
plt.show()
```

```python
# quantidade de imagens correspondentes aos dígitos digit1 e digit2 maiores e menores do que alpha
menor1_train = (SL1_train < alpha).sum()
maior1_train = (SL1_train > alpha).sum()
menor2_train = (SL2_train < alpha).sum()
maior2_train = (SL2_train > alpha).sum()

print('{} imagens correspondentes ao dígito {} tem Simetria Lateral menor do que {:.2f}'.format(menor1_train, digit1, alpha))
print('{} imagens correspondentes ao dígito {} tem Simetria Lateral Maior do que {:.2f}'.format(maior1_train, digit1, alpha))
print('{} imagens correspondentes ao dígito {} tem Simetria Lateral menor do que {:.2f}'.format(menor2_train, digit2, alpha))
print('{} imagens correspondentes ao dígito {} tem Simetria Lateral Maior do que {:.2f}'.format(maior2_train, digit2, alpha))

print('Então:')
print('{:.1f}%'.format(100*(menor1_train/(menor1_train+maior1_train))), 'das imagens com dígito ', digit1, 'têm Simetria Lateral menor do que ', alpha)
print('{:.1f}%'.format(100*(maior2_train/(menor2_train+maior2_train))), 'das imagens com dígito ', digit2, 'têm Simetria Lateral Maior do que ', alpha, '\n')

print('Alem disso:')
menor1_train = (SL1_train < alpha).sum()
maior2_train = (SL2_train > alpha).sum()
maior1_train = (SL1_train > alpha).sum()
menor2_train = (SL2_train < alpha).sum()

acc_trainA = (menor1_train+maior2_train)/(2*n_train)
acc_trainB = (maior1_train+menor2_train)/(2*n_train)
acc_train = max(acc_trainA, acc_trainB)

#acc_train = (menor1_train+maior2_train)/(2*n_train)
print('Esta proposta tem acurácia de {:.1f}% para distinguir os dígitos {} e {}'.format(acc_train*100, digit1, digit2))
```

    314 imagens correspondentes ao dígito 3 tem Simetria Lateral menor do que -16.09
    56 imagens correspondentes ao dígito 3 tem Simetria Lateral Maior do que -16.09
    60 imagens correspondentes ao dígito 8 tem Simetria Lateral menor do que -16.09
    310 imagens correspondentes ao dígito 8 tem Simetria Lateral Maior do que -16.09
    Então:
    84.9% das imagens com dígito  3 têm Simetria Lateral menor do que  -16.093364864864867
    83.8% das imagens com dígito  8 têm Simetria Lateral Maior do que  -16.093364864864867

    Alem disso:
    Esta proposta tem acurácia de 84.3% para distinguir os dígitos 3 e 8

---


```python
import tangent
```

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

```python
x = np.linspace(-1, 3, 10)
```

```python
f(x)
```

    array([-5.        , -1.65294925, -0.14951989,  0.03703704, -0.56652949,
           -1.43347051, -2.03703704, -1.85048011, -0.34705075,  3.        ])

```python
fprime0 = tangent.grad(f)
```

```python
fprime0(1)
```

    -2.0

```python
fprime = np.vectorize(fprime0)
```

```python
fprime(x)
```

    array([ 10.        ,   5.25925926,   1.7037037 ,  -0.66666667,
            -1.85185185,  -1.85185185,  -0.66666667,   1.7037037 ,
             5.25925926,  10.        ])

---

## indefinite integral

= anti derivative

$$ \dfrac{dF(x)}{dx} = f(x)
\;\;\leftrightarrow \;\; F(x) = \int f(x)dx + C $$

- ex
$$ \int 3x^2 dx= x^3 + c$$

$$ \int (3x^2-6x+1)dx = x^3 -3x^2 + x + c $$

$$\int(2 + 6x+ 4\exp(x) + \dfrac{5}{x})dx = 2x + 3x^2 + 4\exp (x) + 5\log x + c$$

$$ \int \dfrac{2x}{x^2-1}dx = \log (x^2-1) + c$$

---

## derivative of partial differentiation

$$ \dfrac{\partial F_1(x, y)}{\partial x} = f(x, y) \;\; \leftrightarrow \;\;
F_1(x, y) = \int f(x, y)dx + C(y) $$

$$ \dfrac{\partial F_2(x, y)}{\partial x} = f(x, y) \;\; \leftrightarrow \;\;
F_2(x, y) = \int f(x, y)dy + C(x) $$

- ex
$$ \int (1+ xy)dx = x + \dfrac{1}{2}x^2y + C(y)$$
$$ \int xy\exp(x^2+y^2)dx = \dfrac{1}{2}y\exp(x^2+y^2) + C(y) $$

---

## $n$th derivative, multiple integration
- also called higher derivative

$$ \dfrac{\partial^2 F_3(x)}{\partial x\partial y} = f(x, y) \;\;\leftrightarrow \;\;
F_3(x, y) = \iint f(x, y)dxdy $$

- ex
$$\iint xy \exp({x^2 + y^2}) dxdy
\;\; = \dfrac{1}{4}\exp(x^2+y^2)$$

---

## integration by SymPy

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

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

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

```python
F_x = sympy.integrate(f, x)
F_x
```

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

```python
sympy.integrate(F_x, y)
```

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

---

## definite integral

$$ \int_{a}^{b} f(x)dx $$

- Fundamental Theorem of Calculus

$$ \int_{a}^{b} f(x)dx = F(b) - F(a) $$

- practice (with sympy)

$$ \int_{0}^{2} (x^3 -3x^2+ x +6)dx $$

```python
f = x **3 - 3*x**2 + x + 6
f
```

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

```python
# indefinite integration
F = sympy.integrate(f)
F
```

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

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

$$10.0$$

```python
# numuerical integration
def f(x):
    return x **3 - 3*x**2 + x + 6
sp.integrate.quad(f, 0, 2) # ( ans, error)

```

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

---

## multiple integral

- double integral
$$ volume = \int_{y=c}^{y=d} \int_{x=a}^{x=b} f(x, y)dxdy $$

- integral
    - f(x, y) is 2-dimensional function, but choose one variable and take the other one to constant(or think that fix y)
    - ex
     $$ f(x, y) = 4x^2 + 4xy + y^2 $$
     $$ f(x; y) = 4x^2 + (4y)x + (y^2) $$

- practice

$$ \int_{-1}^1\int_{-1}^1 \left(1+xy\right) dxdy $$

- Fundamental Theorem

```python
import sympy
```

```python
from sympy import Matrix
```

```python
x1, x2 = sympy.symbols('x1 x2')
X = Matrix([sympy.log(x1-1), x2**3])
X
```

$$\left[\begin{matrix}\log{\left (x_{1} - 1 \right )}\\x_{2}^{3}\end{matrix}\right]$$

```python
X2= Matrix([2*x1**2, sympy.exp(x2-1)])
X2
```

$$\left[\begin{matrix}2 x_{1}^{2}\\e^{x_{2} - 1}\end{matrix}\right]$$

```python
Y = Matrix([[x1], [x2]])
print(X, Y)
X.jacobian(Y)
```

    Matrix([[log(x1 - 1)], [x2**3]]) Matrix([[x1], [x2]])

$$\left[\begin{matrix}\frac{1}{x_{1} - 1} & 0\\0 & 3 x_{2}^{2}\end{matrix}\right]$$

```python
x, y = sympy.symbols('x y')
f = 1 + x*y
F_x = sympy.integrate(f, x) # x integrate
X = (F_x.subs(x, 1) - F_x.subs(x, -1)).evalf()

F_xy = sympy.integrate(X, y) # y integrate
Y = (F_xy.subs(y, 1) - F_xy.subs(y, -1)).evalf()
```

```python
X
```

$$2.0$$

```python
Y
```

$$4.0$$

- numerical integration

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

sp.integrate.nquad(f, [[-1, 1], [-1, 1]])
```

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

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

print(sp.integrate.dblquad(f, 1, np.inf, lambda x:0, lambda x: np.inf))
sp.integrate.nquad(f, [[0, np.inf], [1, np.inf]])
```

    (0.4999999999999961, 1.0684538743333441e-08)

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