```python
import sympy
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
from sympy import sin, cos, pi, Function
from sympy import Symbol, symbols, Matrix, Transpose, init_session, Array, tensorproduct
from sympy.physics.vector import ReferenceFrame, outer, dynamicsymbols, Point

```

## Definindo as funções para h e psi

```python
#Defining h
def h(t, L, w, e_mais, e_cruzado,A):
	h_mais = A*cos(w*t-w*L)
	h_cruzado = A*sin(w*t-w*L)
	return h_mais*e_mais + h_cruzado*e_cruzado
```

\begin{equation}
 h = h_+ + h_\times
\end{equation}

```python
#função PSI(t)
def PSIj(j, k, L, N, A, w, T, ep, ec):
    H = h(T, L[j-1], w, ep, ec,A)
    phij = N[j-1].dot(H.dot(N[j-1]))/2
    return phij/(1-(k.dot(N[j-1]))**2) #expandir aqui
```

\begin{equation}
\Psi (t) = \frac{n^i h_{ij} n^j}{2(1 - (\hat{k}\cdot \hat{n})^2)}
\end{equation}

## Símbolos

```python
phi, theta, t, w, L, A , psi, sigma= symbols('ϕ θ t ω L A ψ σ')
```

## Sistemas de coordenadas e vetores usando o sympy

```python
DetFrame = ReferenceFrame("Det")
WaveFrame = ReferenceFrame("Wave")
WaveFrame.orient(DetFrame, "body", (phi, theta, psi), 'zxz')
```

```python
vx = WaveFrame.x
vy = WaveFrame.y
vz = WaveFrame.z
dbaseii = outer(vx, vx)
dbaseij = outer(vx, vy)
dbaseik = outer(vx, vz)
dbaseji = outer(vy, vx)
dbasejj = outer(vy, vy)
dbasejk = outer(vy, vz)
dbaseki = outer(vz, vx)
dbasekj = outer(vz, vy)
dbasekk = outer(vz, vz)
```

```python
e_plus  = dbaseii - dbasejj
e_cross = dbaseij + dbaseji
```

```python
#n no referencial do detector
n2 = cos(sigma)*DetFrame.x + sin(sigma)*DetFrame.y
n3 = cos(sigma)*DetFrame.x - sin(sigma)*DetFrame.y
```

```python
k = WaveFrame.z
```

## Defining posições dos satélites

```python
O = Point('O') #origin
```

```python
O.set_vel(DetFrame, 0)
```

```python
#seting p1, p2, p3
p1 = Point(r'P_1')
p2 = Point(r'P_2')
p3 = Point(r'P_3')
#r1, r2, r3, gamma1, gamma2, gamma3 = symbols(r'r_1 r_2 r_3 \gamma_1 \gamma_2 \gamma_3') #dist from org & phase angle
l = Symbol('l')
p1.set_pos(O, l*cos(0     )*DetFrame.x + l*sin(0     )*DetFrame.y + 0*DetFrame.z)
p2.set_pos(O, l*cos(2*pi/3)*DetFrame.x + l*sin(2*pi/3)*DetFrame.y + 0*DetFrame.z)
p3.set_pos(O, l*cos(4*pi/3)*DetFrame.x + l*sin(4*pi/3)*DetFrame.y + 0*DetFrame.z)
```

```python
P1 = p1.pos_from(O)
P2 = p2.pos_from(O)
P3 = p3.pos_from(O)
P = [P1, P2, P3]
```

```python
#setting n's, according to KTV notation
n1 = p2.pos_from(p3)
n2 = p3.pos_from(p1)
n3 = p1.pos_from(p2)
L1 = n1.magnitude()
L2 = n2.magnitude()
L3 = n3.magnitude()
N = [n1, n2, n3]
L = [L1, L2, L3]
```

## Início do cálculo do interferômetro

```python
PARAMETERS = (k,L,N,P,A,w,t, e_plus, e_cross)
```

```python
def delay(func, D):
    return func.subs(w*t, w*t - L[D-1])

def ygw(i,j,k,L,N,P,A,w,T, ep, ec):
    m = abs(6-i-j)-1
    return (1+ k.dot(N[m]))*\
           (PSIj(m, k, L, N, A, w, T + k.dot(P[i-1]) - L[m], ep, ec)\
            - PSIj(m,k, L, N, A, w, T + k.dot(P[j-1]), ep, ec))    #   # T + k.dot(P[i]) - L[m]) , T + k.dot(P[j]))

def ygwD(i,j,k,L,N,P,A,w,T, ep, ec, D): #Ygw com delay
    #delay = L[D]
    return delay(ygw(i,j,k,L,N,P,A,w,T, ep, ec), D)

def yij(i,j, parms = PARAMETERS):
    k,L,N,P,A,w,T, ep, ec = parms
    return ygw(i,j,k,L,N,P,A,w,T, ep, ec)

def yijD(i,j,D):
    return delay(yij(i,j),D)

def yijDD(i,j,D, E):
    return delay(delay(yij(i,j),D),E)
```

```python
f = A*cos(w*t)
f
```

$\displaystyle A \cos{\left(t ω \right)}$

```python
delay(f, 2)
```

$\displaystyle A \cos{\left(t ω - \sqrt{3} \sqrt{l^{2}} \right)}$

```python
X =   (yij(3,1) + yijD(1,3,2))\
   + delay(delay((yij(2,1) + yijD(1,2,3)),2),2)\
   - (yij(2,1) + yijD(1,2,3))\
   - delay(delay(yij(3,1)+yijD(1,3,2),2),2)\
   - delay(delay(delay(delay(\
      (yij(3,1) + yijD(1,3,2))\
   + delay(delay((yij(2,1) + yijD(1,2,3)),2),2)\
   - (yij(2,1) + yijD(1,2,3))\
   - delay(delay(yij(3,1)+yijD(1,3,2),2),2)\
     ,2),2),3),3)

```

```python
#X = sympy.trigsimp(X)
```

```python
y1 = yijD(3,1,2) - yij(2,3)
```

```python
#calculando M
X=sympy.trigsimp(y1)

```

```python
X=sympy.expand(X)
```

```python
X
```

$\displaystyle - \left(- \frac{- \frac{\sqrt{3} l \left(\left(- \sin{\left(ψ \right)} \sin{\left(ϕ \right)} + \cos{\left(θ \right)} \cos{\left(ψ \right)} \cos{\left(ϕ \right)}\right) \left(A \left(\frac{3 l \left(- \sin{\left(ψ \right)} \sin{\left(ϕ \right)} \cos{\left(θ \right)} + \cos{\left(ψ \right)} \cos{\left(ϕ \right)}\right)}{2} - \frac{\sqrt{3} l \left(\sin{\left(ψ \right)} \cos{\left(θ \right)} \cos{\left(ϕ \right)} + \sin{\left(ϕ \right)} \cos{\left(ψ \right)}\right)}{2}\right) \left(\sin{\left(ω \left(- \frac{l \sin{\left(θ \right)} \sin{\left(ϕ \right)}}{2} + \frac{\sqrt{3} l \sin{\left(θ \right)} \cos{\left(ϕ \right)}}{2} + t\right) \right)} \cos{\left(\sqrt{3} ω \sqrt{l^{2}} \right)} - \sin{\left(\sqrt{3} ω \sqrt{l^{2}} \right)} \cos{\left(ω \left(- \frac{l \sin{\left(θ \right)} \sin{\left(ϕ \right)}}{2} + \frac{\sqrt{3} l \sin{\left(θ \right)} \cos{\left(ϕ \right)}}{2} + t\right) \right)}\right) - A \left(\sin{\left(ω \left(- \frac{l \sin{\left(θ \right)} \sin{\left(ϕ \right)}}{2} + \frac{\sqrt{3} l \sin{\left(θ \right)} \cos{\left(ϕ \right)}}{2} + t\right) \right)} \sin{\left(\sqrt{3} ω \sqrt{l^{2}} \right)} + \cos{\left(ω \left(- \frac{l \sin{\left(θ \right)} \sin{\left(ϕ \right)}}{2} + \frac{\sqrt{3} l \sin{\left(θ \right)} \cos{\left(ϕ \right)}}{2} + t\right) \right)} \cos{\left(\sqrt{3} ω \sqrt{l^{2}} \right)}\right) \left(- \frac{\sqrt{3} l \left(- \sin{\left(ψ \right)} \sin{\left(ϕ \right)} + \cos{\left(θ \right)} \cos{\left(ψ \right)} \cos{\left(ϕ \right)}\right)}{2} + \frac{3 l \left(- \sin{\left(ψ \right)} \cos{\left(ϕ \right)} - \sin{\left(ϕ \right)} \cos{\left(θ \right)} \cos{\left(ψ \right)}\right)}{2}\right)\right) + \left(A \left(\frac{3 l \left(- \sin{\left(ψ \right)} \sin{\left(ϕ \right)} \cos{\left(θ \right)} + \cos{\left(ψ \right)} \cos{\left(ϕ \right)}\right)}{2} - \frac{\sqrt{3} l \left(\sin{\left(ψ \right)} \cos{\left(θ \right)} \cos{\left(ϕ \right)} + \sin{\left(ϕ \right)} \cos{\left(ψ \right)}\right)}{2}\right) \left(\sin{\left(ω \left(- \frac{l \sin{\left(θ \right)} \sin{\left(ϕ \right)}}{2} + \frac{\sqrt{3} l \sin{\left(θ \right)} \cos{\left(ϕ \right)}}{2} + t\right) \right)} \sin{\left(\sqrt{3} ω \sqrt{l^{2}} \right)} + \cos{\left(ω \left(- \frac{l \sin{\left(θ \right)} \sin{\left(ϕ \right)}}{2} + \frac{\sqrt{3} l \sin{\left(θ \right)} \cos{\left(ϕ \right)}}{2} + t\right) \right)} \cos{\left(\sqrt{3} ω \sqrt{l^{2}} \right)}\right) + A \left(\sin{\left(ω \left(- \frac{l \sin{\left(θ \right)} \sin{\left(ϕ \right)}}{2} + \frac{\sqrt{3} l \sin{\left(θ \right)} \cos{\left(ϕ \right)}}{2} + t\right) \right)} \cos{\left(\sqrt{3} ω \sqrt{l^{2}} \right)} - \sin{\left(\sqrt{3} ω \sqrt{l^{2}} \right)} \cos{\left(ω \left(- \frac{l \sin{\left(θ \right)} \sin{\left(ϕ \right)}}{2} + \frac{\sqrt{3} l \sin{\left(θ \right)} \cos{\left(ϕ \right)}}{2} + t\right) \right)}\right) \left(- \frac{\sqrt{3} l \left(- \sin{\left(ψ \right)} \sin{\left(ϕ \right)} + \cos{\left(θ \right)} \cos{\left(ψ \right)} \cos{\left(ϕ \right)}\right)}{2} + \frac{3 l \left(- \sin{\left(ψ \right)} \cos{\left(ϕ \right)} - \sin{\left(ϕ \right)} \cos{\left(θ \right)} \cos{\left(ψ \right)}\right)}{2}\right)\right) \left(\sin{\left(ψ \right)} \cos{\left(θ \right)} \cos{\left(ϕ \right)} + \sin{\left(ϕ \right)} \cos{\left(ψ \right)}\right)\right)}{4} + \frac{3 l \left(\left(- \sin{\left(ψ \right)} \cos{\left(ϕ \right)} - \sin{\left(ϕ \right)} \cos{\left(θ \right)} \cos{\left(ψ \right)}\right) \left(A \left(\frac{3 l \left(- \sin{\left(ψ \right)} \sin{\left(ϕ \right)} \cos{\left(θ \right)} + \cos{\left(ψ \right)} \cos{\left(ϕ \right)}\right)}{2} - \frac{\sqrt{3} l \left(\sin{\left(ψ \right)} \cos{\left(θ \right)} \cos{\left(ϕ \right)} + \sin{\left(ϕ \right)} \cos{\left(ψ \right)}\right)}{2}\right) \left(\sin{\left(ω \left(- \frac{l \sin{\left(θ \right)} \sin{\left(ϕ \right)}}{2} + \frac{\sqrt{3} l \sin{\left(θ \right)} \cos{\left(ϕ \right)}}{2} + t\right) \right)} \cos{\left(\sqrt{3} ω \sqrt{l^{2}} \right)} - \sin{\left(\sqrt{3} ω \sqrt{l^{2}} \right)} \cos{\left(ω \left(- \frac{l \sin{\left(θ \right)} \sin{\left(ϕ \right)}}{2} + \frac{\sqrt{3} l \sin{\left(θ \right)} \cos{\left(ϕ \right)}}{2} + t\right) \right)}\right) - A \left(\sin{\left(ω \left(- \frac{l \sin{\left(θ \right)} \sin{\left(ϕ \right)}}{2} + \frac{\sqrt{3} l \sin{\left(θ \right)} \cos{\left(ϕ \right)}}{2} + t\right) \right)} \sin{\left(\sqrt{3} ω \sqrt{l^{2}} \right)} + \cos{\left(ω \left(- \frac{l \sin{\left(θ \right)} \sin{\left(ϕ \right)}}{2} + \frac{\sqrt{3} l \sin{\left(θ \right)} \cos{\left(ϕ \right)}}{2} + t\right) \right)} \cos{\left(\sqrt{3} ω \sqrt{l^{2}} \right)}\right) \left(- \frac{\sqrt{3} l \left(- \sin{\left(ψ \right)} \sin{\left(ϕ \right)} + \cos{\left(θ \right)} \cos{\left(ψ \right)} \cos{\left(ϕ \right)}\right)}{2} + \frac{3 l \left(- \sin{\left(ψ \right)} \cos{\left(ϕ \right)} - \sin{\left(ϕ \right)} \cos{\left(θ \right)} \cos{\left(ψ \right)}\right)}{2}\right)\right) + \left(A \left(\frac{3 l \left(- \sin{\left(ψ \right)} \sin{\left(ϕ \right)} \cos{\left(θ \right)} + \cos{\left(ψ \right)} \cos{\left(ϕ \right)}\right)}{2} - \frac{\sqrt{3} l \left(\sin{\left(ψ \right)} \cos{\left(θ \right)} \cos{\left(ϕ \right)} + \sin{\left(ϕ \right)} \cos{\left(ψ \right)}\right)}{2}\right) \left(\sin{\left(ω \left(- \frac{l \sin{\left(θ \right)} \sin{\left(ϕ \right)}}{2} + \frac{\sqrt{3} l \sin{\left(θ \right)} \cos{\left(ϕ \right)}}{2} + t\right) \right)} \sin{\left(\sqrt{3} ω \sqrt{l^{2}} \right)} + \cos{\left(ω \left(- \frac{l \sin{\left(θ \right)} \sin{\left(ϕ \right)}}{2} + \frac{\sqrt{3} l \sin{\left(θ \right)} \cos{\left(ϕ \right)}}{2} + t\right) \right)} \cos{\left(\sqrt{3} ω \sqrt{l^{2}} \right)}\right) + A \left(\sin{\left(ω \left(- \frac{l \sin{\left(θ \right)} \sin{\left(ϕ \right)}}{2} + \frac{\sqrt{3} l \sin{\left(θ \right)} \cos{\left(ϕ \right)}}{2} + t\right) \right)} \cos{\left(\sqrt{3} ω \sqrt{l^{2}} \right)} - \sin{\left(\sqrt{3} ω \sqrt{l^{2}} \right)} \cos{\left(ω \left(- \frac{l \sin{\left(θ \right)} \sin{\left(ϕ \right)}}{2} + \frac{\sqrt{3} l \sin{\left(θ \right)} \cos{\left(ϕ \right)}}{2} + t\right) \right)}\right) \left(- \frac{\sqrt{3} l \left(- \sin{\left(ψ \right)} \sin{\left(ϕ \right)} + \cos{\left(θ \right)} \cos{\left(ψ \right)} \cos{\left(ϕ \right)}\right)}{2} + \frac{3 l \left(- \sin{\left(ψ \right)} \cos{\left(ϕ \right)} - \sin{\left(ϕ \right)} \cos{\left(θ \right)} \cos{\left(ψ \right)}\right)}{2}\right)\right) \left(- \sin{\left(ψ \right)} \sin{\left(ϕ \right)} \cos{\left(θ \right)} + \cos{\left(ψ \right)} \cos{\left(ϕ \right)}\right)\right)}{4}}{1 - \left(\frac{3 l \sin{\left(θ \right)} \sin{\left(ϕ \right)}}{2} + \frac{\sqrt{3} l \sin{\left(θ \right)} \cos{\left(ϕ \right)}}{2}\right)^{2}} + \frac{- \frac{\sqrt{3} l \left(\left(- \sin{\left(ψ \right)} \sin{\left(ϕ \right)} + \cos{\left(θ \right)} \cos{\left(ψ \right)} \cos{\left(ϕ \right)}\right) \left(A \left(\frac{3 l \left(- \sin{\left(ψ \right)} \sin{\left(ϕ \right)} \cos{\left(θ \right)} + \cos{\left(ψ \right)} \cos{\left(ϕ \right)}\right)}{2} - \frac{\sqrt{3} l \left(\sin{\left(ψ \right)} \cos{\left(θ \right)} \cos{\left(ϕ \right)} + \sin{\left(ϕ \right)} \cos{\left(ψ \right)}\right)}{2}\right) \left(\sin{\left(ω \left(- \frac{l \sin{\left(θ \right)} \sin{\left(ϕ \right)}}{2} - \frac{\sqrt{3} l \sin{\left(θ \right)} \cos{\left(ϕ \right)}}{2} + t - \sqrt{3} \sqrt{l^{2}}\right) \right)} \cos{\left(\sqrt{3} ω \sqrt{l^{2}} \right)} - \sin{\left(\sqrt{3} ω \sqrt{l^{2}} \right)} \cos{\left(ω \left(- \frac{l \sin{\left(θ \right)} \sin{\left(ϕ \right)}}{2} - \frac{\sqrt{3} l \sin{\left(θ \right)} \cos{\left(ϕ \right)}}{2} + t - \sqrt{3} \sqrt{l^{2}}\right) \right)}\right) - A \left(\sin{\left(ω \left(- \frac{l \sin{\left(θ \right)} \sin{\left(ϕ \right)}}{2} - \frac{\sqrt{3} l \sin{\left(θ \right)} \cos{\left(ϕ \right)}}{2} + t - \sqrt{3} \sqrt{l^{2}}\right) \right)} \sin{\left(\sqrt{3} ω \sqrt{l^{2}} \right)} + \cos{\left(ω \left(- \frac{l \sin{\left(θ \right)} \sin{\left(ϕ \right)}}{2} - \frac{\sqrt{3} l \sin{\left(θ \right)} \cos{\left(ϕ \right)}}{2} + t - \sqrt{3} \sqrt{l^{2}}\right) \right)} \cos{\left(\sqrt{3} ω \sqrt{l^{2}} \right)}\right) \left(- \frac{\sqrt{3} l \left(- \sin{\left(ψ \right)} \sin{\left(ϕ \right)} + \cos{\left(θ \right)} \cos{\left(ψ \right)} \cos{\left(ϕ \right)}\right)}{2} + \frac{3 l \left(- \sin{\left(ψ \right)} \cos{\left(ϕ \right)} - \sin{\left(ϕ \right)} \cos{\left(θ \right)} \cos{\left(ψ \right)}\right)}{2}\right)\right) + \left(A \left(\frac{3 l \left(- \sin{\left(ψ \right)} \sin{\left(ϕ \right)} \cos{\left(θ \right)} + \cos{\left(ψ \right)} \cos{\left(ϕ \right)}\right)}{2} - \frac{\sqrt{3} l \left(\sin{\left(ψ \right)} \cos{\left(θ \right)} \cos{\left(ϕ \right)} + \sin{\left(ϕ \right)} \cos{\left(ψ \right)}\right)}{2}\right) \left(\sin{\left(ω \left(- \frac{l \sin{\left(θ \right)} \sin{\left(ϕ \right)}}{2} - \frac{\sqrt{3} l \sin{\left(θ \right)} \cos{\left(ϕ \right)}}{2} + t - \sqrt{3} \sqrt{l^{2}}\right) \right)} \sin{\left(\sqrt{3} ω \sqrt{l^{2}} \right)} + \cos{\left(ω \left(- \frac{l \sin{\left(θ \right)} \sin{\left(ϕ \right)}}{2} - \frac{\sqrt{3} l \sin{\left(θ \right)} \cos{\left(ϕ \right)}}{2} + t - \sqrt{3} \sqrt{l^{2}}\right) \right)} \cos{\left(\sqrt{3} ω \sqrt{l^{2}} \right)}\right) + A \left(\sin{\left(ω \left(- \frac{l \sin{\left(θ \right)} \sin{\left(ϕ \right)}}{2} - \frac{\sqrt{3} l \sin{\left(θ \right)} \cos{\left(ϕ \right)}}{2} + t - \sqrt{3} \sqrt{l^{2}}\right) \right)} \cos{\left(\sqrt{3} ω \sqrt{l^{2}} \right)} - \sin{\left(\sqrt{3} ω \sqrt{l^{2}} \right)} \cos{\left(ω \left(- \frac{l \sin{\left(θ \right)} \sin{\left(ϕ \right)}}{2} - \frac{\sqrt{3} l \sin{\left(θ \right)} \cos{\left(ϕ \right)}}{2} + t - \sqrt{3} \sqrt{l^{2}}\right) \right)}\right) \left(- \frac{\sqrt{3} l \left(- \sin{\left(ψ \right)} \sin{\left(ϕ \right)} + \cos{\left(θ \right)} \cos{\left(ψ \right)} \cos{\left(ϕ \right)}\right)}{2} + \frac{3 l \left(- \sin{\left(ψ \right)} \cos{\left(ϕ \right)} - \sin{\left(ϕ \right)} \cos{\left(θ \right)} \cos{\left(ψ \right)}\right)}{2}\right)\right) \left(\sin{\left(ψ \right)} \cos{\left(θ \right)} \cos{\left(ϕ \right)} + \sin{\left(ϕ \right)} \cos{\left(ψ \right)}\right)\right)}{4} + \frac{3 l \left(\left(- \sin{\left(ψ \right)} \cos{\left(ϕ \right)} - \sin{\left(ϕ \right)} \cos{\left(θ \right)} \cos{\left(ψ \right)}\right) \left(A \left(\frac{3 l \left(- \sin{\left(ψ \right)} \sin{\left(ϕ \right)} \cos{\left(θ \right)} + \cos{\left(ψ \right)} \cos{\left(ϕ \right)}\right)}{2} - \frac{\sqrt{3} l \left(\sin{\left(ψ \right)} \cos{\left(θ \right)} \cos{\left(ϕ \right)} + \sin{\left(ϕ \right)} \cos{\left(ψ \right)}\right)}{2}\right) \left(\sin{\left(ω \left(- \frac{l \sin{\left(θ \right)} \sin{\left(ϕ \right)}}{2} - \frac{\sqrt{3} l \sin{\left(θ \right)} \cos{\left(ϕ \right)}}{2} + t - \sqrt{3} \sqrt{l^{2}}\right) \right)} \cos{\left(\sqrt{3} ω \sqrt{l^{2}} \right)} - \sin{\left(\sqrt{3} ω \sqrt{l^{2}} \right)} \cos{\left(ω \left(- \frac{l \sin{\left(θ \right)} \sin{\left(ϕ \right)}}{2} - \frac{\sqrt{3} l \sin{\left(θ \right)} \cos{\left(ϕ \right)}}{2} + t - \sqrt{3} \sqrt{l^{2}}\right) \right)}\right) - A \left(\sin{\left(ω \left(- \frac{l \sin{\left(θ \right)} \sin{\left(ϕ \right)}}{2} - \frac{\sqrt{3} l \sin{\left(θ \right)} \cos{\left(ϕ \right)}}{2} + t - \sqrt{3} \sqrt{l^{2}}\right) \right)} \sin{\left(\sqrt{3} ω \sqrt{l^{2}} \right)} + \cos{\left(ω \left(- \frac{l \sin{\left(θ \right)} \sin{\left(ϕ \right)}}{2} - \frac{\sqrt{3} l \sin{\left(θ \right)} \cos{\left(ϕ \right)}}{2} + t - \sqrt{3} \sqrt{l^{2}}\right) \right)} \cos{\left(\sqrt{3} ω \sqrt{l^{2}} \right)}\right) \left(- \frac{\sqrt{3} l \left(- \sin{\left(ψ \right)} \sin{\left(ϕ \right)} + \cos{\left(θ \right)} \cos{\left(ψ \right)} \cos{\left(ϕ \right)}\right)}{2} + \frac{3 l \left(- \sin{\left(ψ \right)} \cos{\left(ϕ \right)} - \sin{\left(ϕ \right)} \cos{\left(θ \right)} \cos{\left(ψ \right)}\right)}{2}\right)\right) + \left(A \left(\frac{3 l \left(- \sin{\left(ψ \right)} \sin{\left(ϕ \right)} \cos{\left(θ \right)} + \cos{\left(ψ \right)} \cos{\left(ϕ \right)}\right)}{2} - \frac{\sqrt{3} l \left(\sin{\left(ψ \right)} \cos{\left(θ \right)} \cos{\left(ϕ \right)} + \sin{\left(ϕ \right)} \cos{\left(ψ \right)}\right)}{2}\right) \left(\sin{\left(ω \left(- \frac{l \sin{\left(θ \right)} \sin{\left(ϕ \right)}}{2} - \frac{\sqrt{3} l \sin{\left(θ \right)} \cos{\left(ϕ \right)}}{2} + t - \sqrt{3} \sqrt{l^{2}}\right) \right)} \sin{\left(\sqrt{3} ω \sqrt{l^{2}} \right)} + \cos{\left(ω \left(- \frac{l \sin{\left(θ \right)} \sin{\left(ϕ \right)}}{2} - \frac{\sqrt{3} l \sin{\left(θ \right)} \cos{\left(ϕ \right)}}{2} + t - \sqrt{3} \sqrt{l^{2}}\right) \right)} \cos{\left(\sqrt{3} ω \sqrt{l^{2}} \right)}\right) + A \left(\sin{\left(ω \left(- \frac{l \sin{\left(θ \right)} \sin{\left(ϕ \right)}}{2} - \frac{\sqrt{3} l \sin{\left(θ \right)} \cos{\left(ϕ \right)}}{2} + t - \sqrt{3} \sqrt{l^{2}}\right) \right)} \cos{\left(\sqrt{3} ω \sqrt{l^{2}} \right)} - \sin{\left(\sqrt{3} ω \sqrt{l^{2}} \right)} \cos{\left(ω \left(- \frac{l \sin{\left(θ \right)} \sin{\left(ϕ \right)}}{2} - \frac{\sqrt{3} l \sin{\left(θ \right)} \cos{\left(ϕ \right)}}{2} + t - \sqrt{3} \sqrt{l^{2}}\right) \right)}\right) \left(- \frac{\sqrt{3} l \left(- \sin{\left(ψ \right)} \sin{\left(ϕ \right)} + \cos{\left(θ \right)} \cos{\left(ψ \right)} \cos{\left(ϕ \right)}\right)}{2} + \frac{3 l \left(- \sin{\left(ψ \right)} \cos{\left(ϕ \right)} - \sin{\left(ϕ \right)} \cos{\left(θ \right)} \cos{\left(ψ \right)}\right)}{2}\right)\right) \left(- \sin{\left(ψ \right)} \sin{\left(ϕ \right)} \cos{\left(θ \right)} + \cos{\left(ψ \right)} \cos{\left(ϕ \right)}\right)\right)}{4}}{1 - \left(\frac{3 l \sin{\left(θ \right)} \sin{\left(ϕ \right)}}{2} + \frac{\sqrt{3} l \sin{\left(θ \right)} \cos{\left(ϕ \right)}}{2}\right)^{2}}\right) \left(- \sqrt{3} l \sin{\left(θ \right)} \cos{\left(ϕ \right)} + 1\right) + \left(- \frac{\sqrt{3} l \left(\left(- \sin{\left(ψ \right)} \sin{\left(ϕ \right)} + \cos{\left(θ \right)} \cos{\left(ψ \right)} \cos{\left(ϕ \right)}\right) \left(- \sqrt{3} A l \left(- \sin{\left(ψ \right)} \sin{\left(ϕ \right)} + \cos{\left(θ \right)} \cos{\left(ψ \right)} \cos{\left(ϕ \right)}\right) \left(\sin{\left(ω \left(l \sin{\left(θ \right)} \sin{\left(ϕ \right)} + t\right) \right)} \sin{\left(\sqrt{3} ω \sqrt{l^{2}} \right)} + \cos{\left(ω \left(l \sin{\left(θ \right)} \sin{\left(ϕ \right)} + t\right) \right)} \cos{\left(\sqrt{3} ω \sqrt{l^{2}} \right)}\right) + \sqrt{3} A l \left(\sin{\left(ω \left(l \sin{\left(θ \right)} \sin{\left(ϕ \right)} + t\right) \right)} \cos{\left(\sqrt{3} ω \sqrt{l^{2}} \right)} - \sin{\left(\sqrt{3} ω \sqrt{l^{2}} \right)} \cos{\left(ω \left(l \sin{\left(θ \right)} \sin{\left(ϕ \right)} + t\right) \right)}\right) \left(\sin{\left(ψ \right)} \cos{\left(θ \right)} \cos{\left(ϕ \right)} + \sin{\left(ϕ \right)} \cos{\left(ψ \right)}\right)\right) + \left(\sin{\left(ψ \right)} \cos{\left(θ \right)} \cos{\left(ϕ \right)} + \sin{\left(ϕ \right)} \cos{\left(ψ \right)}\right) \left(\sqrt{3} A l \left(- \sin{\left(ψ \right)} \sin{\left(ϕ \right)} + \cos{\left(θ \right)} \cos{\left(ψ \right)} \cos{\left(ϕ \right)}\right) \left(\sin{\left(ω \left(l \sin{\left(θ \right)} \sin{\left(ϕ \right)} + t\right) \right)} \cos{\left(\sqrt{3} ω \sqrt{l^{2}} \right)} - \sin{\left(\sqrt{3} ω \sqrt{l^{2}} \right)} \cos{\left(ω \left(l \sin{\left(θ \right)} \sin{\left(ϕ \right)} + t\right) \right)}\right) + \sqrt{3} A l \left(\sin{\left(ω \left(l \sin{\left(θ \right)} \sin{\left(ϕ \right)} + t\right) \right)} \sin{\left(\sqrt{3} ω \sqrt{l^{2}} \right)} + \cos{\left(ω \left(l \sin{\left(θ \right)} \sin{\left(ϕ \right)} + t\right) \right)} \cos{\left(\sqrt{3} ω \sqrt{l^{2}} \right)}\right) \left(\sin{\left(ψ \right)} \cos{\left(θ \right)} \cos{\left(ϕ \right)} + \sin{\left(ϕ \right)} \cos{\left(ψ \right)}\right)\right)\right)}{- 6 l^{2} \sin^{2}{\left(θ \right)} \cos^{2}{\left(ϕ \right)} + 2} + \frac{\sqrt{3} l \left(\left(- \sin{\left(ψ \right)} \sin{\left(ϕ \right)} + \cos{\left(θ \right)} \cos{\left(ψ \right)} \cos{\left(ϕ \right)}\right) \left(- \sqrt{3} A l \left(- \sin{\left(ψ \right)} \sin{\left(ϕ \right)} + \cos{\left(θ \right)} \cos{\left(ψ \right)} \cos{\left(ϕ \right)}\right) \left(\sin{\left(ω \left(- \frac{l \sin{\left(θ \right)} \sin{\left(ϕ \right)}}{2} + \frac{\sqrt{3} l \sin{\left(θ \right)} \cos{\left(ϕ \right)}}{2} + t - \sqrt{3} \sqrt{l^{2}}\right) \right)} \sin{\left(\sqrt{3} ω \sqrt{l^{2}} \right)} + \cos{\left(ω \left(- \frac{l \sin{\left(θ \right)} \sin{\left(ϕ \right)}}{2} + \frac{\sqrt{3} l \sin{\left(θ \right)} \cos{\left(ϕ \right)}}{2} + t - \sqrt{3} \sqrt{l^{2}}\right) \right)} \cos{\left(\sqrt{3} ω \sqrt{l^{2}} \right)}\right) + \sqrt{3} A l \left(\sin{\left(ω \left(- \frac{l \sin{\left(θ \right)} \sin{\left(ϕ \right)}}{2} + \frac{\sqrt{3} l \sin{\left(θ \right)} \cos{\left(ϕ \right)}}{2} + t - \sqrt{3} \sqrt{l^{2}}\right) \right)} \cos{\left(\sqrt{3} ω \sqrt{l^{2}} \right)} - \sin{\left(\sqrt{3} ω \sqrt{l^{2}} \right)} \cos{\left(ω \left(- \frac{l \sin{\left(θ \right)} \sin{\left(ϕ \right)}}{2} + \frac{\sqrt{3} l \sin{\left(θ \right)} \cos{\left(ϕ \right)}}{2} + t - \sqrt{3} \sqrt{l^{2}}\right) \right)}\right) \left(\sin{\left(ψ \right)} \cos{\left(θ \right)} \cos{\left(ϕ \right)} + \sin{\left(ϕ \right)} \cos{\left(ψ \right)}\right)\right) + \left(\sin{\left(ψ \right)} \cos{\left(θ \right)} \cos{\left(ϕ \right)} + \sin{\left(ϕ \right)} \cos{\left(ψ \right)}\right) \left(\sqrt{3} A l \left(- \sin{\left(ψ \right)} \sin{\left(ϕ \right)} + \cos{\left(θ \right)} \cos{\left(ψ \right)} \cos{\left(ϕ \right)}\right) \left(\sin{\left(ω \left(- \frac{l \sin{\left(θ \right)} \sin{\left(ϕ \right)}}{2} + \frac{\sqrt{3} l \sin{\left(θ \right)} \cos{\left(ϕ \right)}}{2} + t - \sqrt{3} \sqrt{l^{2}}\right) \right)} \cos{\left(\sqrt{3} ω \sqrt{l^{2}} \right)} - \sin{\left(\sqrt{3} ω \sqrt{l^{2}} \right)} \cos{\left(ω \left(- \frac{l \sin{\left(θ \right)} \sin{\left(ϕ \right)}}{2} + \frac{\sqrt{3} l \sin{\left(θ \right)} \cos{\left(ϕ \right)}}{2} + t - \sqrt{3} \sqrt{l^{2}}\right) \right)}\right) + \sqrt{3} A l \left(\sin{\left(ω \left(- \frac{l \sin{\left(θ \right)} \sin{\left(ϕ \right)}}{2} + \frac{\sqrt{3} l \sin{\left(θ \right)} \cos{\left(ϕ \right)}}{2} + t - \sqrt{3} \sqrt{l^{2}}\right) \right)} \sin{\left(\sqrt{3} ω \sqrt{l^{2}} \right)} + \cos{\left(ω \left(- \frac{l \sin{\left(θ \right)} \sin{\left(ϕ \right)}}{2} + \frac{\sqrt{3} l \sin{\left(θ \right)} \cos{\left(ϕ \right)}}{2} + t - \sqrt{3} \sqrt{l^{2}}\right) \right)} \cos{\left(\sqrt{3} ω \sqrt{l^{2}} \right)}\right) \left(\sin{\left(ψ \right)} \cos{\left(θ \right)} \cos{\left(ϕ \right)} + \sin{\left(ϕ \right)} \cos{\left(ψ \right)}\right)\right)\right)}{- 6 l^{2} \sin^{2}{\left(θ \right)} \cos^{2}{\left(ϕ \right)} + 2}\right) \left(- \frac{3 l \sin{\left(θ \right)} \sin{\left(ϕ \right)}}{2} + \frac{\sqrt{3} l \sin{\left(θ \right)} \cos{\left(ϕ \right)}}{2} + 1\right)$

```python
#M=sympy.trigsimp(M)
```

```python
F_mais=X.coeff(cos(w*t))
F_cruzado=X.coeff(sin(w*t))
```

```python
F_cruzado
```

$\displaystyle 0$

```python
f_mais = sympy.lambdify([   phi, theta, w, A, l], F_mais)
f_cruzado = sympy.lambdify([phi, theta, w, A, l], F_cruzado)
M_eval = sympy.lambdify([phi, theta, w, A, l], M)
```

```python
f_mais
```

```python
#defining parameters
phi_value, theta_value = np.mgrid[-np.pi:np.pi:100j, 0:np.pi:100j]
arm=5e9/3e8 #segundos

f=10**-3 #Hz
freq=2*np.pi*f
a=1

```

```python
#atribuindos os valores acima nas funções
#                       [phi          , theta       , w   , A, r1, r2, r3, gamma1, gamma2, gamma3]
f_mais_data    = f_mais((phi_value), (theta_value), freq, a, arm)
f_cruzado_data = f_cruzado((phi_value),(theta_value), freq, a, arm)

```

```python
f_mais_data
```

```python
#plot phi, theta e F
fig = plt.figure()
ax = fig.gca(projection='3d')
ax.plot_surface(phi_value, theta_value,(f_mais_data),color='b')
#ax.plot_surface(phi_value, theta_value,(f_cruzado_data),color='g')
#ax.plot_surface(phi_value, theta_value,(f_cruzado_data-f_mais_data),color='g')
ax.set_xlabel('phi')
ax.set_ylabel('theta')
ax.set_zlabel('F+')
plt.show()

```

```python
#plot x,y,z

fig = plt.figure()
ax = fig.gca(projection='3d')

x_mais=(f_mais_data)*np.sin(theta_value)*np.sin(phi_value)
y_mais=-(f_mais_data)*np.sin(theta_value)*np.cos(phi_value)
z_mais=(f_mais_data)*np.cos(theta_value)

x_cruzado=(f_cruzado_data)*np.sin(theta_value)*np.sin(phi_value)
y_cruzado=-(f_cruzado_data)*np.sin(theta_value)*np.cos(phi_value)
z_cruzado=(f_cruzado_data)*np.cos(theta_value)

ax.plot_surface(x_mais,y_mais,z_mais,color='b')
#ax.plot_surface(x_cruzado,y_cruzado,z_cruzado,color='g')
#ax.plot_surface((x_cruzado-x_mais),(y_cruzado-y_mais),(z_cruzado-z_mais),color='g', label = 'F_cruzado')

ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
plt.show()
```

```python

```


```python
%matplotlib inline
import numpy as np
import matplotlib.pyplot as pl
import shutil
import torch
import torch.nn as nn
import torch.utils.data
import h5py
from torch.autograd import Variable
import time
from tqdm import tqdm
import model_wavefront as model
import argparse
import glob
import platform
import os
import astropy.units as u
import wavefront as wf
from IPython.display import Image
from IPython.core.display import HTML
from ipywidgets import interact_manual
import ipywidgets as widgets
import pandas as pd
```

# Real-time wavefront sensing with phase diversity
In this brief note I consider a proof-of-concept of how to carry out online wavefront sensing using two cameras, one located at the focus of the instrument and the other one located slightly defocused. This is a somehow standard way to obtain the wavefront but suffers from computational problems. The reason is that one needs to solve a complex optimization problem which is computationally intensive.

Since we all know how phase diversity works, I'll only briefly descript the method and mainly focus on how to accelerate it and find the wavefront in real time. In the linear theory of image formation the discrete image arriving at the detector after having being affected by the atmosphere and all the optical elements of the telescope and instrument, $\mathbf{i}$, is obtained from the image without perturbation, $\mathbf{o}$, by computing

\begin{equation}
\mathbf{i} = \mathbf{h} * \mathbf{o} + \mathbf{n}
\end{equation}

where $\mathbf{h}$ is the point spread function (PSF) of the whole system and $\mathbf{n}$ is a noise vector with some known statistics. The PSF can be written as the square modulus of the Fourier transform of the complex amplitude in the aperture of the telescope:

\begin{equation}
\mathbf{h} = |\mathcal{F}\left( \mathcal{P} e^{\phi} \right) |^2,
\end{equation}
where $\mathcal{P}$ is the modulus of the amplitude which, for a perfect telescope, is constant inside the pupil of the telescope and zero otherwise. $\phi$ is the phase of the wavefront, which are produced by differences in the optical path in the atmosphere due to the turbulence, as well as by aberrations in the optical systems.
In the following, we describe the wavefront as a linear commbination of Zernike polynomials, which are orthogonal functions in the unit circle (other functions like Karhunen-Loeve modes are probably desirable in more realistic situations):

\begin{equation}
\phi = \sum_{j=4}^N a_j Z_j(x,y),
\end{equation}
where $(x,y)$ are coordinates in the pupil plane. We start the summation in index 4 (Noll indices) because we neglect the piston, tip and tilts.

In the standard phase-diversity scheme, one obtains an image at focus and another one with a known defocus. Therefore, the two images are given by:

\begin{align}
\mathbf{i}_\mathrm{focus} &= \mathbf{h}(\phi) * \mathbf{o} + \mathbf{n}_1 \\
\mathbf{i}_\mathrm{defocus} &= \mathbf{h}(\phi+\Delta \phi) * \mathbf{o} + \mathbf{n}_2,
\end{align}
where we make explicit the dependence of the PSF on the wavefront. $\Delta \phi$ is the defocus introduced in the second camera, which can be easily described in the Zernike basis as $\Delta \phi=\delta a_4 Z_4$. The previous problem is routinely solved by writing the likelihood associated with the statistics of the noise and maximizing it. In the case of Gaussian noise, the log-likelihood function is given by:

\begin{equation}
\log L = \lVert  \mathbf{i}_\mathrm{focus} - \mathbf{h}(\phi) * \mathbf{o} \rVert^2 + \lVert \mathbf{i}_\mathrm{defocus} - \mathbf{h}(\phi+\Delta \phi) * \mathbf{o} \rVert^2.
\end{equation}
This likelihood can be optimized using an alternatinv optimization method for the wavefront and the image $\mathbf{o}$. When the wavefront is considered to be known, the solution for the object can be obtained in closed form. On the contrary, when the object is assumed to be known, the solution to the wavefront is not given in closed form and one can iterate the two solutions until a full solution is found. The main message is that obtaining the wavefront and the image in phase diversity techniques requires the solution of a nonlinear problem, which is computationally heavy.

We proposed here to fully bypass the optimization process leveraging a deep neural network that directly extracts the wavefront from the couple of focus+defocus images. Initially, I started training the network in a supervised way by optimizing the loss function computed from the Zernike modes of the wavefront from each pair of images and the real Zernike modes. However, this has the problem that it completely bypasses the physics behind the image formation and the presence of the diversity in the defocused channel. Therefore, it makes much more sense to add this physics during the training. This is done by following the scheme below.

There is an encoder that takes as input two images (in our case images of 64$\times$64 pixels) and produces as output a vector of Zernike modes. This network is a fully convolutional network with input of size 2$\times$64$\times$64 and output of size 37$\times$1$\times$1. The images are passed through successive convolutional layers in residual connection mode (some of them also contain batch normalization and LeakyReLU connections. The output is supposed to be the Zernike modes of the wavefront. Then, I compute the wavefront from these modes and compute the associated PSF. Simultaneously, I add the diversity to the wavefront and compute the defocused PSF. Then I use the original image from a simulation from which I generated the training set and apply the two PSFs, generating a new focused and defocused pair. This pair is then compared with those in the input until a good fit is obtained. Additionally, I add as a regularization the l2 distance between the generated wavefront and the original wavefront.

The general scheme is, then, as follows:

```python
Image(url= "network.png")
```

The encoder is, as a first proposal, given by the following convolutional neural network:

```python
Image(url= "cnn.png")
```

In principle, PD is not sensitive to phases larger than $2 \pi$, so the wavefront might suffer from wrapping. However, I hope that the fact that I'm introducing a regularization on the wavefront produces Zernike modes such that the wavefront is automatically unwrapped.

### Training
The training is done by using images from an emerging flux region simulation carried out by M. Cheung. Here is an example of such images:

```python
image = np.copy(np.memmap('/scratch1/3dcubes/cheung/quiet/I_out.064000',dtype='float32',offset=4*4,mode='r',shape=(1024,1920)))
image /= np.mean(image[0:50,0:50])
f, ax = pl.subplots(figsize=(12,8))
im = ax.imshow(np.clip(image, 0,1.5))
```

We extract patches randomly from these maps (of the order of 100) and apply random realization of a Kolmogorov atmosphere with Fried radius uniformly distributed between 5 and 40 cm. After applying the PSFs, we remove any residual tip-tilt by recentering the images with respect to the original image.

The training is done in PyTorch [https://pytorch.org], which allows one to define the deep neural network and our complicated loss function (that contains the generation of the wavefront, PSFs and convolution of the images) and automatically compute the necessary derivatives. Let's plot the evolution of the loss function at all epochs. I note that the network does not show any hint of overfitting but more work is needed for improving the results.

```python
tmp = pd.read_csv('../training/trained_wavefront/2018-12-19-13:28.loss.csv')
f, ax = pl.subplots()
ax.plot(tmp.values[:,1], label='Training')
ax.plot(tmp.values[:,2], label='Validation')
ax.legend()
```

## Examples
Once the network is trained (not shown in this notebook), we define a class that contains all the machinery to generate wavefronts from the validation dataset.

```python
class deep_phasediv(object):
    def __init__(self, telescope_diameter, pixel_size, lambda0, npix_psf, n_zernike, batch_size, n_testing=-1, resume=None, checkpoint=None):

        # Define all parameters for correctly carrying out the convolution with generated PSFs
        self.lambda0 = lambda0
        self.telescope_diameter = telescope_diameter
        self.pixel_size = pixel_size
        self.npix_psf = npix_psf
        self.n_zernike = n_zernike
        self.batch_size = batch_size
        self.cuda = torch.cuda.is_available()
        self.device = torch.device("cuda" if self.cuda else "cpu")
        self.n_testing = n_testing

        # Compute the PSF scale appropriate for the required pixel size, wavelength and telescope diameter
        self.overfill = wf.psfScale(self.telescope_diameter, self.lambda0, self.pixel_size)

        self.nbig = int(np.ceil(self.npix_psf * self.overfill))
        if (self.nbig % 2 != 0):
            self.nbig += 1

        self.half = (self.nbig - self.npix_psf) // 2
        center = self.nbig // 2

        if (self.n_zernike != 0):
            self.zernikes = np.zeros((self.n_zernike,self.npix_psf,self.npix_psf))
            for j in range(self.n_zernike):
                self.zernikes[j,:,:] = wf.zernike(j+4,npix=self.npix_psf)

        self.zernikes_torch = torch.zeros((self.n_zernike,self.nbig,self.nbig),dtype=torch.float32).to(self.device)

        self.zernikes_torch[:,self.half:self.half+self.npix_psf,self.half:self.half+self.npix_psf] = torch.from_numpy(self.zernikes)

        # Temporary arrays for PSF calculation and convolution
        self.wfbig = torch.zeros((self.batch_size,self.nbig,self.nbig),dtype=torch.float32).to(self.device)
        self.illum = torch.zeros((self.batch_size,self.nbig,self.nbig),dtype=torch.float32).to(self.device)
        self.phase = torch.zeros((self.batch_size,self.nbig,self.nbig,2),dtype=torch.float32).to(self.device)
        self.psf = torch.zeros((self.batch_size,self.npix_psf,self.npix_psf,2),dtype=torch.float32).to(self.device)
        self.image = torch.zeros((self.batch_size,self.npix_psf,self.npix_psf,2),dtype=torch.float32).to(self.device)
        self.prod = torch.zeros((self.batch_size,self.npix_psf,self.npix_psf,2),dtype=torch.float32).to(self.device)
        self.zernike_defocus = torch.zeros((self.batch_size,37),dtype=torch.float32).to(self.device)

        self.zeros = torch.zeros((self.batch_size, self.npix_psf, self.npix_psf, 1),dtype=torch.float32).to(self.device)

        # Defocus Zernike coefficient. It is at index 0 because we are not considering modes 1,2 and 3 (piston, tip, tilt)
        self.zernike_defocus[:,0] = 1.0 * np.pi / np.sqrt(3.0)

        # Compute telescope aperture
        self.aperture = wf.aperture(npix = self.npix_psf, cent_obs = 0, spider=0)

        # Illumination of the pupil
        self.illum[:,self.half:self.half+self.npix_psf,self.half:self.half+self.npix_psf] = torch.from_numpy(self.aperture)[None,:,:]

        # Compute mask to select the central part of the PSF, which has been computed in a larger
        # array to fit the pixel size with the spatial frequencies
        self.mask = torch.ones((self.batch_size, self.nbig, self.nbig)).byte().to(self.device)

        self.mask[:,center-self.half:center+self.half+1,:] = 0
        self.mask[:,:,center-self.half:center+self.half+1] = 0

        torch.backends.cudnn.benchmark = True

        kwargs = {'num_workers': 1, 'pin_memory': True} if self.cuda else {}

        telescope_radius = 0.5*1.5 * u.meter
        pixel_size = 0.0545 * u.arcsec / u.pixel
        fov = (101, 101) * u.pixel
        pix_fov = 101
        lambda0 = 500 * u.nm

        self.model = model.network(2*telescope_radius.value, pixel_size.value, lambda0.to(u.AA).value, pix_fov, self.device, self.batch_size, 37).to(self.device)

        if (checkpoint is None):
            files = glob.glob('../training/trained_wavefront/*.pth')
            self.checkpoint = max(files, key=os.path.getctime)

        else:
            self.checkpoint = '{0}.pth'.format(checkpoint)

        print("=> loading checkpoint '{}'".format(self.checkpoint))
        if (self.cuda):
            checkpoint = torch.load(self.checkpoint)
        else:
            checkpoint = torch.load(self.checkpoint, map_location=lambda storage, loc: storage)
        self.model.load_state_dict(checkpoint['state_dict'])
        print("=> loaded checkpoint '{}'".format(self.checkpoint))

        computer_name = platform.node()

        if (computer_name == 'Andres-Mac.local'):
            self.test_file = '/scratch/deep_learning/wavefrontPD/validation_cheung.h5'

        if (computer_name == 'vena'):
            self.test_file = '/scratch1/deepLearning/wavefrontPD/validation_cheung.h5'

        self.file = h5py.File(self.test_file, 'r')
        self.n_images, _, self.nx, self.ny = self.file['images'].shape
        _, self.zernike = self.file['wavefront'].shape

    def get_psf_wavefront(self, zernike):

        # Compute wavefront by summing over Zernike polynomials
        wavefront = torch.sum(zernike[:,:,None,None] * self.zernikes_torch, 1)

        # Compute real and imaginary parts of the pupil
        tmp1 = torch.unsqueeze(torch.cos(wavefront) * self.illum, -1)
        tmp2 = torch.unsqueeze(torch.sin(wavefront) * self.illum, -1)

        self.phase = torch.cat([tmp1, tmp2], -1)

        # Compute FFT of the pupil function and compute autocorrelation
        ft = torch.fft(self.phase, 2)
        psf = ft[:,:,:,0]**2 + ft[:,:,:,1]**2

        # Extract the central part of the PSF of the size of the image
        psf = psf[self.mask].view((self.batch_size, self.npix_psf, self.npix_psf))
        return wavefront, psf

    def plot(self, index=None):
        self.model.eval()

        if (index is None):
            index = np.random.randint(self.n_images)

        im_focus_defocus = self.file['images'][index:index+2,1:,:,:]
        im_focus_defocus += np.random.normal(loc=0.0, scale=1e-4, size=im_focus_defocus.shape)

        minim = np.min(im_focus_defocus, axis=(2,3))
        maxim = np.max(im_focus_defocus, axis=(2,3))

        im_focus_defocus = (im_focus_defocus - minim[:,:,None,None]) / (maxim[:,:,None,None] - minim[:,:,None,None])

        im_original = self.file['image_large'][index:index+2,:,:]

        zernike_target = self.file['wavefront'][index:index+2,3:]

        r0 = self.file['r0'][index:index+2]

        f, ax = pl.subplots(nrows=3, ncols=3, figsize=(18,10))

        with torch.no_grad():

            im_focus_defocus = torch.from_numpy(im_focus_defocus)
            im_original = torch.from_numpy(im_original)
            zernike_target = torch.from_numpy(zernike_target)
            minim = torch.from_numpy(minim)
            maxim = torch.from_numpy(maxim)

            im_focus_defocus, im_original, zernike_target = im_focus_defocus.to(self.device), im_original.to(self.device), zernike_target.to(self.device)
            minim, maxim = minim.to(self.device), maxim.to(self.device)

            output_focus, output_defocus, zernike_nn, wavefront, wavefront_target = self.model(im_focus_defocus, im_original, zernike_target)

            output_focus = (output_focus - minim[:,0,None,None]) / (maxim[:,0,None,None] - minim[:,0,None,None])
            output_defocus = (output_defocus - minim[:,1,None,None]) / (maxim[:,1,None,None] - minim[:,1,None,None])

            wavefront, psf = self.get_psf_wavefront(zernike_nn)
            wavefront_target, psf_target = self.get_psf_wavefront(zernike_target)

            im_focus_defocus = im_focus_defocus.cpu().data.numpy()
            im_original = im_original.cpu().data.numpy()
            output_focus = output_focus.cpu().data.numpy()
            output_defocus = output_defocus.cpu().data.numpy()
            wavefront = wavefront.cpu().data.numpy()
            wavefront_target = wavefront_target.cpu().data.numpy()
            psf = psf.cpu().data.numpy()
            psf_target = psf_target.cpu().data.numpy()
            zernike_nn = zernike_nn.cpu().data.numpy()
            zernike_target = zernike_target.cpu().data.numpy()

            psf = np.roll(psf, 50, axis=(1,2))
            psf_target = np.roll(psf_target, 50, axis=(1,2))

            #ax[0].imshow(im_original[0,18:82,18:82])
            #ax[0].set_title('Original HR')
            ax[0,0].imshow(im_focus_defocus[0,0,:,:])
            ax[0,0].set_title('Original Focus')
            ax[1,0].imshow(output_focus[0,:,:])
            ax[1,0].set_title('NN Focus')
            ax[0,1].imshow(im_focus_defocus[0,1,:,:])
            ax[0,1].set_title('Original Defocus')
            ax[1,1].imshow(output_defocus[0,:,:])
            ax[1,1].set_title('NN Defocus')
            im = ax[0,2].imshow(wavefront_target[0,:,:], cmap=pl.cm.viridis, vmin=-5, vmax=5)
            pl.colorbar(im, ax=ax[0,2])
            ax[0,2].set_title('Original wavefront')
            im = ax[1,2].imshow(wavefront[0,:,:], cmap=pl.cm.viridis, vmin=-5, vmax=5)
            pl.colorbar(im, ax=ax[1,2])
            ax[1,2].set_title('NN wavefront')
            ax[2,0].imshow(np.log(psf_target[0,18:82,18:82]), cmap=pl.cm.viridis)
            ax[2,0].set_title('Original PSF')
            ax[2,1].imshow(np.log(psf[0,18:82,18:82]), cmap=pl.cm.viridis)
            ax[2,1].set_title('NN PSF')
            ax[2,2].imshow(wavefront[0,:,:] - wavefront_target[0,:,:], cmap=pl.cm.viridis)
            ax[2,2].set_title('Residual wf rms={0}'.format(np.std(wavefront[0,:,:] - wavefront_target[0,:,:])))

            print(r0[0])
            print(zernike_nn[0,0:5])
            print(zernike_target[0,0:5])

            #ax[i,0].set_ylabel('{0}'.format(r0[n].numpy()))

        #ax[0,0].set_title('Original')
        #ax[0,1].set_title('Focus (input)')
        #ax[0,2].set_title('Focus (NN)')
        #ax[0,3].set_title('Defocus (input)')
        #ax[0,4].set_title('Defocus (NN)')
        #ax[0,5].set_title('Wavefront (target)')
        #ax[0,6].set_title('Wavefront (NN)')
        #ax[0,7].set_title('PSF (target)')
        #ax[0,8].set_title('PSF (NN)')

        #pl.show()
```

We work with a telescope of D=1.5 m, a pixel size of 0.0545$"$ and at 500 nm.

```python
telescope_radius = 0.5*1.5 * u.meter
pixel_size = 0.0545 * u.arcsec / u.pixel
fov = (101, 101) * u.pixel
pix_fov = 101
lambda0 = 500 * u.nm

network = '../training/trained_wavefront/2018-12-19-13:28'
# network = 'trained_wavefront/2018-12-17-11:19'

deep_pd_network = deep_phasediv(2*telescope_radius.value, pixel_size.value, lambda0.to(u.AA).value, pix_fov, n_zernike=37, batch_size=2,
    checkpoint=network)
```

    => loading checkpoint '../training/trained_wavefront/2018-12-19-13:28.pth'
    => loaded checkpoint '../training/trained_wavefront/2018-12-19-13:28.pth'

The wavefront is described as a linear combination of Zernike polynomials, that I show in the following. We discard piston, tip and tilt.

```python
f, ax = pl.subplots(nrows=2, ncols=3, figsize=(15,10))
ax = ax.flatten()
for i in range(6):
    ax[i].imshow(deep_pd_network.zernikes_torch[i,:,:].cpu().numpy(), cmap=pl.cm.viridis)
```

Here is an example of a patch. The Fried radius is 24.5 cm and I show that the wavefront is decently recovered

```python
deep_pd_network.plot(index=2000)
```

```python
interact_manual(deep_pd_network.plot, index=widgets.IntSlider(min=0,max=15000,step=1,value=10));
```

    interactive(children=(IntSlider(value=10, description='index', max=15000), Button(description='Run Interact', …

```python

```


# Cox Mixtures with Heterogeneous Effects Demo
Author: Mononito Goswami (<mgoswami@cs.cmu.edu>)

## 1. Introduction

Estimation of treatment efficacy of real-world clinical interventions involves working with continuous outcomes such as time-to-death, re-hospitalization, or a composite event that may be subject to censoring. Causal reasoning in such scenarios requires decoupling the effects of confounding physiological characteristics that affect baseline survival rates from the effects of the interventions being assessed. In this paper, we present a latent variable approach to model heterogeneous treatment effects by proposing that an individual can belong to one of latent clusters with distinct response characteristics. We show that this latent structure can mediate the base survival rates and helps determine the effects of an intervention. We demonstrate the ability of our approach to discover actionable phenotypes of individuals based on their treatment response on multiple large randomized clinical trials originally conducted to assess appropriate treatment strategies to reduce cardiovascular risk.

Fig A. Schematic description of CMHE.

Fig B. The CMHE Model in Plate Notation

Schematic description of the proposed model is shown in **Figure A**. The set of features (confounders) $\mathbf{x}$ are passed through an encoder to obtain deep non-linear representations. These representations then describe the latent phenogroups $\mathbf{P}(Z|X=\mathbf{x})$ and $\mathbf{P}(\mathbf{\phi}|X=\mathbf{x})$ that determine the base survival rate and the treatment effect respectively. Finally, the individual level hazard (survival) curve under an intervention $A=\mathbf{a}$ is described by marginalizing over $Z$ and $\mathbf{\phi}$ as $\mathbf{S}(t|X=x, A=a) = \mathbf{E}_{(Z,\mathbf{\phi)}\sim \mathbf{P}(\cdot|X)}\big[ \mathbf{S}(t|A=\mathbf{a}, X, Z, \mathbf{\phi})\big]$.

**Figure B** presents the proposed model in Plate Notation. $\mathbf{x}$ confounds treatment assignment $A$ and outcome $T$ (Model parameters and censoring distribution have been abstracted out).

## 2. Synthetic Data Example

```python
import pandas as pd
import torch
from tqdm import tqdm
import sys
sys.path.append('../auton_survival/')

from datasets import load_dataset
from example_utils import *
```

### 2.1. Generative Process for the Synthetic Data

1. Features $x_1$, $x_2$ and the base survival phenotypes $Z$ are sampled from $\texttt{scikit-learn's make_blobs(...)}$ function which generates isotropic Gaussian blobs:
$$[x_1, x_2], Z \sim \texttt{sklearn.datasets.make_blobs(K = 3)}$$
2. Features $x_3$ and $x_4$ are sampled uniformly, whereas the underlying treatment effect phenotypes $\phi$ are defined according to an $L_1$-ball:
$$ [x_1, x_2] \sim \texttt{Uniform}(-2, 2) $$
$$ \phi \triangleq \mathbb{1}\{|x_3| + |x_3| > 2\} $$
3. We then sample treat assignments from a Bernoulli distribution:
$$ A \sim  \texttt{Bernoulli}(\frac{1}{2}) $$
4. Next, the time-to-event $T$ conditioned on the confounders $x$, latent $Z$ and latent effect group $\phi$ are generated from a Gompertz distribution:
$$ T^{*}| (Z=k, {\phi}=m, A={a}) \sim \nonumber \texttt{Gompertz}\big({\beta}_{k}^{\top}{x} +({-a}^m)\big) $$
5. Finally, the observed time $T$ is obtained after censoring some of the events and censoring time is chosen uniformly at random upto $T^*$:
$$\delta \sim \texttt{Bernoulli}(\frac{3}{4}), \quad C \sim \texttt{Uniform}(0, {T}^{*})$$
$$ T =  \begin{cases} T^*, & \text{if } \delta = 1 \\ C, & \text{if } \delta = 0 \end{cases} $$

```python
# Load the synthetic dataset
outcomes, features, interventions = load_dataset(dataset='SYNTHETIC')

# Let's take a look at take the dataset
features.head(5)
```

      X1
      X2
      X3
      X4
      X5
      X6
      X7
      X8

      0
      0.148745
      1.892484
      0.195254
      0.860757
      0.696523
      0.483697
      0.339551
      0.374794

      1
      1.139439
      -0.943330
      0.411054
      0.179533
      0.428686
      0.683057
      0.600948
      0.070483

      2
      -0.961237
      0.782706
      -0.305381
      0.583576
      0.157478
      0.070556
      0.034590
      0.776005

      3
      0.466508
      0.694348
      -0.249651
      1.567092
      0.850959
      0.416178
      0.968841
      0.863598

      4
      -0.249002
      -0.552091
      1.854651
      -0.466234
      0.860385
      0.367184
      0.954347
      0.748930

### 2.2. Visualizing the Synthetic Data

```python
plot_synthetic_data(outcomes, features, interventions)
```

### 2.3 Train and Test data split

```python
# Hyper-parameters
random_seed = 0
test_size = 0.25

# Split the synthetic data into training and testing data
import numpy as np

np.random.seed(random_seed)
n = features.shape[0]

test_idx = np.zeros(n).astype('bool')
test_idx[np.random.randint(n, size=int(n*test_size))] = True

features_tr = features.iloc[~test_idx]
outcomes_tr = outcomes.iloc[~test_idx]
interventions_tr = interventions[~test_idx]
print(f'Number of training data points: {len(features_tr)}')

features_te = features.iloc[test_idx]
outcomes_te = outcomes.iloc[test_idx]
interventions_te = interventions[test_idx]
print(f'Number of test data points: {len(features_te)}')

x_tr = features_tr.values.astype('float32')
t_tr = outcomes_tr['time'].values.astype('float32')
e_tr = outcomes_tr['event'].values.astype('float32')
a_tr = interventions_tr.values.astype('float32')

x_te = features_te.values.astype('float32')
t_te = outcomes_te['time'].values.astype('float32')
e_te = outcomes_te['event'].values.astype('float32')
a_te = interventions_te.values.astype('float32')

print('Training Data Statistics:')
print(f'Shape of covariates: {x_tr.shape} | times: {t_tr.shape} | events: {e_tr.shape} | interventions: {a_tr.shape}')
```

    Number of training data points: 3899
    Number of test data points: 1101
    Training Data Statistics:
    Shape of covariates: (3899, 8) | times: (3899,) | events: (3899,) | interventions: (3899,)

```python
def find_max_treatment_effect_phenotype(g, zeta_probs, factual_outcomes):
    """
    Find the group with the maximum treatement effect phenotype
    """
    mean_differential_survival = np.zeros(zeta_probs.shape[1]) # Area under treatment phenotype group
    outcomes_train, interventions_train = factual_outcomes

    # Assign each individual to their treatment phenotype group
    for gr in range(g): # For each treatment phenotype group
        # Probability of belonging the the g^th treatment phenotype
        zeta_probs_g = zeta_probs[:, gr]
        # Consider only those individuals who are in the top 75 percentiles in this phenotype
        z_mask = zeta_probs_g>np.quantile(zeta_probs_g, 0.75)

        mean_differential_survival[gr] = find_mean_differential_survival(
            outcomes_train.loc[z_mask], interventions_train.loc[z_mask])

    return np.nanargmax(mean_differential_survival)
```

## 3. CMHE for Counterfactual Regression

### 3.1 Train CMHE for Counterfactual Regression

```python
# Hyper-parameters to train model
k = 1 # number of underlying base survival phenotypes
g = 2 # number of underlying treatment effect phenotypes.
layers = [50, 50] # number of neurons in each hidden layer.

model_random_seed = 3
iters = 50 # number of training epochs
learning_rate = 0.001
batch_size = 128
vsize = 0.15 # size of the validation split
patience = 3
optimizer = "Adam"
```

```python
from models.cmhe import DeepCoxMixturesHeterogenousEffects

torch.manual_seed(model_random_seed)
np.random.seed(model_random_seed)

# Instantiate the CMHE model
model = DeepCoxMixturesHeterogenousEffects(k=k, g=g, layers=layers)

model = model.fit(x_tr, t_tr, e_tr, a_tr, vsize=vsize, val_data=None, iters=iters,
                  learning_rate=learning_rate, batch_size=batch_size,
                  optimizer=optimizer, random_state=model_random_seed,
                  patience=patience)
```

      0%|                                                   | 0/50 [00:00<?, ?it/s]/zfsauton2/home/mgoswami/auton-survival/examples/../auton_survival/models/cmhe/cmhe_utilities.py:121: RuntimeWarning: invalid value encountered in log
      probs.append(np.log(event_probs))
    /zfsauton2/home/mgoswami/auton-survival/examples/../auton_survival/models/cmhe/cmhe_utilities.py:71: RuntimeWarning: invalid value encountered in power
      return spl(ts)**risks
    /zfsauton2/home/mgoswami/auton-survival/examples/../auton_survival/models/cmhe/cmhe_utilities.py:66: RuntimeWarning: invalid value encountered in power
      s0ts = (-risks)*(spl(ts)**(risks-1))
    /zfsauton2/home/mgoswami/auton-survival/examples/../auton_survival/models/cmhe/cmhe_utilities.py:121: RuntimeWarning: divide by zero encountered in log
      probs.append(np.log(event_probs))
    100%|██████████████████████████████████████████| 50/50 [01:05<00:00,  1.31s/it]

```python
print(f'Treatment Effect for the {g} groups: {model.torch_model[0].omega.detach()}')

zeta_probs_train = model.predict_latent_phi(x_tr)
zeta_train =  np.argmax(zeta_probs_train, axis=1)
print(f'Distribution of individuals in each treatement phenotype in the training data: \
{np.unique(zeta_train, return_counts=True)[1]}')

max_treat_idx_CMHE = find_max_treatment_effect_phenotype(
    g=2, zeta_probs=zeta_probs_train, factual_outcomes=(outcomes_tr, interventions_tr))
print(f'\nGroup {max_treat_idx_CMHE} has the maximum restricted mean survival time on the training data!')
```

    Treatment Effect for the 2 groups: tensor([-0.5131,  0.3845])
    Distribution of individuals in each treatement phenotype in the training data: [1968 1931]

    Group 1 has the maximum restricted mean survival time on the training data!

    /zfsauton2/home/mgoswami/anaconda3/envs/cox-subgroup/lib/python3.8/site-packages/lifelines/fitters/__init__.py:204: ApproximationWarning: Approximating using linear interpolation`.

      warnings.warn("Approximating using linear interpolation`.\n", exceptions.ApproximationWarning)

### 3.2 Evaluate CMHE on Test Data

```python
# Now for each individual in the test data, let's find the probability that
# they belong to the max treatment effect group

zeta_probs_test_CMHE = model.predict_latent_phi(x_te)
zeta_test =  np.argmax(zeta_probs_test_CMHE, axis=1)
print(f'Distribution of individuals in each treatement phenotype in the test data: \
{np.unique(zeta_test, return_counts=True)[1]}')

# Now let us evaluate our performance
plot_phenotypes_roc(outcomes_te, zeta_probs_test_CMHE[:, max_treat_idx_CMHE])
```

### 3.3 Comparison with the Clustering phenotyper

We compare the ability of CMHE against dimensionality reduction followed by clustering for counterfactual phenotyping. Specifically, we first perform dimensionality reduction of the input confounders, $\mathbf{x}$, followed by clustering. Due to a small number of confounders in the synthetic data, in the following experiment, we directly perform clustering using a Gaussian Mixture Model (GMM) with 2 components and diagonal covariance matrices.

```python
from phenotyping import ClusteringPhenotyper
from sklearn.metrics import auc

clustering_method = 'gmm'
dim_red_method = None # We would not perform dimensionality reduction for the synthetic dataset
n_components = None
n_clusters = 2 # Number of underlying treatment effect phenotypes

# Running the phenotyper
phenotyper = ClusteringPhenotyper(clustering_method=clustering_method,
                                  dim_red_method=dim_red_method,
                                  n_components=n_components,
                                  n_clusters=n_clusters)
```

```python
zeta_probs_train = phenotyper.fit_phenotype(features_tr.values)
zeta_train = np.argmax(zeta_probs_train, axis=1)
print(f'Distribution of individuals in each treatement phenotype in the training data: \
{np.unique(zeta_train, return_counts=True)[1]}')

max_treat_idx_CP = find_max_treatment_effect_phenotype(
    g=2, zeta_probs=zeta_probs_train, factual_outcomes=(outcomes_tr, interventions_tr))
print(f'\nGroup {max_treat_idx_CP} has the maximum restricted mean survival time on the training data!')
```

    No Dimensionaity reduction specified...
     Proceeding to learn clusters with the raw features...
    Fitting the following Clustering Model:
     GaussianMixture(covariance_type='diag', n_components=3)
    Distribution of individuals in each treatement phenotype in the training data: [ 612 1408 1879]

    Group 2 has the maximum restricted mean survival time on the training data!

    /zfsauton2/home/mgoswami/anaconda3/envs/cox-subgroup/lib/python3.8/site-packages/lifelines/fitters/__init__.py:204: ApproximationWarning: Approximating using linear interpolation`.

      warnings.warn("Approximating using linear interpolation`.\n", exceptions.ApproximationWarning)

### 3.4 Evaluate Clustering Phenotyper on Test Data

```python
# Now for each individual in the test data, let's find the probability that
# they belong to the max treatment effect group

# Use the phenotyper trained on training data to phenotype on testing data
zeta_probs_test_CP = phenotyper.phenotype(x_te)
zeta_test_CP =  np.argmax(zeta_probs_test_CP, axis=1)
print(f'Distribution of individuals in each treatement phenotype in the test data: \
{np.unique(zeta_test_CP, return_counts=True)[1]}')

# Now let us evaluate our performance
plot_phenotypes_roc(outcomes_te, zeta_probs_test_CP[:, max_treat_idx_CP])
```

## 4. CMHE for Factual Regression

For completeness, we further evaluate the performance of CMHE in estimating factual risk over multiple time horizons using the standard survival analysis metrics, including:

1. $\textbf{Brier Score} \ (\textrm{BS})$: Defined as the Mean Squared Error (MSE) around the probabilistic prediction at a certain time horizon.
\begin{align}
\text{BS}(t) = \mathop{\mathbf{E}}_{x\sim\mathcal{D}}\big[ ||\mathbf{1}\{ T > t \} - \widehat{\mathbf{P}}(T>t|X)\big)||_{_\textbf{2}}^\textbf{2}  \big]
\end{align}
2. $ \textbf{Time Dependent Concordance Index} \ (C^{\text{td}}$): A rank order statistic that computes model performance in ranking patients based on their estimated risk at a specfic time horizon.
\begin{align}
C^{td }(t) = \mathbf{P}\big( \hat{F}(t| \mathbf{x}_i) > \hat{F}(t| \mathbf{x}_j)  | \delta_i=1, T_i<T_j, T_i \leq t \big)
\end{align}

We compute the censoring adjusted estimates of the Time Dependent Concordance Index (Antolini et al., 2005; Gerds et al., 2013) and the Integrated Brier Score (i.e. Brier Score integrated over 1, 3 and 5 years. $\text{IBS} = \mathop{\sum}_t  \frac{t}{t_\text{max}}  \cdot \text{BS}(t)$) (Gerds and Schumacher, 2006; Graf et al., 1999) to assess both discriminative performance and model calibration at multiple time horizons.

*We find that CMHE had similar or better discriminative performance than a simple Cox Model with MLP hazard functions. CMHE was also better calibrated as evidenced by overall lower Integrated Brier Score, suggesting utility for factual risk estimation.*

### 4.1 Evaluate CMHE on Test Data

```python
horizons = [1, 3, 5]

# Now let us predict survival using CMHE
predictions_test_CMHE = model.predict_survival(x_te, a_te, t=horizons)

CI1, CI3, CI5, IBS = factual_evaluate((x_tr, t_tr, e_tr, a_tr), (x_te, t_te, e_te, a_te),
                                      horizons, predictions_test_CMHE)
print(f'Concordance Index (1 Year): {np.around(CI1, 4)} (3 Year) {np.around(CI3, 4)}: (5 Year): {np.around(CI5, 4)}')
print(f'Integrated Brier Score: {np.around(IBS, 4)}')
```

    Concordance Index (1 Year): 0.6876 (3 Year) 0.6947: (5 Year): 0.6968
    Integrated Brier Score: 0.1514

### 4.2 Comparison with Deep Cox-proportional Hazards Model

```python
from auton_survival.estimators import SurvivalModel

# Now let us train a Deep Cox-proportional Hazard model with two linear layers and tanh activations
random_seed = 0
hyperparams = {'layers':[[50, 50]],
               'lr':[1e-3],
               'bs':[128],
               'activation':['tanh']}
```

```python
dcph_model = SurvivalModel(model='dcph', random_seed=0, hyperparams=hyperparams)

interventions_tr.name, interventions_te.name = 'treat', 'treat'
features_tr_dcph = pd.concat([features_tr, interventions_tr], axis=1)
features_te_dcph = pd.concat([features_te, interventions_te], axis=1)

# Train the DCPH model
dcph_model = dcph_model.fit(features_tr_dcph, outcomes_tr)
```

    0:	[0s / 0s],		train_loss: 3.4532,	val_loss: 3.4632
    1:	[0s / 0s],		train_loss: 3.3954,	val_loss: 3.4371
    2:	[0s / 0s],		train_loss: 3.3650,	val_loss: 3.4152
    3:	[0s / 0s],		train_loss: 3.3477,	val_loss: 3.4047
    4:	[0s / 0s],		train_loss: 3.3262,	val_loss: 3.3957
    5:	[0s / 1s],		train_loss: 3.3250,	val_loss: 3.3977
    6:	[0s / 1s],		train_loss: 3.3166,	val_loss: 3.3921
    7:	[0s / 1s],		train_loss: 3.3198,	val_loss: 3.3872
    8:	[0s / 1s],		train_loss: 3.3122,	val_loss: 3.3956
    9:	[0s / 1s],		train_loss: 3.3094,	val_loss: 3.3870
    10:	[0s / 1s],		train_loss: 3.3096,	val_loss: 3.3898
    11:	[0s / 1s],		train_loss: 3.3086,	val_loss: 3.3829
    12:	[0s / 2s],		train_loss: 3.3122,	val_loss: 3.3848
    13:	[0s / 2s],		train_loss: 3.3011,	val_loss: 3.3826
    14:	[0s / 2s],		train_loss: 3.3027,	val_loss: 3.3834
    15:	[0s / 2s],		train_loss: 3.2962,	val_loss: 3.3813
    16:	[0s / 2s],		train_loss: 3.2998,	val_loss: 3.3841
    17:	[0s / 2s],		train_loss: 3.2925,	val_loss: 3.3838
    18:	[0s / 2s],		train_loss: 3.2986,	val_loss: 3.3823
    19:	[0s / 2s],		train_loss: 3.2884,	val_loss: 3.3801
    20:	[0s / 2s],		train_loss: 3.2876,	val_loss: 3.3791
    21:	[0s / 3s],		train_loss: 3.2769,	val_loss: 3.3832
    22:	[0s / 3s],		train_loss: 3.2780,	val_loss: 3.3746
    23:	[0s / 3s],		train_loss: 3.2781,	val_loss: 3.3786
    24:	[0s / 3s],		train_loss: 3.2726,	val_loss: 3.3837
    25:	[0s / 3s],		train_loss: 3.2717,	val_loss: 3.3707
    26:	[0s / 3s],		train_loss: 3.2811,	val_loss: 3.3745
    27:	[0s / 3s],		train_loss: 3.2788,	val_loss: 3.3713
    28:	[0s / 4s],		train_loss: 3.2759,	val_loss: 3.3757
    29:	[0s / 4s],		train_loss: 3.2737,	val_loss: 3.3727
    30:	[0s / 4s],		train_loss: 3.2634,	val_loss: 3.3662
    31:	[0s / 4s],		train_loss: 3.2579,	val_loss: 3.3700
    32:	[0s / 4s],		train_loss: 3.2546,	val_loss: 3.3659
    33:	[0s / 4s],		train_loss: 3.2525,	val_loss: 3.3711
    34:	[0s / 4s],		train_loss: 3.2624,	val_loss: 3.3742
    35:	[0s / 4s],		train_loss: 3.2626,	val_loss: 3.3682
    36:	[0s / 5s],		train_loss: 3.2522,	val_loss: 3.3710
    37:	[0s / 5s],		train_loss: 3.2538,	val_loss: 3.3656
    38:	[0s / 5s],		train_loss: 3.2633,	val_loss: 3.3629
    39:	[0s / 5s],		train_loss: 3.2537,	val_loss: 3.3658
    40:	[0s / 5s],		train_loss: 3.2494,	val_loss: 3.3608
    41:	[0s / 5s],		train_loss: 3.2437,	val_loss: 3.3665
    42:	[0s / 5s],		train_loss: 3.2431,	val_loss: 3.3643
    43:	[0s / 5s],		train_loss: 3.2516,	val_loss: 3.3680
    44:	[0s / 6s],		train_loss: 3.2487,	val_loss: 3.3597
    45:	[0s / 6s],		train_loss: 3.2450,	val_loss: 3.3704
    46:	[0s / 6s],		train_loss: 3.2503,	val_loss: 3.3636
    47:	[0s / 6s],		train_loss: 3.2525,	val_loss: 3.3664
    48:	[0s / 6s],		train_loss: 3.2386,	val_loss: 3.3634
    49:	[0s / 6s],		train_loss: 3.2405,	val_loss: 3.3588

### 4.3 Evaluate DCPH on Test Data

```python
# Find suvival scores in the test data
predictions_test_DCPH = dcph_model.predict_survival(features_te_dcph, horizons)

CI1, CI3, CI5, IBS = factual_evaluate((x_tr, t_tr, e_tr, a_tr), (x_te, t_te, e_te, a_te),
                                      horizons, predictions_test_DCPH)
print(f'Concordance Index (1 Year): {np.around(CI1, 4)} (3 Year) {np.around(CI3, 4)}: (5 Year): {np.around(CI5, 4)}')
print(f'Integrated Brier Score: {np.around(IBS, 4)}')
```

    Concordance Index (1 Year): 0.6908 (3 Year) 0.6926: (5 Year): 0.6946
    Integrated Brier Score: 0.1531

```python

```

examples/CMHE Experiments on Synthetic Data.ipynb


# 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
import scipy.optimize as opt
```

```python
## Construcción de parámetros
# 1. Sigma: matriz de varianza-covarianza
Sigma = np.array(np.diag(annual_ret_summ.loc['Volatilidad']).dot(corr).dot(np.diag(annual_ret_summ.loc['Volatilidad'])))
# 2. Eind: rendimientos esperados activos individuales
Eind = np.array(annual_ret_summ.loc['Media'])
```

```python
# Función objetivo
def varianza(w, Sigma):
    return w.dot(Sigma).dot(w)
```

```python
# Dato inicial
w0 = np.ones((5,))/5
# Cotas de las variables
bnds = ((0,None),)*5
# Restricciones
cons = ({'type': 'eq', 'fun': lambda w: np.sum(w)-1},)
```

```python
# Portafolio de mínima varianza
#min_var = opt.minimize(varianza, w0, args=(Sigma,), bounds=bnds, constraints=cons)
min_var = opt.minimize(varianza, w0, args=(Sigma,), constraints=cons)
min_var
```

         fun: 0.018597185133053987
         jac: array([0.03710198, 0.03730888, 0.03695629, 0.03754007, 0.03706507])
     message: 'Optimization terminated successfully.'
        nfev: 49
         nit: 7
        njev: 7
      status: 0
     success: True
           x: array([ 0.63118955, -0.01320919, -0.02229447,  0.22484425,  0.17946986])

```python
# Pesos, rendimiento y riesgo del portafolio de mínima varianza
w_minvar = min_var.x
E_minvar = Eind.dot(w_minvar)
s_minvar = np.sqrt(w_minvar.dot(Sigma).dot(w_minvar))
E_minvar, s_minvar
```

    (0.13917250171316226, 0.13637149677646715)

##### 1.2. Encontrar portafolio EMV

```python
# Función objetivo
def sharpe_r(w, Eind, Sigma, rf):
    return -(Eind.dot(w)-rf)/np.sqrt(w.dot(Sigma).dot(w))
```

```python
# Dato inicial
w0 = np.ones((5,))/5
# Cotas de las variables
bnds = ((0,None),)*5
# Restricciones
cons = ({'type': 'eq', 'fun': lambda w: np.sum(w)-1},)
```

```python
# Portafolio EMV
#min_var = opt.minimize(varianza, w0, args=(Sigma,), bounds=bnds, constraints=cons)
EMV = opt.minimize(sharpe_r, w0, args=(Eind, Sigma, rf), constraints=cons)
EMV
```

         fun: -0.664437313467
         jac: array([-0.36094854, -0.36085168, -0.36128246, -0.36058135, -0.36095859])
     message: 'Optimization terminated successfully.'
        nfev: 42
         nit: 6
        njev: 6
      status: 0
     success: True
           x: array([0.50719417, 0.07489246, 0.02359206, 0.19050772, 0.2038136 ])

```python
# Pesos, rendimiento y riesgo del portafolio EMV
w_EMV = EMV.x
E_EMV = Eind.dot(w_EMV)
s_EMV = np.sqrt(w_EMV.dot(Sigma).dot(w_EMV))
E_EMV, s_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
cov_port = w_minvar.dot(Sigma).dot(w_EMV)
cov_port
```

    0.01859498639216849

```python
# Correlación entre los portafolios
corr_port = cov_port/(s_EMV*s_minvar)
corr_port
```

    0.984161982875638

```python
# Vector de w
w = np.linspace(-4, 10, 1401)
```

```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
fron_minvar = pd.DataFrame(columns=['wEMV', '1-wEMV', 'Ret', 'Vol', 'SR'])
fron_minvar['wEMV'] = w
fron_minvar['1-wEMV'] = 1-w
fron_minvar['Ret'] = w*E_EMV+(1-w)*E_minvar
fron_minvar['Vol'] = np.sqrt((w*s_EMV)**2+((1-w)*s_minvar)**2+2*w*(1-w)*cov_port)
fron_minvar['SR'] = (fron_minvar['Ret']-rf)/fron_minvar['Vol']
fron_minvar.round(4)
```

      wEMV
      1-wEMV
      Ret
      Vol
      SR

      0
      -4.00
      5.00
      0.1276
      0.1681
      0.4617

      1
      -3.99
      4.99
      0.1277
      0.1680
      0.4623

      2
      -3.98
      4.98
      0.1277
      0.1678
      0.4629

      3
      -3.97
      4.97
      0.1277
      0.1677
      0.4634

      4
      -3.96
      4.96
      0.1277
      0.1676
      0.4640

      5
      -3.95
      4.95
      0.1278
      0.1674
      0.4646

      6
      -3.94
      4.94
      0.1278
      0.1673
      0.4651

      7
      -3.93
      4.93
      0.1278
      0.1671
      0.4657

      8
      -3.92
      4.92
      0.1279
      0.1670
      0.4663

      9
      -3.91
      4.91
      0.1279
      0.1668
      0.4669

      10
      -3.90
      4.90
      0.1279
      0.1667
      0.4674

      11
      -3.89
      4.89
      0.1279
      0.1666
      0.4680

      12
      -3.88
      4.88
      0.1280
      0.1664
      0.4686

      13
      -3.87
      4.87
      0.1280
      0.1663
      0.4691

      14
      -3.86
      4.86
      0.1280
      0.1661
      0.4697

      15
      -3.85
      4.85
      0.1281
      0.1660
      0.4703

      16
      -3.84
      4.84
      0.1281
      0.1659
      0.4708

      17
      -3.83
      4.83
      0.1281
      0.1657
      0.4714

      18
      -3.82
      4.82
      0.1282
      0.1656
      0.4720

      19
      -3.81
      4.81
      0.1282
      0.1654
      0.4726

      20
      -3.80
      4.80
      0.1282
      0.1653
      0.4731

      21
      -3.79
      4.79
      0.1282
      0.1652
      0.4737

      22
      -3.78
      4.78
      0.1283
      0.1650
      0.4743

      23
      -3.77
      4.77
      0.1283
      0.1649
      0.4748

      24
      -3.76
      4.76
      0.1283
      0.1647
      0.4754

      25
      -3.75
      4.75
      0.1284
      0.1646
      0.4760

      26
      -3.74
      4.74
      0.1284
      0.1645
      0.4766

      27
      -3.73
      4.73
      0.1284
      0.1643
      0.4771

      28
      -3.72
      4.72
      0.1284
      0.1642
      0.4777

      29
      -3.71
      4.71
      0.1285
      0.1641
      0.4783

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

      1371
      9.71
      -8.71
      0.1672
      0.2746
      0.4267

      1372
      9.72
      -8.72
      0.1672
      0.2749
      0.4265

      1373
      9.73
      -8.73
      0.1672
      0.2751
      0.4262

      1374
      9.74
      -8.74
      0.1673
      0.2753
      0.4260

      1375
      9.75
      -8.75
      0.1673
      0.2755
      0.4258

      1376
      9.76
      -8.76
      0.1673
      0.2757
      0.4256

      1377
      9.77
      -8.77
      0.1674
      0.2759
      0.4253

      1378
      9.78
      -8.78
      0.1674
      0.2761
      0.4251

      1379
      9.79
      -8.79
      0.1674
      0.2764
      0.4249

      1380
      9.80
      -8.80
      0.1674
      0.2766
      0.4247

      1381
      9.81
      -8.81
      0.1675
      0.2768
      0.4244

      1382
      9.82
      -8.82
      0.1675
      0.2770
      0.4242

      1383
      9.83
      -8.83
      0.1675
      0.2772
      0.4240

      1384
      9.84
      -8.84
      0.1676
      0.2774
      0.4238

      1385
      9.85
      -8.85
      0.1676
      0.2776
      0.4235

      1386
      9.86
      -8.86
      0.1676
      0.2778
      0.4233

      1387
      9.87
      -8.87
      0.1676
      0.2781
      0.4231

      1388
      9.88
      -8.88
      0.1677
      0.2783
      0.4229

      1389
      9.89
      -8.89
      0.1677
      0.2785
      0.4227

      1390
      9.90
      -8.90
      0.1677
      0.2787
      0.4224

      1391
      9.91
      -8.91
      0.1678
      0.2789
      0.4222

      1392
      9.92
      -8.92
      0.1678
      0.2791
      0.4220

      1393
      9.93
      -8.93
      0.1678
      0.2793
      0.4218

      1394
      9.94
      -8.94
      0.1679
      0.2796
      0.4216

      1395
      9.95
      -8.95
      0.1679
      0.2798
      0.4213

      1396
      9.96
      -8.96
      0.1679
      0.2800
      0.4211

      1397
      9.97
      -8.97
      0.1679
      0.2802
      0.4209

      1398
      9.98
      -8.98
      0.1680
      0.2804
      0.4207

      1399
      9.99
      -8.99
      0.1680
      0.2806
      0.4205

      1400
      10.00
      -9.00
      0.1680
      0.2809
      0.4202

1401 rows × 5 columns

```python
# Importar librerías de gráficos
import matplotlib.pyplot as plt
%matplotlib inline
```

```python
# Gráfica de dispersión de puntos coloreando
# de acuerdo a SR
plt.figure(figsize=(10,7))
plt.plot(s_minvar, E_minvar, '*g', ms=20, label = 'Port. Min. Var.')
plt.plot(s_EMV, E_EMV, '*k', ms=20, label = 'Port. EMV')
plt.plot(annual_ret_summ.loc['Volatilidad'], Eind, 'oy', ms=10, label = 'Activos ind.')
plt.scatter(fron_minvar['Vol'], fron_minvar['Ret'], c=fron_minvar['SR'], cmap='RdYlBu', label = 'Front. Min. Var')
plt.xlabel('Volatility $\sigma$')
plt.ylabel('Expected return $E[r]$')
plt.legend(loc='best')

plt.colorbar()
plt.show()
```

**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.