UNIVERSIDADE FEDERAL DO PIAUÍ

CURSO DE GRADUAÇÃO EM ENGENHARIA ELÉTRICA

DISCIPLINA: TÉCNICAS DE OTIMIZAÇÃO

DOCENTE: ALDIR SILVA SOUSA

DISCENTE: MARIANA DE SOUSA MOURA

Atividade 4: Otimização Irrestrita pelo Método de Devidon-Fletcher-Powell

Resolva os exercícios usando o método de Devidon-Fletcher-Powell.

**A classe Parametros**

Esta classe tem por finalidade enviar em uma única variável, todos os parâmetros necessários para executar o método.

Para o método de Davidon-Fletcher-Powell, precisamos da função que se deseja minimizar, o intervalo de incerteza inicial e a tolerância requerida.

```python
import numpy as np
import sympy as sym

class Parametros:
  def __init__(self,f,x0,vars,eps):
    self.f = f
    self.x0 = np.array(x0)
    self.vars = vars
    self.eps = eps
```

#**Método de Newton Monovariável**

Trecho responsável por realizar a minimização do valor de lamda, que corresponde a taxa com a qual a otimização da função é feita.

```python
lmbd = sym.Symbol('lmbd')
def sym_newton(f,x1,eps):
   x  =  x1
   px = x
   k  = 1
   stop = False
   df = sym.diff(f)
   d2f = sym.diff(df)
   map = dict()

   while (not stop):
       map[lmbd]= float(x)
       jac = df.subs(map)
       hes = d2f.subs(map)
       px = x
       x = x - float(jac/hes)
       e = abs(px - x)
       k = k + 1
       if  (e < eps): stop=True
       if (k > 30): stop=True
       if (abs(jac) < eps):stop=True
   return  x

```

#**Cômputo do gradiente e avaliação das funções nos pontos**

Nesta seção, têm-se definições de funções para calcular os valores do gradiente e da hessiana utilizando-se a função em termos de variáveis simbólicas. Também realiza-se aqui, por meio dessas definições de funções, a substituição das variáveis simbólicas por valores numéricos passados como parâmetro.

```python
#Avalia a função simbólica f em x
def eval_f_simbolica(f,variaveis,x,convertToFloat=False):
   mp = dict()
   for i in range(len(variaveis)):
      mp[variaveis[i]] = x[i]
   if (convertToFloat):
      return float(f.subs(mp))
   return f.subs(mp)
   #return f.subs(mp)

def eval_f_simb(f,variaveis,x):
   mp = dict()
   for i in range(len(variaveis)):
      mp[variaveis[i]] = x[i]
   return f.subs(mp)

# Retorna o gradiente simbólico
def gradiente_simbolico(funcao,variaveis):
     g = [sym.diff(funcao,x) for x in variaveis]
     return g

# Retorna o vetor gradiente em x
def eval_gradiente(grad_simb,variaveis,x):

   g = [ eval_f_simbolica(f,variaveis,x) for f in grad_simb]
   return np.array(g)

```

#**O método Davidson-Fletcher-Powell**

Principal definição para o método. Aqui são passados como parâmetro a função, intervalo de incerteza inicial e tolerância, utilizados para realizar a otimização. Dentro desta função calcula-se a derivada e a matriz D2 simétrica resultante (que substitui a matriz hessiana do método de Newton) após diversas iterações em busca do ponto de mínimo. E, após convergência, encontra-se o valor de y, ponto de mínimo para determinada tolerância.

```python
import pandas as pd
import math

def Davidon_Fletcher_Powell(params):
    n = len(params.x0)
    x = params.x0
    D = np.eye(n,n) # gerando uma matriz identidade nxn
    vars = params.vars
    f = params.f
    eps = params.eps

    cols = ['x','y','df(x)']
    table = pd.DataFrame([], columns=cols)

    j = k = 0
    y = x

    sym_grad = gradiente_simbolico(f,vars) #criando o vetor gradiante simbólico
    G = eval_gradiente(sym_grad,vars,y) #calcula o gradiente simbólico em y
    G_ant = G
    B = np.array(G)
    tolerancia = pow(sum(pow(B,2)),1/2)
    #Passo 1 do passo principal.
    while (j < n):
          if (tolerancia<eps): #critério de parada ===> np.linalg.norm(G) equivale ao módulo
              return solucao,table

          d = -np.matmul(np.array(D),G)  #calculando   -D x G'
          #para calcular min f(y + lambda* d)
          #m = y.flatten() + lmbd*d # versão anterior
          m = y + lmbd*d

          subProb = eval_f_simb(f,vars,m)
          lmbdOtimo = sym_newton(subProb,0,0.001)

          y_ant = y
          #y = y.flatten() + lmbdOtimo*d
          y = y + lmbdOtimo*d

          if (j < (n-1)):
            #Passo 2 do passo principal
             G_ant = G
             G = eval_gradiente(sym_grad,vars,y)

             p = y - y_ant
             q = G - G_ant
             q = q.reshape(n,1)

             pt = p.transpose()
             qt = q.transpose()

             p_pt = p.dot(pt)
             pt_q = pt.dot(q)

             qt_D = np.matmul(qt,D)
             D_q = np.matmul(D,q)

             num  = np.matmul(D_q,qt_D)
             dem  = np.matmul(qt_D,q)

             D = D + p_pt/pt_q - num/dem

          else:
              D = np.eye(n,n) # gerando uma matriz identidade nxn
              j = -1
              x = y
              #print('x: ',x.flatten())
          j = j + 1

          solucao = list(y)
          B = np.array(G)
          tolerancia = pow(sum(pow(B,2)),1/2)
          row = pd.DataFrame(list([[x,y,G]]),columns=cols)
          table = table.append(row, ignore_index=True) # concatena valores de cada iteração
    return solucao,table

```

**1.** Considere o seguinte problema:

Minimizar $\sum_{i=2}^{n} [100(x_i-x^2_{i-1})^2 + (1-x_{i-1})^2]$

Resolva para n = 5, 10, e 50. Iniciando do ponto $x_0 = [-1.2,1.0,-1.2,1.0,...]$

```python
# Para n = 5
import numpy as np
import sympy as sym

variaveis = list(sym.symbols("x:5"))
c = variaveis

def f(c):
  fo = 0
  for i in range(1,5):
     fo = fo + 100*(c[i] - c[i-1]**2)**2 + (1 - c[i-1])**2
  return fo

x = []
for i in range(1,6):
    if (i%2 != 0):
        x.append(-1.2)
    else:
        x.append(1)

eps = 1e-3
p  = Parametros(f(c),x,c,eps)
y,table = Davidon_Fletcher_Powell(p)
```

```python
y
```

    [0.999955714246688,
     0.999912026734920,
     0.999824951144613,
     0.999648476789618,
     0.999295698727518]

```python
table
```

      x
      y
      df(x)

      0
      [-1.2, 1.0, -1.2, 1.0, -1.2]
      [-1.06832397392761, 0.516292149121821, -0.7995...
      [-271.227882018711, 94.2067684642814, -256.190...

      1
      [-1.2, 1.0, -1.2, 1.0, -1.2]
      [-0.793673731889360, 0.640867373737510, -0.678...
      [-0.111253017012160, 280.679602471813, -215.63...

      2
      [-1.2, 1.0, -1.2, 1.0, -1.2]
      [-0.854933688861322, 0.642036288692871, -0.540...
      [-34.1028706989055, 226.273900679618, -98.3330...

      3
      [-1.2, 1.0, -1.2, 1.0, -1.2]
      [-0.947345102043352, 0.696352955493222, -0.643...
      [-80.1028388306721, 273.467787065711, -145.789...

      4
      [-0.847182478692355, 0.539057854628998, -0.851...
      [-0.847182478692355, 0.539057854628998, -0.851...
      [-80.1028388306721, 273.467787065711, -145.789...

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

      147
      [0.999975782934895, 0.999923810190700, 0.99982...
      [0.999970972279620, 0.999921598361809, 0.99982...
      [0.00808052432370232, 0.00307923166616320, 0.0...

      148
      [0.999975782934895, 0.999923810190700, 0.99982...
      [0.999969955136985, 0.999926959881690, 0.99983...
      [0.00512027261589765, 0.00599443357663176, 0.0...

      149
      [0.999954219961018, 0.999913924161985, 0.99982...
      [0.999954219961018, 0.999913924161985, 0.99982...
      [0.00512027261589765, 0.00599443357663176, 0.0...

      150
      [0.999954219961018, 0.999913924161985, 0.99982...
      [0.999954360630490, 0.999914088847319, 0.99982...
      [-0.00223738214259583, 0.00206784432936047, 3....

      151
      [0.999954219961018, 0.999913924161985, 0.99982...
      [0.999955714246688, 0.999912026734920, 0.99982...
      [-0.000327073069968264, -0.000412633342307345,...

152 rows × 3 columns

```python
# Para n = 10
import numpy as np
import sympy as sym

variaveis = list(sym.symbols("x:10"))

c = variaveis

def f(c):
  fo = 0
  for i in range(1,10):
     fo = fo + 100*(c[i] - c[i-1]**2)**2 + (1 - c[i-1])**2
  return fo

x = []
for i in range(1,11):
    if (i%2 != 0):
        x.append(-1.2)
    else:
        x.append(1)

eps = 1e-3
p  = Parametros(f(c),x,c,eps)
y,table = Davidon_Fletcher_Powell(p)
```

```python
y
```

    [-0.993262625748586,
     0.996603910236679,
     0.998236862156483,
     0.998982143527128,
     0.999213606237084,
     0.999048392017777,
     0.998403878905059,
     0.996955573848852,
     0.993977951531375,
     0.987992351319893]

```python
table
```

      x
      y
      df(x)

      0
      [-1.2, 1.0, -1.2, 1.0, -1.2, 1.0, -1.2, 1.0, -...
      [-1.07645464892126, 0.546159934812791, -0.8243...
      [-267.925064211607, 121.823653191237, -272.138...

      1
      [-1.2, 1.0, -1.2, 1.0, -1.2, 1.0, -1.2, 1.0, -...
      [-0.810350221164158, 0.658754347153573, -0.700...
      [-2.94426344350433, 298.735656013492, -217.266...

      2
      [-1.2, 1.0, -1.2, 1.0, -1.2, 1.0, -1.2, 1.0, -...
      [-0.831462898688653, 0.608354672118218, -0.635...
      [-31.2594720058003, 227.427928816017, -158.931...

      3
      [-1.2, 1.0, -1.2, 1.0, -1.2, 1.0, -1.2, 1.0, -...
      [-0.919262672716396, 0.703747551776220, -0.658...
      [-55.7938946555927, 295.996036128778, -199.841...

      4
      [-1.2, 1.0, -1.2, 1.0, -1.2, 1.0, -1.2, 1.0, -...
      [-0.933853718696140, 0.664062351949551, -0.642...
      [-81.5719630458559, 245.493847009460, -185.124...

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

      798
      [-0.993264209878781, 0.996607331028612, 0.9982...
      [-0.993261818772765, 0.996605317242838, 0.9982...
      [0.000936506616710631, 0.00117671793321961, 0....

      799
      [-0.993261361769827, 0.996604806406364, 0.9982...
      [-0.993261361769827, 0.996604806406364, 0.9982...
      [0.000936506616710631, 0.00117671793321961, 0....

      800
      [-0.993261361769827, 0.996604806406364, 0.9982...
      [-0.993262001982900, 0.996604001980436, 0.9982...
      [0.000269716030402378, -0.000162599059706281, ...

      801
      [-0.993261361769827, 0.996604806406364, 0.9982...
      [-0.993262714877388, 0.996603626815056, 0.9982...
      [-0.000440559946468966, -0.000667923525782044,...

      802
      [-0.993261361769827, 0.996604806406364, 0.9982...
      [-0.993262625748586, 0.996603910236679, 0.9982...
      [-0.000257789040290390, -0.000326213591677060,...

803 rows × 3 columns

```python
# Para n = 50
import numpy as np
import sympy as sym

variaveis = list(sym.symbols("x:50"))

c = variaveis

def f(c):
  fo = 0
  for i in range(1,50):
     fo = fo + 100*(c[i] - c[i-1]**2)**2 + (1 - c[i-1])**2
  return fo

x = []
for i in range(1,51):
    if (i%2 != 0):
        x.append(-1.2)
    else:
        x.append(1)

eps = 1e-3
p  = Parametros(f(c),x,c,eps)
y,table = Davidon_Fletcher_Powell(p)
```

```python
y
```

```python
table
```

Este trecho foi interrompido pois depois de aproximadamente 40 min, o gradiente começou a aumentar, se distanciando, portanto, da solução. Desse modo, não houve a convergência deste método usando 50 variáveis. Isso se deve à grande quantidade de vetores extensos e necessidade de manupulação destes para resolver o problema.

**2.** Resolva:

Minimizar $(x_1 - x^3_2)^2 + 3(x_1 - x_2)^4$

```python
import numpy as np
import sympy as sym
import pandas as pd
import math

x1 = sym.Symbol('x1')
x2 = sym.Symbol('x2')
variaveis = [x1,x2]

c = variaveis

def fo(c):
     return (c[0] - c[1]**3)**2 + 3*(c[0] - c[1])**4

x = [1.2,1.5]

eps = 1e-3
nmax = 7
p  = Parametros(fo(c),x,c,eps)
y,table = Davidon_Fletcher_Powell(p)
```

```python
y
```

    [1.05453004230805, 1.01789085244265]

```python
table
```

      x
      y
      df(x)

      0
      [1.2, 1.5]
      [1.26535873347338, 1.08487975154954]
      [0.0475326522102890, 0.0107070255988813]

      1
      [1.21320466631612, 1.07585039918436]
      [1.21320466631612, 1.07585039918436]
      [0.0475326522102890, 0.0107070255988813]

      2
      [1.21320466631612, 1.07585039918436]
      [1.19385242940156, 1.07149118743402]
      [-0.0506546747373421, 0.228204703643416]

      3
      [1.17172646122159, 1.05388219082351]
      [1.17172646122159, 1.05388219082351]
      [-0.0506546747373421, 0.228204703643416]

      4
      [1.17172646122159, 1.05388219082351]
      [1.17144866336849, 1.05513369973298]
      [0.0124053367750883, 0.00275361755094361]

      5
      [1.12320180547598, 1.04187681579765]
      [1.12320180547598, 1.04187681579765]
      [0.0124053367750883, 0.00275361755094361]

      6
      [1.12320180547598, 1.04187681579765]
      [1.12082641102867, 1.04134954854492]
      [-0.0108203223147021, 0.0487749740716879]

      7
      [1.11387010795007, 1.03646728218220]
      [1.11387010795007, 1.03646728218220]
      [-0.0108203223147021, 0.0487749740716879]

      8
      [1.11387010795007, 1.03646728218220]
      [1.11378004399109, 1.03687326521452]
      [0.00352092457619746, 0.000790848090488658]

      9
      [1.08117662184621, 1.02736932370259]
      [1.08117662184621, 1.02736932370259]
      [0.00352092457619746, 0.000790848090488658]

      10
      [1.08117662184621, 1.02736932370259]
      [1.08289671544652, 1.02775568038528]
      [-0.00339378476439923, 0.0151178742600304]

      11
      [1.08229078007844, 1.02676292586343]
      [1.08229078007844, 1.02676292586343]
      [-0.00339378476439923, 0.0151178742600304]

      12
      [1.08229078007844, 1.02676292586343]
      [1.08227726430907, 1.02682313289367]
      [0.00130661544784256, 0.000293533426851037]

      13
      [1.05575092270299, 1.01850357077861]
      [1.05575092270299, 1.01850357077861]
      [0.00130661544784256, 0.000293533426851037]

      14
      [1.05575092270299, 1.01850357077861]
      [1.05573460550716, 1.01849990509227]
      [-0.000976885022259920, 0.00434844233969078]

      15
      [1.05454761510246, 1.01781263005055]
      [1.05454761510246, 1.01781263005055]
      [-0.000976885022259920, 0.00434844233969078]

      16
      [1.05454761510246, 1.01781263005055]
      [1.05453004230805, 1.01789085244265]
      [0.000373247926976385, 8.42095303715040e-5]

**3.** Resolva:

$2(x_1 - 2)^4 + (2x_1 - x_2)^2 = 4$

```python
import numpy as np
import sympy as sym

x1 = sym.Symbol('x1')
x2 = sym.Symbol('x2')
c = [x1,x2]

def fo(c):
     return (2*(c[0] - 2)**4 + (2*c[0] - c[1])**2 - 4)**2

x = [1.2,2]

eps = 1e-3
nmax = 7
p  = Parametros(fo(c),x,c,eps)
y,table = Davidon_Fletcher_Powell(p)
```

```python
y
```

    [1.97585164252150, 3.95169228507291]

```python
table
```

      x
      y
      df(x)

      0
      [1.2, 2.0]
      [1.35045430325702, 2.04822253309520]
      [-2.69247368424650, 8.40133714524466]

      1
      [1.58717567512133, 3.26408285714833]
      [1.58717567512133, 3.26408285714833]
      [-2.69247368424650, 8.40133714524466]

      2
      [1.58717567512133, 3.26408285714833]
      [1.61350853538107, 3.18191632072678]
      [2.22538489024428, 0.713194351729630]

      3
      [1.66334938968787, 3.21406511684388]
      [1.66334938968787, 3.21406511684388]
      [2.22538489024428, 0.713194351729630]

      4
      [1.66334938968787, 3.21406511684388]
      [1.65467907947995, 3.21128644413196]
      [-0.498097504414925, 1.55421796638029]

      5
      [1.76884028190128, 3.57833837067244]
      [1.76884028190128, 3.57833837067244]
      [-0.498097504414925, 1.55421796638029]

      6
      [1.76884028190128, 3.57833837067244]
      [1.77844014230379, 3.54838384293424]
      [0.423665595991962, 0.135776821850525]

      7
      [1.79211383206660, 3.55740885010328]
      [1.79211383206660, 3.55740885010328]
      [0.423665595991962, 0.135776821850525]

      8
      [1.79211383206660, 3.55740885010328]
      [1.78939250127265, 3.55653671498091]
      [-0.113956153248817, 0.355578378252645]

      9
      [1.87992678379448, 3.77612770095414]
      [1.87992678379448, 3.77612770095414]
      [-0.113956153248817, 0.355578378252645]

      10
      [1.87992678379448, 3.77612770095414]
      [1.88334732139263, 3.76545456767236]
      [0.0619049190279326, 0.0198393571460691]

      11
      [1.88533850566193, 3.76653686545886]
      [1.88533850566193, 3.76653686545886]
      [0.0619049190279326, 0.0198393571460691]

      12
      [1.88533850566193, 3.76653686545886]
      [1.88502845011569, 3.76643749851333]
      [-0.0185553956494742, 0.0579051785558165]

      13
      [1.94513359596402, 3.89279509740138]
      [1.94513359596402, 3.89279509740138]
      [-0.0185553956494742, 0.0579051785558165]

      14
      [1.94513359596402, 3.89279509740138]
      [1.94565175195275, 3.89117810600902]
      [0.00626117040587103, 0.00200635758333732]

      15
      [1.94583964753411, 3.89126541037764]
      [1.94583964753411, 3.89126541037764]
      [0.00626117040587103, 0.00200635758333732]

      16
      [1.94583964753411, 3.89126541037764]
      [1.94581875945721, 3.89125871690841]
      [-0.00194214729852917, 0.00606080576329611]

      17
      [1.96391747270432, 3.92775288327737]
      [1.96391747270432, 3.92775288327737]
      [-0.00194214729852917, 0.00606080576329611]

      18
      [1.96391747270432, 3.92775288327737]
      [1.96390861865529, 3.92778051386515]
      [0.00183362856059585, 0.000587574628222477]

      19
      [1.96398908369533, 3.92783584075431]
      [1.96398908369533, 3.92783584075431]
      [0.00183362856059585, 0.000587574628222477]

      20
      [1.96398908369533, 3.92783584075431]
      [1.96397066228906, 3.92782993773165]
      [-0.000571088491781694, 0.00178218803628849]

      21
      [1.97588815150078, 3.95157835200119]
      [1.97588815150078, 3.95157835200119]
      [-0.000571088491781694, 0.00178218803628849]

      22
      [1.97588815150078, 3.95157835200119]
      [1.97585164252150, 3.95169228507291]
      [0.000549245644752644, 0.000175999491515480]

**4** Resolva:

Minimizar $(x_1 - 3)^4 + (x_1 - 3x_2)^2$

```python
import numpy as np
import sympy as sym

x1 = sym.Symbol('x1')
x2 = sym.Symbol('x2')
c = [x1,x2]

def fo(c):
     return (c[0] - 3)**4 + (c[0] - 3*c[1])**2

x = [1.5,2]

eps = 1e-3
p  = Parametros(fo(c),x,c,eps)
m,table = Davidon_Fletcher_Powell(p)
```

```python
m
```

    [2.93741017755409, 0.979101208908746]

```python
table
```

      x
      y
      df(x)

      0
      [1.5, 2.0]
      [2.49073861653863, 0.811113660153645]
      [-0.413506695085297, -0.344385816466160]

      1
      [2.59276775978705, 0.894972839719642]
      [2.59276775978705, 0.894972839719642]
      [-0.413506695085297, -0.344385816466160]

      2
      [2.59276775978705, 0.894972839719642]
      [2.59183614644195, 0.894196952832449]
      [-0.453506112200395, 0.544528272332393]

      3
      [2.74263279272075, 0.901051456174850]
      [2.74263279272075, 0.901051456174850]
      [-0.453506112200395, 0.544528272332393]

      4
      [2.74263279272075, 0.901051456174850]
      [2.73579601838682, 0.909260423631364]
      [-0.0577402132943634, -0.0480884849563665]

      5
      [2.76960744565758, 0.931544682153159]
      [2.76960744565758, 0.931544682153159]
      [-0.0577402132943634, -0.0480884849563665]

      6
      [2.76960744565758, 0.931544682153159]
      [2.76453410742791, 0.927319392095910]
      [-0.0870689972292755, 0.104544413158933]

      7
      [2.85458636521515, 0.946312279202572]
      [2.85458636521515, 0.946312279202572]
      [-0.0870689972292755, 0.104544413158933]

      8
      [2.85458636521515, 0.946312279202572]
      [2.85149513186943, 0.950023946803551]
      [-0.0102537418634556, -0.00853974875268904]

      9
      [2.85881779993772, 0.954846185400209]
      [2.85881779993772, 0.954846185400209]
      [-0.0102537418634556, -0.00853974875268904]

      10
      [2.85881779993772, 0.954846185400209]
      [2.85757286055431, 0.953809347367007]
      [-0.0192671803741185, 0.0231310892802803]

      11
      [2.90546968553353, 0.968758160828806]
      [2.90546968553353, 0.968758160828806]
      [-0.0192671803741185, 0.0231310892802803]

      12
      [2.90546968553353, 0.968758160828806]
      [2.90572372089813, 0.968453180308314]
      [-0.00262335666411673, -0.00218507983910143]

      13
      [2.90767793213993, 0.969752598632971]
      [2.90767793213993, 0.969752598632971]
      [-0.00262335666411673, -0.00218507983910143]

      14
      [2.90767793213993, 0.969752598632971]
      [2.90733229760538, 0.969464708288713]
      [-0.00530671710150354, 0.00637096356457789]

      15
      [2.93694741896715, 0.979656772365430]
      [2.93694741896715, 0.979656772365430]
      [-0.00530671710150354, 0.00637096356457789]

      16
      [2.93694741896715, 0.979656772365430]
      [2.93741017755409, 0.979101208908746]
      [-0.000767677325407945, -0.000639304967091192]

**Conclusões**

O método Quasi-Newton, Davidon-Fletcher-Powell, consegue uma convergência mais rápida que o método do Gradiente Conjugado, Fletcher-Revees, sem tanta variação pela região de busca, levando em geral menos iterações com uma precisão razoável.

O problema envolvendo 5 e 10 variáveis foi solucionado mais rapidamente pelo método Quasi-Newton, porém em ambos, o tempo levado para 50 variáveis era inviáveis e em algumas tentativas, o resultado divergia da solução, principalmente no caso do Gradiente Conjugado, ao qual apresenta bastante oscilação em torno da solução.

O método do Gradiente Conjugado obtém uma precisão muito alta e consegue a convergência, mesmo lenta, quando se trabalha com uma maior número de variáveis. Pode acontecer de chegar a oscilar muito em torno da resposta e não convergir quando os valores são muito distantes da solução ou a precisão requerida não for muito grande.


# Neuro-Fuzzy Classifier and MNIST
In this notebook I will constructing the neuro-fuzzy classifier in Keras and testing its performance on the original MNIST data.

## Loading the Training Data
The MNIST data is downloaded from http://yann.lecun.com/exdb/mnist/ and kept in the same directory as this notebook. The images and labels will be loaded into NumPy arrays for further processing.

```python
import gzip
import numpy as np

def load_data(filename, dims):
    with gzip.open(filename, "rb") as infile:
        # consume magic number
        infile.read(4)
        # consume dimensions data
        infile.read(4 * len(dims))

        return np.frombuffer(infile.read(np.prod(dims)), dtype=np.uint8).reshape(dims)

train_images = load_data("data/mnist/train-images-idx3-ubyte.gz", [60000, 28, 28])
train_labels = load_data("data/mnist/train-labels-idx1-ubyte.gz", [60000])

test_images = load_data("data/mnist/t10k-images-idx3-ubyte.gz", [10000, 28, 28])
test_labels = load_data("data/mnist/t10k-labels-idx1-ubyte.gz", [10000])
```

Convert the image data from unsigned bytes ranging from 0-255 to 32-bit floating-point numbers ranging from 0-1.

```python
train_images = train_images.astype(np.float) / 255
test_images = test_images.astype(np.float) / 255
```

Plot an image from the training images and labels to ensure that the data was loaded correctly.

```python
import matplotlib.pyplot as plt

%matplotlib inline
plt.imshow(train_images[0,:,:], cmap="Greys", interpolation="none")
print("The digit {}:".format(train_labels[0]))
```

## Fuzzy C-Means Clustering
The membership functions for the neuro-fuzzy classifier are initialized using fuzzy c-means clustering. Fuzzy clustering can be more difficult to apply in high dimensional spaces, and can often fail to find an appropriate solution. The dimension reduction technique UMAP will be used to visualize the MNIST data and the fuzzy centers in a 2D feature space.

As shown below, the data forms a distinct cluster for each digit.

The UMAP computations were done using this package: https://github.com/lmcinnes/umap

```python
from umap import UMAP

# reshape the training images into vectors
data = train_images.reshape(60000, 28 * 28)

# create the mapping and apply it
reducer = UMAP(n_neighbors=10, min_dist=0.001, random_state=42).fit(data)
embedded = reducer.transform(data)

# plot the data
plt.figure(figsize=(16, 12))
plt.scatter(embedded[:,0], embedded[:,1], s=1, c=train_labels, cmap='Spectral')
plt.colorbar(boundaries=np.arange(11)-0.5).set_ticks(np.arange(10))
```

Next, we attempt fuzzy clustering using a random initialization.

```python
import skfuzzy as skf

# apply fuzzy clustering
centers, memberships, u0, d, jm, p, fpc = skf.cmeans(data.T, 10, 2.0, 1e-8, 1000, seed=0)

# plot the loss
plt.figure(figsize=(16, 4))
plt.plot(np.arange(p)+1, jm)

# plot data with centers
plt.figure(figsize=(16, 12))
plt.scatter(embedded[:,0], embedded[:,1], s=1, c=train_labels, cmap='Spectral')

embedded_centers = reducer.transform(centers)
plt.scatter(embedded_centers[:,0], embedded_centers[:,1], s=100, c="k", marker="^")

centroid = reducer.transform(np.mean(data, axis=0, keepdims=True)).squeeze()
plt.scatter(centroid[0], centroid[1], s=100, c="r", marker="^")
```

As the plots above show, the fuzzy clustering was unsuccessful when initialized randomly. After only 2 iterations the loss function reaches a local minimum and gets stuck. All of the clusters (represented by the black triangles) are all gathered near the center of mass of the MNIST training data (represented by the red triangle).

More information about why the algorithm fails can be found in this paper: https://www.researchgate.net/publication/225005392_Fuzzy_C-Means_in_High_Dimensional_Spaces

### Initializing the Membership Matrix
Since a random initialization fails, I will investigate using the training labels to create an initial membership matrix that will allow the algorithm to converge to a more appropriate solution.

```python
init_memberships = np.zeros((10, 60000))
init_memberships[train_labels,np.arange(60000)] = 1

centers, memberships, u0, d, jm, p, fpc = skf.cmeans(
    data.T, 10, 2.0, 1e-8, 1000, init=init_memberships, seed=0)

# plot the loss
plt.figure(figsize=(16, 4))
plt.plot(np.arange(p)+1, jm)

# plot data with centers
plt.figure(figsize=(16, 12))
plt.scatter(embedded[:,0], embedded[:,1], s=1, c=train_labels, cmap='Spectral')

embedded_centers = reducer.transform(centers)
plt.scatter(embedded_centers[:,0], embedded_centers[:,1], s=100, c="k", marker="^")
```

Even with the smarter initialization, the fuzzy clustering algorithm still moves the cluster centers to the center of gravity of the entire dataset.

Another, solution to investigate set the exponents variable according to the following equation.

\begin{align}
\omega & = \frac{n + 2}{n}
\end{align}

This leads to better results in high dimensions However, this is effectively makes each cluster less fuzzy and more crisp.

```python
omega = 1 + 2 / 784

centers, memberships, u0, d, jm, p, fpc = skf.cmeans(
    data.T, 10, omega, 1e-8, 1000, init=init_memberships, seed=0)

# plot the loss
plt.figure(figsize=(16, 4))
plt.plot(np.arange(p)+1, jm)

# plot data with centers
plt.figure(figsize=(16, 12))
plt.scatter(embedded[:,0], embedded[:,1], s=1, c=train_labels, cmap='Spectral')

embedded_centers = reducer.transform(centers)
plt.scatter(embedded_centers[:,0], embedded_centers[:,1], s=100, c="k", marker="^")
```

While the results above are still not ideal (some of the centers appear to share the same clusters), they are much better than the results achieved initially. We can also compare the membership values returned by the cmeans function and the true memberships to get an idea of how well the clustering performed.

```python
pred_labels = np.argmax(memberships, axis=0)
accuracy = np.mean(train_labels == pred_labels)

print("Clustering Accuracy: {:.2%}".format(accuracy))
```

    Clustering Accuracy: 55.55%

Using the data points, centers and membership values we can fit the value of sigma for each membership function. The method used replicates MATLABs genfis function and is based on source code found at the following links.

http://en.verysource.com/code/5248161_1/invgaussmf4sigma.m.html

http://read.pudn.com/downloads215/sourcecode/math/1013060/Fuzzy_part1/genfis3.m__.htm

```python
data = train_images.reshape(60000, 28 * 28).T

def compute_sigma(data, memberships, centers):
    # remove any points with a membership of one
    mask = np.any(memberships >= 1.0, axis=0)
    data = data[:,~mask]
    memberships = memberships[:,~mask]

    data = np.expand_dims(data, axis=0)
    memberships = np.expand_dims(memberships, axis=1)
    centers = np.expand_dims(centers, axis=2)

    return np.mean(np.sqrt(-np.square(data - centers) / (2 * np.log(memberships))), axis=2)

# parameters for the membership functions
mu = centers
sigma = compute_sigma(data, memberships, centers)

plt.figure(figsize=(16, 4))
plt.boxplot(mu.reshape(-1), vert=False)

plt.figure(figsize=(16, 4))
plt.boxplot(sigma.reshape(-1), vert=False)
```

While the values for mu are within a reasonable range, the values for sigma contain lots of zeros (which means membership values will be zero) and lots of extremely large numbers (which will give membership values of ~1 always). The parameters for sigma cannot be used in the model and will lead to computational problems due to the limited precision of floating point numbers.

```python
plt.figure()
fig, ax_array = plt.subplots(8, 8, figsize=(16, 8), sharey=True)
axes = ax_array.flatten()
for i, ax in enumerate(axes):
    ax.bar(np.arange(10), memberships[:,i])

plt.setp(axes, xticks=[], yticks=[0, 1], frame_on=True)
plt.tight_layout(h_pad=0.5, w_pad=0.01)
```

The plots above are bar graphs of the membership values for the 64 points in the training data. Because of the small exponent used the membership values essentially mimic crisp clustering. This is the reason for such extreme sigma values.

## Creating the Model
The model will be constructed and analyzed in NumPy to allow for easier inspection of the activations and gradients. Below the model's parameters are defined.

```python
# mu and sigma are also parameters
W = np.random.normal(0, 0.1, size=(10, 10))
b = np.full((1, 10), 0.1)
```

The first layer applies the membership functions to the inputs.

```python
# use first 100 samples to speed up computation
x = train_images.reshape(-1, 784)[0:100,:]

def first_layer(x):
    # reshape and tile the inputs
    x_reshaped = np.expand_dims(x, 1)
    x_tiled = np.tile(x_reshaped, (1, 10, 1))

    return np.exp(-0.5 * np.square((x_tiled - mu) / sigma))

membership_val = first_layer(x)

print(np.sum(np.isnan(membership_val)))
print(np.size(membership_val))
```

    67000
    784000

    /home/ryan-desktop/anaconda3/envs/keras/lib/python3.7/site-packages/ipykernel_launcher.py:9: RuntimeWarning: divide by zero encountered in true_divide
      if __name__ == '__main__':
    /home/ryan-desktop/anaconda3/envs/keras/lib/python3.7/site-packages/ipykernel_launcher.py:9: RuntimeWarning: invalid value encountered in true_divide
      if __name__ == '__main__':

The values found for sigma lead to NaN values in the first layer activations. Random normal numbers will be used as placeholders until better initial values can be generated.

```python
mu = np.random.normal(0.5, 0.1, size=(10, 784))
sigma = np.random.normal(1.0, 0.1, size=(10, 784))

membership_val = first_layer(x)

plt.figure(figsize=(16, 4))
plt.boxplot(membership_val.reshape(-1), vert=False)
```

The random parameters used above give reasonable values for the activations in the first layer. The second layer activations represent the firing strength of each rule. Similar ANFIS we will be using multiplication as the t-norm operator.

```python
def second_layer(x):
    return np.prod(x, 2)

firing_strength = second_layer(membership_val)

plt.figure(figsize=(16, 4))
plt.boxplot(firing_strength.reshape(-1), vert=False)
```

This layer causes problems for numerical stability. The multiplication of 784 numbers less than 1 causes extremely small activations in the second layer. This will have to be rectified by some form of normalization.

```python
def third_layer(x):
    return (x - np.mean(x, 1, keepdims=True)) / np.std(x, 1, keepdims=True)

normalized = third_layer(firing_strength)

plt.figure(figsize=(16, 4))
plt.boxplot(normalized.reshape(-1), vert=False)
```

Normalizing the activations to a standard normal distribution gives much more reasonable values while still preserving their magnitudes relative to each other. The classifiers output is a linear combination of normalized firing strength of each rule, with a sigmoid activation applied to bound the result between 0 and 1. Loss will be calculated using binary cross entropy.

```python
def sigmoid(x):
    return 1 / (1 + np.exp(-x))

def final_layer(x):
    logits = np.matmul(x, W) + b
    return sigmoid(logits)

def loss(target, output):
    y = target
    y_ = output

    return -(y * np.log(y_) + (1 - y) * np.log(1 - y_))

output = final_layer(normalized)

plt.figure(figsize=(16, 4))
plt.boxplot(output.reshape(-1), vert=False)
```

Next, the gradients will be computed and inspected for phenomema such as vanishing gradients.

```python
def grad_logits_fn(target, output):
    return output - target

# one-hot training data
train_target = np.zeros((60000, 10))
train_target[np.arange(60000),train_labels] = 1

grad_logits = grad_logits_fn(train_target[0:100,:], output)

plt.figure(figsize=(16, 4))
plt.boxplot(grad_logits.reshape(-1), vert=False)
```

```python
# gradients for affine transformation
def grad_W_fn(grad_logits, normalized):
    return np.matmul(normalized.T, grad_logits) / normalized.shape[0]

def grad_b_fn(grad_logits):
    return np.mean(grad_logits, axis=0, keepdims=True)

grad_W = grad_W_fn(grad_logits, normalized)
grad_b = grad_b_fn(grad_logits)

plt.figure(figsize=(16, 4))
plt.boxplot(grad_W.reshape(-1), vert=False)

plt.figure(figsize=(16, 4))
plt.boxplot(grad_b.reshape(-1), vert=False)
```

```python
def grad_normalized_fn(grad_logits):
    return np.matmul(grad_logits, W.T)

grad_normalized = grad_normalized_fn(grad_logits)

plt.figure(figsize=(16, 4))
plt.boxplot(grad_normalized.reshape(-1), vert=False)
```

```python
def grad_fstrength_fn(grad_normalized, normalized, firing_strength):
    s = np.std(firing_strength, 1, keepdims=True)
    N = normalized.shape[0]

    delta = (1 - 1 / N) / s - np.square(normalized) / (s * (N - 1))
    return delta * grad_normalized

grad_fstrength = grad_fstrength_fn(grad_normalized, normalized, firing_strength)

plt.figure(figsize=(16, 4))
plt.boxplot(grad_fstrength.reshape(-1), vert=False)
```

```python
def grad_membership_fn(grad_fstrength, firing_strength, membership_val):
    return np.expand_dims(grad_fstrength * firing_strength, 2) / membership_val

grad_membership = grad_membership_fn(grad_fstrength, firing_strength, membership_val)

plt.figure(figsize=(16, 4))
plt.boxplot(grad_membership.reshape(-1), vert=False)
```

```python
x_tiled = np.tile(np.expand_dims(x, 1), (1, 10, 1))

def grad_mu_fn(grad_membership, mu, sigma, x_tiled, membership_val):
    delta = (x_tiled - mu) / np.square(sigma) * membership_val
    return np.mean(delta * grad_membership, axis=0)

def grad_sigma_fn(grad_membership, mu, sigma, x_tiled, membership_val):
    delta = np.square(x_tiled - mu) / (sigma * sigma * sigma) * membership_val
    return np.mean(delta * grad_membership, axis=0)

grad_mu = grad_mu_fn(grad_membership, mu, sigma, x_tiled, membership_val)
grad_sigma = grad_sigma_fn(grad_membership, mu, sigma, x_tiled, membership_val)

plt.figure(figsize=(16, 4))
plt.boxplot(grad_mu.reshape(-1), vert=False)

plt.figure(figsize=(16, 4))
plt.boxplot(grad_sigma.reshape(-1), vert=False)
```

All of the gradients appear to have a reasonable magnitude, so vanishing gradients should not be a problem when training this network.

```python
train_x = train_images.reshape(-1, 784)
train_y = train_target

iterations = 1000
batch_size = 64
learning_rate = 0.005

def training_batch(batch_size):
    batch_idx = np.random.randint(0, 60000, size=(batch_size))
    return train_x[batch_idx, :], train_y[batch_idx, :]

def update(param, grad):
    return param - learning_rate * grad

train_loss = []
train_accuracy = []

mu = centers
sigma = np.random.normal(1.0, 0.1, size=(10, 784))

W = np.random.normal(0, 0.1, size=(10, 10))
b = np.full((1, 10), 0.1)

for i in range(iterations):
    batch_x, batch_y = training_batch(batch_size)

    # forward pass
    membership_val = first_layer(batch_x)
    firing_strength = second_layer(membership_val)
    normalized = third_layer(firing_strength)
    y = final_layer(normalized)

    # calculate loss
    loss_val = np.mean(loss(batch_y, y))
    train_loss.append(loss_val)

    accuracy = np.mean(np.argmax(y, axis=1) == np.argmax(batch_y, axis=1))
    train_accuracy.append(accuracy)

    # backward pass
    grad_logits = grad_logits_fn(batch_y, y)
    grad_W = grad_W_fn(grad_logits, normalized)
    grad_b = grad_b_fn(grad_logits)

    grad_normalized = grad_normalized_fn(grad_logits)
    grad_fstrength = grad_fstrength_fn(grad_normalized, normalized, firing_strength)
    grad_membership = grad_membership_fn(grad_fstrength, firing_strength, membership_val)

    x_tiled = np.tile(np.expand_dims(batch_x, 1), (1, 10, 1))
    grad_mu = grad_mu_fn(grad_membership, mu, sigma, x_tiled, membership_val)
    grad_sigma = grad_sigma_fn(grad_membership, mu, sigma, x_tiled, membership_val)

    # gradient updates
    W = update(W, grad_W)
    b = update(b, grad_b)
    mu = update(mu, grad_mu)
    sigma = update(sigma, grad_sigma)

plt.figure(figsize=(16, 4))
plt.plot(np.arange(iterations), train_loss)

plt.figure(figsize=(16, 4))
plt.plot(np.arange(iterations), train_accuracy)
```

```python
test_x = test_images.reshape(-1, 784)
test_y = np.zeros((10000, 10))
test_y[np.arange(10000),test_labels] = 1

# forward pass
membership_val = first_layer(test_x)
firing_strength = second_layer(membership_val)
normalized = third_layer(firing_strength)
y = final_layer(normalized)

# calculate loss
loss_val = np.mean(loss(test_y, y))
print("testing loss: {:.3f}".format(loss_val))

accuracy = np.mean(np.argmax(y, axis=1) == np.argmax(test_y, axis=1))
print("testing accuracy: {:.2%}".format(accuracy))
```

    testing loss: 0.311
    testing accuracy: 58.51%

```python

```


# Selección óptima de portafolios II

Entonces, tenemos que:
- La LAC describe las posibles selecciones de riesgo-rendimiento entre un activo libre de riesgo y un activo riesgoso.
- Su pendiente es igual al radio de Sharpe del activo riesgoso.
- La asignación óptima de capital para cualquier inversionista es el punto tangente de la curva de indiferencia del inversionista con la LAC.

Para todo lo anterior, supusimos que ya teníamos el portafolio óptimo (activo riesgoso).

En la clase pasada aprendimos a hallar este portafolio óptimo si el conjunto de activos riesgosos estaba conformado únicamente por dos activos:

$$w_{1,EMV}=\frac{(E[r_1]-r_f)\sigma_2^2-(E[r_2]-r_f)\sigma_{12}}{(E[r_2]-r_f)\sigma_1^2+(E[r_1]-r_f)\sigma_2^2-((E[r_1]-r_f)+(E[r_2]-r_f))\sigma_{12}}.$$

- Sin embargo, la complejidad del problema crece considerablemente con el número de variables, y la solución analítica deja de ser viable cuando mencionamos que un portafolio bien diversificado consta aproximadamente de 50-60 activos.
- En esos casos, este problema se soluciona con rutinas numéricas que hagan la optimización por nosotros, porque son una solución viable y escalable a más variables.

**Objetivos:**
- ¿Cuál es el portafolio óptimo de activos riesgosos cuando tenemos más de dos activos?
 - ¿Cómo construir la frontera de mínima varianza cuando tenemos más de dos activos?

*Referencia:*
- Notas del curso "Portfolio Selection and Risk Management", Rice University, disponible en Coursera.
___

## 1. Maximizando el radio de Sharpe

### ¿Qué pasa si tenemos más de dos activos riesgosos?

En realidad es algo muy similar a lo que teníamos con dos activos.
- Para dos activos, construir la frontera de mínima varianza es trivial: todas las posibles combinaciones.
- Con más de dos activos, recordar la definición: la frontera de mínima varianza es el lugar geométrico de los portafolios que proveen el mínimo riesgo para un nivel de rendimiento dado.

 Ver en el tablero.

Analíticamente:
- $N$ activos,
- caracterizados por $(\sigma_i,E[r_i])$,
- cada uno con peso $w_i$, con $i=1,2,\dots,N$.

Entonces, buscamos los pesos tales que

\begin{align}
\min_{w_1,\dots,w_n} & \quad \sum_{i=1}^{N}w_i^2\sigma_i^2+\sum_{i=1}^{N}\sum_{j=1,j\neq i}^{N}w_iw_j\sigma_{ij}\\
\text{s.a.}          & \quad \sum_{i=1}^{N}w_i=1, w_i\geq0\\
                     & \quad \sum_{i=1}^{N}w_iE[r_i]=\bar{\mu},
\end{align}

donde $\bar{\mu}$ corresponde a un nivel de rendimiento objetivo.

**Obviamente, tendríamos que resolver este problema para muchos niveles de rendimiento objetivo.**

-  Explicar relación con gráfica.

-  Recordar clase 10.

Lo anterior se puede escribir vectorialmente como:

\begin{align}
\min_{\boldsymbol{w}} & \quad \boldsymbol{w}^T\Sigma\boldsymbol{w}\\
\text{s.a.}          & \quad \boldsymbol{1}^T\boldsymbol{w}=1, \boldsymbol{w}\geq0\\
                     & \quad E[\boldsymbol{r}^T]\boldsymbol{w}=\bar{\mu},
\end{align}

donde:
- $\boldsymbol{w}=\left[w_1,\dots,w_N\right]^T$ es el vector de pesos,
- $\boldsymbol{1}=\left[1,\dots,1\right]^T$ es un vector de unos,
- $E[\boldsymbol{r}]=\left[E[r_1],\dots,E[r_N]\right]^T$ es el vector de rendimientos esperados, y
- $\Sigma=\left[\begin{array}{cccc}\sigma_{1}^2 & \sigma_{12}  & \dots  & \sigma_{1N} \\
                                   \sigma_{21}  & \sigma_{2}^2 & \dots  & \sigma_{2N} \\
                                   \vdots       & \vdots       & \ddots & \vdots      \\
                                   \sigma_{N1}  & \sigma_{N2}  & \dots  & \sigma_{N}^2\end{array}\right]$ es la matriz de varianza-covarianza.

**Esta última forma es la que comúnmente usamos al programar, por ser eficiente y escalable a problemas de N variables.**

### Entonces, ¿para cuántos niveles de rendimiento objetivo tendríamos que resolver el anterior problema con el fin de graficar la frontera de mínima varianza?
- Observar que el problema puede volverse muy pesado a medida que incrementamos el número de activos en nuestro portafolio...
- Una tarea bastante compleja.

### Sucede que, en realidad, sólo necesitamos conocer dos portafolios que estén sobre la *frontera de mínima varianza*.
- Si logramos encontrar dos portafolios sobre la frontera, entonces podemos a la vez encontrar todas las posibles combinaciones de estos dos portafolios para trazar la frontera de mínima varianza.
- Ver el caso de dos activos.

### ¿Qué portafolios usar?
Hasta ahora, hemos estudiando profundamente como hallar dos portafolios muy importantes que de hecho yacen sobre la frontera de mínima varianza:
1. Portafolio de EMV: máximo SR.
2. Portafolio de mínima varianza: básicamente, el mismo problema anterior, sin la restricción de rendimiento objetivo.

Luego, tomar todas las posibles combinaciones de dichos portafolios usando las fórmulas para dos activos de medias y varianzas:
- w: peso para el portafolio EMV,
- 1-w: peso para le portafolio de mínima varianza.

## 2. Ejemplo ilustrativo.

Retomamos el ejemplo de mercados de acciones en los países integrantes del $G5$: EU, RU, Francia, Alemania y Japón.

```python
# Importamos pandas y numpy
import pandas as pd
import numpy as np
```

```python
# Resumen en base anual de rendimientos esperados y volatilidades
annual_ret_summ = pd.DataFrame(columns=['EU', 'RU', 'Francia', 'Alemania', 'Japon'], index=['Media', 'Volatilidad'])
annual_ret_summ.loc['Media'] = np.array([0.1355, 0.1589, 0.1519, 0.1435, 0.1497])
annual_ret_summ.loc['Volatilidad'] = np.array([0.1535, 0.2430, 0.2324, 0.2038, 0.2298])

annual_ret_summ.round(4)
```

      EU
      RU
      Francia
      Alemania
      Japon

      Media
      0.1355
      0.1589
      0.1519
      0.1435
      0.1497

      Volatilidad
      0.1535
      0.243
      0.2324
      0.2038
      0.2298

```python
# Matriz de correlación
corr = pd.DataFrame(data= np.array([[1.0000, 0.5003, 0.4398, 0.3681, 0.2663],
                                    [0.5003, 1.0000, 0.5420, 0.4265, 0.3581],
                                    [0.4398, 0.5420, 1.0000, 0.6032, 0.3923],
                                    [0.3681, 0.4265, 0.6032, 1.0000, 0.3663],
                                    [0.2663, 0.3581, 0.3923, 0.3663, 1.0000]]),
                    columns=annual_ret_summ.columns, index=annual_ret_summ.columns)
corr.round(4)
```

      EU
      RU
      Francia
      Alemania
      Japon

      EU
      1.0000
      0.5003
      0.4398
      0.3681
      0.2663

      RU
      0.5003
      1.0000
      0.5420
      0.4265
      0.3581

      Francia
      0.4398
      0.5420
      1.0000
      0.6032
      0.3923

      Alemania
      0.3681
      0.4265
      0.6032
      1.0000
      0.3663

      Japon
      0.2663
      0.3581
      0.3923
      0.3663
      1.0000

```python
# Tasa libre de riesgo
rf = 0.05
```

Esta vez, supondremos que tenemos disponibles todos los mercados de acciones y el activo libre de riesgo.

#### 1. Construir la frontera de mínima varianza

##### 1.1. Encontrar portafolio de mínima varianza

```python
# Importamos scipy

```

```python
## Construcción de parámetros
# 1. Sigma: matriz de varianza-covarianza

# 2. Eind: rendimientos esperados activos individuales

```

```python
# Función objetivo

```

```python
# Dato inicial

# Cotas de las variables

# Restricciones

```

```python
# Portafolio de mínima varianza

```

```python
# Pesos, rendimiento y riesgo del portafolio de mínima varianza

```

##### 1.2. Encontrar portafolio EMV

```python
# Función objetivo

```

```python
# Dato inicial

# Cotas de las variables

# Restricciones

```

```python
# Portafolio EMV

```

```python
# Pesos, rendimiento y riesgo del portafolio EMV

```

    (0.14205760808891876, 0.13854972654766612)

##### 1.3. Construir frontera de mínima varianza

También debemos encontrar la covarianza (o correlación) entre estos dos portafolios:

```python
# Covarianza entre los portafolios

```

```python
# Correlación entre los portafolios

```

```python
# Vector de w

```

```python
# DataFrame de portafolios:
# 1. Índice: i
# 2. Columnas 1-2: w, 1-w
# 3. Columnas 3-4: E[r], sigma
# 4. Columna 5: Sharpe ratio

```

```python
# Importar librerías de gráficos

```

```python
# Gráfica de dispersión de puntos coloreando
# de acuerdo a SR

```

**A partir de lo anterior, solo restaría construir la LAC y elegir la distribución de capital de acuerdo a las preferencias (aversión al riesgo).**
___

## 3. Comentarios finales

### 3.1. Restricciones adicionales

Los inversionistas pueden tener restricciones adicionales:
1. Restricciones en posiciones cortas.
2. Pueden requerir un rendimiento mínimo.
3. Inversión socialmente responsable: prescinden de inversiones en negocios o paises considerados éticamente o políticamente indeseables.

Todo lo anterior se puede incluir como restricciones en el problema de optimización, y puede ser llevado a cabo a costa de un cociente de Sharpe menor.

### 3.2. Críticas a la optimización media varianza
1. Solo importan medias y varianzas: recordar que la varianza subestima el riesgo en algunos casos.
2. Preferencias media-varianza tratan las ganancias y pérdidas simétricamente: el sentimiento de insatisfacción de una perdida es mayor al sentimiento de satisfacción de una ganancia (aversión a pérdidas).
3. La aversión al riesgo es constante: la actitud frente al riesgo puede cambiar, por ejemplo con el estado de la economía.
4. Horizonte corto (un periodo).
5. Basura entra - basura sale: la optimización media varianza es supremamente sensible a las entradas: estimaciones de rendimientos esperados y varianzas.
___

# Anuncios parroquiales

## 1. Revisar archivo Tarea 6.
## 2. Fin de módulo. Abrir Clase0 y revisar objetivos del módulo.
## 3. [Nota interesante](http://yetanothermathprogrammingconsultant.blogspot.com/2016/08/portfolio-optimization-maximize-sharpe.html)

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


# TODO
- how to post data? small data file?

# Predicting NBA game wins

In this notebook, we'll form a simple model to predict NBA game winners based on which teams are playing, whether they are playing at home or away, and which players are playing in the game and for how long.

The goal is to demonstrate implementing a simple (but non-standard) regression model with CVXPY.

# Imports and helper functions

In the cell below, we define some helper functions to load the data, manipulate it, format it, and plot the results.

The actual optimization code will be given in-line later in the notebook.

```
import os
import json
import datetime
from collections import defaultdict
import numpy as np
import cvxpy as cvx
import matplotlib.pyplot as plt
import matplotlib
matplotlib.rcParams.update({'font.size': 14})

class PlayerAppearance(object):
    __slots__= "name", "id", "minutes", 'team'

class TeamAppearance(object):
    __slots__ = 'id', 'score', 'players', 'home', 'gameid'

class Game(object):
    __slots__ = 'winner', 'loser', 'id', 'season'

def get_game_data():
    '''Return a list of game objects from `gamestats` directory.'''
    games = []

    for fn in os.listdir('gamestats'):
         if fn.endswith('.json'):
            with open('gamestats/'+fn) as f:
                a = json.load(f)

                hometeamid = int(a['resultSets'][0]['rowSet'][0][6])
                game = Game()
                game.season = int(a['resultSets'][0]['rowSet'][0][8])
                game.id = int(a['resultSets'][0]['rowSet'][0][2])

                teams = [TeamAppearance(), TeamAppearance()]
                for i, jteam in enumerate(a['resultSets'][1]['rowSet']):
                    teams[i].id = str(jteam[4])
                    teams[i].score = jteam[21]
                    teams[i].home = jteam[3] == hometeamid
                    teams[i].gameid = game.id
                    teams[i].players = []

                for jplayer in a['resultSets'][4]['rowSet']:
                    if jplayer[8] == None or jplayer[8] == 0.0:
                        continue
                    player = PlayerAppearance()
                    player.name = str(jplayer[5])
                    player.id = jplayer[4]
                    minutes = jplayer[8].split(':')
                    minutes = float(minutes[0]) + float(minutes[1])/60.0
                    player.minutes = minutes
                    player.team = str(jplayer[2])

                    for team in teams:
                        if player.team == team.id:
                            team.players.append(player)

                if teams[0].score > teams[1].score:
                    game.winner, game.loser = teams[0], teams[1]
                else:
                    game.winner, game.loser = teams[1], teams[0]

                games.append(game)
    return games

def index_mappings(games):
    '''Return mappings between player and game identifiers and an indexed ordering.

    Puts teams and players appearing in `games` in an arbitrary order.
    This is the ordering which will be used to determine the relationship between
    matrix rows in the matrix-stuffing step.

    Returns:
    teams    --  ordered list of teams
                 (index -> team)
    players  --  ordered list of player pairs: (player name, unique player ID)
                 (index -> player)
    team2idx --  mapping team to index
                 (team -> index)
    pid2idx  --  mapping from unique player id to index
                 (player -> index)
    '''
    teams = set()
    players = set()
    for game in games:
        for team in game.winner, game.loser:
            teams.add(team.id)
            for player in team.players:
                players.add((player.name,player.id))

    teams = list(teams)
    players = list(players)

    team2idx = {t: i for i, t in enumerate(teams)}
    pid2idx = {t[1]: i for i, t in enumerate(players)}

    return teams, players, team2idx, pid2idx

def split_games(games,p=0.3):
    '''Return games split into disjoint training and hold-out sets.'''

    games_holdout = np.random.choice(len(games),size=int(len(games)*p),replace=False)

    # training set
    games_t = [games[i] for i in range(len(games)) if i not in games_holdout]

    # hold-out set
    games_h = [games[i] for i in games_holdout]

    return games_t, games_h,

def data_mats(games, team2idx,home=False,pid2idx=[]):
    '''Stuff feature matrices for fitting model.

    Features included in the matrices depend on the function inputs.

    data_mats(games, team2idx) -- Team identities are the only features.
        That is, W_ij = 1 if team j won game i. 0 otherwise.

    data_mats(games, team2idx, home=True) -- Appends a column to W and L corresponding to
        whether the team was the home team.

    data_mats(games, team2idx,home=True,pid2idx=pid2idx) -- Appends columns
        to W and L corresponding to which players played and what fraction
        of 48 minutes they played in the game. That is, *if* j corresponds
        to a player column, then W_ij = t/48, where t is how many minutes
        player j (from the winning team) played in game i.
    '''
    m, n = len(games), len(team2idx)
    W, L = np.zeros((m,n)), np.zeros((m,n))

    Wh, Lh = np.zeros((m,1)), np.zeros((m,1))

    n = len(pid2idx)
    Wp, Lp = np.zeros((m,n)), np.zeros((m,n))

    for i, game in enumerate(games):
        for A, Ah, Ap, team in [(W, Wh, Wp, game.winner),(L, Lh, Lp, game.loser)]:
            A[i,team2idx[team.id]] = 1
            if team.home:
                Ah[i] = 1
            if len(pid2idx) > 0:
                for player in team.players:
                    Ap[i,pid2idx[player.id]] = player.minutes/48

    if home:
        W = np.hstack([W, Wh])
        L = np.hstack([L, Lh])
    if len(pid2idx) > 0:
        W = np.hstack([W, Wp])
        L = np.hstack([L, Lp])

    return W, L

def percent_correct(W,L,w):
    '''Return percentage correctly predicted for games (W,L) and model parameters w.

    W and L can be from training or hold-out data.
    '''
    m, n = W.shape
    return float(sum((W-L).dot(w) > 0))/m

def reg_path(games,holdout,gammas,N, home=False, playertime=False):
    '''Plot a regularization path.

    For each of N times, partition games into a training
    and hold-out set, with `holdout` giving the hold-out proportion.
    For each gamma in gammas, train the model on the training set
    and evaluate it on both the training and hold out sets.

    Plot a data point for each instance with gamma on the x-axis
    and the percent correct on the y-axis. Also plot a line showing
    the average for each gamma.

    Optional arguments:
    home -- include home team feature
    playertime -- include player minutes played features
    '''
    v_t = []
    v_h = []

    teams, players, team2idx, pid2idx = index_mappings(games)
    if playertime is False:
        pid2idx = []

    for i in range(N):
        print '\nPartition {}'.format(i),
        games_t, games_h = split_games(games, holdout)
        W, L = data_mats(games_t, team2idx, home, pid2idx)
        Wh, Lh = data_mats(games_h, team2idx, home, pid2idx)
        for gamma in gammas:
            print '.',
            x = fit(W,L,gamma)
            v_t.append((gamma,percent_correct(W,L,x)))
            v_h.append((gamma,percent_correct(Wh,Lh,x)))

    alpha = .3
    fig, ax = plt.subplots(2, 1, sharex=True)
    fig.set_size_inches((10,10))
    a,b = zip(*v_t)
    ax[0].scatter(a,b,alpha=alpha)
    ax[0].set_ylabel('Training set % correct')
    a,b = zip(*v_h)
    ax[1].scatter(a,b,alpha=alpha)
    ax[1].set_ylabel('Test set % correct')
    ax[1].set_xlabel('Regularization $\gamma$')

    for i, path in enumerate([v_t,v_h]):
        avg = defaultdict(float)
        tot = defaultdict(float)
        for gamma, pred in path:
            avg[gamma] += pred
            tot[gamma] += 1.0

        for gamma in avg.keys():
            avg[gamma] /= tot[gamma]

        gammas = sorted(avg.keys())
        vals = [avg[key] for key in gammas]
        ax[i].plot(gammas, vals, 'r' )

    ax[0].legend(('average','data partition instance'))

    return v_t, v_h
```

# Raw data

Below, we load the NBA game data from the 'gamestats' folder. We have games from NBA seasons 2010-2013. For each game, we identify the winning and losing team and the NBA season in which the game was played. For each team appearance in a game, we have the score, a list of player appearances, and whether the team played at home or away. For each player appearance in a game, we have the player's name, a unique player ID, and the number of minutes they played in the game.

# Model

We will train our model on a set of $m$ games.
The data point for game $i$ consists of an ordered pair of feature vectors, $(x^w_i, x^l_i) \in \mathbf{R}^n \times \mathbf{R}^n$.
The first vector in the pair represents the winning team. Feature vectors consist of $n$ features which may include which team is playing, if they are the home team, and how many minutes each player on the team played.

We want to find model parameters $w$ for the classifier function $f_w(x,y) = (x-y)^T w$.
The model predicts that a team with features $x$ will beat a team with features $y$ if $f_w(x,y) > 0$.
Note that the classification inequality is homogenous in $w$.
That is, the classifier is invariant to scalings of $w$.

To properly predict all games, we would need $f_w(x^w_i, x^l_i) > 0$ for all games $i$.
This set of homogenous strict inequalities in $w$ is feasible if and only if
there is some scaling $\tilde{w} = \alpha w$ such that
$f_{\tilde{w}}(x^w_i, x^l_i) \geq 1$ for all $i$. This motivates the loss function which we'll use:

\begin{equation}
L(w) = \frac{1}{m}\sum_{i=1}^m \max \lbrace 1 - (x^w_i - x^l_i)^T w, 0 \rbrace.
\end{equation}

The function $L(w)$ assigns a positive loss whenever the classification inequality $f_w(x^w_i, x^l_i) \geq 1$
is violated.

We'll choose $w$ by minimizing the loss $L(w)$, plus a regularization term $\gamma \| w \|_2$.
The regularization term is used to prevent over-fitting.

We use the `fit(W,L,gamma)` function defined below to solve the optimization problem

\begin{array}{ll}
\mbox{minimize} & L(w) + \gamma \| w \|_2,
\end{array}

where $W,L \in \mathbf{R}^{m \times n}$ are data matrices, with row $i$ of $W$ ($L$) corresponding to $x^w_i$ ($x^l_i$).

```
def fit(W,L,gamma):
    '''Return model parameters w trained on data (W,L) with regularization gamma.'''
    m,n = W.shape
    w = cvx.Variable(n)

    objective = cvx.sum_entries(cvx.pos(1 - (W-L)*w))/m + gamma*cvx.norm(w)
    objective = cvx.Minimize( objective )
    prob = cvx.Problem(objective)
    result = prob.solve(solver=cvx.ECOS, verbose=False)
    if prob.status != 'optimal':
        print "ERROR!"
    return np.array(w.value).flatten()
```

# Example

Load the list of games from the `gamestats` directory and select only the games from a single season.

```
games = get_game_data()

# select just games in a single season
games = [game for game in games if game.season == 2010]
len(games)
```

    1230

Choose some ordering of the teams and players for indexing purposes, and get a mapping between the index and unique identifiers.

```
teams, players, team2idx, pid2idx = index_mappings(games)
```

Randomly split the games so that 70% are in a training set and 30% are in a hold-out set. Produce the data matrices $W$ and $L$ for the training and hold-out sets. Use features giving the team id, if they are the home team, and the time played for each player.

```
np.random.seed(2)
games_t, games_h = split_games(games, .3)

W, L = data_mats(games_t, team2idx, True, pid2idx)
Wh, Lh = data_mats(games_h, team2idx, True, pid2idx)
```

Train the model with **no** regularization on the training set and evaluate the classification performance on both the training and hold-out set.

```
gamma = 0
w = fit(W,L,gamma)

print "Training set %% correct: %f"%percent_correct(W,L,w)
print "Hold-out set %% correct: %f"%percent_correct(Wh,Lh,w)
```

    Training set % correct: 1.000000
    Hold-out set % correct: 0.582656

We can see that we've over fit the model because the classification is perfect on the training set, but low on the hold-out set. Adding some regularization should improve the predictive performance on the hold-out set:

```
gamma = .03
w = fit(W,L,gamma)

print "Training set %% correct: %f"%percent_correct(W,L,w)
print "Hold-out set %% correct: %f"%percent_correct(Wh,Lh,w)
```

    Training set % correct: 0.786295
    Hold-out set % correct: 0.682927

# Regularization paths

We'll find good choices for the regularization hyper-parameter $\gamma$ by plotting a regularization path.
We'll randomly partition the data into training and hold-out sets and plot the percentage of games predicted correctly over a range of choices of $\gamma$. We'll do this several times for each choice of $\gamma$ and plot the average result.

We'll plot the regularization path over different sets of features. First, we'll just use team identities, then we'll add whether the team played at home, and then we'll add the times played of each player.

We should see an increase in predictive power as we add features.

Note that some of these examples may take a few minutes to run.

## Team identities

When the features include just the team identities, the model reduces to just assigning a ranking to each team. This model is so simple that it is hard to over-fit it on our data, so regularization has little effect.

```
np.random.seed(0)
gammas = np.linspace(0,1,20)
v_t, v_h = reg_path(games,.3,gammas,5,False, False)
```

## Identities + home/away

We add a single feature denoting whether the teams played at home. This doesn't add much to our model complexity, so it is still difficult to over-fit. The predictive power is about the same as the previous model.

```
np.random.seed(0)
gammas = np.linspace(0,.1,50)
v_t, v_h = reg_path(games,.3,gammas,5,True, False)
```

## Identities + home/away + player minutes

We add in the number of minutes each player played in each game. This adds many variables to the model and thus makes it much easier to over-fit. We can see that the classification is perfect on the training set, which suggests over fitting. We find that we get better performance on the hold-out set with some added regularization.

```
np.random.seed(0)
gammas = np.linspace(0,.2,40)
v_t, v_h = reg_path(games,.3,gammas,5,True, True)
```


# Econometrics HW_11

**11510691 程远星$\DeclareMathOperator*{\argmin}{argmin}
\DeclareMathOperator*{\argmax}{argmax}
\DeclareMathOperator*{\plim}{plim}
\newcommand{\ffrac}{\displaystyle \frac}
\newcommand{\d}[1]{\displaystyle{#1}}
\newcommand{\space}{\text{ }}
\newcommand{\bspace}{\;\;\;\;}
\newcommand{\bbspace}{\;\;\;\;\;\;\;\;}
\newcommand{\QQQ}{\boxed{?\:}}
\newcommand{\void}{\left.\right.}
\newcommand{\Tran}[1]{{#1}^{\mathrm{T}}}
\newcommand{\CB}[1]{\left\{ #1 \right\}}
\newcommand{\SB}[1]{\left[ #1 \right]}
\newcommand{\P}[1]{\left( #1 \right)}
\newcommand{\abs}[1]{\left| #1 \right|}
\newcommand{\norm}[1]{\left\| #1 \right\|}
\newcommand{\given}[1]{\left. #1 \right|}
\newcommand{\using}[1]{\stackrel{\mathrm{#1}}{=}}
\newcommand{\asim}{\overset{\text{a}}{\sim}}
\newcommand{\RR}{\mathbb{R}}
\newcommand{\EE}{\mathbb{E}}
\newcommand{\II}{\mathbb{I}}
\newcommand{\NN}{\mathbb{N}}
\newcommand{\ZZ}{\mathbb{Z}}
\newcommand{\QQ}{\mathbb{Q}}
\newcommand{\PP}{\mathbb{P}}
\newcommand{\AcA}{\mathcal{A}}
\newcommand{\FcF}{\mathcal{F}}
\newcommand{\AsA}{\mathscr{A}}
\newcommand{\FsF}{\mathscr{F}}
\newcommand{\dd}{\mathrm{d}}
\newcommand{\I}[1]{\mathrm{I}\left( #1 \right)}
\newcommand{\N}[1]{\mathcal{N}\left( #1 \right)}
\newcommand{\Exp}[1]{\mathrm{E}\left[ #1 \right]}
\newcommand{\Var}[1]{\mathrm{Var}\left[ #1 \right]}
\newcommand{\Avar}[1]{\mathrm{Avar}\left[ #1 \right]}
\newcommand{\Cov}[1]{\mathrm{Cov}\left( #1 \right)}
\newcommand{\Corr}[1]{\mathrm{Corr}\left( #1 \right)}
\newcommand{\ExpH}{\mathrm{E}}
\newcommand{\VarH}{\mathrm{Var}}
\newcommand{\AVarH}{\mathrm{Avar}}
\newcommand{\CovH}{\mathrm{Cov}}
\newcommand{\CorrH}{\mathrm{Corr}}
\newcommand{\ow}{\text{otherwise}}
\newcommand{\wp}{\text{with probability }}
\newcommand{\FSD}{\text{FSD}}
\newcommand{\SSD}{\text{SSD}}$**

## Question 14.3

$\P{\text i}$

$\bspace \forall \, t$, $\Exp{v_{it}} = 0 \Rightarrow \Exp{e_{it}} = \Exp{v_{it} - \theta \bar v_i} = 0 - \theta \cdot \Exp{\bar v_i} = 0$

$\P{\text{ii}}$

$$\begin{align}
\Var{e_{it}} &= \Var{v_{it} - \theta \cdot \bar v_i}\\
&= \Var{v_{it}} + \theta^2 \cdot\Var{\bar v_i} - 2\theta \cdot \Cov{v_{it},\bar v_i}\\
&= \Var{a_i} + \Var{u_{it}} + \theta^2 \cdot\P{\Var{a_i} + \Var{\bar u_i}} - 2\theta \cdot \Cov{a_i + u_{it},a_i + \bar u_i}\\
&= \sigma_a^2 + \sigma_u^2 + \theta^2\P{\sigma_a^2 + \ffrac{\sigma_u^2}{T}} - 2\theta \P{\sigma_a^2 + \ffrac{\sigma_u^2}{T}}\\
&= \sigma_a^2 + \sigma_u^2 + \P{1 + \ffrac{\sigma_u^2}{\sigma_u^2 + T\sigma_a^2} - 2\sqrt{\ffrac{\sigma_u^2}{\sigma_u^2 + T\sigma_a^2}}}\P{\sigma_a^2 + \ffrac{\sigma_u^2}{T}} - 2\P{1 - \sqrt{\ffrac{\sigma_u^2}{\sigma_u^2 + T\sigma_a^2}}} \P{\sigma_a^2 + \ffrac{\sigma_u^2}{T}}\\
&= \sigma_u^2
\end{align}$$

$\P{\text{iii}}$

$$\begin{align}
\Cov{e_{it},e_{is}}&= \Cov{v_{it}-\theta\bar v_i,v_{is}-\theta\bar v_i}\\
&= \Exp{e_{it}\cdot e_{is}} - \Exp{e_{it}}\cdot \Exp{e_{is}}\\
&= \Exp{e_{it}\cdot e_{is}}\\
&= \Exp{v_{it}\cdot v_{is}} - \lambda \P{\Exp{\bar v_i \cdot v_{is}} + \Exp{v_{it} \cdot \bar v_i}} + \lambda^2 \Exp{\bar v_i^2}\\
&= \sigma_a^2 - 2\theta \P{\sigma_a^2 + \ffrac{\sigma_u^2}{T}} + \theta^2\P{\sigma_a^2 + \ffrac{\sigma_u^2}{T}}\\
&= \sigma_a^2 - \sigma_a^2 = 0,\bspace t \neq s
\end{align}$$

***


## The SEAIRQ model

In the present notebook, we consider the stochastic version of the SEAIRQ model.

In the SEAIRQ model we consider, there are the following classes:

* **$S$** Susceptible
* **$E$** Exposed
* **$A$** Asymptomatic infected (this class is between $E$ and $I_a$, $I_s$, and models the time an individual is already infectious, but it is not clear yet whether the individual will remain asymptomatic)
* **$I_a$** Asymptomatic infected
* **$I_s$** Symptomatic infected
* **$R$** Recovered
* **$Q$** Quarantined

In the SEAIR model, once an individual is exposed, it undergoes the transitions

\begin{equation}
    E \rightarrow A \rightarrow I_x \rightarrow R,
\end{equation}

where $I_x$ is either $I_a$ or $I_s$.

In the SEAIRQ model, there is a finite rate from the states $S$, $E$, $A$, $I_a$, $I_s$ to a quarantined class $Q$, in which an individual does not contribute to any infection rates anymore.

# Outline of this notebook

In this notebook we

1. Run and visualise a single simulation of the stochastic SEAIR model, obtained from the SEAIRQ model by setting all rates to the quarantine state $Q$ to zero.
2. Compare the result of 1. to a SEAIRQ model, in which only symptomatic infectives can get quarantined.
3. Compare the results of 1. and 2. to a SEAIRQ model, in which one could get quarantined in the states $E$, $A$, $I_a$, $I_s$.

For all cases, we also run 200 simulations to estimate the relevance of the stochastic nature of the dynamics.

# 0. Definition of the model

```python
%matplotlib inline
import numpy as np
import pyross
import matplotlib.pyplot as plt
#from matplotlib import rc; rc('text', usetex=True)
```

```python
M = 1                  # the SIR model has no age structure
Ni = 10000*np.ones(M)   # so there is only one age group
N = np.sum(Ni)         # and the total population is the size of this age group

#
alpha = 0              # fraction of asymptomatic infectives
beta  = 0.2            # infection rate

gE    = 1.             # progression rate from E to A
gA   = 0.2            # rate to go from A to Ia
gIa   = 0.1            # recovery rate of asymptomatic infectives
gIs   = 0.1            # recovery rate of symptomatic infectives
gE    = 0.04           # recovery rate of E
fsa   = 1              # the self-isolation parameter

E0  = np.array([0])
A0  = np.array([1])
Ia0 = np.array([0])     # the SIR model has only one kind of infective
Is0 = np.array([20])     # we take these to be symptomatic
Q0  = np.array([0])
R0  = np.array([0])     # and assume there are no recovered individuals initially
S0  = N-(Ia0+Is0+R0+E0)    # The initial susceptibles are obtained from S + E + A + Ia + Is + R = N

# there is no contact structure
def contactMatrix(t):
    return np.identity(M)

# duration of simulation and number of datapoints
Tf = 500;  Nt=Tf+1;
```

# 1. SEAIR model

```python
# For the SEAIR model, we set all rates to the Q-class to zero
tS    = 0.0            # rate S  -> Q
tE    = 0.00           # rate E  -> Q
tA    = 0.00           # rate A  -> Q
tIa   = 0.00           # rate Ia -> Q
tIs   = 0.00           # rate Is -> Q
#tIs   = 0.05           # rate Is -> Q

# instantiate model
parameters = {'alpha':alpha, 'beta':beta,
              'gE':gE,'gA':gA,
              'gIa':gIa, 'gIs':gIs, 'gE':gE, 'fsa':fsa,
             'tE':tE,'tA':tA,'tIa':tIa,'tIs':tIs}
model = pyross.stochastic.SEAIRQ(parameters, M, Ni)

# simulate model
data=model.simulate(S0, E0, A0, Ia0, Is0, Q0, contactMatrix, Tf, Nt)
```

```python
# plot the results of the simulation

S  = data['X'][:,0].flatten()
E  = data['X'][:,1].flatten()
A  = data['X'][:,2].flatten()
Ia = data['X'][:,3].flatten()
Is = data['X'][:,4].flatten()
Q  = data['X'][:,5].flatten()
t = data['t']

fig = plt.figure(num=None, figsize=(10, 8), dpi=80, facecolor='w', edgecolor='k')
plt.rcParams.update({'font.size': 22})

plt.fill_between(t, 0, S/N, color="#348ABD", alpha=0.3)
plt.plot(t, S/N, '-', color="#348ABD", label='$S$', lw=4)

plt.plot(t, E/N, '-', color="green", label='$E$', lw=4)
plt.fill_between(t, 0, E/N, color='green', alpha=0.3)

plt.fill_between(t, 0, A/N, color='#A60628', alpha=0.3)
plt.plot(t, A/N, '-', color='#A60628', label='$A$', lw=4)

plt.fill_between(t, 0, (Ia+Is)/N, color='orange', alpha=0.1)
plt.plot(t, (Ia+Is)/N, '-', color='orange', label='$I$', ls='--',lw=4)

R=N-S-E-A-Ia-Is; plt.fill_between(t, 0, R/N, color="dimgrey", alpha=0.3)
plt.plot(t, R/N, '-', color="dimgrey", label='$R$', lw=4)

plt.legend(fontsize=26); plt.grid()
plt.xlabel(r'time')
plt.ylabel(r'fraction of total population')
plt.autoscale(enable=True, axis='x', tight=True)
```

### Averaging over several simulations

We now run 200 simulations, and consider the total number of susceptibles at the end of the simulation

```python
# run 200 simulations, calculate mean and standard deviation

# This might take a while.
N_runs = 200
trajectories_SEAIR = np.zeros([N_runs,Nt,6],dtype=float)
for i in range(N_runs):
    print('Running simulation {0} of {1}'.format(i+1,N_runs),end='\r')
    cur_result = model.simulate(S0, E0, A0, Ia0, Is0, Q0, contactMatrix, Tf, Nt)
    trajectories_SEAIR[i] = cur_result['X']
t_SEAIR = cur_result['t']
```

    Running simulation 200 of 200

```python
S_final_SEAIR = trajectories_SEAIR[:,-1,0]/N

mean_S_final_SEAIR = np.mean( S_final_SEAIR )
std_S_final_SEAIR = np.std( S_final_SEAIR )

hist, bin_edges = np.histogram(S_final_SEAIR, bins= 10,density=True)
bin_centers = bin_edges[1:] + bin_edges[:-1]
bin_centers /= 2

fig,ax = plt.subplots(1,1,figsize=(10,6))
fig.suptitle('Number of susceptibles at final time')
ax.bar(bin_centers,hist,width=bin_centers[1]-bin_centers[0],
       color='dodgerblue',label='Simulations',
      alpha=0.7)
ax.axvline(mean_S_final_SEAIR,ls='--',lw=4,color='red',
         label= 'Mean')
ax.locator_params(axis='x', nbins=6)
ax.legend() #loc=8)
ax.set_xlabel(r'S')
ax.set_ylabel(r'$P(S)$')
plt.show(fig)
#fig.savefig('S_distribution_SEAIR.png',bbox_inches='tight',dpi=100)
plt.close(fig)

print('At final time, on average {0:3.1f}% of the population has never been infected.'.format(mean_S_final_SEAIR*100))
```

# 2. SEAIRQ model

We now set the rate at which symptomatic infectives get quarantined to $1/20$ days. All other classes stay as before, i.e. never get quarantined.

(Technically, this is similar to changing the self-isolation parameter "fsa".)

```python
# We now consider a nonzero rate Is -> Q
tS    = 0.0            # rate S  -> Q
tE    = 0.00           # rate E  -> Q
tA    = 0.00           # rate A  -> Q
tIa   = 0.00           # rate Ia -> Q
tIs   = 0.05           # rate Is -> Q

# instantiate model
parameters = {'alpha':alpha, 'beta':beta,
              'gE':gE,'gA':gA,
              'gIa':gIa, 'gIs':gIs, 'gE':gE, 'fsa':fsa,
             'tE':tE,'tA':tA,'tIa':tIa,'tIs':tIs}
model = pyross.stochastic.SEAIRQ(parameters, M, Ni)

# simulate model
data=model.simulate(S0, E0, A0, Ia0, Is0, Q0, contactMatrix, Tf, Nt)
```

```python
# plot the results of the simulation

S  = data['X'][:,0].flatten()
E  = data['X'][:,1].flatten()
A  = data['X'][:,2].flatten()
Ia = data['X'][:,3].flatten()
Is = data['X'][:,4].flatten()
Q  = data['X'][:,5].flatten()
t = data['t']

fig = plt.figure(num=None, figsize=(10, 8), dpi=80, facecolor='w', edgecolor='k')
plt.rcParams.update({'font.size': 22})

plt.fill_between(t, 0, S/N, color="#348ABD", alpha=0.3)
plt.plot(t, S/N, '-', color="#348ABD", label='$S$', lw=4)

plt.plot(t, E/N, '-', color="green", label='$E$', lw=4)
plt.fill_between(t, 0, E/N, color='green', alpha=0.3)

plt.fill_between(t, 0, A/N, color='#A60628', alpha=0.3)
plt.plot(t, A/N, '-', color='#A60628', label='$A$', lw=4)

plt.fill_between(t, 0, (Ia+Is)/N, color='orange', alpha=0.1)
plt.plot(t, (Ia+Is)/N, '-', color='orange', label='$I$', ls='--',lw=4)

R=N-S-E-A-Ia-Is; plt.fill_between(t, 0, R/N, color="dimgrey", alpha=0.3)
plt.plot(t, R/N, '-', color="dimgrey", label='$R$', lw=4)

plt.legend(fontsize=26); plt.grid()
plt.xlabel(r'time')
plt.ylabel(r'fraction of total population')
plt.autoscale(enable=True, axis='x', tight=True)
```

```python
# run 200 simulations, calculate mean and standard deviation

# This might take a while
N_runs = 200
trajectories_SEAIRQ1 = np.zeros([N_runs,Nt,6],dtype=float)
for i in range(N_runs):
    print('Running simulation {0} of {1}'.format(i+1,N_runs),end='\r')
    cur_result = model.simulate(S0, E0, A0, Ia0, Is0, Q0, contactMatrix, Tf, Nt)
    trajectories_SEAIRQ1[i] = cur_result['X']
t_SEAIRQ1 = cur_result['t']
```

    Running simulation 200 of 200

```python
S_final_SEAIRQ1 = trajectories_SEAIRQ1[:,-1,0]/N

mean_S_final_SEAIRQ1 = np.mean( S_final_SEAIRQ1 )
std_S_final_SEAIRQ1 = np.std( S_final_SEAIRQ1 )

hist, bin_edges = np.histogram(S_final_SEAIRQ1, bins= 10,density=True)
bin_centers = bin_edges[1:] + bin_edges[:-1]
bin_centers /= 2

fig,ax = plt.subplots(1,1,figsize=(10,6))
fig.suptitle('Number of susceptibles at final time')
ax.bar(bin_centers,hist,width=bin_centers[1]-bin_centers[0],
       color='dodgerblue',label='Simulations',
      alpha=0.7)
ax.axvline(mean_S_final_SEAIRQ1,ls='--',lw=4,color='red',
         label= 'Mean')
ax.locator_params(axis='x', nbins=6)
ax.legend() #loc=8)
ax.set_xlabel(r'S')
ax.set_ylabel(r'$P(S)$')
plt.show(fig)
#fig.savefig('S_distribution_SEAIRQ1.png',bbox_inches='tight',dpi=100)
plt.close(fig)

print('At final time, on average {0:3.1f}% of the population has never been infected.'.format(mean_S_final_SEAIRQ1*100))
```

By comparing this to the result from 1, we see that if symptomatic infectives are quarantined with a rate $1/20$ days, then the fraction of the population that never gets exposed increases by about 12%.

# 3. SEAIRQ model with more nonzero rates to quarantine class

We now set the rate at which symptomatic infectives get quarantined to $1/20$ days, and the rates at which the $E$, $A$, $I_a$, $I_s$ get quarantined to $1/50$ days.

```python
# For the SEAIR model, we set all rates to the Q-state to zero
tS    = 0.0            # rate S  -> Q
tE    = 0.02           # rate E  -> Q
tA    = 0.02           # rate A  -> Q
tIa   = 0.02           # rate Ia -> Q
tIs   = 0.05           # rate Is -> Q

# instantiate model
parameters = {'alpha':alpha, 'beta':beta,
              'gE':gE,'gA':gA,
              'gIa':gIa, 'gIs':gIs, 'gE':gE, 'fsa':fsa,
             'tE':tE,'tA':tA,'tIa':tIa,'tIs':tIs}
model = pyross.stochastic.SEAIRQ(parameters, M, Ni)

# simulate model
data=model.simulate(S0, E0, A0, Ia0, Is0, Q0, contactMatrix, Tf, Nt)
```

```python
# plot the results of the simulation

S  = data['X'][:,0].flatten()
E  = data['X'][:,1].flatten()
A  = data['X'][:,2].flatten()
Ia = data['X'][:,3].flatten()
Is = data['X'][:,4].flatten()
Q  = data['X'][:,5].flatten()
t = data['t']

fig = plt.figure(num=None, figsize=(10, 8), dpi=80, facecolor='w', edgecolor='k')
plt.rcParams.update({'font.size': 22})

plt.fill_between(t, 0, S/N, color="#348ABD", alpha=0.3)
plt.plot(t, S/N, '-', color="#348ABD", label='$S$', lw=4)

plt.plot(t, E/N, '-', color="green", label='$E$', lw=4)
plt.fill_between(t, 0, E/N, color='green', alpha=0.3)

plt.fill_between(t, 0, A/N, color='#A60628', alpha=0.3)
plt.plot(t, A/N, '-', color='#A60628', label='$A$', lw=4)

plt.fill_between(t, 0, (Ia+Is)/N, color='orange', alpha=0.1)
plt.plot(t, (Ia+Is)/N, '-', color='orange', label='$I$', ls='--',lw=4)

R=N-S-E-A-Ia-Is; plt.fill_between(t, 0, R/N, color="dimgrey", alpha=0.3)
plt.plot(t, R/N, '-', color="dimgrey", label='$R$', lw=4)

plt.legend(fontsize=26); plt.grid()
plt.xlabel(r'time')
plt.ylabel(r'fraction of total population')
plt.autoscale(enable=True, axis='x', tight=True)
```

We now observe qualitatively different behaviour as compared to 1 and 2. There is no clear peak in the number of exposed anymore, and the number of susceptibles decreases slowly. The processes has not reached a steady state yet, even after 500 days.

We now replot only the $E$, $A$, and $I = I_a + I_s$ classes, which are hardly visible in the above plot:

```python
# plot only the E, A, and I = Ia + Is classes

fig = plt.figure(num=None, figsize=(10, 8), dpi=80, facecolor='w', edgecolor='k')
plt.rcParams.update({'font.size': 22})

#plt.fill_between(t, 0, S/N, color="#348ABD", alpha=0.3)
#plt.plot(t, S/N, '-', color="#348ABD", label='$S$', lw=4)

plt.plot(t, E/N, '-', color="green", label='$E$', lw=4)
plt.fill_between(t, 0, E/N, color='green', alpha=0.3)

plt.fill_between(t, 0, A/N, color='#A60628', alpha=0.3)
plt.plot(t, A/N, '-', color='#A60628', label='$A$', lw=4)

plt.fill_between(t, 0, (Ia+Is)/N, color='orange', alpha=0.1)
plt.plot(t, (Ia+Is)/N, '-', color='orange', label='$I$', ls='--',lw=4)

#R=N-S-E-A-Ia-Is; plt.fill_between(t, 0, R/N, color="dimgrey", alpha=0.3)
#plt.plot(t, R/N, '-', color="dimgrey", label='$R$', lw=4)

plt.legend(fontsize=26); plt.grid()
plt.xlabel(r'time')
plt.ylabel(r'fraction of total population')
plt.autoscale(enable=True, axis='x', tight=True)
```

As can be seen, the population fraction in these classes stays very low.

We now consider 200 simulations, and look at mean and standard deviation of the classes $E$, $A$, $I_a$, $I_s$:

```python
# run 200 simulations, calculate mean and standard deviation

N_runs = 200
trajectories_SEAIRQ2 = np.zeros([N_runs,Nt,6],dtype=float)
for i in range(N_runs):
    print('Running simulation {0} of {1}'.format(i+1,N_runs),end='\r')
    cur_result = model.simulate(S0, E0, A0, Ia0, Is0, Q0, contactMatrix, Tf, Nt)
    trajectories_SEAIRQ2[i] = cur_result['X']
t_SEAIRQ2 = cur_result['t']
```

    Running simulation 200 of 200

```python
# plot means and standard deviations

mean_SEAIRQ2 = np.mean(trajectories_SEAIRQ2,axis=0)
std_SEAIRQ2 = np.std(trajectories_SEAIRQ2,axis=0)

E_SEAIRQ2 = mean_SEAIRQ2[:,1].flatten()
A_SEAIRQ2 = mean_SEAIRQ2[:,2].flatten()
# I = I_asymptomatic + I_symptomatic
I_SEAIRQ2 = np.mean(trajectories_SEAIRQ2[:,:,3] + trajectories_SEAIRQ2[:,:,4] ,axis=0).flatten()

dE_SEAIRQ2 = std_SEAIRQ2[:,1].flatten()
dA_SEAIRQ2 = std_SEAIRQ2[:,2].flatten()
dI_SEAIRQ2 =  np.std(trajectories_SEAIRQ2[:,:,3] + trajectories_SEAIRQ2[:,:,4],axis=0).flatten()

fig = plt.figure(num=None, figsize=(10, 8), dpi=80, facecolor='w', edgecolor='k')
plt.rcParams.update({'font.size': 22})

plt.fill_between(t_SEAIRQ2,(E_SEAIRQ2 - dE_SEAIRQ2)/N,(E_SEAIRQ2 + dE_SEAIRQ2)/N,color="green", alpha=0.2)
plt.plot(t_SEAIRQ2, E_SEAIRQ2/N, '-', color="green", label=r'$\langle E\rangle$', lw=4)

plt.fill_between(t_SEAIRQ2,(A_SEAIRQ2 - dA_SEAIRQ2)/N,(A_SEAIRQ2 + dA_SEAIRQ2)/N,color="#348ABD", alpha=0.2)
plt.plot(t_SEAIRQ2, A_SEAIRQ2/N, '-', color="#348ABD", label=r'$\langle S\rangle$', lw=4)

plt.fill_between(t_SEAIRQ2,(I_SEAIRQ2 - dI_SEAIRQ2)/N,(I_SEAIRQ2 + dI_SEAIRQ2)/N,color="#A60628", alpha=0.2)
plt.plot(t_SEAIRQ2, I_SEAIRQ2/N, '-', color="#A60628", label=r'$\langle I\rangle$', lw=4)

plt.legend(fontsize=26,loc='upper right',bbox_to_anchor=(1.3,1)); plt.grid()
plt.xlabel(r'time')
plt.ylabel(r'fraction of total population')
plt.autoscale(enable=True, axis='x', tight=True)
```

While the solid lines denote the instantaneous means, the colored shaded regions denote the standard deviation of the samples from the instantaneous mean.

As can be seen, the peak now is at about 350 days, and after 500 days the average numbers in all classes are decreasing.

```python

```


<div align='center'>

# Resolução da Lista 3
### complemento, calculos e solução das equações
---
<small>
    William Henrique - 119111128

    15 de fevereiro de 2022
</small>

---
</div>

```python
from sympy import *
from sympy.plotting import plot
import matplotlib.pyplot as plt
import numpy as np
```

```python
# matrícula (1,2,8)
x = 1 + 1
y = 1 + 2
z = 1 + 8

#função para plotar gráficos
#exp: expresão
#r: intervalo
#step: passo
def myplot(exp, r=(0, 2), step = .005):
    x = np.arange(r[0], r[1], step, dtype = np.float64)
    y1 = x.copy()

    for k, v in enumerate(x):
        y1[k] = exp.subs({t: x[k]})
    plt.plot(x, y1)
```

# Questão 1 - Calculos e Gráficos

## Questão 1 (A)

```python

# indutâncias
L1 = 4e-3
L2 = 1e-3
L3 = 5e-3
# as expresões dadas das tensões na fonte

t = symbols("t")
v1 = -0.05*x*E**(-x*t)
v2 = -0.02*y*E**(-y*t)*cos(20*t)
v3 = +0.01*z*E**(-z*t)
```

Calculando $V_{L1}(t)$ e $I_{L1}(t)$

```python
vl1 = -v3 - v1
il1 = 1/L1 * integrate(vl1, (t, 0, t))
il1
```

$\displaystyle 10.0 - 12.5 e^{- 2 t} + 2.5 e^{- 9 t}$

Calculando $V_{L2}(t)$ e  $I_{L2}(t)$

```python
vl2 = -v3 + v2
il2 = 1/L2 * integrate(vl2, (t, 0, t))
il2
```

$\displaystyle -10.440097799511 - 2.93398533007335 e^{- 3 t} \sin{\left(20 t \right)} + 0.440097799511002 e^{- 3 t} \cos{\left(20 t \right)} + 10.0 e^{- 9 t}$

Calculando $V_{L3}(t)$ e $I_{L3}(t)$

```python
vl3 = -v2 - v1
il3 = 1/L3 * integrate(vl3, (t, 0, t))
il3
```

$\displaystyle 10.0880195599022 - 10.0 e^{- 2 t} + 0.58679706601467 e^{- 3 t} \sin{\left(20 t \right)} - 0.0880195599022005 e^{- 3 t} \cos{\left(20 t \right)}$

## Questão 1 (B)

Calculando $p_{L1}(t)$

```python
pl1 = vl1 * il1
pl1.simplify()
```

$\displaystyle \left(0.1 e^{7 t} - 0.09\right) \left(10.0 e^{9 t} - 12.5 e^{7 t} + 2.5\right) e^{- 18 t}$

Calculando $p_{L2}(t)$

```python
pl2 = vl2 * il2
pl2.simplify()
```

$\displaystyle \left(0.06 e^{6 t} \cos{\left(20 t \right)} + 0.09\right) \left(\left(2.93398533007335 \sin{\left(20 t \right)} - 0.440097799511002 \cos{\left(20 t \right)}\right) e^{6 t} + 10.440097799511 e^{9 t} - 10.0\right) e^{- 18 t}$

Calculando $p_{L3}(t)$

```python
pl3 = vl3 * il3
pl3
```

$\displaystyle \left(0.1 e^{- 2 t} + 0.06 e^{- 3 t} \cos{\left(20 t \right)}\right) \left(10.0880195599022 - 10.0 e^{- 2 t} + 0.58679706601467 e^{- 3 t} \sin{\left(20 t \right)} - 0.0880195599022005 e^{- 3 t} \cos{\left(20 t \right)}\right)$

## Questão 1 (C)

Analisando os gráficos de potência

```python
passo = 0.005
x = np.arange(0, 5, passo,  dtype = np.float64)
y1 = x.copy()
y2 = y1.copy()
y3 = y1.copy()

for k, v in enumerate(x):
    y1[k] = pl1.subs({t: x[k]})
    y2[k] = pl2.subs({t: x[k]})
    y3[k] = pl3.subs({t: x[k]})

plt.plot(x, y1, x, y2, x, y3)
plt.legend(("$p_{l1}(t)$", "$p_{l2}(t)$", "$p_{l3}(t)$"))
```

Pelo gráfico das potências nos indutores para $t>=0$, é possivel perceber que $p_{l2}(t)$ possui o maior pico de potência

```python
dpl2 = diff(pl2, t)
root = nsolve(dpl2, t, (0, .1), solve="bisect")
print(f"raiz encontrada em t = {root} s é um ponto de")
valor = pl2.subs({t:root})
if (valor > 0):
    print(f"máximo valor: {valor} W")
plt.plot(x, y1, x, y2, x, y3, root, pl2.subs({t: root}), 'or')
```

## Questão 1 (D)

$w(t) = \frac{L}{2}i(t)^2$

$W_{l1}(t = oo)$

```python
# energia acumulada em t = +oo
wl1 = 1/2 * L1 * il1**2
wl1 = limit(wl1, t, oo)
wl1
```

$W_{l2}(t = oo)$

```python
wl2 = 1/2 * L2 * il2**2
wl2 = limit(wl2, t, oo)
wl2
```

$W_{l3}(t = oo)$

```python
wl3 = 1/2 * L3 * il3**2
wl3 = limit(wl3, t, oo)
wl3
```

```python
wl1 + wl2 + wl3
```

$\displaystyle 0.508918167634101$

# Questão 2 - Calculos e Gráficos

```python
t = symbols("t")
```

## Questão 2 (A)

```python
i = 500*E**(-z*t)*cos(20*t)*1e-3      # milli Ampere -> em Ampere
v1 = -2*E**(-y*t)                     # V
v2 = 5*t*E**(-x*t)                    # V
C = 500e-6   # C
L = 100e-3   # H
```

$v_L(t) = v_1(t) + v_2(t)$

```python
vl = v1 + v2
vl
```

$\displaystyle 5 t e^{- 2 t} - 2 e^{- 3 t}$

$v_C(t) = C\int_{t_0}^{t}{i(\tau)d\tau} + v_o(t_0)$

```python
#        v(t)                    + v(0)
vc = 1/C * integrate(i, (t, 0, t)) + (-x)
vc
```

$\displaystyle 16.7110187110187 + 41.5800415800416 e^{- 9 t} \sin{\left(20 t \right)} - 18.7110187110187 e^{- 9 t} \cos{\left(20 t \right)}$

## Questão 2 (B)

Aplicando L.K.T. na malha I (malha da esquerda)

```python
vi = vl + v1 - vc
vi
```

$\displaystyle 5 t e^{- 2 t} - 16.7110187110187 - 4 e^{- 3 t} - 41.5800415800416 e^{- 9 t} \sin{\left(20 t \right)} + 18.7110187110187 e^{- 9 t} \cos{\left(20 t \right)}$

Logo, a potência na fonte de corrente

```python
pi = -vi * i
pi
```

$\displaystyle 0.5 \left(- 5 t e^{- 2 t} + 16.7110187110187 + 4 e^{- 3 t} + 41.5800415800416 e^{- 9 t} \sin{\left(20 t \right)} - 18.7110187110187 e^{- 9 t} \cos{\left(20 t \right)}\right) e^{- 9 t} \cos{\left(20 t \right)}$

```python
myplot(pi)
```

## Questão 2 (C)

Calculando $I_L(t)$

```python
il = 1/L * integrate(vl, (t, 0, t)) + 0.1*y
il
```

$\displaystyle 0.833333333333333 \left(- 30 t - 15\right) e^{- 2 t} + 6.13333333333333 + 6.66666666666667 e^{- 3 t}$

```python
limit(il, t, oo) # il(t = oo)
```

$\displaystyle 6.13333333333333$

$p_{v1}(t) = -v_1(t)*i_L(t)$

```python
pv1 = - v1 * il
pv1
```

$\displaystyle 2 \left(0.833333333333333 \left(- 30 t - 15\right) e^{- 2 t} + 6.13333333333333 + 6.66666666666667 e^{- 3 t}\right) e^{- 3 t}$

$p_{v2}(t) = -v_2(t)[i_L(t) + i_C(t)]$ tal que $i_C(t) = i(t)$

```python
pv2 = -v2 * (il + i)
pv2
```

$\displaystyle - 5 t \left(0.833333333333333 \left(- 30 t - 15\right) e^{- 2 t} + 6.13333333333333 + 6.66666666666667 e^{- 3 t} + 0.5 e^{- 9 t} \cos{\left(20 t \right)}\right) e^{- 2 t}$

A potência desenvolvida pelas fontes de tensão é $p_V = p_{v1} + p_{v2}$

```python
pv = pv1 + pv2
pv
```

$\displaystyle - 5 t \left(0.833333333333333 \left(- 30 t - 15\right) e^{- 2 t} + 6.13333333333333 + 6.66666666666667 e^{- 3 t} + 0.5 e^{- 9 t} \cos{\left(20 t \right)}\right) e^{- 2 t} + 2 \left(0.833333333333333 \left(- 30 t - 15\right) e^{- 2 t} + 6.13333333333333 + 6.66666666666667 e^{- 3 t}\right) e^{- 3 t}$

## Questão 2 (D)

A carga total desenvolvida pelo capacitor em t = oo

v_C(t = oo) -> 16,71 V

```python
carga = limit(vc, t, oo)*C
print(f" carga = {carga} C = {carga*1e3} milliC")
```

     carga = 0.00835550935550936 C = 8.35550935550936 milliC

Questão 2 (E)

A energia acumulada em t = oo pelo capacitor e pelo indutor

```python
wl = 1/2 * L * il**2    #energia no indutor
wc = 1/2 * C * vc**2     #energia no capacitor
```

```python
wl = limit(wl, t, oo) # energia armazena em t = oo pelo indutor
print(f"wc = {wl} J")

```

    wc = 1.88088888888889 J

```python
wc = limit(wc, t, oo) # energia armazenada em t = oo pelo capacitor
print(f"wc = {wc} J")
```

    wc = 0.0698145365900044 J

```python
# energia total armazena pelo indutor e pelo capacitor
print(f"wc + wl = {wc + wl} J")
```

    wc + wl = 1.95070342547889 J

## Questão 2 (F)

```python
myplot(vc, r=(0, 10))
```

```python
myplot(vi, r=(0, 10))
```

```python
myplot(vl, r=(0, 10))

```