(EIGVALEIGVEC)=

# 2.2 Eigenvalores y eigenvectores

```{admonition} Notas para contenedor de docker:

Comando de docker para ejecución de la nota de forma local:

nota: cambiar `<ruta a mi directorio>` por la ruta de directorio que se desea mapear a `/datos` dentro del contenedor de docker.

`docker run --rm -v <ruta a mi directorio>:/datos --name jupyterlab_optimizacion -p 8888:8888 -d palmoreck/jupyterlab_optimizacion:2.1.4`

password para jupyterlab: `qwerty`

Detener el contenedor de docker:

`docker stop jupyterlab_optimizacion`

Documentación de la imagen de docker `palmoreck/jupyterlab_optimizacion:2.1.4` en [liga](https://github.com/palmoreck/dockerfiles/tree/master/jupyterlab/optimizacion).

```

---

Nota generada a partir de [liga](https://www.dropbox.com/s/s4ch0ww1687pl76/3.2.2.Factorizaciones_matriciales_SVD_Cholesky_QR.pdf?dl=0).

```{admonition} Al final de esta nota el y la lectora:
:class: tip

* Aprenderá las definiciones más relevantes en el tema de eigenvalores y eigenvectores para su uso en el desarrollo de algoritmos en el análisis numérico en la resolución de problemas del álgebra lineal numérica. En específico las definiciones de: diagonalizable o *non defective* y similitud son muy importantes.

* Comprenderá el significado geométrico de calcular los eigenvalores y eigenvectores de una matriz simétrica para una forma cuadrática que define a una elipse.

* Aprenderá cuáles problemas en el cálculo de eigenvalores y eigenvectores de una matriz son bien y mal condicionados.

```

- El problema es calcular eigenvalores... vamos a ver si este cálculo está bien o mal condicionado.

En esta nota **asumimos** que $A \in \mathbb{R}^{n \times n}$.

## Eigenvalor (valor propio o característico)

```{admonition} Definición

El número $\lambda$ (real o complejo) se denomina *eigenvalor* de A si existe $v \in \mathbb{C}^n - \{0\}$ tal que $Av = \lambda v$. El vector $v$ se nombra eigenvector (vector propio o característico) de $A$ correspondiente al eigenvalor $\lambda$.
```

```{admonition} Observación
:class: tip

Observa que si $Av=\lambda v$ y $v \in \mathbb{C}^n-\{0\}$ entonces la matriz $A-\lambda I_n$ es singular por lo que su determinante es cero.

```

```{admonition} Comentarios

* Una matriz con componentes reales puede tener eigenvalores y eigenvectores con valores en $\mathbb{C}$ o $\mathbb{C}^n$ respectivamente.
* El conjunto de eigenvalores se le nombra **espectro de una matriz** y se denota como:

$$\lambda(A) = \{ \lambda | \det(A-\lambda I_n) = 0\}.$$

* El polinomio

$$p(z) = \det(A-zI_n) = (-1)^nz^n + a_{n-1}z^{n-1}+ \dots + a_1z + a_0$$

se le nombra **polinomio característico asociado a $A$** y sus raíces o ceros son los eigenvalores de $A$.

* La multiplicación de $A$ por un eigenvector es un reescalamiento y posible cambio de dirección del eigenvector.
* Si consideramos que nuestros espacios vectoriales se definen sobre $\mathbb{C}$ entonces siempre podemos asegurar que $A$ tiene un eigenvalor con eigenvector asociado. En este caso $A$ tiene $n$ eigenvalores y pueden o no repetirse.

* Se puede probar que el determinante de $A$: $\det(A) = \displaystyle \prod_{i=1}^n \lambda_i$ y la traza de $A$: $tr(A) = \displaystyle \sum_{i=1}^n \lambda_i$.
```

- Cuidado con no confundir los coeficientes del polinomio con la matriz.
- El determinante de la matriz A es el producto de sus eigenvalores.

### Ejemplo

```python
import numpy as np
```

```python
np.set_printoptions(precision=3, suppress=True)
```

```python
A=np.array([[10,-18],[6,-11]])
```

```python
print(A)
```

    [[ 10 -18]
     [  6 -11]]

**En *NumPy* con el módulo [numpy.linalg.eig](https://docs.scipy.org/doc/numpy/reference/generated/numpy.linalg.eig.html) podemos obtener eigenvalores y eigenvectores**

```python
evalue, evector = np.linalg.eig(A)
```

```python
print('eigenvalores:')
print(evalue)
print('eigenvectores:')
print(evector)
```

    eigenvalores:
    [ 1. -2.]
    eigenvectores:
    [[0.894 0.832]
     [0.447 0.555]]

```{margin}

$Av_1 = \lambda_1 v_1$.
```

```python
print('matriz * eigenvector:')
print(A@evector[:,0])
print('eigenvalor * eigenvector:')
print(evalue[0]*evector[:,0])
```

    matriz * eigenvector:
    [0.894 0.447]
    eigenvalor * eigenvector:
    [0.894 0.447]

```{margin}

$Av_2 = \lambda_2 v_2$.
```

- Aquí estamos demostrando la igualdad vista.

```python
print('matriz * eigenvector:')
print(A@evector[:,1])
print('eigenvalor * eigenvector:')
print(evalue[1]*evector[:,1])
```

    matriz * eigenvector:
    [-1.664 -1.109]
    eigenvalor * eigenvector:
    [-1.664 -1.109]

### Ejemplo

Si $v$ es un eigenvector entonces $cv$ es eigenvector donde: $c$ es una constante distinta de cero.

```python
const = -2
const_evector = const*evector[:,0]
print(const_evector)
```

    [-1.789 -0.894]

```{margin}

$cv$ es un eigenvector con eigenvalor asociado $c\lambda$ pues $A(cv) = \lambda(cv)$ se satisface si $Av = \lambda v$ y $c \neq 0$.

```

```python
print('matriz * (constante * eigenvector):')
print(A@const_evector)
print('eigenvalor * (constante * eigenvector):')
print(evalue[0]*const_evector)
```

    matriz * (constante * eigenvector):
    [-1.789 -0.894]
    eigenvalor * (constante * eigenvector):
    [-1.789 -0.894]

- Un punto importante es que el múltiplo de un eigenvector es también un eigenvector.

### Ejemplo

Una matriz con entradas reales puede tener eigenvalores y eigenvectores complejos:

```python
A=np.array([[3,-5],[1,-1]])
```

```python
print(A)
```

    [[ 3 -5]
     [ 1 -1]]

```python
evalue, evector = np.linalg.eig(A)
```

```{margin}

Para $A \in \mathbb{R}^{n \times n}$ se tiene: $\lambda \in \mathbb{C}$ si y sólo si $\bar{\lambda} \in \mathbb{C}$ con $\bar{\lambda}$ el conjugado de $\lambda$.

```

```python
print('eigenvalores:')
print(evalue)
print('eigenvectores:')
print(evector)
```

    eigenvalores:
    [1.+1.j 1.-1.j]
    eigenvectores:
    [[0.913+0.j    0.913-0.j   ]
     [0.365-0.183j 0.365+0.183j]]

```{admonition} Observación
:class: tip

En el ejemplo anterior cada eigenvalor tiene una multiplicidad simple y la multiplicidad geométrica de cada eigenvalor es $1$.

```

### Ejemplo

Los eigenvalores de una matriz diagonal son iguales a su diagonal y sus eigenvectores son los vectores canónicos $e_1, e_2, \dots e_n$.

```python
A = np.diag([2, 2, 2, 2])
```

```python
print(A)
```

    [[2 0 0 0]
     [0 2 0 0]
     [0 0 2 0]
     [0 0 0 2]]

```python
evalue, evector = np.linalg.eig(A)
```

```python
print('eigenvalores:')
print(evalue)
print('eigenvectores:')
print(evector)
```

    eigenvalores:
    [2. 2. 2. 2.]
    eigenvectores:
    [[1. 0. 0. 0.]
     [0. 1. 0. 0.]
     [0. 0. 1. 0.]
     [0. 0. 0. 1.]]

- Aquí vemos que los eigenvalores son uno mismo repetido muchas veces.
- La multiplicidad geométrica se da con las columnas linealmente independientes.
    - En este ejemplo vemos que la multiplicidad geométrica es de 4.
- La multiplicidad algebráica es simplemente ver cuales eigenvalores se repiten.
- La función `eig` que usamos nos acomoda los eigenvalores y eigenvectores en orden.

```{admonition} Definición

La **multiplicidad algebraica** de un eigenvalor es su multiplicidad considerado como raíz/cero del polinomio característico $p(z)$. Si no se repite entonces tal eigenvalor se le nombra de multiplicidad **simple**.

La **multiplicidad geométrica** de un eigenvalor es el número máximo de eigenvectores linealmente independientes asociados a éste.

```

### Ejemplo

Los eigenvalores de una matriz triangular son iguales a su diagonal.

```python
A=np.array([[10,0, -1],
            [6,10, 10],
            [3, 4, 11.0]])
A = np.triu(A)
```

```python
print(A)
```

    [[10.  0. -1.]
     [ 0. 10. 10.]
     [ 0.  0. 11.]]

```python
evalue, evector = np.linalg.eig(A)
```

```{margin}

Observa que el eigenvalor igual a $10$ está repetido dos veces (multiplicidad algebraica igual a $2$) y se tienen dos eigenvectores linealmente independientes asociados a éste (multiplicidad geométrica igual a $2$).
```

```python
print('eigenvalores:')
print(evalue)
print('eigenvectores:')
print(evector)
```

    eigenvalores:
    [10. 10. 11.]
    eigenvectores:
    [[ 1.     0.    -0.099]
     [ 0.     1.     0.99 ]
     [ 0.     0.     0.099]]

- Aquí vemos que dos eigenvectores asociados solo a un eigenvalor.
- Aquí decimos que la multiplicidad geométrica del 10 es 2 porque hay dos vectores linealmente independientes asociados al 10.

**Otro ejemplo:**

```python
A=np.array([[10,18, -1],
            [6,10, 10],
            [3, 4, 11.0]])
A = np.triu(A)
```

```python
print(A)
```

    [[10. 18. -1.]
     [ 0. 10. 10.]
     [ 0.  0. 11.]]

```python
evalue, evector = np.linalg.eig(A)
```

```{margin}

Observa que en este ejemplo el eigenvalor $10$ está repetido dos veces (multiplicidad algebraica es igual a $2$) y sus eigenvectores asociados son linealmente dependientes (multiplicidad geométrica es igual a $1$).
```

```python
print('eigenvalores:')
print(evalue)
print('eigenvectores:')
print(evector)
```

    eigenvalores:
    [10. 10. 11.]
    eigenvectores:
    [[ 1.    -1.     0.998]
     [ 0.     0.     0.056]
     [ 0.     0.     0.006]]

- En este caso hay eigenvectores linealmente dependientes.

### Ejemplo

Un eigenvalor puede estar repetido y tener un sólo eigenvector linealmente independiente:

```python
A = np.array([[2, 1, 0],
              [0, 2, 1],
              [0, 0, 2]])
```

```python
evalue, evector = np.linalg.eig(A)
```

```{margin}

Observa que en este ejemplo el eigenvalor $2$ está repetido tres veces (multiplicidad algebraica es igual a $3$) y sus eigenvectores asociados son linealmente dependientes (multiplicidad geométrica es igual a $1$).
```

```python
print('eigenvalores:')
print(evalue)
print('eigenvectores:')
print(evector)
```

    eigenvalores:
    [2. 2. 2.]
    eigenvectores:
    [[ 1. -1.  1.]
     [ 0.  0. -0.]
     [ 0.  0.  0.]]

- Veremos como regla que la multiplicidad geométrica será menor o igual que la algebráica.

```{admonition} Definición

Si $(\lambda, v)$ es una pareja de eigenvalor-eigenvector de $A$ tales que $Av = \lambda v$ entonces $v$ se le nombra eigenvector derecho. Si $(\lambda, v)$ es una pareja de eigenvalor-eigenvector de $A^T$ tales que $A^Tv = \lambda v$ (que es equivalente a $v^TA=\lambda v^T$) entonces $v$ se le nombra eigenvector izquierdo.
```

```{admonition} Observaciones
:class: tip

* En todos los ejemplos anteriores se calcularon eigenvectores derechos.

* Los eigenvectores izquierdos y derechos para una matriz simétrica son iguales.

```

(DIAGONALIZABLE)=

- Las matrices que tengan eigenvalores con multiplicidad algebráica y geométrica igual les llamaremos "no defectuosas" o "diagonalizable"

## $A$ diagonalizable

```{admonition} Definición

Si $A$ tiene $n$ eigenvectores linealmente independientes entonces $A$ se nombra diagonalizable o *non defective*. En este caso si $x_1, x_2, \dots, x_n$ son eigenvectores de $A$ con $Ax_i = \lambda_i x_i$ para $i=1,\dots,n$ entonces la igualdad anterior se escribe en ecuación matricial como:

$$AX = X \Lambda$$

o bien:

$$A = X \Lambda X^{-1}$$

donde: $X$ tiene por columnas los eigenvectores de $A$ y $\Lambda$ tiene en su diagonal los eigenvalores de $A$.

A la descomposición anterior $A = X \Lambda X^{-1}$ para $A$ diagonalizable o *non defective* se le nombra ***eigen decomposition***.
```

- Esta eigen descomposición la aplicamos solo para las matrices no defectuosas.
- La $X$ y la "lambda mayúscula" son matrices.

```{admonition} Observación
:class: tip

* Si $A = X \Lambda X^{-1}$ entonces $X^{-1}A = \Lambda X^{-1}$ y los renglones de $X^{-1}$ (o equivalentemente las columnas de $X^{-T}$) son eigenvectores izquierdos.

* Si $A = X \Lambda X^{-1}$ y $b = Ax = (X \Lambda X^{-1}) x$ entonces:

$$\tilde{b} = X^{-1}b = X^{-1}  (Ax) = X^{-1} (X \Lambda X^{-1}) x = \Lambda X^{-1}x = \Lambda \tilde{x}.$$

Lo anterior indica que el producto matricial $Ax$ para $A$ diagonalizable es equivalente a multiplicar una matriz diagonal por un vector denotado como $\tilde{x}$ que contiene los coeficientes de la combinación lineal de las columnas de $X$ para el vector $x$ . El resultado de tal multiplicación es un vector denotado como $\tilde{b}$ que también contiene los coeficientes de la combinación lineal de las columnas de $X$ para el vector $b$. En resúmen, si $A$ es diagonalizable o *non defective* la multiplicación $Ax$ es equivalente a la multiplicación por una matriz diagonal $\Lambda \tilde{x}$ (salvo un cambio de bases, ver [Change of basis](https://en.wikipedia.org/wiki/Change_of_basis)).

* Si una matriz $A$ tiene eigenvalores distintos entonces es diagonalizable y más general: si $A$ tiene una multiplicidad geométrica igual a su multiplicidad algebraica de cada eigenvalor entonces es diagonalizable.

```

### Ejemplo

La matriz:

$$A = \left[
\begin{array}{ccc}
1 & -4 & -4\\
8 & -11 & -8\\
-8 & 8 & 5
\end{array}
\right]
$$

es diagonalizable.

```python
A = np.array([[1, -4, -4],
              [8, -11, -8],
              [-8, 8, 5.0]])
```

```python
print(A)
```

    [[  1.  -4.  -4.]
     [  8. -11.  -8.]
     [ -8.   8.   5.]]

```python
evalue, evector = np.linalg.eig(A)
```

```python
print('eigenvalores:')
print(evalue)
```

    eigenvalores:
    [ 1. -3. -3.]

```{margin}

Se verifica que los eigenvectores de este ejemplo es un conjunto linealmente independiente por lo que $A=X\Lambda X^{-1}$.

```

```python
print('eigenvectores:')
print(evector)
```

    eigenvectores:
    [[ 0.333 -0.717 -0.241]
     [ 0.667 -0.02  -0.796]
     [-0.667 -0.697  0.555]]

- Las columnas de esta matriz son linealmente independientes.

```python
X = evector
Lambda = np.diag(evalue)
```

```{margin}

Observa que si $Z$ es desconocida y $X^T Z^T = \Lambda$ entonces $Z^T = X^{-T} \Lambda$ y por tanto $XZ =X\Lambda X^{-1}$.
```

```python
print(X@np.linalg.solve(X.T, Lambda).T)
```

    [[  1.  -4.  -4.]
     [  8. -11.  -8.]
     [ -8.   8.   5.]]

- Este producto es en realidad "X" por "Lambda mayúscula" por "X^-1"
- Se usa el solve porque estamos usando Gauss-Jordan. Estás resolviendo un sistema de ecuaciones lineales para sacar la inversa.

```python
print(A)
```

    [[  1.  -4.  -4.]
     [  8. -11.  -8.]
     [ -8.   8.   5.]]

$A$ es diagonalizable pues: $X^{-1} A X = \Lambda$

- Recordemos que $Lambda$ es una matriz diagonal.
- Los valores de la diagonal son los eigenvalores.
- Obtener los eigenvalores de una matriz diagonal superior también es muy fácil.

```{margin}

Observa que si $Z$ es desconocida y $XZ = A$ entonces $Z = X^{-1}A$ y por tanto $ZX = X^{-1} A X$.
```

```python
print(np.linalg.solve(X, A)@X)
```

    [[ 1.  0. -0.]
     [-0. -3. -0.]
     [ 0. -0. -3.]]

```python
print(Lambda)
```

    [[ 1.  0.  0.]
     [ 0. -3.  0.]
     [ 0.  0. -3.]]

```{admonition} Observación
:class: tip

Observa que **no necesariamente** $X$ en la *eigen decomposition* es una matriz ortogonal.

```

- Si haces producto punto entre primera y segunda columna no necesariamente es cero.

```{margin}

Aquí se toma $X[1:3,1]$ como la primera columna de $X$ y se satisface $X[1:3,1]^TX[1:3,1] = 1$ en este ejemplo pero en general esto no se cumple.

```

```python
X[:,0].dot(X[:,0])
```

    1.0

```{margin}

$X[1:3,1]^TX[1:3,2] \neq 0$ por lo que la primera y segunda columna de $X$ no son ortogonales.

```

```python
X[:,0].dot(X[:,1])
```

    0.2117366284008178

**Eigenvectores derechos:**

```{margin}

`x_1` es la primer columna de $X$: $X[1:3, 1]$ y `lambda_1` el eigenvalor asociado.
```

```python
x_1 = X[:,0]
lambda_1 = Lambda[0,0]
```

```python
print(A@x_1)
```

    [ 0.333  0.667 -0.667]

```{margin}

$Ax_1 = \lambda_1 x_1$.
```

```python
print(lambda_1*x_1)
```

    [ 0.333  0.667 -0.667]

```{margin}

`x_2` es la segunda columna de $X$: $X[1:3, 2]$ y `lambda_2` el eigenvalor asociado.
```

```python
x_2 = X[:,1]
lambda_2 = Lambda[1,1]
```

```python
print(A@x_2)
```

    [2.151 0.061 2.09 ]

```{margin}

$Ax_2 = \lambda_2 x_2$.
```

```python
print(lambda_2*x_2)
```

    [2.151 0.061 2.09 ]

**Eigenvectores izquierdos:**

```{admonition} Observación
:class: tip

Para los eigenvectores izquierdos se deben tomar los renglones de $X^{-1}$ (o equivalentemente las columnas de $X^{-T}$) sin embargo no se utiliza el método [inv](https://numpy.org/doc/stable/reference/generated/numpy.linalg.inv.html) de *NumPy* pues es más costoso computacionalmente y amplifica los errores por redondeo. En su lugar se utiliza el método [solve](https://numpy.org/doc/stable/reference/generated/numpy.linalg.solve.html) y se resuelve el sistema: $X^{-T} z  = e_i$ para $e_i$ $i$-ésimo vector canónico.

```

```python
e1 = np.zeros((X.shape[0],1))
```

```python
e1[0] = 1
```

```python
print(e1)
```

    [[1.]
     [0.]
     [0.]]

```{margin}

`x_inv_1` es el primer renglón de $X^{-1}$: $X^{-1}[1, 1:3]$.
```

- Vemos que es mucho más barato calcular por un lado derecho que por varios lados derechos.

```python
x_inv_1 = np.linalg.solve(X.T, e1)
```

```python
print(x_inv_1)
```

    [[ 3.]
     [-3.]
     [-3.]]

```python
print(A.T@x_inv_1)
```

    [[ 3.]
     [-3.]
     [-3.]]

```{margin}

$A^TX^{-T}[1:3,1] = \lambda_1 X^{-T}[1:3,1]$, `lambda_1` el eigenvalor asociado a `x_inv_1`.
```

```python
print(lambda_1*x_inv_1)
```

    [[ 3.]
     [-3.]
     [-3.]]

```python
e2 = np.zeros((X.shape[0],1))
```

```python
e2[1] = 1
```

```{margin}

`x_inv_2` es el segundo renglón de $X^{-1}$: $X^{-1}[2, 1:3]$.
```

```python
x_inv_2 = np.linalg.solve(X.T, e2)
```

```python
print(x_inv_2)
```

    [[-0.851]
     [-0.13 ]
     [-0.556]]

```python
print(A.T@x_inv_2)
```

    [[2.552]
     [0.391]
     [1.667]]

```{margin}

$A^TX^{-T}[1:3,2] = \lambda_2 X^{-T}[1:3,2]$, `lambda_2` el eigenvalor asociado a `x_inv_2`.
```

```python
print(lambda_2*x_inv_2)
```

    [[2.552]
     [0.391]
     [1.667]]

```{admonition} Ejercicio
:class: tip

¿Es la siguiente matriz diagonalizable?

$$A = \left [
\begin{array}{ccc}
-1 & -1 & -2\\
8 & -11 & -8\\
-10 & 11 & 7
\end{array}
\right]
$$

si es así encuentra su *eigen decomposition* y diagonaliza a $A$.
```

(DESCESP)=

- El cálculo de eigenvalores y eigenvectores nos dan pie a métodos de clusterización, reconocimiento de imágenes, etc.
- La idea es que entendamos qué está pasando detrás de los paquetes que utilizamos.

### Resultado: $A$ simétrica

Si A es simétrica entonces tiene eigenvalores reales. Aún más: $A$ tiene eigenvectores reales linealmente independientes, forman un conjunto ortonormal y se escribe como un producto de tres matrices nombrado **descomposición espectral o *symmetric eigen decomposition***:

$$A = Q \Lambda Q^T$$

donde: $Q$ es una matriz ortogonal cuyas columnas son eigenvectores de $A$ y $\Lambda$ es una matriz diagonal con eigenvalores de $A$.

- Aquí cambiamos la notación de $X$ a $Q$.
- $Q$ es ortogonal.
- Nótese que la inversa de una ortogonal es lo mismo que la traspuesta.

```{admonition} Comentarios

* Por lo anterior una matriz simétrica es **ortogonalmente diagonalizable**, ver {ref}`A diagonalizable <DIAGONALIZABLE>`.

* Los eigenvalores de $A$ simétrica se pueden ordenar:

$$\lambda_n(A) \leq \lambda_{n-1}(A) \leq \dots \leq \lambda_1(A)$$

con:

$\lambda_{max}(A) = \lambda_1(A)$, $\lambda_{min}(A) = \lambda_n(A)$.

* Se prueba para $A$ simétrica:

$$\lambda_{max}(A) = \displaystyle \max_{x \neq 0} \frac{x^TAx}{x^Tx}$$

$$\lambda_{min}(A) = \displaystyle \min_{x \neq 0} \frac{x^TAx}{x^Tx}.$$

por lo tanto:

$$\lambda_{min}(A) \leq \frac{x^TAx}{x^Tx} \leq \lambda_{max}(A) \forall x \neq 0.$$

* $||A||_2 = \displaystyle \max\{|\lambda_1(A)|, |\lambda_n(A)|\}$.

* $||A||_F = \left( \displaystyle \sum_{i=1}^n \lambda_i ^2 \right)^{1/2}$.

* Los valores singulares de $A$ son el conjunto $\{|\lambda_1(A)|, \dots, |\lambda_{n-1}(A)|, |\lambda_n(A)|\}$.
```

- Lo importante aquí es podemos acotar los eigenvalores a un rango.
- También podemos sacar la norma de una matriz a través de sus eigenvalores.

### Ejemplo

Matriz simétrica y descomposición espectral de la misma:

```python
A=np.array([[5,4,2],[4,5,2],[2,2,2]])
```

```python
print(A)
```

    [[5 4 2]
     [4 5 2]
     [2 2 2]]

```python
evalue, evector = np.linalg.eigh(A)
```

- Aquí podemos usar la función especial `eigh` porque es más eficiente.

```{margin}

Como $A$ es simétrica sus eigenvalores son reales y sus eigenvectores forman un conjunto linealmente independiente. Por lo anterior $A$ tiene descomposción espectral.
```

```python
print('eigenvalores:')
print(evalue)
print('eigenvectores:')
print(evector)
```

    eigenvalores:
    [ 1.  1. 10.]
    eigenvectores:
    [[ 0.482  0.569  0.667]
     [-0.727 -0.166  0.667]
     [ 0.49  -0.806  0.333]]

- Vemos que, como esperábamos, los eigenvalores son reales.
- Los eigenvectores forman conjunto linealmente independiente.

```{margin}

$A = Q \Lambda Q^T$
```

```python
print('descomposición espectral:')
Lambda = np.diag(evalue)
Q = evector
print('QLambdaQ^T:')
print(Q@Lambda@Q.T)
print('A:')
print(A)
```

    descomposición espectral:
    QLambdaQ^T:
    [[5. 4. 2.]
     [4. 5. 2.]
     [2. 2. 2.]]
    A:
    [[5 4 2]
     [4 5 2]
     [2 2 2]]

A es diagonalizable pues: $Q^T A Q = \Lambda$

```python
print(Q.T@A@Q)
```

    [[ 1.  0.  0.]
     [-0.  1. -0.]
     [ 0.  0. 10.]]

```python
print(Lambda)
```

    [[ 1.  0.  0.]
     [ 0.  1.  0.]
     [ 0.  0. 10.]]

Ver [numpy.linalg.eigh](https://docs.scipy.org/doc/numpy-1.14.0/reference/generated/numpy.linalg.eigh.html).

## Condición del problema del cálculo de eigenvalores y eigenvectores

La condición del problema del cálculo de eigenvalores y eigenvectores de una matriz, es la sensibilidad de los mismos ante perturbaciones en la matriz, ver {ref}`Condición de un problema y estabilidad de un algoritmo <CPEA>`. Diferentes eigenvalores o eigenvectores de una matriz no necesariamente son igualmente sensibles a perturbaciones en la matriz.

```{admonition} Observación
:class: tip

La condición del problema del cálculo de eigenvalores y eigenvectores de una matriz **no** es igual a la condición del problema de resolver un sistema de ecuaciones lineales, ver {ref}`Número de condición de una matriz <NCM>`.

```

Se prueba que la condición de un eigenvalor **simple** de una matriz $A$ está dado por $\frac{1}{|y^Tx|}$ con $x$ eigenvector derecho, $y$ eigenvector izquierdo de $A$ ambos asociados al eigenvalor simple y normalizados esto es: $x^Tx = y^Ty=1$.

```{admonition} Comentarios

* Para los casos en que: $\lambda$ eigenvalor de $A$ sea simple, $A$ sea diagonalizable, existen eigenvectores izquierdos y derechos asociados a un eigenvalor de $A$ tales que $y^Tx \neq 0$. En tales casos, el análisis del condicionamiento del problema del cálculo de eigenvalores y eigenvectores es más sencillo de realizar que para matrices no diagonalizables o eigenvalores con multiplicidad algebraica mayor a $1$. En particular, los eigenvalores de una matriz simétrica están muy bien condicionados: las perturbaciones en $A$ únicamente perturban a los eigenvalores en una magnitud medida con la norma de las perturbaciones y no depende de otros factores, por ejemplo del número de condición de $A$.

* La sensibilidad de un eigenvector depende de la sensibilidad de su eigenvalor asociado y de la distancia de tal eigenvalor de otros eigenvalores.

* Los eigenvalores que son "cercanos" o aquellos de multiplicidad mayor a $1$ pueden ser mal condicionados y por lo tanto difíciles de calcularse de forma exacta y precisa en especial si la matriz es defectuosa (no diagonalizable). Puede mejorarse el número de condición si se escala el problema por una matriz diagonal y similar a $A$, ver {ref}`similitud <SIMILITUD>`.
```

(SIMILITUD)=

## Similitud

- Si estoy haciendo transformaciones (e.g. hacerla diagonal superior), ¿qué me garantiza que se respeta el espectro?
- Similitud es lo que nos ayuda a evaluar eso.

```{admonition} Definición

Si existe $X \in \mathbb{R}^{n \times n}$ tal que $B = XAX^{-1}$ con $A, B \in \mathbb{R}^{n \times n}$ entonces $A$ y $B$ se nombran similares.

```

```{admonition} Observación
:class: tip

Las matrices que son similares tienen el mismo espectro, de hecho: $Ax = \lambda x$ si y sólo si $By = \lambda y$ para $y=Xx$. Lo anterior quiere decir que los eigenvalores de una matriz son **invariantes** ante cambios de bases o representación en coordenadas distintas.

```

### Ejemplo

Dada la matriz

$$A=
\left [
\begin{array}{cccc}
-1 & -1 & -1 & -1\\
0 & -5 & -16 & -22\\
0 & 3 & 10 & 14\\
4 & 8 & 12 & 14
\end{array}
\right ]
$$

Definir matrices $B_1, B_2$ similares a $A$ a partir de las matrices:

$$
\begin{array}{l}
X_1 =
\left [
\begin{array}{cccc}
2 & -1 & 0 & 0\\
-1 & 2 & -1 & 0\\
0 & -1 & 2 & -1\\
0 & 0 & -1 & 1
\end{array}
\right ],
X_2 = \left [
\begin{array}{cccc}
2 & -1 & 1 & 0\\
-1 & 2 & 0 & 0\\
0 & -1 & 0 & 0\\
0 & 0 & 0 & 1
\end{array}
\right ]
\end{array}
$$

y verificar que los eigenvalores de $A$ son los mismos que los de $B_1, B_2$, esto es, tienen el mismo espectro.

- Las matrices $X$ nos ayudan a preservar el espectro.

```python
A = np.array([[-1, -1 , -1, -1],
              [0, -5, -16, -22],
              [0, 3, 10, 14],
              [4, 8, 12, 14.0]])
```

```python
X1 = np.array([[2, -1, 0, 0],
              [-1, 2, -1, 0],
              [0, -1, 2, -1],
              [0, 0, -1, 1.0]])
```

$B_1 = X_1^{-1}AX_1$:

```{margin}

Calculamos $B1$ explícitamente para revisar qué forma tiene pero no es necesario.

```

- Aqui estamos usando otra vez el `solve` aunque con ganamos en costo computacional.

```python
B1 = np.linalg.solve(X1, A)@X1

```

```python
print(B1)
```

    [[ 1.  2. -0.  0.]
     [ 3.  4. -0.  0.]
     [ 0.  0.  5.  6.]
     [ 0.  0.  7.  8.]]

```python
X2 = np.array([[2, -1, 1, 0],
               [-1, 2, 0, 0],
               [0, -1, 0, 0],
               [0, 0, 0, 1.0]])
```

$B_2 = X_2^{-1}AX_2$:

```{margin}

Calculamos $B2$ explícitamente para revisar qué forma tiene pero no es necesario.

```

```python
B2 = np.linalg.solve(X2, A)@X2

```

```python
print(B2)
```

    [[  1.   2.   0.  -6.]
     [  3.   4.   0. -14.]
     [ -0.   0.  -1.  -3.]
     [  0.   0.   4.  14.]]

**$B1$ y $B2$ son similares a $A$ por tanto tienen los mismos eigenvalores:**

```python
evalue, evector = np.linalg.eig(A)
```

```{margin}

`evalue` son los eigenvalores de $A$.

```

```python
print(evalue)
```

    [13.152  5.372 -0.152 -0.372]

```python
evalue_B1, evector_B1 = np.linalg.eig(B1)
```

```{margin}

`evalue_B1` son los eigenvalores de $B_1$, obsérvese que son iguales a los de $A$ salvo el orden.

```

```python
print(evalue_B1)
```

    [-0.372  5.372 -0.152 13.152]

```python
evalue_B2, evector_B2 = np.linalg.eig(B2)
```

```{margin}

`evalue_B2` son los eigenvalores de $B_2$, obsérvese que son iguales a los de $A$ salvo el orden.

```

```python
print(evalue_B2)
```

    [ 5.372 -0.372 -0.152 13.152]

Los eigenvectores **no son los mismos** pero pueden obtenerse vía multiplicación de matrices:

- Los vectores van a generar el mismo subespacio vectorial.

```{margin}

Elegimos un eigenvalor de $A$.

```

```python
print(evalue[1])
```

    5.372281323269013

```{margin}

Y elegimos el mismo eigenvalor en el *array* `evalue_B1` para $B_1$ que para este ejemplo corresponde al índice $1$ (el mismo que en `evalue` pero podría haber sido otro).

```

```python
print(evalue_B1[1])
```

    5.3722813232690125

```{margin}

Su correspondiente eigenvector en el índice $1$ del *array* `evector_B1`.

```

```python
print(evector_B1[:,1])
```

    [-0.416 -0.909  0.     0.   ]

**$X^{-1}x$ es eigenvector de $B_1$ para $x$ eigenvector de $A$**:

```{margin}

`evector[:,1]` es el eigenvector de $A$ correspondiente al eigenvalor `evalue[1]`. En esta celda se hace el producto $X_1^{-1}x$ y `evector[:,1]` representa a $x$.

```

```python
X1_inv_evector = np.linalg.solve(X1, evector[:,1])
```

```python
print(X1_inv_evector)
```

    [ 0.249  0.543 -0.    -0.   ]

```python
print(B1@(X1_inv_evector))
```

    [ 1.335  2.919 -0.    -0.   ]

```{margin}

Se verifica que $B1(X_1^{-1}x) = \lambda (X_1^{-1}x)$ con $\lambda$ igual al valor `evalue_B1[1]`.

```

```python
print(evalue_B1[1]*(X1_inv_evector))
```

    [ 1.335  2.919 -0.    -0.   ]

```{admonition} Observación
:class: tip

Obsérvese que son los mismos eigenvectores salvo una constante distinta de cero.

```

```python
print(evector_B1)
```

    [[-0.825 -0.416  0.     0.   ]
     [ 0.566 -0.909 -0.     0.   ]
     [ 0.     0.    -0.759 -0.593]
     [ 0.     0.     0.651 -0.805]]

```{margin}

El valor `1.33532534` es la primera entrada de `X1_inv_evector` que es $X_1^{-1}x$ y $x$ eigenvector de $A$. El valor `2.91920903` es la segunda entrada de `X_1_inv_evector`. Las entradas restantes son cercanas a cero.
```

```python
print(1.33532534e+00/evector_B1[0,1])
```

    -3.2101207266138467

```python
print(2.91920903e+00/evector_B1[1,1])
```

    -3.2101207350977647

La constante es aproximadamente $-3.21$:

```{margin}

`evector_B1` fue calculado con la función `eig` pero en la siguiente celda se observa que no es necesario si se tiene un eigenvector de $A$.
```

```python
print(evector_B1[:,1]*(-3.21))
```

    [ 1.335  2.919 -0.    -0.   ]

- Con esto ya verificamos que se trata del mismo eigenvector.

```{margin}

Recuerda que `X_1_inv_evector` es $X_1^{-1}x$ con $x$ eigenvector de $A$ que en este caso se utilizó `evector[:,1]`.

```

```python
print(B1@(X1_inv_evector))
```

    [ 1.335  2.919 -0.    -0.   ]

```{margin}

Se comprueba que $X_1^{-1}x$ es eigenvector de $B$ si $x$ es eigenvector de $A$.
```

```python
print(evalue_B1[1]*(X1_inv_evector))
```

    [ 1.335  2.919 -0.    -0.   ]

Como $A$ tiene eigenvalores distintos entonces es diagonalizable, esto es existen $X_3, \Lambda$ tales que $X_3^{-1} A X_3 = \Lambda$.

- Las matrices que tienen distintos eigenvalores son diagonalizables.

```python
X_3 = evector
Lambda = np.diag(evalue)
```

```python
print(A)
```

    [[ -1.  -1.  -1.  -1.]
     [  0.  -5. -16. -22.]
     [  0.   3.  10.  14.]
     [  4.   8.  12.  14.]]

```python
print(np.linalg.solve(X_3, A)@X_3)
```

    [[13.152 -0.     0.    -0.   ]
     [ 0.     5.372 -0.     0.   ]
     [-0.     0.    -0.152  0.   ]
     [-0.     0.    -0.    -0.372]]

```python
print(Lambda)
```

    [[13.152  0.     0.     0.   ]
     [ 0.     5.372  0.     0.   ]
     [ 0.     0.    -0.152  0.   ]
     [ 0.     0.     0.    -0.372]]

```{admonition} Comentario

**$X_1$ diagonaliza a $A$ por bloques, $X_2$ triangulariza a $A$ por bloques y $X_3$ diagonaliza a $A$.** Las tres matrices representan al mismo operador lineal (que es una transformación lineal del espacio vectorial sobre sí mismo) pero en **coordenadas diferentes**. Un aspecto muy **importante** en el álgebra lineal es representar a tal operador lineal en unas coordenadas lo más simple posible. En el ejemplo la matriz $X_3$, que en sus columnas están los eigenvectores de $A$, ayuda a representarlo de forma muy simple.

```

- Hay algoritmos que transformarán las matrices en lo más sencillo posible. Por ejemplo, una matriz casi diagnoal.
- Vemos que en este caso, el método con las $X_3$ es el mejor porque nos da el resultado más simple.

```{admonition} Observación
:class: tip

$X_3$ es una matriz que diagonaliza a $A$ y tiene en sus columnas a eigenvectores de $A$, si el objetivo es diagonalizar a una matriz **no es necesario** resolver un problema de cálculo de eigenvalores-eigenvectores pues cualquier matriz $X$ no singular puede hacer el trabajo. Una opción es considerar una factorización para $A$ simétrica del tipo $LDL^T$ (que tiene un costo computacional bajo para calcularse), la matriz $L$ no es ortogonal y la matriz $D$ tiene los pivotes que se calculan en la eliminación Gaussiana, ver {ref}` Operaciones y transformaciones básicas del Álgebra Lineal Numérica <OTBALN>`.

```

```{admonition} Ejercicio
:class: tip

Considera

$$A=
\left [
\begin{array}{cccc}
-2 & -1 & -5 & 2\\
-9 & 0 & -8 & -2\\
2 & 3 & 11 & 5\\
3 & -5 & -13 & -7
\end{array}
\right ]
$$

Define $X_1$ tal que $X_1^{-1}AX_1$ sea diagonal.
```

### Ejemplo

```python
import sympy
import matplotlib.pyplot as plt
```

Considérese la siguiente ecuación cuadrática:

$$\frac{\tilde{x}^2}{16} + \frac{\tilde{y}^2}{9} = 1.$$

Con Geometría Analítica sabemos que tal ecuación representa una elipse. Además si

```python
D = sympy.Matrix([[sympy.Rational(1,16), 0],
                  [0, sympy.Rational(1,9)]])
```

```python
sympy.pprint(D)
```

    ⎡1/16   0 ⎤
    ⎢         ⎥
    ⎣ 0    1/9⎦

Entonces el producto

$$\left [ \begin{array}{c}
\tilde{x}\\
\tilde{y}
\end{array}
\right ] ^TD
\left [
\begin{array}{c}
\tilde{x}\\
\tilde{y}
\end{array}
\right ]
$$

es:

```python
x_tilde, y_tilde = sympy.symbols("x_tilde, y_tilde")
x_y_tilde = sympy.Matrix([x_tilde, y_tilde])
```

```python
sympy.pprint((x_y_tilde.T*D*x_y_tilde)[0])
```

           2          2
    x_tilde    y_tilde
    ──────── + ────────
       16         9

```{admonition} Definición

Al producto $x^TAx$ con $A$ simétrica se le nombra forma cuadrática y es un número en $\mathbb{R}$.

```

Rotemos al [eje mayor de la elipse](https://en.wikipedia.org/wiki/Semi-major_and_semi-minor_axes) un ángulo de $\theta = \frac{\pi}{3}$ con una {ref}`transformación de rotación <TROT>` que genera la ecuación matricial:

$$\begin{array}{l}
\left[
\begin{array}{c}
x\\
y
\end{array}
\right ]
=
\left [
\begin{array}{cc}
\cos(\theta) & -\sin(\theta)\\
\sin(\theta) & \cos(\theta)
\end{array}
\right ]
\left[
\begin{array}{c}
\tilde{x}\\
\tilde{y}
\end{array}
\right ]
=
\left [
\begin{array}{cc}
\frac{1}{2} & -\frac{\sqrt{3}}{2}\\
\frac{\sqrt{3}}{2} & \frac{1}{2}
\end{array}
\right ]
\left[
\begin{array}{c}
\tilde{x}\\
\tilde{y}
\end{array}
\right ]
=
Q\left[
\begin{array}{c}
\tilde{x}\\
\tilde{y}
\end{array}
\right ]
\end{array}
$$

donde: $Q$ es la matriz de rotación en sentido contrario a las manecillas del reloj por el ángulo $\theta$.

Esto es:

$$
\begin{eqnarray}
x =\frac{\tilde{x}}{2} - \frac{\tilde{y}\sqrt{3}}{2} \nonumber \\
y =\frac{\tilde{x}\sqrt{3}}{2} + \frac{\tilde{y}}{2} \nonumber
\end{eqnarray}
$$

Despejando $\tilde{x},\tilde{y}$ y sustituyendo en $\frac{\tilde{x}^2}{16} + \frac{\tilde{y}^2}{9} = 1$ resulta en la ecuación:

```python
theta = sympy.pi/3
Q = sympy.Matrix([[sympy.cos(theta), -sympy.sin(theta)],
                  [sympy.sin(theta), sympy.cos(theta)]])
x,y = sympy.symbols("x, y")
x_tilde = (Q.T*sympy.Matrix([x,y]))[0]
y_tilde = (Q.T*sympy.Matrix([x,y]))[1]

sympy.pprint((x_tilde**2/16 + y_tilde**2/9).expand()*576)

```

        2                   2
    57⋅x  - 14⋅√3⋅x⋅y + 43⋅y

- Vemos en este caso que estamos obteniendo la representación de una elipse pero en una forma un poco más compleja.

O equivalentemente el producto

$$\left [ \begin{array}{c}
x\\
y
\end{array}
\right ]^T A
\left [
\begin{array}{c}
x\\
y
\end{array}
\right ]
$$

```{margin}

Esta es una ecuación de una elipse inclinada.

```

```python
x_y = sympy.Matrix([x,y])
A = Q*D*Q.T
sympy.pprint(((x_y.T*A*x_y)[0]).expand()*576)
```

        2                   2
    57⋅x  - 14⋅√3⋅x⋅y + 43⋅y

con $A$ matriz dada por $A=QDQ^T$:

- Vemos en este caso que la $A$ y la $D$ serán similares (mismos eigenvalores)

```{margin}

Observa que $A$ es **simétrica**.

```

```python
sympy.pprint(A)
```

    ⎡  19    -7⋅√3 ⎤
    ⎢ ───    ──────⎥
    ⎢ 192     576  ⎥
    ⎢              ⎥
    ⎢-7⋅√3     43  ⎥
    ⎢──────   ───  ⎥
    ⎣ 576     576  ⎦

En este ejemplo la matriz $Q$ de rotación es la matriz que diagonaliza ortogonalmente a $A$ pues: $Q^TAQ = D.$

Para realizar la **gráfica** de la elipse con *NumPy* observar que:

- Aquí podemos ver que se preserva la elipse pero con distintos eigenvectores.

```{margin}

Estas ecuaciones nos indican que la misma elipse se puede representar en diferentes coordenadas. El cambio de coordenadas se realiza con la matriz $Q$.

```

$$
\begin{eqnarray}
1&=&57x^2 - 14 \sqrt{3}xy + 43 y^2 \nonumber \\
&=& \left [ \begin{array}{c}
x\\
y
\end{array}
\right ]^T A
\left [
\begin{array}{c}
x\\
y
\end{array}
\right ] \nonumber \\
&=& \left [ \begin{array}{c}
x\\
y
\end{array}
\right ]^T QDQ^T \left [
\begin{array}{c}
x\\
y
\end{array}
\right ] \nonumber \\
&=& \left(Q^T \left [ \begin{array}{c}
x\\
y
\end{array}
\right ]\right)^TD\left(Q^T \left [ \begin{array}{c}
x\\
y
\end{array}
\right ]\right) \nonumber \\
&=& \left [ \begin{array}{c}
\tilde{x}\\
\tilde{y}
\end{array}
\right ] ^TD
\left [
\begin{array}{c}
\tilde{x}\\
\tilde{y}
\end{array}
\right ] \nonumber \\
&=& \frac{\tilde{x}^2}{16} + \frac{\tilde{y}^2}{9} \nonumber
\end{eqnarray}
$$

**Gráfica para eigenvalores ordenados de forma decreciente en la diagonal de la matriz $D$.**

```{margin}

Usamos [eig](https://numpy.org/doc/stable/reference/generated/numpy.linalg.eig.html) para el cálculo numérico de eigenvalores, eigenvectores de $A$.

```

```python
A_np = np.array(A.evalf(),dtype = float)
evalue, evector = np.linalg.eig(A_np)
Q_np = evector
D_np = np.diag([evalue[0], evalue[1]])
```

```python
print(Q_np)
```

    [[ 0.866  0.5  ]
     [-0.5    0.866]]

```python
Q = sympy.Matrix([Q[1,:],-Q[0,:]])
sympy.pprint(Q)
```

    ⎡ √3      ⎤
    ⎢ ──   1/2⎥
    ⎢ 2       ⎥
    ⎢         ⎥
    ⎢      √3 ⎥
    ⎢-1/2  ── ⎥
    ⎣      2  ⎦

```python
print(D_np)
```

    [[0.111 0.   ]
     [0.    0.062]]

```python
D = sympy.Matrix([[D[1,1], 0],
                  [0, D[0,0]]])
sympy.pprint(D)
```

    ⎡1/9   0  ⎤
    ⎢         ⎥
    ⎣ 0   1/16⎦

```python
small_value = 1e-4
d1_inv=1/4
d2_inv=1/3
density=1e-2 + small_value
x=np.arange(-1/d1_inv,1/d1_inv,density)
y1=1/d2_inv*np.sqrt(1-(d1_inv*x)**2)
y2=-1/d2_inv*np.sqrt(1-(d1_inv*x)**2)
#transform
x_y1_hat = np.column_stack((x,y1))
x_y2_hat = np.column_stack((x,y2))
apply_Q = lambda vec : np.transpose(Q@np.transpose(vec))
Q_to_vector_1 = apply_Q(x_y1_hat)
Q_to_vector_2 = apply_Q(x_y2_hat)
fig = plt.figure(figsize=(12, 7))
ax1 = fig.add_subplot(1,2,1)
ax2 = fig.add_subplot(1,2,2)
#first plot
ax1.plot(Q_to_vector_1[:,0],Q_to_vector_1[:,1],'g',
         Q_to_vector_2[:,0],Q_to_vector_2[:,1],'g')
ax1.set_title("$57x^2-14\\sqrt{3}xy+43y^2=1$", fontsize=18)
ax1.set_xlabel("Ejes coordenados típicos")
ax1.axhline(color='r')
ax1.axvline(color='r')
#second plot
evector_1 = 1/d1_inv*Q_np[:,0]
evector_2 = 1/d2_inv*Q_np[:,1]
Evector_1 = np.row_stack((np.zeros(2), evector_1))
Evector_2 = np.row_stack((np.zeros(2), evector_2))
ax2.plot(Q_to_vector_1[:,0],Q_to_vector_1[:,1],
         color='g', label = "Elipse")
ax2.plot(Q_to_vector_2[:,0],Q_to_vector_2[:,1],
         color='g', label = "_nolegend_")
ax2.plot(Evector_1[:,0], Evector_1[:,1],
         color='b', label = "Eigenvector Q[:,0], define al semieje mayor principal de la elipse")
ax2.plot(-Evector_1[:,0], -Evector_1[:,1],
         color='b', label = "_nolegend_")
ax2.plot(Evector_2[:,0], Evector_2[:,1],
         color='m', label = "Eigenvector Q[:,1],  define al semieje menor principal de la elipse")
ax2.plot(-Evector_2[:,0], -Evector_2[:,1],
         color='m', label = "_nolegend_")
ax2.set_title("$\\frac{\\tilde{x}^2}{16} + \\frac{\\tilde{y}^2}{9}=1$", fontsize=18)
ax2.set_xlabel("Ejes coordenados rotados")
ax2.legend(bbox_to_anchor=(1, 1))
fig.suptitle("Puntos en el plano que cumplen $z^TAz=1$ y $\\tilde{z}^TD\\tilde{z}=1$")
ax2.grid()
plt.show()
```

- Podemos ver una gran diferencia en la complejidad de las dos ecuaciones que dibujan a la elipse.

```{margin}

Recuerda que $A = Q D Q^T$, $A$ es similar a $D$ matriz diagonal y $Q$ es ortogonal.

```

En la gráfica anterior se representa la rotación de los ejes coordenados definidos por los vectores canónicos $e_1, e_2$ y los rotados definidos por los eigenvectores de $A$. Los eigenvectores de $A$ están en las columnas de $Q$. La primera columna de $Q$ define al eje mayor principal de la elipse y la segunda columna al eje menor principal. La longitud de los semiejes están dados respectivamente por la raíz cuadrada de los recíprocos de los eigenvalores de $A$ que en este caso son: $\frac{1}{9}, \frac{1}{16}$, esto es: $3$ y $4$. Ver por ejemplo: [Principal_axis_theorem](https://en.wikipedia.org/wiki/Principal_axis_theorem), [Diagonalizable_matrix](https://en.wikipedia.org/wiki/Diagonalizable_matrix).

```{admonition} Ejercicio
:class: tip

Rotar los ejes coordenados $45^o$ la ecuación de la elipse:

$$13x^2+10xy+13y^2=72$$

para representar tal ecuación alineando los ejes mayor y menor de la elipse a sus eigenvectores. Encontrar las matrices $Q, D$ tales que $A=QDQ^T$ con $Q$ ortogonal y $D$ diagonal.

```

## Algunos algoritmos para calcular eigenvalores y eigenvectores

Dependiendo de las siguientes preguntas es el tipo de algoritmo que se utiliza:

* ¿Se requiere el cómputo de todos los eigenvalores o de sólo algunos?

* ¿Se requiere el cómputo de únicamente los eigenvalores o también de los eigenvectores?

* ¿$A$ tiene entradas reales o complejas?

* ¿$A$ es de dimensión pequeña y es densa o grande y rala?

* ¿$A$ tiene una estructura especial o es una matriz general?

Para la última pregunta a continuación se tiene una tabla que resume las estructuras en las matrices que son relevantes para problemas del cálculo de eigenvalores-eigenvectores:

|Estructura|Definición|
|:---:|:---:|
|Simétrica|$A=A^T$|
|Ortogonal|$A^TA=AA^T=I_n$|
|Normal|$A^TA = AA^T$|

Ver {ref}`Ejemplos de matrices normales <EJMN>`.

(EJMN)=

### Una opción (inestable numéricamente respecto al redondeo): encontrar raíces del polinomio característico...

```{margin}

Como ejemplo que no es posible expresar las raíces o ceros por una fórmula cerrada que involucren a los coeficientes, operaciones aritméticas y raíces $\sqrt[n]{\cdot}$ para polinomios de grado mayor a $4$, considérese las raíces de $x^5 - x^2 + 1 = 0$.

```

Por definición, los eigenvalores de $A \in \mathbb{R}^{n \times n}$ son las raíces o ceros del polinomio característico $p(z)$ por lo que un método es calcularlas vía tal polinomio. Sin embargo, **no es un buen método** calcular tales raíces o ceros pues para una $n > 4$ [Abel](https://en.wikipedia.org/wiki/Abel%E2%80%93Ruffini_theorem) probó de forma teórica que las raíces en general no son posibles expresarlas por una fórmula cerrada que involucren los coeficientes, operaciones aritméticas y raíces $\sqrt[n]{\cdot}$ . Por lo anterior para calcular eigenvalores de matrices con dimensión $n>4$ requiere de un **método iterativo**.

```{margin}

Como ejemplo de este enunciado considérese:

$$A=\left[
\begin{array}{cc}
1 & \epsilon\\
\epsilon & 1\\
\end{array}
\right]
$$

cuyos eigenvalores son $1 + \epsilon$, $1 - \epsilon$ con $\epsilon$ menor que $\epsilon_{maq}$. Usando aritmética en el SPF se prueba que las raíces del polinomio característico es $1$ de multiplicidad $2$.
```

Además de lo anterior, en ciertas bases de polinomios, por ejemplo $\{1, x, x^2, \dots, x^n\}$, los coeficientes de los polinomios  numéricamente no están bien determinados por los errores por redondeo y las raíces de los polinomios son muy sensibles a perturbaciones de los coeficientes, esto es, es un problema mal condicionado, ver {ref}`condición de un problema y estabilidad de un algoritmo <CPEA>`. Ver [Wilkinson's polynomial](https://en.wikipedia.org/wiki/Wilkinson%27s_polynomial) para un ejemplo.

### Alternativas

Revisaremos en la nota {ref}`Algoritmos y aplicaciones de eigenvalores, eigenvectores de una matriz <AAEVALEVEC>` algunos algoritmos como:

* Método de la potencia y método de la potencia inversa o iteración inversa.

* Iteración por el cociente de Rayleigh.

* Algoritmo QR.

* Método de rotaciones de Jacobi.

---

## Ejemplos de matrices normales

```{sidebar} Descomposición espectral para matrices normales

Las matrices normales generalizan al caso de entradas en $\mathbb{C}$ la diagonalización ortogonal al ser **unitariamente diagonalizables**. $A \in \mathbb{C}^{n \times n}$ es normal si y sólo si $A = U \Lambda U^H$ con $U$ matriz unitaria (generalización de una matriz ortogonal a entradas $\mathbb{C}$), $U^H$ la conjugada transpuesta de $U$ y $\Lambda$ matriz diagonal. Para $A \in \mathbb{R}^{n \times n}$ lo anterior se escribe como: $A$ es simétrica si y sólo si es ortogonalmente diagonalizable: $A = Q \Lambda Q^T$ (ver {ref}`descomposición espectral <DESCESP>`).

```

$$\begin{array}{l}
\left[
\begin{array}{cc}
1 &-2 \\
2 &1
\end{array}
\right],
\left[
\begin{array}{ccc}
1 &2 & 0\\
0 & 1 & 2\\
2 & 0 & 1
\end{array}
\right]
\end{array}
$$

Otro ejemplo:

$$A =
\left[
\begin{array}{ccc}
1 &1 & 0\\
0 & 1 & 1\\
1 & 0 & 1
\end{array}
\right]
$$

```python
A = np.array([[1, 1, 0],
              [0, 1, 1],
              [1, 0, 1.0]])
```

```python
print(A.T@A)
```

    [[2. 1. 1.]
     [1. 2. 1.]
     [1. 1. 2.]]

```{margin}

Como $A$ es normal entonces se cumple que $AA^T=A^TA$.

```

```python
print(A@A.T)
```

    [[2. 1. 1.]
     [1. 2. 1.]
     [1. 1. 2.]]

```python
evalue, evector = np.linalg.eig(A)
```

```python
print('eigenvalores:')
print(evalue)
```

    eigenvalores:
    [0.5+0.866j 0.5-0.866j 2. +0.j   ]

```{margin}

Se verifica que los eigenvectores de este ejemplo forman un conjunto linealmente independiente pues $A$ es normal.

```

```python
print('eigenvectores:')
print(evector)
```

    eigenvectores:
    [[-0.289+0.5j -0.289-0.5j -0.577+0.j ]
     [-0.289-0.5j -0.289+0.5j -0.577+0.j ]
     [ 0.577+0.j   0.577-0.j  -0.577+0.j ]]

```{margin}

Para una matriz normal $A$ se cumple que es unitariamente diagonalizable y $A = Q \Lambda Q^H$ donde: $Q^H$ es la conjugada transpuesta de $Q$.

```

```python
print('descomposición espectral:')
Lambda = np.diag(evalue)
Q = evector

```

    descomposición espectral:

```python
print('QLambdaQ^H:')
print(Q@Lambda@Q.conjugate().T)

```

    QLambdaQ^H:
    [[ 1.+0.j  1.+0.j -0.+0.j]
     [ 0.+0.j  1.+0.j  1.+0.j]
     [ 1.+0.j -0.+0.j  1.+0.j]]

```python
print(A)
```

    [[1. 1. 0.]
     [0. 1. 1.]
     [1. 0. 1.]]

```{margin}

Observa que $Q^HQ=QQ^H = I_3$ donde: $Q^H$ es la conjugada transpuesta de $Q$.
```

```python
print(Q.conjugate().T@Q)
```

    [[1.+0.j 0.-0.j 0.+0.j]
     [0.+0.j 1.+0.j 0.-0.j]
     [0.-0.j 0.+0.j 1.+0.j]]

```{admonition} Observación
:class: tip

El problema del cálculo de eigenvalores para matrices normales es bien condicionado.

```

**Preguntas de comprehensión:**

1)¿Qué son los eigenvalores de una matriz y qué nombre recibe el conjunto de eigenvalores de una matriz?

2)¿Cuántos eigenvalores como máximo puede tener una matriz?

3)¿Qué característica geométrica tiene multiplicar una matriz por su eigenvector?

4)¿A qué se le nombra matriz diagonalizable o *non defective*?

5)¿Cuál es el número de condición del problema de cálculo de eigenvalores con multiplicidad simple para una matriz simétrica?

6)¿Verdadero o Falso?

a.Una matriz es diagonalizable entonces tiene eigenvalores distintos.

b.Una matriz con eigenvalores distintos es diagonalizable.

c.Si $A=XDX^{-1}$ con $X$ matriz invertible entonces en la diagonal de $D$ y en las columnas de $X$ encontramos eigenvalores y eigenvectores derechos de $A$ respectivamente.

7)Describe la descomposición espectral de una matriz simétrica.

8)¿Qué característica tienen las matrices similares?

**Referencias:**

1. M. T. Heath, Scientific Computing. An Introductory Survey, McGraw-Hill, 2002.

2.  G. H. Golub, C. F. Van Loan, Matrix Computations, John Hopkins University Press, 2013.

3. L. Trefethen, D. Bau, Numerical linear algebra, SIAM, 1997.

4. C. Meyer, Matrix Analysis and Applied Linear Algebra, SIAM, 2000.


# A hysteresis mechanism
> The way up is not the same as the way down

- toc: true
- badges: true
- comments: false
- categories: [jupyter]

> youtube: https://youtu.be/xgRDhOifFow

## Introduction

Hysteresis mechanism created by bistability of states.

Energy function:
$$f = u^4 - 2u^2 + hu$$

## The code

```python
# comment these lines if you want interactive mode,
# i.e., if you want to see the animation in real time.
import matplotlib
matplotlib.use('Agg')
```

```python
import matplotlib.pyplot as plt
import numpy as np
import os
import sympy
from scipy.integrate import ode

# learn how to configure: http://matplotlib.sourceforge.net/users/customizing.html
params = {#'backend': 'GTKAgg',
          'legend.handlelength': 2.5,
          'legend.borderaxespad': 0,
          'font.family':'serif',
          'font.size': 18,
          'font.serif':['Times'], # Times, Palatino, New Century Schoolbook, Bookman, Computer Modern Roman
          'ps.usedistiller': 'xpdf',
          'text.usetex': True,
          }

plt.rcParams.update(params)
fig=plt.figure(1,figsize=(9.6,5.4),dpi=100) # 1920x1080 # figsize accepts only inches. if you rather think in cm, change the code yourself.
fig.clf()
fig.subplots_adjust(left=0.07, right=0.93,top=0.90, bottom=0.12,hspace=0.02,wspace=0.10)

Hlim=2.5 # parameter range from -Hlim to Hlim

ax1=fig.add_subplot(121)
ax1.set_xticks([])
ax1.set_yticks([])
ax1.set_xlabel(r'System response',labelpad=12)
ax1.set_ylabel('Energy',labelpad=12)
ax1.axis([-Hlim,Hlim,-5,5])

ax2=fig.add_subplot(122)
ax2.set_xticks([])
ax2.set_yticks([])
ax2.set_xlabel(r'Parameter',labelpad=12)
ax2.set_ylabel(r'System response',labelpad=12)
ax2.yaxis.set_label_position("right")
ax2.axis([-Hlim*1.2,Hlim*1.2,-2,2])

frame_names = []
frame_index = 0
make_movie=True
plt.ion()
```

```python
# energy function and its derivative
f = lambda u,h: u**4-2*u**2+h*u
fprime = lambda u,h: sympy.diff(f(u,h),u)

Hinit=Hlim
ulim=2.5 # system response axis, from -ulim to ulim
u = np.linspace(-ulim,ulim,101)

x = sympy.Symbol('x')

def res(h):
    """System response is one of the real roots
       of the energy function derivative
    """
    # derivative roots, complex
    resp = sympy.solvers.solve(fprime(x,h),x)
    # numerical evaluation
    resp = map(sympy.N,resp)
    # let's check which roots are real
    isreal = len(resp)*[False]
    for i in range(len(resp)):
        # negligible imaginary component
        if np.abs(sympy.functions.im(resp[i]))<1e-15:
            resp[i]=sympy.functions.re(resp[i])
            isreal[i]=True
    resp = np.array(resp)
    # return only real roots
    return resp[np.array(isreal)]

# let's plot stuff, and make a nice movie
#### left plot, ax1 ####
line_func, = ax1.plot(u,f(u,Hinit),lw=2,color='black')
# ball color
ball_color = "blue"
# minimum = the smallest root, the leftmost root
mini = np.min(res(Hinit)) # calculated for initial parameter value
boost = 0.22 # so that ball sits on top of the curve
# plot ball
ball_u, = ax1.plot([mini],[f(mini,Hinit)+boost],'o',
                 markersize=12, markerfacecolor=ball_color)

#### right plot, ax2 ####
# build empty hysteresis array, we will add values
# as simulation progresses
deetype = np.dtype([('h', 'float64'), ('u', 'float64')])
hysteresis = np.array([(Hinit,mini)],dtype=deetype)
line_hyst, = ax2.plot(hysteresis['h'],hysteresis['u'], lw=2,color='black')
ballH, = ax2.plot([hysteresis['h'][-1]],[hysteresis['u'][-1]],'o',
                  markersize=12, markerfacecolor=ball_color)
plt.show()
```

```python
# time to simulate
Total_time = 15 # seconds
fps = 24 # frames per second

# divided by 2 because we ramp down then up
param_vec = np.linspace(Hlim,-Hlim,Total_time*fps/2)

# ramp down
for H in param_vec:
    line_func.set_data(u,f(u,H)) # update line on the left
    mini = np.min(res(H)) # calculate new minimum
    ball_u.set_data([mini],[f(mini,H)+boost]) # update ball on the left
    new_line = np.array([(H,mini)],dtype=deetype) # create new line
    # append new line to hysteresis array
    hysteresis = np.concatenate((hysteresis,new_line))
    line_hyst.set_data(hysteresis['h'],hysteresis['u']) # update line
    ballH.set_data([hysteresis['h'][-1]],[hysteresis['u'][-1]]) # update ball on the right
    fig.canvas.draw()
    if make_movie:
        fname = "_tmp{:05d}.png".format(frame_index)
        frame_names.append(fname)
        fig.savefig(fname,dpi=200)
        frame_index+=1

# ramp up
for H in param_vec[::-1]: # just reverse parameter array
    line_func.set_data(u,f(u,H))
    maxi = np.max(res(H)) # everything is the same, but now with maximum
    ball_u.set_data([maxi],[f(maxi,H)+boost])
    new_line = np.array([(H,maxi)],dtype=deetype)
    hysteresis = np.concatenate((hysteresis,new_line))
    line_hyst.set_data(hysteresis['h'],hysteresis['u'])
    ballH.set_data([hysteresis['h'][-1]],[hysteresis['u'][-1]])
    fig.canvas.draw()
    if make_movie:
        fname = "_tmp{:05d}.png".format(frame_index)
        frame_names.append(fname)
        fig.savefig(fname,dpi=200)
        frame_index+=1

if make_movie:
    frames = "_tmp%5d.png"
    movie_command = "ffmpeg -y -r {:} -i {:} ball.mp4".format(fps,frames)
    os.system(movie_command)

    for fname in frame_names:
        # pass
        os.remove(fname)
```


```python
import matplotlib.pyplot as plt
import pandas as pd
pd.set_option('display.latex.repr', True)
import sympy as sym
sym.init_printing(use_latex=True)
import numpy as np
from IPython.display import Image, Latex
```

# Markdown

## General

Some markdown text.

A list:

- something
- something else

A numbered list

1. something
2. something else

non-ascii characters TODO

This is a long section of text, which we only want in a document (not a presentation)
some text
some more text
some more text
some more text
some more text
some more text
some more text
some more text
some more text

This is an abbreviated section of the document text, which we only want in a presentation

- summary of document text

## References and Citations

References to \cref{fig:example}, \cref{tbl:example}, =@eqn:example_sympy and \cref{code:example_mpl}.

A latex citation.\cite{zelenyak_molecular_2016}

A html citation.(Kirkeminde, 2012)

## Todo notes

\todo[inline]{an inline todo}

Some text.\todo{a todo in the margins}

# Text Output

```python
print("""
This is some printed text,
with a nicely formatted output.
""")
```

    This is some printed text,
    with a nicely formatted output.

# Images and Figures

```python
Image('example.jpg',height=400)
```

## Displaying a plot with its code

A matplotlib figure, with the caption set in the markdowncell above the figure.

The plotting code for a matplotlib figure (\cref{fig:example_mpl}).

```python
plt.scatter(np.random.rand(10), np.random.rand(10),
            label='data label')
plt.ylabel(r'a y label with latex $\alpha$')
plt.legend();
```

# Tables (with pandas)

The plotting code for a pandas Dataframe table (\cref{tbl:example}).

```python
df = pd.DataFrame(np.random.rand(3,4),columns=['a','b','c','d'])
df.a = ['$\delta$','x','y']
df.b = ['l','m','n']
df.set_index(['a','b'])
df.round(3)
```

      a
      b
      c
      d

      0
      $\delta$
      l
      0.391
      0.607

      1
      x
      m
      0.132
      0.205

      2
      y
      n
      0.969
      0.726

# Equations (with ipython or sympy)

```python
Latex('$$ a = b+c $$')
```

$$ a = b+c $$

The plotting code for a sympy equation (=@eqn:example_sympy).

```python
y = sym.Function('y')
n = sym.symbols(r'\alpha')
f = y(n)-2*y(n-1/sym.pi)-5*y(n-2)
sym.rsolve(f,y(n),[1,4])
```


```python
import os
import sys
from matplotlib import pyplot as plt
import numpy as np
from datadrivenpdes.core import equations
from datadrivenpdes.core import grids
import datadrivenpdes as pde
import tensorflow as tf
# tf.enable_eager_execution()
import xarray as xr
```

# First example: Advection diffusion

In this example we'll see how to integrate in time a pre-defined equation. Here we deal with the Advection-Diffusion equation, which describes the time evolution of the concentration $c(x,y,t)$ when it is advected by the velocity field $\vec v(x,y)=(v_x(x,y), v_y(x,y))$ and also undergoes diffusion. The equation reads
$$\frac{\partial c}{\partial t}+\vec{v}\cdot\vec{\nabla}c= D \nabla^2 c$$
where $D$ is the diffusion coefficient. The equation is implemented in various forms in the folder `advection/equations`. Here we choose the Finite Volume formulation.

```python
equation = pde.advection.equations.FiniteVolumeAdvectionDiffusion(diffusion_coefficient=0.1)
grid = grids.Grid.from_period(size=256, length=2*np.pi)
```

Note that we also chose a grid to solve the equation on. The $x$ and $y$ coordinates can be obtained by

```python
x, y = grid.get_mesh()
```

To integrate in time we need an initial state. Equations instances have a `random_state` method that generates a state. The distribution of these initial conditions, when sampled from different seeds, will define the training set for later. Let's sample one random initial state and plot it:

```python
initial_state = equation.random_state(grid, seed=7109179)
fig, axs = plt.subplots(1,2, figsize=(8,4))
axs[0].pcolor(grid.get_mesh()[1],
              grid.get_mesh()[0],
              initial_state['concentration'])
axs[0].set_title('initial concentration')

axs[1].streamplot(grid.get_mesh()[1],
                  grid.get_mesh()[0],
                  initial_state['y_velocity'],initial_state['x_velocity'],
                  density=2)
axs[1].set_title('velocity field');
```

The state of an equation is a `dict` object that contains all relevant fields needed for integrating in time. For advection diffusion these are `concentration`, `x_velocity`, and  `y_velocity`:

```python
print(initial_state.keys())
```

    dict_keys(['concentration', 'x_velocity', 'y_velocity'])

To perform the actual integration we need to choose a method with which to estimate the spatial derivatives of the concentration $c$. The object which estimates the derivatives is called a `model` and there are various models defined in `models.py`. Here we will use a finite difference estimation. Lastly, we need to choose a timestep, which we can ask the equation instance to supply.

```python
time_step = equation.get_time_step(grid)
times = time_step*np.arange(400)
results = pde.core.integrate.integrate_times(
    model=pde.core.models.FiniteDifferenceModel(equation,grid),
    state=initial_state,
    times=times, axis=0)
```

The result is a `dict` object. The `concentration` member of the dict is a tensor whose first axis corresponds to the times at which the solution was evaluated. Here we save the result as an `xarray.DataArray`, which makes it easy to plot.

```python
conc=xr.DataArray(results['concentration'].numpy(),
                  dims=['time', 'x','y'],
                  coords={'time':times, 'x': x[:,0], 'y': y[0]}
                 )
conc[::99].plot(col='time', robust=True, aspect=1)
```

# Defining a new equation

In this section we learn how to define a new equation. We will look at coupled reaction diffusion equations, aka the **Turing Equation**. They describe the evolution of two fields, $A$ and $B$, according to:
$$\begin{align}
\frac{\partial A}{\partial t} &= D_A\nabla^2 A + R_A(A,B)+S\\
\frac{\partial B}{\partial t} &= D_B\nabla^2 B + R_B(A,B)
\end{align}$$
$D_{A,B}$ are the diffusion constants of $A$ and $B$, $R_{A,B}$ are nonlinear reaction terms and $S$ is some constant source term. For example, we'll take
$$\begin{align}
R_A&=A(1-A^2)-\alpha B &
R_B&=\beta(A-B)
\end{align}$$
where $\alpha$ and $\beta$ are model parameters. For simplicity, we'll implelment the equation in one spatial dimension.

## Equation Keys
Because the computational framework is [fully differentiable](https://en.wikipedia.org/wiki/Differentiable_programming), defining an equation requires specifiying in advance what are the quantities that are used in calcualting time derivatives. These are called **keys** and are stored in the `equation` attribute `key_definitions`. In our case, to calculate the time evolution we need $A, B, \partial_{xx}A, \partial_{xx}B $ and $S$.

The auxilliary function `states.StateDefinition` defines these keys. Its input arguments are:
* `name` - The base name of the field. For example, the field $\partial_{xx} A$ is derived from the base field `A`.
* `tensor_indices` - In 2D and above, specify whether a field is a component of a tensor (like $v_x$ and $v_y$ in the advection example).
* `derivative_orders` - Specifies whether a key is a spatial derivative of a different key.
* `offset` - Specifies whether a field is evaluated off the center point of a grid (useful for staggered grids, e.g. finite volume schemes)

For example, in our case the `key_definitions` for $A$ and $\partial_{xx}A$ are

```python
key_definitions = {
    'A': states.StateDefinition(name='A',
                  tensor_indices=(),          # Not used in one dimenional equations
                  derivative_orders=(0,0,0),  # A is not a derivative of anything else
                  offset=(0,0)),              # A is evaluated on the centerpoints of the grid

    'A_xx': states.StateDefinition(name='A',       # A_xx is is derived from A
                     tensor_indices=(),
                     derivative_orders=(2, 0, 0),  # Two derivatives on the x axis
                     offset=(0, 0)),
  }
```
There are two types of keys: those that evolve in time, in our case $A$ and $B$, and constant ones, in our case $S$ (and in the Advection Diffusion example - the velocity field $v$). When defining the equation we need to set the attributes `evolving_keys` and `constant_keys`, which are both python `set`s.

The different keys of an `Equation` instance can be inspected with
```python
equation.all_keys        # in our case: {'A', 'A_xx', 'B', 'B_xx', 'Source'}
equation.base_keys       # in our case: {'A', 'B', 'Source'}
equation.evolving_keys   # in our case: {'A', 'B'}
equation.constant_keys   # in our case: {'Source'}
```

## Defining the equation

Here is a full definition of the equation:

```python
from datadrivenpdes.core import equations
from datadrivenpdes.core import grids
from datadrivenpdes.core import polynomials
from datadrivenpdes.core import states
import scipy as sp

def smooth_random_field(N, amp=0.1, np_random_state=None):
    """
    generates a random field of shape (N,1) and smoothes it a bit
    """
    if np_random_state is None:
      np_random_state = np.random.RandomState()
    noise=np_random_state.randn(N)
    kernel=np.exp(-np.linspace(-6,6,N)**2)
    return amp*sp.ndimage.convolve(noise, kernel, mode='wrap')[:,np.newaxis]

class TuringEquation(equations.Equation):

    DISCRETIZATION_NAME = 'finite_difference'
    METHOD = polynomials.Method.FINITE_DIFFERENCE
    MONOTONIC = False
    CONTINUOUS_EQUATION_NAME = 'Turing'

    key_definitions = {
        'A': states.StateDefinition(name='A',
                      tensor_indices=(),
                      derivative_orders=(0,0,0),
                      offset=(0,0)),
        'A_xx': states.StateDefinition(name='A',
                         tensor_indices=(),
                         derivative_orders=(2, 0, 0),
                         offset=(0, 0)),
        'B': states.StateDefinition(name='B',
                      tensor_indices=(),
                      derivative_orders=(0, 0, 0),
                      offset=(0, 0)),
        'B_xx': states.StateDefinition(name='B',
                         tensor_indices=(),
                         derivative_orders=(2, 0, 0),
                         offset=(0, 0)),
        'Source' : states.StateDefinition(name='Source',
                         tensor_indices=(),
                         derivative_orders=(0, 0, 0),
                         offset=(0, 0)),
    }
    evolving_keys = {'A', 'B'}
    constant_keys = {'Source'}

    def __init__(self, alpha, beta, D_A, D_B, timestep=1e-4):
        self.alpha = alpha
        self.beta = beta
        self.D_A = D_A
        self.D_B = D_B
        self._timestep = timestep
        super().__init__()

    def time_derivative(
          self, grid, A, A_xx, B, B_xx, Source):
        """See base class."""
        rA = self.reaction_A(A, B)
        rB = self.reaction_B(A, B)
        diff_A = self.D_A * A_xx
        diff_B = self.D_B * B_xx
        return {'A': rA + diff_A + Source,
                'B': rB + diff_B,}

    def reaction_A(self, A, B):
        return A - (A ** 3) - B + self.alpha

    def reaction_B(self, A, B):
        return (A - B) * self.beta

    def get_time_step(self, grid):
        return self._timestep

    def random_state(self, grid, seed=None, dtype=tf.float32):
        if seed is None:
            R = np.random.RandomState()
        else:
            R = np.random.RandomState(seed=seed)

        state = {
          'A': smooth_random_field(N=grid.size_x, np_random_state=R),
          'B': smooth_random_field(N=grid.size_x, np_random_state=R),
          'Source': smooth_random_field(N=grid.size_x, np_random_state=R),
        }

        state = {k: tf.cast(v, dtype) for k, v in state.items()}
        return state
```

Now we can generate a random state and evolve it in time

```python
eq = TuringEquation(alpha=-0.0001, beta=10, D_A=1, D_B=30)
NX=100
NY=1 # 1D can be obtained by haveing a y dimension of size 1
LX=200
grid = grids.Grid(NX, NY, step=LX/NX)
x, y=grid.get_mesh()

initial_state = eq.random_state(grid=grid, seed=12345)
times = eq._timestep*np.arange(0, 1000, 20)
model = pde.core.models.FiniteDifferenceModel(eq,grid)
res = pde.core.integrate.integrate_times(
    model=model,
    state=initial_state,
    times=times, axis=0)
```

```python
fig, axs=plt.subplots(1,2, figsize=(10,5), sharey=True)
for ax, k in zip(axs, ['A','B']):
    ax.pcolormesh(x.flat, times, res[k].numpy()[...,0], cmap='RdBu')
    ax.set_title(k)
    ax.set_xlabel('x')
axs[0].set_ylabel('time')
fig.tight_layout()
```


# 베타 분포

베타 분포(Beta  distribution)는 다른 확률 분포와 달리 자연계에 존재하는 데이터의 분포를 묘사하기 보다는 베이지안 추정의 결과를 묘사하기위한 목적으로 주로 사용된다. 베이지안 추정(Bayesian estimation)은 추정하고자 하는 모수의 값을 하나의 숫자로 나타내는 것이 아니라 분포로 묘사한다.

베타 분포의 확률 밀도 함수는 $a$와 $b$라는 두 개의 모수(parameter)를 가지며 수학적으로 다음과 같이 정의된다.

$$
\begin{align}
\text{Beta}(x;a,b)
& = \frac{\Gamma(a+b)}{\Gamma(a)\Gamma(b)}\, x^{a-1}(1-x)^{b-1}
\end{align}
$$

이 식에서
$$
\Gamma(a) = \int_0^\infty  x^{a-1} e^{-x}\, dx
$$

베타 분포의 확률 밀도 함수는 그림에서 볼 수 있듯이 0부터 1까지만의 값을 가진다. 이러한 함수를 finite support를 가진다고 한다.

```python
xx = np.linspace(0, 1, 1000)
plt.subplot(221)
plt.fill(xx, sp.stats.beta(1.0001, 1.0001).pdf(xx)); plt.ylim(0, 6)
plt.title("(A) a=1, b=1")
plt.subplot(222)
plt.fill(xx, sp.stats.beta(4, 2).pdf(xx)); plt.ylim(0, 6)
plt.title("(B) a=4, b=2, mode={0}".format((4-1)/(4+2-2)))
plt.subplot(223)
plt.fill(xx, sp.stats.beta(8, 4).pdf(xx)); plt.ylim(0, 6)
plt.title("(C) a=8, b=4, mode={0}".format((8-1)/(8+4-2)))
plt.subplot(224)
plt.fill(xx, sp.stats.beta(30, 12).pdf(xx)); plt.ylim(0, 6)
plt.title("(D) a=30, b=12, mode={0}".format((30-1)/(30+12-2)))
plt.tight_layout()
plt.show()
```

위 그림이 베이지안 추정 결과라면 각각은 모수에 대해 다음과 같이 추정한 것과 같다.

* (A): 추정할 수 없다. (정보가 없음)
* (B): 모수값이 0.75일 가능성이 가장 크다. (정확도 낮음)
* (C): 모수값이 0.70일 가능성이 가장 크다. (정확도 중간)
* (D): 모수값이 0.725일 가능성이 가장 크다. (정확도 높음)

베타 분포의 기댓값, 최빈값, 분산은 각각 다음과 같다.

* 기댓값

$$E[x] = \dfrac{a}{a+b}$$

* 최빈값
$$\dfrac{a - 1}{a+b - 2}$$

* 분산
$$\text{Var}[x] = \dfrac{ab}{(a+b)^2(a+b+1)}$$

10. 기초 확률론3 - 확률 분포 모형/08. 베타 분포 (파이썬 버전).ipynb


Sam Lam
Thomas Verduyn

Project D: Computer exercise for WI4201
Solving the convection-diffusion equation using upwind finite
differences

```python
# setup problem parameters/functions
import numpy as np
import matplotlib.pyplot as plt
import scipy.sparse
import sympy
import copy

def system_solver(N,e): # sets up system Au=f and solves it
    # Initial values
    h=1/N # nr of lines

    # Constant values - Boundary conditions
    u0=1
    unp1=0

    # Discretisation
    A=scipy.sparse.diags([-e/h-1, 2*e/h+1, -e/h], [-1, 0, 1], shape=(N-1, N-1)).toarray()
    f=np.zeros(N-1)
    f[0]=e/h+1  # bring bc to rhs
    un=np.linalg.inv(A)@f
    return np.concatenate(([u0],un,[unp1])), A, f

def system_solver2(N,e):
    # Initial values
    h=1/N

    # Constant values - Boundary conditions
    u0=1
    unp1=0

    # Discretisation
    A=scipy.sparse.diags([-e/h-1, 2*e/h+1, -e/h], [-1, 0, 1], shape=(N-1, N-1)).toarray()
    A=np.vstack((np.zeros((1,N-1)),A,np.zeros((1,N-1))))
    A=np.hstack((np.zeros((N+1,1)),A,np.zeros((N+1,1))))
    A[0,0]=u0
    A[-1,-1]=unp1
    print(A)
    f=np.zeros(N+1)
    f[0]=e/h+1
    un=np.linalg.inv(A)@f
    return np.concatenate(([u0],un,[unp1])), A, f

def testfunc(N,e):
    x=np.linspace(0,1,N+1)
    return (np.exp(x/e)-np.exp(1/e))/(1-np.exp(1/e))

#system_solver2(3,0.5)
```

Problem 2

```python
N=25
#%matplotlib notebook
eps = np.linspace(1e-2,1,11)
for e in eps:
    plt.plot(np.linspace(0,1,N+1),system_solver(N,e)[0],label=str(e),ls='dotted')
    plt.plot(np.linspace(0,1,N+1),testfunc(N,e),label='testfunc'+str(e))
#    plt.plot(np.linspace(0,1,N+2),(testfunc(N+2,e)-system_solver(N,e)[0])/testfunc(N+2,e),label=str(e))
#plt.legend()
plt.show()
```

Problem 2

```python
n=5
eps=0.01
error=[]
N_lst=[]
for i in range(4,n+4):
    N=2**i
    N_lst.append(N)
    un=system_solver(N,eps)
    error.append(max(np.abs(un[0]-testfunc(N,eps))))
plt.plot(1/np.array(N_lst),error) # is not linear
plt.plot([1/N_lst[0],1/N_lst[-1]],[error[0],error[-1]],marker='x')
#plt.plot(range(5),error)
#plt.yscale('log')
print (1/np.array(N_lst))

```

Problem 3

To prove whether matrix A is symmetric or not we can use the transpose

```python
A=system_solver(5,0.5)[1]
np.allclose(A,A.T)
print(sympy.Matrix(A).rref())
print(np.linalg.eig(A)[0])
print(np.linalg.inv(A))
np.any(np.linalg.inv(A)>=0)  # Check if A_ij>=0
```

    (Matrix([
    [1, 0, 0, 0],
    [0, 1, 0, 0],
    [0, 0, 1, 0],
    [0, 0, 0, 1]]), (0, 1, 2, 3))
    [10.78620908  7.82816919  1.21379092  4.17183081]
    [[0.25961117 0.15933343 0.08770648 0.03654436]
     [0.2230668  0.38240023 0.21049554 0.08770648]
     [0.17190469 0.29469376 0.38240023 0.15933343]
     [0.10027774 0.17190469 0.2230668  0.25961117]]

    True

Problem 4

```python
N=100
#%matplotlib notebook
eps=1
A=system_solver(N,eps)[1]
ev, ef=np.linalg.eig(A)
plt.plot(np.linspace(0,1,N-1),ef[:,::25])
#print(np.linspace(1/(N-1),1-1/(N-1),N-1))
plt.show()

#plt.plot(range(1,N),ev)
#plt.hist(ev,bins=len(ev))
#plt.show()
```

Problem 5

```python
N=15
A=system_solver(N,0.5)[1]
D = np.diag(np.ones(N-1)*A[0,0])
B_jac=np.identity(N-1)-np.matmul(np.linalg.inv(D),A)
ev, ef=np.linalg.eig(B_jac)
#print(np.linalg.eig(A)[0])
print(max(np.abs(ev)))
#print(A)
#print(B_jac)
print(ev)

```

    0.9762352868798289
    [-0.97623529 -0.91175944 -0.80743534 -0.66782243 -0.49902248 -0.30841286
     -0.10432411  0.10432411  0.30841286  0.97623529  0.91175944  0.80743534
      0.66782243  0.49902248]

Problem 6

```python
def jacobi(N,eps,rtol=1e-6):
    u,A,f=system_solver(N,eps)
    u_current=np.zeros(N-1)
    tol=1
    u_new=np.zeros(N-1)
    while tol>rtol:
        for i, row in enumerate(A):
            u_new[i]=(f[i]-(row@u_current-A[i,i]*u_current[i]))/A[i,i]
            #print(row,u_current)
        u_current=u_new
        res=f-A@u_new # res_k+1 = f - A*u_k
        tol=np.max(res)/np.max(f)
        #print(res)
    return u_current

N=8
eps=0.5
u=jacobi(N,eps)
u0,A,f=system_solver(N,eps)

print(u)
print(u0[1:-1])
```

    [0.94959915 0.88659891 0.80784973 0.70941448 0.58637159 0.43256891
     0.24031606]
    [0.94960149 0.88660336 0.80785569 0.70942111 0.58637788 0.43257385
     0.24031881]

Problem 7

```python
def forwardGS(N, eps, rtol=1e-6):
    u, A, f =system_solver(N,eps)
    u_current = np.zeros(N-1)
    tol = 1
    u_new = np.zeros(N-1)
    while tol>rtol:
        for i, row in enumerate(A):
            u_current[i] = (f[i]-row[:i]@u_current[:i]-row[i+1:]@u_current[i+1:])/A[i,i]
        res = f - A@u_current
        tol=np.max(res)/np.max(f)
    return u_current

N=8
eps=0.5
u=forwardGS(N,eps)
u0,A,f=system_solver(N,eps)

#print(np.array(range(8))[2:])
print(u)
print(u0[1:-1])
```

    [0.94959915 0.88659891 0.80784973 0.70941448 0.58637159 0.43256891
     0.24031606]
    [0.94960149 0.88660336 0.80785569 0.70942111 0.58637788 0.43257385
     0.24031881]

Problem 8

```python
def backwardGS(N, eps, rtol=1e-6):
    u, A, f =system_solver(N,eps)
    u_current = np.zeros(N-1)
    tol = 1
    u_new = np.zeros(N-1)
    print(A)
    while tol>rtol:
        for i, row in enumerate(A[::-1]):
            row_inv=row
            u_inv=u_current
            u_current[-(i+1)]=(f[-(i+1)]-row[-(i+1):]@u_current[-(i+1):]-(row_inv[i:])[::-1]@(u_inv[i:])[::-1])/A[i,i]

        res = f - A@u_current
        tol=np.max(res)/np.max(f)
    return u_current

N=5
eps=1
u=backwardGS(N,eps)
u0,A,f=system_solver(N,eps)

#print(u0[:-(0+1)],u0)

print(u)
print(u0[1:-1])

"""row_inv = row[::-1]
            u_inv = u_current[::-1]
            print(row_inv[:i],u_inv[:i])
            #print(row[:-(i+1)],u_current[:-(i+1)])
            print(row_inv[i:],u_inv[i:],row_inv[i:]@u_inv[i:])
            print(u_current,'current')
            print(f[-(i+1)],-row_inv[:i]@u_inv[:i]-row_inv[i:]@u_inv[i:],A[i,i])
            print('\n')
            u_current[-(i+1)] = (f[-(i+1)]-row_inv[:i]@u_inv[:i]-row_inv[i:]@u_inv[i:])/A[i,i]"""
```


## 1
已知摄像机的内参矩阵如下所示:

$\begin{equation}
K=\left[\begin{array}{ccc}
f & & p_{x} \\
& f & p_{y} \\
& & 1
\end{array}\right]
\end{equation}$

### (1)

过光心以及图像上一点u（非齐次）的射线在摄像机坐标系下的坐标是多
少？

$\lambda\left[\begin{array}{c}u_{x} \\ u_{y} \\ 1\end{array}\right]=\left[\begin{array}{ccc}f & & p_{x} \\ & f & p_{y} \\ & & 1\end{array}\right]\left[\begin{array}{c}X \\ Y \\ Z\end{array}\right]$

解得

$\left[\begin{array}{c}X \\ Y \\ Z\end{array}\right]=\lambda\left[\begin{array}{cc}\frac{1}{f}& & -\frac{p_{z}}{f} \\ & \frac{1}{f} & -\frac{p_{y}}{f} \\ & & 1\end{array}\right]\left[\begin{array}{c}u_{x} \\ u_{y} \\ 1\end{array}\right]=\lambda\left[\begin{array}{c}\left(u_{x}-p_{x}\right) / f \\ \left(u_{y}-P_{y}\right) / f \\ 1\end{array}\right]$

### (2)

摄像机内参矩阵K 的逆是什么？

$K^{-1}=\left[\begin{array}{cc}\frac{1}{f} & & -\frac{p_{x}}{f} \\ & \frac{1}{f} & -\frac{p_{y}}{f} \\ & & 1\end{array}\right]$

### (3)

假设以标定板子的左上角为坐标原点，标定板的长宽方向分别对应着世界
坐标系的 x 和 y 方向，z 方向定义与垂直于 xy 平面的方向，尝试计算标定板平面
到摄像机成像平面的单应矩阵，其中摄像机的外参分别为：

$\mathbf{R}=\left[\begin{array}{ccc}-0.4207 & 0.4835 & -0.7676 \\ 0.7320 & -0.3190 & -0.6020 \\ -0.5360 & -0.8152 & -0.2197\end{array}\right] \quad \mathbf{t}=\left[\begin{array}{c}-0.4031 \\ 0.6733 \\ 1.5511\end{array}\right]$

$H=\left[\begin{array}{rr}f & & p_{x} \\ & f & p_{y} \\ & & 1\end{array}\right]\left[\begin{array}{lll}r_{1} & r_{2} & t\end{array}\right]$
### (4)

如果我们将旋转矩阵R的第1,2,3列分别为r1,r2,r3,我们要求r1·r2=0,并且r1·r1=r2·r2。
请问从标定板到摄像机成像平面的透视投影矩阵应该满足什么条件？

$P=\left[\begin{array}{llll}p_{1} & p_{2} & p_{3} & p_{4}\end{array}\right]=\left[\begin{array}{cc}f & & p_{x} \\ & f & p_{y} \\ & & 1\end{array}\right]\left[\begin{array}{llll}r_{1} & r_{2} & r_{3} & t\end{array}\right]$

$r_{1}=K^{-1} p_{1}, r_{2}=K^{-1} p_{2}$

$p_{1}^{T} K^{-T} K^{-1} p_{2}=0$

$p_{1}^{T} K^{-T} K^{-1} p_{1}=p_{2}^{T} K^{-T} K^{-1} p_{2}$

### (5)

如果利用标定板对摄像机的内部参数进行标定，如果知道摄像机在某个姿
态下，标定板到摄像机的单应矩阵为

$\mathrm{H}=\left[\begin{array}{ccc}-55.2766 & 139.7806 & 306.6967 \\ -4.3942 & 103.3933 & 215.5850 \\ -0.1416 & 0.3768 & 1.0000\end{array}\right]$


# Circuito RLC paralelo sem fonte

Jupyter Notebook desenvolvido por [Gustavo S.S.](https://github.com/GSimas)

Circuitos RLC em paralelo têm diversas aplicações, como em projetos de filtros
e redes de comunicação. Suponha que a corrente inicial I0 no indutor e a tensão inicial V0 no capacitor sejam:

\begin{align}
{\Large i(0) = I_0 = \frac{1}{L} \int_{-\infty}^{0} v(t) dt}
\end{align}

\begin{align}
{\Large v(0) = V_0}
\end{align}

Portanto, aplicando a LKC ao nó superior fornece:

\begin{align}
{\Large \frac{v}{R} + \frac{1}{L} \int_{-\infty}^{t} v(\tau) d\tau + C \frac{dv}{dt} = 0}
\end{align}

Extraindo a derivada em relação a t e dividindo por C resulta em:

\begin{align}
{\Large \frac{d^2v}{dt^2} + \frac{1}{RC} \frac{dv}{dt} + \frac{1}{LC} v = 0}
\end{align}

Obtemos a equação característica substituindo a primeira derivada por s e a segunda
por s^2:

\begin{align}
{\Large s^2 + \frac{1}{RC} s + \frac{1}{LC} = 0}
\end{align}

Assim, as raízes da equação característica são:

\begin{align}
{\Large s_{1,2} = -\alpha \pm \sqrt{\alpha^2 - \omega_0^2}}
\end{align}

onde:

\begin{align}
{\Large \alpha = \frac{1}{2RC}, \space \space \space \omega_0 = \frac{1}{\sqrt{LC}} }
\end{align}

## Amortecimento Supercrítico / Superamortecimento (α > ω0)

Quando α > ω0, as raízes da equação característica são reais e negativas. A resposta é:

\begin{align}
{\Large v(t) = A_1 e^{s_1 t} + A_2 e^{s_2 t} }
\end{align}

## Amortecimento Crítico (α = ω0)

Quando α = ω0 as raízes da equação característica são reais e iguais de modo que a resposta seja:

\begin{align}
{\Large v(t) = (A_1 + A_2t)e^{-\alpha t}}
\end{align}

## Subamortecimento (α < ω0)

Quando α < ω0, nesse caso, as raízes são complexas e podem ser expressas como segue:

\begin{align}
{\Large s_{1,2} = -\alpha \pm j\omega_d}
\\
\\{\Large \omega_d = \sqrt{\omega_0^2 - \alpha^2}}
\end{align}

\begin{align}
{\Large v(t) = e^{-\alpha t}(A_1 cos(\omega_d t) + A_2 sen(\omega_d t))}
\end{align}

As constantes A1 e A2 em cada caso podem ser determinadas a partir das
condições iniciais. Precisamos de v(0) e dv(0)/dt.

**Exemplo 8.5**

No circuito paralelo da Figura 8.13, determine v(t) para t > 0, supondo que v(0) = 5 V,
i(0) = 0, L = 1 H e C = 10 mF. Considere os seguintes casos:

R = 1,923 Ω,

R = 5 Ω e

R = 6,25 Ω .

```python
print("Exemplo 8.5")

from sympy import *

m = 10**(-3) #definicao de mili
L = 1
C = 10*m
v0 = 5
i0 = 0

A1 = symbols('A1')
A2 = symbols('A2')
t = symbols('t')

def sqrt(x, root = 2): #definir funcao para raiz
    y = x**(1/root)
    return y

print("\n--------------\n")

## PARA R = 1.923
R = 1.923

print("Para R = ", R)

def resolve_rlc(R,L,C):

    alpha = 1/(2*R*C)
    omega = 1/sqrt(L*C)
    print("Alpha:",alpha)
    print("Omega:",omega)

    s1 = -alpha + sqrt(alpha**2 - omega**2)
    s2 = -alpha - sqrt(alpha**2 - omega**2)

    def rlc(alpha,omega): #funcao para verificar tipo de amortecimento
        resposta = ""
        if alpha > omega:
            resposta = "superamortecimento"
            v = A1*exp(s1*t) + A2*exp(s2*t)
        elif alpha == omega:
            resposta = "amortecimento critico"
            v = (A1 + A2*t)*exp(-alpha*t)
        else:
            resposta = "subamortecimento"
            v = exp(-alpha*t)*(A1*cos(omega_d*t) + A2*sin(omega_d*t))
        return resposta,v

    resposta,v = rlc(alpha,omega)
    print("Tipo de resposta:",resposta)
    print("Resposta v(t):",v)
    print("v(0):",v.subs(t,0))
    print("dv(0)/dt:",v.diff(t).subs(t,0))

    return alpha,omega,s1,s2,resposta,v

alpha,omega,s1,s2,resposta,v = resolve_rlc(R,L,C)

#v(0) = 5 = A1 + A2 -> A2 = 5 - A1
#dv(0)/dt = -2A1 - 50A2
#C*dv(0)/dt + i(0) + v(0)/R = 0
    #0.01*(-2A1 - 50A2) + 0 + 5/1.923 = 0
    #(-2A1 -50(5 - A1)) = -5/(1.923*0.01)
    #48A1 = 250 - 5/(1.923*0.01)
A1 = (250 - 5/(1.923*0.01))/48
print("Constante A1:",A1)
A2 = 5 - A1
print("Constante A2:",A2)
v = A1*exp(s1*t) + A2*exp(s2*t)
print("Resposta v(t):",v,"V")

print("\n--------------\n")

## PARA R = 5
R = 5

A1 = symbols('A1')
A2 = symbols('A2')

print("Para R = ", R)

alpha,omega,s1,s2,resposta,v = resolve_rlc(R,L,C)

#v(t) = (A1 + A2t)e^(-alpha*t)

#v(0) = A1 = 5
A1 = 5
#C*dv(0)/dt + i(0) + v(0)/R = 0
    #0.01(-10A1 + A2) + 0 + 5/5 = 0
    #0.01A2 = -1 + 0.5
A2 = (-1 + 0.5)/0.01

print("Constante A1:",A1)
print("Constante A2:",A2)
v = (A1 + A2*t)*exp(-alpha*t)
print("Resposta v(t):",v,"V")

print("\n--------------\n")

## PARA R = 6.25
R = 6.25

A1 = symbols('A1')
A2 = symbols('A2')

print("Para R = ", R)

omega_d = sqrt(omega**2 - alpha**2)
alpha,omega,s1,s2,resposta,v = resolve_rlc(R,L,C)

#v(t) = e^-(alpha*t)*(A1cos(wd*t) + A2sen(wd*t))

#v(0) = A1 = 5
A1 = 5
#C*dv(0)/dt + i(0) + v(0)/R = 0
    #0.01*(-8A1 + 6A2) + 0 + 5/6.25 = 0
    #-0.4 + 0.06A2 = -5/6.25
A2 = (-5/6.25 + 0.4)/0.06

print("Constante A1:",A1)
print("Constante A2:",A2)
v = exp(-alpha*t)*(A1*cos(omega_d*t) + A2*sin(omega_d*t))
print("Resposta v(t):",v,"V")
```

    Exemplo 8.5

    --------------

    Para R =  1.923
    Alpha: 26.001040041601662
    Omega: 10.0
    Tipo de resposta: superamortecimento
    Resposta v(t): A1*exp(-1.9999133337787*t) + A2*exp(-50.0021667494246*t)
    v(0): A1 + A2
    dv(0)/dt: -1.9999133337787*A1 - 50.0021667494246*A2
    Constante A1: -0.20855000866701326
    Constante A2: 5.208550008667014
    Resposta v(t): 5.20855000866701*exp(-50.0021667494246*t) - 0.208550008667013*exp(-1.9999133337787*t) V

    --------------

    Para R =  5
    Alpha: 10.0
    Omega: 10.0
    Tipo de resposta: amortecimento critico
    Resposta v(t): (A1 + A2*t)*exp(-10.0*t)
    v(0): A1
    dv(0)/dt: -10.0*A1 + A2
    Constante A1: 5
    Constante A2: -50.0
    Resposta v(t): (-50.0*t + 5)*exp(-10.0*t) V

    --------------

    Para R =  6.25
    Alpha: 8.0
    Omega: 10.0
    Tipo de resposta: subamortecimento
    Resposta v(t): A1*exp(-8.0*t)
    v(0): A1
    dv(0)/dt: -8.0*A1
    Constante A1: 5
    Constante A2: -6.666666666666667
    Resposta v(t): 5*exp(-8.0*t) V

**Problema Prático 8.5**

Na Figura 8.13, seja R = 2 Ω, L = 0,4 H, C = 25 mF, v(0) = 0, e i(0) = 50 mA. Determine v(t) para t > 0.

```python
print("Problema Prático 8.5")

R = 2
L = 0.4
C = 25*m
v0 = 0
i0 = 50*m

A1 = symbols('A1')
A2 = symbols('A2')

alpha,omega,s1,s2,resposta,v = resolve_rlc(R,L,C)

#C*dv(0)/dt + i(0) + v(0)/R = 0
    #C*(-10A1 + A2) + i0 + v(0)/2 = 0
    #v(0) = 0 = A1
    #C*A2 = -i0
A2 = -i0/C
A1 = 0

print("Constante A1:",A1)
print("Constante A2:",A2)
v = (A1 + A2*t)*exp(-10.0*t)
print("Resposta v(t):",v,"V")
```

    Problema Prático 8.5
    Alpha: 10.0
    Omega: 10.0
    Tipo de resposta: amortecimento critico
    Resposta v(t): (A1 + A2*t)*exp(-10.0*t)
    v(0): A1
    dv(0)/dt: -10.0*A1 + A2
    Constante A1: 0
    Constante A2: -2.0
    Resposta v(t): -2.0*t*exp(-10.0*t) V

**Exemplo 8.6**

Determine v(t) para t > 0 no circuito RLC da Figura 8.15.

```python
print("Exemplo 8.6")

u = 10**(-6) #definicao de micro
Vs = 40
L = 0.4
C = 20*u

A1 = symbols('A1')
A2 = symbols('A2')

#Para t < 0
v0 = Vs*50/(50 + 30)
i0 = -Vs/(50 + 30)

print("V0:",v0,"V")
print("i0:",i0,"A")

#Para t > 0
#C*dv(0)/dt + i(0) + v(0)/50 = 0
    #20u*dv(0)/dt - 0.5 + 0.5 = 0
    #dv(0)/dt = 0

R = 50
alpha,omega,s1,s2,resposta,v = resolve_rlc(R,L,C)

#v(0) = 25 = A1 + A2
    #A1 = 25 - A2
#dv(0)/dt = -146A1 - 854A2 = 0
    #-146(25 - A2) - 854A2 = 0
    #146A2 - 854A2 = 3650
    #-708A2 = 3650
A2 = -3650/708
A1 = 25 - A2

print("Constante A1:",A1)
print("Constante A2:",A2)

v = A1*exp(s1*t) + A2*exp(s2*t)
print("Resposta v(t):",v,"V")
```

    Exemplo 8.6
    V0: 25.0 V
    i0: -0.5 A
    Alpha: 500.0
    Omega: 353.5533905932738
    Tipo de resposta: superamortecimento
    Resposta v(t): A1*exp(-146.446609406726*t) + A2*exp(-853.553390593274*t)
    v(0): A1 + A2
    dv(0)/dt: -146.446609406726*A1 - 853.553390593274*A2
    Constante A1: 30.15536723163842
    Constante A2: -5.155367231638418
    Resposta v(t): -5.15536723163842*exp(-853.553390593274*t) + 30.1553672316384*exp(-146.446609406726*t) V