# Restricted Boltzmann Machines

### Energy-based model
Energy-based models associate a scalar energy to each configuration of the variables of interest. Low energy is more desirable. The probability distribution based on an energy function can be defined as follows
$$ \Pr(x) = \frac{\exp (-E(x))}{Z}\,,$$
where $Z = \sum_{x} \exp (-E(x))$ denotes the normalization factor or **partition function**.

### Restricted Boltzmann Machine

Restricted Boltzmann Machine (RBM) has an efficient training algorithm. In order to increase the expressive power of the model, we do not observe the example $x$ fully, we also want to introduce some non-observed variables.  Consider an observed part $x$ and a hidden part $h$. We can then write:
$$\Pr(x) = \sum_h \frac{\exp (-E(x, h))}{Z} \,.$$

In RBM, the energy function is defined as
$$
E(x, h) = -a^\top x - b^\top h - x^\top W h \,.
$$

To make RBM as an energy-based model, the free energy function is computed as follows
$$
\begin{align}
F(x) &= -\log \sum_h \exp (-E(x, h)) \\
     &= -a^\top x - \sum_j \log (1 + \exp(W^{\top}_jx + b_j))\,.
\end{align}
$$

We have an tractable expression for the conditional probabilities
$$
\Pr (h|x) = \prod_i \Pr (h_i | x)
$$

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

import torch
import torch.nn.functional as F
from torchvision import datasets, transforms
from torchvision.utils import make_grid

from rbm import RBM

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
print(f"Available device: {device}")
```

    Available device: cuda:0

Make some configurations

```python
batch_size = 64 # batch size
n_epochs = 25 # number of epochs
lr = 0.01 # learning rate
n_hidden = 128 # number of units in the hidden layer
n_visible = 784 # input size
```

Create a RBM model

```python
# create a Restricted Boltzmann Machine
model = RBM(n_visible, n_hidden, k=1, cuda=True)
```

    CUDA device was selected successfully.
    PyTorch target device: cuda:0

Prepare the data set

```python
train_loader = torch.utils.data.DataLoader(
    datasets.MNIST('./out', train=True, download=True,
                   transform=transforms.Compose([
                       transforms.ToTensor()
                   ])),
    batch_size=batch_size)
```

Then train the model.

```python
def train(model, train_loader, input_size, n_epochs=n_epochs):
    for epoch in range(n_epochs):
        epoch_error = 0.0

        for batch, _ in train_loader:
            batch = batch.view(-1, input_size)  # flatten input data
            batch = batch.to(device)
            batch_error = model.contrastive_divergence(batch)
            epoch_error += batch_error

        print(f'Epoch: {epoch + 1} | Error: {epoch_error:.4f}')
    return model
```

```python
model = train(model, train_loader, n_visible, n_epochs=n_epochs)
```

    Epoch: 1 | Error: 97.9001
    Epoch: 2 | Error: 64.1085
    Epoch: 3 | Error: 52.9415
    Epoch: 4 | Error: 47.0880
    Epoch: 5 | Error: 43.8372
    Epoch: 6 | Error: 41.5832
    Epoch: 7 | Error: 39.9684
    Epoch: 8 | Error: 38.7417
    Epoch: 9 | Error: 37.6557
    Epoch: 10 | Error: 36.6814
    Epoch: 11 | Error: 35.7948
    Epoch: 12 | Error: 35.0488
    Epoch: 13 | Error: 34.4137
    Epoch: 14 | Error: 33.8698
    Epoch: 15 | Error: 33.3766
    Epoch: 16 | Error: 32.9624
    Epoch: 17 | Error: 32.5494
    Epoch: 18 | Error: 32.1709
    Epoch: 19 | Error: 31.7928
    Epoch: 20 | Error: 31.5103
    Epoch: 21 | Error: 31.2001
    Epoch: 22 | Error: 30.9243
    Epoch: 23 | Error: 30.6535
    Epoch: 24 | Error: 30.4295
    Epoch: 25 | Error: 30.2275

```python
images = next(iter(train_loader))[0]
v, v_rec = model.forward(images.to(device).view(-1, 784), train=False)
```

```python
# show the real images
def show_and_save(img, file_name):
    r"""Show and save the image.

    Args:
        img (Tensor): The image.
        file_name (Str): The destination.

    """
    npimg = np.transpose(img.to('cpu').numpy(), (1, 2, 0))
    f = "./%s.png" % file_name
    plt.imshow(npimg, cmap='gray')
    plt.imsave(f, npimg)
```

```python
show_and_save(make_grid(v.view(batch_size, 1, 28, 28).data), 'out/real')
```

```python
# show the generated images
show_and_save(make_grid(v_rec.view(batch_size, 1, 28, 28).data), 'out/generated')
```

How one image is factorized through the hidden variables

```python
for j in range(18, 28):
    n_sample = 10
    d = images[j:j+1]

    V = torch.sigmoid(F.linear(d.view(1, -1), model.weights.to('cpu').t(), model.h_bias.to('cpu')))
    v, o = torch.sort(V.view(-1))

    fig, ax = plt.subplots(1, n_sample + 1, figsize=(3*(1 + n_sample),3))
    ax[0].imshow(d.view(28, 28).numpy(), cmap='gray')
    ax[0].set_title('Original image')

    for k, i in enumerate(o[-n_sample:].numpy()):
        weights_i = model.weights.t()[i].to('cpu')
        f = weights_i.view(28, 28).data.numpy()
        ax[k + 1].imshow(f, cmap='gray')
        ax[k + 1].set_title('p=%.2f'% V[0][i].item())

    plt.savefig(f'out/factor_{j}.png', dpi=150)
```

```python

```


# Scalarization methods in multi-objective optimization
> An illustration of different scalarization methods in multi-objective optimization.

- toc: true
- categories: [multi-objective optimization]
- image: images/scalarization.png

In multi-objective optimization we aim to optimize $M$ different and generally conflicting objectives
\begin{align}
\min_{x \in \mathcal{X}} ~ &f_{m}(x) \quad m = 1,..,M
\end{align}
where the design variables $x$ may be subject to (in-)equality constraints that are satisfied in the feasible set $\mathcal{X}$.
Since the objectives are generally conflicting there is not a single solution that simultaneously optimizes all objectives.
Instead we need to find a suitable compromise specific to our preferences.
This can be done by either
1. an a priori formulated scalarization of the objectives,
2. an iteractive search in the objective space, or
3. an a posteriori selection on the Pareto front of non-dominated solutions.

All three approaches involve a scalarization (also called decomposition) of the multiple objectives into a single objective.
In the following we compare different scalarization methods and discuss them from the perspective of searching for the Pareto front for an a posteriori selection.

Many of the methods involve specifiying an ideal / utopian reference point $z^*$.
In practice this point can be found by seperately optimizing the individual objectives.
The objective space can then be shifted by $f_m'(x) = f_m(x) - z_m^* $ so that it's bounded to the positive values.
Hence, the reference point is omitted in the following.

## Example problems

As examples we'll look at the simplest class of multi-objective problem, involving one design variable and two conflicting objectives.
Having just one variable will make the optimization problem easier, without affecting the interpretation of the Pareto exploration methods itself. Note however that since we only have one variable the feasible solutions form a 1-dimensional manifold (a line) in the 2-dimensional objective space.
Having just two objectives allows us to conviently plot the objective space.

We'll consider the following three cases:

```python
#collapse-hide
import numpy as np
import matplotlib.pyplot as plt
import pygmo

def plot_design_space(problem, ax=None):
    if ax is None:
        fig, ax = plt.subplots(1)
    X = np.linspace(-1, 1, 1000)
    F1, F2 = problem(X).T
    ax.plot(X, F1, label='$f_1$', color="C0")
    ax.plot(X, F2, label='$f_2$', color="C1")
    ax.legend(loc='upper center')
    ax.set(xlim=(-1, 1), xlabel="$x$", ylabel="$f(x)$")

def plot_objective_space(problem, ax=None):
    if ax is None:
        fig, ax = plt.subplots(1)
    X = np.linspace(-1, 1, 200)
    F1, F2 = problem(X).T
    ndf = pygmo.fast_non_dominated_sorting(np.column_stack([F1, F2]))[0][0]
    ax.plot(F1, F2, color="C2", label="Feasible solutions")
    ax.scatter(F1[ndf], F2[ndf], color="r", s=30, alpha=0.5, label="Pareto front")
    ax.legend(loc='upper center')
    ax.set(xlim=(0, 2), ylim=(0, 2), xlabel="$f_1$", ylabel="$f_2$")

def plot_problem(problem):
    fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
    plot_design_space(problem, ax=ax1)
    plot_objective_space(problem, ax=ax2)
    ax1.set_title("Design space")
    ax2.set_title("Objective space")
```

### Case 1: Convex front
Two convex objectives and convex Pareto front.

```python
def problem1(X):
    f1 = (X + 0.5)**2 + 0.3
    f2 = (X - 0.5)**2 + 0.3
    return np.column_stack([f1, f2])

plot_problem(problem1)
```

### Case 2: Non-convex front
Two non-convex objectives and non-convex Pareto front.

```python
def problem2(X):
    f1 = 2 + 1 / (X - 1.5)
    f2 = 2 - 1 / (X + 1.5)
    return np.column_stack([f1, f2])

plot_problem(problem2)
```

### Case 3: Non-connected front
Objectives with two similar local minima but conflicting global minima, leading to two separatly convex parts of the Pareto front. In this particular case the Pareto front is also non-connected.

```python
def problem3(X):
    f1 = (X + 0.5)**2 / 2 - np.cos(5*(X + 0.5)) + 1.2
    f2 = (X - 0.5)**2 / 2 - np.cos(5*(X - 0.5)) + 1.2
    return np.column_stack([f1, f2])

plot_problem(problem3)
```

```python
#collapse-hide
problems = problem1, problem2, problem3

def plot_problems():
    fig, axs = plt.subplots(1, 3, figsize=(15, 5), sharey=True)
    for F, ax in zip(problems, axs):
        plot_objective_space(F, ax=ax)
    for ax in axs[1:]:
        ax.set(ylabel=None)
    fig.tight_layout()
    return fig, axs

def plot_scalarization(scalarization):
    fig, axs = plot_problems()
    for F, ax in zip(problems, axs):
        # find the optimum of the scalarization via brute-force
        X = np.linspace(-1, 1, 200)
        S = scalarization(F(X))
        i = np.argmin(S)
        F_min = F(X[i]).T
        S_min = S[i]
        ax.plot(*F_min, "k+", ms=10, mew=2, label="Optimum of scalarization")
        ax.legend(loc="upper center")
        F_grid = np.meshgrid(np.linspace(0, 2, 200), np.linspace(0, 2, 200))
        S_grid = scalarization(np.stack(F_grid, axis=-1))
        ax.contour(*F_grid, S_grid, colors='k', levels=S_min + np.linspace(-2, 2, 9), linewidths=0.5)
```

## Weighted sum

The weighted sum computes a linear combination of the individual objectives.
\begin{align}
s(x) = \sum_{m=1} w_m f_m(x)
\end{align}

For any point on a convex front there exists a set of weights such that the point is the minimizer for the corresponding weighted sum scalarization. However, for non-convex Pareto fronts there are points which cannot be found by any weight vector.

```python
def weighted_sum(F, weights):
    return F @ weights

plot_scalarization(lambda F: weighted_sum(F, weights=[1, 1]))
```

## Weighted p-norm

The weighted norm ($L_p$ metric) is a generalization of the weighted sum.
\begin{align}
s(x) = \left( \sum_{m=1}^M w_m |f_m(x)|^p \right)^{1/p}
\end{align}

```python
def weighted_norm(f, weights, power=0.5):
    s = weights * f ** power
    return s.sum(axis=-1) ** (1 / power)

plot_scalarization(lambda F: weighted_norm(F, weights=[1, 1], power=2))
```

By increasing the value of $p$ we can see that we'll eventually be able to reach every point in the non-convex part of the front.

```python
plot_scalarization(lambda F: weighted_norm(F, weights=[1, 1], power=4))
```

## Weighted Chebyshef norm

The weighted Chebyshef norm is the weighted infinity or max norm ($L_\infty$).
\begin{align}
s(x) = \max_m \left( w_m f_m(x) \right)
\end{align}
Isocontours take the form of rectangles, which directly mirrors the definition of the Pareto order.

```python
def chebyshef(f, weights):
    return np.max(weights * f, axis=-1)

plot_scalarization(lambda F: chebyshef(F, weights=[1, 1]))
```

## Augmented Chebyshef norm

One limitation of the Chebyshef norm is that it can find weakly efficient points, i.e. objective vectors which are not strictly better in at least one of the objectives.
This can be avoided by adding a linear term with a tiny slope ε {% cite Steuer1989 %}:
\begin{align}
s = \max_m \left( w_m f_m \right) + \epsilon \sum_{m} f_m
\end{align}

```python
def augmented_chebyshef(f, weights, ε=1E-3):
    return np.max(weights * f, axis=-1) + ε * np.sum(f, axis=-1)

plot_scalarization(lambda F: augmented_chebyshef(F, weights=[1, 1], ε=0.02))
```

There's a variant of the augmented Chebyshef norm where slope term is pulled inside the max {% cite Kaliszewski1987 %}.
\begin{align}
s = \max_m \left( w_m f_m + \epsilon \sum_{m} f_m \right)
\end{align}

```python
def modified_chebyshef(f, weights, ε=1E-3):
    a = ε * np.sum(f, axis=-1, keepdims=True)
    return np.max(weights * f + a, axis=-1)

plot_scalarization(lambda F: modified_chebyshef(F, weights=[1, 1], ε=0.02))
```

## Penalty Boundary Intersection

The Penalty-based boundary intersection  method is a frequently used in scalarization based multi-objective evolutionary algorithms.
\begin{align}
s(x) &= d_1 + \theta d_2 \\
d_1(x) &= \sum_m f_m \frac{w_m}{\Vert w \Vert} \\
d_2(x) &= \Vert f_m - d_1 \frac{w_m}{\Vert w \Vert} \Vert
\end{align}
As noted in multiple research papers, its performance depends on the choice of the penalty parameter $\theta \in (0, \infty)$ and automated adaptions for use in evolutionary algorithms have been proposed {% cite Ming2017 %}, {% cite Qi2020 %}.

```python
def penalty_boundary_intersection(f, weights, theta=10):
    wn = weights / np.linalg.norm(weights)
    d1 = np.sum(f * wn, axis=-1)
    d2 = np.linalg.norm(f - (np.expand_dims(d1, axis=-1) * wn), axis=-1)
    return d1 + theta * d2

plot_scalarization(lambda F: penalty_boundary_intersection(F, weights=[1, 1], theta=0.5))
```

```python
plot_scalarization(lambda F: penalty_boundary_intersection(F, weights=[1, 1], theta=1.5))
```

## ε-constraint method
The idea here is optimize only of a single objective under the constraint that the other objectives do not get worse than a chosen set of values.
\begin{align}
\min_x ~ &f_i(x) \quad i \in M\\
\text{subject to} ~ &f_{m}(x) \leq \epsilon_m \quad m \neq i
\end{align}
The entire Pareto front can be found with appropriate values for $\epsilon_m$. However, these values are the actual objective values of the Pareto efficient solutions we'd like to find. Hence, aside from reducing the dimensionality by one, the method does not achieve much.

```python
#collapse-hide
# find the optimum for objective 1 under a constraint on objective 2 via brute-force.
ε2 = 0.8

fig, axs = plot_problems()
for prob, ax in zip(problems, axs):
    X = np.linspace(-1, 1, 1000)
    f1, f2 = prob(X).T
    c = f2 < ε2
    i = np.argmin(f1[c])
    f1[c][i], f2[c][i]
    ax.plot(f1[c][i], f2[c][i], "k+", ms=10, mew=2, label="Optimum of scalarization")
    ax.legend(loc="upper center")
    ax.axhspan(ε2, 2, color="k", alpha=0.2)
```

## Pascoletti-Serafini

The Pascoletti-Serafini scalarization {% cite Pascoletti1984 %} belongs to the boundary intersection methods, where we probe the Pareto front by searching for the intersection point from a feasible reference point $\mathbf{d}$ in direction $\mathbf{d}$:
\begin{align}
\max ~ &\lambda \\
\text{subject to} ~~ &\mathbf{r} + \lambda \mathbf{d} - \mathbf{f}(x) = 0 \\
&x \in \mathcal{X}
\end{align}

```python
#collapse-hide
# the following is merely for illustration
r = [1.5, 1.5]
d = [-1, -1]

fig, axs = plot_problems()
for prob, ax in zip(problems, axs):
    ax.arrow(*r, *d, head_width=0.05, fc="k")
    ax.plot(*r, "ko", label="reference point")
    ax.legend(loc="upper center")
```

## Normal boundary intersection

For this method we first construct the convex hull of individual minima, which is the $M-1$ dimensional hyperplane with the individual optima as corners.
Starting from evenly spaced points $\mathbf{r}$ on this plane we then find the intersection with the Pareto front in the negative direction $\mathbf{n}$ normal to the plane {% cite Das2003 %}:
\begin{align}
\max ~ &\lambda \\
\text{subject to} ~~ &\mathbf{r} + \lambda \mathbf{n} - \mathbf{f}(x) = 0 \\
&x \in \mathcal{X}
\end{align}

The main advantage is that with an even spacing of points on the convex hull, a relatively even sampling of the Pareto front can be realized.

```python
#collapse-hide
# the following is merely for illustration
fig, axs = plot_problems()
for prob, ax in zip(problems, axs):
    # find convex hull
    x = np.linspace(-1, 1, 1000)
    F = prob(x)
    CHIM = F[np.argmin(F, axis=0)]
    # evenly sample points on the convex hull
    R = CHIM[0] + (CHIM[1] - CHIM[0]) * np.linspace(0, 1, 7)[1:-1, np.newaxis]
    for r in R:
        ax.arrow(*r, -0.15, -0.15, head_width=0.05, fc="k")  # for the considered problems the normals are [-1, -1]
    ax.plot(*R.T, "ko")
    ax.plot(*CHIM.T, "k--", label="Convex hull of individual minima")
    ax.legend(loc="upper center")
```

## References

{% bibliography --cited %}


---
# Section 1.7: Gaussian Elimination and the LU Decomposition
---

Let $A \in \mathbb{R}^{n \times n}$ be **nonsingular**, and let $b \in \mathbb{R}^n$.

We will now look at solving

$$
Ax = b
$$

by **Gaussian Elimination**:

> Transform $Ax = b$ into an **equivalent** system
>
> $$Ux = y,$$
>
> where $U$ is **upper-triangular**, by using the following **elementary row operations**:
>
> 1. Add a multiple of one equation to another equation.
> 2. Interchange two equations.
> 3. Multiply an equation by a nonzero constant.

This can be done using **augmented matrices**:

$$
\left[\begin{array}{c|c}
A & b
\end{array}\right]
\rightarrow \cdots \rightarrow
\left[\begin{array}{c|c}
U & y
\end{array}\right].
$$

We know that $Ax = b$ and $Ux = y$ will be equivalent (i.e., have exactly the same set of solutions) by the following fact.

> ### Proposition 1:
Let $\hat{A}x = \hat{b}$ be obtained by an elementary row operation on $Ax = b$. Then $Ax = b$ and $\hat{A}x = \hat{b}$ are equivalent .
>
> Moreover,
> $$
\text{$A$ is nonsingular}
\quad \Longleftrightarrow \quad
\text{$\hat{A}$ is nonsingular}.
$$

---

## Example 1

Use Gaussian elimination to solve $Ax = b$, where

$$
A =
\begin{bmatrix}
2 & 1 & 1 \\
2 & 2 & -1 \\
4 & -1 & 6
\end{bmatrix},
\qquad
b =
\begin{bmatrix}
7 \\ 3 \\ 20
\end{bmatrix}.
$$

## Solution

$$
\begin{array}{rcl}
\left[\begin{array}{c|c}
A & b
\end{array}\right] =
\left[\begin{array}{ccc|c}
2 & 1 & 1 & 7 \\
2 & 2 & -1 & 3 \\
4 & -1 & 6 & 20
\end{array}\right]
& \xrightarrow{(r_2 \gets r_2 - r_1)} &
\left[\begin{array}{ccc|c}
2 & 1 & 1 & 7 \\
0 & 1 & -2 & -4 \\
4 & -1 & 6 & 20
\end{array}\right] \\
& \xrightarrow{(r_3 \gets r_3 - 2r_1)} &
\left[\begin{array}{ccc|c}
2 & 1 & 1 & 7 \\
0 & 1 & -2 & -4 \\
0 & -3 & 4 & 6
\end{array}\right] \\
& \xrightarrow{(r_3 \gets r_3 - (-3)r_2)} &
\left[\begin{array}{ccc|c}
2 & 1 & 1 & 7 \\
0 & 1 & -2 & -4 \\
0 & 0 & -2 & -6
\end{array}\right] =
\left[\begin{array}{c|c}
U & y
\end{array}\right].
\end{array}
$$

Then, by backward substitution, we have

$$
x = \begin{bmatrix} 1 \\ 2 \\ 3 \end{bmatrix}.
$$

---

We solved the example above using only the first type of row operation, but this is not always possible.

---

## Example 2

Use Gaussian elimination to solve $Ax = b$, where

$$
A =
\begin{bmatrix}
2 & 2 & 1 \\
2 & 2 & -1 \\
4 & -1 & 6
\end{bmatrix},
\qquad
b =
\begin{bmatrix}
5 \\ 3 \\ 14
\end{bmatrix}.
$$

## Solution

$$
\begin{array}{rcl}
\left[\begin{array}{c|c}
A & b
\end{array}\right] =
\left[\begin{array}{ccc|c}
2 & 2 & 1 & 5 \\
2 & 2 & -1 & 3 \\
4 & -1 & 6 & 14
\end{array}\right]
& \xrightarrow{(r_2 \gets r_2 - r_1)} &
\left[\begin{array}{ccc|c}
2 & 1 & 1 & 5 \\
0 & 0 & -2 & -2 \\
4 & -1 & 6 & 14
\end{array}\right] \\
& \xrightarrow{(r_3 \gets r_3 - 2r_1)} &
\left[\begin{array}{ccc|c}
2 & 1 & 1 & 5 \\
0 & 0 & -2 & -2 \\
0 & -3 & 4 & 4
\end{array}\right] \\
& \xrightarrow{(r_2 \leftrightarrow r_3)} &
\left[\begin{array}{ccc|c}
2 & 1 & 1 & 5 \\
0 & -3 & 4 & 4 \\
0 & 0 & -2 & -2 \\
\end{array}\right] =
\left[\begin{array}{c|c}
U & y
\end{array}\right].
\end{array}
$$

Then, by backward substitution, we have

$$
x = \begin{bmatrix} 2 \\ 0 \\ 1 \end{bmatrix}.
$$

---

To do Gaussian elimination **without row interchanges**, the matrix $A$ needs the following property:

$$
\begin{equation} \tag{1}
\fbox{$A_k$ is nonsingular, for $k = 1,\ldots,n$.}
\end{equation}
$$

Here $A_k$ is the **$\mathbf{k^{th}}$ leading principal submatrix** (i.e., $A_k =$ `A[1:k, 1:k]`).

---

## Example 1 (revisited)

$$
A =
\begin{bmatrix}
2 & 1 & 1 \\
2 & 2 & -1 \\
4 & -1 & 6
\end{bmatrix}
$$

```julia
using LinearAlgebra
```

```julia
A = [2 1 1; 2 2 -1; 4 -1 6]
```

```julia
A1 = A[1:1, 1:1]
```

```julia
det(A1)
```

```julia
A2 = A[1:2, 1:2]
```

```julia
det(A2)
```

```julia
A3 = A[1:3, 1:3]
```

```julia
det(A3)
```

Therefore, the matrix $A$ satisfies property (1).

---

## Example 2 (revisited)

$$
A =
\begin{bmatrix}
2 & 2 & 1 \\
2 & 2 & -1 \\
4 & -1 & 6
\end{bmatrix}
$$

```julia
A = [2 2 1; 2 2 -1; 4 -1 6]
```

```julia
A1 = A[1:1, 1:1]
```

```julia
det(A1)
```

```julia
A2 = A[1:2, 1:2]
```

```julia
det(A2)
```

Therefore, the matrix $A$ does not satisfy property (1).

---

>### Proposition 2:
Let $A \in \mathbb{R}^{n \times n}$ be nonsingular.
>
> If we can solve $Ax = b$ using Gaussian elimination **without row interchanges**, then property (1) holds.

### Proof.

Suppose we do the Gaussian elimination

$$
\left[\begin{array}{c|c}
A & b
\end{array}\right]
\rightarrow \cdots \rightarrow
\left[\begin{array}{c|c}
U & y
\end{array}\right]
$$

without row interchanges. Then $U$ is an upper-triangular matrix which is nonsingular since $A$ is nonsingular.

Since $U$ is nonsingular and triangular, the diagonal entries of $U$ are nonzero. Therefore, $U_k$ is nonsingular, for $k = 1,\ldots,n$.

Since no row interchanges occurred, we have that $U_k$ was obtained from $A_k$ by elementary row operations. Since $U_k$ is nonsingular, we have that $A_k$ is nonsingular.

Therefore, property (1) holds. $\blacksquare$

---

### The converse of Proposition 2

We will now prove that the converse of Proposition 2 holds:

> If property (1) holds for the matrix $A$, then we can do Gaussian elimination on $Ax = b$ without row interchanges.

### Proof.

First we note that if property (1) holds, then $a_{11} \neq 0$.

We will prove this statement by using **block Gaussian elimination**:

$$
A =
\begin{bmatrix}
a_{11} & b^T \\
c & \hat{A}
\end{bmatrix}
\quad \xrightarrow{\left(r_2 \gets r_2 - \frac{1}{a_{11}} c r_1\right)} \quad
\begin{bmatrix}
a_{11} & b^T \\
0 & \hat{A} - \frac{1}{a_{11}} cb^T
\end{bmatrix}.
$$

We let

$$
\tilde{A} = \hat{A} - \frac{1}{a_{11}} cb^T,
$$

which is known as the **Schur complement** of $a_{11}$ in $A$.

The next step of Gaussian elimination will repeat the above row operation on $\tilde{A}$.

Thus, if we can show that $\tilde{A}$ inherits property (1) from $A$, then we know that we can continue the process of Gaussian elimination on $Ax = b$ without row interchanges.

So we need to show that $\tilde{A}_k$ is nonsingular for $k = 1,\ldots,n-1$. Suppose, for the sake of contradiction, that $\tilde{A}_k$ is singular, for some $k$.

Then, $\exists \tilde{y} \in \mathbb{R}^k$ nonzero such that

$$
\tilde{A}_k \tilde{y} = 0.
$$

Letting $M = \begin{bmatrix} a_{11} & b^T \\ 0 & \tilde{A} \end{bmatrix}$, we have

$$
M_{k+1} =
\begin{bmatrix} a_{11} & b_k^T \\ 0 & \tilde{A}_k \end{bmatrix},
$$

where $b_k = b[1 \colon k]$.

Since $M_{k+1}$ was obtained from $A_{k+1}$ by elementary row operations, and since $A_{k+1}$ is nonsingular, we have that $M_{k+1}$ is nonsingular.

Now, let's get a contradiction by finding a vector $y \neq 0$ such that $M_{k+1} y = 0$.

Let

$$
y =
\begin{bmatrix}
-\frac{1}{a_{11}}b_k^T \tilde{y} \\
\tilde{y}
\end{bmatrix}.
$$

Then $y \neq 0$ since $\tilde{y} \neq 0$, and

$$
M_{k+1} y =
\begin{bmatrix} a_{11} & b_k^T \\ 0 & \tilde{A}_k \end{bmatrix}
\begin{bmatrix}
-\frac{1}{a_{11}}b_k^T \tilde{y} \\
\tilde{y}
\end{bmatrix} =
\begin{bmatrix}
-b_k^T \tilde{y} + b_k^T \tilde{y} \\
\tilde{A}_k \tilde{y}
\end{bmatrix} =
\begin{bmatrix}
0 \\ 0
\end{bmatrix},
$$

which gives us our contradiction to the fact $M_{k+1}$ should be nonsingular.

Therefore, $\tilde{A}_k$ is nonsingular for $k = 1,\ldots,n-1$, which implies we can continue to do Gaussian elimination on $Ax = b$ without row interchanges. $\blacksquare$

---

Thus, we have the following result:

> ### Proposition 3:
Let $A \in \mathbb{R}^{n \times n}$ be nonsingular. Then we can solve $Ax = b$ using Gaussian elimination without row interchanges if and only if all leading principal submatrices of $A$ are nonsingular.

---

## LU Decomposition

We will now see that Gaussian elimination without row interchanges gives us a decomposition of the matrix $A$ into a lower-triangular matrix $L$ times an upper-triangular matrix $U$:

$$
A = LU.
$$

Recall that in Example 1, we had

$$
\begin{array}{rcl}
\left[\begin{array}{c|c}
A & b
\end{array}\right]
=
\left[\begin{array}{ccc|c}
2 & 1 & 1 & 7 \\
2 & 2 & -1 & 3 \\
4 & -1 & 6 & 20
\end{array}\right]
& \xrightarrow{(r_2 \gets r_2 - r_1)} &
\left[\begin{array}{ccc|c}
2 & 1 & 1 & 7 \\
0 & 1 & -2 & -4 \\
4 & -1 & 6 & 20
\end{array}\right] \\
& \xrightarrow{(r_3 \gets r_3 - 2r_1)} &
\left[\begin{array}{ccc|c}
2 & 1 & 1 & 7 \\
0 & 1 & -2 & -4 \\
0 & -3 & 4 & 6
\end{array}\right] \\
& \xrightarrow{(r_3 \gets r_3 - (-3)r_2)} &
\left[\begin{array}{ccc|c}
2 & 1 & 1 & 7 \\
0 & 1 & -2 & -4 \\
0 & 0 & -2 & -6
\end{array}\right]
=
\left[\begin{array}{c|c}
U & y
\end{array}\right].
\end{array}
$$

Let's look carefully at what is happening to the right-hand-side.

We can see that $y$ has been computed from $b$ as follows:

\begin{array}{lllllr}
y_1 &=&  b_1 &=& 7 & = & 7, \\
y_2 &=&  b_2 - y_1 &=& 3 - 7 &=& -4,\\
y_3 &=&  b_3 - 2y_1 - (-3)y_2 &=& 20 - 2 \cdot 7 + 3 \cdot (-4) &=& -6.
\end{array}

Putting all the $y$ terms on the left-hand-side, we obtain the following linear system.

\begin{array}{rrrrrrr}
y_1 & &       & &     &=&  b_1 \\
y_1 &+& y_2   & &     &=&  b_2 \\
2y_1 &-& 3y_2 &+& y_3 &=&  b_3
\end{array}

We can write these equations in matrix form as

$$
\begin{bmatrix}
1 & 0 & 0 \\
1 & 1 & 0 \\
2 & -3 & 1 \\
\end{bmatrix}
\begin{bmatrix}
y_1 \\ y_2 \\ y_3
\end{bmatrix}
=
\begin{bmatrix}
b_1 \\ b_2 \\ b_3
\end{bmatrix}
\qquad
(Ly = b),
$$

where $L$ is lower-triangular with ones on the diagonal. We say that $L$ is a **unit lower-triangular matrix**.

Thus, we can solve $Ax = b$ using the following algorithm:

1. Solve $Ly = b$ for $y$.
2. Solve $Ux = y$ for $x$.

This implies that $LUx = b$, and in fact

$$
LU =
\begin{bmatrix}
1 &  &  \\
1 & 1 &  \\
2 & -3 & 1 \\
\end{bmatrix}
\begin{bmatrix}
2 & 1 & 1 \\
 & 1 & -2 \\
 &  & -2
\end{bmatrix}
=
\begin{bmatrix}
2 & 1 & 1  \\
2 & 2 & -1 \\
4 & -1 & 6
\end{bmatrix}
= A
$$

Therefore, Gaussian elimination without row interchanges gives us the $LU$-factorization

$$
A = LU,
$$

where $L$ is **unit lower-triangular** and $U$ is **upper-triangular**.

Since $L$ is known to have ones along its diagonal, we do not need to store the diagonal of $L$. This allows us to store the $LU$-factorization of $A$ in a single matrix:

$$
"LU" \quad = \quad
\begin{bmatrix}
2 & 1 & 1 \\
1 & 1 & -2 \\
2 & -3 & -2
\end{bmatrix}
$$

In fact, we can overwrite the matrix $A$ with its $L$ and $U$ factors.

---

## `lu`

```julia
?lu
```

```julia
A = Float64[2 1 1; 2 2 -1; 4 -1 6]
b = Float64[7; 3; 20]
pivot = Val(false)

F = lu(A, pivot)
```

```julia
dump(F)
```

```julia
F.factors
```

```julia
L = F.L
```

```julia
U = F.U
```

```julia
L*U - A
```

```julia
x = F\b
```

```julia
x = U\(L\b)
```

---

As mentioned above, if you don't need $A$ after doing the $LU$-factorization, and you want to save some memory, you can have $A$ **overwritten** by the $L$ and $U$ factors.

This is done using the `lu!` function.

```julia
A = Float64[2 1 1; 2 2 -1; 4 -1 6]
```

```julia
pivot = Val(false)
lu!(A, pivot)
```

```julia
A
```

---

```julia
A = Float64[2 2 1; 2 2 -1; 4 -1 6]
```

```julia
L, U, p = lu(A, Val(false))
```

```julia
L, U, p = lu(A)
```

```julia
L*U
```

```julia
A
```

```julia
p
```

```julia
A[p,:]
```

```julia
L*U - A[p,:]
```

---

## A larger numerical example

```julia
n = 3000

A = randn(n, n) + n*I
x = rand(n)
b = A*x;
```

```julia
@time F = lu!(A);
```

```julia
xhat = copy(b)
@time ldiv!(F, xhat);
```

```julia
x ≈ xhat
```

---

## Outer-product form of the $LU$-algorithm

We partition $A = LU$ as

$$
\begin{bmatrix}
a_{11} & b^T \\
c & \hat{A} \\
\end{bmatrix}
=
\begin{bmatrix}
1 & 0 \\
l & \hat{L} \\
\end{bmatrix}
\begin{bmatrix}
u_{11} & u^T \\
0 & \hat{U} \\
\end{bmatrix}
=
\begin{bmatrix}
u_{11} & u^T \\
u_{11}l & lu^T + \hat{L}\hat{U} \\
\end{bmatrix}.
$$

This gives us the following algorithm.

### Algorithm:

1. Let $u_{11} = a_{11}$ and $u^T = b^T$.
2. Let $l = c/u_{11}$.
3. Let $\tilde{A} = \hat{A} - lu^T$.
4. Factor $\tilde{A} = \hat{L}\hat{U}$.

### Flop count of the $LU$-algorithm

We can use the above recursive algorithm to count the number of flops.

Let $f_n$ be the number of flops required to compute the $LU$-decomposition of an $n \times n$ matrix $A$.

- Step 1 requires 0 flops.
- Step 2 requires $n-1$ flops.
- Step 3 requires $2(n-1)^2$ flops.
- Step 4 requires $f_{n-1}$ flops.

Therefore,

$$
f_n = (n-1) + 2(n-1)^2 + f_{n-1}, \qquad n = 2, 3, 4, \ldots.
$$

Also, $f_1 = 0$.

Letting $g(n) = 2n^2 + n$, we have

$$
\begin{align}
f_n
&= g(n-1) + f_{n-1} \\
&= g(n-1) + g(n-2) + f_{n-2} \\
&= g(n-1) + g(n-2) + g(n-3) + f_{n-3} \\
&\quad\vdots \\
&= g(n-1) + g(n-2) + g(n-3) + \cdots + g(1) + f_1 \\
&= \sum_{i=1}^{n-1} g(i) + 0 \\
&= \sum_{i=1}^{n-1} \left( 2i^2 + i \right) \\
&= \int_0^n \left(2x^2 + x\right)dx + O(n^2) \\
&= \fbox{$\frac23 n^3 + O(n^2)$}. \\
\end{align}
$$

So, we conclude that Gaussian elimination requires about $\frac23 n^3$ flops (i.e., roughly twice as many flops as Cholesky's algorithm).

---

## The LU Decomposition Theorem

> ### Theorem: (LU Decomposition)
>
> Let $A$ be an $n \times n$ matrix whose leading principal submatrices are nonsingular.
>
> Then there is a **unique** factorization
>
> $$A = LU$$
>
> where $L$ is unit lower-triangular and $U$ is upper-triangular.

### Proof.

We will prove this theorem by induction on $n$.

If $n = 1$, then $A = [a_{11}]$. If $A = LU$, where $L$ is unit lower-triangular and $U$ is upper-triangular, then $L = [1]$ and $U = [a_{11}]$. Therefore, $A$ has a unique $LU$-decomposition.

Now suppose that all $(n-1) \times (n-1)$ matrices with nonsingular leading principal submatrices have a unique $LU$-decomposition. This is our *induction hypothesis*.

Let $A$ be an $n \times n$ matrix with nonsingular leading principal submatrices and consider the following "bordered" partition of $A = LU$.

$$
\tag{2}
\begin{bmatrix}
\hat{A} & b \\
c^T & a_{nn}
\end{bmatrix}
=
\begin{bmatrix}
\hat{L} & 0 \\
l^T & 1
\end{bmatrix}
\begin{bmatrix}
\hat{U} & u \\
0 & u_{nn}
\end{bmatrix}
$$

We want to show that there is a unique solution to equation (2), where $\hat{L}$ is unit lower-triangular and $\hat{U}$ is upper-triangular.

We can rewrite equation (2) as:

$$
\hat{A} = \hat{L}\hat{U}, \quad b = \hat{L}u, \quad c^T = l^T\hat{U}, \quad a_{nn} = l^Tu + u_{nn}.
$$

- Since $A$ has nonsingular leading principal submatrices, so does $\hat{A}$. And since $\hat{A}$ is $(n-1) \times (n-1)$, we know that $\hat{A}$ has a unique $LU$-decomposition, by our induction hypothesis. Thus, there exist a **unique** $\hat{L}$ that is unit lower-triangular and a **unique** $\hat{U}$ that is upper-triangular such that $\hat{A} = \hat{L}\hat{U}$.

- Since $\hat{L}$ is nonsingular, there exists a **unique** solution $u$ to the equation $\hat{L}u = b$.

- Also, $\hat{U}$ is nonsingular because $\hat{A}$ is nonsingular. Therefore, there exists a **unique** solution $l$ to the equation $\hat{U}^Tl = c$.

- Finally, $u_{nn} = a_{nn} - l^Tu$, so $u_{nn}$ is also **uniquely determined**.

Therefore, $A$ has a **unique** $LU$-decomposition. $\blacksquare$

---

Section 1.7 - Gaussian Elimination and the LU Decomposition.ipynb


# Des dates qui font des nombres premiers (suite) ?

Ce petit [notebook Jupyter](https://www.jupyter.org/), écrit en [Python](https://www.python.org/), a pour but de résoudre la question suivante :

> *"Pour un jour fixé, en utilisant le jour le mois et les deux chiffres de l'année comme briques de base, et les opérations arithmétiques $+,\times,-,\mod,\%$, quel est le plus grand nombre premier que l'on peut obtenir ?"*

Par exemple, en 2017, le 31 mai donne `31`, `5`, et `17`.

- $31 \times 5 \times 17$ n'est évidemment pas premier,
- $17 \times (31 \mod 5) = 17$ est premier.

> Pour d'autres questions sur les dates et les nombres premiers, [ce premier notebook est aussi intéressant !](https://nbviewer.jupyter.org/github/Naereen/notebooks/blob/master/simus/Des_dates_qui_font_des_nombres_premiers.ipynb).

----
## Une première solution, naïve
- On va d'abord écrire (ou importer) une fonction pour tester si un entier est premier,
- Puis on va écrire une fonction qui transforme une date en ses trois nombres,
- Et une fonction qui essaie toutes les opérations possibles sur les trois nombres,
- Et enfin une boucle sur les 365 (ou 366) jours de l'année suffira à afficher, pour chaque jour, le plus grand nombre premier obtenu.

### Tester la primalité, version tricheur
[`sympy`](http://www.sympy.org/) propose une fonction [`sympy.isprime`](http://docs.sympy.org/latest/modules/ntheory.html#sympy.ntheory.primetest.isprime).

```python
from sympy import isprime
```

Elle marche très bien, et est très rapide !

```python
[isprime(i) for i in [2, 3, 5, 7, 10, 11, 13, 17, 2017]]
```

    [True, True, True, True, False, True, True, True, True]

Pour des nombres de 8 chiffres (c'est tout petit), elle est vraiment rapide :

```python
from numpy.random import randint
%timeit sum([isprime(i) for i in randint(1e8, 1e9-1, 10**4)])
```

    63.6 ms ± 4.29 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

$\implies$ $65 ~\text{ms}$ pour 10000 nombres à tester, ça me semble assez rapide pour ce qu'on veut en faire !

----
### Transformer une date en nombre
On va utiliser le module [`datetime`](https://docs.python.org/3/library/datetime.html) de la bibliothèque standard :

```python
from datetime import datetime
```

```python
today = datetime.today()
YEAR = today.year
print("On va travailler avec l'année", YEAR, "!")
```

    On va travailler avec l'année 2017 !

C'est ensuite facile de transformer une date en nombre, selon les deux formats.
On utilise [le formatage avec `.format()`](https://pyformat.info/#datetime) (en Python 3) :

```python
def date_vers_nombre(date):
    day = int("{:%d}".format(date))
    month = int("{:%m}".format(date))
    year = int("{:%Y}".format(date)[-2:])
    return day, month, year
```

```python
date = datetime(YEAR, 1, 12)
print(date_vers_nombre(date))
```

    (12, 1, 17)

### Tester toutes les opérations possibles
- On utilise la fonction [`itertools.permutations`](https://docs.python.org/3/library/itertools.html#itertools.permutation) pour obtenir les permutations des trois nombres $x,y,z$,
- On applique les opérations dans l'ordre $f_1(f_2(x, y), z)$ et $f_1(x, f_2(y, z))$, ce qui suffit à couvrir tous les cas.
- On utilise le module [`operator`](https://docs.python.org/3/library/operator.html) pour avoir des fonctions pour les opérations autorisées.

```python
from itertools import permutations
from operator import mod, mul, add, pow, sub, floordiv

operations = [mod, mul, add, sub, floordiv]
```

```python
def tous_les_resultats(nombres, ops=operations):
    assert len(nombres) == 3
    tous = []
    for (x, y, z) in permutations(nombres):
        # on a un ordre pour x, y, z
        for f1 in ops:
            for f2 in ops:
                tous.append(f1(f2(x, y), z))
                tous.append(f1(x, f2(y, z)))
    # on enlève les doublons ici
    return list(set(tous))
```

On peut la vérifier sur de petites entrées :

```python
tous_les_resultats([1, 2, 3], [add])
tous_les_resultats([1, 2, 3], [add, mul])
```

    [6]

    [8, 9, 5, 6, 7]

On voit que stocker juste l'entier résultat ne suffit pas, on aimerait garder trace de chaque façon de l'obtenir !

```python
noms_operations = {
    mod: '%',
    mul: '*',
    add: '+',
    sub: '-',
    floordiv: '/',
}
```

```python
def tous_les_resultats_2(nombres, ops=operations):
    assert len(nombres) == 3
    tous = {}
    for (x, y, z) in permutations(nombres):
        # on a un ordre pour x, y, z
        for f1 in ops:
            for f2 in ops:
                n1 = f1(f2(x, y), z)
                s1 = "{}({}({}, {}), {})".format(noms_operations[f1], noms_operations[f2], x, y, z)
                tous[s1] = n1
                n2 = f1(x, f2(y, z))
                s2 = "{}({}, {}({}, {}))".format(noms_operations[f1], x, noms_operations[f2], y, z)
                tous[s2] = n2
    return tous
```

```python
tous_les_resultats_2([1, 2, 3], [add])
tous_les_resultats_2([1, 2, 3], [add, mul])
```

    {'+(+(1, 2), 3)': 6,
     '+(+(1, 3), 2)': 6,
     '+(+(2, 1), 3)': 6,
     '+(+(2, 3), 1)': 6,
     '+(+(3, 1), 2)': 6,
     '+(+(3, 2), 1)': 6,
     '+(1, +(2, 3))': 6,
     '+(1, +(3, 2))': 6,
     '+(2, +(1, 3))': 6,
     '+(2, +(3, 1))': 6,
     '+(3, +(1, 2))': 6,
     '+(3, +(2, 1))': 6}

    {'*(*(1, 2), 3)': 6,
     '*(*(1, 3), 2)': 6,
     '*(*(2, 1), 3)': 6,
     '*(*(2, 3), 1)': 6,
     '*(*(3, 1), 2)': 6,
     '*(*(3, 2), 1)': 6,
     '*(+(1, 2), 3)': 9,
     '*(+(1, 3), 2)': 8,
     '*(+(2, 1), 3)': 9,
     '*(+(2, 3), 1)': 5,
     '*(+(3, 1), 2)': 8,
     '*(+(3, 2), 1)': 5,
     '*(1, *(2, 3))': 6,
     '*(1, *(3, 2))': 6,
     '*(1, +(2, 3))': 5,
     '*(1, +(3, 2))': 5,
     '*(2, *(1, 3))': 6,
     '*(2, *(3, 1))': 6,
     '*(2, +(1, 3))': 8,
     '*(2, +(3, 1))': 8,
     '*(3, *(1, 2))': 6,
     '*(3, *(2, 1))': 6,
     '*(3, +(1, 2))': 9,
     '*(3, +(2, 1))': 9,
     '+(*(1, 2), 3)': 5,
     '+(*(1, 3), 2)': 5,
     '+(*(2, 1), 3)': 5,
     '+(*(2, 3), 1)': 7,
     '+(*(3, 1), 2)': 5,
     '+(*(3, 2), 1)': 7,
     '+(+(1, 2), 3)': 6,
     '+(+(1, 3), 2)': 6,
     '+(+(2, 1), 3)': 6,
     '+(+(2, 3), 1)': 6,
     '+(+(3, 1), 2)': 6,
     '+(+(3, 2), 1)': 6,
     '+(1, *(2, 3))': 7,
     '+(1, *(3, 2))': 7,
     '+(1, +(2, 3))': 6,
     '+(1, +(3, 2))': 6,
     '+(2, *(1, 3))': 5,
     '+(2, *(3, 1))': 5,
     '+(2, +(1, 3))': 6,
     '+(2, +(3, 1))': 6,
     '+(3, *(1, 2))': 5,
     '+(3, *(2, 1))': 5,
     '+(3, +(1, 2))': 6,
     '+(3, +(2, 1))': 6}

Si on stocke avec comme clés les expressions, on va en avoir BEAUCOUP.
Faisons l'inverse, avec le résultat de l'expression comme clés.

```python
def tous_les_resultats_3(nombres, ops=operations):
    assert len(nombres) == 3
    tous = {}
    for (x, y, z) in permutations(nombres):
        # on a un ordre pour x, y, z
        for f1 in ops:
            for f2 in ops:
                n1 = f1(f2(x, y), z)
                s1 = "{}({}({}, {}), {})".format(noms_operations[f1], noms_operations[f2], x, y, z)
                tous[n1] = s1
                n2 = f1(x, f2(y, z))
                s2 = "{}({}, {}({}, {}))".format(noms_operations[f1], x, noms_operations[f2], y, z)
                tous[n2] = s2
    return tous
```

```python
tous_les_resultats_3([1, 2, 3], [add])
tous_les_resultats_3([1, 2, 3], [add, mul])
```

    {6: '+(3, +(2, 1))'}

    {5: '*(+(3, 2), 1)',
     6: '*(3, *(2, 1))',
     7: '+(*(3, 2), 1)',
     8: '*(+(3, 1), 2)',
     9: '*(3, +(2, 1))'}

Beaucoup plus raisonnable ! Ici, pour le 2ème exemple, le plus grand nombre premier obtenu est $7 = (3 \times 2) + 1$.

```python
def plus_grand_premier(nombres, ops=operations):
    tous = tous_les_resultats_3(nombres, ops=ops)
    premiers = [ p for p in tous.keys() if isprime(p) ]
    plus_grand_premier = max(premiers)
    expression = tous[plus_grand_premier]
    return plus_grand_premier, expression
```

```python
plus_grand_premier([1, 2, 3], [add, mul])
```

    (7, '+(*(3, 2), 1)')

```python
plus_grand_premier([1, 2, 3])
```

Il faut ignorer les erreurs de calculs et ne pas ajouter le nombre dans ce cas:

```python
def tous_les_resultats_4(nombres, ops=operations):
    assert len(nombres) == 3
    tous = {}
    for (x, y, z) in permutations(nombres):
        # on a un ordre pour x, y, z
        for f1 in ops:
            for f2 in ops:
                try:
                    n1 = f1(f2(x, y), z)
                    s1 = "{}({}({}, {}), {})".format(noms_operations[f1], noms_operations[f2], x, y, z)
                    tous[n1] = s1
                except:
                    pass
                try:
                    n2 = f1(x, f2(y, z))
                    s2 = "{}({}, {}({}, {}))".format(noms_operations[f1], x, noms_operations[f2], y, z)
                    tous[n2] = s2
                except:
                    pass
    return tous
```

```python
def plus_grand_premier_2(nombres, ops=operations):
    tous = tous_les_resultats_4(nombres, ops=ops)
    premiers = [ p for p in tous.keys() if isprime(p) ]
    plus_grand_premier = max(premiers)
    expression = tous[plus_grand_premier]
    return plus_grand_premier, expression
```

```python
plus_grand_premier_2([1, 2, 3], [add, mul])
```

    (7, '+(*(3, 2), 1)')

```python
plus_grand_premier_2([1, 2, 3])
```

    (7, '+(*(3, 2), 1)')

```python
plus_grand_premier_2([12, 1, 93])
```

    (1117, '+(*(93, 12), 1)')

### Tester sur un jour

```python
date
x, y, z = date_vers_nombre(date)
plus_grand_premier_2([x, y, z])
```

    datetime.datetime(2017, 2, 3, 0, 0)

    (53, '+(*(17, 3), 2)')

### Tester tous les jours de l'année

On peut partir du 1er janvier de cette année, et ajouter des jours un par un.
On utilise un itérateur (avec le mot clé `yield`), pour pouvoir facilement boucler sur tous les jours de l'année en cours :

```python
from datetime import timedelta

def tous_les_jours(year=YEAR):
    date = datetime(year, 1, 1)
    un_jour = timedelta(days=1)
    for i in range(0, 366):
        yield date
        date += un_jour
        if date.year > year:  # On est allé trop loin
            raise StopIteration
```

```python
tous = []
for date in tous_les_jours():
    x, y, z = date_vers_nombre(date)
    p, expr = plus_grand_premier_2([x, y, z])
    tous.append(([x, y, z], p, expr))
    print("Pour la date {:%d-%m-%Y}, le plus grand nombre premier obtenu est {}, avec l'expression {}.".format(date, p, expr))
```

    Pour la date 01-01-2017, le plus grand nombre premier obtenu est 19, avec l'expression +(17, +(1, 1)).
    Pour la date 02-01-2017, le plus grand nombre premier obtenu est 19, avec l'expression +(/(17, 1), 2).
    Pour la date 03-01-2017, le plus grand nombre premier obtenu est 19, avec l'expression -(17, -(1, 3)).
    Pour la date 04-01-2017, le plus grand nombre premier obtenu est 67, avec l'expression -(*(17, 4), 1).
    Pour la date 05-01-2017, le plus grand nombre premier obtenu est 23, avec l'expression +(17, +(1, 5)).
    Pour la date 06-01-2017, le plus grand nombre premier obtenu est 103, avec l'expression +(*(17, 6), 1).
    Pour la date 07-01-2017, le plus grand nombre premier obtenu est 23, avec l'expression -(17, -(1, 7)).
    Pour la date 08-01-2017, le plus grand nombre premier obtenu est 137, avec l'expression +(*(17, 8), 1).
    Pour la date 09-01-2017, le plus grand nombre premier obtenu est 17, avec l'expression /(17, %(1, 9)).
    Pour la date 10-01-2017, le plus grand nombre premier obtenu est 17, avec l'expression /(17, %(1, 10)).
    Pour la date 11-01-2017, le plus grand nombre premier obtenu est 29, avec l'expression +(17, +(1, 11)).
    Pour la date 12-01-2017, le plus grand nombre premier obtenu est 29, avec l'expression +(/(17, 1), 12).
    Pour la date 13-01-2017, le plus grand nombre premier obtenu est 31, avec l'expression +(17, +(1, 13)).
    Pour la date 14-01-2017, le plus grand nombre premier obtenu est 239, avec l'expression +(*(17, 14), 1).
    Pour la date 15-01-2017, le plus grand nombre premier obtenu est 31, avec l'expression -(17, -(1, 15)).
    Pour la date 16-01-2017, le plus grand nombre premier obtenu est 271, avec l'expression -(*(17, 16), 1).
    Pour la date 17-01-2017, le plus grand nombre premier obtenu est 17, avec l'expression /(17, %(1, 17)).
    Pour la date 18-01-2017, le plus grand nombre premier obtenu est 307, avec l'expression +(*(17, 18), 1).
    Pour la date 19-01-2017, le plus grand nombre premier obtenu est 37, avec l'expression +(17, +(1, 19)).
    Pour la date 20-01-2017, le plus grand nombre premier obtenu est 37, avec l'expression +(/(17, 1), 20).
    Pour la date 21-01-2017, le plus grand nombre premier obtenu est 37, avec l'expression -(17, -(1, 21)).
    Pour la date 22-01-2017, le plus grand nombre premier obtenu est 373, avec l'expression -(*(17, 22), 1).
    Pour la date 23-01-2017, le plus grand nombre premier obtenu est 41, avec l'expression +(17, +(1, 23)).
    Pour la date 24-01-2017, le plus grand nombre premier obtenu est 409, avec l'expression +(*(17, 24), 1).
    Pour la date 25-01-2017, le plus grand nombre premier obtenu est 43, avec l'expression +(17, +(1, 25)).
    Pour la date 26-01-2017, le plus grand nombre premier obtenu est 443, avec l'expression +(*(17, 26), 1).
    Pour la date 27-01-2017, le plus grand nombre premier obtenu est 43, avec l'expression -(17, -(1, 27)).
    Pour la date 28-01-2017, le plus grand nombre premier obtenu est 29, avec l'expression +(%(1, 17), 28).
    Pour la date 29-01-2017, le plus grand nombre premier obtenu est 47, avec l'expression +(17, +(1, 29)).
    Pour la date 30-01-2017, le plus grand nombre premier obtenu est 509, avec l'expression -(*(17, 30), 1).
    Pour la date 31-01-2017, le plus grand nombre premier obtenu est 47, avec l'expression -(17, -(1, 31)).
    Pour la date 01-02-2017, le plus grand nombre premier obtenu est 19, avec l'expression /(+(17, 2), 1).
    Pour la date 02-02-2017, le plus grand nombre premier obtenu est 17, avec l'expression /(17, /(2, 2)).
    Pour la date 03-02-2017, le plus grand nombre premier obtenu est 53, avec l'expression +(*(17, 3), 2).
    Pour la date 04-02-2017, le plus grand nombre premier obtenu est 23, avec l'expression +(17, +(2, 4)).
    Pour la date 05-02-2017, le plus grand nombre premier obtenu est 83, avec l'expression -(*(17, 5), 2).
    Pour la date 06-02-2017, le plus grand nombre premier obtenu est 29, avec l'expression +(17, *(2, 6)).
    Pour la date 07-02-2017, le plus grand nombre premier obtenu est 59, avec l'expression /(*(17, 7), 2).
    Pour la date 08-02-2017, le plus grand nombre premier obtenu est 23, avec l'expression -(17, -(2, 8)).
    Pour la date 09-02-2017, le plus grand nombre premier obtenu est 151, avec l'expression -(*(17, 9), 2).
    Pour la date 10-02-2017, le plus grand nombre premier obtenu est 37, avec l'expression +(17, *(2, 10)).
    Pour la date 11-02-2017, le plus grand nombre premier obtenu est 23, avec l'expression -(*(17, 2), 11).
    Pour la date 12-02-2017, le plus grand nombre premier obtenu est 41, avec l'expression +(17, *(2, 12)).
    Pour la date 13-02-2017, le plus grand nombre premier obtenu est 223, avec l'expression +(*(17, 13), 2).
    Pour la date 14-02-2017, le plus grand nombre premier obtenu est 29, avec l'expression -(17, -(2, 14)).
    Pour la date 15-02-2017, le plus grand nombre premier obtenu est 257, avec l'expression +(*(17, 15), 2).
    Pour la date 16-02-2017, le plus grand nombre premier obtenu est 31, avec l'expression -(17, -(2, 16)).
    Pour la date 17-02-2017, le plus grand nombre premier obtenu est 19, avec l'expression +(17, %(2, 17)).
    Pour la date 18-02-2017, le plus grand nombre premier obtenu est 53, avec l'expression +(17, *(2, 18)).
    Pour la date 19-02-2017, le plus grand nombre premier obtenu est 53, avec l'expression +(*(17, 2), 19).
    Pour la date 20-02-2017, le plus grand nombre premier obtenu est 23, avec l'expression -(*(2, 20), 17).
    Pour la date 21-02-2017, le plus grand nombre premier obtenu est 359, avec l'expression +(*(17, 21), 2).
    Pour la date 22-02-2017, le plus grand nombre premier obtenu est 61, avec l'expression +(17, *(2, 22)).
    Pour la date 23-02-2017, le plus grand nombre premier obtenu est 389, avec l'expression -(*(17, 23), 2).
    Pour la date 24-02-2017, le plus grand nombre premier obtenu est 43, avec l'expression +(17, +(2, 24)).
    Pour la date 25-02-2017, le plus grand nombre premier obtenu est 67, avec l'expression +(17, *(2, 25)).
    Pour la date 26-02-2017, le plus grand nombre premier obtenu est 41, avec l'expression -(17, -(2, 26)).
    Pour la date 27-02-2017, le plus grand nombre premier obtenu est 461, avec l'expression +(*(17, 27), 2).
    Pour la date 28-02-2017, le plus grand nombre premier obtenu est 73, avec l'expression +(17, *(2, 28)).
    Pour la date 01-03-2017, le plus grand nombre premier obtenu est 19, avec l'expression -(+(17, 3), 1).
    Pour la date 02-03-2017, le plus grand nombre premier obtenu est 53, avec l'expression +(*(17, 3), 2).
    Pour la date 03-03-2017, le plus grand nombre premier obtenu est 23, avec l'expression +(17, +(3, 3)).
    Pour la date 04-03-2017, le plus grand nombre premier obtenu est 71, avec l'expression +(*(17, 4), 3).
    Pour la date 05-03-2017, le plus grand nombre premier obtenu est 19, avec l'expression -(17, -(3, 5)).
    Pour la date 06-03-2017, le plus grand nombre premier obtenu est 19, avec l'expression +(17, /(6, 3)).
    Pour la date 07-03-2017, le plus grand nombre premier obtenu est 19, avec l'expression +(17, /(7, 3)).
    Pour la date 08-03-2017, le plus grand nombre premier obtenu est 139, avec l'expression +(*(17, 8), 3).
    Pour la date 09-03-2017, le plus grand nombre premier obtenu est 29, avec l'expression +(17, +(3, 9)).
    Pour la date 10-03-2017, le plus grand nombre premier obtenu est 173, avec l'expression +(*(17, 10), 3).
    Pour la date 11-03-2017, le plus grand nombre premier obtenu est 31, avec l'expression +(17, +(3, 11)).
    Pour la date 12-03-2017, le plus grand nombre premier obtenu est 53, avec l'expression +(17, *(3, 12)).
    Pour la date 13-03-2017, le plus grand nombre premier obtenu est 73, avec l'expression /(*(17, 13), 3).
    Pour la date 14-03-2017, le plus grand nombre premier obtenu est 241, avec l'expression +(*(17, 14), 3).
    Pour la date 15-03-2017, le plus grand nombre premier obtenu est 29, avec l'expression -(17, -(3, 15)).
    Pour la date 16-03-2017, le plus grand nombre premier obtenu est 269, avec l'expression -(*(17, 16), 3).
    Pour la date 17-03-2017, le plus grand nombre premier obtenu est 37, avec l'expression +(17, +(3, 17)).
    Pour la date 18-03-2017, le plus grand nombre premier obtenu est 71, avec l'expression +(17, *(3, 18)).
    Pour la date 19-03-2017, le plus grand nombre premier obtenu est 107, avec l'expression /(*(17, 19), 3).
    Pour la date 20-03-2017, le plus grand nombre premier obtenu est 337, avec l'expression -(*(17, 20), 3).
    Pour la date 21-03-2017, le plus grand nombre premier obtenu est 41, avec l'expression +(17, +(3, 21)).
    Pour la date 22-03-2017, le plus grand nombre premier obtenu est 83, avec l'expression +(17, *(3, 22)).
    Pour la date 23-03-2017, le plus grand nombre premier obtenu est 43, avec l'expression +(17, +(3, 23)).
    Pour la date 24-03-2017, le plus grand nombre premier obtenu est 89, avec l'expression +(17, *(3, 24)).
    Pour la date 25-03-2017, le plus grand nombre premier obtenu est 23, avec l'expression -(25, %(17, 3)).
    Pour la date 26-03-2017, le plus grand nombre premier obtenu est 439, avec l'expression -(*(17, 26), 3).
    Pour la date 27-03-2017, le plus grand nombre premier obtenu est 47, avec l'expression +(17, +(3, 27)).
    Pour la date 28-03-2017, le plus grand nombre premier obtenu est 479, avec l'expression +(*(17, 28), 3).
    Pour la date 29-03-2017, le plus grand nombre premier obtenu est 43, avec l'expression -(17, -(3, 29)).
    Pour la date 30-03-2017, le plus grand nombre premier obtenu est 107, avec l'expression +(17, *(3, 30)).
    Pour la date 31-03-2017, le plus grand nombre premier obtenu est 31, avec l'expression +(/(3, 17), 31).
    Pour la date 01-04-2017, le plus grand nombre premier obtenu est 67, avec l'expression -(*(17, 4), 1).
    Pour la date 02-04-2017, le plus grand nombre premier obtenu est 23, avec l'expression +(17, +(4, 2)).
    Pour la date 03-04-2017, le plus grand nombre premier obtenu est 71, avec l'expression +(*(17, 4), 3).
    Pour la date 04-04-2017, le plus grand nombre premier obtenu est 17, avec l'expression /(17, /(4, 4)).
    Pour la date 05-04-2017, le plus grand nombre premier obtenu est 89, avec l'expression +(*(17, 5), 4).
    Pour la date 06-04-2017, le plus grand nombre premier obtenu est 41, avec l'expression +(17, *(4, 6)).
    Pour la date 07-04-2017, le plus grand nombre premier obtenu est 61, avec l'expression -(*(17, 4), 7).
    Pour la date 08-04-2017, le plus grand nombre premier obtenu est 29, avec l'expression +(17, +(4, 8)).
    Pour la date 09-04-2017, le plus grand nombre premier obtenu est 157, avec l'expression +(*(17, 9), 4).
    Pour la date 10-04-2017, le plus grand nombre premier obtenu est 31, avec l'expression +(17, +(4, 10)).
    Pour la date 11-04-2017, le plus grand nombre premier obtenu est 191, avec l'expression +(*(17, 11), 4).
    Pour la date 12-04-2017, le plus grand nombre premier obtenu est 31, avec l'expression -(*(4, 12), 17).
    Pour la date 13-04-2017, le plus grand nombre premier obtenu est 17, avec l'expression -(17, /(4, 13)).
    Pour la date 14-04-2017, le plus grand nombre premier obtenu est 73, avec l'expression +(17, *(4, 14)).
    Pour la date 15-04-2017, le plus grand nombre premier obtenu est 251, avec l'expression -(*(17, 15), 4).
    Pour la date 16-04-2017, le plus grand nombre premier obtenu est 47, avec l'expression -(*(4, 16), 17).
    Pour la date 17-04-2017, le plus grand nombre premier obtenu est 293, avec l'expression +(*(17, 17), 4).
    Pour la date 18-04-2017, le plus grand nombre premier obtenu est 89, avec l'expression +(17, *(4, 18)).
    Pour la date 19-04-2017, le plus grand nombre premier obtenu est 59, avec l'expression -(*(4, 19), 17).
    Pour la date 20-04-2017, le plus grand nombre premier obtenu est 97, avec l'expression +(17, *(4, 20)).
    Pour la date 21-04-2017, le plus grand nombre premier obtenu est 353, avec l'expression -(*(17, 21), 4).
    Pour la date 22-04-2017, le plus grand nombre premier obtenu est 71, avec l'expression -(*(4, 22), 17).
    Pour la date 23-04-2017, le plus grand nombre premier obtenu est 109, avec l'expression +(17, *(4, 23)).
    Pour la date 24-04-2017, le plus grand nombre premier obtenu est 113, avec l'expression +(17, *(4, 24)).
    Pour la date 25-04-2017, le plus grand nombre premier obtenu est 421, avec l'expression -(*(17, 25), 4).
    Pour la date 26-04-2017, le plus grand nombre premier obtenu est 47, avec l'expression +(17, +(4, 26)).
    Pour la date 27-04-2017, le plus grand nombre premier obtenu est 463, avec l'expression +(*(17, 27), 4).
    Pour la date 28-04-2017, le plus grand nombre premier obtenu est 41, avec l'expression -(17, -(4, 28)).
    Pour la date 29-04-2017, le plus grand nombre premier obtenu est 97, avec l'expression +(*(17, 4), 29).
    Pour la date 30-04-2017, le plus grand nombre premier obtenu est 137, avec l'expression +(17, *(4, 30)).
    Pour la date 01-05-2017, le plus grand nombre premier obtenu est 23, avec l'expression +(17, +(5, 1)).
    Pour la date 02-05-2017, le plus grand nombre premier obtenu est 83, avec l'expression -(*(17, 5), 2).
    Pour la date 03-05-2017, le plus grand nombre premier obtenu est 19, avec l'expression -(+(17, 5), 3).
    Pour la date 04-05-2017, le plus grand nombre premier obtenu est 89, avec l'expression +(*(17, 5), 4).
    Pour la date 05-05-2017, le plus grand nombre premier obtenu est 17, avec l'expression /(17, /(5, 5)).
    Pour la date 06-05-2017, le plus grand nombre premier obtenu est 107, avec l'expression +(*(17, 6), 5).
    Pour la date 07-05-2017, le plus grand nombre premier obtenu est 29, avec l'expression +(17, +(5, 7)).
    Pour la date 08-05-2017, le plus grand nombre premier obtenu est 131, avec l'expression -(*(17, 8), 5).
    Pour la date 09-05-2017, le plus grand nombre premier obtenu est 31, avec l'expression +(17, +(5, 9)).
    Pour la date 10-05-2017, le plus grand nombre premier obtenu est 67, avec l'expression +(17, *(5, 10)).
    Pour la date 11-05-2017, le plus grand nombre premier obtenu est 37, avec l'expression /(*(17, 11), 5).
    Pour la date 12-05-2017, le plus grand nombre premier obtenu est 199, avec l'expression -(*(17, 12), 5).
    Pour la date 13-05-2017, le plus grand nombre premier obtenu est 19, avec l'expression +(17, /(13, 5)).
    Pour la date 14-05-2017, le plus grand nombre premier obtenu est 233, avec l'expression -(*(17, 14), 5).
    Pour la date 15-05-2017, le plus grand nombre premier obtenu est 37, avec l'expression +(17, +(5, 15)).
    Pour la date 16-05-2017, le plus grand nombre premier obtenu est 277, avec l'expression +(*(17, 16), 5).
    Pour la date 17-05-2017, le plus grand nombre premier obtenu est 29, avec l'expression -(17, -(5, 17)).
    Pour la date 18-05-2017, le plus grand nombre premier obtenu est 311, avec l'expression +(*(17, 18), 5).
    Pour la date 19-05-2017, le plus grand nombre premier obtenu est 41, avec l'expression +(17, +(5, 19)).
    Pour la date 20-05-2017, le plus grand nombre premier obtenu est 83, avec l'expression -(*(5, 20), 17).
    Pour la date 21-05-2017, le plus grand nombre premier obtenu est 71, avec l'expression /(*(17, 21), 5).
    Pour la date 22-05-2017, le plus grand nombre premier obtenu est 379, avec l'expression +(*(17, 22), 5).
    Pour la date 23-05-2017, le plus grand nombre premier obtenu est 23, avec l'expression +(/(5, 17), 23).
    Pour la date 24-05-2017, le plus grand nombre premier obtenu est 137, avec l'expression +(17, *(5, 24)).
    Pour la date 25-05-2017, le plus grand nombre premier obtenu est 47, avec l'expression +(17, +(5, 25)).
    Pour la date 26-05-2017, le plus grand nombre premier obtenu est 113, avec l'expression -(*(5, 26), 17).
    Pour la date 27-05-2017, le plus grand nombre premier obtenu est 29, avec l'expression +(%(17, 5), 27).
    Pour la date 28-05-2017, le plus grand nombre premier obtenu est 157, avec l'expression +(17, *(5, 28)).
    Pour la date 29-05-2017, le plus grand nombre premier obtenu est 41, avec l'expression -(17, -(5, 29)).
    Pour la date 30-05-2017, le plus grand nombre premier obtenu est 167, avec l'expression +(17, *(5, 30)).
    Pour la date 31-05-2017, le plus grand nombre premier obtenu est 53, avec l'expression +(17, +(5, 31)).
    Pour la date 01-06-2017, le plus grand nombre premier obtenu est 103, avec l'expression +(*(17, 6), 1).
    Pour la date 02-06-2017, le plus grand nombre premier obtenu est 29, avec l'expression +(17, *(6, 2)).
    Pour la date 03-06-2017, le plus grand nombre premier obtenu est 19, avec l'expression +(17, /(6, 3)).
    Pour la date 04-06-2017, le plus grand nombre premier obtenu est 41, avec l'expression +(17, *(6, 4)).
    Pour la date 05-06-2017, le plus grand nombre premier obtenu est 107, avec l'expression +(*(17, 6), 5).
    Pour la date 06-06-2017, le plus grand nombre premier obtenu est 53, avec l'expression +(17, *(6, 6)).
    Pour la date 07-06-2017, le plus grand nombre premier obtenu est 113, avec l'expression -(*(17, 7), 6).
    Pour la date 08-06-2017, le plus grand nombre premier obtenu est 31, avec l'expression +(17, +(6, 8)).
    Pour la date 09-06-2017, le plus grand nombre premier obtenu est 71, avec l'expression +(17, *(6, 9)).
    Pour la date 10-06-2017, le plus grand nombre premier obtenu est 43, avec l'expression -(*(6, 10), 17).
    Pour la date 11-06-2017, le plus grand nombre premier obtenu est 193, avec l'expression +(*(17, 11), 6).
    Pour la date 12-06-2017, le plus grand nombre premier obtenu est 89, avec l'expression +(17, *(6, 12)).
    Pour la date 13-06-2017, le plus grand nombre premier obtenu est 227, avec l'expression +(*(17, 13), 6).
    Pour la date 14-06-2017, le plus grand nombre premier obtenu est 101, avec l'expression +(17, *(6, 14)).
    Pour la date 15-06-2017, le plus grand nombre premier obtenu est 107, avec l'expression +(17, *(6, 15)).
    Pour la date 16-06-2017, le plus grand nombre premier obtenu est 113, avec l'expression +(17, *(6, 16)).
    Pour la date 17-06-2017, le plus grand nombre premier obtenu est 283, avec l'expression -(*(17, 17), 6).
    Pour la date 18-06-2017, le plus grand nombre premier obtenu est 41, avec l'expression +(17, +(6, 18)).
    Pour la date 19-06-2017, le plus grand nombre premier obtenu est 317, avec l'expression -(*(17, 19), 6).
    Pour la date 20-06-2017, le plus grand nombre premier obtenu est 137, avec l'expression +(17, *(6, 20)).
    Pour la date 21-06-2017, le plus grand nombre premier obtenu est 109, avec l'expression -(*(6, 21), 17).
    Pour la date 22-06-2017, le plus grand nombre premier obtenu est 149, avec l'expression +(17, *(6, 22)).
    Pour la date 23-06-2017, le plus grand nombre premier obtenu est 397, avec l'expression +(*(17, 23), 6).
    Pour la date 24-06-2017, le plus grand nombre premier obtenu est 127, avec l'expression -(*(6, 24), 17).
    Pour la date 25-06-2017, le plus grand nombre premier obtenu est 431, avec l'expression +(*(17, 25), 6).
    Pour la date 26-06-2017, le plus grand nombre premier obtenu est 173, avec l'expression +(17, *(6, 26)).
    Pour la date 27-06-2017, le plus grand nombre premier obtenu est 179, avec l'expression +(17, *(6, 27)).
    Pour la date 28-06-2017, le plus grand nombre premier obtenu est 151, avec l'expression -(*(6, 28), 17).
    Pour la date 29-06-2017, le plus grand nombre premier obtenu est 499, avec l'expression +(*(17, 29), 6).
    Pour la date 30-06-2017, le plus grand nombre premier obtenu est 197, avec l'expression +(17, *(6, 30)).
    Pour la date 01-07-2017, le plus grand nombre premier obtenu est 23, avec l'expression -(+(17, 7), 1).
    Pour la date 02-07-2017, le plus grand nombre premier obtenu est 59, avec l'expression /(*(17, 7), 2).
    Pour la date 03-07-2017, le plus grand nombre premier obtenu est 19, avec l'expression +(17, /(7, 3)).
    Pour la date 04-07-2017, le plus grand nombre premier obtenu est 61, avec l'expression -(*(17, 4), 7).
    Pour la date 05-07-2017, le plus grand nombre premier obtenu est 29, avec l'expression +(17, +(7, 5)).
    Pour la date 06-07-2017, le plus grand nombre premier obtenu est 113, avec l'expression -(*(17, 7), 6).
    Pour la date 07-07-2017, le plus grand nombre premier obtenu est 31, avec l'expression +(17, +(7, 7)).
    Pour la date 08-07-2017, le plus grand nombre premier obtenu est 127, avec l'expression +(*(17, 7), 8).
    Pour la date 09-07-2017, le plus grand nombre premier obtenu est 19, avec l'expression -(17, -(7, 9)).
    Pour la date 10-07-2017, le plus grand nombre premier obtenu est 163, avec l'expression -(*(17, 10), 7).
    Pour la date 11-07-2017, le plus grand nombre premier obtenu est 17, avec l'expression -(17, /(7, 11)).
    Pour la date 12-07-2017, le plus grand nombre premier obtenu est 211, avec l'expression +(*(17, 12), 7).
    Pour la date 13-07-2017, le plus grand nombre premier obtenu est 37, avec l'expression +(17, +(7, 13)).
    Pour la date 14-07-2017, le plus grand nombre premier obtenu est 19, avec l'expression +(17, /(14, 7)).
    Pour la date 15-07-2017, le plus grand nombre premier obtenu est 19, avec l'expression +(17, /(15, 7)).
    Pour la date 16-07-2017, le plus grand nombre premier obtenu est 103, avec l'expression -(*(17, 7), 16).
    Pour la date 17-07-2017, le plus grand nombre premier obtenu est 41, avec l'expression +(17, +(7, 17)).
    Pour la date 18-07-2017, le plus grand nombre premier obtenu est 313, avec l'expression +(*(17, 18), 7).
    Pour la date 19-07-2017, le plus grand nombre premier obtenu est 43, avec l'expression +(17, +(7, 19)).
    Pour la date 20-07-2017, le plus grand nombre premier obtenu est 347, avec l'expression +(*(17, 20), 7).
    Pour la date 21-07-2017, le plus grand nombre premier obtenu est 31, avec l'expression -(17, -(7, 21)).
    Pour la date 22-07-2017, le plus grand nombre premier obtenu est 367, avec l'expression -(*(17, 22), 7).
    Pour la date 23-07-2017, le plus grand nombre premier obtenu est 47, avec l'expression +(17, +(7, 23)).
    Pour la date 24-07-2017, le plus grand nombre premier obtenu est 401, avec l'expression -(*(17, 24), 7).
    Pour la date 25-07-2017, le plus grand nombre premier obtenu est 23, avec l'expression -(25, /(17, 7)).
    Pour la date 26-07-2017, le plus grand nombre premier obtenu est 449, avec l'expression +(*(17, 26), 7).
    Pour la date 27-07-2017, le plus grand nombre premier obtenu est 37, avec l'expression -(17, -(7, 27)).
    Pour la date 28-07-2017, le plus grand nombre premier obtenu est 179, avec l'expression -(*(7, 28), 17).
    Pour la date 29-07-2017, le plus grand nombre premier obtenu est 53, avec l'expression +(17, +(7, 29)).
    Pour la date 30-07-2017, le plus grand nombre premier obtenu est 503, avec l'expression -(*(17, 30), 7).
    Pour la date 31-07-2017, le plus grand nombre premier obtenu est 41, avec l'expression -(17, -(7, 31)).
    Pour la date 01-08-2017, le plus grand nombre premier obtenu est 137, avec l'expression +(*(17, 8), 1).
    Pour la date 02-08-2017, le plus grand nombre premier obtenu est 23, avec l'expression -(+(17, 8), 2).
    Pour la date 03-08-2017, le plus grand nombre premier obtenu est 139, avec l'expression +(*(17, 8), 3).
    Pour la date 04-08-2017, le plus grand nombre premier obtenu est 29, avec l'expression +(17, +(8, 4)).
    Pour la date 05-08-2017, le plus grand nombre premier obtenu est 131, avec l'expression -(*(17, 8), 5).
    Pour la date 06-08-2017, le plus grand nombre premier obtenu est 31, avec l'expression +(17, +(8, 6)).
    Pour la date 07-08-2017, le plus grand nombre premier obtenu est 127, avec l'expression +(*(17, 7), 8).
    Pour la date 08-08-2017, le plus grand nombre premier obtenu est 47, avec l'expression -(*(8, 8), 17).
    Pour la date 09-08-2017, le plus grand nombre premier obtenu est 127, avec l'expression -(*(17, 8), 9).
    Pour la date 10-08-2017, le plus grand nombre premier obtenu est 97, avec l'expression +(17, *(8, 10)).
    Pour la date 11-08-2017, le plus grand nombre premier obtenu est 179, avec l'expression -(*(17, 11), 8).
    Pour la date 12-08-2017, le plus grand nombre premier obtenu est 113, avec l'expression +(17, *(8, 12)).
    Pour la date 13-08-2017, le plus grand nombre premier obtenu est 229, avec l'expression +(*(17, 13), 8).
    Pour la date 14-08-2017, le plus grand nombre premier obtenu est 29, avec l'expression /(*(17, 14), 8).
    Pour la date 15-08-2017, le plus grand nombre premier obtenu est 263, avec l'expression +(*(17, 15), 8).
    Pour la date 16-08-2017, le plus grand nombre premier obtenu est 41, avec l'expression +(17, +(8, 16)).
    Pour la date 17-08-2017, le plus grand nombre premier obtenu est 281, avec l'expression -(*(17, 17), 8).
    Pour la date 18-08-2017, le plus grand nombre premier obtenu est 127, avec l'expression -(*(8, 18), 17).
    Pour la date 19-08-2017, le plus grand nombre premier obtenu est 331, avec l'expression +(*(17, 19), 8).
    Pour la date 20-08-2017, le plus grand nombre premier obtenu est 29, avec l'expression -(17, -(8, 20)).
    Pour la date 21-08-2017, le plus grand nombre premier obtenu est 349, avec l'expression -(*(17, 21), 8).
    Pour la date 22-08-2017, le plus grand nombre premier obtenu est 193, avec l'expression +(17, *(8, 22)).
    Pour la date 23-08-2017, le plus grand nombre premier obtenu est 383, avec l'expression -(*(17, 23), 8).
    Pour la date 24-08-2017, le plus grand nombre premier obtenu est 23, avec l'expression -(24, %(17, 8)).
    Pour la date 25-08-2017, le plus grand nombre premier obtenu est 433, avec l'expression +(*(17, 25), 8).
    Pour la date 26-08-2017, le plus grand nombre premier obtenu est 191, avec l'expression -(*(8, 26), 17).
    Pour la date 27-08-2017, le plus grand nombre premier obtenu est 467, avec l'expression +(*(17, 27), 8).
    Pour la date 28-08-2017, le plus grand nombre premier obtenu est 241, avec l'expression +(17, *(8, 28)).
    Pour la date 29-08-2017, le plus grand nombre premier obtenu est 107, avec l'expression -(*(17, 8), 29).
    Pour la date 30-08-2017, le plus grand nombre premier obtenu est 257, avec l'expression +(17, *(8, 30)).
    Pour la date 31-08-2017, le plus grand nombre premier obtenu est 167, avec l'expression +(*(17, 8), 31).
    Pour la date 01-09-2017, le plus grand nombre premier obtenu est 17, avec l'expression -(17, %(9, 1)).
    Pour la date 02-09-2017, le plus grand nombre premier obtenu est 151, avec l'expression -(*(17, 9), 2).
    Pour la date 03-09-2017, le plus grand nombre premier obtenu est 29, avec l'expression +(17, +(9, 3)).
    Pour la date 04-09-2017, le plus grand nombre premier obtenu est 157, avec l'expression +(*(17, 9), 4).
    Pour la date 05-09-2017, le plus grand nombre premier obtenu est 31, avec l'expression +(17, +(9, 5)).
    Pour la date 06-09-2017, le plus grand nombre premier obtenu est 71, avec l'expression +(17, *(9, 6)).
    Pour la date 07-09-2017, le plus grand nombre premier obtenu est 19, avec l'expression -(+(17, 9), 7).
    Pour la date 08-09-2017, le plus grand nombre premier obtenu est 127, avec l'expression -(*(17, 8), 9).
    Pour la date 09-09-2017, le plus grand nombre premier obtenu est 17, avec l'expression /(17, /(9, 9)).
    Pour la date 10-09-2017, le plus grand nombre premier obtenu est 179, avec l'expression +(*(17, 10), 9).
    Pour la date 11-09-2017, le plus grand nombre premier obtenu est 37, avec l'expression +(17, +(9, 11)).
    Pour la date 12-09-2017, le plus grand nombre premier obtenu est 17, avec l'expression -(17, /(9, 12)).
    Pour la date 13-09-2017, le plus grand nombre premier obtenu est 17, avec l'expression -(17, /(9, 13)).
    Pour la date 14-09-2017, le plus grand nombre premier obtenu est 229, avec l'expression -(*(17, 14), 9).
    Pour la date 15-09-2017, le plus grand nombre premier obtenu est 41, avec l'expression +(17, +(9, 15)).
    Pour la date 16-09-2017, le plus grand nombre premier obtenu est 281, avec l'expression +(*(17, 16), 9).
    Pour la date 17-09-2017, le plus grand nombre premier obtenu est 43, avec l'expression +(17, +(9, 17)).
    Pour la date 18-09-2017, le plus grand nombre premier obtenu est 179, avec l'expression +(17, *(9, 18)).
    Pour la date 19-09-2017, le plus grand nombre premier obtenu est 19, avec l'expression *(/(17, 9), 19).
    Pour la date 20-09-2017, le plus grand nombre premier obtenu est 349, avec l'expression +(*(17, 20), 9).
    Pour la date 21-09-2017, le plus grand nombre premier obtenu est 47, avec l'expression +(17, +(9, 21)).
    Pour la date 22-09-2017, le plus grand nombre premier obtenu est 383, avec l'expression +(*(17, 22), 9).
    Pour la date 23-09-2017, le plus grand nombre premier obtenu est 43, avec l'expression /(*(17, 23), 9).
    Pour la date 24-09-2017, le plus grand nombre premier obtenu est 233, avec l'expression +(17, *(9, 24)).
    Pour la date 25-09-2017, le plus grand nombre premier obtenu est 47, avec l'expression /(*(17, 25), 9).
    Pour la date 26-09-2017, le plus grand nombre premier obtenu est 433, avec l'expression -(*(17, 26), 9).
    Pour la date 27-09-2017, le plus grand nombre premier obtenu est 53, avec l'expression +(17, +(9, 27)).
    Pour la date 28-09-2017, le plus grand nombre premier obtenu est 467, avec l'expression -(*(17, 28), 9).
    Pour la date 29-09-2017, le plus grand nombre premier obtenu est 37, avec l'expression -(17, -(9, 29)).
    Pour la date 30-09-2017, le plus grand nombre premier obtenu est 31, avec l'expression +(/(17, 9), 30).
    Pour la date 01-10-2017, le plus grand nombre premier obtenu est 17, avec l'expression -(17, %(10, 1)).
    Pour la date 02-10-2017, le plus grand nombre premier obtenu est 37, avec l'expression +(17, *(10, 2)).
    Pour la date 03-10-2017, le plus grand nombre premier obtenu est 173, avec l'expression +(*(17, 10), 3).
    Pour la date 04-10-2017, le plus grand nombre premier obtenu est 31, avec l'expression +(17, +(10, 4)).
    Pour la date 05-10-2017, le plus grand nombre premier obtenu est 67, avec l'expression +(17, *(10, 5)).
    Pour la date 06-10-2017, le plus grand nombre premier obtenu est 43, avec l'expression -(*(10, 6), 17).
    Pour la date 07-10-2017, le plus grand nombre premier obtenu est 163, avec l'expression -(*(17, 10), 7).
    Pour la date 08-10-2017, le plus grand nombre premier obtenu est 97, avec l'expression +(17, *(10, 8)).
    Pour la date 09-10-2017, le plus grand nombre premier obtenu est 179, avec l'expression +(*(17, 10), 9).
    Pour la date 10-10-2017, le plus grand nombre premier obtenu est 83, avec l'expression -(*(10, 10), 17).
    Pour la date 11-10-2017, le plus grand nombre premier obtenu est 197, avec l'expression +(*(17, 11), 10).
    Pour la date 12-10-2017, le plus grand nombre premier obtenu est 137, avec l'expression +(17, *(10, 12)).
    Pour la date 13-10-2017, le plus grand nombre premier obtenu est 211, avec l'expression -(*(17, 13), 10).
    Pour la date 14-10-2017, le plus grand nombre premier obtenu est 157, avec l'expression +(17, *(10, 14)).
    Pour la date 15-10-2017, le plus grand nombre premier obtenu est 167, avec l'expression +(17, *(10, 15)).
    Pour la date 16-10-2017, le plus grand nombre premier obtenu est 43, avec l'expression +(17, +(10, 16)).
    Pour la date 17-10-2017, le plus grand nombre premier obtenu est 17, avec l'expression -(17, /(10, 17)).
    Pour la date 18-10-2017, le plus grand nombre premier obtenu est 197, avec l'expression +(17, *(10, 18)).
    Pour la date 19-10-2017, le plus grand nombre premier obtenu est 313, avec l'expression -(*(17, 19), 10).
    Pour la date 20-10-2017, le plus grand nombre premier obtenu est 47, avec l'expression +(17, +(10, 20)).
    Pour la date 21-10-2017, le plus grand nombre premier obtenu est 367, avec l'expression +(*(17, 21), 10).
    Pour la date 22-10-2017, le plus grand nombre premier obtenu est 37, avec l'expression /(*(17, 22), 10).
    Pour la date 23-10-2017, le plus grand nombre premier obtenu est 401, avec l'expression +(*(17, 23), 10).
    Pour la date 24-10-2017, le plus grand nombre premier obtenu est 257, avec l'expression +(17, *(10, 24)).
    Pour la date 25-10-2017, le plus grand nombre premier obtenu est 233, avec l'expression -(*(10, 25), 17).
    Pour la date 26-10-2017, le plus grand nombre premier obtenu est 277, avec l'expression +(17, *(10, 26)).
    Pour la date 27-10-2017, le plus grand nombre premier obtenu est 449, avec l'expression -(*(17, 27), 10).
    Pour la date 28-10-2017, le plus grand nombre premier obtenu est 263, avec l'expression -(*(10, 28), 17).
    Pour la date 29-10-2017, le plus grand nombre premier obtenu est 503, avec l'expression +(*(17, 29), 10).
    Pour la date 30-10-2017, le plus grand nombre premier obtenu est 317, avec l'expression +(17, *(10, 30)).
    Pour la date 31-10-2017, le plus grand nombre premier obtenu est 293, avec l'expression -(*(10, 31), 17).
    Pour la date 01-11-2017, le plus grand nombre premier obtenu est 29, avec l'expression +(17, +(11, 1)).
    Pour la date 02-11-2017, le plus grand nombre premier obtenu est 23, avec l'expression -(*(17, 2), 11).
    Pour la date 03-11-2017, le plus grand nombre premier obtenu est 31, avec l'expression +(17, +(11, 3)).
    Pour la date 04-11-2017, le plus grand nombre premier obtenu est 191, avec l'expression +(*(17, 11), 4).
    Pour la date 05-11-2017, le plus grand nombre premier obtenu est 37, avec l'expression /(*(17, 11), 5).
    Pour la date 06-11-2017, le plus grand nombre premier obtenu est 193, avec l'expression +(*(17, 11), 6).
    Pour la date 07-11-2017, le plus grand nombre premier obtenu est 17, avec l'expression /(17, /(11, 7)).
    Pour la date 08-11-2017, le plus grand nombre premier obtenu est 179, avec l'expression -(*(17, 11), 8).
    Pour la date 09-11-2017, le plus grand nombre premier obtenu est 37, avec l'expression +(17, +(11, 9)).
    Pour la date 10-11-2017, le plus grand nombre premier obtenu est 197, avec l'expression +(*(17, 11), 10).
    Pour la date 11-11-2017, le plus grand nombre premier obtenu est 17, avec l'expression /(17, /(11, 11)).
    Pour la date 12-11-2017, le plus grand nombre premier obtenu est 199, avec l'expression +(*(17, 11), 12).
    Pour la date 13-11-2017, le plus grand nombre premier obtenu est 41, avec l'expression +(17, +(11, 13)).
    Pour la date 14-11-2017, le plus grand nombre premier obtenu est 227, avec l'expression -(*(17, 14), 11).
    Pour la date 15-11-2017, le plus grand nombre premier obtenu est 43, avec l'expression +(17, +(11, 15)).
    Pour la date 16-11-2017, le plus grand nombre premier obtenu est 283, avec l'expression +(*(17, 16), 11).
    Pour la date 17-11-2017, le plus grand nombre premier obtenu est 23, avec l'expression -(17, -(11, 17)).
    Pour la date 18-11-2017, le plus grand nombre premier obtenu est 317, avec l'expression +(*(17, 18), 11).
    Pour la date 19-11-2017, le plus grand nombre premier obtenu est 47, avec l'expression +(17, +(11, 19)).
    Pour la date 20-11-2017, le plus grand nombre premier obtenu est 167, avec l'expression -(*(17, 11), 20).
    Pour la date 21-11-2017, le plus grand nombre premier obtenu est 19, avec l'expression %(*(17, 11), 21).
    Pour la date 22-11-2017, le plus grand nombre premier obtenu est 23, avec l'expression +(/(17, 11), 22).
    Pour la date 23-11-2017, le plus grand nombre premier obtenu est 29, avec l'expression -(17, -(11, 23)).
    Pour la date 24-11-2017, le plus grand nombre premier obtenu est 419, avec l'expression +(*(17, 24), 11).
    Pour la date 25-11-2017, le plus grand nombre premier obtenu est 53, avec l'expression +(17, +(11, 25)).
    Pour la date 26-11-2017, le plus grand nombre premier obtenu est 431, avec l'expression -(*(17, 26), 11).
    Pour la date 27-11-2017, le plus grand nombre premier obtenu est 41, avec l'expression /(*(17, 27), 11).
    Pour la date 28-11-2017, le plus grand nombre premier obtenu est 487, avec l'expression +(*(17, 28), 11).
    Pour la date 29-11-2017, le plus grand nombre premier obtenu est 29, avec l'expression *(/(17, 11), 29).
    Pour la date 30-11-2017, le plus grand nombre premier obtenu est 521, avec l'expression +(*(17, 30), 11).
    Pour la date 01-12-2017, le plus grand nombre premier obtenu est 29, avec l'expression /(+(17, 12), 1).
    Pour la date 02-12-2017, le plus grand nombre premier obtenu est 41, avec l'expression +(17, *(12, 2)).
    Pour la date 03-12-2017, le plus grand nombre premier obtenu est 53, avec l'expression +(17, *(12, 3)).
    Pour la date 04-12-2017, le plus grand nombre premier obtenu est 31, avec l'expression -(*(12, 4), 17).
    Pour la date 05-12-2017, le plus grand nombre premier obtenu est 199, avec l'expression -(*(17, 12), 5).
    Pour la date 06-12-2017, le plus grand nombre premier obtenu est 89, avec l'expression +(17, *(12, 6)).
    Pour la date 07-12-2017, le plus grand nombre premier obtenu est 211, avec l'expression +(*(17, 12), 7).
    Pour la date 08-12-2017, le plus grand nombre premier obtenu est 113, avec l'expression +(17, *(12, 8)).
    Pour la date 09-12-2017, le plus grand nombre premier obtenu est 17, avec l'expression /(17, /(12, 9)).
    Pour la date 10-12-2017, le plus grand nombre premier obtenu est 137, avec l'expression +(17, *(12, 10)).
    Pour la date 11-12-2017, le plus grand nombre premier obtenu est 199, avec l'expression +(*(17, 11), 12).
    Pour la date 12-12-2017, le plus grand nombre premier obtenu est 127, avec l'expression -(*(12, 12), 17).
    Pour la date 13-12-2017, le plus grand nombre premier obtenu est 233, avec l'expression +(*(17, 13), 12).
    Pour la date 14-12-2017, le plus grand nombre premier obtenu est 151, avec l'expression -(*(12, 14), 17).
    Pour la date 15-12-2017, le plus grand nombre premier obtenu est 197, avec l'expression +(17, *(12, 15)).
    Pour la date 16-12-2017, le plus grand nombre premier obtenu est 29, avec l'expression +(17, %(12, 16)).
    Pour la date 17-12-2017, le plus grand nombre premier obtenu est 277, avec l'expression -(*(17, 17), 12).
    Pour la date 18-12-2017, le plus grand nombre premier obtenu est 233, avec l'expression +(17, *(12, 18)).
    Pour la date 19-12-2017, le plus grand nombre premier obtenu est 311, avec l'expression -(*(17, 19), 12).
    Pour la date 20-12-2017, le plus grand nombre premier obtenu est 257, avec l'expression +(17, *(12, 20)).
    Pour la date 21-12-2017, le plus grand nombre premier obtenu est 269, avec l'expression +(17, *(12, 21)).
    Pour la date 22-12-2017, le plus grand nombre premier obtenu est 281, avec l'expression +(17, *(12, 22)).
    Pour la date 23-12-2017, le plus grand nombre premier obtenu est 379, avec l'expression -(*(17, 23), 12).
    Pour la date 24-12-2017, le plus grand nombre premier obtenu est 271, avec l'expression -(*(12, 24), 17).
    Pour la date 25-12-2017, le plus grand nombre premier obtenu est 317, avec l'expression +(17, *(12, 25)).
    Pour la date 26-12-2017, le plus grand nombre premier obtenu est 31, avec l'expression -(17, -(12, 26)).
    Pour la date 27-12-2017, le plus grand nombre premier obtenu est 307, avec l'expression -(*(12, 27), 17).
    Pour la date 28-12-2017, le plus grand nombre premier obtenu est 353, avec l'expression +(17, *(12, 28)).
    Pour la date 29-12-2017, le plus grand nombre premier obtenu est 331, avec l'expression -(*(12, 29), 17).
    Pour la date 30-12-2017, le plus grand nombre premier obtenu est 59, avec l'expression +(17, +(12, 30)).
    Pour la date 31-12-2017, le plus grand nombre premier obtenu est 389, avec l'expression +(17, *(12, 31)).

```python
max(tous, key=lambda t: t[1])
```

    ([30, 11, 17], 521, '+(*(17, 30), 11)')

Le plus grand nombre premier est donc pour le 30 novembre 2017, 521 obtenu avec $(17 \times 30) + 11$.

----
## Conclusions
- C'était bien inutile.
- Mais marrant.

> C'est tout pour aujourd'hui les amis, [allez voir d'autres notebooks si vous êtes curieux !](https://github.com/Naereen/notebooks/).

> [See this repository for other Python notebook doing numerical simulations](https://github.com/Naereen/notebooks/tree/master/simus/).


#   Copyright statement:
#### The initial code was provided by Dr. Sandoval (Assitant professor of Electrical Engineering, New Mexico State University) as a starting point for project. https://web.nmsu.edu/~spsandov/
#### The algorithm in current form is developed by Sina Zamen.

#   Objective:
## In this problem, convergence of Newton’s method for Himmelblau’s cost function, is considered. The initial weight vectors drawn from a Gaussian distribution with mean 0 and variance equal to 1.

### Loading Required Modules:

```python
import math
import numpy as np
import sympy as sym
from PIL import Image
from numpy import array
from matplotlib import style
from numpy.linalg import inv
import matplotlib.pyplot as plt
style.use('ggplot')
```

### Defining Gaussian Dataset Generator Function:

```python
def circGuass(N,Me,VAR):
    dimension = len(Me)
    a = np.array(Me)
    p = np.random.randn(dimension,N)
    h = np.tile(a, (2, N//2))
    x = math.sqrt(Var) * p + h
    return x
```

```python
M1 = 0
Var = 1
N = 2
firstmean = [M1, M1]
Me = firstmean;
X = circGuass(N, Me, Var)
```

### Setting up countour map:

```python
HX ,HY  = 50,50 #number of x,y  points for countour
xmin,xmax = -15,15
ymin,ymax = -12,12
x1 = np.linspace(xmin,xmax,HX)
x2 = np.linspace(ymin,ymax,HY)
X1,X2 = np.meshgrid(x1,x2) # genertate mesh grid
w1=sym.Symbol('w1') # define symbols
w2=sym.Symbol('w2')
```

```python
bowl = 'bowl'
case2= ''
case = bowl #case select
if(case==bowl):
    j=(w1**2 + w2 - 11)**2 + (w1 + w2**2 - 11)**2 # define equation
elif(case==case2):
    #define other surfaces here
    pass
else:
    print('case not recognized')
```

```python
#compute gradient
j_grad1=sym.diff(j,w1)
j_grad2=sym.diff(j,w2)
#compute hessian
hess11=sym.diff(j_grad1,w1)
hess12=sym.diff(j_grad1,w2)
hess21=sym.diff(j_grad2,w1)
hess22=sym.diff(j_grad2,w2)

#generate contour map
ConMap=np.zeros((HX,HY))
for i in range(HX):
    for k in range(HY):
        ConMap[i,k]=j.subs({w1:x1[i],w2:x2[k]})

w=X[0,:] # starting point
wStar1=[3,2] # ending point
wStar2=[-2.8,3.13] # ending point
wStar3=[-3.78,-3.28] # ending point
wStar4=[3.58,-1.85] # ending point
distance1 = math.sqrt(sum([(a - b) ** 2 for a, b in zip(w, wStar1)]))
distance2 = math.sqrt(sum([(a - b) ** 2 for a, b in zip(w, wStar2)]))
distance3 = math.sqrt(sum([(a - b) ** 2 for a, b in zip(w, wStar3)]))
distance4 = math.sqrt(sum([(a - b) ** 2 for a, b in zip(w, wStar4)]))
if distance1 < distance2 and distance1 < distance3 and distance1 < distance4:
    wStar = wStar1
elif distance2 < distance1 and distance2 < distance3 and distance2 < distance4:
    wStar = wStar2
elif distance3 < distance1 and distance3 < distance2 and distance3 < distance4:
    wStar = wStar3
else:
    wStar = wStar4
ew=array([])
jw=array([100])
eta = 0.9 # learning rate
lim=400 # number of iterations
count= 0
line=[]
h = jw.astype(int)
h = jw[-1:].astype(int)
while (h > 0.05):
    h = jw[-1:].astype(int)
    #compute gradient matrix and hessian matrix
    g= np.array([float(j_grad1.subs({w1:w[0],w2:w[1]})),float(j_grad2.subs({w1:w[0],w2:w[1]}))])
    H= np.array([[float(hess11.subs({w1:w[0],w2:w[1]})),float(hess12.subs({w1:w[0],w2:w[1]}))],                 [float(hess21.subs({w1:w[0],w2:w[1]})),float(hess22.subs({w1:w[0],w2:w[1]}))]])

    wnew1 =  w-eta*((inv(H)*np.asmatrix(g).T).T)
    wnew = np.squeeze(np.asarray(wnew1))
    #loop check
    if( count>lim ):
        print('Count Break')
        break
    elif(np.isnan(g).any()):
        print('nan break')
        break
    else:
        count=count +1
        wprev=w.copy()

        line.append(w)
        w=wnew.copy()

        ew1 = (np.linalg.norm(w-wStar))
        jw1 = (j.subs({w1:w[0],w2:w[1]}))
        ew = np.append(ew, ew1)
        jw = np.append(jw, jw1)
line=np.array(line)
```

    Count Break

```python
print("Number of Iterations:")
print(count)
```

    Number of Iterations:
    401

```python
plt.figure(figsize=(10,10))
plt.contour(X1,X2,ConMap)
plt.plot(line[:,1],line[:,0])
plt.scatter(line[:,1],line[:,0])
plt.plot(wStar[1],wStar[0],'go')
plt.show()
```

```python
plt.figure(figsize=(10,10))
plt.title('J(w) vs iterations')
plt.ylabel('J(w)')
plt.xlabel('iteration')
plt.plot(range(len(jw)),jw)
plt.show()
```

```python
plt.figure(figsize=(10,10))
plt.title('E(w) vs iterations')
plt.ylabel('E(w)')
plt.xlabel('iteration')
plt.plot(range(len(ew)),ew)
plt.show()
```

Gradient Decent/Newtons Method Himmelblau's Cost Function.ipynb


# Integration via Green's theorem

In this notebook we validate our expressions for the occultation solution vector, $\mathbb{s}^\top$, which we compute using Green's theorem.

```python
%matplotlib inline
```

```python
%run notebook_setup.py
```

```python
import matplotlib.pyplot as plt
import numpy as np
from scipy.integrate import quad
import sympy
from tqdm.notebook import tqdm

np.seterr(invalid="ignore", divide="ignore");
```

## The Green's basis

First off, here's the Green's basis from the original `starry` paper (Equation 11 in [Luger et al. 2019](https://arxiv.org/pdf/1810.06559.pdf)):

```python
def g(x, y, z=None, n=0):
    """
    Return the nth term of the Green's basis (a scalar).

    """
    if z is None:
        z = np.sqrt(1 - x ** 2 - y ** 2)
    l = int(np.floor(np.sqrt(n)))
    m = n - l * l - l
    mu = l - m
    nu = l + m
    if (nu % 2 == 0):
        I = [mu // 2]
        J = [nu // 2]
        K = [0]
        C = [(mu + 2) // 2]
    elif (l == 1) and (m == 0):
        I = [0]
        J = [0]
        K = [1]
        C = [1]
    elif (mu == 1) and (l % 2 == 0):
        I = [l - 2]
        J = [1]
        K = [1]
        C = [3]
    elif (mu == 1):
        I = [l - 3, l - 1, l - 3]
        J = [0, 0, 2]
        K = [1, 1, 1]
        C = [-1, 1, 4]
    else:
        I = [(mu - 5) // 2, (mu - 5) // 2, (mu - 1) // 2]
        J = [(nu - 1) // 2, (nu + 3) // 2, (nu - 1) // 2]
        K = [1, 1, 1]
        C = [(mu - 3) // 2, -(mu - 3) // 2, -(mu + 3) // 2]
    res = 0
    for i, j, k, c in zip(I, J, K, C):
        res += c * x ** i * y ** j * z ** k
    return res
```

For reference, here are the terms in the basis up to degree 3:

```python
x, y, z = sympy.symbols("x y z")
for n in range(16):
    display(g(x, y, z, n))
```

$\displaystyle 1$

$\displaystyle 2 x$

$\displaystyle z$

$\displaystyle y$

$\displaystyle 3 x^{2}$

$\displaystyle - 3 x z$

$\displaystyle 2 x y$

$\displaystyle 3 y z$

$\displaystyle y^{2}$

$\displaystyle 4 x^{3}$

$\displaystyle - 4 x^{2} z - y^{2} z + z$

$\displaystyle 3 x^{2} y$

$\displaystyle - 3 x y z$

$\displaystyle 2 x y^{2}$

$\displaystyle x^{2} z + 4 y^{2} z - z$

$\displaystyle y^{3}$

## Numerical 2D integration

Let's define a function to integrate these terms numerically. Recall that the vector $\mathbb{s}^\top$ is the integral of the Green's basis over some region bounded by the limb of the occultor, the day/night terminator, and the limb of the occulted body. The integral is a function of the terminator parameters $b$ and $\theta$ and the occultor parameters $b_o$ and $r_o$. Recall that the integration is performed in the frame $\mathcal{F}'$, in which the occultor lies along the $+y'$-axis.

For simplicity, let's code up a brain-dead brute force integrator on a grid:

```python
def sT_numerical(b, theta, bo, ro, n=0, res=300):

    # Create a fine grid
    pts = np.linspace(-1, 1, res)
    x, y = np.meshgrid(pts, pts)

    # Check if a point is inside the unit disk
    in_disk = x ** 2 + y ** 2 <= 1

    # Check if a point is under the occultor
    under_occultor = x ** 2 + (y - bo) ** 2 <= ro ** 2

    # Check if a point is on the dayside
    xr = x * np.cos(theta) + y * np.sin(theta)
    yr = -x * np.sin(theta) + y * np.cos(theta)
    term = 1 - xr ** 2
    yt = b * np.sqrt(term)
    on_dayside = yr >= yt

    # Now, compute the nth term in the basis...
    term = g(x, y, n=n)

    # And sum it over the integration region
    # Each pixel has area dA = (area of square) / (number of pixels)
    dA = 4 / (x.shape[0] * x.shape[1])
    integral = np.nansum(term[in_disk & under_occultor & on_dayside] * dA)

    return integral
```

## 1D integration via Green's theorem

Now, as in Luger et al. (2019), it is straightforward to compute the anti-exterior derivatives of all the terms in this basis, which we can use to transform the 2D surface integrals into 1D line integrals along the boundary. The function `G` below returns the anti-exterior derivative of `g`:

```python
def G(n):
    """
    Return the anti-exterior derivative of the nth term of the Green's basis.
    This is a two-dimensional (Gx, Gy) vector of functions of x and y.

    """
    # Get the mu, nu indices
    l = int(np.floor(np.sqrt(n)))
    m = n - l * l - l
    mu = l - m
    nu = l + m

    # NOTE: The abs prevents NaNs when the argument of the sqrt is
    # zero but floating point error causes it to be ~ -eps.
    z = lambda x, y: np.maximum(1e-12, np.sqrt(np.abs(1 - x ** 2 - y ** 2)))

    if nu % 2 == 0:

        G = [lambda x, y: 0, lambda x, y: x ** (0.5 * (mu + 2)) * y ** (0.5 * nu)]

    elif (l == 1) and (m == 0):

        def G0(x, y):
            z_ = z(x, y)
            if z_ > 1 - 1e-8:
                return -0.5 * y
            else:
                return (1 - z_ ** 3) / (3 * (1 - z_ ** 2)) * (-y)

        def G1(x, y):
            z_ = z(x, y)
            if z_ > 1 - 1e-8:
                return 0.5 * x
            else:
                return (1 - z_ ** 3) / (3 * (1 - z_ ** 2)) * x

        G = [G0, G1]

    elif (mu == 1) and (l % 2 == 0):

        G = [lambda x, y: x ** (l - 2) * z(x, y) ** 3, lambda x, y: 0]

    elif (mu == 1) and (l % 2 != 0):

        G = [lambda x, y: x ** (l - 3) * y * z(x, y) ** 3, lambda x, y: 0]

    else:

        G = [
            lambda x, y: 0,
            lambda x, y: x ** (0.5 * (mu - 3))
            * y ** (0.5 * (nu - 1))
            * z(x, y) ** 3,
        ]

    return G
```

We show in the paper how we can write $\mathbb{s}^\top$ as the sum of the integrals along each of the boundaries, $\mathbb{p}^\top$, $\mathbb{t}^\top$, and $\mathbb{q}^\top$. Below we use the expressions from the paper to construct those integrals. For simplicity, we integrate them numerically in this notebook; we derive analytic expressions later in the text (with their own accompanying notebooks).

```python
def primitive(x, y, dx, dy, theta1, theta2, n=0):
    """A general primitive integral computed numerically."""

    def func(theta):
        Gx, Gy = G(n)
        return Gx(x(theta), y(theta)) * dx(theta) + Gy(x(theta), y(theta)) * dy(theta)

    res, _ = quad(func, theta1, theta2, epsabs=1e-12, epsrel=1e-12)
    return res

def pT(phi, bo, ro, n=0):
    """Compute the pT integral numerically from its integral definition."""
    res = 0
    for phi1, phi2 in phi.reshape(-1, 2):
        x = lambda phi: ro * np.cos(phi)
        y = lambda phi: bo + ro * np.sin(phi)
        dx = lambda phi: -ro * np.sin(phi)
        dy = lambda phi: ro * np.cos(phi)
        res += primitive(x, y, dx, dy, phi1, phi2, n)
    return res

def tT(xi, b, theta, n=0):
    """Compute the tT integral numerically from its integral definition."""
    res = 0
    for xi1, xi2 in xi.reshape(-1, 2):
        x = lambda xi: np.cos(theta) * np.cos(xi) - b * np.sin(theta) * np.sin(xi)
        y = lambda xi: np.sin(theta) * np.cos(xi) + b * np.cos(theta) * np.sin(xi)
        dx = lambda xi: -np.cos(theta) * np.sin(xi) - b * np.sin(theta) * np.cos(xi)
        dy = lambda xi: -np.sin(theta) * np.sin(xi) + b * np.cos(theta) * np.cos(xi)
        res += primitive(x, y, dx, dy, xi1, xi2, n)
    return res

def qT(lam, n=0):
    """Compute the qT integral numerically from its integral definition."""
    res = 0
    for lam1, lam2 in lam.reshape(-1, 2):
        x = lambda lam: np.cos(lam)
        y = lambda lam: np.sin(lam)
        dx = lambda lam: -np.sin(lam)
        dy = lambda lam: np.cos(lam)
        res += primitive(x, y, dx, dy, lam1, lam2, n)
    return res

def sT(phi, xi, lam, b, theta, bo, ro, n=0):
    """The solution vector for occultations, computed via Green's theorem."""
    return pT(phi, bo, ro, n) + tT(xi, b, theta, n) + qT(lam, n)
```

## Validation

For definiteness, let's compute the integral over the region shown in Figure 16 of the paper. The parameters in that figure are

```python
b = 0.5
theta = 75 * np.pi / 180
bo = 0.5
ro = 0.7
```

Here's what the geometry looks like:

```python
import sys
import os
sys.path.insert(1, os.path.abspath("../figures/support_code"))
from support_code import visualize
visualize(b, theta, bo, ro);
```

We're going to compute the integral over the region bounded by P, T, and Q. Let's take note of those angles here:

```python
phi = np.array([158.19625201, 223.34183756]) * np.pi / 180
xi = np.array([96.48097513, 0.0]) * np.pi / 180
lam = np.array([75.0, 130.53580211]) * np.pi / 180
```

First, let's compute the numerical integrals up to $n = 16$ (i.e., spherical harmonic degree 3). We'll do it at very high resolution (4999 grid points on a side, or 25 million points).

```python
numerical = np.zeros(16)
for n in tqdm(range(16)):
    numerical[n] = sT_numerical(b, theta, bo, ro, n=n, res=4999)
```

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

Now let's compute it via Green's theorem (note how much faster this is, even though we're still computing the integral numerically):

```python
analytic = np.zeros(16)
for n in tqdm(range(16)):
    analytic[n] = sT(phi, xi, lam, b, theta, bo, ro, n=n)
```

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

Finally, let's compare the two solutions:

```python
plt.plot(analytic, label="analytic")
plt.plot(numerical, "C1o", label="numerical")
plt.legend()
plt.xlabel(r"$n$", fontsize=22)
plt.ylabel(r"$\mathbb{s}_n^\top$", fontsize=22);
```

They agree! Here's the difference between the two, which is set by the numerical precision of the brute force integration:

```python
plt.plot(np.abs(analytic - numerical), "k-")
plt.yscale("log")
plt.xlabel(r"$n$", fontsize=22)
plt.ylabel(r"difference", fontsize=22)
plt.ylim(1e-6, 1e0);
```


```python
from sympy import *
init_printing(use_unicode=True)
from sympy.codegen.ast import Assignment
```

```python
m, n = symbols('m n')
x = symbols('x')
theta = Symbol("theta")
```

```python
m = cos(theta)
n = sin(theta)
```

# Definition of rotation matrix $A$

```python
A = Matrix([[m**2, n**2, 2*m*n], [n**2, m**2, -2*m*n], [-m*n, m*n,m**2-n**2]])
At = simplify(A.inv())

At, simplify(A)
# The inverse of the rotation matrix in voigt notation is not the transpose
```

```python
simplify(A.subs(theta,0))
```

# Definition of the Reuter Matrix $R$

```python
R = Matrix([[1, 0, 0], [0, 1, 0], [0, 0,2]])
Rt = Matrix([[1, 0, 0], [0, 1, 0], [0, 0,0.5]])
R
```

# transversely isotropic stiffness tensor $C$

$C' = A^{-1}CRAR^{-1}$

```python
C = Matrix( symarray('C', (3,3)) )
C
```

```python
gamma = symbols('\gamma')
C[2,0] = 0
C[2,1] = 0
C[0,2] = 0
C[1,2] = 0
C[1,1] = C[0,0]
C[1,0] = C[0,1]
C[2,2] = 0.5 *(C[0,0] - C[1,0])
#C[2,2] = gamma
C
```

```python
CTrue = simplify(At*C*R*A*Rt)
CTrue
```

```python
CTrue[2,2]
```

```python
CPrime = simplify(At*C*A)
CPrime[2,2]
```

```python
print(ccode(Assignment(x,CTrue[2,2])))
```

    x = 0.5*C_0_0 - 0.5*C_0_1;

```python
CTrue[2,2].subs(theta,0)
```

```python
simplify(CTrue[2,2].subs(theta,pi/2))
```

```python
simplify(CTrue[2,2].subs(theta,pi/4))
```

```python
(R*A*Rt).subs(theta,0)
```

```python
C
```

```python
A
```

```python
R*A*Rt
```

```python
CTrue.subs(theta,pi/2)
```

```python
v1 = Matrix([4,5,6])
v1
```

```python
C[0,0] = 1
C[1,1] = 3
C[0,1] = 2
C[1,0] = 2
C[2,2] = 13
```

```python
CTrue = simplify(At*C*R*A*Rt)
(CTrue*v1).subs(theta, pi/4)
```

```python
-9/2
```

```python
x, n, delta = symbols('x n \delta')

```

```python
Weight = (1 - 2*abs(x))
Weight
```

```python
plot(Weight, (x, -0.5, .5), ylim=[0,1])
```

```python
Weight = ((n+1.0)/(n-1.0))*(1.0 - n * (abs(2.0 * x))**( n - 1.0) + (n - 1.0) * (abs(2.0*x))**(n))
Weight
```

```python
plot(Weight.subs(n,100), (x, -0.5, .5),ylim=[0,2])
```

```python

```

```python

```

```python

```