```python
import numpy as np  #Importa libreria numerica
import sympy as sym #simbolica
import matplotlib.pyplot as plt #importa matplotlib solo pyplot
import matplotlib.image as mpimg
from sympy.plotting import plot  #para plotear 2 variables
from sympy.plotting import plot3d  # para 3
from sympy.plotting import plot3d_parametric_surface
from IPython.display import Image
import ipympl  #Para importar gestor de imagenes
sym.init_printing() #activa a jupyter para mostrar simbolicamente el output

#%matplotlib widget
%matplotlib inline

#TODOS LOS display() ESTAN COMENTADOS porque github NO muestra bien las expresiónes
```

```python
V1, V2, VD, VC = sym.symbols('V_1, V_2, V_D, V_C')
R1, R2, R3, R4, R5, R = sym.symbols('R1, R2, R3, R4, R5, R')
# Se analiza aprovechando la linealidad y el principio de la superposición,
# analizando cada variable pasivando las otras.
Vo1 = sym.Function('Vo_1')(V1,V2) #Defino viarible simbolica Vo1 como una funcion de V1 y V2
eq_Vo1=sym.Eq(Vo1,(sym.diff(Vo1.subs(V2,0),V1))*V1+(sym.diff(Vo1.subs(V1,0),V2)*V2))
#eq_Vo1 es la variable importante. Tiene 2 partes eq_Vo1.lhs y eq_Vo1.rhs
#eq_Vo1 la parte izquierda lhs es Vo1 , y la parte derecha es la función "expresión matemática simbólica"
#display(eq_Vo1)
sym.pprint(eq_Vo1)
#Además si se muestra en display como Vo1=F(V1,V2)
#Si no hiciera esto e igualo simplemente Vo1=F(V1,V2)
#Al imprimir Vo1 solo muestra F(V1,V2)
#Al usar la variable eq_Vo1 tambien podemos imprimir d(Vo1)/dV1 = F'(V1,V2)
# y usarla para los informes en latex
sym.print_latex(eq_Vo1)

Vo1 = sym.Symbol('Vo_1') #Define simbolicamente Vo1 como una variable. Esto es por el analisis al circuito.
Vo2 = sym.Function('Vo_2')(V1,V2,Vo1)
eq_Vo2 = sym.Eq(Vo2,(sym.diff(Vo2.subs({V2:0,Vo1:0}),V1)*V1
                    +sym.diff(Vo2.subs({V1:0,Vo1:0}),V2)*V2
                    +sym.diff(Vo2.subs({V1:0,V2:0}),Vo1)*Vo1))
#display(eq_Vo2)
sym.pprint(eq_Vo2)
```

                      d                    d
    Vo₁(V₁, V₂) = V₁⋅───(Vo₁(V₁, 0)) + V₂⋅───(Vo₁(0, V₂))
                     dV₁                  dV₂
    \operatorname{Vo_{1}}{\left(V_{1},V_{2} \right)} = V_{1} \frac{d}{d V_{1}} \operatorname{Vo_{1}}{\left(V_{1},0 \right)} + V_{2} \frac{d}{d V_{2}} \operatorname{Vo_{1}}{\left(0,V_{2} \right)}
                           d                       d                        d
    Vo₂(V₁, V₂, Vo₁) = V₁⋅───(Vo₂(V₁, 0, 0)) + V₂⋅───(Vo₂(0, V₂, 0)) + Vo₁⋅────(Vo₂(0, 0, Vo₁))
                          dV₁                     dV₂                      dVo₁

```python
Image(filename='CI.png',width=600)
```

```python
# "Se utiliza la LKC para encontrar las derivadas paciales"
#PRIMERO ANALIZO Vo1(V1,V2=0) y SEGUNDO Vo1(V1=0,V2)

#Ecuecion cuando V2=0
#V1(G4+G2+G1)-Vo_1*G2=0
eq_0=sym.Eq(V1*(1/R1+1/R2+1/R4)-eq_Vo1.lhs.subs(V2,0)*1/R2) # Asi se representa simbolicamente la ec.
#display(eq_0)
sym.pprint(eq_0)#Podria usar metodos de sympy para poner todo en funcion de Vo1
#Pero simplemente despejando se llega Vo1(V1,V2=0) = V1*G2*(G1+G2+G4)
eq_Vo1_V1=sym.Eq(eq_Vo1.lhs.subs(V2,0),sym.apart((V1*(1/R1+1/R2+1/R4)*R2),V1))
#el metodo .apart despeja, aparta, saca factor comun V1
#otro metodo parecido es .collect
#display(eq_Vo1_V1) #Entonces eq_Vo1_V1 representa lhs Vo1(V1,0) y rhs la expresión matematica
sym.pprint(eq_Vo1_V1)
eq_dVo1_V1=sym.Eq(sym.diff(eq_Vo1_V1.lhs,V1),sym.diff(eq_Vo1_V1.rhs,V1))
#defino Variable eq_dVo1_V1 que en lhs tiene d(Vo1(V1,V2=0))/dV1 y en rhs la expresión matemática F'(V1,0)
#display(eq_dVo1_V1)
sym.pprint(eq_dVo1_V1)

#ahora solo remplazo todas las Resis. con R usando el metodo .subs({X:X1,...}) para muchas variables o .subs(X,X1)
#display(eq_dVo1_V1.subs({R1:R,R2:R,R3:R,R4:R,R5:R}))
sym.pprint(eq_dVo1_V1.subs({R1:R,R2:R,R3:R,R4:R,R5:R}))
```

       ⎛1    1    1 ⎞   Vo₁(V₁, 0)
    V₁⋅⎜── + ── + ──⎟ - ────────── = 0
       ⎝R₄   R₂   R₁⎠       R₂
                 V₁⋅(R₁⋅R₂ + R₁⋅R₄ + R₂⋅R₄)
    Vo₁(V₁, 0) = ──────────────────────────
                           R₁⋅R₄
     d                R₁⋅R₂ + R₁⋅R₄ + R₂⋅R₄
    ───(Vo₁(V₁, 0)) = ─────────────────────
    dV₁                       R₁⋅R₄
     d
    ───(Vo₁(V₁, 0)) = 3
    dV₁

```python
# AHORA SE ANALIZA CUANDO V1=0
#La ec. de nodo V2*G1+Vo1*G2=0
eq_1=sym.Eq(V2*(1/R1)+eq_Vo1.lhs.subs(V1,0)*1/R2)
#display(eq_1)
sym.pprint(eq_1)
#Despejo Vo1=F(V1)  Se repite el procedimiento de cuando V2=0 pero con V1
eq_Vo1_V2=sym.Eq(eq_Vo1.lhs.subs(V1,0),sym.apart(-(V2*(1/R1)*R2),V2))
#display(eq_Vo1_V2)
sym.pprint(eq_Vo1_V2)
eq_dVo1_V2=sym.Eq(sym.diff(eq_Vo1_V2.lhs,V2),sym.diff(eq_Vo1_V2.rhs,V2))
#display(eq_dVo1_V2)
sym.pprint(eq_dVo1_V2)
#display(eq_dVo1_V2.subs({R1:R,R2:R,R3:R,R4:R,R5:R}))
sym.pprint(eq_dVo1_V2.subs({R1:R,R2:R,R3:R,R4:R,R5:R}))
```

    Vo₁(0, V₂)   V₂
    ────────── + ── = 0
        R₂       R₁
                 -R₂⋅V₂
    Vo₁(0, V₂) = ───────
                    R₁
     d                -R₂
    ───(Vo₁(0, V₂)) = ────
    dV₂                R₁
     d
    ───(Vo₁(0, V₂)) = -1
    dV₂

```python
#LA ECUACIÓN FINAL ES  Vo1 = Vo1_V1 + Vo1_V2
#Utilizo la variable eq_Vo1 donde en rhs tenia d(Vo1(V1,0))/d(V1) * V1 + d(Vo1 ...
eq_Vo1=sym.Eq(eq_Vo1.lhs,(eq_Vo1_V1.rhs+eq_Vo1_V2.rhs)) # y ahora rhs tiene la expresión matematica resultante
#display(eq_Vo1)
sym.pprint(eq_Vo1)
eq_Vo1_R=sym.Eq(eq_Vo1.lhs,(eq_Vo1.rhs).subs({R1:R,R2:R,R3:R,R4:R,R5:R}))
#display(eq_Vo1_R)
sym.pprint(eq_Vo1_R)
```

                    R₂⋅V₂   V₁⋅(R₁⋅R₂ + R₁⋅R₄ + R₂⋅R₄)
    Vo₁(V₁, V₂) = - ───── + ──────────────────────────
                      R₁              R₁⋅R₄
    Vo₁(V₁, V₂) = 3⋅V₁ - V₂

```python
#Plot 3D y guarda /tmp/Vo1__V1_V2.png
plt=plot3d_parametric_surface( V1, V2, eq_Vo1_R.rhs, (V1, -10,10),(V2, -10,10),
                              show=False, title="%s = %s"%(str(eq_Vo1_R.lhs),str(eq_Vo1_R.rhs)),
                              xlabel="V1", ylabel="V2")
plt.show()
plt.save("./plot/Vo1__V1_V2.png")
#guarda imagen en una carpeta plot para luego usarlo en los informes
# amarillo es el valor mas alto de Vo1
# Violeta es el valor mas bajo de Vo1(V1,V2)
```

```python
#Para representar Vo1=F(VD , VC) simplemente remplazo usando el metodo .subs V1=Vc-Vd/2 y V2=Vc+Vd/2
Vo1 = sym.Function('Vo_1')(VD,VC)
eq_Vo1_Vcd=sym.Eq(Vo1,sym.collect(sym.apart((eq_Vo1.rhs).subs({V1:(VC-(VD/2)),V2:(VC+(VD/2))}),VC),VD))
#Aca uso los dos metodos .collect y .apart para apartar VC y VD
#display(eq_Vo1_Vcd)
sym.pprint(eq_Vo1_Vcd)
eq_Vo1_Vcd_R=sym.Eq(Vo1,(eq_Vo1_Vcd.rhs).subs({R1:R,R2:R,R3:R,R4:R,R5:R}))
#display(eq_Vo1_Vcd_R)
sym.pprint(eq_Vo1_R)
plt=plot3d(eq_Vo1_Vcd_R.rhs, (VD, -10,10),(VC, -11,11),
           title="%s = %s"% (str(eq_Vo1_Vcd_R.lhs),str(eq_Vo1_Vcd_R.rhs)),
           xlabel="VD", ylabel="VC",show=False)
plt.show()
plt.save("./plot/Vo1__VD_VC.png")

```

```python
#Repito el codigo de arriba
Vo1 = sym.Symbol('Vo_1')
Vo2 = sym.Function('Vo_2')(V1,V2,Vo1)
eq_Vo2 = sym.Eq(Vo2,(sym.diff(Vo2.subs({V2:0,Vo1:0}),V1)*V1
                    +sym.diff(Vo2.subs({V1:0,Vo1:0}),V2)*V2
                    +sym.diff(Vo2.subs({V1:0,V2:0}),Vo1)*Vo1))
display(eq_Vo2)
Image(filename='CI.png',width=600)
```

```python
# Ahora se busca Vo2 como cuando V1=0 Vo1~= 0
# porque para cumplir que la corriente de I(R1)+I(R2)=0 Vo1 no puede ser cero
# Entonces se analiza como otra variable y luego se remplaza por la expresión encontrada
# En el analisis de Vo1 no se uso Vo2 como variable ind. porque no afectaba
# En V1=0 la ec. de nodo en 2 no es afectada por Vo2 y V2=0 la ec de nodo en 2 tmp
# encambio ahora si afecta.   Eq: V1=0
Vo1 = sym.Symbol('Vo_1')
Vo2 = sym.Function('Vo_2')(V1,V2,Vo1)

eq_0=sym.Eq(V1*(1/R1)+eq_Vo2.lhs.subs({V2:0, Vo1:0})*1/R3)
#display(eq_0)
sym.pprint(eq_0)
eq_Vo2_V1=sym.Eq(eq_Vo2.lhs.subs({V2:0, Vo1:0}),sym.apart((V1*(1/R1)*-R3),V1))
#display(eq_Vo2_V1)
sym.pprint(eq_Vo2_V1)
eq_dVo2_V1=sym.Eq(sym.diff(eq_Vo2_V1.lhs,V1),sym.diff(eq_Vo2_V1.rhs,V1))
#display(eq_dVo2_V1)
sym.pprint(eq_dVo2_V1)
#display(eq_dVo2_V1.subs({R1:R,R2:R,R3:R,R4:R,R5:R}))
sym.pprint(eq_dVo2_V1.subs({R1:R,R2:R,R3:R,R4:R,R5:R}))
```

    Vo₂(V₁, 0, 0)   V₁
    ───────────── + ── = 0
          R₃        R₁
                    -R₃⋅V₁
    Vo₂(V₁, 0, 0) = ───────
                       R₁
     d                   -R₃
    ───(Vo₂(V₁, 0, 0)) = ────
    dV₁                   R₁
     d
    ───(Vo₂(V₁, 0, 0)) = -1
    dV₁

```python
#ec V2=0 CLARAMENTE se ve que se repite una y otra vez los mismos pasos, metodos, eq.
#Pero ahora se pasivan dos nodos
eq_1=sym.Eq(V2*(1/R1+1/R3+1/R5)-eq_Vo2.lhs.subs({V1:0,Vo1:0})*1/R3)
#display(eq_1)
sym.pprint(eq_1)
eq_Vo2_V2=sym.Eq(eq_Vo2.lhs.subs({V1:0,Vo1:0}),sym.apart((V2*(1/R1+1/R3+1/R5)*R3),V2))
#display(eq_Vo2_V2)
sym.pprint(eq_Vo2_V2)
eq_dVo2_V2=sym.Eq(sym.diff(eq_Vo2_V2.lhs,V2),sym.diff(eq_Vo2_V2.rhs,V2))
#display(eq_dVo2_V2)
sym.pprint(eq_dVo2_V2)
sym.pprint(eq_dVo2_V2.subs({R1:R,R2:R,R3:R,R4:R,R5:R}))

```

       ⎛1    1    1 ⎞   Vo₂(0, V₂, 0)
    V₂⋅⎜── + ── + ──⎟ - ───────────── = 0
       ⎝R₅   R₃   R₁⎠         R₃
                    V₂⋅(R₁⋅R₃ + R₁⋅R₅ + R₃⋅R₅)
    Vo₂(0, V₂, 0) = ──────────────────────────
                              R₁⋅R₅
     d                   R₁⋅R₃ + R₁⋅R₅ + R₃⋅R₅
    ───(Vo₂(0, V₂, 0)) = ─────────────────────
    dV₂                          R₁⋅R₅
     d
    ───(Vo₂(0, V₂, 0)) = 3
    dV₂

```python
#Ahora analizo cuando V1=0 y V2=0
eq_2=sym.Eq(Vo1*(1/R2)+eq_Vo2.lhs.subs({V1:0,V2:0})*1/R3)
sym.pprint(eq_2)
eq_Vo2_Vo1=sym.Eq(eq_Vo2.lhs.subs({V1:0,V2:0}),Vo1*-R3/R2)
sym.pprint(eq_Vo2_Vo1)
eq_dVo2_Vo1=sym.Eq(sym.diff(eq_Vo2_Vo1.lhs,Vo1),sym.diff(eq_Vo2_Vo1.rhs,Vo1))
sym.pprint(eq_dVo2_Vo1)
sym.pprint(eq_dVo2_Vo1.subs({R1:R,R2:R,R3:R,R4:R,R5:R}))
```

    Vo₂(0, 0, Vo₁)   Vo₁
    ────────────── + ─── = 0
          R₃          R₂
                     -R₃⋅Vo₁
    Vo₂(0, 0, Vo₁) = ────────
                        R₂
     d                     -R₃
    ────(Vo₂(0, 0, Vo₁)) = ────
    dVo₁                    R₂
     d
    ────(Vo₂(0, 0, Vo₁)) = -1
    dVo₁

```python
#Vo2 TOTAL Vo2= Vo2_V1 + Vo2_V2 + Vo2_Vo1
eq_Vo2=sym.Eq(eq_Vo2.lhs,(eq_Vo2_V1.rhs+eq_Vo2_V2.rhs+eq_Vo2_Vo1.rhs))
sym.pprint(eq_Vo2)
eq_Vo2_R=sym.Eq(eq_Vo2.lhs,(eq_Vo2.rhs).subs({R1:R,R2:R,R3:R,R4:R,R5:R}))
sym.pprint(eq_Vo2_R)
```

                         R₃⋅Vo₁   R₃⋅V₁   V₂⋅(R₁⋅R₃ + R₁⋅R₅ + R₃⋅R₅)
    Vo₂(V₁, V₂, Vo₁) = - ────── - ───── + ──────────────────────────
                           R₂       R₁              R₁⋅R₅
    Vo₂(V₁, V₂, Vo₁) = -V₁ + 3⋅V₂ - Vo₁

```python
#REMPLAZO Vo1 por el encontrado
eq_Vo2=eq_Vo2.subs(Vo1,eq_Vo1.rhs)#PRIMERO Remplazo porque eq_Vo2.lhs es Vo2(V1,V2,Vo1)
#Ahora va a ser eq_Vo2.lhs Vo2(V1,V2)
Vo2_ = sym.Function('Vo_2')(V1,V2)
eq_Vo2=sym.Eq(Vo2_,sym.collect(sym.apart(eq_Vo2.rhs,V1),V2))
sym.pprint(eq_Vo2)
eq_Vo2_R=sym.Eq(eq_Vo2.lhs,(eq_Vo2.rhs).subs({R1:R,R2:R,R3:R,R4:R,R5:R}))
sym.pprint(eq_Vo2_R)
```

                    V₂⋅(-R₁⋅R₃ - R₁⋅R₅ - 2⋅R₃⋅R₅)   V₁⋅(R₁⋅R₂⋅R₃ + R₁⋅R₃⋅R₄ + 2⋅R₂⋅R₃⋅R₄)
    Vo₂(V₁, V₂) = - ───────────────────────────── - ─────────────────────────────────────
                                R₁⋅R₅                              R₁⋅R₂⋅R₄
    Vo₂(V₁, V₂) = -4⋅V₁ + 4⋅V₂

```python
#Plot 3D y guarda /tmp/Vo2__V1_V2.png
plt=plot3d_parametric_surface( V1, V2, eq_Vo2_R.rhs, (V1, -10,10),(V2, -10,10),
                              show=False, title="%s = %s"%(str(eq_Vo2_R.lhs),str(eq_Vo2_R.rhs)),
                              xlabel="V1", ylabel="V2")
plt.show()
plt.save("./plot/Vo2__V1_V2.png")
```

```python
#LO MISMO QUE PARA Vo1(VD,VC)
Vo2 = sym.Function('Vo_2')(VD,VC)
eq_Vo2_Vcd=sym.Eq(Vo2,sym.collect(sym.apart((eq_Vo2.rhs).subs({V1:-VD/2+VC,V2:+VD/2+VC}),VC),VD))
sym.pprint(eq_Vo2_Vcd)
eq_Vo2_Vcd_R=sym.Eq(Vo2,(eq_Vo2_Vcd.rhs).subs({R1:R,R2:R,R3:R,R4:R,R5:R}))
sym.pprint(eq_Vo2_Vcd_R)

plt=plot3d(eq_Vo2_Vcd_R.rhs, (VD, -10,10),(VC, -11,11),
           title="%s = %s"% (str(eq_Vo2_Vcd_R.lhs),str(eq_Vo2_Vcd_R.rhs)),
           xlabel="VD", ylabel="VC",show=False)
plt.show()
plt.save("./plot/Vo2__VD_VC.png")

```

```python
Image(filename='CI.png',width=600)
```

```python
#TODO LO ANTERIOR ES LA METODOLOGIA PARA CALCULAR A MANO SON TODOS LOS PASOS QUE SE DEBE DE SEGUIR
#PERO NO SE APROVECHA NADA DEL CALCULO COMPUTACIONAL. ESTA RED ACTIVA SE PUEDE CALCULAR SIMULTANEMENTE
#USANDO EL METODO DE NODOS  I=G*V
#se puede resolver usando e metodo de cramer
#o calculando la inversas de G y V=G**-1*I
#o usando metodos de sympy
V1, V2, VD, VC = sym.symbols('V_1, V_2, V_D, V_C')
Va, Vb, Vc, Vd = sym.symbols('Va, Vb, Vc, Vd')
#Defino Va a nodo 2 Vb al nodo 1 Vc al nodo 6 y Vd al nodo 7
#entonces Vd es Vo2 y Vb es Vo1
R1, R2, R3, R4, R5, R = sym.symbols('R1, R2, R3, R4, R5, R')
Vo1 = sym.Function('Vo_1')(V1,V2)
Vo2 = sym.Function('Vo_2')(V1,V2)
#SE DEBEN DE PLANTEAR LAS MISMAS CANTIDAD DE EC. QUE DE NODOS => 4 NODOS 4 Ec.
eq__0=sym.Eq(Va,V1)
eq__1=sym.Eq(Vc,V2)
eq__2=sym.Eq(Va*(1/R4+1/R2+1/R1)-Vb/R2-Vc/R1)
eq__3=sym.Eq(Vc*(1/R5+1/R1+1/R3)-Vb/R5-Va/R1-Vd/R3)
#Metodo de sym.solve([ecuaciones],[Variables a buscar])
res=sym.solve([eq__0,eq__1,eq__2,eq__3],[Va,Vb,Vc,Vd])
display(res)
sym.pprint(res)
```

```python
Vo1_=res[Vb].subs({R1:R,R2:R,R3:R,R4:R,R5:R})
display(sym.simplify(Vo1_))
display(sym.collect(sym.apart((Vo1_).subs({V1:-VD/2+VC,V2:+VD/2+VC}),VC),VD))
```

```python
Vo2_=res[Vd].subs({R1:R,R2:R,R3:R,R4:R,R5:R})
display(sym.simplify(Vo2_))
display(sym.collect(sym.apart((Vo2_).subs({V1:-VD/2+VC,V2:+VD/2+VC}),VC),VD))
```


# **Àlgebra Linear com Python: uma breve introdução**

## Introdução

Este notebook objetiva demonstrar a resolução de problemas comuns da Álgebra Linear utilizando NumPy e SymPy.

Primeiro iremos importar as bibliotecas que iremos utilizar no decorrer deste notebook:

```python
import numpy as np
import sympy
from sympy.matrices import zeros,eye,ones
```

## Definição

Da Álgebra linear sabemos que uma matriz A sobre um corpo K ou, simplesmente, uma matriz A (quando K estiver subentendido) é uma tabela
retangular de escalares, costumeiramente representada no formato que segue:$$A= \left[
\begin{array}{c c c}
a_{11}&\ldots& a_{1n}\\
a_{21}& \ldots& a_{2n}\\
\vdots&\ddots &\vdots\\ a_{m1}&\ldots& a_{mn}
\end{array}\right]
$$

Dizemos que uma matriz com m linhas e n colunas é uma matriz m $\times $ n . O par de
números $m$ e $n$ é dito o tamanho da matriz.

## Tipos especiais de matrizes

Consideremos uma matriz com $m$ linhas e $n$ colunas que denotaremos por $A_{m\times n}$. Apresentaremos brevemente algumas matrizes importantes em Álgebra Linear. Utilizaremos o construtor Matrix() da biblioteca SymPy para definir as matrizes.

1. Matriz Quadrada: matriz em que o número de linhas é igual ao número de colunas ($m=n$).

```python
A = sympy.Matrix(2,2,[-1, 2, 4, 5])
A
```

$\displaystyle \left[\begin{matrix}-1 & 2\\4 & 5\end{matrix}\right]$

2. Matriz Nula: matriz em que todos os elementos são nulos, ou seja, $a_{ij}=0$ para todo $i$ e $j$.

```python
B = sympy.zeros(2,3)
B
```

$\displaystyle \left[\begin{matrix}0 & 0 & 0\\0 & 0 & 0\end{matrix}\right]$

3. Matriz-Coluna:matriz que possui uma única coluna ($A_{m \times 1}$).

```python
C = sympy.Matrix(4,1, [-1, 2, 10, 5])
C
```

$\displaystyle \left[\begin{matrix}-1\\2\\10\\5\end{matrix}\right]$

4. Matriz-Linha: matriz com uma única linha ($A_{1 \times n}$)

```python
x,y,z = sympy.symbols(['x','y','z'])  #transform string em símbolos
```

```python
D = sympy.Matrix(1,3, [x,y,z])
D
```

$\displaystyle \left[\begin{matrix}x & y & z\end{matrix}\right]$

5. Matriz diagonal: matriz quadrada em que qualquer elemento fora da diagonal principal é nulo, isto é, $a_{ij}=0$ para $ i \neq j$.

Assim, dizemos que uma matriz quadrada S $=$$[s_{ij}]$ é diagonal se todos os seus elementos fora da diagonal principal forem
nulos. Às vezes, denotamos uma tal matriz por S $=$diag $s_{11},s_{22}, ..., s_{nn}$  onde alguns $s_{ii}$, ou todos, podem ser nulos.

```python
E = sympy.diag(1, -2, -7, 4)
E
```

$\displaystyle \left[\begin{matrix}1 & 0 & 0 & 0\\0 & -2 & 0 & 0\\0 & 0 & -7 & 0\\0 & 0 & 0 & 4\end{matrix}\right]$

5. Matriz identidade: matriz em que $a_{ij}=1$ ,  $\forall i=j$ e $a_{ij}=0$, $\forall i \neq j$. Assim, a matriz identidade, ou matriz unitária, de ordem $n$, denotada por $I{_n}$ ou, simplesmente, por $I$, é a matriz quadrada
com 1 na diagonal principal e 0 em todas as demais entradas. A matriz identidade $I$ é análoga ao escalar $1$, pois,
dada qualquer matriz $A_{m \times n}$, temos que $\mathbf{AI =IA = A}$. Para uma matriz $B_{n \times 1}$, verifica-se $\mathbf{IB=B}$

```python
I = sympy.eye(4)
I
```

$\displaystyle \left[\begin{matrix}1 & 0 & 0 & 0\\0 & 1 & 0 & 0\\0 & 0 & 1 & 0\\0 & 0 & 0 & 1\end{matrix}\right]$

6. Matriz triangular superior: matriz quadrada em que todos os elementos abaixo da diagonal principal são nulos, ou seja, $a_{ij}=0$ se $i>j$.

```python
D = sympy.Matrix(np.triu([2,-1,4],k=0))
D
```

$\displaystyle \left[\begin{matrix}2 & -1 & 4\\0 & -1 & 4\\0 & 0 & 4\end{matrix}\right]$

7. Matriz triangular inferior: matriz quadrada em que todos os elementos acima da diagonal principal são nulos, ou seja, $a_{ij} = 0$, $\forall i<j$.

```python
F = sympy.Matrix(np.tril([[2,1,1],[1,-1,1], [1,1,2]]))
F
```

$\displaystyle \left[\begin{matrix}2 & 0 & 0\\1 & -1 & 0\\1 & 1 & 2\end{matrix}\right]$

8. Matriz simétrica: matriz quadrada em que $a_{ij} = a_{ji}$ para quaisquer $i,j$ e $A^{T} = A$.

```python
G = sympy.Matrix(3, 3, [4, 3, -1, 3, 2, 0, -1, 0, 5])
G
```

$\displaystyle \left[\begin{matrix}4 & 3 & -1\\3 & 2 & 0\\-1 & 0 & 5\end{matrix}\right]$

9. Matriz Idempotente: matriz quadrada em que $A \cdot A = A$ .

```python
H = sympy.Matrix(2, 2, [5, -5, 4, -4])
H
```

$\displaystyle \left[\begin{matrix}5 & -5\\4 & -4\end{matrix}\right]$

```python
H*H
```

$\displaystyle \left[\begin{matrix}5 & -5\\4 & -4\end{matrix}\right]$

10. Matriz Ortogonal: toda matriz em que $A^{-1}=A^{T}$.

## Operações com matrizes

### Adição e multiplicação por escalar

Sejam A $=$ $a_{ij}$ e B  $=$ $b_{ij}$ duas matrizes de mesmo tamanho, digamos, m $\times $ n  . A soma de A e B, denotada por
A $+$ B, é a matriz obtida pela soma de elementos correspondentes de A e B,
ou seja

 $$A+B =\left[
\begin{array}{c c c}
a_{11}+ b_{11}&\ldots& a_{1n}+b_{1n}\\
a_{21}+b_{21}& \ldots& a_{2n}+b_{2n}\\
\vdots&\ddots &\vdots\\ a_{m1}+b_{m1}&\ldots& a_{mn}+ b_{mn}
\end{array}\right]
$$

O múltiplo da matriz A pelo escalar k, denotado por simplesmente $kA$, é a matriz obtida pelo produto de
cada elemento de A por k, ou seja,

$$kA=\left[
\begin{array}{c c c}
ka_{11}&\ldots& ka_{1n}\\
ka_{21}& \ldots& ka_{2n}\\
\vdots&\ddots &\vdots\\ ka_{m1}&\ldots& ka_{mn}
\end{array}\right]
$$

Vejamos uma implementação da soma de duas matrizes utilizando o NumPy.

Seja
A = $\begin{bmatrix}
1 & 2 & 3\\
4 & 5 & 6
\end{bmatrix}$ e B =$\begin{bmatrix}
2 &  4 & 6\\
1 & 3 & 5  \end{bmatrix}$ iremos encontrar a matriz C, dada por $\mathrm{A+B}$. Iremos simplesmente definir $A$ e $B$ e posteriormente usar o operador $+$.

```python
A = np.array([[1,2,3], [4,5,6]])
B = np.array([[2,4,6],[1,3,5]])
C = A+B
```

```python
C
```

    array([[ 3,  6,  9],
           [ 5,  8, 11]])

Encontrada a matriz C, iremos encontrar a matriz D, dada por kC, onde k = -2.

```python
C = np.array ([[3,6,9], [5,8,11]])
k = -2
D = k*C
```

```python
D
```

    array([[ -6, -12, -18],
           [-10, -16, -22]])

Poderíamos resolver este mesmo problema usando a função np.dot() - utilizada para efetuar o produto matricial:

```python
D = np.dot(k,C)
```

```python
D
```

    array([[ -6, -12, -18],
           [-10, -16, -22]])

Propriedades da soma de matrizes

Considerando as matrizes A,B e C de mesma ordem $m x n$ temos que:
1. $\mathbf {A} +\mathbf {B} = \mathbf{B+A} $ (comutatividade)
2. $\mathbf {A}+\mathbf{(B+C)} = \mathbf{(A+B)}+C$ (associatividade)
3. $\mathbf {A}+\mathbf{0} = \mathbf{A}$

Iremos demonstrar a validade de tais propriedades usando funções de Álgebra Linear do SymPy.Para tanto, definiremos três matrizes quadradas genéricas de ordem 2.

```python
a11, a12, a21, a22 = sympy.symbols('a_11, a_12, a_21, a_22')
b11, b12, b21, b22 = sympy.symbols('b_11, b_12, b_21, b_22')
c11, c12, c21, c22 = sympy.symbols('c_11, c_12, c_21, c_22')
A = sympy.Matrix(2, 2, [a11, a12, a21, a22])
B = sympy.Matrix(2, 2, [b11, b12, b21, b22])
C = sympy.Matrix(2, 2, [c11, c12, c21, c22])
```

Vejamos as matrizes definidas:

```python
A
```

$\displaystyle \left[\begin{matrix}a_{11} & a_{12}\\a_{21} & a_{22}\end{matrix}\right]$

```python
B
```

$\displaystyle \left[\begin{matrix}b_{11} & b_{12}\\b_{21} & b_{22}\end{matrix}\right]$

```python
C
```

$\displaystyle \left[\begin{matrix}c_{11} & c_{12}\\c_{21} & c_{22}\end{matrix}\right]$

Da propriedade 1 temos que:

```python
A+B
```

$\displaystyle \left[\begin{matrix}a_{11} + b_{11} & a_{12} + b_{12}\\a_{21} + b_{21} & a_{22} + b_{22}\end{matrix}\right]$

```python
B+A
```

$\displaystyle \left[\begin{matrix}a_{11} + b_{11} & a_{12} + b_{12}\\a_{21} + b_{21} & a_{22} + b_{22}\end{matrix}\right]$

Então:

```python
(A+B) == (B+A)
```

    True

Da propriedade 2 temos que:

```python
A+(B+C)
```

$\displaystyle \left[\begin{matrix}a_{11} + b_{11} + c_{11} & a_{12} + b_{12} + c_{12}\\a_{21} + b_{21} + c_{21} & a_{22} + b_{22} + c_{22}\end{matrix}\right]$

```python
(A+B)+C
```

$\displaystyle \left[\begin{matrix}a_{11} + b_{11} + c_{11} & a_{12} + b_{12} + c_{12}\\a_{21} + b_{21} + c_{21} & a_{22} + b_{22} + c_{22}\end{matrix}\right]$

```python
A+(B+C) == (A+B)+C
```

    True

Por fim, precisaremos criar uma matriz nula para verificar a última propriedade.

```python
from sympy.matrices import zeros
```

```python
zero = zeros(2,2)
```

```python
zero
```

$\displaystyle \left[\begin{matrix}0 & 0\\0 & 0\end{matrix}\right]$

Desta forma, podemos verificar que:

```python
A+zero == A
```

    True

Propriedades da multiplicação por escalar

Sejam as matrizes A e B de mesma ordem e as constantes reais $k$, $k_1$, $k_2$, temos que:
1. $k(\mathbf{A+B}) = k\mathbf{A}+k\mathbf{B}$
2. $(k_1+k_2)\mathbf{A} = k_1\mathbf{A}+k_2\mathbf{A}$
3. $k\mathbf{A} = \mathbf{0} $, em que k=0

Voltando para as matrizes... Observe que ambas, A $+$ B e kA, são matrizes m $\times $ n . Podemos definir
–A $=$(–1)A e A $-$ B $=$ A $+$ (–B).
Dizemos que –A é a matriz simétrica de A e que A $-$ B é a matriz diferença de A e B. Não se define a soma de matrizes
de tamanhos distintos.

Seja A = $\begin{bmatrix}
1 & 2 & 3\\
4 & 5 & 6
\end{bmatrix}$ encontremos a matriz simétrica de A. Definiremos a matriz A e seguidamente obteremos sua matriz simétrica.

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

```python
-A
```

    array([[-1, -2, -3],
           [-4, -5, -6]])

Poderíamos também ter obtido a matriz simétrica de A como segue.

```python
k=-1
A_simetrica = np.dot(k,A)
```

```python
A_simetrica
```

    array([[-1, -2, -3],
           [-4, -5, -6]])

Vamos recuperar a matriz B definida anteriormente e calcular $\mathbf{A-B}$.

```python
B = np.array([[2,4,6],[1,3,5]])
```

```python
A-B
```

    array([[-1, -2, -3],
           [ 3,  2,  1]])

Dadas as matrizes H = $\begin{bmatrix}
1 & 2 & 3\\
4 & -2 & -9\\
0 & -12 & -29\\
\end{bmatrix}$ e G =  $\begin{bmatrix}
11 & 0 & 0\\
41 & -21 & 0\\
0 & -1 & -2\\
\end{bmatrix}$ encontraremos a matriz J dada por J = H+G.

Utilizando NumPy obtemos o seguinte resultado:

```python
H = np.array([[1, 2, 3], [4, -2, -9], [0, -12, -29]])
G = np.array([[11, 0, 0], [41, -21, 0], [0, -1, -2]])
J = H+G
```

```python
J
```

    array([[ 12,   2,   3],
           [ 45, -23,  -9],
           [  0, -13, -31]])

Podemos resolver o mesmo problema com SymPy. Usaremos a função Matrix para construir as matrizes H e G. Informaremos a ordem das respectivas matrizes e passaremos uma lista contendo seus elementos.

```python
H = sympy.Matrix(3, 3, [1, 2, 3, 4, -2, -9, 0, -12, -29])
G = sympy.Matrix(3, 3, [11, 0, 0, 41, -21, 0, 0, -1, -2])
J = G+H
```

```python
J
```

$\displaystyle \left[\begin{matrix}12 & 2 & 3\\45 & -23 & -9\\0 & -13 & -31\end{matrix}\right]$

### Transposta de uma matriz

A transposta de uma matriz A, denotada por $A^{T}$ ou $A' $, é a matriz obtida escrevendo as colunas de A, na mesma ordem, como linhas.

Propriedades
1. Uma matriz é simétrica se, e somente se ela é igual à sua transposta, ou seja, $\mathbf{A=A'}$.
2. $\mathbf{A''=A}$.
3. $\mathbf{(A+B)'=A'+B'}$.
4. $\mathbf{(kA)'=kA'}$, em que k é um escalar.

Seja,  I =  $\begin{bmatrix}
2 & 0 \\
-4 & 11 \\
5 &-2 \\
 6 & 0 \\
\end{bmatrix}$ então $I^{T}$ $= $ $\begin{bmatrix}
2 & -4 & 5 & 6\\
0 &11 & -2 & 0\\
\end{bmatrix}$. Em linhas gerais, a linha de uma matriz passa a ser coluna e a coluna se torna linha.

Dada a matriz K =  $\begin{bmatrix}
-1 & 0 & 0\\
-4 & 11  & 1 \\
5 &-2 & -9\\
 6 & 0 &7\\
\end{bmatrix}$, encontremos sua matriz transposta $K^{T}$ utilizando o NumPy.

```python
K = np.array([[-1, 0, 0], [-4, 11, 1], [5, -2, -9], [6, 0, 7]])
```

```python
K
```

    array([[-1,  0,  0],
           [-4, 11,  1],
           [ 5, -2, -9],
           [ 6,  0,  7]])

Assim, a transposta de K é obtida como segue:

```python
np.transpose(K)
```

    array([[-1, -4,  5,  6],
           [ 0, 11, -2,  0],
           [ 0,  1, -9,  7]])

Ou

```python
K.T
```

    array([[-1, -4,  5,  6],
           [ 0, 11, -2,  0],
           [ 0,  1, -9,  7]])

Dada a matriz L =  $\begin{bmatrix}
0 & 1 & 0\\
-4 & 11  & 1 \\
5 &-2 & -9\\
 6 & 0 &7\\
 36 & 10 &57\\
\end{bmatrix}$ encontremos $L+L^{T}$ utilizando NumPy.

```python
L = np.array([[0, 1, 0], [-4, 11, 1], [5, -2, -9], [6, 0, 7], [36, 10, 57]])
L
```

    array([[ 0,  1,  0],
           [-4, 11,  1],
           [ 5, -2, -9],
           [ 6,  0,  7],
           [36, 10, 57]])

```python
np.transpose(L)
```

    array([[ 0, -4,  5,  6, 36],
           [ 1, 11, -2,  0, 10],
           [ 0,  1, -9,  7, 57]])

Neste caso, não poderemos obter $L+L^{T}$, pois $L^{T}$ não apresenta a mesma ordem de L.

```python
np.transpose(L).shape, np.shape(L)
```

    ((3, 5), (5, 3))

### Multiplicação de matrizes

O produto das matrizes $A$ e $B$ é denotado por $AB$. Particularmente, iremos começar com um caso
especial.
O produto $AB$ de uma matriz linha A $=$ $[a_i]$ e uma matriz coluna B  $=$  $[b_j]$ com o mesmo número de elementos
é definido como o escalar (ou a matriz ) obtido pela soma dos produtos das entradas correspondentes, ou seja, AB= $\begin{bmatrix}
a_{1}, & a_{2}, & ...,&a_{n}\\
\end{bmatrix} $$\begin{bmatrix}
b_{1} \\
b_{2} \\
... \\
b_{n}\\
\end{bmatrix}$ $= $$a_{1}b_{1}$+$ $$a_{2}b_{2}$$+$ ... $+$$a_{n}b_{n}$.

Enfatizamos que, nesse caso, $AB$ é um escalar (ou, então, uma matriz $1 \times 1$). Não definimos o produto AB se a
matriz linha A e a matriz coluna B possuírem um número distinto de elementos. Esta definição é particularmente importante para definição de um problema de regressão linear múltipla.

Dadas as matrizes M = $\begin{bmatrix}
7, & -4,& 5 \\
\end{bmatrix}$ e N = $\begin{bmatrix}
3 \\
2 \\
-1\\
\end{bmatrix}$ encontremos a matriz $O = M \cdot N$.

```python
M = np.array([[7, -4, 5]])
N = np.array([[3],[2],[-1]])
```

```python
M
```

    array([[ 7, -4,  5]])

```python
N
```

    array([[ 3],
           [ 2],
           [-1]])

Para realizar a multiplicação das matrizes é necessário encontrar o número de linhas e colunas da matriz.

```python
M.shape
```

    (1, 3)

Acima vemos que nossa matriz M possui uma linha e três colunas.

```python
N.shape
```

    (3, 1)

Acima vemos que nossa matriz N possui três linhas e uma coluna. Assim, para efetuar o produto matricial o número de colunas da primeira matriz, deve ser igual ao número de linhas da segunda matriz. Assim, a matriz O, será :

```python
O = np.dot(M,N)
O
```

    array([[8]])

Dadas as matrizes Q =  $\begin{bmatrix}
6, & -1,& 8 & 3  \\
\end{bmatrix}$ e S = $\begin{bmatrix}
4 \\
-9 \\
-2\\
5\\
\end{bmatrix}$ encontraremos a matriz P fruto de QS.

```python
Q = np.array([[6, -1, 8, 3]])
S = np.array ([[4], [-9], [-2], [5]])
```

Verificaremos se o número de colunas de Q é igual ao número de linhas de S:

```python
Q.shape[1] == S.shape[0]
```

    True

Então a matriz P é obtida da seguinte forma:

```python
P = np.dot(Q,S)
P
```

    array([[32]])

De um modo geral, dadas A $=$ $[a_{ik}]$ e B  $=$  $[b_{kj}]$ duas matrizes tais que o número de colunas de A seja igual ao número
de linhas de B, digamos p, supomos assim que A seja uma matriz m $\times$ n e B uma matriz p $\times$ n. Então o produto AB de A e B é a matriz  m $\times$ n cuja ij-ésima entrada é dada pelo produto da i-ésima linha de A com a j-ésima coluna de B. Assim,

$\begin{bmatrix}
a_{11} & ... & a_{1p}\\
. & ... & . \\
a_{i1}& ...&  a_{1p}\\
. & ... & . \\
a_{m1}& ...&  a_{mp}
\end{bmatrix}$
$\begin{bmatrix}
b_{11} & ... & b_{ij} & ... & b_{1n}\\
. & ... & .  & ... & . \\
. & ... & .  & ... & . \\
. & ... & .  & ... & . \\
b_{p1}& ...& b_{pj} &...& b_{pn}
\end{bmatrix}$ onde $c_{ij}$ $= $$a_{i1}b_{ij}$+$ $$a_{i2}b_{j2}$$+$ ... $+$$a_{ip}b_{pj}$.

Não definimos o produto AB quando A é uma matriz m $\times$ p e B é uma matriz com q $\times$ n com p $\neq$ q.

Propriedades da Multiplicação de Matrizes
1. Em geral $\mathbf{AB}\neq\mathbf{BA}$.
2. $\mathbf{AI} = \mathbf{IA} = \mathbf{A} $ (o que justifica o nome da matriz identidade)
3. $\mathbf{A(B+C)} = \mathbf{AB}+\mathbf{AC}$ (distributividade à esquerda da multiplicação)
4. $\mathbf{(A+B)C} = \mathbf{AC+BC} $ (distributividade à direita da multiplicação)
5. $\mathbf{(AB)C = A(BC)} $ (associatividade)
6. $\mathbf{(AB)' = B'A'} $
7. $\mathbf{0 \cdot A = 0}$ e $\mathbf{A \cdot 0 = 0}$

Demonstremos algumas destas propriedades utilizando matrizes genéricas.

```python
from sympy.matrices import eye
a11, a12, a21, a22 = sympy.symbols('a_11, a_12, a_21, a_22')
b11, b12, b21, b22 = sympy.symbols('b_11, b_12, b_21, b_22')
c11, c12, c21, c22 = sympy.symbols('c_11, c_12, c_21, c_22')
A = sympy.Matrix(2, 2, [a11, a12, a21, a22])
B = sympy.Matrix(2, 2, [b11, b12, b21, b22])
C = sympy.Matrix(2, 2, [c11, c12, c21, c22])
I = eye(2)
zero = zeros(2)
```

Respectivamente temos as matrizes A,B,C,I e a matriz nula:

```python
display(A)
display(B)
display(C)
display(I)
display(zero)
```

$\displaystyle \left[\begin{matrix}a_{11} & a_{12}\\a_{21} & a_{22}\end{matrix}\right]$

$\displaystyle \left[\begin{matrix}b_{11} & b_{12}\\b_{21} & b_{22}\end{matrix}\right]$

$\displaystyle \left[\begin{matrix}c_{11} & c_{12}\\c_{21} & c_{22}\end{matrix}\right]$

$\displaystyle \left[\begin{matrix}1 & 0\\0 & 1\end{matrix}\right]$

$\displaystyle \left[\begin{matrix}0 & 0\\0 & 0\end{matrix}\right]$

2. $\mathbf{AI} = \mathbf{IA} = \mathbf{I} $

```python
A*I == I*A
```

    True

3. $\mathbf{A(B+C)} = \mathbf{AB}+\mathbf{AC}$

```python
A*(B+C)
```

$\displaystyle \left[\begin{matrix}a_{11} \left(b_{11} + c_{11}\right) + a_{12} \left(b_{21} + c_{21}\right) & a_{11} \left(b_{12} + c_{12}\right) + a_{12} \left(b_{22} + c_{22}\right)\\a_{21} \left(b_{11} + c_{11}\right) + a_{22} \left(b_{21} + c_{21}\right) & a_{21} \left(b_{12} + c_{12}\right) + a_{22} \left(b_{22} + c_{22}\right)\end{matrix}\right]$

```python
A*B+A*C
```

$\displaystyle \left[\begin{matrix}a_{11} b_{11} + a_{11} c_{11} + a_{12} b_{21} + a_{12} c_{21} & a_{11} b_{12} + a_{11} c_{12} + a_{12} b_{22} + a_{12} c_{22}\\a_{21} b_{11} + a_{21} c_{11} + a_{22} b_{21} + a_{22} c_{21} & a_{21} b_{12} + a_{21} c_{12} + a_{22} b_{22} + a_{22} c_{22}\end{matrix}\right]$

```python
sympy.expand(A*(B+C)) == A*B+A*C
```

    True

4. $\mathbf{(A+B)C} = \mathbf{AC+BC} $

```python
(A+B)*C
```

$\displaystyle \left[\begin{matrix}c_{11} \left(a_{11} + b_{11}\right) + c_{21} \left(a_{12} + b_{12}\right) & c_{12} \left(a_{11} + b_{11}\right) + c_{22} \left(a_{12} + b_{12}\right)\\c_{11} \left(a_{21} + b_{21}\right) + c_{21} \left(a_{22} + b_{22}\right) & c_{12} \left(a_{21} + b_{21}\right) + c_{22} \left(a_{22} + b_{22}\right)\end{matrix}\right]$

```python
A*C+B*C
```

$\displaystyle \left[\begin{matrix}a_{11} c_{11} + a_{12} c_{21} + b_{11} c_{11} + b_{12} c_{21} & a_{11} c_{12} + a_{12} c_{22} + b_{11} c_{12} + b_{12} c_{22}\\a_{21} c_{11} + a_{22} c_{21} + b_{21} c_{11} + b_{22} c_{21} & a_{21} c_{12} + a_{22} c_{22} + b_{21} c_{12} + b_{22} c_{22}\end{matrix}\right]$

```python
sympy.expand((A+B)*C) == A*C+B*C
```

    True

5. $\mathbf{(AB)C = A(BC)} $

```python
(A*B)*C
```

$\displaystyle \left[\begin{matrix}c_{11} \left(a_{11} b_{11} + a_{12} b_{21}\right) + c_{21} \left(a_{11} b_{12} + a_{12} b_{22}\right) & c_{12} \left(a_{11} b_{11} + a_{12} b_{21}\right) + c_{22} \left(a_{11} b_{12} + a_{12} b_{22}\right)\\c_{11} \left(a_{21} b_{11} + a_{22} b_{21}\right) + c_{21} \left(a_{21} b_{12} + a_{22} b_{22}\right) & c_{12} \left(a_{21} b_{11} + a_{22} b_{21}\right) + c_{22} \left(a_{21} b_{12} + a_{22} b_{22}\right)\end{matrix}\right]$

```python
A*(B*C)
```

$\displaystyle \left[\begin{matrix}a_{11} \left(b_{11} c_{11} + b_{12} c_{21}\right) + a_{12} \left(b_{21} c_{11} + b_{22} c_{21}\right) & a_{11} \left(b_{11} c_{12} + b_{12} c_{22}\right) + a_{12} \left(b_{21} c_{12} + b_{22} c_{22}\right)\\a_{21} \left(b_{11} c_{11} + b_{12} c_{21}\right) + a_{22} \left(b_{21} c_{11} + b_{22} c_{21}\right) & a_{21} \left(b_{11} c_{12} + b_{12} c_{22}\right) + a_{22} \left(b_{21} c_{12} + b_{22} c_{22}\right)\end{matrix}\right]$

```python
sympy.expand((A*B)*C) == sympy.expand(A*(B*C))
```

    True

6. $\mathbf{(AB)' = B'A'} $

```python
(A*B).transpose() == B.transpose()*A.transpose()
```

    True

7. $\mathbf{0 \cdot A = 0}$

```python
zero*A
```

$\displaystyle \left[\begin{matrix}0 & 0\\0 & 0\end{matrix}\right]$

Iremos abordar outros exemplos numéricos de multiplicação de matrizes utilizando o NumPy.

Encontraremos XY sendo X = $\begin{bmatrix}
1 & 3 \\
2 &-1\\
\end{bmatrix}$ e Y =  $\begin{bmatrix}
2 & 0 & -4 \\
5 &-2 & 6 \\
\end{bmatrix}$

Devemos inicialmente verificar a condição do produto matricial.

```python
X = np.array([[1, 3], [2, -1]])
Y = np.array([[2, 0, -4], [5, -2, 6]])
X.shape[1] == Y.shape[0]
```

    True

```python
np.dot(X,Y)
```

    array([[ 17,  -6,  14],
           [ -1,   2, -14]])

### Traço de uma matriz

O traço de uma matriz quadrada A, denotado por tr(A), é a soma dos elementos da diagonal principal, a saber, $ tr\mathbf{(A)}= a_{11}+a_{22}+a_{33}+...+a_{nn}$

Propriedades
1. $tr\mathbf{(A+B)} = tr\mathbf{(B+A)}$
2. $tr\mathbf{(\alpha \cdot A)} = \alpha \cdot tr\mathbf{(A)}$, com $\alpha \in \mathbb{R} $
3. $tr\mathbf{(A)} = tr\mathbf{(A')} $
4. $tr\mathbf{(I_{n})} = n$
5. $tr\mathbf{(AB)} = tr\mathbf{(BA)}$

A partir das matrizes definidas a seguir, calcularemos o traço utilizando SymPy e NumPy. Nosso primeiro exemplo será com uma matriz Identidade $I_{2}$. Para construir uma matriz identidade no NumPy podemos utilizar o comando: np.eye(). Por exemplo, a matriz

```python
F = np.eye(2)
F
```

    array([[1., 0.],
           [0., 1.]])

O traço da matriz F é dado pela função np.trace(). Assim,

```python
np.trace(F)
```

    2.0

Ou,

```python
F.trace()
```

    2.0

Definiremos uma matriz identidade 10 $\times$ 10 e calcularemos seu traço.

```python
O = np.eye(10)
```

```python
O
```

    array([[1., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
           [0., 1., 0., 0., 0., 0., 0., 0., 0., 0.],
           [0., 0., 1., 0., 0., 0., 0., 0., 0., 0.],
           [0., 0., 0., 1., 0., 0., 0., 0., 0., 0.],
           [0., 0., 0., 0., 1., 0., 0., 0., 0., 0.],
           [0., 0., 0., 0., 0., 1., 0., 0., 0., 0.],
           [0., 0., 0., 0., 0., 0., 1., 0., 0., 0.],
           [0., 0., 0., 0., 0., 0., 0., 1., 0., 0.],
           [0., 0., 0., 0., 0., 0., 0., 0., 1., 0.],
           [0., 0., 0., 0., 0., 0., 0., 0., 0., 1.]])

```python
np.trace(O)
```

    10.0

Definiremos uma matriz diagonal e em seguida obteremos seu traço utilizando o NumPy

```python
P = np.diag(np.arange(1,6))
P
```

    array([[1, 0, 0, 0, 0],
           [0, 2, 0, 0, 0],
           [0, 0, 3, 0, 0],
           [0, 0, 0, 4, 0],
           [0, 0, 0, 0, 5]])

```python
P.trace()
```

    15

Para recuperar a diagonal principal podemos utilizar o método np.diagonal() .

```python
np.diagonal(P)
```

    array([1, 2, 3, 4, 5])

Ou ainda, acessando como um método do objeto criado:

```python
P.diagonal()
```

    array([1, 2, 3, 4, 5])

Para encontra a diagonal secundária, podemos utilizar o método: np.flipl().

```python
np.diagonal(np.fliplr(P))
```

    array([0, 0, 3, 0, 0])

Definiremos agora duas matrizes genéricas A e B utilizando o SymPy, em seguida calcularemos o traço associado a cada matriz.

```python
from sympy.abc import a, b, c, d,e,f,g,h
A = sympy.Matrix(2, 2, [a, b, c, d])
B = sympy.Matrix(2, 2, [e, f, g, h])
```

```python
A
```

$\displaystyle \left[\begin{matrix}a & b\\c & d\end{matrix}\right]$

```python
B
```

$\displaystyle \left[\begin{matrix}e & f\\g & h\end{matrix}\right]$

Traço de A:

```python
sympy.trace(A)
```

$\displaystyle a + d$

Traço de B:

```python
sympy.trace(B)
```

$\displaystyle e + h$

Vamos verificar as propriedade (1), (3) e (5) definidas anteriormente.

$tr\mathbf{(A+B)} = tr \mathbf{(B+A)} $

```python
sympy.trace(A+B)
```

$\displaystyle a + d + e + h$

```python
sympy.trace(B+A)
```

$\displaystyle a + d + e + h$

$tr\mathbf{(A)} = tr\mathbf{(A')}$

```python
sympy.trace(A) == sympy.trace(A.transpose())
```

    True

$tr\mathbf{(AB)} = tr\mathbf{(BA)}$

Temos que $\mathbf{AB}$:

```python
A*B
```

$\displaystyle \left[\begin{matrix}a e + b g & a f + b h\\c e + d g & c f + d h\end{matrix}\right]$

```python
B*A
```

$\displaystyle \left[\begin{matrix}a e + c f & b e + d f\\a g + c h & b g + d h\end{matrix}\right]$

```python
sympy.trace(A*B) == sympy.trace(B*A)
```

    True

### Determinante

Podemos pensar no determinante como um número associado a uma matriz quadrada $A_{m \times n}$. Denotamos $\det A$.

$$\left| \begin{array}{cccc}
a_{11} & a_{12} & \ldots & a_{1n}\\
a_{21} & a_{22} & \ldots & a_{2n}\\
\vdots & \vdots & \ddots & \vdots\\
a_{n1} & a_{n2} & \ldots & a_{nn}\\
\end{array}
\right|$$

Calculemos o determinante de uma matriz usando o NumPy.

Dada a matriz V = $\begin{bmatrix}
1 & 2 & 3 & 4\\
4& 3 & 2 & 1\\
0 & 2 & 4  & 6 \\
 5 & 7 & 9 & 11 \\
\end{bmatrix}$ encontremos det (V).

Basta utilizar a função np.linalg.det()

```python
V=np.array([[1, 2, 3, 4], [4, 3, 1, 1], [0, 2, 4, 6], [5, 7, 9, 1]])
```

```python
round (np.linalg.det(V))
```

    -20

### Matrizes invertiveis ou não singulares

Uma matriz quadrada $A$ é dita invertível, ou não singular, se existir uma matriz $B$ tal que
$AB = BA = I$ onde $I$ é a matriz identidade. Uma tal matriz $B$ é única.

Dizemos que uma tal matriz $B$ é a inversa de $A$ e a denotamos por $A^{-1}$.Observe que a relação que define a inversa é
simétrica; ou seja, se $B$ for a inversa de $A$, então $A$ será a inversa de $B$.

Suponha que A =  $\begin{bmatrix}
2 & 5 \\
1 & 3\\
\end{bmatrix}$ e B  =  $\begin{bmatrix}
3 & -5 \\
-1 & 2\\
\end{bmatrix}$, verifiquemos se estas matrizes admitem inversa.

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

```python
np.dot(A,B)
```

    array([[1, 0],
           [0, 1]])

```python
np.dot(B,A)
```

    array([[1, 0],
           [0, 1]])

Assim, B é inversa de A e A é inversa de B.

Determinemos a inversa da matriz Z =  $\begin{bmatrix}
3 & 4 \\
1 & 0\\
\end{bmatrix}$

```python
Z = np.array([[3,4],[1,0]])
```

```python
np.linalg.inv(Z)
```

    array([[ 0.  ,  1.  ],
           [ 0.25, -0.75]])

### Sistemas Lineares

De uma maneira geral, denomina-se sistema linear de m equações e n incógnitas $x_{1}, x_{2},..., x_{n}$ a todo sistema linear da forma :$\begin{cases}
a_{11}x_{1}+a_{12}x_{2}+...+a_{1n}x_{n}=b_{1} \\
a_{21}x_{1}+a_{22}x_{2}+...+a_{2n}x_{n}=b_{2} \\
..............................\\
a_{m1}x_{1}+a_{m2}x_{2}+...+a_{mn}x_{n}=b_{m}\\
\end{cases}$ em que $a_{11}, a_{12},..., a_{1n}, b_{1}, b_{2}, ..., b_{m}$ são números reais.

Matriz associadas a um sistema linear:

Seja o sistema linear de m equações e n incógnitas $\begin{cases}
a_{11}x_{1}+a_{12}x_{2}+...+a_{1n}x_{n}=b_{1} \\
a_{21}x_{1}+a_{22}x_{2}+...+a_{2n}x_{n}=b_{2} \\
..............................\\
a_{m1}x_{1}+a_{m2}x_{2}+...+a_{mn}x_{n}=b_{m}\\
\end{cases}$

Desse sistema, destacamos as seguintes matrizes:
- matriz completa do sistema
$\begin{bmatrix}
a_{11} & a_{12} & ... & a_{1n} &b_{1}\\
a_{21} & a_{22} & ... & a_{2n} & b_{2}\\
... & ... & ... & ...&....\\
a_{n1} & a_{n2} & ... & a_{nn}&b_{1m}\\
\end{bmatrix}$

- matriz incompleta do sistema
$\begin{bmatrix}
a_{11} & a_{12} & ... & a_{1n}\\
a_{21} & a_{22} & ... & a_{2n}\\
... & ... & ... & ...\\
a_{n1} & a_{n2} & ... & a_{nn}\\
\end{bmatrix}$

Consideremos ainda as seguintes matrizes-colunas assosciadas ao sistema:
X = $\begin{bmatrix}
x_{1} \\
x_{2} \\
\vdots\\
x_{n}\\
\end{bmatrix}$    $\space$, B = $\begin{bmatrix}
b_{1} \\
b_{2} \\
\vdots\\
b_{m}\\
\end{bmatrix}$

Multiplicando - se a matriz incompleta pela matriz das incognitas, obtemos a matriz dos temos independentes:

$$\begin{bmatrix}
a_{11} & a_{12} & ... & a_{1n}\\
a_{21} & a_{22} & ... & a_{2n}\\
... & ... & ... & ...\\
a_{n1} & a_{n2} & ... & a_{nn}\\
\end{bmatrix}\cdot \begin{bmatrix}
x_{1} \\
x_{2} \\
\vdots\\
x_{n}\\
\end{bmatrix}=\begin{bmatrix}
b_{1} \\
b_{2} \\
\vdots\\
b_{m}\\
\end{bmatrix}$$

Denominamos então que o modelo acima é a forma matricial do sistema linear.
Em notação simplificada temos:  $A \cdot X = B $.
Se a matriz incompleta do sistema for uma matriz quadrada, o seu determinante D$_{A}$ é dito determinante do sistema.
Se D$_{A}$ $\neq$ 0 então a matriz A é invertivel, isto é, existe A$^{-1}$, inversa de A.
Se D$_{A}$ $\neq$ 0, o sistema admite solução única e , portanto, é possível e determinado.

Representemos o sistema $\begin{cases}
3a-7b=1\\
5a+2b=4 \\
\end{cases}$ na forma matricial e encontremos sua solução.

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

```python
X = (np.linalg.solve(A, B))
```

```python
X  #solução do sistema
```

    array([[0.73170732],
           [0.17073171]])

## Exercícios

1. Encontre WI sendo W = $\begin{bmatrix}
1 & 3 & 0 & 20\\
2 &-1 & 10 & 13\\
3 & 4 & 10 & 0\\
4 & 9 & -1 & 0 \\
\end{bmatrix}$ e I =  $\begin{bmatrix}
2 & 0 \\
-4 & 11 \\
5 &-2 \\
 6 & 0 \\
\end{bmatrix}$

2. Construa uma matriz $5 \times 5$ e encontre traço, diagonal principal e secundária.

3. Crie uma matriz $2 \times 2$ somente com elementos pares e calcule seu determinante.

4. Represente os sistemas abaixo  na forma matricial e encontre as respectivas soluções.

a) $\begin{cases}
2x+y=5\\
x-3y=0 \\
\end{cases}$

b)$\begin{cases}
2a+b+c=-1\\
a+c=0 \\
-3a+5b-c=2\\
\end{cases}$

c) $\begin{cases}
x+y+z+t=2\\
x-y-2z-3t=5 \\
2x+y-3z+t=-9\\
3x-y-z+t=6\\
\end{cases}$

5.  Determine a inversa das matrizes:

a) H =  $\begin{bmatrix}
1 & 0 \\
3 & 0\\
\end{bmatrix}$

b) I = $\begin{bmatrix}
1 & 0 & 0\\
1 & 3 & 1\\
1 & 2 & 0\\
\end{bmatrix}$

*Todos os conceitos, proposições e teoremas aqui apresentados foram retirados do livro cujo autor é Seymour Lipschutz e/ou Simon e Blume


## 4.1 基本介绍

由于LaTeX编辑的数学公式颜值非常高，很多理工科研究领域的顶级期刊甚至明确要求投稿论文必须按照给定的LaTeX模板进行论文排版，这样做一方面能保证论文整体排版的美观程度，另一方面也能让生成出来的数学公式更加规范。一般而言，使用LaTeX编辑公式的一系列规则与数学公式的编写原则是一致的，例如，在LaTeX中，我们可以用`$\frac{\partial f}{\partial x}$`生成偏导数$\frac{\partial f}{\partial x}$。

### 4.1.1 数学公式环境

#### 美元符号

在LaTeX中生成数学公式也有一些基本规则，插入公式的方式有很多种，最基本的一种方式是使用美元符号，这种方式不仅在LaTeX适用，在Markdown中也是适用的，具体插入数学公式的方法是：

- 如果我们想插入行内公式，可以直接在两个美元符号中间编辑需要的公式。
- 如果想用美元符号插入行间公式，我们需要输入四个美元符号，与此同时，在四个美元符号中间编辑需要的公式。需要注意的是，这里生成的数学公式会自动居中对齐。

【**例1**】用美元符号分别在行内和行间生成一条简单的数学公式。

```tex
\documentclass[12pt]{article}
\begin{document}

$x+y=2$ is a simple linear equation

$$x+y=2$$

\end{document}
```
编译上述代码，得到幻灯片如图4-1-1所示。

图4-1-1 编译后的幻灯片效果

需要注意的是，LaTeX源文件中的美元符号一般都默认为申明数学公式环境，如果想要在文档中编译出美元符号，需要在美元符号前加上一个反斜线，这种做法同样适用于百分号，百分号一般被默认为注释功能。

#### `equation`环境

尽管美元符号可以在行间插入公式，但却没办法对公式进行编号。自动生成带有公式编号的行间公式需要用到数学公式环境`\begin{equation} \end{equation}`，使用数学公式环境`\begin{equation} \end{equation}`， LaTeX编译时会自动将公式进行居中对齐。

【**例2**】用`equation`数学公式环境生成一条带有编号的简单数学公式。

```tex
\documentclass[12pt]{article}
\begin{document}

\begin{equation}
x+y=2
\end{equation}

\end{document}
```

编译上述代码，得到幻灯片如图4-1-2所示。

图4-1-2 编译后的幻灯片效果

在`equation`环境中，如果不需要公式编号，只需要在数学公式环境中加上一个星号，例如，使用`\begin{equation*} \end{equation*}`就可以移除公式编号。

【**例3**】在数学公式环境中加上一个星号，来移除公式编号。

```tex
\documentclass[12pt]{article}
\begin{document}

\begin{equation*}
x+y=2
\end{equation*}

\end{document}
```

编译上述代码，得到幻灯片如图4-1-3所示。

图4-1-3 编译后的幻灯片效果

更进一步，在`equation`环境中，如果想对公式进行索引，可以使用`\label`和`\eqref`两个命令。

【**例4**】在数学公式环境中使用使用`\label`和`\eqref`两个命令对数学公式进行索引。

```tex
\documentclass[12pt]{article}
\begin{document}

Equation~\eqref{eq1} shows a simple formula.

\begin{equation}\label{eq1}
x+y=2
\end{equation}

\end{document}
```

编译上述代码，得到幻灯片如图4.1.4所示。

图4-1-4 编译后的幻灯片效果

#### `align`环境

在LaTeX中，除了`equation`数学公式环境，还有其他几种数学公式环境可以使用。我们要介绍的第一种是`\begin{align} \end{align}`，它主要用于数组型的数学表达式，`align`环境可以将公式进行自动对齐，它也能对每一条数学表达式分别进行公式编号。

【**例5**】使用`\begin{align} \end{align}`编译一个方程组。

```tex
\documentclass[12pt]{article}
\usepackage{amsmath}
\begin{document}

%% 使用align环境
\begin{align}
x+y&=2 \\
2x+y&=3
\end{align}

\end{document}
```
编译上述代码，得到幻灯片如图4-1-5所示。其中，`&`符号声明了不同行公式之间的对齐参照点。

图4-1-5 编译后的幻灯片效果

在`align`环境中，如果不需要公式编号，同样只需要在数学公式环境中加上一个星号即可，利用这一特性，我们可以使用`align`环境，定义多列公式。

【**例6**】使用`\begin{align*} \end{align*}`编译多列公式：

\begin{align*}
2x+1&=7 & 3y-2&=-5 & -5z+8&=-2 \\
  2x&=6 &   3y&=-3 &   -5z&=-10 \\
   x&=3 &    y&=-1 &     z&=2
\end{align*}

```tex
\documentclass[12pt]{article}
\usepackage{amsmath}
\begin{document}

\begin{align*}
2x+1&=7 & 3y-2&=-5 & -5z+8&=-2 \\
  2x&=6 &   3y&=-3 &   -5z&=-10 \\
   x&=3 &    y&=-1 &     z&=2
\end{align*}

\end{document}
```

需要注意的是，如果想对多行公式对齐，并且共用同一个公式编号，可以在`\begin{equation} \end{equation}`内使用`\begin{aligned} \end{aligned}`，这里的`aligned`与`align`功能类似。

【**例7**】在`\begin{equation} \end{equation}`内使用`\begin{aligned} \end{aligned}`编译多列公式：

\begin{equation}
\begin{aligned}
2x+1&=7 & 3y-2&=-5 & -5z+8&=-2 \\
  2x&=6 &   3y&=-3 &   -5z&=-10 \\
   x&=3 &    y&=-1 &     z&=2
\end{aligned}
\end{equation}

```tex
\documentclass[12pt]{article}
\usepackage{amsmath}
\begin{document}

\begin{equation}
\begin{aligned}
2x+1&=7 & 3y-2&=-5 & -5z+8&=-2 \\
  2x&=6 &   3y&=-3 &   -5z&=-10 \\
   x&=3 &    y&=-1 &     z&=2
\end{aligned}
\end{equation}

\end{document}
```

当然，我们也能只对`align`环境中的某一些公式进行编号，而其他公式不作编号。

> 参考[Eqnarray: numbering last line only.](https://latex.org/forum/viewtopic.php?f=46&t=4543)

【**例8**】使用`\begin{align} \end{align}`编译一个方程组，并且只对第2个方程进行编号。

```tex
\documentclass[12pt]{article}
\usepackage{amsmath}
\begin{document}

\begin{align}
x+y=2 \nonumber \\
2x+y=3
\end{align}

\end{document}
```

编译上述代码，得到幻灯片如图4-1-6所示。

图4-1-6 编译后的幻灯片效果

#### `gather`环境

我们要介绍的第二种数学公式环境是`\begin{gather} \end{gather}`，它既可以将公式进行居中对齐，也能对每一条数学表达式分别进行公式编号。同样的，如果想要移除公式编号，只需要在公式环境中加上星号即可。

【**例9**】使用`\begin{gather} \end{gather}`编译一个方程组。

```tex
\documentclass[12pt]{article}
\usepackage{amsmath}
\begin{document}

\begin{gather}
x+y=2 \\
2x+y=3
\end{gather}

\end{document}
```

编译上述代码，得到幻灯片如图4-1-7所示。

图4-1-7 编译后的幻灯片效果

### 4.1.2 基本格式调整

#### 字符类型

在文本编辑中，我们已经介绍了几种常见的字符类型，实际上，对于数学公式而言，书写时也可以设置不同的字符类型。以$X,Y,Z,x,y,z$为例，具体而言，

- 命令`\boldsymbol{X,Y,Z,x,y,z}`，编译后的效果为$\boldsymbol{X,Y,Z,x,y,z}$，使用之前需申明`\usepackage{amsmath}`；
- 命令`\mathrm{X,Y,Z,x,y,z}`，编译后的效果为$\mathrm{X,Y,Z,x,y,z}$；
- 命令`\mathit{X,Y,Z,x,y,z}`，编译后的效果为$\mathit{X,Y,Z,x,y,z}$；
- 命令`\mathbf{X,Y,Z,x,y,z}`，编译后的效果为$\mathbf{X,Y,Z,x,y,z}$；
- 命令`\mathsf{X,Y,Z,x,y,z}`，编译后的效果为$\mathsf{X,Y,Z,x,y,z}$；
- 命令`\mathtt{X,Y,Z,x,y,z}`，编译后的效果为$\mathtt{X,Y,Z,x,y,z}$；
- 命令`\boldmath{X,Y,Z,x,y,z}`，编译后的效果为${X,Y,Z,x,y,z}$；依赖于特定工具包，使用之前需申明`\usepackage{amssymb}`；
- 命令`\mathcal{X,Y,Z}`，编译后的效果为$\mathcal{X,Y,Z}$；
- 命令`\mathbb{X,Y,Z}`，依赖于特定工具包，使用之前需申明`\usepackage{amssymb, amsfonts}`，编译后的效果为$\mathbb{X,Y,Z}$，概率论与数理统计中常见的数学期望符号$\mathbb{E}$也是用该命令编译的，即`\mathbb{E}`；
- 命令`\mathfrak{X,Y,Z,x,y,z}`，依赖于特定工具包，使用之前需申明`\usepackage{amssymb, amsfonts, eufrak}`，编译后的效果为$\mathfrak{X,Y,Z,x,y,z}$。

【**例10**】使用`\boldmath`和`\unboldmath`对如下公式进行加粗：

\begin{equation}
x^{2}+y^{2}-\sin z=4
\end{equation}

```tex
\documentclass[12pt]{article}
\begin{document}

\begin{equation}
x^{2}+y^{2}-\sin z=4
\end{equation}

\boldmath
\begin{equation}
x^{2}+y^{2}-\sin z=4
\end{equation}
\unboldmath

\end{document}
```

编译上述代码，得到幻灯片如图4-1-8所示。

图4-1-8 编译后的幻灯片效果

除此之外，如果想在公式中插入正常的文本，可以使用`\text{}`命令，例如`T_{\text{start}}`编译后的效果为$T_{\text{start}}$。

#### 调整公式大小

如果想对单个公式调整公式字符大小，在美元符号插入的公式中，可以使用`\displaystyle、\textstyle、\scriptstyle和\scriptscriptstyle`等申明命令对公式大小进行调整，公式显示效果依次从小到大，这些命令一般放在公式前即可。

【**例11**】使用`\displaystyle`、`\textstyle`、`\scriptstyle`和`\scriptscriptstyle`这四种命令分别书写函数$f(x)=\sum_{i=1}^{n}\frac{1}{x_{i}}$。

```tex
\documentclass[12pt]{article}
\begin{document}

$\displaystyle{f(x)=\sum_{i=1}^{n}\frac{1}{x_{i}}}$,
$\textstyle{f(x)=\sum_{i=1}^{n}\frac{1}{x_{i}}}$,
$\scriptstyle{f(x)=\sum_{i=1}^{n}\frac{1}{x_{i}}}$,
$\scriptscriptstyle{f(x)=\sum_{i=1}^{n}\frac{1}{x_{i}}}$.

\end{document}
```

编译上述代码，得到幻灯片如图4-1-9所示。

图4-1-9 编译后的幻灯片效果

在各类公式环境（如`equation`、`align`、`gather`）中，可以外使用一系列字符大小命令进行调整，例如用`\begingroup \endgroup`限定字符区域。

> 参考[Increase Equation Size](https://latex.org/forum/viewtopic.php?f=44&t=19788)。

【**例12**】在`\begingroup \endgroup`中使用字符大小命令`\small`和`\Large`对公式大小进行调整。

```tex
\documentclass[12pt]{article}
\usepackage{amsmath}
\begin{document}

%% Small size
\begingroup
\small
\begin{align}
x+y=2 \\
2x+y=3
\end{align}
\endgroup

%% Large size
\begingroup
\Large
\begin{align}
x+y=2 \\
2x+y=3
\end{align}
\endgroup

\end{document}
```
编译上述代码，得到幻灯片如图4-1-10所示。

图4.1.10 编译后的幻灯片效果

#### 其他格式调整

在`equation`、`align`等公式环境中，我们也可以通过数组`array`环境对数学公式进行对齐，例如，

【**例13**】使用`\begin{array} \end{array}`编译一个方程组，并添加半边花括号，如

$$\begin{equation}
\left\{\begin{array}{l}
x+y=2 \\
2x+y=3
\end{array}\right.
\end{equation}$$

```tex
\documentclass[12pt]{article}
\usepackage{amsmath}
\begin{document}

\begin{equation}
\left\{\begin{array}{l}
x+y=2 \\
2x+y=3
\end{array}\right.
\end{equation}

\begin{align}
\left\{\begin{array}{l}
x+y=2 \\
2x+y=3
\end{array}\right.
\end{align}

\end{document}
```

编译上述代码，得到幻灯片如图4-1-11所示。

图4-1-11 编译后的幻灯片效果

其中，对齐的方式有`l`（左侧对齐）、`c`（居中对齐）和`r`（右侧对齐）。

【**例14**】使用`\begin{array} \end{array}`编译公式，并让公式居中对齐。

```tex
\documentclass[12pt]{article}
\usepackage{amsmath, mathtools}

\begin{document}

\begin{equation}
\begin{array}{c@{\qquad}c}
A = B + C
\qquad\Rightarrow
& D = E - F, \\ \\
G = H
\qquad\Rightarrow
& K = P + Q + M.
\end{array}
\end{equation}

\end{document}
```

编译上述代码，得到幻灯片如图4-1-12所示。

图4-1-12 编译后的幻灯片效果

当公式过长时，还有一些工具包提供的环境可以让公式进行自动跨行，以工具包`breqn`为例，在使用时，用`\begin{dmath} \end{dmath}`即可。

### 练习题

> 打开LaTeX在线系统[https://www.overleaf.com](https://www.overleaf.com/project)或本地安装好的LaTeX编辑器，创建名为LaTeX_practice的项目，并同时新建一个以`.tex`为拓展名的源文件，完成以下几道练习题。

[1] 使用`\displaystyle`命令书写表达式$\max_{0\leq x\leq n-1}\prod_{i=1}^{c}f_{i}(x)$。

```tex
\documentclass[12pt]{article}
\begin{document}

${\max_{0\leq x\leq n-1}\prod_{i=1}^{c}f_{i}(x)}$.

\end{document}
```

[2] 在`align`环境中使用`\nonumber`命令只对如下方程组的第二个方程进行编号。

```tex
\documentclass[12pt]{article}
\usepackage{amsmath}

\begin{document}

%% 使用\nonumber命令去除第一个和第三个方程的编号
\begin{align}
(a+b)^2&=a^2+2ab+b^2 \\
(a-b)^2&=a^2-2ab+b^2 \\
(a+b)(a-b)&=a^2-b^2
\end{align}

\end{document}
```

[3] 使用`\begin{flalign} \end{flalign}`编译一个方程组。

> 【背景知识】在LaTeX的`amsmath`工具包中，使用`align`环境编译的数学公式会自动居中对齐。实际上，`amsmath`工具包还有一种使数学公式自动居左对齐的数学公式环境`flalign`。（参考[Left-Justified "align"](https://latex.org/forum/viewtopic.php?f=46&t=22839)）

```tex
\documentclass[12pt]{article}
\usepackage{amsmath}
\begin{document}

%% 使用flalign环境
\begin{}
&x+y=2& \\
&2x+y=3
\end{}

\end{document}
```

[4] 使用`\begin{array} \end{array}`编译如下表达式：

$$\begin{equation}
f(x)=\left\{
\begin{array}{c l}
    x, & x>0, \\
    -x, & x<0.
\end{array}\right.
\end{equation}$$

```tex
\documentclass[12pt]{article}
\usepackage{amsmath}
\begin{document}

%% 提示：使用equation和array环境
\begin{equation}
f(x)=\left\{
\begin{}{c l}
    x, & x>0, \\
    -x, & x<0.
\end{}\right.
\end{equation}

\end{document}
```

[5] 使用`\begin{subequations} ... \end{subequations}`生成子公式，并进行自动编号。

> 参考[How to properly label two aligned equations on the same line?](https://latex.org/forum/viewtopic.php?f=46&t=31577)

```tex
\documentclass[12pt]{article}
\usepackage{amsmath}

\begin{document}
\noindent
Here are two funny equations:
\begin{subequations}
\begin{equation}
\label{eq-a}
a = b
\end{equation}
\begin{equation}
\label{eq-b}
c = d
\end{equation}
\end{subequations}
Equation \eqref{eq-a} is not the same as equation \eqref{eq-b}.
\end{document}
```

【回放】[**导言**](https://nbviewer.jupyter.org/github/xinychen/latex-cookbook/blob/main/chapter-4/section0.ipynb)

【继续】[**4.2 常用数学符号**](https://nbviewer.jupyter.org/github/xinychen/latex-cookbook/blob/main/chapter-4/section2.ipynb)

### License

This work is released under the MIT license.


# Optimización de funciones escalares diferenciables con `SymPy`

- Mediante optimización se obtienen soluciones elegantes tanto en teoría como en ciertas aplicaciones.
- La teoría de optimización usa elementos comenzando con cálculo elemental y álgebra lineal básica, y luego se extiende con análisis funcional y convexo.
- Las aplicaciones en optimización involucran ciencia, ingeniería, economía, finanzas e industria.
- El amplio y creciente uso de la optimización lo hace escencial para estudiantes y profesionales de cualquier rama de la ciencia y la tecnología.

**Referencia:**
- http://www.math.uwaterloo.ca/~hwolkowi//henry/reports/talks.d/t06talks.d/06msribirs.d/optimportance.shtml

### Algunas aplicaciones son:

1. **Ingeniería:**
 - Encontrar la composición de equilibrio de una mezcla de diferentes átomos.
 - Planeación de ruta para un robot (o vehículo aéreo no tripulado).
 - Planeación de la mano de obra óptima en una construcción o planta de producción.
 - Análisis conformacional - Isomería conformacional en modelado molecular
 - Desempeño en fermentación de productos.
 - Estimación de parámetros en bioquímica e ingeniería metabólica.

2. **Distribución óptima de recursos:**
 - Distribución de rutas de vuelo.
 - Encontrar una dieta óptima.
 - Planeación de ruta óptima.

3. **Optimización financiera:**
 - Administración de riesgos.
 - Portafolios de inversión.
 - Problema de maximización de la utilidad.
 -

###
En esta clase veremos aspectos básicos de optimización. Específicamente, veremos cómo obtener máximos y mínimos de una función escalar de una variable (como en cálculo diferencial).

___
## 0. Librerías que usaremos

Como ya dijimos en la primer clase `python` es el lenguaje de programación (el cual es de alto nivel). Sin embargo, `python` solo tiene unos pocos comandos primitivos y para hacer más fácil su uso en nuestras actividades de simulación en ingeniería, otras personas ya han escrito ciertas librerías por nosotros.

### 0.1 `NumPy`

`NumPy` (Numerical Python) es la librería fundamental para computación científica (numérica) con `Python`. Contiene, entre otras cosas:
- un objeto tipo arreglo N-dimensional muy poderoso
- funciones sofisticadas
- funciones de álgebra lineal, transformada de Fourier y números aleatorios.

Por lo anterior, `NumPy` es de amplio uso entre la comunidad científica e ingenieril (por su manejo de cantidades vectoriales). De la misma manera, se usa para guardar datos. Para nuestros propósitos, se puede usar libremente.

**Referencia:**
- http://www.numpy.org/

`NumPy` ya viene incluido en la instalación estándar de Anaconda por defecto. Para comenzar a usarlo, solo debemos de importarlo:

```python
# Importar librería numpy
import numpy as np
```

### 0.2 `SymPy`

`SymPy` (Symbolic Python) es una librería de `Python` para matemáticas simbólicas. Su objetivo es convertirse en un sistema de álgebra computacional con las mejores características, manteniendo el código lo más simple posible para que sea comprensible.

**Referencia:**
- http://www.sympy.org/en/index.html

`SymPy` ya viene incluido en la instalación estándar de Anaconda por defecto. Para comenzar a usarlo, solo debemos de importarlo:

```python
#Importarmos la librería sympy
import sympy as sym

# Imprimir datos en formato Latex
sym.init_printing(use_latex="mathjax")
```

La funcionalidad de imprimir en formato LaTex que nos da `SymPy` mediante el proyecto `mathjax` hace de `SymPy` una herramienta muy atractiva...

Notar que en `SymPy` y en `NumPy` existen funciones con el mismo nombre, pero reciben tipos de datos diferentes...

```python
np.cos(0)
```

$\displaystyle 1.0$

```python
sym.var("x")
sym.cos(x)
```

$\displaystyle \cos{\left(x \right)}$

Explicar el uso de la sintaxis `from numpy import *` y sus peligros (no recomendable).

### 0.3 `PyPlot` de `matplotlib`

El módulo `PyPlot` de la librería `matplotlib` contiene funciones que nos permite generar una gran cantidad de gráficas rápidamente. Las funciones de este módulo están escritas con el mismo nombre que las funciones para graficar en `Matlab`.

**Referencia:**
- https://matplotlib.org/api/pyplot_summary.html

```python
# importar matplotlib.pyplot
import matplotlib.pyplot as plt

# comando para que las gráficas salgan en la misma ventana
%matplotlib inline
```

Ya que revisamos todas las librerías que usaremos, empecemos con la clase como tal...

___

Todos los resultados que veremos son basados en el siguiente teorema:

## 1. Teorema de Fermat (análisis)

Si una función $f(x)$ alcanza un máximo o mínimo local en $x=c$, y si la derivada $f'(c)$ existe en el punto $c$, entonces $f'(c) = 0$.

### Ejemplo

Sabemos que la función $f(x)=(x-2)^2$ tiene un mínimo global en $x=2$, pues

$$f(x)=(x-2)^2\geq0,\qquad\text{y}\qquad f(x)=(x-2)^2=0 \qquad\text{si y solo si}\qquad x=2.$$

```python
#Declarar variable x como real
sym.var("x",real=True)
```

$\displaystyle x$

```python
# Declarar función f y mostrar
f = (x-2)**2
f
```

$\displaystyle \left(x - 2\right)^{2}$

```python
# Encontrar la primera derivada de la función f
df = sym.diff(f,x)
df
```

$\displaystyle 2 x - 4$

```python
#Resolver f'(x)= 0 y mostrar punto
xc = sym.solve(df,x)
xc
```

$\displaystyle \left[ 2\right]$

Veamos la gráfica...

```python
# Convertir función simbólica f a una función numérica
fnum = sym.lambdify(x,f)
```

```python
# Definir coordenas x
xnum = np.linspace(-4,8,300)
ynum = fnum(xnum)
```

```python
# Graficar
# Crear nueva ventana de gráficos y se da medida a la ventana
plt.figure(figsize=(10,8))

#Agregar gráficas
plt.plot(xnum,ynum,"-.k",lw=3,label="$y=(x-2)^2$")
plt.plot([2],[0],"*y",ms=30,label="$(2,0)$")

#Nombrar los ejes de la gráfica
plt.xlabel("$x$")
plt.ylabel("$y$")

plt.grid()      # Agrega cuadrícula de fondo a la gráfica
plt.legend(loc="best")
plt.show()
```

### **Otra manera de hacer lo anterior**

Revisemos el concepto de función en `Python`

```python
#Definimos una función en Python
def mi_funcion(x):
    return (x-2)**2
```

```python
sym.var("x",real=True)
df = sym.diff(mi_funcion(x),x)
df
```

$\displaystyle 2 x - 4$

```python
# sym.solve soluciona la ecuación df=0
xc = sym.solve(df,x)
xc
```

$\displaystyle \left[ 2\right]$

```python
xnum = np.linspace(-4,8,50)
ynum = mi_funcion(xnum)
```

```python

```

```python
# Graficar
# Crear el tamaño de la figura a graficar
plt.figure(figsize=(10,8))

# Agregar las gráficas
plt.plot(xnum,ynum,"--g",lw=4,label="$y = (x-2)^2$")
plt.plot([2],[0], 'dr', ms=15, label="$(2,0)$")

#Nombrar los ejes de la gráfica
plt.xlabel("$x$",fontsize=15)
plt.ylabel("$y$",fontsize=15)

plt.grid()
plt.legend(loc='best')
plt.show()
```

**El converso del teorema anterior no es cierto.**

### Actividad
Considere $g(x)=(x-2)^3$.
- Usando `sympy`, muestre que $g'(2)=0$.
- Sin embargo, descartar que $x=2$ es un extremo de $g(x)$ viendo su **gráfica**.

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

```python
dg = sym.diff(g(x),x)
dg
```

$\displaystyle 3 \left(x - 2\right)^{2}$

```python
xc = sym.solve(dg,x)
xc
```

$\displaystyle \left[ 2\right]$

```python
xnum = np.linspace(-1,5,126)
ynum = g(xnum)
```

```python
# Graficar
# Crear el tamaño de la figura a graficar
plt.figure(figsize=(10,8))

#Agregamos los gráficos
plt.plot(xnum, ynum, 'b',lw=4,label='$y = (x-2)^3$')
plt.plot([2],[0],'pm',ms=18,label='$(2,0)$')

#Nombrar los ejes de la gráfica
plt.xlabel('$x$',fontsize=20)
plt.ylabel('$y$',fontsize=20)

plt.grid()
plt.legend(loc='best')
plt.show()
```

## 2. Criterio de la segunda derivada

Sea $f(x)$ una función tal que $f’(c)=0$ y cuya segunda derivada existe en un intervalo abierto que contiene a $c$.
- Si $f’’(c)>0$, entonces $f(c)$ es un mínimo relativo.
- Si $f’’(c)<0$, entonces $f(c)$ es un máximo relativo.
- Si $f’’(c)=0$, entonces el criterio no decide.

### Ejemplo

Mostrar, usando `sympy`, que la función $f(x)=(x-2)^2$ tiene un mínimo relativo en $x=2$.

Ya vimos que $f'(2)=0$. Notemos que:

```python

```

```python

```

```python

```

Por tanto, por el criterio de la segunda derivada, $f(2)=0$ es un mínimo relativo (en efecto, el mínimo global).

### Ejemplo
¿Qué pasa con $g(x)=(x-2)^3$ al intentar utilizar el criterio de la segunda derivada? (usar `sympy`).

```python

```

```python

```

```python

```

Como $g''(2)=0$ entonces el criterio de la segunda derivada no concluye.

### Actividad

¿Qué pasa con $h(x)=(x-2)^4$ al intentar utilizar el criterio de la segunda derivada?.

```python

```

```python

```

```python

```

```python

```

```python

```

```python
# Graficar

# Crear el tamaño de la figura a graficar

#Nombrar los ejes de la gráfica

```

## 3. Método para determinar extremos absolutos de una función continua y=f(x) en [a,b]

- Determinar todos los valores críticos $c_1, c_2, c_3, \dots, c_n$ en $(a,b)$.
- Evaluar $f$ en todos los valores críticos y en los extremos $x=a$ y $x=b$.
- El más grande y el más pequeño de los valores de la lista $f(a), f(b), f(c_1), f(c_2), \dots, f(c_n)$ son el máximo absoluto y el mínimo absoluto, respectivamente, de $f$ en el intervalo $[a,b]$.

### Ejemplo

Determinar los extremos absolutos de $f(x)=x^2-2x+5$ en $\left[0,3\right]$.

Obtenemos los puntos críticos de $f$ en $\left[0,3\right]$:

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

```python
df = sym.diff(f(x),x)
df
```

$\displaystyle 2 x - 2$

```python
xc = sym.solve(df,x)
xc
```

$\displaystyle \left[ 1\right]$

```python

```

Evaluamos $f$ en los extremos y en los puntos críticos:

```python
f(0), f(3), f(1)
```

$\displaystyle \left( 5, \  8, \  4\right)$

Concluimos que el máximo absoluto de $f$ en $\left[0,3\right]$ es $8$ y se alcanza en $x=3$, y que el mínimo absoluto es $4$ y se alcanza en $x=1$.

```python
#Gráfica
xnum = np.linspace(0,3,254)
ynum = f(xnum)

# Crear el tamaño de la figura a graficar
plt.figure(figsize=(10,8))

#Agregar los gráficos
plt.plot(xnum,ynum,':y',lw=5,label='$y=x^2-2x+5$')
plt.plot([3],[8],'pr',ms=18,label='Punto máximo en el intervalo')
plt.plot([1],[4],'pg',ms=18,label='Punto mínimo en el intervalo')

#Nombrar los ejes de la gráfica
plt.xlabel('$x$',fontsize=18)
plt.ylabel('$y$',fontsize=18)

plt.grid()
plt.legend(loc='best')
plt.show()
```

### Actividad
Determinar los valores extremos absolutos de  $f(x) = (x-3)^2(x+3)^2$ en el intervalo $[1,\,4]$, usando `sympy`. Mostrar en una gráfica.

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

```python
df = sym.diff(f(x),x)
df
```

$\displaystyle \left(x - 3\right)^{2} \left(2 x + 6\right) + \left(x + 3\right)^{2} \left(2 x - 6\right)$

```python
xc = sym.solve(df,x)
xc
```

$\displaystyle \left[ -3, \  0, \  3\right]$

```python
f(1), f(4), f(3)
```

$\displaystyle \left( 64, \  49, \  0\right)$

```python
#Gráfica
xnum = np.linspace(1,4,254)
ynum = f(xnum)

# Crear el tamaño de la figura a graficar
plt.figure(figsize=(10,8))

#Agregar los gráficos
plt.plot(xnum,ynum,'-.y',lw=5,label='$y=(x-3)^2(x+3)^2$')
plt.plot([1],[64],'pr',ms=18,label='Punto máximo en el intervalo')
plt.plot([3],[0],'pg',ms=18,label='Punto mínimo en el intervalo')

#Nombrar los ejes de la gráfica
plt.xlabel('$x$',fontsize=18)
plt.ylabel('$y$',fontsize=18)

plt.grid()
plt.legend(loc='best')
plt.show()
```

```python

```

```python

```

### En varias variables...

El procedimiento es análogo.

Si una función $f:\mathbb{R}^n\to\mathbb{R}$ alcanza un máximo o mínimo local en $\boldsymbol{x}=\boldsymbol{c}\in\mathbb{R}^n$, y $f$ es diferenciable en el punto $\boldsymbol{x}=\boldsymbol{c}$, entonces $\left.\frac{\partial f}{\partial \boldsymbol{x}}\right|_{\boldsymbol{x}=\boldsymbol{c}}=\boldsymbol{0}$ (todas las derivadas parciales en el punto $\boldsymbol{x}=\boldsymbol{c}$ son cero).

**Criterio de la segunda derivada:** para ver si es máximo o mínimo, se toma la segunda derivada (matriz jacobiana) y se verifica definición negativa o positiva, respectivamente.

Si se restringe a cierta región, hay ciertas técnicas. La más general, pero también la más compleja es la de **multiplicadores de Lagrange**.

**Ejemplo:** Determinar los valores extremo de $z(x,y)= (x-3)^2 + (y+5)^2$ usando el criterio de la segunda derivada y corroborarlo con la gráfica.

```python
sym.var('x y')
```

$\displaystyle \left( x, \  y\right)$

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

```python
dfx = sym.diff(f(x,y),x)
dfy = sym.diff(f(x,y),y)
dfx,dfy
```

$\displaystyle \left( 2 x - 6, \  2 y + 10\right)$

```python
xy_c = sym.solve([dfx,dfy],[x,y])
xy_c
```

$\displaystyle \left\{ x : 3, \  y : -5\right\}$

```python
x_c = xy_c[x]
y_c = xy_c[y]
x_c,y_c
```

$\displaystyle \left( 3, \  -5\right)$

```python
d2fx = sym.diff(f(x,y),x,2)
d2fy = sym.diff(f(x,y),y,2)
d2fxy = sym.diff(f(x,y),x,y)
```

```python
A = sym.Matrix([[d2fx, d2fxy],[d2fxy,d2fy]])
A
```

$\displaystyle \left[\begin{matrix}2 & 0\\0 & 2\end{matrix}\right]$

```python
A.eigenvals()
```

$\displaystyle \left\{ 2 : 2\right\}$

```python
from mpl_toolkits.mplot3d import Axes3D
```

```python
xnum = np.linspace(0,6,100)
ynum = np.linspace(-8,-2,100)

X, Y = np.meshgrid(xnum,ynum)
znum = f(X,Y)

#Crear gráfica y definir tamaño
fig = plt.figure(figsize=(10,10))
ax = fig.add_subplot(111,projection='3d')

ax.plot_surface(X,Y,znum)
ax.plot([x_c],[y_c],[f(x_c,y_c)],'pg',ms=25)

#Definir ejes coordenados

```

```python

```

### Tarea 3.

1. Obtener máximos y mínimos absolutos, usando `sympy`, de las funciones dadas en los intervalos dados, y graficar la función en dicho intervalo señalando los puntos máximo y mínimo.

    + $f(x)=2x^3+4x^2+1$ en $[-1.5,\,0.5]$
    + $g(x) = 5x^3-3x+10$ en el intervalo $[-1, \,0]$

Created with Jupyter by Cristian Camilo Zapata Zuluaga.


# Rosenblatt's perceptron

Implement the Rosenblatt's perceptron as described in Haykin, Chap. 1
We tried to keep the same structure ("update weight", "train") as used in the
regression section.

## Neuron Model

### 2D example

While neural networks developed really quickly recently due to advances in computer hardware, the idea of an artificial neuron goes back to 1943 where McCulloch and Pitts introduced the concept of neural networks as computing machines. They drew parallels with the behaviour of biological neurons to define a general signal processing model that may be used for learning. The model is presented below.

The model first make a linear combinations of the inputs:

\begin{equation}
v = w_1x_1 + w_2x_2 + b
\end{equation}

Then, pass it through a non-linear activation function:

\begin{align}
y & = f(v) \\
y & = f(w_1x_1 + w_2x_2 + b)
\end{align}

Throughout the tutorials, we will see that there are a wide range of activation functions that can be used (step function, sigmoid, ReLU, ...). Here, it is [the unit step function](https://en.wikipedia.org/wiki/Heaviside_step_function):

\begin{equation}
y =
\begin{cases}
1 & \text{if} & w_1x_1 + w_2x_2 + b > 0 \\
0 & \text{if} & w_1x_1 + w_2x_2 + b \leq 0
\end{cases}
\end{equation}

This divide the space in two parts, with the boundary given by:

\begin{equation}
w_1x_1 + w_2x_2 + b = 0
\end{equation}

Therefore, by adjusting $w_1$, $w_2$, and $b$, the position of the line can be adjusted to separate two categories.

### Higher dimensions

If we want to generalize it to a system with $ n $-dimensions, we need to introduce more general notations. We define the input vector, $\mathbf{x}$, and weight vector, $\mathbf{w}$, as:

\begin{align}
\mathbf{x} & = \left[1, x_1, x_2, ..., x_n \right]^T \\
\mathbf{w} & = \left[b, w_1, w_2, ..., w_n \right]^T
\end{align}

In that case the boundary can be expressed in the compact form of:

\begin{equation}
\mathbf{w}^T \mathbf{x} = 0
\end{equation}

## Algorithm

The Rosenblatt's perceptron is a first case of rule-based supervised learning. Let's break that down:

* "rule-based": it applies fixed rules to update the weights and converge to a solution
* "supervised learning": it

We implement the algorithm to update the weights.

First, if the point is on the properly categorized well, we do not change the weights: no reason to change anything if the data is categorized properly. Second, if the point is wrongly categorized, we adjust the weights, according to a learning rate $\eta$.

\begin{align}
& (1) &\mathbf{w}(n+1) = \mathbf{w}(n) - \eta(n)\mathbf{x}(n) & \text{ if }  \mathbf{w}^T(n)\mathbf{x}(n) > 0 \text{ and } \mathbf{x}(n) \in \mathscr{C_2} \\
& (2) &\mathbf{w}(n+1) = \mathbf{w}(n) + \eta(n)\mathbf{x}(n) & \text{ if } \mathbf{w}^T(n)\mathbf{x}(n) \leq 0 \text{ and } \mathbf{x}(n) \in \mathscr{C_1}
\end{align}

Therefore, the function will try to classify

## Implementation

### Import

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

### Data generation

At first we have to generate some data to classify. We define two categories, 1 and 2, each following a Gaussian distribution.

```python
# First Gaussian distribution

n1 = 200    # Number of points in first category
x1, y1 = -5, 5              # Mean
x1_std, y1_std = 3, 1.5     # Standard deviation

X1 = np.stack((np.random.normal(x1, x1_std, n1),
               np.random.normal(y1, y1_std, n1)), axis=1)
y1 = np.ones(n1)    # First category is 1

# Second distribution

n2 = 200
x2, y2 = 10, 10
x2_std, y2_std = 3, 3

X2 = np.stack((np.random.normal(x2, x2_std, n2),
               np.random.normal(y2, y2_std, n2)), axis=1)
y2 = np.ones(n2) * 2   # Second category is 2
```

Let's have a look at the data:

```python
fig1, ax1 = plt.subplots()

ax1.scatter(X1[:, 0], X1[:, 1], marker='.', alpha=0.3, label='1')
ax1.scatter(X2[:, 0], X2[:, 1], marker='.', alpha=0.3, label='2')
ax1.legend()

ax1.set(xlabel='$x_1$', ylabel='$x_2$')
plt.show()
```

Then, we concatenate them together in one general dataset with $X$ as the input and $y$ as the output.

```python
X = np.concatenate((X1, X2), axis=0)
y = np.concatenate((y1, y2), axis=0)
```

```python
def update_weights(X, y, w, alpha):
    """
    Follows equation 1.3 and 1.6 to update the weight vector w.
    """

    for i in range(len(X)):
        # Define the vector according to the textbook
        x_temp = np.array([[1], [X[i, 0]], [X[i, 1]]]).T

        # Combiner output (Eq. 1.3)
        v = np.matmul(x_temp, w.T)

        # Eq 1.6: weight update
        if (v > 0) and (y[i] == 2):
            w -= alpha * x_temp
        elif (v <= 0) and (y[i] == 1):
            w += alpha * x_temp
    return w
```

After defining the rules to update the weights, we define the general training loop. We will Given the training data $(\mathbf{X}, \mathbf{y})$

```python
def train(X, y, w, alpha, epochs):
    for i in range(epochs):
        w = update_weights(X, y, w, alpha)
        if i % 100 == 0:
            print("EPOCH: ", i)
    return w
```

```python
# Define the vector according to the textbook
w = np.array([[0.0], [0.0], [0.0]]).T
alpha = 1e-4    # Learning rate
epochs = 1000
```

```python
w = train(X, y, w, alpha, epochs)
w = w.squeeze()     # Get rid of unwanted dimensions
```

    EPOCH:  0
    EPOCH:  100
    EPOCH:  200
    EPOCH:  300
    EPOCH:  400
    EPOCH:  500
    EPOCH:  600
    EPOCH:  700
    EPOCH:  800
    EPOCH:  900

### Graph

```python
# Determine the boundary line

x_plot = np.linspace(-10, 10)
y_plot = -(w[1] * x_plot + w[0]) / w[2]

# Make the figure

fig2, ax2 = plt.subplots()

ax2.scatter(X1[:, 0], X1[:, 1], marker='.', alpha=0.3, label='1')
ax2.scatter(X2[:, 0], X2[:, 1], marker='.', alpha=0.3, label='2')
ax2.plot(x_plot, y_plot, 'k', label='Linear boundary')

ax2.set(xlabel='$x_1$', ylabel='$x_2$',
        xlim=[-15, 20], ylim=[-5, 20])
ax2.legend()
plt.show()
```

## Review

In this first example, we introduced the neuron model.


# __PROBLEM 1__

#### In this problem we consider a signal u that you observe during an hour at intervals of one minute. In other words, you measure the values $u(1), . . . , u(60)$. You decide to represent these values on a vector $$\pmb{u}=\begin{bmatrix}u(1)\\ \vdots \\ u(60)\end{bmatrix} $$  in $\mathbb{R}^60$ The signal is shown in Fig.1a and can be found in the file signal.py. To better understand the information contained in this signal you decide to approximate by taking linear combinations of vectors $v_1, . . . , v_{12}$. An example of these vectors can be seen in Fig. 1b and Fig. 1c and you find them in the file basis.py. This file contains a $60 \times 12$ matrix $V$ where each column represents a vector in the collection

#### a) Interpret each one of the elements of the collection $v_1, . . . , v_{12}$ in terms of a feature. For example, do elements of the collection measure averages over certain time periods? Or changes in the value of the signal over different time periods?

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

```python
#Import
u_aux=np.load('signal.npz')
v_aux=np.load('basis.npz')
u=u_aux['arr_0']
v1=v_aux['arr_0'].T[0]
v2=v_aux['arr_0'].T[1]
v3=v_aux['arr_0'].T[2]
v4=v_aux['arr_0'].T[3]
v5=v_aux['arr_0'].T[4]
v6=v_aux['arr_0'].T[5]
v7=v_aux['arr_0'].T[6]
v8=v_aux['arr_0'].T[7]
v9=v_aux['arr_0'].T[8]
v10=v_aux['arr_0'].T[9]
v11=v_aux['arr_0'].T[10]
v12=v_aux['arr_0'].T[11]
```

```python
plt.stem(np.arange(1,61),u)
plt.title('observed signals vector U')
plt.show()
```

```python
fig, axs = plt.subplots(3, 4,figsize=(40,30),sharex=True,sharey=True)

axs[0,0].stem(np.arange(1,61),v1)
axs[0,0].set_title('v1')
axs[0,1].stem(np.arange(1,61),v2)
axs[0,1].set_title('v2')
axs[0,2].stem(np.arange(1,61),v3)
axs[0,2].set_title('v3')
axs[0,3].stem(np.arange(1,61),v4)
axs[0,3].set_title('v4')
axs[1,0].stem(np.arange(1,61),v5)
axs[1,0].set_title('v5')
axs[1,1].stem(np.arange(1,61),v6)
axs[1,1].set_title('v6')
axs[1,2].stem(np.arange(1,61),v7)
axs[1,2].set_title('v7')
axs[1,3].stem(np.arange(1,61),v8)
axs[1,3].set_title('v8')
axs[2,0].stem(np.arange(1,61),v9)
axs[2,0].set_title('v9')
axs[2,1].stem(np.arange(1,61),v10)
axs[2,1].set_title('v10')
axs[2,2].stem(np.arange(1,61),v11)
axs[2,2].set_title('v11')
axs[2,3].stem(np.arange(1,61),v12)
axs[2,3].set_title('v12')

plt.show()

```

#### b) Are the elements of the collection $v_1, . . . , v_{12}$ orthogonal? What is the Euclidean norm of each element of the collection? Are these vectors linearly independent?

```python
#orthogonality
A=[]
for i in np.arange(0,12):
    aux=[]
    for j in np.arange(0,12):
        aux.append(np.dot(v_aux['arr_0'].T[i],v_aux['arr_0'].T[j]))
    A.append(aux)
```

```python
A
```

    [[10.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
     [0.0, 10.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
     [0.0, 0.0, 10.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
     [0.0, 0.0, 0.0, 10.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
     [0.0, 0.0, 0.0, 0.0, 10.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
     [0.0, 0.0, 0.0, 0.0, 0.0, 10.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
     [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 10.0, 0.0, 0.0, 0.0, 0.0, 0.0],
     [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 10.0, 0.0, 0.0, 0.0, 0.0],
     [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 10.0, 0.0, 0.0, 0.0],
     [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 10.0, 0.0, 0.0],
     [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 10.0, 0.0],
     [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 10.0]]

```python
# Eucllidean norm
np.linalg.norm(v1)
for i in np.arange(0,12):
    print('the euclidean norm of v'+str(i)+' is', np.linalg.norm(v_aux['arr_0'].T[i]))
```

    the euclidean norm of v0 is 3.1622776601683795
    the euclidean norm of v1 is 3.1622776601683795
    the euclidean norm of v2 is 3.1622776601683795
    the euclidean norm of v3 is 3.1622776601683795
    the euclidean norm of v4 is 3.1622776601683795
    the euclidean norm of v5 is 3.1622776601683795
    the euclidean norm of v6 is 3.1622776601683795
    the euclidean norm of v7 is 3.1622776601683795
    the euclidean norm of v8 is 3.1622776601683795
    the euclidean norm of v9 is 3.1622776601683795
    the euclidean norm of v10 is 3.1622776601683795
    the euclidean norm of v11 is 3.1622776601683795

```python
V=v_aux['arr_0']
import sympy
_,inds=sympy.Matrix(V).rref()
inds
```

    (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)

#### c) Using Python, find the orthogonal projection of $\pmb{u}$ onto the subspace $$\mathbf{span}\{v_1,...,v_{12}\}$$. If we let $\pmb{p}$ be the orthogonal projection, then plot the components of the vector $\pmb{u}$ and the components of the vector p on the same figure. Comment on the differences between these two signals.

```python
p=0
for i in np.arange(0,12):
    p+=v_aux['arr_0'].T[i] * np.dot(u, v_aux['arr_0'].T[i])/np.dot(v_aux['arr_0'].T[i] , v_aux['arr_0'].T[i])
```

```python
plt.figure(figsize=(30, 10))
plt.stem(np.arange(1,61),u,markerfmt='bo',label='u')
plt.stem(np.arange(1,61),p,'g',markerfmt='go',label='p')
plt.title('observed signals vector U and proyection onto V')
plt.legend()
plt.show()
```

#### d) Let $\alpha_1, . . . , \alpha_{12}$ be scalars such that $$\pmb{p} = \alpha_1v_1 + . . . + \alpha_{12}v_{12}$$. Plot the square of the magnitudes of the scalars $\alpha_1, . . . , \alpha_{12}$. Which one of these values is larger? Deduce which term of the collection $v_1, . . . , v_{12}$ contributes most to $\pmb{u}$. Explain in your own words using your answer in (a) what feature of u is represented by the largest coefficient.

```python
a1s=(-0.05228460134250296)**2
a7s=(0.061499530025728624)**2
a2s=(0.06689232208416643)**2
a8s=(0.1074850703066513)**2
a3s=(1.523886681362907)**2
a9s=(1.2134409041015142)**2
a4s=(0.0748295252979975)**2
a10s=(-0.0242966260984043)**2
a5s=(-1.0396608785455728)**2
a11s=( -1.504132216645049)**2
a6s=(-0.04397997163537463)**2
a12s=(0.0025609223494861416)**2

plt.stem(np.arange(1,13),[a1s,a2s,a3s,a4s,a5s,a6s,a7s,a8s,a9s,a10s,a11s,a12s])
plt.title('alpha squares')
plt.show()
```

```python
a12s
```

    6.55832328009762e-06

# __Problem 2__

#### To impress everyone this Christmas you decide to animate your Christmas tree based on the 3D positions of the LED lights you use for decoration. To get the 3D coordinates of the LEDs, you take photos of your Christmas tree from $N = 100$ different angles $\theta_i \in [0, \pi)$. On each photo you write the coordinates of every one of the $M = 100$ lights. The data you collect looks like Fig. 2.

#### Your store these coordinates in the text files /lights/0.txt, ..., /lights/99.txt. The first line on the file represents the angle $\theta_i$ and the posterior lines are the coordinates $x_j'$ and $z_j'$ of the positions of the lights on each image as in Fig. 2. Here the index j denotes the j-th Christmas light.

##### a) Find an expression relating the coordinates $x_j'$ and $z_j'$ of the lights on each image in terms of the “real world” positions in 3D space $x_j$ , $y_j$ , and $z_j$.

##### b) Use the expression you found to formulate a linear system to find the original positions from the projections.

```python
#create a data frame of the positions of the light and a list of the angles
degrees=[]
for i in np.arange(0,10):
    for j in np.arange(0,10):
        if i==0:
            if j==0:
                position=pd.read_csv('lights/'+str(j)+'.txt',header=None,delimiter=' ',skiprows=1,names=['x´'+str(i)+str(j),'z´'+str(i)+str(j)])

            else:
                aux=pd.read_csv('lights/'+str(j)+'.txt',header=None,delimiter=' ',skiprows=1,names=['x´'+str(i)+str(j),'z´'+str(i)+str(j)])
                position=pd.concat([position,aux],axis=1)

            d_aux=np.loadtxt('lights/'+str(j)+'.txt',max_rows=1)
        else:
            aux=pd.read_csv('lights/'+str(i)+str(j)+'.txt',header=None,delimiter=' ',skiprows=1,names=['x´'+str(i)+str(j),'z´'+str(i)+str(j)])
            position=pd.concat([position,aux],axis=1)
            d_aux=np.loadtxt('lights/'+str(i)+str(j)+'.txt',max_rows=1)

        degrees.append(float(d_aux))
```

```python
print(degrees)
position
```

    [0.0, 0.031733, 0.063467, 0.0952, 0.126933, 0.158666, 0.1904, 0.222133, 0.253866, 0.285599, 0.317333, 0.349066, 0.380799, 0.412532, 0.444266, 0.475999, 0.507732, 0.539465, 0.571199, 0.602932, 0.634665, 0.666398, 0.698132, 0.729865, 0.761598, 0.793331, 0.825065, 0.856798, 0.888531, 0.920265, 0.951998, 0.983731, 1.015464, 1.047198, 1.078931, 1.110664, 1.142397, 1.174131, 1.205864, 1.237597, 1.26933, 1.301064, 1.332797, 1.36453, 1.396263, 1.427997, 1.45973, 1.491463, 1.523196, 1.55493, 1.586663, 1.618396, 1.650129, 1.681863, 1.713596, 1.745329, 1.777063, 1.808796, 1.840529, 1.872262, 1.903996, 1.935729, 1.967462, 1.999195, 2.030929, 2.062662, 2.094395, 2.126128, 2.157862, 2.189595, 2.221328, 2.253061, 2.284795, 2.316528, 2.348261, 2.379994, 2.411728, 2.443461, 2.475194, 2.506927, 2.538661, 2.570394, 2.602127, 2.633861, 2.665594, 2.697327, 2.72906, 2.760794, 2.792527, 2.82426, 2.855993, 2.887727, 2.91946, 2.951193, 2.982926, 3.01466, 3.046393, 3.078126, 3.109859, 3.141593]

      x´00
      z´00
      x´01
      z´01
      x´02
      z´02
      x´03
      z´03
      x´04
      z´04
      ...
      x´95
      z´95
      x´96
      z´96
      x´97
      z´97
      x´98
      z´98
      x´99
      z´99

      0
      0.020634
      2.069269
      0.019342
      2.069269
      0.018030
      2.069269
      0.016700
      2.069269
      0.015354
      2.069269
      ...
      -0.025583
      2.069269
      -0.024381
      2.069269
      -0.023155
      2.069269
      -0.021906
      2.069269
      -0.020634
      2.069269

      1
      0.104137
      2.011542
      0.100874
      2.011542
      0.097509
      2.011542
      0.094046
      2.011542
      0.090488
      2.011542
      ...
      -0.116111
      2.011542
      -0.113286
      2.011542
      -0.110346
      2.011542
      -0.107296
      2.011542
      -0.104137
      2.011542

      2
      0.089024
      2.053986
      0.087387
      2.053986
      0.085662
      2.053986
      0.083850
      2.053986
      0.081954
      2.053986
      ...
      -0.094662
      2.053986
      -0.093392
      2.053986
      -0.092028
      2.053986
      -0.090572
      2.053986
      -0.089024
      2.053986

      3
      0.058903
      1.949353
      0.056726
      1.949353
      0.054492
      1.949353
      0.052203
      1.949353
      0.049861
      1.949353
      ...
      -0.066997
      1.949353
      -0.065069
      1.949353
      -0.063077
      1.949353
      -0.061021
      1.949353
      -0.058903
      1.949353

      4
      0.035450
      1.952526
      0.033050
      1.952526
      0.030616
      1.952526
      0.028152
      1.952526
      0.025659
      1.952526
      ...
      -0.044670
      1.952526
      -0.042426
      1.952526
      -0.040140
      1.952526
      -0.037814
      1.952526
      -0.035450
      1.952526

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

      95
      -0.645496
      0.092383
      -0.654067
      0.092383
      -0.661978
      0.092383
      -0.669223
      0.092383
      -0.675795
      0.092383
      ...
      0.604812
      0.092383
      0.615924
      0.092383
      0.626415
      0.092383
      0.636276
      0.092383
      0.645496
      0.092383

      96
      -0.690131
      0.069020
      -0.695072
      0.069020
      -0.699313
      0.069020
      -0.702850
      0.069020
      -0.705680
      0.069020
      ...
      0.663477
      0.069020
      0.671161
      0.069020
      0.678169
      0.069020
      0.684495
      0.069020
      0.690131
      0.069020

      97
      -0.740145
      0.085831
      -0.742680
      0.085831
      -0.744468
      0.085831
      -0.745505
      0.085831
      -0.745792
      0.085831
      ...
      0.722589
      0.085831
      0.728082
      0.085831
      0.732843
      0.085831
      0.736865
      0.085831
      0.740145
      0.085831

      98
      -0.702810
      0.094490
      -0.701729
      0.094490
      -0.699942
      0.094490
      -0.697450
      0.094490
      -0.694256
      0.094490
      ...
      0.700055
      0.094490
      0.701805
      0.094490
      0.702848
      0.094490
      0.703183
      0.094490
      0.702810
      0.094490

      99
      -0.727086
      0.015075
      -0.722476
      0.015075
      -0.717137
      0.015075
      -0.711077
      0.015075
      -0.704301
      0.015075
      ...
      0.738173
      0.015075
      0.736511
      0.015075
      0.734108
      0.015075
      0.730965
      0.015075
      0.727086
      0.015075

100 rows × 200 columns

```python
fig, axs = plt.subplots(10, 10,figsize=(20,30),sharex=True,sharey=True)
for i in np.arange(0,10):
    for j in np.arange(0,10):
        axs[i,j].plot(position['x´'+str(i)+str(j)],position['z´'+str(i)+str(j)],c='g',marker='.')
        axs[i,j].set_title('position '+str(i)+str(j))
plt.show()
```

```python
x=position['x´00']
z=position['z´00']
y=(x*np.cos(degrees[8])-position['x´08'])/np.sin(degrees[8])
```

#### c) Using Python, solve the aforementioned problem. Display the obtained positions using the function plot_tree(x,y,z).

```python
from numpy import array, mean
from matplotlib.pyplot import scatter, figure, show
def plot_tree(x,y,z):
    fig = figure()
    ax = fig.add_subplot(projection='3d')
    ax.scatter(x, y, z,c='r')
    def axisEqual3D(ax):
        extents = array([getattr(ax, 'get_{}lim'.format(dim))() for dim in 'xyz'])
        sz = extents[:,1] - extents[:,0]
        centers = mean(extents, axis=1)
        maxsize = max(abs(sz))
        r = maxsize/2
        for ctr, dim in zip(centers, 'xyz'):
            getattr(ax, 'set_{}lim'.format(dim))(ctr - r, ctr + r)
    axisEqual3D(ax)
    show()
```

```python
plot_tree(x,y,z)
```

#### d) Program a creative animation in which you change the colors of the lights based on its 3D positions. As a start, try to color the lights using their distance to a moving plane. Save your animation as a GIF or a MP4.

https://www.youtube.com/watch?v=v7eHTNm1YtU

```python
def ascendent_spiral(x,y,z):import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from mpl_toolkits.mplot3d import axes3d
```


```python
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
plt.style.use('classic')
%matplotlib inline
```

# Class 12: Introduction to the `linearsolve` Module

In general, dynamic stochastic general equilibrium (DSGE) models do not admit analytic (i.e., pencil-and-paper) solutions and they are time-consuming to work with. The `linearsolve` module approximates, solves, and simulates DSGE models and therefore makes DSGE models easier to use.

## Installing `linearsolve`

`linearsolve` is not included in the Anaconda Python installation and so before you can import it, you need to download and install the `linearsolve` package from PyPI, the Python Package Index. In Windows, open the Anaconda Prompt and in Mac, open the Terminal and run the following commmand:

    pip install linearsolve

You only have to install the package once.

```python
# Import the linearsolve under the 'ls' namespace

```

## Example: A One-Equation Model of TFP

Consider the following AR(1) specification for $\log$ TFP:

\begin{align}
\log A_{t+1} & = \rho \log A_t + \epsilon_{t+1},
\end{align}

where $\epsilon_{t+1} \sim \mathcal{N}(0,\sigma^2)$. The goal is to simulate

\begin{align}
\log (A_{t}/\bar{A}) & \approx \frac{A_t - \bar{A}}{\bar{A}},
\end{align}

where $\bar{A}$ is the nonstochastic (i.e., $\epsilon_t = 0$) steady state value of $A_t$. Let's compute the model simulation with `linearsolve`. To do this we need to do several things:

1. Create a Pandas series that stores the names of the parameters of the model.
2. Define a function that returns the equilibrium conditions of the model solved for zero.
3. Initialize an instance of the `linearsolve.model` class
4. Compute and input the steady state of the model.
5. Approximate and solve the model.
6. Compute simulations of the model.

Use the following values for the simulation:

| $\rho$ | $\sigma$ |
|--------|----------|
| 0.95   | 0.01    |

**Step 1:** Create a variable called `parameters` that stores parameter values as a Pandas Series.

```python
# Create a variable called 'parameters' that stores the model parameter values in a Pandas Series

# Print stored parameter values

```

```python
# Create a variable called 'sigma' that stores the value of sigma

```

```python
# Create variable called 'var_names' that stores the variable names in a list with state variables ordered first

# Create variable called 'shock_names' that stores an exogenous shock name for each state variable.

```

**Step 2:** Define a function called `equilibrium_equations` that evaluates the equilibrium
equations of model solved for zero. The function should accept three arguments:

1. `variables_forward`: Values of $t+1$-dated variables
2. `variables_current`: Values of $t$-dated variables
3. `parameters`: Pandas Series with the parameters for the model

The function should return a NumPy array of the model's equilibrium conditions solved for zero.

```python
# Define a function that evaluates the equilibrium conditions of the model solved for zero. PROVIDED
def equilibrium_equations(variables_forward,variables_current,parameters):

    # Create a variable called 'p' that storres the model's parameters. PROVIDED.
    p = parameters

    # Create variable called 'cur' that stores the values of current (date t) variables. PROVIDED.
    cur = variables_current

    # Create variable called 'fwd' that stores the values of one-period-ahead (date t+1) variables. PROVIDED.
    fwd = variables_forward

    # Create variable called 'tfp_proc' that returns the law of motion for TFP solved for zero (exogenous shock excluded)

    # Return equilibrium conditions stacked in a numpy array. PROVIDED.
    return np.array([

```

**Step 3:** Initialize a model instance using the `ls.model()` function. The

1. `equations`: Name of function that stores the equilibrium conditions
2. `n_states`: Number of *state* variables (i.e., variables that are *predetermined*)
3. `var_names`: List of the names of the endogenous variables
4. `shock_names`: List of the names of the exogenous shocks
5. `parameters`: Pandas Series of parameter values

```python
# Initialize the model into a variable named 'ar1_model' using the ls.model() function

```

**Step 4:** Set the steady state of the model. Either use the `.compute_ss()` method which requires an initial guess of what the steady state is. Or set the steady state `.ss` attribute directly.

```python
# Compute the steady state numerically using .compute_ss() method of ar1_model

```

```python
# Set the .ss attribute of ar1_model directly

```

**Step 5:** Compute a transform the model into a log-linear approximation around the nonstochastic steady state. Then rewrite the equilibrium conditions so that all endogenous variables are expressed as linear functions of state variables and exogenous shocks.

```python
# Appproximate and solve using the .approximate_and_solve() method of ar1_model

```

**Step 6:** Simulate the model using one of the following methods:

1. `.impulse()`: Compute impulse responses to a one-time shock to each exogenous variables. Results stored in `.irs` attribute.
2. `.stoch_sim()`: Compute stochastic simulation. Results stored in `.simulated` attribute.

First, we'll compute an impulse response simulation. Let's consider the effect of a one time shock to $\epsilon$ of 0.01 in period 5. Simulate 41 periods.

```python
# Compute impulse response of a to a one-time shock to e_a

```

The impulse response simulations are stored in the `.irs` attribute as a dictionary with keys equal to the names of the exogenous shocks.

```python
# Print ar1_model.irs

```

Let's look at the first 10 rows of the `'e_a'` element of `ar1_model.irs`.

```python
# Print first 10 rows of the element in ar1_model.irs that corresponds to the shock to TFP

```

```python
# Plot simulated impulse response to e_a

```

Next, we'll use the `.stoch_sim()` method to compute a stochastic simulation. The method takes arguments:

1. `seed`: Seed of NumPy RNG. (Optional)
2. `T`: Number of periods to simulate
3. `covMat`: Covariance matrix for exogenous shock process

The simulation should be for 201 periods.

```python
# Compute stochastic simulation

# Print the first 10 rows of `model.simulated`

```

The stochastic simulations are stored in the `.stoch_sim` attribute as a Pandas `DataFrame`.

```python
# Plot the stochastic simulation

```

## Example 2: The Stochastic Solow Growth Model Revisited

Now consider the following system of equations:

\begin{align}
Y_t & = A_t K_t^{\alpha} \\
K_{t+1} & = sY_t + (1-\delta) K_t\\
\log A_{t+1} & = \rho \log A_t + \epsilon_{t+1}
\end{align}

where $\epsilon_{t+1} \sim \mathcal{N}(0,\sigma^2)$. Let's simulate the model with `linearsolve`. Before proceding, let's also go ahead and rewrite the model with all variables moved to the lefthand side of the equations:

\begin{align}
0 & = A_t K_t^{\alpha} - Y_t \\
0 & = sY_t + (1-\delta) K_t - K_{t+1}\\
0 & = \rho \log A_t + \epsilon_{t+1} - \log A_{t+1}
\end{align}

Capital and TFP are called *state variables* because they're $t+1$ values are predetermined. Output is called a *costate* or *control* variable. Note that the model as 3 endogenous variables with 2 state variables.

Use the following values for the simulation:

| $\rho$ | $\sigma$ | $s$ | $\alpha$ | $\delta $ | $T$  |
|--------|----------|-----|----------|-----------|------|
| 0.75   | 0.006    | 0.1 | 0.35     |  0.025    | 201  |

### Initialization, Approximation, and Solution

The next several cells initialize the model in `linearsolve` and then approximate and solve it.

```python
# Create a variable called 'parameters' that stores the model parameter values in a Pandas Series

# Print the model's parameters

```

```python
# Create a variable called 'sigma' that stores the value of sigma

```

```python
# Create variable called 'var_names' that stores the variable names in a list with state variables ordered first

# Create variable called 'shock_names' that stores an exogenous shock name for each state variable.

```

```python
# Define a function that evaluates the equilibrium conditions of the model solved for zero. PROVIDED
def equilibrium_equations(variables_forward,variables_current,parameters):

    # Parameters. PROVIDED
    p = parameters

    # Current variables. PROVIDED
    cur = variables_current

    # Forward variables. PROVIDED
    fwd = variables_forward

    # Production function

    # Capital evolution

    # Exogenous tfp

    # Stack equilibrium conditions into a numpy array

```

Next, initialize the model.

```python
# Initialize the model into a variable named 'solow_model'

```

```python
# Compute the steady state numerically using .compute_ss() method of solow_model

# Print the computed steady state

```

```python
# Find the log-linear approximation around the non-stochastic steady state and solve using .approximate_and_solve() method of solow_model

```

### A Few Details About the Approximation (Optional)

The previous step constructs a log-linear approximation of the model and then solves for the endogenous variables as functions of the state variables and exogenous shocks only.

View the approximated model by calling the `.approximated()` method.

```python
# Print the log-linear approximation to the models's equilibrium conditions

```

Each variable represents the log-deviation from the steady state of the respective variable in our model. For example, the variable `y[t]` means $\log(Y_t) - \log(Y)$ in terms of the stochastic Solow model. But how do these equations relate to the original model?

The first equation appears to be:

\begin{align}
0 &= -2.1095\cdot a_t-0.7383\cdot k_t + 2.1095\cdot y_t
\end{align}

Note that dividing by 2.1095 and solving for $y_t$ yields:

\begin{align}
y_t &= a_t +0.3499\cdot k_t,
\end{align}

so the coefficient on $k_t$ appears to be close to $\alpha=0.35$. We can derive this linear equation directly. First, start with the production function:

\begin{align}
Y_t &= A_t K_t^{\alpha}.
\end{align}

Then divide both sides by steady state output:

\begin{align}
\frac{Y_t}{Y} &= \frac{A_t K_t^{\alpha}}{AK^{\alpha}} \, = \, \frac{A_t}{A}\frac{K_t^{\alpha}}{K^{\alpha}}.
\end{align}

Then, take the log of both sides:
\begin{align}
\log\left(\frac{Y_t}{Y} \right)&= \log\left(\frac{A_t}{A}\right) + \alpha\log\left(\frac{K_t}{K}\right).
\end{align}

finally, letting $y_t = \log(Y_t/Y)$, k_t = $\log(K_t/K)$, and $a_t = \log(A_t/A)$, we have:

\begin{align}
y_t &= a_t + \alpha k_t.
\end{align}

However, understanding this process isn't as important as being able to interpret the graphs and statistics that compute using the output of `linearsolve`.

### A Few Details About the Solution (Optional)

It's also worth seeing what it means for a model to be *solved*. After `linearsolve` computes the log-linear approximation to the model, it solves for each endogenous variable as a function of state variables only. View the solved model by calling the `.solved()` method.

```python
# Print the solved model

```

### Impulse Responses

Compute a 41 period impulse responses of the model's variables to a 0.01 unit shock to TFP in period 5.

```python
# Compute impulse responses

# Print the first 10 rows of the computed impulse responses.

```

```python
# Plot the computed impulse responses to a TFP shock

```

### Stochastic Simulation

Compute a 201 period stochastic simulation of the model's variables. Set the variance of $\epsilon_t$ to $\sigma^2$ and the variance of the shock to capital to 0 so that the covariance matrix for the shock process is:

\begin{align}
\text{Covariance matrix} & = \left[\begin{array}{cc} \sigma^2 & 0\\ 0 & 0\end{array} \right]
\end{align}

```python
# Compute stochastic simulation and print the simulated values.

# Print first 10 rows of model.simulated

```

```python
# Plot the computed stochastic simulation

```

```python
# Compute standard deviations of simulated TFP, output, and capital

```

```python
# Compute correlation coefficients of simulated TFP, output, and capital

```