```julia
using LinearAlgebra
using Plots
using LaTeXStrings
using Printf
```

# Joao pedro de Moraes Bonucci - 218733

### 1. Partindo da implementação do método de Jacobi abaixo, escreva uma variante que implementa o método de Gauss-Seidel.

```julia
function jacobi(A, b, maxiters = 100, prec = 1.0e-5)
	n = length(b) # n eh o total de equacoes no sistema
	x = 1.0 ./ diag(A) .* b # D^{-1} * b
	# Cria um vetor do mesmo tipo e comprimento de x
	xnovo = similar(x)
	iters = 0
	while iters < maxiters && norm(A*x - b) > prec
		for i = 1:n
			xnovo[i] = b[i]
			for j = 1:i - 1
				xnovo[i] = xnovo[i] - A[i, j]*x[j]
			end
			for j = i + 1:n
				xnovo[i] = xnovo[i] - A[i, j]*x[j]
			end
			xnovo[i] = xnovo[i] / A[i, i]
		end
		# Copia o valor de xnovo sobre x, coordenada a coordenada
		x .= xnovo
		iters = iters + 1
	end
	return x
end
```

    jacobi (generic function with 3 methods)

A diferenca entre o metodo de gauss-jacobi e o de gaus-seidel esta na utilizacao dos dados recem computados em gauss-seidel, ou seja se temos um sistema linear com variaveis $x_1^{k}$ e $x_2{k}$ ao inves de calcularmos $x_1^{k+1}$ e $x_2^{k+1}$ com $x_1^k$ e $x_2^k$, calculamos $x_2^{k+1}$ com $x_1^{k+1}$ e $x_2^k$ e para adaptar o codigo acima precisamos apenas fazer com que os termos 1 ate i-1 do laco sejam calculados com os resultados ja obtidos.

```julia
function seidel(A, b, maxiters = 100, prec = 1.0e-5)
	n = length(b) # n eh o total de equacoes no sistema
	x = 1.0 ./ diag(A) .* b # D^{-1} * b
	# Cria um vetor do mesmo tipo e comprimento de x
	xnovo = similar(x)
	iters = 0
	while iters < maxiters && norm(A*x - b) > prec
		for i = 1:n
			xnovo[i] = b[i]
			for j = 1:i - 1
				xnovo[i] = xnovo[i] - A[i, j]*xnovo[j]
			end
			for j = i + 1:n
				xnovo[i] = xnovo[i] - A[i, j]*x[j]
			end
			xnovo[i] = xnovo[i] / A[i, i]
		end
		# Copia o valor de xnovo sobre x, coordenada a coordenada
		x .= xnovo
		iters = iters + 1
	end
	return x
end
```

    seidel (generic function with 3 methods)

### 2. Qual o número de FLOPs executado pelo método de Jacobi a cada iteração? Justifique a sua resposta.

O metodo de jacobi consiste em iterar por n - 1 variaveis ao longo de n equacoes, como mostrado no algoritmo da questao anterior.

Como cada variavel consequentemente precisa de um FLOP para ser multiplicada pelo seu coeficiente e outro FLOP para ser adicionada ao resultado atualizado, sabemos que cada variavel precisa de 2 FLOPs para ser devidamente computada no metodo, no entanto a variavel estudada no momento ainda precis passar por uam divisao ao final das iteracoes por uma equacao (i.e. ao fim do laco mais interno). Com isso, temos $n(2\cdot(n-1) + 1) = 2n^2 - n$ FLOPs por iteracao.

### 3.Considere o sistema linear
$\begin{align*}
\begin{bmatrix}
4 & 3 & 2\\
1 & 5 & 0\\
1 & 1 & 3
\end{bmatrix} x =
&
\begin{bmatrix}
4 \\
9 \\
4
\end{bmatrix}
\end{align*}$

#### (a) É possível garantir facilmente a convergência do método de Jacobi para esse sistema (justifique)?

Sabemos que um criterio para garantir convergencia de metodos iterativos eh que a matriz B possua norma menor que 1.

Tambem sabemos que a funcao de iteracao eh da forma:
$$\phi(x) = B^J x + c$$

Para ver como esse criterio se aplica ao metodo de jacobi podemos dividir essa matriz a, no caso a matriz acima, em uma decomposicao A = L + D + U sendo L,D e U as submatrizes inferior a diagonal principal, a diagonal principal e superior a diagonal principal, respectivamente. Com isso temos que o sistema
$$ Ax^* = b \rightarrow (L + D + U)x^* = b \rightarrow Dx^* = -(L + U)x^* + b \rightarrow$$
$$x^* = -D^{-1}(L+U)x^* + D^{-1}b = \phi(x^*)$$

e daqui tiramos que $B^J = -D^{-1}(L+U)$ e $c=D^{-1}b$

Como precisamos que $||B^J|| < 1$ para garantir convergencia e sabendo que o resultado da operacao $-D^{-1}(L+U)x^*$ eh sempre uma matriz do tipo

$
\begin{bmatrix}
0 & a_{12}/a_{11} & ... & a_{1n}/a_{11}\\
a_{21}/a_{22} & 0 & ... & a_{2n}/a_{22}\\
... & ... & ... & ...\\
a_{n1}/a_{nn} & a_{n2}/a_{nn} & ...  & 0
\end{bmatrix}
$

Com isso para garantir que a matriz $B^J$ possui norma menor que 1 precisamos apenas checar se a seguinte inequacao eh satisfeita:
$$|a_{ii}| > \sum^n_{k=1,k\neq i} |a_{ik}|$$
ou seja, precisamos checar se a matriz eh diagonalmente dominante por linhas.

No caso da matriz acima, podemos perceber que:
$$ 4 > 1 + 1$$
$$ 5 > 3 + 1$$
$$ 3 > 2 + 0$$

Portanto, pelo criterio das linhas, a matriz teoricamente converge pelo metodo de gauss-jacobi

#### (b) Realiza três iterações do método de Jacobi a partir de x 0 = (1, 1, 1) t . Calcule o resíduo a cada iteração, ou seja ||Ax − b||. O que parece que está ocorrendo? Há alguma contradição com a sua resposta do item (a)?

Sistema a ser resolvido:
$$ 4x_1 = 4 - 3x_2 - 2x_3 $$
$$ 5x_2 = 9 - x_1 - 0x_3 $$
$$ 3x_3 = 4 - x_1 - x_2 $$

Condicao inicial:
$\begin{align*}
x_0 = \begin{bmatrix}
1 \\
1 \\
1
\end{bmatrix}
&
b = \begin{bmatrix}
4 \\
9 \\
4
\end{bmatrix}
\end{align*}$

```julia
A = [1 3 2 ; 1 5 0 ; 1 1 3]
b = [4 ; 9 ; 4]
x = [1 ; 1 ; 1]

print("residuo: ",round.(norm(A*x - b), digits=3))
```

    residuo: 3.742

Iteracao 1:
$$ 4x_1 = 4 - 3\cdot 1 - 2\cdot 1 $$
$$ 5x_2 = 9 - 1 - 0\cdot 1 $$
$$ 3x_3 = 4 - 1 - 1 $$

$\begin{align*}
x_1 = \begin{bmatrix}
-1/4 \\
8/5 \\
2/3
\end{bmatrix}
&
b = \begin{bmatrix}
4 \\
9 \\
4
\end{bmatrix}
\end{align*}$

```julia
x = [-1/4 ; 8/5 ; 2/3]
print("residuo: ",round.(norm(A*x - b), digits=3))
```

    residuo: 2.352

Iteracao 2:
$$ 4x_1 = 4 - 3\cdot 8/5 - 2\cdot 2/3 $$
$$ 5x_2 = 9 + 1/4 - 0\cdot 2/3 $$
$$ 3x_3 = 4 + 1/4 - 8/5 $$

$\begin{align*}
x_2 = \begin{bmatrix}
-8/15 \\
37/20 \\
53/60
\end{bmatrix}
&
b = \begin{bmatrix}
4 \\
9 \\
4
\end{bmatrix}
\end{align*}$

```julia
x = [-8/15 ; 37/20 ; 53/60]
print("residuo: ",round.(norm(A*x - b), digits=3))
```

    residuo: 2.798

Iteracao 3:

$$ 4x_1 = 4 - 3\cdot (-8/15) - 2\cdot 53/60 $$
$$ 5x_2 = 9 + 8/15 - 0\cdot 53/60 $$
$$ 3x_3 = 4 + 8/15 - 37/20 $$

$\begin{align*}
x_3 = \begin{bmatrix}
-199/240 \\
143/75 \\
161/180
\end{bmatrix}
&
b = \begin{bmatrix}
4 \\
9 \\
4
\end{bmatrix}
\end{align*}$

```julia
x = [-199/240 ; 143/75 ; 161/180]
print("residuo: ",round.(norm(A*x - b), digits=3))
```

    residuo: 2.707

Apesar da terceira iteracao apresentar residuo menor que o valor originalmente proposto, podemos ver que da primeira para a terceira iteracao a solucao parece divergir. Contrariando a conclusao do item anterior (item a).

No entanto, tres iteracoes formam um conjunto de dados muito pequeno para analizarmos se o metodo converge ou diverge da solucao para esse caso.

### 4. Represente graficamente o sistema não linear abaixo e use a figura para encontrar uma solução dos sistemas por inspeção. Se quiser, use o computador para desenhar a figura para você. Em seguida, confirme que a solução encontrada é exata avaliando as funções no ponto encontrado. Por fim, simule a execução do método de Newton partindo de x 0 = (1.05, 1.07) t e até que a a distância do ponto x k obtido e uma raiz seja menor que 10 −4 .Quantas iterações foram necessárias.

$$ x^2_1 + x^2_2 = 2$$
$$e^{x_1 −1} + x^3_2 = 2.$$

Obs: Para simular o método de Newton você pode usar os computador
para resolver os sistemas lineares intermediários, mas precisa colocar in-
formação de todos os passos, inclusive com uma indicação aproximada dos
sistemas que foram resolvidos.

$$ x^2_1 + x^2_2 = 2 \rightarrow  x_2 = \sqrt{2 - x^2_1}$$
$$e^{x_1 −1} + x^3_2 = 2 \rightarrow  x_2 = (2 - e^{x_1 −1})^{1/3}$$

```julia
f1(x) = sqrt(2-x^2)
f2(x) = (2 - exp(x - 1))^(1/3)
f3(x) = (2 - exp(x - 1))^(1/3) - sqrt(2-x^2)
plot([f1,f2,f3], -2, 2, label = ["f1(x)" "f2(x)" "f1(x)-f2(x)"], minorticks=true)
```

```julia
plot([f1,f2,f3], 0.5, 1.5, label = ["f1(x)" "f2(x)" "f1(x)-f2(x)"], minorticks=true)
```

Pelos graficos acima podemos perceber que existem duas raizes para o sistema, sendo que uma delas aparenta ter valor exato = 1.

Agora para a segunda parte do ex vamos utilizar o metodo de Newton partindo de $x_0 = (1.05, 1.07)$ com criterio de parada sendo uma precisao melhor que $10 ^ {-4}$.

Esse problema consistem em achar uma raiz da equacao $$f(x) = (2 - exp(x - 1))^{1/3} - \sqrt{2-x^2}$$

cuja derivada primeira eh dada por
$$ f'(x) = \frac{x}{\sqrt{2 - x^2}} - \frac{e^{x-1}}{3 (2 e - e^x)^{2/3}}$$

Agora para resolver sistemas por newton precisamos utilizar duas formulas:

$$J(x)s = -F(x)$$
$$\text{ e }$$
$$x_{k+1} = s + x_k$$

$$ x^2_1 + x^2_2 -2 = 0$$
$$e^{x_1 −1} + x^3_2 - 2 = 0$$

$
F(x_1,x_2) =
\begin{bmatrix}
x^2_1 + x^2_2 -2\\
e^{x_1 −1} + x^3_2 - 2
\end{bmatrix} = 0
$
$
J(x) = \begin{bmatrix}
2x_1   2x_2\\
e^{x_1 - 1}   3x_2^2
\end{bmatrix}
$

$
x_0 = \begin{bmatrix}
1.05 \\
1.07
\end{bmatrix}
$

Sistema para encontrar o s:

$\begin{align}
\begin{bmatrix}
2x_1   2x_2\\
e^{x_1 - 1}   3x_2^2
\end{bmatrix} s =
&
-1 \begin{bmatrix}
x^2_1 + x^2_2 -2\\
e^{x_1 −1} + x^3_2 - 2
\end{bmatrix}
\end{align}$

Iteracao 1:

$\begin{align}
\begin{bmatrix}
2(1.05)   2(1.07)\\
e^{1.05 - 1}   3(1.07)^2
\end{bmatrix} s =
&
-1 \begin{bmatrix}
(1.05)^2 + (1.07)^2 -2\\
e^{(1.05) −1} + (1.07)^3 - 2
\end{bmatrix}
\end{align}$

$
s = \begin{bmatrix}
-0.05207\\
-0.06451
\end{bmatrix}
$

$\begin{align}
x_{k+1} = x_k + s =
\begin{bmatrix}
1.05-0.05207\\
1.07-0.06451
\end{bmatrix} =
&
\begin{bmatrix}
0.99793\\
1.00549
\end{bmatrix}
\end{align}$

Iteracao 2:

$\begin{align}
\begin{bmatrix}
2(0.99793)   2(1.00549)\\
e^{0.99793 - 1}   3(1.00549)^2
\end{bmatrix} s =
&
-1 \begin{bmatrix}
(0.99793)^2 + (1.00549)^2 -2\\
e^{(0.99793) −1} + (1.00549)^3 - 2
\end{bmatrix}
\end{align}$

$
s = \begin{bmatrix}
0.00204\\
-0.00545
\end{bmatrix}
$

$\begin{align}
x_{k+1} = x_k + s =
\begin{bmatrix}
0.99793+0.00204\\
1.00549-0.00545
\end{bmatrix} =
&
\begin{bmatrix}
0.99997\\
1.00004
\end{bmatrix}
\end{align}$

Como $1-0.99997 = 0.00003$ e $1 - 1.00004 = -0.00004$ e ambos possuem um erro menor que $10^{-4}$ o criterio de para foi satisfeito e precisamos de 2 iteracoes do metodo de newton para chegar do $x_0$ proposto para um resultado com o precisao pedida.

### 5. Exiba graficamente os pontos amostrados abaixo e ajuste, por quadrados mínimos, a curva $φ(x) = c_1 e^{−x} + c_2 + c_3 e^x$ . Qual é o resíduo quadrático,

$$ x | -2.00 | -1.00 | +0.00 | +1.00 | +2.00 | +3.00$$
$$ y | +9.10 | +3.14 | +1.39 | +1.82 | +4.83 | +13.52$$

### ou seja, o valor de $||Φc − y||^2$ que é minimizado no método dos quadrados mínimos?

Obs: Indique claramente qual sistema deve ser resolvido em função da
matriz Φ. A resolução do sistema pode ser feita em um computador.

Primeiro vamos plotar o grafico de uma curva generica com a forma dada e os pontos dados:

```julia
a = 1
b = 1
c = 1
f(x) = a*exp(-x) + b*1 + c*exp(x)
plot(f, -2, 2, label="f(x)")
scatter!([-2 -1 0 1 2 3], [9.1 3.14 1.39 1.82 4.83 13.52], c = :orange)
```

Agora precisamos descobrir os coeficientes da curva que melhor representem o conjunto de pontos dado.

ouseja precisamos encontrar $\phi(x) = c_1 e^{−x} + c_2 + c_3 e^x$

que minimize a equacao:

$$ ||\phi - y||^2 = \sum_{i=1}^m (\phi(x_i)-y_i)^2 $$

onde temos que $\phi = (\phi(x_1),\phi(x_2),...,\phi(x_m))^T$ e $y = (y_1,y_2,...,y_m)^T$

sendo m o total de pontos dados

podemos reescrever a notacao anterior com um produto matrix vetor:
$$ \phi = \boldsymbol{\phi}c $$
onde

$\begin{align*}
\boldsymbol{\phi} = \begin{bmatrix}
e^{-x_1} & 1 & e^{x_1}\\
e^{-x_2} & 1 & e^{x_2}\\
... & ... & ...\\
e^{-x_m} & 1 & e^{x_m}
\end{bmatrix}
&
c = \begin{bmatrix}
c_1 \\
c_2 \\
c_3
\end{bmatrix}
\end{align*}$

Portanto o objetivo eh minimizar $||\boldsymbol{\phi}c - y||^2 \rightarrow (\boldsymbol{\phi}c - y)^T(\boldsymbol{\phi}c - y)$

em outras palavras, precisamos calcular o gradiente da funcao acima:
$$\nabla (||\boldsymbol{\phi}c - y||^2) = 2\cdot \boldsymbol{\phi}^T \boldsymbol{\phi} c - 2\cdot \boldsymbol{\phi}^T y = \rightarrow \boldsymbol{\phi}^T \boldsymbol{\phi} c = \boldsymbol{\phi}^T y$$

$\boldsymbol{\phi}^T \boldsymbol{\phi} =
\begin{pmatrix}
\frac{e^{10}+e^8+e^6+e^4+e^2+1}{e^6} & \frac{e^5+e^4+e^3+e^2+e+1}{e^3} & 6 \\
\frac{e^5+e^4+e^3+e^2+e+1}{e^3} & 6 & \frac{1+e+e^2+e^3+e^4+e^5}{e^2}\\
6 & \frac{1+e+e^2+e^3+e^4+e^5}{e^2} & \frac{1+e^2+e^4+e^6+e^8+e^{10}}{e^4}
\end{pmatrix}$

$\boldsymbol{\phi}^T \boldsymbol{\phi} \approx
\begin{pmatrix}
63.1433358063769 & 11.6603397201656 & 6\\
11.6603397201656 & 6 & 31.6960895749854\\
6 & 31.6960895749854 & 466.56965054693535
\end{pmatrix}$

$\boldsymbol{\phi}^T y \approx
\begin{pmatrix}
79.16214058293202502 \\
33.8\\
315.96956144527832
\end{pmatrix}$

Com isso temos que os coeficientes ideias sao $c = [1.29411, -0.57904, 0.69991]$

```julia
a = 1.29411
b = -0.57904
c = 0.69991
f(x) = a*exp(-x) + b*1 + c*exp(x)
plot(f, -2, 4, label="f(x)")
scatter!([-2 -1 0 1 2 3], [9.1 3.14 1.39 1.82 4.83 13.52], c = :orange)
```

```julia
y = [9.1 ; 3.14 ; 1.39 ; 1.82 ; 4.83 ; 13.52]
c = [1.29411; -0.57904; 0.69991]
phi = [exp(2) 1 exp(-1) ;
exp(1) 1 exp(-1) ;
exp(0) 1 exp(0) ;
exp(-1) 1 exp(1) ;
exp(-2) 1 exp(2) ;
exp(-3) 1 exp(3) ]

println("norma minimizada:",norm(phi * c - y)^2)
```

    norma minimizada:0.028416094565852805

### 6. Esse exercício tenta "ensinar"que as vezes ajustes que não parecem lineares podem ser transformados em ajustes lineares. Considere que queremos ajustar uma curva na forma $\phi(x) = α\sqrt{xe^{βx}}$ para diferentes valores de α e β. Com respeito a esses parâmetros não é possível fazer ajuste linear. O principal problema é que essa função é escrita como produto de outras funções e não soma. Mas há uma forma de contornar isso:

#### (a) Considere $\hat{\phi}(x) = log_eφ(x)$. Mostre que $\hat{\phi}(x)$ pode ser vista como combinação linear de uma função com parâmetro fixo mais duas duas funções com parâmetros livres que podem ser facilmente calculados a partir de α e β e vice-versa. Note que, ao fazer isso, é possível ajustar $\hat{\phi}(x)$ e partir dos seus parâmetros obter parâmetros de α e β que ajustam $\phi(x)$.

Podemos manipular a equacao dada ao utilizarmos o logaritmo e suas propriedades. dessa forma:
$$\phi(x) = α\sqrt{xe^{\beta x}} \rightarrow log(\phi(x)) = log(\alpha\sqrt{xe^{βx}}) \rightarrow$$
$$log(\phi(x)) = log(\alpha) + \frac{(\beta x)}{2} + \gamma \frac{log(x)}{2}$$

#### (b) Usando a ideia sugerida acima, ajuste $\phi(x_i ) ≈ y_i$ :

$$ x | +1.00 | +1.50 | +2.00 | +2.50 | +3.00 | +4.00 | +5.00 | +6.00$$
$$ y | +1.72 | +1.90 | +1.99 | +2.01 | +2.00 | +1.89 | +1.73 | +1.55$$

Obs: Indique claramente qual sistema deve ser resolvido em função da
matriz $\phi$. A resolução do sistema pode ser feita em um computador.
E observe que estou pedindo para ajustar $\phi ≈ y$ e não $\hat{\phi}≈ y$.

Agora seguindo a teoria explicada no ex anterior e sabendo que:

$\begin{align*}
\boldsymbol{\phi} = \begin{bmatrix}
1 & \frac{(x_1)}{2} & \frac{log(x_1)}{2}\\
1 & \frac{(x_2)}{2} & \frac{log(x_2)}{2}\\
... & ... & ...\\
1 & \frac{(x_m)}{2} & \frac{log(x_m)}{2}\\
\end{bmatrix}
&
c = \begin{bmatrix}
log(\alpha) \\
\beta \\
\gamma
\end{bmatrix}
\end{align*}$

precismos resolver a equacao:
$$\boldsymbol{\phi}^T \boldsymbol{\phi} c = \boldsymbol{\phi}^T log(y)$$

dessa fez precisamos considerar o log de y pois tivemos que passar a equacao original tambem para o log. E vale mencionar que o coeficiente gamma esta ali para ilustracao, mas nao afetara o resultado, pois este paresentara um valor igual ou muito proximo de 1.

```julia
log_a = 1
b = 1
c = 1

x = [1.00 1.50 2.00 2.50 3.00 4.00 5.00 6.00]
y = [1.72 ; 1.90 ; 1.99 ; 2.01 ; 2.00 ; 1.89 ; 1.73 ; 1.55]
log_y = []
c = [log_a ; b ; c]

# monta a matrix phi
phi = fill(0.0, (length(x),3))
for i in 1:length(x)
	phi[i,1] = 1.0
	phi[i,2] = x[i]/2
	phi[i,3] = log(x[i])/2
end

# encontra a transposta de phi
phi_T = transpose(phi)

# transforma os valores de y
for i in 1:length(y)
	push!(log_y,log(y[i]))
end
```

```julia
# encontra os melhores parametros
c_result = inv(phi_T * phi)*(phi_T * log_y)
c_result
```

    3-element Vector{Any}:
      0.7404896068054061
     -0.39916107985011706
      1.0004153912415745

Pelo resultado acima encontramos os valores otimos de $\alpha$ e $\beta$ para os pontos dados. Porem precisamos reverter o valor de $log(\alpha)$

```julia
# retorna o valor de alpha do log
a = 2.7182818284590^c_result[1]

# retorna o valor de beta e gama
b = c_result[2]
c = c_result[3]

f(x) = a * sqrt(x^c*2.7182818284590^(b*x))
plot(f,0.0001,7)
scatter!(x, y, c = :orange)
```

```julia

```


# Simulate preparing a GHZ state and compute covariance

In this notebook, we simulate preparation of the $n$-qubit GHZ state
$$
\vert\text{GHZ}\rangle = \frac1{\sqrt2} \Bigl( \vert0\dots0\rangle + \vert1\dots1\rangle \Bigr)
$$
via the unitary circuit
$$
\vert\text{GHZ}\rangle = \text{CNOT}_{n-1,n} \dots \text{CNOT}_{1,2} H_1 \vert 0 \dots 0 \rangle
$$
where the Hadamard and CNOT gates are given by
\begin{align}
H &= \frac1{\sqrt2} \begin{pmatrix}1 & 1 \\ 1 & -1\end{pmatrix},
&
\text{CNOT} &= \vert0\rangle\langle0\vert \otimes \mathbb 1 + \vert1\rangle\langle1\vert \otimes \sigma_X, &
&
\sigma_X = \begin{pmatrix}0 & 1\\1 & 0\end{pmatrix}.
\end{align}

Applicable keywords:

- Efficient classical simulation of a unitary circuit

- Quantum computation

- Quantum information

- Matrix product states, tensor trains

- Matrix product operators (MPO), matrix product density operators (MPDO)

- Tensor network methods

The code below uses Python and `mpnum` (http://mpnum.readthedocs.io/). This notebook is part of https://github.com/milan-hl/mpnum-examples/.

## Definitions

Open ``init.ipynb`` to see which MPSs and MPOs it defines.

```python
%run init.ipynb
```

```python
# Let's do 6 qubits
n_sites = 6
```

The Hadamard and CNOT gates are constructed as follows:
\begin{align}
  H &= \frac1{\sqrt 2}(\sigma_X + \sigma_Z) \\
  \text{CNOT} &= \vert0\rangle\langle0\vert \otimes \mathbb 1 + \vert1\rangle\langle1\vert \otimes \sigma_X
\end{align}
where $\sigma_{X,Y,Z}$ are Pauli matrices.

With `mpnum` and the definitions from `init.ipynb`, we have e.g.
\begin{align}
  \vert1\rangle\langle1\vert \otimes \sigma_X &= \texttt{mp.chain((mdd, mpx))}
\end{align}

```python
# Define Hadamard gate (single qubit)
mh = (mpx + mpz) / 2**0.5

# Define CNOT gate (two qubits)
mcnot = mp.chain((muu, mid)) + mp.chain((mdd, mpx))
```

Here, we compute
$$
\Vert H H^\dagger - \mathbb 1 \Vert_F
$$
where the Norm is the Frobenius norm $\Vert A \Vert_F^2 = \operatorname{tr}(A^\dagger A)$ and $A^\dagger$ denotes the Hermitian adjoint (cojugate transpose).

```python
# Verify that Hadamard is unitary
mp.normdist(mp.dot(mh, mh.adj()), mp.factory.eye(1, ldim))
```

    3.1401849173675503e-16

```python
# Verify that CNOT is unitary
mp.normdist(mp.dot(mcnot, mcnot.adj()), mp.factory.eye(2, ldim))
```

    0.0

## Prepare GHZ as MPS

Define a pure state, containing spin up on `n_sites` sites:

```python
psi = mp.chain([mup] * n_sites)
```

Number of sites of the pure state:

```python
len(psi)
```

    6

Number of sites on which the two gates act:

```python
len(mh), len(mcnot)
```

    (1, 2)

```python
psi = mp.chain([mup] * n_sites)
# Apply Hadamard at site 0 (the first site)
psi = mp.partialdot(mh, psi, 0)

print(psi.ranks)

for pos in range(n_sites - 1):
    # Apply CNOT at sites (pos, pos + 1)
    psi = mp.partialdot(mcnot, psi, pos)
    print(psi.ranks)
```

    (1, 1, 1, 1, 1)
    (2, 1, 1, 1, 1)
    (2, 2, 1, 1, 1)
    (2, 2, 2, 1, 1)
    (2, 2, 2, 2, 1)
    (2, 2, 2, 2, 2)

The output shows that each application of a CNOT gate increases the bond dimension (=rank) by a factor of two (=the bond dimension/rank of the CNOT gate).

The following function computes
$$
\text{cov}_\rho(A_i, B_j) = \text{tr}(A_i B_j \rho) - \text{tr}(A_i \rho) \text{tr}(B_j \rho)
$$
for a pure state $\rho = \vert\psi\rangle\langle\psi\vert$.

```python
def cov_ab(psi, a, b, pos1, pos2):
    if pos1 == pos2:
        return np.nan  # Return not a number
    # Todo: Verify that imaginary parts are small
    # Apply a on site pos1
    ept_a = mp.inner(psi, mp.partialdot(a, psi, pos1)).real
    # Apply b on site pos2
    ept_b = mp.inner(psi, mp.partialdot(b, psi, pos2)).real
    ept_ab = mp.inner(
        psi,
        mp.partialdot(a, mp.partialdot(b, psi, pos2), pos1)
    ).real
    cov = ept_ab - ept_a * ept_b
    return cov
```

```python
covariances = [
    [
        cov_ab(psi, mpz, mpz, pos1, pos2)
        for pos2 in range(n_sites)
    ]
    for pos1 in range(n_sites)
]
```

```python
sns.heatmap(covariances, vmin=-1, vmax=1, cmap='coolwarm', square=True) \
    .set(title='$cov(Z_i, Z_j)$ of the GHZ state')
None
```

## Prepare GHZ as MPO

```python
psi = mp.chain([mup] * n_sites)
rho = mp.mps_to_mpo(psi)
# Apply Hadamard at site 0 (the first site)
rho = mp.partialdot(mh, rho, 0)
rho = mp.partialdot(rho, mh.adj(), 0)

for pos in range(n_sites - 1):
    # Apply CNOT at sites (pos, pos + 1)
    rho = mp.partialdot(mcnot, rho, pos)
    rho = mp.partialdot(rho, mcnot.adj(), pos)
```

```python
#rho.to_array_global().reshape([ldim**n_sites] * 2)
```

The following function computes the same covariance as `cov_ab()`, but for a mixed state provided as MPO.

```python
def cov_ab_mpo(rho, a, b, pos1, pos2):
    # Todo: Verify that imaginary parts are small
    if pos1 == pos2:
        return np.nan
    ept_a = mp.trace(mp.partialdot(rho, a, pos1)).real
    ept_b = mp.trace(mp.partialdot(rho, b, pos2)).real
    ept_ab = mp.trace(mp.partialdot(a, mp.partialdot(b, rho, pos2), pos1)).real
    cov = ept_ab - ept_a * ept_b
    return cov
```

```python
covariances_mpo = [
    [
        cov_ab_mpo(rho, mpz, mpz, pos1, pos2)
        for pos2 in range(n_sites)
    ]
    for pos1 in range(n_sites)
]
```

```python
sns.heatmap(covariances_mpo, vmin=-1, vmax=1, cmap='coolwarm', square=True) \
    .set(title='$cov(Z_i, Z_j)$ of the GHZ state')
None
```

## Prepare GHZ as MPO, with errors

```python
n_sites = 20
```

```python
# Prepare GHZ as MPO with some error probability eta

eta = 0.1

psi = mp.chain([mup] * n_sites)
rho = mp.mps_to_mpo(psi)
# Apply Hadamard at site 0 (the first site)
rho = mp.partialdot(mh, rho, 0)
rho = mp.partialdot(rho, mh.adj(), 0)

for pos in range(n_sites - 1):
    rho_last = rho
    # Apply CNOT at sites (pos, pos + 1)
    rho = mp.partialdot(mcnot, rho, pos)
    rho = mp.partialdot(rho, mcnot.adj(), pos)
    # Apply CNOT with probability (1 - eta) only.
    rho = eta * rho_last + (1 - eta) * rho
    rho.compress(method='svd', relerr=1e-10)
    #print(rho.ranks)
```

```python
covariances_mpo_eta = [
    [
        cov_ab_mpo(rho, mpz, mpz, pos1, pos2)
        for pos2 in range(n_sites)
    ]
    for pos1 in range(n_sites)
]
```

```python
sns.heatmap(covariances_mpo_eta, vmin=-1, vmax=1, cmap='coolwarm', square=True) \
    .set(title='$\mathrm{cov}(Z_i, Z_j)$')
None
```

```python

```


# Notions d'algèbre linéaire

## Introduction

Ce document est une traduction "libre", approximative, et incomplète du cours de Zico Kolter : [Linear Algebra Review and Reference](https://see.stanford.edu/materials/aimlcs229/cs229-linalg.pdf), aggrémenté de quelques exemples Python. Les lecteurs sont fortement encouragés à se référer au document d'origine!

## Concepts de base et notation

L'algèbre linéaire fournit les outils permettant de représenter et manipuler simplement des ensembles d'équations linaires.

\begin{align}
     4 x_1 - 5 x_2 & = -13 \\
    -2 x_1 + 3 x_2 & =  9
\end{align}

Il est possible à partir de ces deux équiations à deux variables de trouver une unique solution pour $x_1$ et $x_2$ (à moins que les équations soient en quelque sorte dégénérées, par exemple si la seconde équation est un multiple de la première). Dans notre exemple il n'y a une solution unique. En notation matricielle, le système précédent peut être écrit de manière plus compacte :

\begin{equation*}
Ax=b \\
\text{ avec } A = \begin{bmatrix}
    4  & -5 \\
    -2 & 3
\end{bmatrix}
\text{ , }
b = \begin{bmatrix}
-13 \\
9
\end{bmatrix}
\end{equation*}

### Exemple de définition numpy

L'exemple précédent peut s'écrire de la manière suivante avec numpy :

```python
import numpy as np
A = np.matrix([[4,-5], [-2, 3]])
b = np.matrix([-13,9]).transpose()
print("A = \n",A)
print("b = \n",b)
```

    A =
     [[ 4 -5]
     [-2  3]]
    b =
     [[-13]
     [  9]]

## Notations de base

Nous utilisons les notations suivantes:

- $A \in \mathbb{R}^{m \times n}$ définit une matrice ayant $m$ lignes et $n$ colonnes dans laquelle les élements sont des nombres réels.

- $x \in \mathbb{R}^n$ définit un vecteur à $n$ éléments. Généralement un vecteur $x$ est un **vecteur en colonne**, c'est à dire une matrice ayant une ligne et $n$ colonnes (soit une matrice $\mathbb{R}^{1 \times n}$). Si l'on veut décrire un **vecteur ligne** ayant 1 ligne et $n$ colonnes, on écrit $x^T$ ($x^T$ veut dire la transposée de $x$, que nous allons décrire bientôt).

- Le $i^{eme}$ élément d'un vecteur $x$ se note $x_i$ :

\begin{equation*}
x = \begin{bmatrix}
x_1 \\
x_2 \\
\vdots \\
x_n
\end{bmatrix}
\end{equation*}

- La notation $a_ij$ (ou $A_{ij}$, $A_{i,j}$, etc) définit l'élément de $A$ présent à la $i^{eme}$ ligne et à la $j^{ieme}$ colonne:

\begin{equation*}
A = \begin{bmatrix}
a_{11} & a_{12} & \dots & a_{1n} \\
a_{21} & a_{22} & \dots & a_{2n} \\
\vdots & \vdots & \ddots & \vdots \\
a_{m1} & a_{m2} & \dots & a_{mn}
\end{bmatrix}
\end{equation*}

- La $j^{eme}$ colonne de A se note $a_j$ ou $A_{:,j}$ :

\begin{equation*}
A = \begin{bmatrix}
\rvert & \rvert	&       & \rvert \\
a_1 & a_2 & \dots & a_n \\
\rvert & \rvert	&       & \rvert
\end{bmatrix}
\end{equation*}

- La $i^{eme}$ ligne de A se note $a_i^T$ ou $A_{i,:}$ :

\begin{equation*}
A = \begin{bmatrix}
- & a_1^T & - \\
- & a_2^T & - \\
  & \vdots & \\
- & a_m^T & -
\end{bmatrix}
\end{equation*}

### Manipulation avec numpy

> Dans numpy la première ligne et la premiere colonne commencent à 0 et non à 1 comme dans les conventions mathématiques classiques.

```python
a22 = A[1,1]
print("La valeur de l'element a22 est :\n",a22)

a2 = A[:,1]
print("Le vecteur de la deuxieme colonne a2 est :\n",a2)

aT2 = A[1,:]
print("Le vecteur de la deuxieme ligne aT2 est :\n",aT2)
```

    La valeur de l'element a22 est :
     3
    Le vecteur de la deuxieme colonne a2 est :
     [[-5]
     [ 3]]
    Le vecteur de la deuxieme ligne aT2 est :
     [[-2  3]]

## Multiplication matricielle

Le produit de deux matrices $A \in \mathbb{R}^{m \times n}$ et  $B \in \mathbb{R}^{n \times p}$ donne la matrice:

\begin{equation*}
C = AB \in \mathbb{R}^{m \times p}, \\
\end{equation*}

où

\begin{equation*}
C_{ij} = \sum_{k = 1}^n A_{ik}B_{kj}
\end{equation*}

Notez que pour que le produit matriciel existe le nombre de colonnes de $A$ doit être égal au nombre de lignes de $B$. Etudions maintenant quelques cas particuliers:

### Multiplication de deux vecteurs entre eux

#### Produit scalaire

Etant donnés deux vecteurs $x,y \in \mathbb{R}^{n}$, la quantité $x^Ty$ est un nombre réel appelé le produit scalaire des deux vecteurs. Cette opération est aussi appelée **dot product** ou **inner product** en anglais.

\begin{equation*}
x^Ty \in \mathbb{R} = \sum_{i = 1}^n x_{i}y_{i}
\end{equation*}

Notez que l'égalité suivante est toujours vérifiée : $x^Ty = y^Tx$
On note aussi le produit scalaire de $x$ et $y$: $x \centerdot y$.

#### Produit extérieur

Etant donnés deux vecteurs $x \in \mathbb{R}^{m}$ et $y \in \mathbb{R}^{n}$ (ils n'ont pas à être de la même dimension), $xy^T$ est appelé **le produit extérieur** des deux vecteurs (on le note aussi $x \otimes y$). Le résultat est une matrice :

\begin{equation*}
xy^T \in \mathbb{R}^{m \times n} = \begin{bmatrix}
x_1y_1 & x_1y_2 & \dots & x_1y_n \\
x_2y_1 & x_2y_2 & \dots & x_2y_n \\
\vdots & \vdots & \ddots & \vdots \\
x_my_1 & x_my_2 & \dots & x_my_n
\end{bmatrix}
\end{equation*}

```python
import numpy as np

x = np.matrix([2,3,4])
y = np.matrix([5,6,7]).transpose()
s = np.dot(x,y)
print ("Le produit scalaire de x et y est ",s[0,0])
```

    Le produit scalaire de x et y est  56

### Multiplication d'une matrice et d'un vecteur

#### Matrice multiplié par un vecteur

Etant donné une matrice $A \in \mathbb{R}^{m \times n}$ et un vecteur $x \in \mathbb{R}^n$, leur produit est un vecteur $y = Ax \in \mathbb{R}^m$.

Il y a deux manière d'appréhender la multiplication d'une matrice et d'un vecteur, nous allons étudier les deux:

- Si nous écrivons $A$ selon ses lignes, alors nous pouvous exprimer $Ax$ comme:

\begin{equation*}
y = \begin{bmatrix}
- & a_1^T & - \\
- & a_2^T & - \\
  & \vdots & \\
- & a_m^T & -
\end{bmatrix} x = \begin{bmatrix}
a_1^Tx \\
a_2^Tx \\
\vdots \\
a_m^Tx
\end{bmatrix}
\end{equation*}

Autrement dit, le $i^{eme}$ élément de $y$ est égal au produit scalaire de la $i^{eme}$ ligne de $A$ et de $x$, $y_i = a_i^Tx = a_i \centerdot x$.

- Nous pouvons aussi écrire $A$ sous forme de colonnes. Dans ce cas nous voyons que:

\begin{equation*}
y = \begin{bmatrix}
\rvert & \rvert	&       & \rvert \\
a_1 & a_2 & \dots & a_n \\
\rvert & \rvert	&       & \rvert
\end{bmatrix} \begin{bmatrix}
x_1 \\
x_2 \\
\vdots \\
x_n
\end{bmatrix} =  \begin{bmatrix} a_1 \end{bmatrix} x_1 + \begin{bmatrix} a_2 \end{bmatrix} x_2 +
\dots + \begin{bmatrix} a_n \end{bmatrix} x_n
\end{equation*}

En d'autres termes, $y$ est une **combinaison linéaire** des colonnes de $A$, dans laquelle les coefficients de la combinaison linéaire sont données par les éléments de $x$.

#### Vecteur multiplié par une matrice

Jusqu'alors nous avons mutliplié à droite par un vecteur colonne, mais il est aussi possible de multiplier à gauche par un vecteur ligne. Cette opération s'écrit : $y^T = x^TA$ avec $A \in \mathbb{R}^{m \times n }$, $x \in \mathbb{R}^m$ et $y \in \mathbb{R}^n$. Comme auparavant, on peut exprimer $y^T$ de deux façons:

- Exprimons $A$ selon ses colonnes:

\begin{equation*}
y^T = x^T \begin{bmatrix}
\rvert & \rvert	&       & \rvert \\
a_1 & a_2 & \dots & a_n \\
\rvert & \rvert	&       & \rvert
\end{bmatrix} = \begin{bmatrix} x^Ta_1 & x^Ta_2 & \dots & x^Ta_n \end{bmatrix}
\end{equation*}

Cette forme démontre que le $i^{eme}$ élément de $y^T$ est égal au produit scalaire de $x$ et de la $i^{eme}$ colonne de $A$.

- Exprimons $A$ selon ses lignes:

\begin{align}
y^T &= \begin{bmatrix} x_1 & x_2 & \dots & x_n \end{bmatrix} \begin{bmatrix}
- & a_1^T & - \\
- & a_2^T & - \\
  & \vdots & \\
- & a_m^T & -
\end{bmatrix} \\
 &= x_1 \begin{bmatrix} - & a_1^T & - \end{bmatrix} +
 x_2 \begin{bmatrix} - & a_2^T & - \end{bmatrix} +
 \dots +
 x_n \begin{bmatrix} - & a_n^T & - \end{bmatrix}
\end{align}

Nous voyons ici que $y^T$ est une combinaison linéaire des lignes de $A$, où les coefficients de la combinaison linéaire sont donnés par les éléments de $x$.

```python
import numpy as np

A = np.matrix([[2,3,4],[5,6,6]])
x = np.matrix([1,6,9]).transpose()
y = A*x
print ("Le produit de la matrice A et de x est:\n y= ",y)

```

    Le produit de la matrice A et de x est:
     y=  [[56]
     [95]]

### Produit de deux matrices

Armés de nos nouvelles connaissances, nous pouvons voir 4 différentes (mais bien etendu équivalentes) façons de comprendre la multiplication matrice à matrice $C = AB$ comme défini dans le début de ce chapitre. Premièrement nous pouvons voir la multiplication matrice à matrice comme un ensemble de produits de vecteurs. Le point de vue le plus évidents, qui découle immédiatement de la définition, est que l'élément $i,j$ de $C$ est égal au produit scalaire de la $i^{eme}$ ligne de $A$ et de la $j^{eme} colonne de $B$. Ceci peut être vu de la manière suivante:

\begin{align}
C & = AB \\
    & = \begin{bmatrix}
            - & a_1^T & - \\
            - & a_2^T & - \\
              & \vdots & \\
            - & a_m^T & -
        \end{bmatrix} \begin{bmatrix}
            \rvert & \rvert &       & \rvert \\
            b_1    & b_2    & \dots & b_p    \\
            \rvert & \rvert &       & \rvert
        \end{bmatrix} \\
    & = \begin{bmatrix}
        a_1^Tb_1 & a_1^Tb2 & \dots & a_1^Tb_p \\
        a_2^Tb_1 & a_2^Tb2 & \dots & a_2^Tb_p \\
        \vdots   & \vdots  & \ddots & \vdots \\
        a_m^Tb_1 & a_m^Tb2 & \dots & a_m^Tb_p
        \end{bmatrix}
\end{align}

Remarquez que comme $A \in \mathbb{R}^{m \times n}$ et $B \in \mathbb{R}^{n \times p}$, $a_i \in \mathbb{R}^n$ et $b_j \in \mathbb{R}^n$, tous le produits scalaires ont du sens. Cette représentation de la multiplication est la plus naturelle. Il est cependant possible de représenter $A$ sous forme de colonnes et $B$ par lignes, ce qui nous emmène à l'interprétation de $AB$ comme la somme de produits externes.

\begin{align}
    C &= AB  \\
      &= \begin{bmatrix}
           \rvert & \rvert &       & \rvert \\
           a_1    & a_2    & \dots & a_n    \\
           \rvert & \rvert &       & \rvert
         \end{bmatrix} \begin{bmatrix}
           - & b_1^T & - \\
           - & b_2^T & - \\
             & \vdots & \\
           - & b_n^T & -
         \end{bmatrix} \\
      &=\sum_{i = 1}^n a_{i}b_{i}^T
\end{align}

Dit d'une autre manière, $AB$ est égal à la somme sur $i$ des produits externes de la $i^{eme}$ colonne de $A$ et de la $i^{eme}$ ligne de $B$. Comme, dans ce cas, $a_i \in  \mathbb{R}^m$ et $b_i \mathbb{R}^p$, la dimension du produit externe $a_ib_i^T$ est $m \times p$, ce qui coïncide avec la dimension de $C$.

Nous pouvons voir la multiplication de matrices comme un ensemble de multiplications de matrices à vecteurs. Si nous représentons $B$ en colonnes, nous pouvons voir les colonnes de $C$ comme un produit matrice-vecteur entre $A$ et les colonnes de $B$.

\begin{align}
    C &= AB \\
      &= A \begin{bmatrix}
               \rvert & \rvert &       & \rvert \\
               b_1    & b_2    & \dots & b_p    \\
               \rvert & \rvert &       & \rvert
           \end{bmatrix} \\
      &= \begin{bmatrix}
               \rvert & \rvert &       & \rvert \\
               Ab_1    & Ab_2    & \dots & Ab_p    \\
               \rvert & \rvert &       & \rvert
         \end{bmatrix}
\end{align}

Ici les $i^{eme}$ colonne de $C$ est donné par le produit matrice à vecteur de droite, $c_i = Ab_i$. De la même manière, nous pouvons représenter $A$ en lignes:

\begin{align}
    C &= AB \\
      &=  \begin{bmatrix}
          - & a_1^T & - \\
          - & a_2^T & - \\
          - & \vdots & - \\
          - & a_m^T & -
           \end{bmatrix} B \\
      &=  \begin{bmatrix}
          - & a_1^TB & - \\
          - & a_2^TB & - \\
          - & \vdots & - \\
          - & a_m^TB & -
           \end{bmatrix} \\
\end{align}

Dans ce cas la $i^{eme}$ ligne de $C$ est donnée par le produit matrice à vecteur avec le vecteur à gauche, $c_i^T = a_i^TB$.

Quelques propriétés utiles de la multiplication matricielle:

- La multiplication de matrice est associative: $(AB)C = A(BC)$
- La multiplication de matrice est distributive: $A(B+C) = AB+AC$
- La multiplication de matrice n'est en **général** pas commutative: $AB \ne BA$

```python
import numpy as np

A = np.matrix([[2,3,4],[5,6,6]])
B = np.matrix([[1,6],[5,9],[0,8]])
C = A*B
print ("Le produit de la matrice A et de B est:\n C= ",C)
```

    Le produit de la matrice A et de B est:
     C=  [[ 17  71]
     [ 35 132]]

## Opérations et propriétés

Dans ce chapitre sont pésentés différentes opérations et propriétés des matrices et des vecteurs.

### La matrice identité et les matrices diagonales

La matrice **identité**, notée $I \in \mathbb{R}^{n \times n}$, est une matrice carrée ne contenant que des 1 sur la diagonales et des 0 partout ailleurs. C'est à dire:

\begin{align}
I =
\begin{cases}
1 & \text{ si } i = j \\
0 & \text{ si } i \ne j
\end{cases}
\end{align}

Elle a pour propriété que pour tout $A \in \mathbb{R}^{m \times n}$,

\begin{align}
AI = A = IA
\end{align}

Où la dimension de $I$ est déterminée par la dimension de $A$ de manière à ce que la mutiplication soit possible.

Une **matrice diagonale** est une matrice pour laquelle tous les éléments qui ne sont pas sur la diagonale sont 0. Elle est généralement noté $D = diag(d_1,d_2,\dots,d_n)$, avec:

\begin{align}
D_ij =
\begin{cases}
d_i & \text{ si } i = j \\
0 & \text{ si } i \ne j
\end{cases}
\end{align}

On a évidemment: $I = diag(1,1,\dots,1)$.

```python
import numpy as np

I = np.identity(3)
print ("La matrice identité de taille 3 est:\n I= ",I)
```

    La matrice identité de taille 3 est:
     I=  [[ 1.  0.  0.]
     [ 0.  1.  0.]
     [ 0.  0.  1.]]

### La transposée

La **transposée** d'une matrice s'obtient en inversant les lignes et les colonnes de la matrice. Soit un matrice $A \in \mathbb{R}^{m \times n}$, sa transposée, notée $A^T$, est définie comme:

\begin{align}
A^T \in \mathbb{R}^{n \times m}, (A^T)_{ij} = A_{ji}
\end{align}

Nous avons déjà utilisé la transposée quand nous avons décrit les vecteurs lignes comme la transposée d'un vecteur colonne.

Les propriétés suivantes peuvent être facilement vérifiées:

- $(A^T)^T = A$
- $(AB)^T=B^TA^T$
- $(A+B)^T = A^T + B^T$

```python
import numpy as np

A = np.matrix([[2,3,4],[5,6,6],[1,9,-1]])
B = np.matrix([[1,6,7],[5,9,4],[0,8,1]])
ATT = A.transpose().transpose()
print ("====== Propriété 1:\n A= ",A,"\n ATT=",ATT)

ABT=(A*B).transpose()
BTAT=B.transpose() * A.transpose()
print ("====== Propriété 2:\n ABT= ",ABT,"\n BTAT=",BTAT)

AplusBT= (A+B).transpose()
ATplusBT= A.transpose()+B.transpose()
print ("====== Propriété 3:\n AplusBT= ",AplusBT,"\n ATplusBT=",ATplusBT)

```

    ====== Propriété 1:
     A=  [[ 2  3  4]
     [ 5  6  6]
     [ 1  9 -1]]
     ATT= [[ 2  3  4]
     [ 5  6  6]
     [ 1  9 -1]]
    ====== Propriété 2:
     ABT=  [[ 17  35  46]
     [ 71 132  79]
     [ 30  65  42]]
     BTAT= [[ 17  35  46]
     [ 71 132  79]
     [ 30  65  42]]
    ====== Propriété 3:
     AplusBT=  [[ 3 10  1]
     [ 9 15 17]
     [11 10  0]]
     ATplusBT= [[ 3 10  1]
     [ 9 15 17]
     [11 10  0]]

### Les matrices symétriques

Une matrice carrée $A \in  \mathbb{R}^{n \times n}$ est **symétrique** si $A = A^T$. Elle est **anti-symétrique** si $A = -A^T$. Il est aisé de montrer que pour toute matrice $A \in  \mathbb{R}^{n \times n}$, la matrice $A + A^T$ est symétrique et la matrice $A - A^T$ est anti-symétrique. De cette propriété découle que toute matrice $A \in  \mathbb{R}^{n \times n}$ peut être décomposée comme la somme d'une matrice symétrique et d'une matrice anti-symétrique car:

\begin{align}
A = \frac{1}{2}(A + A^T)+\frac{1}{2}(A - A^T)
\end{align}

La matrice de droite est symétrique, alors que la matrice de gauche est anti-symétrique. Il est courant de noter n'ensemble des matrices symétriques de taille $n$ comme étant $\mathbb{S}^{n}$. Si $A \in \mathbb{S}^{n}$ alors A est une matrice symétrique de dimension $n$.

```python
import numpy as np

A = np.matrix([[2,3,4],[5,6,6],[1,9,-1]])

AplusAT = A + A.transpose()
AmoinsAT = A - A.transpose()
print ("====== Propriété 1:\nAplusAT=\n",AplusAT)
print ("====== Propriété 2:\nAmoinsAT=\n",AmoinsAT)

Formule=(1/2)*AplusAT + (1/2)*AmoinsAT
print ("====== Propriété 3:\Formule=\n",Formule)

```

    ====== Propriété 1:
    AplusAT=
     [[ 4  8  5]
     [ 8 12 15]
     [ 5 15 -2]]
    ====== Propriété 2:
    AmoinsAT=
     [[ 0 -2  3]
     [ 2  0 -3]
     [-3  3  0]]
    ====== Propriété 3:\Formule=
     [[ 2.  3.  4.]
     [ 5.  6.  6.]
     [ 1.  9. -1.]]

### La trace

La trace d'une matrice $A \in \mathbb{R}^{n \times n}$, notée $tr(A)$ ou juste $tr A$ est la somme des éléments de la diagonale de la matrice:

\begin{align}
tr(A) = \sum_{i=1}^n A_{ii}
\end{align}

Le trace d'une matrice a les propriétés suivantes:

- Pour $A \in \mathbb{R}^{n \times n}$, $tr A = tr A^T$
- Pour $A,B \in \mathbb{R}^{n \times n}$, $tr(A+B) = tr A + tr B$
- Pour $A \in \mathbb{R}^{n \times n}$, $t \in \mathbb{R}$, $tr(tA)=t\text{ } tr A$
- Pour $A,B$ tels que $AB$ est carré, $tr AB = tr BA$
- Pour $A,B,C$ tels que $ABS$ est carré, $tr ABC = tr BCA = tra CAB$, cette propriété se généralise pour un ensemble quelconque de matrice.

```python
import numpy as np

A = np.matrix([[2,3,4],[5,6,6],[1,9,-1]])
B = np.matrix([[2,6,4],[1,5,6],[2,9,8]])
C = np.matrix([[1,2,9],[2,3,1],[9,1,8]])

trA = np.trace(A)
trAT = np.trace(A.transpose())
print ("====== Propriété 1: trA=",trA," trAT=",trAT)

trAplusB = np.trace(A+B)
trAplustrB = np.trace(A) + np.trace(B)
print ("====== Propriété 2: trAplusB=",trAplusB," trAplustrB=",trAplustrB)
t = 8
trtA= np.trace(t*A)
ttrA= t * np.trace(A)
print ("====== Propriété 3: trtA=",trtA," ttrA=",ttrA)

trAB=np.trace(A*B)
trBA=np.trace(B*A)
print ("====== Propriété 4: trAB=",trAB," trBA=",trBA)

trABC=np.trace(A*B*C)
trBCA=np.trace(B*C*A)
trCAB=np.trace(C*A*B)
print ("====== Propriété 5: trABC=",trABC," trBCA=",trBCA," trCAB=",trCAB)

```

    ====== Propriété 1: trA= 7  trAT= 7
    ====== Propriété 2: trAplusB= 22  trAplustrB= 22
    ====== Propriété 3: trtA= 56  ttrA= 56
    ====== Propriété 4: trAB= 179  trBA= 179
    ====== Propriété 5: trABC= 1688  trBCA= 1688  trCAB= 1688

### Normes

#### Définitions

La **norme** d'un vecteur $\|x\|$ est une mesure de la "longueur" d'un vecteur. Par exemble, nous avons la norme euclidienne courammment utilisée (ou norme $\ell_2$) :

\begin{align}
\|x\|_2 = \sqrt{ \sum_{i = 1}^n x_i^2 }
\end{align}

Remarquez que : $\|x\|_2^2= x^Tx$

Plus formellement, une norme est une fonction $f: \mathbb{R}^n \to \mathbb{R}$ qui satisfait 4 propriétés:

1. Pour tout $x \in \mathbb{R}^n$, $f(x) \geq 0$
2. $f(x) = 0$ si et seulement si $x=0$
3. Pour tout $x \in \mathbb{R}^n$, $t \in \mathbb{R}$, $f(tx)=|t| f(x)$
4. Pour tout $x,y \in \mathbb{R}^n$, $f(x+y) \leq f(x)+f(y)$

Autres exemple de normes:

#### Norme $\ell_1$

\begin{align}
\|x\|_1 = \sum_{i=1}^n|x_i|
\end{align}

#### Norme $\ell_\infty$

\begin{align}
\|x\|_\infty = max_i|x_i|
\end{align}

#### Généralisation: norme $\ell_p$

En réalité les trois normes présentées précédemment, sont des exemples de norme de la famille des normes $\ell_p$. Ces normes sont paramétrées par un nombre réel $p \geq 1$, et définies comme suit:

\begin{align}
\|x\|_p = \left(\sum_{i=1}^n |x_i|^{p}\right)^\frac{1}{p}
\end{align}

#### Norme de matrices

Des normes peuvent aussi être définies pour des matrices, comme par exemple la norme de Frobenius:

\begin{align}
\| A \|_F = \sqrt{\sum_{i=1}^m \sum_{j=1}^n A_ij^2} = \sqrt{ tr (A^TA) }
\end{align}

```python
import numpy as np
from numpy import linalg as LA

A = np.matrix([[2,3,4],[5,6,6],[1,9,-1]])
lF = LA.norm(A,'fro')
print("Norme de Frobenius de A: ",lF)

V= np.matrix([1,2,3])
print ("Norme de L1 de V: ",LA.norm(V,2))
print ("Norme de Linf de V: ",LA.norm(V,1))
```

    Norme de Frobenius de A:  14.4568322948
    Norme de L1 de V:  3.74165738677
    Norme de Linf de V:  3.0

### Indépendance linéaire et rang

Un ensemble de vecteurs $\{x_1,x_2,\dots,x_n\}$ est dit (linéairement) **indépendant** si aucun des vecteurs ne peut être représenté comme combinaison linéaire des autres vecteurs. Réciproquement, si un vecteur  peut être représenté comme une combinaison linéaire des autres vecteurs, il est dit (linéairement) **dépendant**. Par exemple si:

\begin{align}
x_n=\sum_{i=1}^{n-1} \alpha_i x_i
\end{align}

pour les coefficients $\{\alpha_1,\dots,\alpha_{n-1}\}$ alors $x_n$ est dépendant de $\{x_1,\dots,x_{n-1}\}$; sinon il est indépendant de $\{x_1,\dots,x_{n-1}\}$.

Le rang des colonnes d'une matrice $A$ est le plus grand nombre de colonnes de $A$ qui constitue un ensemble linéairement indépendant. De la même manière, le rang des lignes d'une matrice $A$ est le plus grand nombre de lignes de $A$ qui constitue un ensemble linéairement indépendant. En réalité $rangcolonne(A) = rangligne(A)$, cette quantité est donc simplement appelée **rang** de $A$, notée $rang(A)$. Quelques propriétés du rang:

- Pour $A \in \mathbb{R}^{m \times n}$, $rang(A) \leq min(m,n)$. Si $rang(A) = min(m,n)$, alors $A$ est dit **de rang plein**.
- Pour $A \in \mathbb{R}^{m \times n}$, $rang(A) = rang(A^T)$.
- Pour $A \in \mathbb{R}^{m \times n}$, $B \in \mathbb{R}^{n \times p}$, $rang(AB) \leq min(rang(A),rang(b))$.
- Pour $A,B \in \mathbb{R}^{m \times n}$, $rang(A+B) \leq rang(A)+rang(B)$.

```python
import numpy as np
from numpy.linalg import matrix_rank

A = np.matrix([[2,3,4],[5,6,6],[1,9,-1]])
print ("Rang de A: ",matrix_rank(A))
B = np.matrix([[2,3,4],[5,6,6]])
print ("Rang de B: ",matrix_rank(B))
print ("Rang de B: ",matrix_rank(B.transpose()))

```

    Rang de A:  3
    Rang de B:  2
    Rang de B:  2

### L'inverse d'une matrice

L'**inverse** d'une matrice carrée $A \in \mathbb{R}^{m \times n}$, est notée $A^{-1}$ et est l'unique matrice telle que:

\begin{align}
A^{-1}A= I = AA^{-1}
\end{align}

Il en suit que $A^{-1}$ peut ne pas exister pour certaines matrices $A$. Nous disons que $A$ est **inversible** ou **non-singulière** si $A^{-1} $ existe,  et **non-inversible** ou **singulière** sinon.

$A^{-1}$ existe si et seulement si $A$ est de rang plain. Nous verrons par la suite qu'il existe d'autres conditions nécessaires et suffisantes pour le caractère inversible des matrices. Les propriétés suivantes partent de l'hypothèse que $A,B \in \mathbb{R}^{m \times n}$ sont non singulières.

- $(A^{-1})^{-1} = A$
- Si $Ax = b$ , nous pouvons multiplier par $A^{-1}$ des deux cés pour obtenir $x = A^{-1}b$.
- $(AB)^{-1}=B^{-1}A^{-1}$
- $(A^{-1})^T = (A^T)^{-1}$. Pour cette raison cette matrice est souvent notée $A^{-T}$.

```python
import numpy as np
from numpy.linalg import inv

A = np.matrix([[2,3,4],[5,6,6],[1,9,-1]])
Ainv = inv(A)
print("La matrice inverse de A est:\nAinv=\n",Ainv)
print("La matrice A*Ainv est:\nA*Ainv=\n",A*Ainv)
```

    La matrice inverse de A est:
    Ainv=
     [[-0.86956522  0.56521739 -0.08695652]
     [ 0.15942029 -0.08695652  0.11594203]
     [ 0.56521739 -0.2173913  -0.04347826]]
    La matrice A*Ainv est:
    A*Ainv=
     [[  1.00000000e+00  -1.11022302e-16   0.00000000e+00]
     [  2.22044605e-16   1.00000000e+00  -6.93889390e-17]
     [  3.33066907e-16  -1.11022302e-16   1.00000000e+00]]

### Matrice othogonale

Deux vecteurs sont $x,y \in \mathbb{R}^n$ sont **orthogonaux** si $x^Tx=0$. Un vecteur $x \in \mathbb{R}$ est **normalisé** si $\|x\|_2 = 1$. Une matrice $U \in \mathbb{R}^{n times n}$ est dite **orthogonale**  si toutes ses colonnes sont orthogonales entre elles et sont normalisées (les colonnes sont dites **orthonormales**).

Il s'en suit immédiatement de la définition de l'orthogonalité et de la normalité que:

\begin{align}
U^TU = I = UU^T
\end{align}

En d'autres mots, l'inverse d'une matrice orthogonale est sa transposée. Notez que si $U$ n'est pas carrée - i.e., $U \in \mathbb{R}^{m \times n}, n < m$ - mais que ses colonnes sont quand même orthonormales, alors $U^TU = I$ mais $UU^T \ne I$. En général, nous n'utilisons le terme orthogonal que pour décrire des matrices $U$ carrées.

Une autre propriété intéressante des matrices othogonales est que multiplier un vecteur par un matrice orthogonale ne change pas la norme euclidienne du vecteur, i.e.:

\begin{align}
\|Ux\|_2 = \|x\|_2
\end{align}

pour tout $x \in \mathbb{R}^n, U \in \mathbb{R}^{n \times n}$ orthogonale.

### Sous-espace vectoriel engendré, projection, image, noyau

#### Sous espace vectoriel engendré
Le **sous-espace vectoriel engendré** d'un ensemble de vecteurs $\{x_1,x_2,\dots,x_n\}$ est l'ensemble des vecteurs qui peuvent être exprimés sous forme d'une combinaison linéaire de $\{x_1,x_2,\dots,x_n\}$. C'est à dire:

\begin{align}
Vect(\{x_1,x_2,\dots,x_n\}) =
\left\lbrace v : v = \sum_{i=1}^n \alpha_ix_i, \text{  }\alpha_i \in \mathbb{R}\right\rbrace
\end{align}

On peut démontrer que si $\{x_1,x_2,\dots,x_n\}$ est un ensemble de $n$ vecteurs linéairement indépendants, où chaque $x_i \in\mathbb{R}^n$, alors $Vect(\{x_1,x_2,\dots,x_n\}) = \mathbb{R}^n$. En d'autre mots, tout vecteur $v \in \mathbb{R}^n$ peut être écrit comme une combinaison des vecteurs $x_1$ à $x_n$.

__Note__: le sous-espace vectoriel engendré est appelé "span" en anglais et noté $span(\{x_1,x_2,\dots,x_n\})$.

#### Projection
La **projection** d'un vecteur $y \in \mathbb{R}^m$ dans l'espace vectoriel engendré par $\{x_1,x_2,\dots,x_n\}$ (nous considérons que $x_i \in \mathbb{R}^m$) est le vecteur $v \in Vect(\{x_1,x_2,\dots,x_n\})$, tel que $v$ soit aussi proche que possible de $y$, comme mesuré par la norme euclidienne $\|v-y\|_2$. On note cette projection: $Proj(y;\{x_1,x_2,\dots,x_n\})$, et plus formellement:

\begin{align}
Proj(y;\{x_1,x_2,\dots,x_n\}) = \text{argmin}_{v \in Vect(\{x_1,x_2,\dots,x_n\})} \|y-v\|_2
\end{align}

#### Image
L'**image** (parfois appelée espace des colonnes) d'une matrice $A \in \mathbb{R}^{m \times n}$, noté $\mathcal{Im}(A)$ est le sous-espace vectoriel engendré par les colonnes de $A$. En d'autres mots:

\begin{align}
\mathcal{Im}(A)=\{v \in \mathbb{R}^m: v = Ax, \text{  } x \in \mathbb{R}^n\}
\end{align}

Si nous faisons quelques hypothèses (principalement que $A$ est une matrice pleine et que $n < m$), la projection d'un vecteur $y \in \mathbb{R}^m$ dans l'image de $A$ s'exprime de la manière suivante:

\begin{align}
Proj(y;A) = argmin_{v \in \mathcal{R}(A)}\|v-y\|_2 = A(A^TA)^{-1}A^Ty
\end{align}

__Note__: l'image est appelée "Range" en anglais et est notée $\mathcal{R}(A)$.

#### Noyau (kernel ou nullspace)

Le **noyau** d'une matrice $A \in \mathbb{R}^{m \times n}$, noté $\mathcal{N}(A)$  est l'ensemble des vecteurs égaux à 0 lorsqu'ils sont multipliés par $A$, i.e.:

\begin{align}
\mathcal{N}(A) = \{ x \in \mathbb{R}^n : Ax = 0 \}
\end{align}

Les vecteurs dans $\mathcal{Im}(A)$ sont de taille $m$, alors que les vecteurs dans $\mathcal{N}(A)$ sont de taille $n$, les vecteurs dans $\mathcal{Im}(A^T)$ et dans $\mathcal{N}(A)$ sont quant-à eux tous dans $\mathbb{R}^n$. En réalité nous pouvons en dire beaucoup plus, il s'avère que:

\begin{align}
\{ w: w = u + v, u \in \mathcal{Im}(A^T), v \in \mathcal{N}(A) \} = \mathbb{R}^n \text{ and } \mathcal{Im}(A^T) \cap \mathcal{N}(A) = \emptyset
\end{align}

En d'autres mots, $\mathcal{Im}(A^T)$  et $ \mathcal{N}(A)$ sont deux sous-espaces disjoint qui convrent entièrement l'espace $\mathbb{R}^n$. Les ensembles de ce type sont appelés des **compléments orthogonaux**, et ils sont notés  $\mathcal{Im}(A^T) = \mathcal{N}(A)^\perp$

__Note__: le noyau est appelée "nullspace" en anglais et est notée $\mathcal{N}(A)$.

### Le déterminant

Le **déterminant** d'une matrice carrée $A \in \mathbb{R}^{n \times n}$, est une fonction $det :  \mathbb{R}^{n \times n} \to \mathbb{R}$, et est noté $\left\vert A\right\vert$ ou $det(A)$, ou en supprimant les parenthèses $det A$. La formule complète de calcul du déterminant ne donne que peu d'intuition sur son sens, nous allons donc à la place donner trois propriétés définissant le déterminant, desquelles découle le reste (ainsi que la formule générale).

1. Le déterminant de la matrice identité est 1: $\left\vert I\right\vert = 1$.
2. Etant donnée une matrice $A \in \mathbb{R}^{n \times n}$, si on multiplie une ligne de $A$ par $t \in \mathbb{R}$, alors les déterminant de la nouvelle matrice est $t\left\vert A\right\vert$.
\begin{equation*}
\left\vert \text{ } \begin{bmatrix}
- & a_1^T & - \\
- & a_2^T & - \\
  & \vdots & \\
- & a_m^T & -
\end{bmatrix} \text{ } \right\vert = t \left\vert A \right\vert
\end{equation*}
3. Si nous échangeons deux lignes $a_i^T$ et $a_j^T$ de $A$, le déterminant de la nouvelle matrice est $-\left\vert A\right\vert$, par exemple:
\begin{equation*}
\left\vert \text{ } \begin{bmatrix}
- & a_2^T & - \\
- & a_1^T & - \\
  & \vdots & \\
- & a_m^T & -
\end{bmatrix} \text{ } \right\vert =  -t \left\vert A \right\vert
\end{equation*}

Ces propriétés ne nous donnent cependant que peu d'intuition sur la nature du déterminant, nous allons donc ajouter quelques propriétés qui découlent des 3 précédentes.

- Pour $A \in \mathbb{R}^{n \times n}$, $|A|=|A^T|$.
- Pour $A,B \in \mathbb{R}^{n \times n}$, $|AB|=|A||B|$.
- Pour $A \in \mathbb{R}^{n \times n}$, $|A|=0$ si et seulement si $A$ est singulière (non inversible).
- Pour $A \in \mathbb{R}^{n \times n}$, et $A$ non-singulière, $|A|^{-1} = \frac{1}{|A|}$.

Avant de donner une définition générale du déterminant, nous définissons, pour $A \in \mathbb{R}^{n \times n}$, $A_{\setminus i,\setminus j} \in \mathbb{R}^{(n-1) \times (n-1)}$ comme étant la matrice résultant de la suppression de la $i^{eme}$ ligne et de la $j^{eme}$ colonne de $A$. La formule générale (récursive) du déterminant devient:

\begin{align}
|A| &= \sum_{i=1}^n (-1)^{i+j} a_{ij} \left\vert A_{\setminus i,\setminus j} \right\vert & (\text{pour tout  } j \in 1,\dots,n)\\
 &= \sum_{j=1}^n (-1)^{i+j} a_{ij} \left\vert A_{\setminus i,\setminus j} \right\vert & (\text{pour tout  } i \in 1,\dots,n)\\
\end{align}

avec le cas spécial $|A| = a_{11}$ pour $A \in \mathbb{R}^{1 \times 1}$. Si nous procédons à une expansion de cette formule complètement pour $A \in \mathbb{R}^{n \times n}$, il y aurait un total de $n!$ (n factoriel)termes. C'est pour cette raison que nous n'allons pas écrire la formule explicite au-delà de matrices 3x3.

\begin{align}
\left\vert \text{ } \begin{bmatrix}
a_{11}
\end{bmatrix} \text{ } \right\vert & = a_{11} \\
\left\vert \text{ } \begin{bmatrix}
a_{11} & a_{12} \\
a_{21} & a_{22}
\end{bmatrix} \text{ } \right\vert & = a_{11}a_{22} - a_{12}a_{21} \\
\left\vert \text{ } \begin{bmatrix}
a_{11} & a_{12} & a_{13}\\
a_{21} & a_{22} & a_{23}\\
a_{31} & a_{32} & a_{33}
\end{bmatrix} \text{ } \right\vert & = \begin{array}
+ a_{11}a_{22}a_{21} + a_{12}a_{23}a_{31} + a_{13}a_{21}a_{32} \\
- a_{11}a_{23}a_{32} - a_{12}a_{21}a_{33} - a_{13}a_{22}a_{31} \end{array}
\end{align}

La matrice **adjointe**, aussi appelée **comatrice** d'une matrice $A \in \mathbb{R}^{n \times n}$, notée $adj(A)$, est définie comme suit:

\begin{align}
adj(A) \in \mathbb{R}^{n \times n}, (adj(A))_{ij}=(-1)^{i + j}|A_{\setminus j,\setminus i}|
\end{align}

Il peut être démontré que pour toute matrice $A \in \mathbb{R}^{n \times n}$ non singulières:

\begin{align}
A^{-1}= \frac{1}{|A|}adj(A)
\end{align}

En pratique:

\begin{align}
B &=\begin{bmatrix}
1 & 2 & 3 \\
1 & 1 & 2 \\
-1 & -4 & -1
\end{bmatrix} \\
det(B) &= +6 \\
adj(B) &=\begin{bmatrix}
+ \begin{vmatrix} 1 & 2 \\ -4 & -1 \end{vmatrix} &
- \begin{vmatrix} 0 & 2 \\ -1 & -1 \end{vmatrix} &
+ \begin{vmatrix} 0 & 1 \\ -1 & -4 \end{vmatrix} \\
- \begin{vmatrix} 2 & 3 \\ -4 & -1 \end{vmatrix} &
+ \begin{vmatrix} 1 & 3 \\ -1 & -1 \end{vmatrix} &
- \begin{vmatrix} 1 & 2 \\ -1 & -4 \end{vmatrix} \\
+ \begin{vmatrix} 2 & 3 \\ 1 & 2 \end{vmatrix} &
- \begin{vmatrix} 1 & 3 \\ 0 & 2 \end{vmatrix} &
+ \begin{vmatrix} 1 & 2 \\ 0 & 1 \end{vmatrix} \\
\end{bmatrix} \\
adj(B) &=\begin{bmatrix}
7 & -2 & 1 \\
-10 & 2 & 2 \\
1 & -2 & 1
\end{bmatrix} \\
adj(B)^T &=\begin{bmatrix}
7 & -10 & 1 \\
-2 & 2 & -2 \\
1 & 2 & 1
\end{bmatrix} \\
\text{d'où } B^{-1} &= \frac{1}{6}\begin{bmatrix}
7 & -10 & 1 \\
-2 & 2 & -2 \\
1 & 2 & 1
\end{bmatrix}
\end{align}

Bien que que cette formule soit élégante, il existe des méthodes bien plus efficaces (numériquement) pour calculer l'inverse d'une matrice.

### Forme quadratique et matrices positives semi définies

Etant donnée une matrice carrée $A \in \mathbb{R}^{n \times n}$ et un vecteur $x \in \mathbb{R}^n$, la valeur scalaire $x^TAx$ est appelée **la forme quadratique**. En écrivant explicitement cette forme, nous voyons que:

\begin{align}
x^TAx = \sum_{i=1}^n \sum_{j=1}^n A_{ij}x_ix_j
\end{align}

Exemple:

\begin{align}
x &= \begin{bmatrix}x_1\\x_2\end{bmatrix}\\
A &= \begin{bmatrix}a_{11} & a_{12}\\ a_{21} & a_{22} \end{bmatrix} \\
x^TAx &= \begin{bmatrix}x_1 & x_2\end{bmatrix}
\begin{bmatrix}a_{11} & a_{12}\\ a_{21} & a_{22} \end{bmatrix}
\begin{bmatrix}x_1\\x_2\end{bmatrix}\\
x^TAx &= \begin{bmatrix}x_1 & x_2\end{bmatrix}
\begin{bmatrix}a_{11}x_1 + a_{12}x_2\\ a_{21}x1 + a_{22}x_2 \end{bmatrix}  \\
x^TAx &= x_1 \times (a_{11}x_1 + a_{12}x_2)+x_2\times (a_{21}x_1 + a_{22}x_2) \\
x^TAx &= a_{11}x_1^2 + (a_{12} + a_{21}) x_1x_2 + a_{22}x_2^2
\end{align}

Notez que:

\begin{align}
x^TAx = (x^TAx)^T = x^TA^Tx = x^T(\frac{1}{2}A+\frac{1}{2}A^T)x
\end{align}

i.e., seule la partie symétrique de $A$ contribue à la forme quadratique. C'est pour cette raison que nous partons souvent du principe implicite que les matrices impliquées dans les formes quadratiques sont symétriques.

Donnons les définitions suivantes:

- Une matrice symétrique $A \in \mathbb{S}^{n}$ est **positive  définie** (PD) si pour tous les vecteurs non nuls $x^TAx \ge 0$. Cette propriété est notée $A \succ 0$ (ou juste $A > 0$). L'ensemble des matrices PD est souvent notée $\mathbb{S}_{++}^{n}$
- Une matrice symétrique $A \in \mathbb{S}^{n}$ est **positive semi définie** (PSD) si pour tous les vecteurs $x^TAx \ge 0$. Cette propriété est notée $A \succeq 0$ (ou juste $A \ge 0$). L'ensemble des matrices PSD est souvent notée $\mathbb{S}_{+}^{n}$
- Une matrice symétrique $A \in \mathbb{S}^{n}$ est **negative définie** (ND) si pour tous les vecteurs non nuls $x^TAx < 0$. Cette propriété est notée $A \prec 0$ (ou juste $A < 0$).
- Une matrice symétrique $A \in \mathbb{S}^{n}$ est **negative semi définie** (NSD) si pour tous les vecteurs $x^TAx \le 0$. Cette propriété est notée $A \preceq 0$ (ou juste $A \le 0$).
- Une matrice symétrique $A \in \mathbb{S}^{n}$ est **indéfinie**, si elle n'est ni PSD di NSD - i.e., si il existe $x_1,x_2 \in \mathbb{R}^n$ tel que $x_1^TAx_1 > 0$ et $x_2^TAx_2 < 0$

Il est évident que si $A$ est PD, alors $-A$ est ND et vice versa. De la même manière si $A$ est PSD, alors $-A$ est NSD et vice versa. Si $A$ est indéfinie, alors $-A$ est indéfinie. Il peut être montré que les matrices PD et ND sont toujours inversibles.

Enfin, il y a un type de matrice positive définie qui apparait fréquemment: soit une matrice $A \in \mathbb{R}^{m \times n}$ (pas nécessairement symétrique ou carré), la matrice $G = A^TA$ (parfois appelée **matrice de Gram** ) est toujours positive et semi définie. Plus encore, si $m \ge n$ (et nous partons de l'hypothèse que $A$ est de rang plein), alors $G = A^TA$ est positive et définie.

### Valeurs et vecteurs propres

Soit une matrice $A \in \mathbb{R}^{n \times n}$, nous disons que $\lambda \in \mathbb{C}$ est une **valeur propre** de $A$ et que $x \in \mathbb{C}^n$ est le **vecteur propre** correspondant si:

\begin{align}
Ax= \lambda x, \text{    } x \ne 0
\end{align}

Intuitivement, cette définition signifie de la multiplication de $A$ par le vecteur $x$ résulte un nouveau vecteur pointant dans la même direction que $x$, mais dilaté d'un facteur $\lambda$. Notez aussi que pour tout vecteur propre $x \in \mathbb{C}^n$ et scalaire $c \in \mathbb{C}$, $A(cx) = cAx = c\lambda x = \lambda(cx)$, donc $cx$ est aussi un vecteur propre de $A$. C'est pour cela que lorsque l'on parle **du** vecteur propre associé à $\lambda$, nous partons du principe que le vecteur propre est normalisé de longueur 1.

Nous pouvons alors réécrire l'équation précédente pour définir que $(\lambda,x)$ est un couple de valeur et vecteur propre de $A$:

\begin{align}
(\lambda I - A)x = 0, \text{  } x \ne 0
\end{align}

Mais $(\lambda I - A)x = 0$ a un solution non nulle pour $x$ si et seulement si $(\lambda I - A)$ a un un noyau non null, ce qui est seulement le cas si $(\lambda I - A)$ est une matrice singulière, i.e.,

\begin{align}
\left\vert(\lambda I - A)\right\vert = 0
\end{align}

Nous pouvons maitenant utiliser la définition du déterminant pour développer cette expression en une (très grande) équation polynomiale en $\lambda$, de degré maximum $n$. Nous pouvons trouver les $n$ racines de cette équation polynomiale (éventuellement complexes) qui sont les $n$ valeurs propres recherchées $\lambda_1, \lambda_2, \dots, \lambda_n$.
Pour trouver le vecteur propre de la valeur propre $\lambda_i$, nous n'avons qu'à résoudre l'équation linéaire $(\lambda_i I - A)x = 0$. Cette méthode n'est cependant pas utilisée en pratique pour le calcul numérique des valeurs propres et des vecteurs propres (l'expension complète du déterminant est en complexité $n!$).

La suite définit les propriétés des valeurs propres et des vecteurs propres. Toutes les propriétés partent des hypothèses suivantes: $A \in \mathbb{R}^{n \times n}$, $A$ a comme valeurs propres $\lambda_1, \lambda_2, \dots, \lambda_n$, et comme vecteurs propres associés $x_1, x_2, \dots, x_n$.

- La trace de $A$ est égale à la somme de ses valeurs propres,

\begin{align}
    tr A = \sum_{i=1}^n \lambda_i
\end{align}

- Le déterminand de $A$ est égal au produit des ses valeurs propres,

\begin{align}
    |A| = \prod_{i=1}^n \lambda_i
\end{align}

- Le rang de $A$ est égal au nombre de valeurs propres non nulles de $A$.
- Si $A$ n'est pas singulière, alors $1/\lambda_i$ est une valeur propre de $A^{-1}$ avec comme vecteur propre associé $x_i$, i.e., $A^{-1}c_i = (1/\lambda_i)x_i$.
- Les valeurs propres d'une matrice diagonale $D= diag(d_1,d_2,\dots,d_n)$ sont simplement les éléments de la diagonale $d_1,d_2,\dots,d_n$.

Nous pouvons écrire l'équation de tous les vecteurs propres simultanément comme:

\begin{align}
    AX = X\Lambda
\end{align}

Où les colonne de $X \in \mathbb{R}^{n \times n}$ sont les vecteurs propres de $A$ et $\Lambda$ est la matrice diagonale dont les éléments sont les valeurs propres de $A$, i.e.,

\begin{align}
    X \in  \mathbb{R}^{n \times n} = \begin{bmatrix}
\rvert & \rvert	&       & \rvert \\
x_1 & x_2 & \dots & x_n \\
\rvert & \rvert	&       & \rvert
\end{bmatrix}\text{ , } \Lambda = diag(\lambda_1, \lambda_2, \dots, \lambda_n)
\end{align}

Si les vecteurs propres de $A$ sont linéairement indépendants, alors la matrice $X$  est inversible, donc $A = X \Lambda X^{-1}$. Une matrice qui peut sécrire sous cette forme est dite **diagonalisable**.

### Valeurs et vecteurs propres des matrices symétriques

Deux propriété remarquables apparaissent lorsque nous étudions les valeurs propres et les vecteurs propres de matrice symétrique $A \in \mathbb{S}^n$. Premièrement, il peut être montré que les valeurs propres de $A$ sont réelles. Deuxièmement, les vecteurs propres forment une base orthonormale, i.e., la matrice $X$ définie précédemment est une matrice orthogonale (pour cette raison, nous notons la matrice des vecteurs propres $U$ dans ce cas). Nous pouvons alors écrire $A$  comme $A = U\Lambda U^T$ (car l'inverse d'une matrice orthogonale est sa transposée).

En utilisant cette définition, on peut montrer que la "définition" d'une matrice dépend entièrement du signe de ses valeurs propres: Soit $A \in \mathbb{S}^n = U\Lambda U^T$. Alors:

\begin{align}
x^TAx = x^TU\Lambda U^T x= y^T\Lambda y = \sum_{i=1}^n \lambda_iy_i^2 \end{align}

Où $y = U^Tx$ et comme $U$ est de rang plein, tous les vecteurs $y \in \mathbb{R}^{n}$, peuvent s'écrire sous cette forme. Comme $y_i^2$ est toujours positif, alors le signe de cette expression dépend entièrement des $\lambda_i$.

- Si tous les $\lambda_i > 0$, alors $A$ est de type PD.
- Si tous les $\lambda_i \ge 0$, alors $A$ est de type PSD.
- Si tous les $\lambda_i < 0$, alors $A$ est de type ND.
- Si tous les $\lambda_i \le 0$, alors $A$ est de type NSD.
- Si $A$ a des valeurs propres positives et négatives, $A$ est indéfinie.

Une application fréquente des valeurs et vecteurs propres est la maximisation de fonctions d'une matrice. En particulier, pour une matrice $A \in \mathbb{S}^n$, étudions les problême de maximisation suivant:

\begin{align}
    \text{max}_{x \in \mathbb{R}^n} x^TAx \text{ tel que } \|x\|_2^2=1
\end{align}

i.e., nous voulons trouver les vecteur (de norme 1) qui maximise la forme quadratique. Si nous considérons les valeurs propres ordonnées telles que $\lambda_1 \ge \lambda_1 \ge \dots \ge \lambda_n$, le vecteur $x$ optimal pour ce probleme d'optimisation est $x_1$. Dans ce cas la valeur maximum de la forme quadratique est $\lambda_1$.

Par symétrie, la solution optimale pour le problème :

\begin{align}
    \text{min}_{x \in \mathbb{R}^n} x^TAx \text{ tel que } \|x\|_2^2=1
\end{align}

est le vecteur $x_n$ et a pour valeur minimale $\lambda_n$.

## Calcul matriciel

### Le gradient

Soit $f : \mathbb{R}^{m \times n} \to \mathbb{R}$ une fonction prenant en paramètre une matrice $A$ de dimension $m \times n$ et retournant une valeur réelle. Alors le **gradient** de $f$ (selon $A \in  \mathbb{R}^{m \times n}$) est la matrice des dérivées partielles définie comme:

\begin{align}
\nabla_A f(A) \in  \mathbb{R}^{m \times n} &= \begin{bmatrix}
\frac{\delta f(A)}{\delta A_{11}} &
\frac{\delta f(A)}{\delta A_{12}} &
\dots &
\frac{\delta f(A)}{\delta A_{1n}} \\
\frac{\delta f(A)}{\delta A_{21}} &
\frac{\delta f(A)}{\delta A_{22}} &
\dots &
\frac{\delta f(A)}{\delta A_{2n}} \\
\vdots   & \vdots  & \ddots & \vdots \\
\frac{\delta f(A)}{\delta A_{m1}} &
\frac{\delta f(A)}{\delta A_{m2}} &
\dots &
\frac{\delta f(A)}{\delta A_{mn}} \\
\end{bmatrix} \\
\text{i.e.} \\
(\nabla_A f(A))_{ij} &= \frac{\delta f(A)}{\delta A_{ij}}
\end{align}

Notez que la dimension de $\nabla_A f(A)$ est toujours de la même dimension que $A$. Donc si, en particulier, $A$ est juste un vecteur $x \in \mathbb{R}^n$,

\begin{align}
\nabla_x f(x) &= \begin{bmatrix}
\frac{\delta f(x)}{\delta x_1} \\
\frac{\delta f(x)}{\delta x_2} \\
\vdots \\
\frac{\delta f(x)}{\delta x_n}
\end{bmatrix}
\end{align}

Il est très important de se souvenir que le gradient d'une fonction est seulement définie si elle retourne une valeur scalaire. Il n'est pas possible par exemple de calculer les gradient de $Ax, A \in \mathbb{R}^{n \times n}$ selon $x$, puisque la fonction retourne un vecteur.

Il s'en suit que:

- $\nabla_x(f(x)+g(x) =  \nabla_x f(x) + \nabla_x g(x)$
- $\nabla_x(f(x)g(x) =  f(x) \nabla_x g(x) + g(x) \nabla_x f(x)$
- Pour $t \in \mathbb{R}, \nabla_x(t f(x)) = t\nabla_x(f(x))$

### Le Hessien

Soit $f : \mathbb{R}^{n} \to \mathbb{R}$ une fonction prenant en paramètre un vecteur dimension $n$ et retournant une valeur réelle. Alors le **hessien** de $f$ selon $x$ est la matrice de dimension $n \times n$ notée $\nabla_x^2f(x)$ ou simplement $H$:

\begin{align}
\nabla_x^2 f(x) \in  \mathbb{R}^{n \times n} &= \begin{bmatrix}
\frac{\delta f(x)}{\delta x_1^2} &
\frac{\delta f(x)}{\delta x_1 \delta x_2} &
\dots &
\frac{\delta f(x)}{\delta x_1 \delta x_n} \\
\frac{\delta f(x)}{\delta x_2 \delta x_1} &
\frac{\delta f(x)}{\delta x_2^2} &
\dots &
\frac{\delta f(x)}{\delta x_2 \delta x_n} \\
\vdots   & \vdots  & \ddots & \vdots \\
\frac{\delta f(x)}{\delta x_n \delta x_1 } &
\frac{\delta f(x)}{\delta x_n \delta x_2} &
\dots &
\frac{\delta f(x)}{\delta x_n^2} \\
\end{bmatrix} \\
\text{i.e.} \\
(\nabla_x^2 f(x))_{ij} &= \frac{\delta^2 f(x)}{\delta x_i \delta x_j}
\end{align}

Notez que le Hessien est une matrice symétrique, car:

\begin{align}
\frac{\delta^2 f(x)}{\delta x_i \delta x_j} = \frac{\delta^2 f(x)}{\delta x_j \delta x_i}
\end{align}

Comme pour le gradient, le hessien n'est défini que si $f(x)$ retourne un scalaire.

Il est naturel de considérer les gradient, comme analogue à la dérivée pour les fonctions de vecteurs, et le hessien comme la dérivée seconde. Cette intuition est en général correcte, mais il y a quand même une mise en garde à garder à l'esprit.

Premièrement, pour des fonctions à une variable de type $f: \mathbb{R} \to \mathbb{R}$, la dérivée seconde est bien la dérivée de la dérivée première (par définition).

\begin{align}
\frac{\delta^2 f(x)}{\delta x^2} = \frac{\delta}{\delta x} \frac{\delta}{\delta x} f(x)
\end{align}

Cependant pour une fonction de vecteur, le gradient d'un fonction est un vecteur, et il n'est pas possible de calculer le gradient d'un vecteur. Le hessien n'est donc pas le gradient d'un gradient. C'est "presque vrai" dans un sens : si nous regardons le $i^{eme}$ élément du gradient $(\nabla_x f(x))_i = \frac{\delta f(x)}{\delta x_i}$, et que nous en prenons le gradient selon $x$, nous obtenons:

\begin{align}
\nabla_x \frac{\delta f(x)}{\delta x_i} = \begin{bmatrix}
\frac{\delta^2 f(x)}{\delta x_i \delta x_1} \\
\frac{\delta^2 f(x)}{\delta x_i \delta x_2} \\
\vdots \\
\frac{\delta^2 f(x)}{\delta x_i \delta x_n}
\end{bmatrix}
\end{align}

Ce résultat est la $i^{eme}$ colonne du hessien. D'où:

\begin{align}
\nabla_x^2 f(x) = \begin{bmatrix}
\nabla_x(\nabla_x f(x))_1 &
\nabla_x(\nabla_x f(x))_2 &
\dots &
\nabla_x(\nabla_x f(x))_n
\end{bmatrix}
\end{align}

Finalement, noter que bien que nous ayons évoqué le gradient d'une matrice $A \in \mathbb{R}^n$ et le Hessien d'un vecteur $x \in \mathbb{R}$. Il est bien sûr possible de calculer le hessien d'une matrice, mais le hessien consiste à représenter toutes les dérivée partielles ce qui dans le cas d'une matrice s'avère laborieux $\frac{\delta^2 f(A)}{\delta A_{ij}\delta A_{kl}}$ .

### Le gradient et le hessien des fonctions linéaires et quadratiques

Essayons maintenant de déterminer les gradient et le hessien de quelques fonctions simples.

Pour $x \in \mathbb{R}^n$, soit $f(x)=b^Tx$ avec $b \in \mathbb{R}^n$. Alors:

\begin{align}
f(x) = \sum_{i=1}^n b_ix_i
\end{align}

alors:

\begin{align}
\frac{\delta f(x)}{\delta x_k} &= \frac{\delta}{\delta x_k}  \sum_{i=1}^n b_ix_i \\
 &= b_k
\end{align}

De ce résultat, nous pouvons déduire que $\nabla_xb^Tx = b$. Cette situation est comparable au calcul d'une seule variable, où $\frac{\delta}{\delta x}ax=a$.

Considérons maintenant la fonction quadratique $f(x) = x^TAx$ pour $A \in \mathbb{S}^n$. Souvenez vous que:

\begin{align}
    f(x)=\sum_{i=1}^n \sum_{j=1}^n A_{ij}x_ix_j
\end{align}

alors:

\begin{align}
\frac{\delta f(x)}{\delta x_k} &= \frac{\delta}{\delta x_k}  \sum_{i=1}^n \sum_{j=1}^n A_{ij}x_i x_j \\
 &= \sum_{i=1}^n A_{ik}x_i + \sum_{i=1}^n A_{kj}x_j  \\
 &= 2 \sum_{i=1}^n A_{ik}x_i
\end{align}

La dernière égalité est vrai car $A$ est symétrique. Notez que le $k^{eme}$ élément de $\nabla_x f(x)$ est juste le produit scalaire de la $k^{eme}$ ligne de $A$ et de $x$, d'où: $\nabla_xx^TAx=2Ax$. Encore un fois, cette équation doit vous rappeler le cas d'une fonction d'une variable: $\frac{\delta}{\delta x}ax^2=2ax$

Finalement, étudions les Hessien des fonction quadratiques $f(x) = x^TAx$ (il est évident que le Hessien d'une fonction linéaire $b^T$ est 0). Le Hessien est encore plus simple que déterminer le gradient:

\begin{align}
\frac{\delta^2 f(x)}{\delta x_k \delta x_l} &=
\frac{\delta^2}{\delta x_k \delta x_l} \sum_{i=1}^n \sum_{j=1}^n  A_{ij} x_i x_j \\
 &= A_{kl} + A_{lk} \\
 &= 2A_{kl}
\end{align}

De là, il doit être clair que $\nabla_x^2x^TAx=2A$, ce qui devait être attendu et analogue au cas d'une fonction à une variable: $\frac{\delta^2}{\delta x^2}ax^2=2a$.

Pour récapituler:

- $\nabla_xb^Tx=b$
- $\nabla_xx^TAx=2Ax$ (Si $A$ est symétrique)
- $\nabla_x^2x^TAx=2A$ (Si $A$ est symétrique)

### Les moindres carrés

Utilisons les équations que nous avons obtenues dans le chapitre précédent pour en déduire les équation du moindre carré. Supposons que nous ayons une matrice $A \in \mathbb{R}^{m \times n}$ de rang plein, et un vecteur $b \in \mathbb{R}^m$ tel que $b \notin \mathcal{Im}(A)$. Dans ce cas, nous ne pouvons pas trouver un vecteur $x \in \mathbb{R}^n$, tel que $Ax = b$, à la place nous voulons un vecteur $x$ tel que $Ax$ est aussi proche que possible de $b$, distance définie par la norme euclidienne $\|Ax-b\|_2^2$.

Sachant que $\|x\|_2^2=x^Tx$, nous avons:

\begin{align}
\|Ax-b\|_2^2 &= (Ax - b)^T(Ax -b) \\
             &= ((Ax)^T - b^T )(Ax -b) \\
             &= (x^TA^T - b^T)(Ax -b) \\
             &= x^TA^TAx - x^TA^Tb -b^TAx + b^Tb\\
             &= x^TA^TAx - (b^TAx)^T -b^TAx + b^Tb\\
& b^TAx \text{ est un scalaire donc } b^TAx = (b^TAx)^T \\
             &= x^TA^TAx-2b^TAx+b^Tb
\end{align}

Si l'on prend le gradient selon $x$, nous avons:

\begin{align}
\nabla_x(x^TA^TAx-2b^TAx+b^Tb)&=\nabla_x x^TA^TAx-\nabla_x2b^TAx+b^Tb \\
&= 2A^TAx - 2A^Tb
\end{align}

En positionnant cette expression à 0, la résolution de l'équation pour $x$ donne:

\begin{align}
2A^TAx - 2A^Tb &= 0 \\
A^TAx &= A^Tb \\
x &= (A^TA)^{-1}A^Tb
\end{align}

### Le gradient du déterminant

Considérons maintenant une situation dans laquelle nous voulons trouver le gradient du déterminant d'une matrice $A \in \mathbb{R}^{n \times n}$ en fonction de $A$ : $\nabla_A|A|$.

Du chapitre sur le déterminant, nous avons:

\begin{align}
|A| &= \sum_{i=1}^n (-1)^{i+j} a_{ij} \left\vert A_{\setminus i,\setminus j} \right\vert & (\text{pour tout  } j \in 1,\dots,n)\\
\end{align}

donc:

\begin{align}
\frac{\delta}{\delta a_{kl}} |A| &= \frac{\delta}{\delta a_{kl}} \sum_{i=1}^n (-1)^{i+j} a_{ij} \left\vert A_{\setminus i,\setminus j} \right\vert \\
 &= (-1)^{k+l} \left\vert A_{\setminus k,\setminus l} \right\vert \\
 &= (adj(A))_{lk}
\end{align}

De cette égalité suit immédiatement que:

\begin{align}
\nabla_A  \left\vert A \right\vert &= (adj(A))^T \\
\text{ avec } A^{-1} &= \frac{1}{A} adj(A) \text{ alors } adj(A) = |A|A^{-1}\\
 (adj(A))^T &= (|A|A^{-1})^T \\
 &= |A|(A^{-1})^T \\
 &= \left\vert A \right\vert A^{-T}
\end{align}

Considérons maintenant la fonction $f: \mathbb{S}_{++}^n \to \mathbb{R}, f(A) = log|A|$. Notez que nous avons restreint le domaine de $f$ aux matrices PD, de manière à s'assurer que $|A| > 0$, pour que $log|A|$ soit un nombre réel. Dans ce cas nous avons:

\begin{align}
\frac{\delta log |A|}{\delta a_{ij}} &= \frac{\delta log |A|}{\delta|A|} \frac{\delta|A|}{ \delta a_{ij}} \\
 &= \frac{1}{\delta|A|}  \frac{\delta |A|}{\delta a_{ij}} \\
\end{align}

De cette égalité, il est trivial que:

\begin{align}
\nabla_A log |A| &= \frac{1}{|A|}\nabla_A|A| \\
 \text{ or } \nabla_A|A| &= \left\vert A \right\vert A^{-T} \\
 \text{ d'où } \nabla_A log|A| &=  \frac{1}{|A|}\left\vert A \right\vert A^{-T} \\
 &= A^{-T} \\
 &= A^{-1} \text{ car A est une matrice symétrique}
\end{align}

Notez la similarité avec le cas scalaire : $(\delta log(x))/(\delta x) = 1/x$.


# Aplicando Python para análisis de precios: simulación de escenarios futuros de precios

> En la clase anterior vimos como importar datos de activos de la base de datos de Yahoo Finance usando el paquete pandas-datareader. En esta clase, veremos como pronosticar escenarios de evolución de precios, suponiendo que los rendimientos diarios se distribuyen normalmente. Como esta evolución de precios es aleatoria, utilizaremos la simulación montecarlo (hacer muchas simulaciones de escenarios de evolución de precios) para obtener probabilidades de que los precios de cierre estén encima de un valor umbral y tomar decisiones con base en estas probabilidades.

**Referencias:**
- http://pandas.pydata.org/
- http://www.learndatasci.com/python-finance-part-yahoo-finance-api-pandas-matplotlib/

## 1. Recordemos como descargar datos...

Antes que nada, para poder hacer simular escenarios de predicción de precios, vamos a recordar lo que hicimos en la clase pasada de descargar los datos de Yahoo Finance, utilizando el paquete `data` de la librería `pandas_datareader`.

Esta vez, utilizaremos los datos de precios de cierre ajustados de activos de la compañía Apple en el año 2016 para nuestra aplicación.

```python
# Importamos librerías
import pandas_datareader.data as web
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
```

```python
# Función para descargar precios de cierre ajustados de varios activos a la vez:
def get_closes(tickers, start_date=None, end_date=None):
    # Fecha inicio por defecto (start_date='2010-01-01') y fecha fin por defecto (end_date=today)
    # Frecuencia de muestreo por defecto (freq='d')
    closes = web.DataReader(tickers, start=start_date, end=end_date, data_source='yahoo')['Adj Close']
    closes = closes.sort_index()
    return closes
```

```python
# Descargamos datos...
# Instrumento: Apple
instrumento = "AAPL"
# Fechas de interés (inicio y fin): 2010-2019
start = "2010-01-01"
end = "2019-12-31"
```

```python
# Obtenemos precios
closes = get_closes(tickers=instrumento,
                    start_date=start,
                    end_date=end)
```

```python
# Primeros precios
closes.head()
```

    Date
    2010-01-04    6.572422
    2010-01-05    6.583784
    2010-01-06    6.479061
    2010-01-07    6.467082
    2010-01-08    6.510078
    Name: Adj Close, dtype: float64

```python
# Ultimos precios
closes.tail()
```

    Date
    2019-12-24    70.234581
    2019-12-26    71.628067
    2019-12-27    71.600876
    2019-12-30    72.025833
    2019-12-31    72.552094
    Name: Adj Close, dtype: float64

```python
# Graficamos
closes.plot();
```

## 2. Simulación de rendimientos diarios

Recordemos que los precios diarios de cierre ajustados no son un proceso estocástico estacionario, pero los rendimientos diarios si lo son. Por tanto calculamos los rendimientos a partir de los precios de cierre, obtenemos sus propiedades estadísticas muestrales y proyectamos los rendimientos. Luego, obtenemos la proyección de los precios.

Para una sucesión de precios $\{S_t\}_{t=0}^{n}$, el rendimiento simple $R_t$ se define como el el cambio porcentual

$$
R_t=\frac{S_t-S_{t-1}}{S_{t-1}}\approx \ln\left(\frac{S_t}{S_{t-1}}\right)=r_t.
$$
para $t=1,\ldots,n$.

Para el ejemplo en curso, ¿cómo calcular esto?

Además, supusimos que los rendimientos diarios eran una variable aleatoria con distribución normal (que se caracteriza con su media y varianza). Por tanto obtenemos la media y desviación estandar muestrales. Hagamos una función que retorne lo anterior.

```python
# Calcular rendimientos diarios y graficarlos
# ret = (closes - closes.shift()) / closes.shift()
ret = closes.pct_change().dropna()
```

```python
# Primeros rendimientos
ret.head()
```

    Date
    2010-01-05    0.001729
    2010-01-06   -0.015906
    2010-01-07   -0.001849
    2010-01-08    0.006648
    2010-01-11   -0.008821
    Name: Adj Close, dtype: float64

```python
# Gráfico de rendimiento
ret.plot();
```

```python
ret.hist(bins=20);
```

Entonces, suponemos que la diferencia logaritmica de los precios (rendimientos diarios) tiene una distribución normal.

¿Cómo se caracteriza una [distribución normal](https://es.wikipedia.org/wiki/Distribuci%C3%B3n_normal)?

```python
# Calculamos media y desviación estándar
mu, sigma = ret.mean(), ret.std()
mu, sigma
```

    (0.0010870240419151478, 0.016219941536048575)

```python
z = np.linspace(-0.1, 0.1, 100)
f = np.exp(-(z - mu)**2 / (2 * sigma**2)) / (np.sqrt(2 * np.pi * sigma**2))
```

```python
ret.hist(bins=50, density=True)
plt.plot(z, f)
```

Parece ser que nuestra suposición de que los rendimientos distribuyen normal, no es una suposición muy alejada de la realiad.

Habiendo caracterizado los rendimientos diarios como una variable aleatoria normal con la media y la varianza muestral obtenida de los datos del 2016, podemos generar números aleatorios con estas características para simular el comportamiento de los precios de cierre de las acciones en el 2017 (hay un supuesto de que las cosas no cambiarán fundamentalmente).

Sin embargo, cada simulación que hagamos nos conducirá a distintos resultados (los precios siguen evolucionando aleatoriamente). Entonces, lo que haremos es simular varios escenarios para así ver alguna tendencia y tomar decisiones.

Hagamos una una función que simule varios escenarios de rendimientos diarios rendimientos diarios y que devuelva un dataframe con esta simulación.

```python
# Ayuda en la función np.random.normal
help(np.random.normal)
```

    Help on built-in function normal:

    normal(...) method of numpy.random.mtrand.RandomState instance
        normal(loc=0.0, scale=1.0, size=None)

        Draw random samples from a normal (Gaussian) distribution.

        The probability density function of the normal distribution, first
        derived by De Moivre and 200 years later by both Gauss and Laplace
        independently [2]_, is often called the bell curve because of
        its characteristic shape (see the example below).

        The normal distributions occurs often in nature.  For example, it
        describes the commonly occurring distribution of samples influenced
        by a large number of tiny, random disturbances, each with its own
        unique distribution [2]_.

        .. note::
            New code should use the ``normal`` method of a ``default_rng()``
            instance instead; see `random-quick-start`.

        Parameters
        ----------
        loc : float or array_like of floats
            Mean ("centre") of the distribution.
        scale : float or array_like of floats
            Standard deviation (spread or "width") of the distribution. Must be
            non-negative.
        size : int or tuple of ints, optional
            Output shape.  If the given shape is, e.g., ``(m, n, k)``, then
            ``m * n * k`` samples are drawn.  If size is ``None`` (default),
            a single value is returned if ``loc`` and ``scale`` are both scalars.
            Otherwise, ``np.broadcast(loc, scale).size`` samples are drawn.

        Returns
        -------
        out : ndarray or scalar
            Drawn samples from the parameterized normal distribution.

        See Also
        --------
        scipy.stats.norm : probability density function, distribution or
            cumulative density function, etc.
        Generator.normal: which should be used for new code.

        Notes
        -----
        The probability density for the Gaussian distribution is

        .. math:: p(x) = \frac{1}{\sqrt{ 2 \pi \sigma^2 }}
                         e^{ - \frac{ (x - \mu)^2 } {2 \sigma^2} },

        where :math:`\mu` is the mean and :math:`\sigma` the standard
        deviation. The square of the standard deviation, :math:`\sigma^2`,
        is called the variance.

        The function has its peak at the mean, and its "spread" increases with
        the standard deviation (the function reaches 0.607 times its maximum at
        :math:`x + \sigma` and :math:`x - \sigma` [2]_).  This implies that
        normal is more likely to return samples lying close to the mean, rather
        than those far away.

        References
        ----------
        .. [1] Wikipedia, "Normal distribution",
               https://en.wikipedia.org/wiki/Normal_distribution
        .. [2] P. R. Peebles Jr., "Central Limit Theorem" in "Probability,
               Random Variables and Random Signal Principles", 4th ed., 2001,
               pp. 51, 51, 125.

        Examples
        --------
        Draw samples from the distribution:

        >>> mu, sigma = 0, 0.1 # mean and standard deviation
        >>> s = np.random.normal(mu, sigma, 1000)

        Verify the mean and the variance:

        >>> abs(mu - np.mean(s))
        0.0  # may vary

        >>> abs(sigma - np.std(s, ddof=1))
        0.1  # may vary

        Display the histogram of the samples, along with
        the probability density function:

        >>> import matplotlib.pyplot as plt
        >>> count, bins, ignored = plt.hist(s, 30, density=True)
        >>> plt.plot(bins, 1/(sigma * np.sqrt(2 * np.pi)) *
        ...                np.exp( - (bins - mu)**2 / (2 * sigma**2) ),
        ...          linewidth=2, color='r')
        >>> plt.show()

        Two-by-four array of samples from N(3, 6.25):

        >>> np.random.normal(3, 2.5, size=(2, 4))
        array([[-4.49401501,  4.00950034, -1.81814867,  7.29718677],   # random
               [ 0.39924804,  4.68456316,  4.99394529,  4.84057254]])  # random

```python
# Función que simula varios escenarios de rendimientos diarios
def simulate_daily_ret(mu, sigma, n_days, n_scen, start_date):
    dates = pd.date_range(start=start_date, periods=n_days, freq="B")
    return pd.DataFrame(np.random.normal(loc=mu, scale=sigma, size=(n_days, n_scen)), index=dates)
```

```python
# Simulamos 100 escenarios para todo el 2020
n_scen = 10000
n_days = 252
sim_ret = simulate_daily_ret(mu, sigma, n_days, n_scen, "2020-01-01")
sim_ret
```

      0
      1
      2
      3
      4
      5
      6
      7
      8
      9
      ...
      9990
      9991
      9992
      9993
      9994
      9995
      9996
      9997
      9998
      9999

      2020-01-01
      0.008738
      -0.008845
      0.040066
      0.021370
      0.001821
      -0.010181
      -0.002622
      0.026765
      0.016325
      0.010498
      ...
      -0.017501
      0.002387
      -0.009051
      -0.005317
      0.001706
      0.016926
      0.006040
      -0.005398
      -0.010356
      -0.012014

      2020-01-02
      0.008232
      0.013626
      -0.010909
      0.020965
      0.003685
      0.006525
      -0.014277
      -0.015127
      -0.012247
      0.013325
      ...
      0.014123
      -0.024606
      0.013364
      0.010782
      -0.030693
      0.003510
      -0.008952
      0.019691
      0.018919
      -0.008778

      2020-01-03
      0.008326
      0.035665
      0.008803
      0.002930
      -0.000040
      0.016712
      -0.009062
      -0.012211
      0.000393
      0.000814
      ...
      -0.016777
      0.009971
      -0.024705
      0.013176
      -0.024566
      -0.000471
      -0.001864
      0.030411
      0.030684
      0.005335

      2020-01-06
      -0.008996
      -0.021165
      -0.014558
      0.001376
      -0.006585
      0.020577
      0.014396
      0.006326
      -0.000719
      0.013944
      ...
      -0.002572
      -0.026657
      0.005666
      -0.010830
      -0.018633
      0.003968
      -0.011655
      -0.026495
      0.006795
      -0.008058

      2020-01-07
      -0.005691
      0.014258
      -0.006781
      -0.010849
      0.012831
      -0.005000
      0.001367
      -0.003235
      0.010267
      0.024418
      ...
      -0.009788
      -0.000611
      0.012154
      0.035984
      0.017092
      -0.030977
      0.003820
      -0.019825
      -0.012893
      -0.006325

      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...

      2020-12-11
      0.001258
      -0.032347
      -0.000307
      0.008401
      -0.002235
      -0.024198
      -0.026047
      0.025685
      -0.023646
      0.006339
      ...
      -0.022702
      -0.018857
      0.008627
      0.000940
      0.013430
      -0.011348
      0.019693
      -0.009633
      -0.024114
      0.017824

      2020-12-14
      0.002295
      0.016244
      -0.003562
      -0.009071
      0.000194
      0.022425
      0.001758
      0.015052
      0.036937
      -0.007746
      ...
      0.012104
      0.014336
      -0.007830
      0.005824
      -0.003020
      0.009224
      0.014727
      -0.023782
      0.001909
      -0.013512

      2020-12-15
      -0.012160
      -0.025661
      -0.009186
      0.018232
      -0.004926
      -0.007811
      -0.007642
      -0.018289
      -0.030910
      0.000326
      ...
      -0.029289
      0.011128
      0.031227
      0.003419
      0.026377
      0.014330
      -0.008716
      0.003144
      0.012172
      0.007804

      2020-12-16
      -0.006211
      0.010608
      0.025526
      -0.004661
      0.020345
      0.000147
      -0.017293
      -0.007929
      0.012099
      -0.021097
      ...
      -0.000946
      0.002254
      0.008540
      0.010585
      0.021971
      0.002930
      -0.003572
      0.012741
      0.014323
      0.004565

      2020-12-17
      -0.022887
      0.042783
      0.013353
      -0.006809
      0.009481
      0.011124
      -0.016055
      0.014128
      0.013718
      -0.004386
      ...
      0.016047
      -0.033822
      0.000766
      0.005471
      -0.025512
      -0.003677
      -0.011243
      0.001592
      -0.017284
      0.037752

252 rows × 10000 columns

```python
ret.iloc[-250:].plot();
```

```python
# Mostrar
# sim_ret.plot(legend=False)
```

## 3. Proyección de precios de cierre

Por tanto, para calcular los precios, tenemos:

$$\begin{align}
p_i&=p_{i-1}(R_i+1)\\
p_{i+1}&=p_i(R_{i+1}+1)=p_{i-1}(R_i+1)(R_{i+1}+1)\\
&\vdots\\
p_{i+k}&=p_{i-1}(R_i+1)\cdots(R_{i+k}+1).
\end{align}$$

Si hacemos $i=0$ en la última ecuación, tenemos que $p_{k}=p_{-1}\exp(r_0+\cdots+r_{k})$, donde $p_{-1}$ es el último precio reportado en el 2016.

Con los rendimientos, calculamos los precios de cierre...

```python
closes.iloc[-1]
```

    72.55209350585938

```python
sim_closes = closes.iloc[-1] * (1 + sim_ret).cumprod()
```

```python
# Obtenemos los precios
# sim_closes
```

```python
# sim_closes.plot(legend=False);
```

Concatenamos y graficamos...

```python
# Concatenar y graficar
# closes_cat = pd.concat((closes.iloc[-252:], sim_closes), axis=0)
# closes_cat.plot(legend=False)
```

## 4. Probabilidad Precio-Umbral

Ya que tenemos muchos escenarios de precios proyectados, podemos ver varias cosas. Por ejemplo, ¿cuál es la probabilidad de que el precio de cierre sobrepase algún valor umbral en algún momento?

```python
# Umbral de 10% del ultimo precio
K = 1.1 * closes.iloc[-1]
# Fechas
dates = pd.date_range(start="2020-01-01", periods=n_days, freq="B")
# DataFrame de Strikes
strike = pd.DataFrame(K * np.ones((n_days, n_scen)), index=dates)
# Concatenar y graficar
# sim_closes_cat_strikes = pd.concat([closes_cat, strike])
# sim_closes_cat_strikes.plot(legend=False);
```

```python
sim_closes
```

      0
      1
      2
      3
      4
      5
      6
      7
      8
      9
      ...
      9990
      9991
      9992
      9993
      9994
      9995
      9996
      9997
      9998
      9999

      2020-01-01
      73.186069
      71.910364
      75.458959
      74.102565
      72.684200
      71.813472
      72.361859
      74.493972
      73.736513
      73.313766
      ...
      71.282370
      72.725300
      71.895425
      72.166301
      72.675897
      73.780115
      72.990296
      72.160444
      71.800716
      71.680440

      2020-01-02
      73.788564
      72.890199
      74.635767
      75.656130
      72.952052
      72.282072
      71.328719
      73.367097
      72.833431
      74.290697
      ...
      72.289073
      70.935819
      72.856258
      72.944421
      70.445238
      74.039061
      72.336882
      73.581353
      73.159101
      71.051252

      2020-01-03
      74.402957
      75.489820
      75.292800
      75.877805
      72.949106
      73.490067
      70.682370
      72.471207
      72.862091
      74.351155
      ...
      71.076247
      71.643145
      71.056333
      73.905511
      68.714704
      74.004185
      72.202053
      75.819065
      75.403898
      71.430313

      2020-01-06
      73.733626
      73.892099
      74.196664
      75.982180
      72.468753
      75.002264
      71.699917
      72.929650
      72.809726
      75.387918
      ...
      70.893417
      69.733370
      71.458922
      73.105112
      67.434340
      74.297852
      71.360541
      73.810264
      75.916299
      70.854695

      2020-01-07
      73.313986
      74.945653
      73.693538
      75.157858
      73.398574
      74.627225
      71.797941
      72.693749
      73.557269
      77.228732
      ...
      70.199489
      69.690742
      72.327463
      75.735730
      68.586922
      71.996294
      71.633128
      72.346956
      74.937504
      70.406542

      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...

      2020-12-11
      85.674288
      124.907575
      73.747738
      111.076137
      83.637962
      82.883800
      66.827281
      94.360904
      100.346179
      77.674951
      ...
      91.353622
      77.192220
      73.278315
      72.787630
      67.224759
      132.248281
      59.943896
      63.964843
      46.891285
      99.969270

      2020-12-14
      85.870948
      126.936536
      73.485025
      110.068567
      83.654146
      84.742493
      66.944745
      95.781251
      104.052673
      77.073305
      ...
      92.459355
      78.298839
      72.704539
      73.211558
      67.021709
      133.468075
      60.826716
      62.443602
      46.980782
      98.618487

      2020-12-15
      84.826796
      123.679176
      72.809984
      112.075334
      83.242054
      84.080544
      66.433153
      94.029501
      100.836456
      77.098437
      ...
      89.751323
      79.170142
      74.974893
      73.461845
      68.789544
      135.380638
      60.296536
      62.639947
      47.552650
      99.388099

      2020-12-16
      84.299913
      124.991182
      74.668559
      111.552956
      84.935596
      84.092871
      65.284319
      93.283935
      102.056498
      75.471911
      ...
      89.666379
      79.348554
      75.615181
      74.239462
      70.300891
      135.777357
      60.081181
      63.438028
      48.233733
      99.841777

      2020-12-17
      82.370576
      130.338716
      75.665608
      110.793367
      85.740829
      85.028354
      64.236212
      94.601874
      103.456560
      75.140862
      ...
      91.105256
      76.664852
      75.673118
      74.645608
      68.507345
      135.278113
      59.405698
      63.539047
      47.400086
      103.611053

252 rows × 10000 columns

```python
strike
```

      0
      1
      2
      3
      4
      5
      6
      7
      8
      9
      ...
      9990
      9991
      9992
      9993
      9994
      9995
      9996
      9997
      9998
      9999

      2020-01-01
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      ...
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303

      2020-01-02
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      ...
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303

      2020-01-03
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      ...
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303

      2020-01-06
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      ...
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303

      2020-01-07
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      ...
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303

      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...

      2020-12-11
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      ...
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303

      2020-12-14
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      ...
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303

      2020-12-15
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      ...
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303

      2020-12-16
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      ...
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303

      2020-12-17
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      ...
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303
      79.807303

252 rows × 10000 columns

```python
# Comparar cada escenario en cada fecha
comp = sim_closes > strike
# Sumamos para cada fecha y dividimos entre el número de escenarios
prob = comp.sum(axis=1) / n_scen
# Gráfico de probabilidad
prob.plot();
```

___
Entonces, ya aprendimos a bajar datos con pandas-datareader. En específico, a partir de los precios de cierre ajustados obtuvimos los rendimientos diarios.

Suponiendo que los rendimientos diarios son un proceso estocástico estacionario de distribución normal, pudimos caracaterizarlo y proyectar varios escenarios de evolución de los precios (montecarlo).

Con estas proyecciones pudimos calcular probabilidades de sobrepasar cierto precio umbral: toma de decisiones.

# Martes 29 de junio no hay clase
# Viernes 2 de julio clase de reposición: Asesoría para el proyecto (módulo 2)

Created with Jupyter by Esteban Jiménez Rodríguez.