# Introducción al MEF en una dimensión

*Alejandro Alonso Membrilla*

En este cuaderno se expone la solución al ejercicio obligatorio sobre la resolución numérica de un problema con condiciones de tipo Robin.

Aplicaremos el método sugerido en el comentario previo al enunciado del ejercicio, basado en el segundo método para resolver el problema de tipo Neumann con condiciones a la derecha del intervalo visto en las clases de prácticas. Como se verá, esta técnica calcula directamente la solución al problema (sin necesidad de descomponerla en una solución homogénea y un término más sencillo) añadiendo ciertos términos al vector de cargas y a la matriz de rigidez.

```python
import numpy as np # Importamos el módulo NumPy con el pseudónimo np
import sympy as sp # Importamos el módulo SymPy con el pseudónimo sp
import math
%matplotlib inline
import matplotlib.pyplot as plt
```

# Definición de las funciones de base del E.F.

```python
t,z,xL,xR,yL,yR = sp.symbols('t,z,xL,xR,yL,yR')
def linea(z,xL,yL,xR,yR):
    """linea que pasa por los puntos (xL,yL) y (xR,yR)"""
    return yL+(z-xL)*(yL-yR)/(xL-xR)
```

```python
def l1(t):
    """primera func. de base del E.F. de Lagrange P1 en 1D"""
    return 1-t

def l2(t):
    """segunda func. de base del E.F. de Lagrange P1 en 1D"""
    return t
```

```python
def dl1(t):
    """derivada de la primera func. de base del E.F. de Lagrange P1 en 1D"""
    return -1

def dl2(t):
    """derivada de la segunda func. de base del E.F. de Lagrange P1 en 1D"""
    return 1
```

```python
def Finv(z,a,b):
    """afinidad entre cada subintervalo de la partición
        y el intervalo unidad"""
    return (z-a)/(b-a)

def dFinv(z,a,b):
    """ derivada de la afinidad Finv"""
    return 1/(b-a)
```

```python
def wi(z,x,i):  # en los nodos interiores
    """funciones de base del E.F. de Lagrange P1 unidimensional"""
    if (x[i-1]<=z)*(z<=x[i]):
        valor = l2(Finv(z,x[i-1],x[i]))
    elif (x[i]<=z)*(z<=x[i+1]):
        valor = l1(Finv(z,x[i],x[i+1]))
    else:
        valor = 0
    return valor

def w0(z,x):  # en el extremo izquierdo del intervalo
    """funcion de base del E.F. de Lagrange P1 unidimensional
    en el extremo izquierdo"""
    if (x[0]<=z)*(z<=x[1]):
        valor = l1(Finv(z,x[0],x[1]))
    else:
        valor = 0
    return valor

def wn(z,x):  # en el extremo derecho del intervalo
    """funcion de base del E.F. de Lagrange P1 unidimensional
    en el extremo derecho"""
    # recuérdese que x[-1] y x[-2] indican respectivamente el último y penúltimo nodos
    if (x[-2]<=z)*(z<=x[-1]):
        valor = l2(Finv(z,x[-2],x[-1]))
    else:
        valor = 0
    return valor
```

```python
def f(z):
    return np.sin(z)
```

# Planteamiento del problema diferencial de 2º orden a resolver

Supongamos que queremos resolver un problema de contorno de segundo orden muy sencillo, con condiciones de tipo Dirichlet en los extremos de cierto intervalo $[a,b]$; pongamos por ejemplo

$$-y''(x) = f(x), \; x\in [a,b]\\
y(a) = y_a,\quad y(b)=y_b$$

para cierta función $f(x)$ continua en dicho intervalo. Tomemos por ejemplo $f(x)=\sin(x),\; x\in[0,10]$ para empezar.

```python
z = sp.Symbol('z')
y   = sp.Function('y')
def L(y,z): # usaremos la variable z como variable independiente
    """operador diferencial que define la ec. dif."""
    return -sp.diff(y(z),z,2)
L(y,z)
```

$\displaystyle - \frac{d^{2}}{d z^{2}} y{\left(z \right)}$

```python
# z = sp.Symbol('z')
def f(z):
    """función del segundo término de la ec. diferencial"""
    return sp.sin(z)

sp.Eq(L(y,z),f(z)) #  ec. diferencial que estamos considerando
```

$\displaystyle - \frac{d^{2}}{d z^{2}} y{\left(z \right)} = \sin{\left(z \right)}$

# Caso de condiciones mixtas (de tipo Robin)

#### Posibilidad de proponer como ejercicio este caso

Resolveremos finalmente la misma ecuación diferencial anterior, pero con alguna de las condiciones de tipo Robin (o mixto), aunque la otra siga siendo de tipo Dirichlet en alguno de los dos extremos de cierto intervalo $[a,b]$, con lo cuál seguimos teniendo asegurada la unicidad de la correspondiente solución; pongamos por ejemplo (con $|\alpha_b|+|\beta_b|\neq 0$)

$$-y''(x) = f(x), \; x\in [a,b]\\
y(a) = y_a,\quad \alpha_b y(b)+\beta_b y'(b)=\widehat{y}_b$$

para cierta función $f(x)$ continua en dicho intervalo. Seguiremos tomando $f(x)=\sin(x),\; x\in[0,10]$ como antes.

**Comentario:** Nótese que si $\beta_b = 0$ entonces la condición de tipo Robin se reduciría a una condición de tipo Dirichlet estandard ($y(b)=\frac{\widehat{y}_b}{\alpha_b}$), mientras que si por el contrario $\alpha_b = 0$ entonces dicha condición se convierte en una condición de tipo Neumann ($y'(b)=\frac{\widehat{y}_b}{\beta_b}$); así pues, consideraremos $\alpha_b, \beta_b\neq 0$ a partir de ahora.

De manera completamente equivalente se podría haber planteado el problema con condiciones de tipo Robin en el extremo de la izquierda:
$$\alpha_a y(a)+\beta_a y'(a)=\widehat{y}_a, \quad y(b) = y_b $$

Buscaremos soluciones aproximadas de la siguiente forma
$$\widetilde{y}_h(x) = y_0 w_0(x)+ \sum_{j=1}^{n}y_j w_j(x)\equiv y_a w_0(x)+\widetilde{z}_h(x),$$ con el valor $y_0=y_a$ fijado a partir de la condición de tipo Dirichlet dada en ese extremo izquierdo del intervalo, mientras que tendremos que dejar $y_n$ libre, por la condición de tipo mixto impuesta en el otro extremo del intervalo.  Como es habitual, usaremos las funciones de base $w_i(x),\, i=0,1,\ldots, n$ para este E.F. de Lagrange lineal.

En este caso pues, también se deberá satisfacer la correspondiente formulación variacional del problema correspondiente a una condición de tipo Robin: $\alpha_b y(b)+\beta_b y'(b)=\widehat{y}_b$ en el extremo derecho del intervalo $[a,b]$ (con $\beta_b\neq 0$), a saber

$$\mathcal{A}(\widetilde{y}_h,w_i)
+w_i(b)\widetilde{y}_h(b)\frac{\alpha_b}{\beta_b}
=\mathcal{B}_{f}(w_i)+w_i(b)\frac{\widehat{y}_b}{\beta_b},\; \forall i =1,\ldots,n$$

donde ahora podríamos escribir $\widetilde{\mathcal{A}}(w_i,w_j)\equiv\mathcal{A}(w_i,w_j)
+w_i(b)\, w_j(b)\,\frac{\alpha_b}{\beta_b}$  y
$\widetilde{\mathcal{B}}_{f}(w_i)\equiv\mathcal{B}_{f}(w_i)+ w_i(b)\frac{\widehat{y}_b}{\beta_b}$; pero claramente estos nuevos  sumandos sólo cambiarán algo cuando ni $w_i(b)$ ni $w_j(b)$ se anule; es decir para los índices $i=j=n$.

Usando además la bilinealidad de la forma cuadrática $\mathcal{A}$ nos quedaría $\forall i =1,\ldots,n$ que

$$\widetilde{\mathcal{B}}_{f}(w_i)= \int_a^b f(x)w_i(x)\,dx + w_i(b)\frac{\widehat{y}_b}{\beta_b}\\
= \widetilde{\mathcal{A}}(y_a w_0+ \sum_{j=1}^{n}y_j w_j,w_i) \\
=y_a \mathcal{A}(w_0,w_i)+w_0(b)\,w_i(b)\,\frac{\alpha_b}{\beta_b}\\
+\sum_{j=1}^{n}y_j \mathcal{A}(w_j,w_i)
+w_i(b)\,w_j(b)\, \frac{\alpha_b}{\beta_b}$$

pero claramente todos los elementos $w_i(b)=w_j(b)=0,\; \forall i=1,\ldots,n-1$ y $\mathcal{A}(w_0,w_i)$ también serán nulos salvo en todo caso el correspondiente a $i=1$
$$\mathcal{A}(w_0,w_1)=-\frac{1}{x_1-x_0}.$$

Así pues, tan sólo tendríamos que modificar el primer elemento del vector de cargas, sumándole el siguiente término:

$$-y_a\mathcal{A}(w_0,w_1) = +\frac{y_a}{x_1-x_0}$$

En cuanto a los términos $w_i(b)\,w_j(b)\,\frac{\alpha_b}{\beta_b}$, nótese que todos se anularían también, salvo el caso $i=j=n$; por lo tanto esto provocaría que cambie tan sólo el último elemento de la diagonal de la matriz de rígidez:
$\widetilde{\mathcal{A}}(w_n,w_n) = \mathcal{A}(w_n,w_n) +\,\frac{\alpha_b}{\beta_b}$.

**Ejercicio (obligatorio):** Terminar de implementar el código correspondiente a la resolución numérica aproximada de un problema como este con condiciones de contorno de tipo Robin, en uno de los extremos del intervalo y resuelva el correspondiente problema con $a=0$, $b=M=\max_\limits{1\leq i\leq 8}{d_i}$, $y_a=d_2$, $\alpha_b = \max{(d_3,1)}$, $\beta_b = \max{(d_4,1)}$ e $\widehat{y}_b = d_5$, si el último dígito de su DNI es par o 0; o bien $a=0$, $b=M$, $y_b=d_2$, $\alpha_a = \max{ (d_3,1)}$, $\beta_a =\max{(1, d_4)}$ e $\widehat{y}_a = d_5$, si el último dígito de su DNI es impar, siendo $d_i$ (con $i=1,\ldots 8$) el dígito de su DNI o pasaporte situado en la posición i-ésima.

```python
solgen = sp.dsolve(sp.Eq(-y(z).diff(z,2), f(z)))
print(solgen)
solgen
```

    Eq(y(z), C1 + C2*z + sin(z))

$\displaystyle y{\left(z \right)} = C_{1} + C_{2} z + \sin{\left(z \right)}$

```python
# Para calcular la solución exacta representaremos nuestros parámetros
# de forma simbólica y construiremos un método general
a,b,α,β = sp.symbols('a,b,α,β')
ya,yb = sp.symbols('ya,yb')
C1,C2 = sp.symbols('C1,C2')
```

```python
print(sp.diff(C1 + C2*z + sp.sin(z),z))
```

    C2 + cos(z)

```python
ecs = (C1 + C2*z + sp.sin(z)).subs({z:a})-ya, (α*(C1 + C2*z + sp.sin(z))+β*(C2 + sp.cos(z))).subs({z:b})-yb
ecs  # continuar a partir de aquí
```

    (C1 + C2*a - ya + sin(a), -yb + α*(C1 + C2*b + sin(b)) + β*(C2 + cos(b)))

```python
c1c2 = sp.solve(ecs,[C2,C1])
```

```python
c1c2
```

    {C2: ya*α/(a*α - b*α - β) - yb/(a*α - b*α - β) - α*sin(a)/(a*α - b*α - β) + α*sin(b)/(a*α - b*α - β) + β*cos(b)/(a*α - b*α - β),
     C1: a*yb/(a*α - b*α - β) - a*α*sin(b)/(a*α - b*α - β) - a*β*cos(b)/(a*α - b*α - β) - b*ya*α/(a*α - b*α - β) + b*α*sin(a)/(a*α - b*α - β) - ya*β/(a*α - b*α - β) + β*sin(a)/(a*α - b*α - β)}

```python
# Sustituimos C1 y C2 en la solución general de la ecuación
C1_val = (a*(-yb + α*sp.sin(b) + β*sp.cos(b)) + (ya - sp.sin(a))*(b*α + β))/(-a*α + b*α + β)
C2_val = (ya*α - yb - α*sp.sin(a) + α*sp.sin(b) + β*sp.cos(b))/(a*α - b*α - β)
print(solgen.subs({C1:C1_val,C2:C2_val}))
```

    Eq(y(z), z*(ya*α - yb - α*sin(a) + α*sin(b) + β*cos(b))/(a*α - b*α - β) + (a*(-yb + α*sin(b) + β*cos(b)) + (ya - sin(a))*(b*α + β))/(-a*α + b*α + β) + sin(z))

```python
print(solgen.subs(c1c2))
```

    Eq(y(z), a*yb/(a*α - b*α - β) - a*α*sin(b)/(a*α - b*α - β) - a*β*cos(b)/(a*α - b*α - β) - b*ya*α/(a*α - b*α - β) + b*α*sin(a)/(a*α - b*α - β) - ya*β/(a*α - b*α - β) + z*(ya*α/(a*α - b*α - β) - yb/(a*α - b*α - β) - α*sin(a)/(a*α - b*α - β) + α*sin(b)/(a*α - b*α - β) + β*cos(b)/(a*α - b*α - β)) + β*sin(a)/(a*α - b*α - β) + sin(z))

```python
# Definimos una función que evalúa la expresión analítica
def yexacta(z,a,b,ya,yb,α,β):
    """solución exacta del problema tomado como ejemplo"""
    return z*(ya*α - yb - α*sp.sin(a) + α*sp.sin(b) + β*sp.cos(b))/(a*α - b*α - β) + (a*(-yb + α*sp.sin(b) \
        + β*sp.cos(b)) + (ya - sp.sin(a))*(b*α + β))/(-a*α + b*α + β) + sp.sin(z)
```

```python
# Sustituimos los parámetros siguiendo las indicaciones dadas en el enunciado
dni = [7,5,5,7,7,3,9,4]
a = 0; b = max(dni);
ya = dni[1]; yb = dni[4];
α = max(dni[2],1);  β = max(dni[3],1);
yex = yexacta(z,a,b,ya,yb,α,β)
yex
```

$\displaystyle - \frac{z \left(7 \cos{\left(9 \right)} + 5 \sin{\left(9 \right)} + 18\right)}{52} + \sin{\left(z \right)} + 5$

```python
sp.diff(yex,z,2)
```

$\displaystyle - \sin{\left(z \right)}$

```python
sp.N(yex.subs({z:a}))
```

$\displaystyle 5.0$

```python
sp.N((α*yex+β*sp.diff(yex,z,1)).subs({z:b}))
```

$\displaystyle 7.0$

```python
nx = 10; x = np.linspace(a,b,nx+1)
nxx = 1000; xx = np.linspace(a,b,nxx+1)

yyexacta = [yexacta(z,a,b,ya,yb,α,β) for z in xx]
plt.plot(xx,yyexacta);
```

### Ensamblaje de la matriz de rigidez

```python
# Matriz local
a00 = sp.integrate((dl1(Finv(z,xL,xR))*dFinv(z,xL,xR))**2,[z,xL,xR])
a01 = sp.integrate(dl1(Finv(z,xL,xR))*dl2(Finv(z,xL,xR))*dFinv(z,xL,xR)**2,[z,xL,xR])
a11 = sp.integrate((dl2(Finv(z,xL,xR))*dFinv(z,xL,xR))**2,[z,xL,xR])

Agen = sp.Matrix([[a00,a01],[a01,a11]])
Agen = sp.simplify(Agen) # y esta sería la correspondiente matriz

h_e = sp.Symbol('h_e') # ahora sustituiremos xR-xL por he, una
Agen = Agen.subs({xL-xR:-h_e, -xL+xR:h_e}) # longitud genérica
Agen
```

$\displaystyle \left[\begin{matrix}\frac{1}{h_{e}} & - \frac{1}{h_{e}}\\- \frac{1}{h_{e}} & \frac{1}{h_{e}}\end{matrix}\right]$

```python
A = np.zeros((nx,nx),dtype=float)
np.shape(A)
# print(A)
```

    (10, 10)

```python
for i in range(1,nx): # ¡Atención con los índices en Python!
# hemos empezado con los elementos de la diagonal principal de la
# matriz de rigidez, con índices desde 0 hasta nx-2.
    A[i-1,i-1] = Agen[1,1].subs({h_e:x[i]-x[i-1]})+Agen[0,0].subs({h_e:x[i+1]-x[i]})
A[nx-1,nx-1]   = Agen[1,1].subs({h_e:x[nx]-x[nx-1]}) + α/β
# pero el caso nx-1 es especial y hay que definirlo aparte
```

```python
for i in range(1,nx): # ahora nos encargamos de la diagonal inferior
    A[i-1,i] = Agen[0,1].subs({h_e:x[i+1]-x[i]})
    A[i,i-1] = A[i-1,i] # y de la superior por simetría
print(A)
```

    [[ 2.22222222 -1.11111111  0.          0.          0.          0.
       0.          0.          0.          0.        ]
     [-1.11111111  2.22222222 -1.11111111  0.          0.          0.
       0.          0.          0.          0.        ]
     [ 0.         -1.11111111  2.22222222 -1.11111111  0.          0.
       0.          0.          0.          0.        ]
     [ 0.          0.         -1.11111111  2.22222222 -1.11111111  0.
       0.          0.          0.          0.        ]
     [ 0.          0.          0.         -1.11111111  2.22222222 -1.11111111
       0.          0.          0.          0.        ]
     [ 0.          0.          0.          0.         -1.11111111  2.22222222
      -1.11111111  0.          0.          0.        ]
     [ 0.          0.          0.          0.          0.         -1.11111111
       2.22222222 -1.11111111  0.          0.        ]
     [ 0.          0.          0.          0.          0.          0.
      -1.11111111  2.22222222 -1.11111111  0.        ]
     [ 0.          0.          0.          0.          0.          0.
       0.         -1.11111111  2.22222222 -1.11111111]
     [ 0.          0.          0.          0.          0.          0.
       0.          0.         -1.11111111  1.82539683]]

```python
B = np.zeros(nx) # Ahora empezamos a construir el array correspondiente
print(B)         # al vector de términos independientes del sist. lineal
                 # con valores nulos en un primer momento.
```

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

```python
from scipy.integrate import quad

for i in range(1,nx):  # Atención: ¡cuidado con los índices en Python!
    intizda = quad(lambda z:f(z)*l2(Finv(z,x[i-1],x[i])),x[i-1],x[i])
    intdcha = quad(lambda z:f(z)*l1(Finv(z,x[i],x[i+1])),x[i],x[i+1])
    B[i-1] = intizda[0] + intdcha[0]
B[nx-1] = quad(lambda z:f(z)*l2(Finv(z,x[nx-1],x[nx])),x[nx-1],x[nx])[0]
print(B)
```

    [ 0.65867354  0.81887608  0.35936953 -0.37210072 -0.82197256 -0.64979195
      0.01413825  0.66736891  0.81554808  0.29138434]

```python
B[0]  = B[0]  + ya/(x[1]-x[0]) # modificamos el primer elemento
B[-1] = B[-1] + yb/β # así como el último del vector de cargas B
B[0],B[-1]
```

    (6.214229098196412, 1.2913843445476447)

```python
Y = np.linalg.solve(A,B) # y ya podemos resolver el nuevo sistema
```

```python
Y  # cuya solución nos aportará los coeficientes que necesitábamos
```

    array([5.54651128, 5.50021638, 4.716933  , 3.61021705, 2.83839175,
           2.80634176, 3.35910452, 3.89914286, 3.83854918, 3.04396223])

```python
yy = np.zeros(len(xx)) # con estos valores ya podemos construir la
yy = np.array([sum([wi(z,x,i)*Y[i-1] for i in range(1,nx)]) for z in xx])
# sumatoria, a la que añadimos las dos func. de base de los extremos
yy = yy +    ya*np.array([w0(z,x) for z in xx]) # izquierdo y
yy = yy + Y[-1]*np.array([wn(z,x) for z in xx]) # derecho
# multiplicadas por los coeficientes apropiados
plt.plot(xx,yyexacta); # para poder recuperar las condiciones
plt.plot(xx,yy); # de contorno del problema de partida
```


```python
import cirq
import numpy as np
import scipy as sp
```

After seeing our first example of how one can use a quantum circuit to solve the Deutsch(-Jozsa) Problem in a more efficient way (but only slightly more efficient than a classical probabilistic computer), we now turn to a problem where the runtime on a classical computer is exponential with $\mathcal{O}(2^n/2)$, while using a quantum circuit together with classical post processing the problem can be solved in $\mathcal{O}(n)$

Reading Ch.6, SIAM J. Comput., 26(5), 1474–1483

\begin{problem}
Given a function $f: \{0,1\}^n \rightarrow \{0,1\}^n$, such that there exists $\mathbf{s} = s_1s_2...s_n$ so that $f(\mathbf{x}) = f(\mathbf{y}) \iff (\mathbf{x} = \mathbf{y}\, \mathrm{or} \, \mathbf{x}= \mathbf{y} \oplus \mathbf{s})$.

Determine the $\mathbf{s}$
\end{problem}
Note, that we can see $\{0,1\}^n$ as the a vector space over $Z_2$ this vector space has dimension $n$ (one basis is $\{(\delta_{i,k})_{i\in (0,1,..,n) )}\}$. This is not the same as the (complex) Hilbert space of the qubits $(\mathbb{C}^2)^{\otimes n}$, which has dimension $2^n$.

Before we write down Simon's algorithm, we discuss some identities that will help with understanding the algorithm.

We start by applying $H^{\otimes n}$ to $|0\rangle^{\otimes n}$
\begin{equation}
H^{\otimes n}|0\rangle^{\otimes n} = \frac{1}{2^{n/2}}\left(|0\rangle+|1\rangle \right)^{\otimes n}=\frac{1}{2^{n/2}} \sum_{x \in \{0,1\}}|x\rangle\left(|0\rangle+|1\rangle \right)^{\otimes (n-1)} = ... =\frac{1}{2^{n/2}}\sum_{\mathbf{x} \in \{0,1\}^n}|\mathbf{x}\rangle
\end{equation}
Let ${\bf x,y} \in A_n = \{0,1\}^n $ and $\mathbf{s} = \mathbf{x}\oplus \mathbf{y}$ (by modular arithmetic this implies $\mathbf{x}\oplus\mathbf{s} =  \mathbf{y}$). We now apply $H^{\oplus n}$ to the superposition of $\bf x, y$
First we note that if $x\in \{0,1\}^n$
\begin{equation}
H^{\otimes n}|\mathbf{x}\rangle = H|x_1\rangle H|x_2\rangle... H|x_n\rangle = \frac{1}{2^{n/2}}\left(|0\rangle +(-1)^{x_1}|1\rangle\right)\left(|0\rangle +(-1)^{x_2}|1\rangle \right)...\left(|0\rangle +(-1)^{x_n}|1\rangle\right) = \frac{1}{2^{n/2}}\sum_{z\in A_n}\left(-1\right)^{\bf x \cdot z} |\mathbf{z}\rangle
\end{equation}
using this result we immediately find
\begin{equation}
H^{\oplus n}\frac{1}{\sqrt{2}}\left(|\mathbf{x}\rangle +|\mathbf{y}\rangle \right) = \frac{1}{2^{(n+1)/2}}\sum_{z\in A_n}(\left(-1\right)^{\bf x \cdot z}+\left(-1\right)^{(\mathbf{x}\oplus\mathbf{s})\cdot \mathbf{z}} )|\mathbf{z}\rangle
\end{equation}
By inspection it becomes clear that
\begin{equation}
(\left(-1\right)^{\bf x \cdot z}+\left(-1\right)^{(\mathbf{x}\oplus\mathbf{s})\cdot \mathbf{z}}) = \left(-1\right)^{\bf x \cdot z} \left(1+\left(-1\right)^{\bf s\cdot z }\right) = \begin{cases}
1 &\text{$z \in \{s\}^\perp$}\\
0 &\text{else}
\end{cases}
\end{equation}
Putting it all together gives
\begin{equation}
H^{\oplus n}\frac{1}{\sqrt{2}}\left(|\mathbf{x}\rangle +|\mathbf{y}\rangle \right) = \frac{1}{2^{(n-1)/2}}\sum_{z \in\{s\}^\perp } \left(-1\right)^{\bf x \cdot z}|\mathbf{z}\rangle
\end{equation}
This effectively means that after applying $H^{\otimes n}$ to a state of the from $|{\bf x }\rangle+|{\bf x\oplus s}\rangle$ the system is in a uniform superposition of all states belonging to $\{\mathbf{s}\}^\perp$. One can now devise a strategy to solve Simon's problem. Namely, we use a quantum circuit to give us a state that is a uniform superposition of states in $\{\bf s\}^\perp$ and then use this circuit to sample $n-1$ linearly independent vectors spanning $\{\bf s\}^\perp$. From linear algebra, we know that by finding the orthogonal complement to $\{\mathbf{s}\}^\perp$ (which can be easily done by solving a linear system), we find $\bf s$. The exact algorithm is summarised below.

\begin{theorem}
Simon's problem can be solved with expected run-time in the quantum operation $\mathcal{O}(n)$ plus $\mathcal{O}(n^3)$ additional classical operation (in total polynomial run time) by applying the following algorithm.

1. i =0
2. Prepare $|\psi_1\rangle = \frac{1}{\sqrt{2}^n} \sum_{x\in A_n} |\mathbf{x}\rangle|\mathbf{0}\rangle$
($|\psi_1\rangle \in Z^{\otimes 2n}_2$)
3. Apply $cU_f$,  $|\psi_2\rangle =cU_f |\psi_1\rangle =\frac{1}{\sqrt{2}^n} \sum_{x\in A_n} |\mathbf{x}\rangle|f(\mathbf{x})\rangle$
4. Apply $H^{\oplus n}$ to the first register (first $n$ qubits)
5. Measure the first register and record $\mathbf{w}_i \in Z^{\otimes n}_2$
6. if: $\mathrm{dim}\left(\mathrm{span}(w_j)_{j\in\{0,1,...,i\}}\right) = n-1$ go to 7, else: $i \mathrel{+}=1$ go to 2.
7. (Classical) Solve $W \mathbf{s}^\top = 0 \mod 2$,
8. Output unique non-zero solution
\end{theorem}

To see why this algorithm works, we only have to combine the results from above.
As usual we start with the state $|0 \rangle^{\otimes 2n}$ and then apply $H^\otimes n$ to obtain $|\psi_1\rangle$. The application of $cU_f$ now works in the same way as already seen for the Deutsch problem.
We are now going to use that $\{0,1\}^n$ can be partitioned into $2^{n-1}$ pairs of the form $\{\mathbf{x},\mathbf{x}\oplus \mathbf{s}\}$. One can then rewrite $|\psi_2\rangle$ in the following form
\begin{equation}
|\psi_2\rangle =\frac{1}{{2}^{(n-1)/2}} \sum_{x\in I} \left(|\mathbf{x}\rangle|f(\mathbf{x})\rangle + |\mathbf{x} \oplus \mathbf{s}\rangle|f(\mathbf{x}\oplus \mathbf{s})\rangle\right) = \frac{1}{2^{(n-1)/2}} \sum_{x\in I} \left(|\mathbf{x}\rangle + |\mathbf{x} \oplus \mathbf{s}\rangle\right) |f(\mathbf{x})\rangle
\end{equation}
where we have used that $f(\mathbf{x}\oplus \mathbf{s}) =f(\mathbf{x}) $ and denoted the subset consisiting of one representative of each pair by $I$ (note which one we chose does not matter since if we chose $\bf x \oplus s$ have $x = \bf x \oplus s \oplus s$).
From (5), we know that after applying the second set of $H$-gates we will obtain a uniform superposition in $\{\bf s\}^\perp$ and measuring those, we will get an element $\mathbf{w}_i \in \{\bf s\}^\perp$.

Comment 1: If it is not entirely clear how the superposition after 4 will look like, imagine that we had measured the second register after (this has no effect on the outcome) but will get rid of the sum in 3 and therefore simplifies the notation

Comment 2: If $ \mathbf{s} = 0$ we have $\{\mathbf{s}\}^\perp = Z^n_2$ and stopping after obtaining $n-1$ linear independent vectors will not give the right result, but repeated application of the alogorithm will yield random results, which will show  $ \mathbf{s} = 0$

We are now in the position the implement the algorithm outlined above. We start by implementing the function. Here for simplicity, we chose f to be the identity, modified in such a way, that $f(\mathbf{x}) = f(\mathbf{y}) \iff (\mathbf{x} = \mathbf{y}\, \mathrm{or} \, \mathbf{x}= \mathbf{y} \oplus \mathbf{s})$

```python
def f(q,s,n):
    for i in range(n):
        # f = id
        yield cirq.CNOT(q[i],q[i+n])
        #enforece condition on f by setting f(s) = 0
        try:
            j = s.index(1)
        except:
            pass
        if s[i]:
             yield cirq.CNOT(q[j],q[i+n])
```

```python
def simon_circuit(s):
    n = len(s)
    c = cirq.Circuit()
    q_l = cirq.LineQubit.range(2*n)
    # create |\psi_1>
    for i in range(n):
        c.append(cirq.H(q_l[i]))
    c.append(f(q_l,s,n))
    for i in range(n):
        c.append(cirq.H(q_l[i]))
    c.append(cirq.measure(*q_l[0:n], key='first_reg'))
    return c
```

```python
print(simon_circuit([0,1,1,0,1]))
```

              ┌─────┐   ┌──┐
    0: ───H────@─────────H─────────────────M('first_reg')───
               │                           │
    1: ───H────┼@────────@─────@───@───H───M────────────────
               ││        │     │   │       │
    2: ───H────┼┼@───────┼H────┼───┼───────M────────────────
               │││       │     │   │       │
    3: ───H────┼┼┼@──────┼H────┼───┼───────M────────────────
               ││││      │     │   │       │
    4: ───H────┼┼┼┼@─────┼H────┼───┼───────M────────────────
               │││││     │     │   │
    5: ────────X┼┼┼┼─────┼─────┼───┼────────────────────────
                ││││     │     │   │
    6: ─────────X┼┼┼─────X─────┼───┼────────────────────────
                 │││           │   │
    7: ──────────X┼┼───────────X───┼────────────────────────
                  ││               │
    8: ───────────X┼───────────────┼────────────────────────
                   │               │
    9: ────────────X───────────────X────────────────────────
              └─────┘   └──┘

We will use the SVD to check the rank of the matrix.
In principle one could solve the linear system using gaussian elimination over $Z_2$, but here it is easier to use an out of the box method wokring in $\mathbb{R}^n$ since we know the vectors will be parallel and we can jut convert the non-zero entries to $1$.

```python
def get_s(s):
    simulator =cirq.Simulator()
    n = len(s)
    c = simon_circuit(s)
    w_l = []
    for i in range(n-1):
        w_l.append(np.array(simulator.run(c).measurements['first_reg'][0]))
    sig = sp.linalg.svdvals(w_l)
    while np.count_nonzero(sig[sig>1e-3]) < n-1:
        w_l.append(np.array(simulator.run(c).measurements['first_reg'][0]))
        sig = sp.linalg.svdvals(w_l)
    sol = sp.linalg.null_space(w_l).T[0]
    sol = [1 if abs(i) >1e-3 else 0 for i in sol]
    return sol
```

```python
get_s([0,1])
```

    [0, 1]

```python
get_s([0,1,1])
```

    [0, 1, 1]

Note for longer $s$ we do not always get the right answer immediately, here we have to re-run the algorithm several times and record the most coomon outcome. Strictly speaking we have to do this anyways to account for the possibility $s=0$.

```python

```


# Collapsed Gibbs sampler for Latent Dirichlet Allocation

  $
      \newcommand{\dir}{\mathop{\rm Dirichlet}\nolimits}
      \newcommand{\dis}{\mathop{\rm Discrete}\nolimits}
      \newcommand{\btheta}{\mathbf{\theta}}
      \newcommand{\norm}[1]{\left\| #1 \right\|}
      \newcommand{\p}[1]{p \left( #1 \right)}
      \newcommand{\cp}[2]{p \left( #1 \middle| #2 \right)}
      \newcommand{\Betaf}{\mathop{\rm B}\nolimits}
      \newcommand{\Gammaf}{\mathop{\Gamma}\nolimits}
  $

Here is a full derivation of the collapsed Gibbs sampler for Latent Dirichlet Allocation.  The original ideas come from [this paper](http://psiexp.ss.uci.edu/research/papers/sciencetopics.pdf) by Griffiths and Steyvers (2004).  I found the Wikipedia articles on [Latent Dirichlet Allocation](https://en.wikipedia.org/wiki/Latent_Dirichlet_allocation) and the [Dirichlet-Multinomial distribution](https://en.wikipedia.org/wiki/Dirichlet-multinomial_distribution) very helpful, as well as [these notes](https://people.cs.umass.edu/~wallach/courses/s11/cmpsci791ss/readings/griffiths02gibbs.pdf) by Griffiths.  My Gibbs sampler implementation in Cython closely follows [Allen Riddell](https://ariddell.org/)'s [LDA code](https://github.com/ariddell/lda/).

Recall the generative model:

$$\begin{align}
\theta^{(d)} & \sim \dir(\alpha) & \text{(topic distribution for document $d \in \{1, \ldots, D\}$)}\\
\phi^{(k)} & \sim \dir(\beta) & \text{(term distribution for topic $k \in \{1, \ldots, K\}$)}\\
z_n^{(d)} \mid \theta^{(d)} & \sim \dis(\theta^{(d)}) & \text{(topic of $n$th token of document $d$, $n \in \{1, \ldots, N^{(d)}\}$)}\\
w_n^{(d)} \mid \phi^{(z_n^{(d)})} & \sim \dis(\phi^{(z_n^{(d)})}) & \text{(term of $n$th token of document $d$, $n \in \{1, \ldots, N^{(d)}\}$)}
\end{align}$$

where each token can be any one of $V$ terms in our vocabulary.

"Smoothed LDA" by Slxu.public - Own work. Licensed under CC BY-SA 3.0 via Wikimedia Commons.

Plate notation for latent Dirichlet allocation.  Please note that $M$ should be $D$.

Therefore, the joint probability distribution can be factorized as follows:

$$\begin{align}
\cp{\theta, \phi, z, w}{\alpha, \beta}
&=
\prod_{k=1}^{K} \cp{\phi^{(k)}}{\beta}
\prod_{d=1}^{D} \cp{\theta^{(d)}}{\alpha}
\prod_{n=1}^{N^{(d)}} \cp{z_n^{(d)}}{\theta^{(d)}} \cp{w_n^{(d)}}{\phi^{(z_n^{(d)})}}
\\ &=
\prod_{k=1}^{K} \frac{\prod_{v=1}^{V} \left(\phi_v^{(k)}\right)^{\beta_v-1}}{\Betaf(\beta)}
\prod_{d=1}^{D} \frac{\prod_{k'=1}^{K} \left(\theta_{k'}^{(d)}\right)^{\alpha_{k'}-1}}{\Betaf(\alpha)}
\prod_{k''=1}^{K} \left(\theta_{k''}^{(d)}\right)^{a_{k''}^{(d)}}
\prod_{v'=1}^{V} \left(\phi_{v'}^{(k)}\right)^{b_{v'}^{(k)}}
\\ &=
\left( \prod_{k=1}^{K}
\frac{\prod_{v=1}^{V} \left( \phi_v^{(k)}\right)^{b_{v}^{(k)}+\beta_v-1}}{\Betaf(\beta)} \right)
\left( \prod_{d=1}^{D} \frac{\prod_{k'=1}^{K} \left( \theta_{k'}^{(d)} \right)^{a_{k'}^{(d)} + \alpha_{k'}-1}}{\Betaf(\alpha)} \right)
\\ &=
\left( \prod_{k=1}^{K}
\frac{\Betaf(b^{(k)} + \beta)}{\Betaf(\beta)}
\frac{\prod_{v=1}^{V} \left(\phi_v^{(k)}\right)^{b_{v}^{(k)}+\beta_v-1}}{\Betaf(b^{(k)} + \beta)} \right)
\left( \prod_{d=1}^{D}
\frac{\Betaf(a^{(d)} + \alpha)}{\Betaf(\alpha)}
\frac{\prod_{k'=1}^{K}
\left( \theta_{k'}^{(d)}\right)^{a_{k'}^{(d)} + \alpha_{k'}-1}}{\Betaf(a^{(d)} + \alpha)} \right)
\\ &=
\left( \prod_{k=1}^{K} \frac{\Betaf(b^{(k)} + \beta)}{\Betaf(\beta)} \cp{\phi^{(k)}}{b^{(k)} + \beta} \right)
\left( \prod_{d=1}^{D} \frac{\Betaf(a^{(d)} + \alpha)}{\Betaf(\alpha)} \cp{\theta^{(d)}}{a^{(d)} + \alpha} \right)
\end{align}$$

where $a_k^{(d)}$ is the number of tokens in document $d$ assigned to topic $k$, $b_v^{(k)}$ is the number of tokens equal to term $v$ and assigned to topic $k$, and $\Betaf$ is the [multivariate Beta function](https://en.wikipedia.org/wiki/Beta_function#Multivariate_beta_function).

In this form, we can easily marginalize out $\theta$ and $\phi$ by integrating with respect to each $\theta^{(d)}$ and $\phi^{(k)}$ over the standard simplexes $\Delta^{D-1}$ and $\Delta^{K-1}$, respectively, where

$$\Delta^{n-1} = \left\{x \in \mathbb{R}^n \middle| x \ge 0, \sum_{i=1}^n x_i = 1 \right\},$$

and noting that each integral is equal to 1 since each $p$ is a probability distribution over its respective sample space. Hence,

$$\begin{align}
\cp{z, w}{\alpha, \beta} &=
\left( \prod_{k=1}^{K} \frac{\Betaf(b^{(k)} + \beta)}{\Betaf(\beta)} \int_{\Delta^{K-1}} \cp{\phi^{(k)}}{b^{(k)} + \beta} d\phi^{(k)} \right)
\left( \prod_{d=1}^{D} \frac{\Betaf(a^{(d)} + \alpha)}{\Betaf(\alpha)} \int_{\Delta^{D-1}} \cp{\theta^{(d)}}{a^{(d)} + \alpha} d\theta^{(d)} \right) \\ &=
\prod_{k=1}^{K} \frac{\Betaf(b^{(k)} + \beta)}{\Betaf(\beta)}
\prod_{d=1}^{D} \frac{\Betaf(a^{(d)} + \alpha)}{\Betaf(\alpha)} \\ &=
\cp{w}{z, \beta} \cp{z}{\alpha}.
\end{align}$$

Our goal is to calculate the posterior distribution

$$\cp{z}{w, \alpha, \beta} = \frac{\cp{z, w}{\alpha, \beta}}{\sum_z \cp{z, w}{\alpha, \beta}}$$

in order to infer the topic assignment of each token from the given term assignments.  However, calculating this directly is out of the question since the sum in the denominator does not factor into managable parts and involves a (potentially enormous) $K^N$ summands (here $N = \sum_{d=1}^D N^{(d)}$).

We therefore resort to collapsed Gibbs sampling.  The sampler is "collapsed" because we marginalized out $\theta$ and $\phi$, and will estimate them from the topic assignments $z$:

$$\hat\theta_k^{(d)} = \frac{a_k^{(d)} + \alpha_k}{\sum_{k'=1}^K \left(a_{k'}^{(d)} + \alpha_{k'} \right)},
\quad
\hat\phi_v^{(k)} = \frac{b_v^{(k)} + \beta_v}{\sum_{v'=1}^V \left(b_{v'}^{(k)} + \beta_{v'} \right)}.$$

These estimates are derived from the predictive distribution for a new term and topic assignment $w_0$ and $z_0$ given the existing word and topic assignments $w$ and $z$, respectively:

$$\begin{align}
\hat\phi_v^{(k)}
&=
\cp{w_0 = v}{w^{(k)}}
\\ &=
\int_{\Delta^{K-1}} \cp{w_0 = v}{\phi^{(k)}} \cp{\phi^{(k)}}{w^{(k)}} d\phi^{(k)}
\\ &=
\int_{\Delta^{K-1}} \phi_v^{(k)} \frac{\prod_{v'=1}^V \left( \phi_{v'}^{(k)} \right)^{b_{v'}^{(k)} + \beta_{v'} - 1}}{\Betaf(b^{(k)} + \beta)} d\phi^{(k)}
\\ &=
\int_{\Delta^{K-1}} \cp{\phi^{(k)}}{b^{(k)} + \beta + e_v}
\frac{ b_v^{(k)} + \beta_v }{ \sum_{v'=1}^V \left( b_{v'}^{(k)} + \beta_{v'} \right) } d\phi^{(k)}
\\ &=
\frac{ b_v^{(k)} + \beta_v }{ \sum_{v'=1}^V \left( b_{v'}^{(k)} + \beta_{v'} \right) }
\end{align}$$

where $w^{(k)}$ is the vector of term assignments for tokens assigned to topic $k$, since

$$
\cp{\phi^{(k)}}{w^{(k)}}
=
\frac{ \cp{w^{(k)}}{\phi^{(k)}} \p{\phi^{(k)}} }{ \p{w^{(k)}} }
=
\frac{ \prod_{v=1}^{V} \left(\phi_{v}^{(k)}\right)^{b_{v}^{(k)}}
\frac{\prod_{v'=1}^V \left( \phi_{v'}^{(k)}\right)^{\beta_{v'}-1}}{\Betaf(\beta)}
 }{ \frac{ \Betaf(b^{(k)} + \beta) }{ \Betaf(\beta) } }
=
\frac{\prod_{v=1}^V \left( \phi_{v}^{(k)} \right)^{b_{v}^{(k)} + \beta_{v} - 1}}{\Betaf(b^{(k)} + \beta)}
$$

and

$$\begin{align}
\phi_v^{(k)}
\frac{ \prod_{v'=1}^V \left( \phi_{v'}^{(k)} \right)^{b_{v'}^{(k)} + \beta_{v'} - 1} }{ \Betaf(b^{(k)} + \beta) }
&=
\prod_{v'=1}^V \left( \phi_{v'}^{(k)} \right)^{b_{v'}^{(k)} + \beta_{v'} - 1 + \delta_{v, v'}}
\frac{ \Gammaf \left[ \sum_{v'=1}^V \left( b_{v'}^{(k)} + \beta_{v'} \right) \right] }
{ \prod_{v'=1}^V \Gammaf \left( b_{v'}^{(k)} + \beta_{v'} \right) }
\\&=
\prod_{v'=1}^V \left( \phi_{v'}^{(k)} \right)^{b_{v'}^{(k)} + \beta_{v'} - 1 + \delta_{v, v'}}
\frac{ \Gammaf \left[ \sum_{v'=1}^V \left( b_{v'}^{(k)} + \beta_{v'} + \delta_{v, v'} \right) \right] }
{ \prod_{v'=1}^V \Gammaf \left( b_{v'}^{(k)} + \beta_{v'} + \delta_{v, v'} \right) }
\frac{ b_v^{(k)} + \beta_v }{ \sum_{v'=1}^V \left( b_{v'}^{(k)} + \beta_{v'} \right) }
\\&=
\frac{ \prod_{v'=1}^V \left( \phi_{v'}^{(k)} \right)^{b_{v'}^{(k)} + \beta_{v'} - 1 + \delta_{v, v'}} }
{ \Betaf \left( b^{(k)} + \beta + e_v \right) }
\frac{ b_v^{(k)} + \beta_v }{ \sum_{v'=1}^V \left( b_{v'}^{(k)} + \beta_{v'} \right) }
\\&=
\cp{\phi^{(k)}}{b^{(k)} + \beta + e_v}
\frac{ b_v^{(k)} + \beta_v }{ \sum_{v'=1}^V \left( b_{v'}^{(k)} + \beta_{v'} \right) }
\end{align}$$

where $\delta_{v, v'}$ is the [Kronecker delta function](https://en.wikipedia.org/wiki/Kronecker_delta), $e_v$ is the vector of length $V$ whose $v'$th entry is $\delta_{v, v'}$, and we used the fact that $\Gammaf(x) = \Gammaf(x+1) / x$.  Similarly,

$$\begin{align}
\hat\theta_k^{(d)}
&=
\cp{z_0 = k}{z^{(d)}}
\\ &=
\int_{\Delta^{N-1}} \cp{z_0 = k}{\theta^{(d)}} \cp{\theta^{(d)}}{z^{(d)}} d\theta^{(d)}
\\ &=
\int_{\Delta^{N-1}} \theta_k^{(d)} \frac{\prod_{k'=1}^K \left( \theta_{k'}^{(d)} \right)^{a_{k'}^{(d)} + \alpha_{k'} - 1}}{\Betaf(a^{(d)} + \alpha)} d\theta^{(d)}
\\ &=
\int_{\Delta^{N-1}} \cp{\theta^{(d)}}{a^{(d)} + \alpha + e_k}
\frac{a_k^{(d)} + \alpha_k}{\sum_{k'=1}^K \left(a_{k'}^{(d)} + \alpha_{k'} \right)} d\theta^{(d)}
\\ &=
\frac{a_k^{(d)} + \alpha_k}{\sum_{k'=1}^K \left(a_{k'}^{(d)} + \alpha_{k'} \right)}
\end{align}$$

where $z^{(d)}$ is the vector of topic assignments for document $d$ and $e_k$ is the vector of length $K$ whose $k'$th entry is $\delta_{k, k'}$.

Gibbs sampling requires us to compute the full conditionals for each $z_n^{(d)}$, i.e. we need to calculate, for all $n$, $d$ and $k$,

$$\cp{z_n^{(d)} = k}{z \setminus z_n^{(d)}, w, \alpha, \beta} =
\frac{\cp{z_n^{(d)} = k, z \setminus z_n^{(d)}, w}{\alpha, \beta}}{\cp{z \setminus z_n^{(d)}, w}{\alpha, \beta}} \propto \cp{z_n^{(d)} = k, z \setminus z_n^{(d)}, w}{\alpha, \beta}
\propto \prod_{k'=1}^{K} \Betaf(b^{(k')} + \beta) \prod_{d'=1}^{D}\Betaf(a^{(d')} + \alpha).$$

Here $z \setminus z_n^{(d)}$ means the vector $z$ with the entry $z_n^{(d)}$ removed.  We will use this "set-minus" notation with other variables in our model to likewise denote the same variable with the indicated entry removed.  In each step of the above equation where the proportional symbol $\propto$ is used, I have dropped terms that do not contribute to the sampling variation of $z_n^{(d)}$, i.e., they do not vary with $k$ for fixed values of $n$ and $d$.

Let's first consider $\prod_{k'=1}^{K} \Betaf(b^{(k')} + \beta)$:

$$\begin{align}
\prod_{k'=1}^{K} \Betaf(b^{(k')} + \beta) &=
\left( \prod_{k'=1, k' \neq k}^{K} \Betaf(b^{(k')} + \beta) \right) \Betaf(b^{(k)} + \beta) \\ &=
\left( \prod_{k'=1, k' \neq k}^{K} \Betaf(b^{(k')} + \beta) \right) \frac{\prod_{v=1}^{V} \Gammaf \left(b_v^{(k)} + \beta_v\right)}
{\Gammaf \left[ \sum_{v=1}^V \left(b_v^{(k)} + \beta_v \right)\right]} \\ &=
\left( \prod_{k'=1, k' \neq k}^{K} \Betaf(b^{(k')} + \beta) \right)
\frac{\prod_{v=1, v \neq w_n^{(d)}}^{V} \Gammaf \left(b_v^{(k)} + \beta_v\right)}
{\Gammaf \left[ \sum_{v=1}^V \left(b_v^{(k)} + \beta_v \right)\right]}
\Gammaf \left(b_{w_n^{(d)}}^{(k)} + \beta_{w_n^{(d)}}\right) \\ &=
\left( \prod_{k'=1, k' \neq k}^{K} \Betaf(b^{(k')} \setminus z_n^{(d)} + \beta) \right)
\frac{\prod_{v=1, v \neq w_n^{(d)}}^{V} \Gammaf \left(b_v^{(k)} \setminus z_n^{(d)} + \beta_v\right)}
{\Gammaf \left[ \sum_{v=1}^V \left( b_v^{(k)} \setminus z_n^{(d)} + \beta_v\right) + 1\right]}
\Gammaf \left(b_{w_n^{(d)}}^{(k)} \setminus z_n^{(d)} + \beta_{w_n^{(d)}} + 1\right) \\ &=
\left( \prod_{k'=1, k' \neq k}^{K} \Betaf(b^{(k')} \setminus z_n^{(d)} + \beta) \right)
\frac{\prod_{v=1, v \neq w_n^{(d)}}^{V} \Gammaf \left(b_v^{(k)} \setminus z_n^{(d)} + \beta_v\right)}
{\Gammaf \left[ \sum_{v=1}^V \left( b_v^{(k)} \setminus z_n^{(d)} + \beta_v\right) \right] \sum_{v=1}^V \left( b_v^{(k)} \setminus z_n^{(d)} + \beta_v\right)}
\Gammaf \left(b_{w_n^{(d)}}^{(k)} \setminus z_n^{(d)} + \beta_{w_n^{(d)}}\right) \left(b_{w_n^{(d)}}^{(k)} \setminus z_n^{(d)} + \beta_{w_n^{(d)}} \right) \\ &=
\left( \prod_{k'=1, k' \neq k}^{K} \Betaf(b^{(k')} \setminus z_n^{(d)} + \beta) \right)
\frac{\prod_{v=1}^{V} \Gammaf \left(b_v^{(k)} \setminus z_n^{(d)} + \beta_v\right)}
{\Gammaf \left[ \sum_{v=1}^V \left( b_v^{(k)} \setminus z_n^{(d)} + \beta_v\right) \right]}
\frac{b_{w_n^{(d)}}^{(k)} \setminus z_n^{(d)} + \beta_{w_n^{(d)}}}{ \sum_{v=1}^V \left( b_v^{(k)} \setminus z_n^{(d)} + \beta_v\right)} \\ &=
\left( \prod_{k'=1, k' \neq k}^{K} \Betaf(b^{(k')} \setminus z_n^{(d)} + \beta) \right) \Betaf(b^{(k)} \setminus z_n^{(d)} + \beta)
\frac{b_{w_n^{(d)}}^{(k)} \setminus z_n^{(d)} + \beta_{w_n^{(d)}}}{ \sum_{v=1}^V \left( b_v^{(k)} \setminus z_n^{(d)} + \beta_v\right)} \\ &=
\left( \prod_{k'=1}^{K} \Betaf(b^{(k')} \setminus z_n^{(d)} + \beta) \right)
\frac{b_{w_n^{(d)}}^{(k)} \setminus z_n^{(d)} + \beta_{w_n^{(d)}}}{ \sum_{v=1}^V \left( b_v^{(k)} \setminus z_n^{(d)} + \beta_v\right)} \\ &\propto
\frac{b_{w_n^{(d)}}^{(k)} \setminus z_n^{(d)} + \beta_{w_n^{(d)}}}{ \sum_{v=1}^V \left( b_v^{(k)} \setminus z_n^{(d)} + \beta_v\right)}
\end{align}$$

where we have used the fact that $\Gammaf(x+1) = x\Gammaf(x)$.  Similarly, one can show that

$$\prod_{d'=1}^{D}\Betaf(a^{(d')} + \alpha)
\propto \frac{a_k^{(d)} \setminus z_n^{(d)} + \alpha_k}{ \sum_{k'=1}^K \left( a_{k'}^{(d)} \setminus z_n^{(d)} + \alpha_{k'}^{(d)} \right)}
\propto a_k^{(d)} \setminus z_n^{(d)} + \alpha_k$$

and therefore the full conditional can be written

$$\cp{z_n^{(d)} = k}{z \setminus z_n^{(d)}, w, \alpha, \beta}
\propto \frac{b_{w_n^{(d)}}^{(k)} \setminus z_n^{(d)} + \beta_{w_n^{(d)}}}{ \sum_{v=1}^V \left( b_v^{(k)} \setminus z_n^{(d)} + \beta_v\right)} \left( a_k^{(d)} \setminus z_n^{(d)} + \alpha_k \right)$$

which we use in the code implementation below.

## Graphical test

```python
from modules.helpers import plot_images
from functools import partial

imshow = partial(plt.imshow, cmap='gray', interpolation='nearest')
```

### Generate topics

We assume a vocabulary of 25 terms, and create ten "topics", where each topic assigns exactly 5 consecutive terms equal probability.

```python
V = 25
K = 10
N = 100
D = 1000
```

```python
topics = []
topic_base = np.concatenate((np.ones((1, 5)) * 0.2, np.zeros((4, 5))), axis=0).ravel()
for i in range(5):
    topics.append(np.roll(topic_base, i * 5))
topic_base = np.concatenate((np.ones((5, 1)) * 0.2, np.zeros((5, 4))), axis=1).ravel()
for i in range(5):
    topics.append(np.roll(topic_base, i))
topics = np.array(topics)
plt.figure(figsize=(10, 5))
plot_images(plt, topics, (5, 5), layout=(2, 5), figsize=(10, 5))
```

### Generate documents from topics

We generate 1,000 documents from these 10 topics by sampling 1,000 topic distributions, one for each document, from a Dirichlet distribution with parameter $\alpha = (1, \ldots, 1)$.

```python
alpha = np.ones(K)
np.random.seed(42)
thetas = np.random.dirichlet(alpha, size=D)
topic_assignments = np.array([np.random.choice(range(K), size=100, p=theta)
                              for theta in thetas])
word_assignments = np.array([[np.random.choice(range(V), size=1, p=topics[topic_assignments[d, n]])[0]
                              for n in range(N)] for d in range(D)])
doc_term_matrix = np.array([np.histogram(word_assignments[d], bins=V, range=(0, V - 1))[0] for d in range(D)])
plt.figure(figsize=(10, 10))
imshow(doc_term_matrix, aspect='auto')
```

### Estimate parameters

Again, much of this comes from [this repository](https://github.com/ariddell/lda/).  However, my class is about twice as fast as the one there.

```python
%load_ext cython
```

```cython
%%cython -lgsl
#cython: language_level=3
#cython: boundscheck=False
#cython: wraparound=False
#cython: cdivision=True

import numbers
import numpy as np
from numpy cimport (ndarray, float64_t)
from cython cimport (boundscheck, wraparound, cdivision)
from cython.operator cimport (preincrement, predecrement)
from cython_gsl cimport gsl_sf_lngamma as lgamma

cdef int searchsorted(double[:] arr, int length, double value):
    """
    Bisection search (c.f. numpy.searchsorted)
    Find the index into sorted array `arr` of length `length` such that, if
    `value` were inserted before the index, the order of `arr` would be
    preserved.
    """
    cdef:
        int imin = 0
        int imax = length
        int imid
    while imin < imax:
        imid = imin + ((imax - imin) >> 2)
        if value > arr[imid]:
            imin = imid + 1
        else:
            imax = imid
    return imin

cdef double loglikelihood(int[:, :] nzw, int[:, :] ndz,
                          int[:] nz, int[:] nd,
                          double[:] alpha, double[:] beta,
                          double sum_alpha, double sum_beta,
                          double lBeta_alpha, double lBeta_beta,
                          double lgamma_sum_alpha_nd):
    cdef int k, d
    cdef int n_docs = ndz.shape[0]
    cdef int n_topics = ndz.shape[1]
    cdef int n_terms = nzw.shape[1]
    cdef double ll = 0
    # calculate log p(w|z)
    ll += n_topics * lBeta_beta
    for k in range(n_topics):
        ll -= lgamma(sum_beta + nz[k])
        for w in range(n_terms):
            ll += lgamma(beta[w] + nzw[k, w])
    # calculate log p(z)
    ll += n_docs * lBeta_alpha
    ll -= lgamma_sum_alpha_nd
    for d in range(n_docs):
        for k in range(n_topics):
            ll += lgamma(alpha[k] + ndz[d, k])
    return ll

class LDA(object):
    """
    myLDA is my LDA, implemented in Cython!

    Parameters
    ----------
    n_topics : int
        Number of topics

    alpha : array-like, shape = (n_topics,)
        Dirichlet distribution parameter for each document's topic distributions

    beta : array-like, shape = (n_terms,)
        Dirichlet distribution parameter for each topic's term distributions

    n_iter : int, default=500
        Number of iterations of Gibbs sampler

    random_state : int, optional
        Seed for random number generator
    """
    def __init__(self, int n_topics, double[:] alpha, double[:] beta, int n_iter=500,
                 random_state=None):
        self.n_topics = n_topics
        self.alpha = alpha
        self.beta = beta
        self.n_iter = n_iter
        self.rng = self._check_random_state(random_state)

    def _check_random_state(self, seed):
        """
        Turn seed into a np.random.RandomState instance
        If seed is None, return the RandomState singleton used by np.random.
        If seed is an int, return a new RandomState instance seeded with seed.
        If seed is already a RandomState instance, return it.
        Otherwise raise ValueError.

        I copied this straight from here:
        https://github.com/scikit-learn/scikit-learn/blob/master/sklearn/utils/validation.py#L530-L545
        """
        if seed is None or seed is np.random:
            return np.random.mtrand._rand
        if isinstance(seed, (numbers.Integral, np.integer)):
            return np.random.RandomState(seed)
        if isinstance(seed, np.random.RandomState):
            return seed
        raise ValueError('%r cannot be used to seed a numpy.random.RandomState'
                         ' instance' % seed)

    def fit(self, X):
        """
        Perform (collapsed) Gibbs sampling inference.

        Parameters
        ----------
        X : array-like, shape = (n_docs, n_terms)
            The document-term matrix
        """
        self.doc_term_matrix = X
        _docs, _terms = np.nonzero(X)
        self.n_docs, self.n_terms = X.shape
        self.n_tokens = X.sum()
        self.loglikelihoods = []
        cdef:
            int i, j, k, d, w, z, new_z
            double p_sum, uval
            # creating these local C variables reduces calls to self, and hence the C-API
            int n_iter = self.n_iter
            int n_tokens = self.n_tokens
            int n_topics = self.n_topics
            int n_terms = self.n_terms
            int n_docs = self.n_docs
            double[::1] alpha = np.ascontiguousarray(self.alpha, dtype=np.float64)
            double[::1] beta = np.ascontiguousarray(self.beta, dtype=np.float64)
            double sum_alpha = np.asarray(self.alpha).sum()
            double sum_beta = np.asarray(self.beta).sum()
            double lBeta_alpha = 0.
            double lBeta_beta = 0.
            double lgamma_sum_alpha_nd = 0.
            # convenient lookup tables linking tokens to their docs, terms and topics
            int[::1] doc_lookup = np.ascontiguousarray(np.repeat(_docs, X[_docs, _terms]), dtype=np.intc)
            int[::1] term_lookup = np.ascontiguousarray(np.repeat(_terms, X[_docs, _terms]), dtype=np.intc)
            int[::1] topic_lookup = np.ascontiguousarray(self.rng.randint(0, self.n_topics, size=self.n_tokens),
                                                         dtype=np.intc)
            # number of tokens in document d assigned to topic z, shape = (n_docs, n_topics)
            int[:, ::1] ndz = np.zeros((self.n_docs, self.n_topics), dtype=np.intc, order='C')
            # number of tokens assigned to topic z equal to term w, shape = (n_topics, n_terms)
            int[:, ::1] nzw = np.zeros((self.n_topics, self.n_terms), dtype=np.intc, order='C')
            # number of tokens assigned to topic k, shape = (n_topics,)
            int[::1] nz = np.zeros(self.n_topics, dtype=np.intc, order='C')
            # number of tokens in doc d, shape = (n_docs,)
            int[::1] nd = np.zeros(self.n_docs, dtype=np.intc, order='C')
            # (weighted) probabilities for the discrete distribution
            double[::1] p_cumsum = np.empty(self.n_topics, dtype=np.float64, order='C')
            # preallocate uniformly random numbers on the interval [0, 1)
            double[::1] urands = self.rng.rand(1024**2 // 8)
            int n_urands = urands.shape[0]
            int u = 0
        # initialize counts
        for j in range(n_tokens):
            preincrement(ndz[doc_lookup[j], topic_lookup[j]])
            preincrement(nzw[topic_lookup[j], term_lookup[j]])
            preincrement(nz[topic_lookup[j]])
            preincrement(nd[doc_lookup[j]])
        # initialize lBeta_alpha
        for k in range(n_topics):
            lBeta_alpha += lgamma(alpha[k])
            lBeta_alpha -= lgamma(sum_alpha)
        # initialize lBeta_beta
        for w in range(n_terms):
            lBeta_beta += lgamma(beta[w])
            lBeta_beta -= lgamma(sum_beta)
        # initialize lgamma_sum_alpha_nd
        for d in range(n_docs):
            lgamma_sum_alpha_nd += lgamma(sum_alpha + nd[d])
        # iterate
        for i in range(n_iter):
            for j in range(n_tokens):
                d = doc_lookup[j]
                w = term_lookup[j]
                z = topic_lookup[j]
                predecrement(ndz[d, z])
                predecrement(nzw[z, w])
                predecrement(nz[z])
                p_sum = 0.
                for k in range(n_topics):
                    p_sum += (nzw[k, w] + beta[w]) / (nz[k] + sum_beta) * (ndz[d, k] + alpha[k])
                    p_cumsum[k] = p_sum
                preincrement(u)
                if u == n_urands:
                    u = 0
                uval = urands[u] * p_sum
                new_z = topic_lookup[j] = searchsorted(p_cumsum, n_topics, uval)
                preincrement(ndz[d, new_z])
                preincrement(nzw[new_z, w])
                preincrement(nz[new_z])
            self.loglikelihoods.append(loglikelihood(nzw, ndz, nz, nd,
                                                     alpha, beta, sum_alpha, sum_beta,
                                                     lBeta_alpha, lBeta_beta, lgamma_sum_alpha_nd))
        # populate the topic and word distributions
        self.theta = np.asarray(ndz) + np.tile(self.alpha, (self.n_docs, 1))
        self.theta = (self.theta.T / self.theta.sum(axis=1)).T
        self.phi = np.asarray(nzw) + np.tile(self.beta, (self.n_topics, 1))
        self.phi = (self.phi.T / self.phi.sum(axis=1)).T
```

```python
lda = LDA(10, 0.1 * np.ones(K), 0.01 * np.ones(V), 300, random_state=42)
```

```python
%%time
lda.fit(doc_term_matrix)
```

    CPU times: user 3.41 s, sys: 7.09 ms, total: 3.41 s
    Wall time: 3.41 s

```python
plot_images(plt, lda.phi, (5, 5), (2, 5), figsize=(10, 5))
```

```python
plt.plot(lda.loglikelihoods)
```

```python

```


Make sure you remove `raise NotImplementedError()` and fill in any place that says `# YOUR CODE HERE`, as well as your `NAME`, `ID`, and `LAB_SECTION` below:

```python
NAME = "Syed Zuhair Hossain"
ID = "19101573"
LAB_SECTION = "02"
```

---

Polynomial Root Finding Using Bisection Method
---

### `pandas` Dataframe:
Before we start we will know a little about `pandas` dataframe. `pandas` is a python library. Dataframes are classes that are used to store complex data. You can initialize one as a python dictionary. Let's take a look.

```python
import pandas as pd

x = [0, 1, 2, 3, 4, 5, 6, 7]
y = [1.0, 5.2, 3.9, 8.4, 14.6, 12.3, 8.9, 10.2]

dictionary = {
    "x": x,
    "y": y,
}
df = pd.DataFrame(dictionary)
df.head(8)
```

      x
      y

      0
      0
      1.0

      1
      1
      5.2

      2
      2
      3.9

      3
      3
      8.4

      4
      4
      14.6

      5
      5
      12.3

      6
      6
      8.9

      7
      7
      10.2

We can use it to print data in a tabular format. We can even use more numpy arrays with it.

```python
import numpy as np

x = np.linspace(0, np.pi/2, 15)
y = np.sin(x)

dictionary = {
    "x": x,
    "sin(x)": y,
}
df = pd.DataFrame(dictionary)
df.head(15)
```

      x
      sin(x)

      0
      0.000000
      0.000000

      1
      0.112200
      0.111964

      2
      0.224399
      0.222521

      3
      0.336599
      0.330279

      4
      0.448799
      0.433884

      5
      0.560999
      0.532032

      6
      0.673198
      0.623490

      7
      0.785398
      0.707107

      8
      0.897598
      0.781831

      9
      1.009798
      0.846724

      10
      1.121997
      0.900969

      11
      1.234197
      0.943883

      12
      1.346397
      0.974928

      13
      1.458597
      0.993712

      14
      1.570796
      1.000000

### Root Finding:
Let $f(x)$ be a function of $x$. if for some $x=\alpha, f(x) = f(\alpha) = 0$, we say $\alpha$ is a root of function $x$.

Let,
$$f(x) = x^5 + 2.5x^4 - 2x^3 -6x^2 + x + 2\tag{6.1}$$

The graph of $f(x)$ looks like this.

```python
from numpy.polynomial import Polynomial
import matplotlib.pyplot as plt

f = Polynomial([2.0, 1.0, -6.0, -2.0, 2.5, 1.0])
x = np.linspace(-2.5, 1.5, 100)
y = f(x)
dictionary = {
    'x': x,
    'y': y
}
plt.axhline(y=0, color='k')
plt.plot(x, y)
plt.plot(f.roots(), [0.0]*5, 'ro')
print(f.roots())
```

Roots are the points where the graph intersects with the $X$-axis.

### Bisection

One way to find out root's are to use bisection method. Here is the strategy, if $\alpha$ is a root between and interval $[a, b]$ then graph will cross the $X$-axis at $\alpha$. So, $sign( f(\alpha-h) ) = - sign( f(\alpha+h) )$, for small value of $h$.

So, we can work our way up towards the root by taking average of $a$ and $b$, as long as the signs are different.

we will start with $a_0$ and $b_0$, such that, $f(a_0) f(b_0) < 0$.
Then we iterate as this,
\begin{align}
m_k &= \frac{a_k + b_k}{2} \\
\text{if, } f(a_k) f(m_k) < 0, \text{ then, } a_{k+1} &= a_k \text{ and } b_{k+1} = m_k\\
\text{else, } a_{k+1} &= m_k \text{ and, } b_{k+1} = b_k
\end{align}

We keep iterating until we find the root with sufficient precision. We usually use a formula like this,
$$
\frac{|m_{k+1} - m_k|}{|m_{k+1}|} \leq \epsilon \\
$$
Where, $\epsilon$ is a very small value, like $\epsilon < 10^{-6}$

### Complete the code below
Complete the code below to iterate and solve for a root of the following equation, between the interval, $[-0.5, 1.3]$:
\begin{aligned}
    f(x) = 2 + x - 6x^2 - 2x^3 + 2.5x^4 + x^5.
\end{aligned}

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

# The polynomial and the range is defined for you
f = Polynomial([2.0, 1.0, -6.0, -2.0, 2.5, 1.0])
a = -0.5
b = 1.3
m = (a + b) / 2
e = 1e-6

root = m    # You need to update this value

# Populate the following lists in each iteration
list_a = [-0.5]
list_b = [1.3]
list_m = [m]
list_f = [f(m)]

# YOUR CODE HERE
# raise NotImplementedError()

x = 0
y = 0
val = 2e1
while(val >= e):
    if(f(list_a[x]) * f(list_m[x]) < 0):
        list_a.append(list_a[x])
        list_b.append(list_m[x])
    elif(f(list_b[x]) * f(list_m[x]) < 0):
        list_a.append(list_m[x])
        list_b.append(list_b[x])
    m = (list_a[x + 1] + list_b[x + 1]) / 2
    root = m
    list_f.append(f(m))
    list_m.append(m)

    if x > 1:
        val = (abs(list_m[y + 1] - list_m[y]) / abs(list_m[y]))
    print(val)
    x = x + 1
    y = y + 1
```

    20.0
    20.0
    0.18000000000000008
    0.07627118644067798
    0.04128440366972487
    0.021531100478469036
    0.010538641686182735
    0.00521436848203943
    0.002620850320326249
    0.001306999709555752
    0.0006543550966992081
    0.0003269635980527831
    0.00016342836390058934
    8.170082971728208e-05
    4.0847077618425525e-05
    2.0422704601330178e-05
    1.0211560848355814e-05
    5.105728286722832e-06
    2.5528771775760805e-06
    1.2764353301228634e-06
    6.382184797060476e-07

```python
xs = np.linspace(-2.5, 1.5, 100)
ys = f(xs)

plt.axhline(y=0, color='k')
plt.plot(xs, ys)
plt.plot(root, f(root), 'ro')

print(pd.DataFrame({'a':list_a, 'b':list_b, 'm':list_m, 'f(m)':list_f}))

assert "{:.3f}".format(root) == "0.672"
```

```python

```


# PS2, part 1: Welfare effects of capital injection

Import packages, classes and settings:

```python
import sys
import numpy as np
import math
import itertools
from scipy import optimize
import scipy.stats as stats
import PS2 as func
# For plots:
import matplotlib.pyplot as plt
import matplotlib as mpl
%matplotlib inline
plt.style.use('seaborn-whitegrid')
mpl.style.use('seaborn')
prop_cycle = plt.rcParams["axes.prop_cycle"]
colors = prop_cycle.by_key()["color"]
import ipywidgets as widgets
from ipywidgets import interact, interact_manual
```

## The Setup:

The exercise consists of the core setup:
* Two periods, $t=0,1$,
* Two types of agents: Consumers and entrepreneurs.
* **Consumers:**
    * Linear utility $u_c = c_0^c+c_1^c$.
    * Large endowment of consumption goods in each period: $e$.
    * Labor endowment of $e_l=1$. Inelastically supplied in a competitive labor market at the price $w_1$.
* **Entrepreneurs:**
    * Linear utility $u_e = c_0^e+c_1^e$.
    * Small endowment $n_0$ in period 0.
    * Borrow $b_1$, invest in capital $k_1$.
    * Period 1 they hire labor $l_1$ at wage $w_1$.
    * Entrepreneurs utilize capital and labor in $t=1$ with Cobb-Douglas production function
    $$ \begin{align}
    y_1 = k_1^{\alpha}l_1^{1-\alpha} \notag
    \end{align} $$
    * **Entrepreneurs are borrowing constrained:**
    $$\begin{align}
    b_1\leq \lambda \left(y_1-w_1l_1\right), && \lambda\in (0,1) \notag
    \end{align} $$
* Assume that endowment of consumption goods $(e)$ is *sufficiently* large, for the interest rate to be 1 in equilibrium. (We will show what *sufficiently* means here).

## Q1: Three parts. (i) State the entrepreneur's problem. (ii) Argue that entrepreneurs will choose to maximize profits in period 1, a linear function of $k_1$, and (3) restate entrepreneur's problem as a simple linear problem.

### (i) Entrepreneur problem:

Interpret bullets from **entrepreneurs** above as a problem:

$$
\begin{align}
    \max_{c_0,c_1,k_1,l_1,y_1,b_1} \mbox{ } c_0+c_1,& \notag \\
    \text{s.t. }\mbox{ }c_0+k_1 &= n_0+b_1 \notag \\
                        c_1+b_1 &= y_1-w_1l_1 \tag{Problem 1}\\
                            y_1 &= k_1^{\alpha}l_1^{1-\alpha} \notag \\
                            b_1\leq &\lambda \left(y_1-w_1l_1\right) \notag
\end{align}$$

States that:
1. Maximize consumption (risk-neutrality).
2. Budget $t=0$: Consumption $(c_0)$ and investment $(k_1)$ equals income from endowment $(n_0)$ and borrowing $(b_1)$.
3. Budget $t=1$: Consumption $(c_1)$ and repayment of debt $(b_1)$ equals income from production $(y_1)$ minues wages $(w_1l_1)$.
4. Production is Cobb-Douglas.
5. Entrepreneur is credit-constrained, with $\lambda$ denoting the degree of future profits that can be used as collateral.

Substitute for $c_0$ and $c_1$ into the utility function, using the budgets for $t=0,1$:

$$\begin{align}
    c_0+c_1 &= n_0+b_1-k_1+y_1-w_1l_1-b_1 \\
            &= n_0+y_1-k_1-w_1l_1
\end{align}$$

Substitue for $y_1$ using the Cobb-Douglas function (remember you should substitute for $y_1$ in both the object function **and** the constraints). The problem is then reduced to:

$$\begin{align}
    \max_{k_1,l_1,b_1} n_0+k_1^{\alpha}l_1^{1-\alpha}-k_1-w_1l_1, && s.t. b_1\leq \lambda \left(k_1^{\alpha}l_1^{1-\alpha}-w_1l_1\right). \tag{Problem 2}
\end{align}$$

The Lagrangian function corresponding to this problem is then given by:

$$\begin{align}
    \mathcal{L}_2 = n_0+k_1^{\alpha}l_1^{1-\alpha}-k_1-w_1l_1+\mu\left[\lambda\left(k_1^{\alpha}l_1^{1-\alpha}-w_1l_1\right)-b_1\right],
\end{align}$$

where $\mu\geq0$ is the shadow-variable related to the credit-constraint. As in problem set, exercise 3.5 in (JT), this problem is characterized by a set of first order conditions with Karush-Kuhn-Tucker conditions for $\mu$ and the credit constraint. This is because of the inequality constraint (instead of standard Lagrangian approach when the constraint is with "=").

### (ii) Optimal choice of labor:

Note that profits in $t=1$ is given by:

$$\begin{align}
    \pi_1 = k_1^{\alpha}l_1^{1-\alpha}-w_1l_1.
\end{align}$$

Optimal choice of labor $(l^*)$ is then characterized by:

$$\begin{align}
    \dfrac{\partial \mathcal{L}}{\partial l_1} = (1-\alpha)k_1^{\alpha}l_1^{-\alpha} -w_1 + \mu\lambda\left[(1-\alpha)k_1^{\alpha}l_1^{-\alpha}-w_1\right] = 0
\end{align}$$

We can rewrite this condition as:

$$\begin{align}
    \left[(1-\alpha)k_1^{\alpha}l_1^{-\alpha}-w_1\right](1+\mu\lambda)=0 && \Rightarrow && w_1 = (1-\alpha)k_1^{\alpha}l_1^{-\alpha}
\end{align}$$

Note that this corresponds to maximizing $\pi_1$ wrt. labor. Note further that we can use this to write up two other terms:

$$\begin{align}
w_1l_1^* = (1-\alpha)k_1^{\alpha}(l_1^*)^{1-\alpha}, \tag{Labor Share}
\end{align}$$

and by isolating for $l_1$:

$$\begin{align}
    l_1^* = \dfrac{(1-\alpha)^{1/\alpha}k_1}{w_1^{1/\alpha}}. \tag{$l^*$}
\end{align}$$

To show that the profit function is linear in $k_1$, when labor is chosen optimally, start by using (Labor Share):

$$\begin{align}
    \pi_1(l=l^*) &= k_1^{\alpha}(l_1^*)^{1-\alpha}-w_1l_1^* \notag \\
                 &= \alpha k_1^{\alpha}(l_1^*)^{1-\alpha}
\end{align}$$

Now substitute for $(l^*)$:

$$\begin{align}
    \pi_1(l=l^*) &= \alpha k_1^{\alpha}\dfrac{(1-\alpha)^{(1-\alpha)/\alpha}k_1^{1-\alpha}}{w_1^{(1-\alpha)/\alpha}} \notag \\
                 &= \underbrace{\dfrac{\alpha (1-\alpha)^{(1-\alpha)/\alpha}}{w_1^{(1-\alpha)/\alpha}}}_{\equiv R(w_1)}k_1 \tag{Profit}
\end{align}$$

### (iii) Restate as linear problem:

Using (Profit) in (Problem 2):

$$\begin{align}
    \max_{k_1,b_1}\mbox{ }n_0-k_1+R(w_1)k_1, && \text{s.t. } b_1\leq \lambda R(w_1)k_1 \tag{Problem 3}.
\end{align} $$

## Q2: Derive entrepreneur's demand for capital, and argue how the solution depends on $\lambda$ and $R(w_1)$.

Problem $3$ is linear in $k_1$. Thus capital demand depends on:

* If $R(w_1)<1$: Investment in capital is not profitable, $k_1^*=0$. (it is implicitly assumed that $k\geq0$)
* If $\lambda R(w_1)>1$: Investment in capital is so profitable that borrowing constraint never binds, $k_1^*=\infty$.
* If $\lambda R(w_1)<1<R(w_1)$: Capital is postponed as much as possible, but borrowing constraint will bind. Solution is then to set $c_0=0$ in the budget at $t=0$, and use that $b_1$ is determined by the borrowing constraint:

$$\begin{align}
    k_1^* &= n_0 +b_1 \\
          &= n_0+\lambda R(w_1)k_1^* \\
\Rightarrow k_1^* &= \dfrac{n_0}{1-\lambda R(w_1)}
\end{align}$$

* Finally, if $R(w_1)=1$ the entrepreneur is indifferent between consumption and investment. In this case capital demand is indeterminate:
$$\begin{align}
    k_1^*\in\left[0,\mbox{ }\dfrac{n_0}{1-\lambda}\right].
\end{align}$$

**Illustrating demand for capital, and equilibrium capital**:

Before we proceed to the next questions, let us briefly illustrate how capital demand looks like, and how it behaves in equilibrium. The following illustrates the demand for capital for varying parameter values, and the interest rate function $R(w)=\alpha k^{\alpha-1}$.

```python
Model = func.TwoAgent_GE()
Model.plot_interactive_ds()
```

    interactive(children=(SelectionSlider(description='Endowment, $n$', options=(0.0, 0.02, 0.04, 0.06, 0.08, 0.1,…

## Q3: Show the cutoff $n^*$, where $n_0 \geq n^*$$ implies that the first-best level of capital can be financed, and the borrowing constraint is not binding.

Let us define the first-best level of capital. Recall that in equilibrium $l_1=1$ (as this is the endowment supplied by consumers). Define first-best as **the level of capital that maximizes net output**:

$$\begin{align}
    \max_{k_1} k_1^{\alpha}-k_1 && \Rightarrow && k_1^{fb} = \alpha^{1/(1-\alpha)}.
\end{align}$$

In equilibrium the interest rate $R(w_1)$ is defined by the marginal product of capital:

$$\begin{align}
    R(w_1) = \alpha k_1^{\alpha-1}, && \Rightarrow && k_1 = \left(\dfrac{\alpha}{R(w_1)}\right)^{1/(1-\alpha)}
\end{align}$$

Thus for $k_1=k_1^{fb}$ we need $R(w_1)=1$. When $R(w_1)=1$ the capital demand was indeterminate, i.e. it covered a range of possible solutions. The optimal level $k_1^{fb}$ is one of the feasible capital demand levels as long as:

$$\begin{align}
k_1^{fb}\equiv \alpha^{1/(1-\alpha)}\in\left[0,\mbox{ }\dfrac{n_0}{1-\lambda}\right].
\end{align}$$

As long as $n_0$ is sufficiently large the first best is thus an equilibrium. The threshold $n^*$ where the first best is an equilibrium for $n_0\geq n^*$ is then given by the condition above with equality:

$$\begin{align}
    \alpha^{1/(1-\alpha)} = \dfrac{n^*}{1-\lambda} && \Rightarrow && n^* = (1-\lambda)\alpha^{1/(1-\alpha)}
\end{align}$$

**Addtional remark:** Give another explanation for why we need R=1 for optimality:

Optimality requires that the $\color{blue}{\text{marginal rate of transformation}}$ equals $\color{blue}{\text{the marginal rate of substitution}}$

**Plotting the equilibrium function:**

```python
Model.plot_interactive_cequi()
```

    interactive(children=(SelectionSlider(description='Borrowing constr., $\\lambda$', options=(0.0, 0.04, 0.08, 0…

## Q4: Show that if n<n* there is an equilibrium where the entrepreneurs are constrained and $k_1$ becomes an increasing function of $n_0$.

* **Capital demand:** Recall that when $n_0<n^*$ the entrepreneurs do not have sufficient equity; thus they are credit constrained.
* In the credit constrained case recall that capital was given by the budget constraint for $t=0$ with $c_0=0$ imposed, i.e.:

$$\begin{align}
    k_1 = n_0+\lambda R(w_1) k_1.
\end{align}$$

Substituting for $R(w_1)$ this yields:

$$\begin{align}
    k_1 - \lambda\alpha k_1^{\alpha}=n_0. \tag{$k_1^*$, credit-constrained}
\end{align}$$

As long as $n_0<n^*$ this equations determines $k_1$. Differentiation wrt. $n_0$ then yields

$$\begin{align}
    \dfrac{\partial k_1}{\partial n_0}-\lambda \alpha^2k_1^{\alpha-1}\dfrac{\partial k_1}{\partial n_0}=1 && \Rightarrow && \dfrac{\partial k_1}{\partial n_0} = \left(1-\lambda \alpha k_1^{\alpha}\dfrac{\alpha}{k_1}\right)^{-1}.
\end{align}$$

Using the expression above for the credit constrained $k_1$ we can rearrange slightly to show that

$$\begin{align}
\dfrac{\partial k_1}{\partial n_0} = \dfrac{1}{1-\alpha(k_1-n_0)/k_1}>1
\end{align}$$

another neat way of writing this is:

$$\begin{align}
    \dfrac{\partial k_1}{\partial n_0} = \dfrac{1}{1-\alpha \lambda R(w_1)}>1.
\end{align}$$

Capital thus increases more than 1-to-1 with the entrepreneur's equity here. The reason for this is the following:

In the credit constrained case the entrepreneur prefers to allocate **all** of his consumption to $t=1$: Thus when $n_0$ increases marginally so does $k_1$, initially 1-to-1. When $k_1$ increases the entrepreneur increases his future profits $(y_1-w_1l_1)$; this relaxes the credit-constraint, allowing him to borrow more against future profits. This increases investments further. These findings can be confirmed in the equilibrium plot below:

```python
Model.plot_interactive_cequi()
```

    interactive(children=(SelectionSlider(description='Borrowing constr., $\\lambda$', options=(0.0, 0.04, 0.08, 0…

## Q5: Introduce a lump-sum tax on consumers in period 0 that transfers wealth to entrepreneurs. Derive expected utility as a function of the lump sum tax rate.

**Plot utility:**

Note:
* The marginal utility of consumers is unambiguously decreasing in the tax. It converges on  a linear negative slope (-1).
* The marginal utility of entrepreneurs is similarly decreasing in the tax. It converges on a positive linear slope (1).

The following sections explains why this is the case.

```python
Model.plot_u_ce_tau()
```

Let us start with the consumers' utility. This is straightforward:

$$\begin{align}
    U_C(\tau) = 2e-\tau + \overbrace{(1-\alpha)k_1^{\alpha}}^{\equiv w_1(\tau)}
\end{align}$$

**The entrepreneur part:**

Setup is as defined in (Problem 3) earlier, simply with an added $\tau$:

$$\begin{align}
    U_E(\tau) = n_0+\tau +k_1\left[R(w_1)-1\right]
\end{align}$$

Substitute for the equilibrium interest rate we then have

$$\begin{align}
    U_E(\tau) = n_0+\tau + \alpha k_1^{\alpha}-k_1
\end{align}$$

Now we have two cases:

### The credit-constrained case

If $n_0+\tau<n^*$: The entrepreneur is **credit-constrained**, and capital is defined as earlier by the equality

$$\begin{align}
k_1-\lambda \alpha k_1^{\alpha} = n_0+\tau.
\end{align}$$

Substituting for this we get

$$\begin{align}
    U_E(\tau) = \alpha k_1^{\alpha}(1-\lambda)
\end{align}$$

Increasing the tax $\tau$ corresponds to increasing the available endowment for entrepreneurs at $t=0$. As we confirmed above, capital is increasing in $n_0$ and thus in $\tau$, when the credit constraint is binding. Thus $\partial U_E/\partial\tau>0$ when the entrepreneur is constrained. Technically we can confirm this:

$$\begin{align}
    \dfrac{\partial U_E}{\partial \tau} &= \alpha^2k_1^{\alpha-1}(1-\lambda)\dfrac{\partial k_1}{\partial n_0} \\
                                        &= \alpha R(w_1)(1-\lambda)\dfrac{\partial k_1}{\partial n_0}>0
\end{align}$$

Using the derivative $\partial k_1/\partial n_0$, we can (if we prefer) simplify this further as

$$\begin{align}
    \dfrac{\partial U_E}{\partial \tau} &= \dfrac{\alpha R(w_1)(1-\lambda)}{1-\alpha \lambda R(w_1)}>0, && n_0+\tau<n^*
\end{align}$$

### The non-constrained case:

If $n_0+\tau\geq n^*$ the capital equilibrium is unaffected. In this case the tax simply transfers wealth from consumers to entrepreneurs, who then immediately consume it. In other words we sum up:

$$\begin{align}
    \dfrac{\partial U_e}{\partial \tau} = \left\lbrace \begin{array}{ll}  1 & n_0+\tau\geq n^* \\
    \dfrac{\alpha R(w_1)(1-\lambda)}{1-\alpha \lambda R(w_1)} & n_0+\tau<n^* \end{array}\right.
\end{align}$$

## Q6: Show that the relationship between consumer utility and the lump-sum tax is non-monotone. Show that for sufficiently small $n_0$ the tax can be pareto-improving.

Entrepreneurs always benefit from the tax. For consumers the tax includes three potential effects:

$$\begin{align}
    \dfrac{\partial U_c}{\partial \tau} = \left\lbrace \begin{array}{ll} \color{red}{-1}, & n_0+\tau\geq n^* \\ \color{red}{-1} +\color{blue}{(1-\alpha)R(w_1)}\color{blueviolet}{\dfrac{\partial k_1}{\partial \tau}}, & n_0+\tau<n^*\end{array}\right.
\end{align}$$

When $n_0+\tau\geq n^*$ the effect is clearly negative. However, when entrepreneurs are credit-constrained:

1. Negative wealth transfer effect: $\color{red}{-1}$.
2. Entrepreneurs increase capital investment, which increases labor wages: $\color{blue}{(1-\alpha)R(w_1)}$.
3. **(Credit multiplier)** The transfer loosens credit constraitn incuding further investment: $\color{blueviolet}{\partial k_1/\partial \tau}>1$.

If effects 2-3. dominate effect 1., the tax has a pareto-improving effect. Effects 2-3. are largest for small values of $k_1$ (thus $n_0$).

## Q7: Discuss how this justifies government's capital injection during the financial crisis. What features might the model be missing?

The role for capital injections in this model:

* A capital injection relaxes the banks' borrowing constraint, increases lending to the real sector and thus investments. This increases employment (not in our model though) and wages, which can offset the effects of the recession.

* *Caveats (a few of potentially many):*
    1. Moral hazard (reputation effect): If financial institutions expect bailouts $\Rightarrow$ increase risky investments.
    2. Endogenous uncertainty: A feature of the financial crisis was precautionary behavior in light of increased uncertainty. The capital injection reduces uncertainty of asset prices.

## Q8: How does key findings in Q6 depend on the type of borrowing constraint?

The short answer: The borrowing constraint is quite standard. There are microfoundations that lead to this type of constraint.

In this model future profits can (partly) be collateralized. There are several potential explanations why future profits cannot entirely be collateralized.

* **Informational/enforcement frictions** are at the heart of any borrowing constraint (to the best of my knowledge at least).
* **Moral Hazard**: Behave/shirk.
* **Collateralized assets** as Kiyotaki and Moore (1997) leads to similar results.


# Spectral Estimation of Random Signals

*This jupyter/Python notebook is part of a [collection of notebooks](../index.ipynb) in the masters module [Digital Signal Processing](http://www.int.uni-rostock.de/Digitale-Signalverarbeitung.48.0.html), Comunications Engineering, Universität Rostock. Please direct questions and suggestions to <mailto:Sascha.Spors@uni-rostock.de>.*

## Parametric Methods

### Motivation

Non-parametric methods for the estimation of the power spectral density (PSD), like the [periodogram](periodogram.ipynb) or [Welch's method](welch_method.ipynb), don't rely on a-priori information about the process generating the random signal. Often some a-priori information is available that can be used to formulate a parametric model of the random process. The goal is then to estimate these parameters in order to characterize the random signal. Such techniques are known as *[parametric methods](https://en.wikipedia.org/wiki/Spectral_density_estimation#Parametric_estimation)* or *model-based methods*. The incorporation of a-priori knowledge can improve the estimation of the PSD significantly, as long as the underlying model is a valid description of the random process. The parametric model of the random process can also be used to generate random signals with a desired PSD.

### Process Models

For the remainder we assume wide-sense stationary real-valued random processes. For many applications the process can be modeled by a linear-time invariant (LTI) system

where $n[k]$ is [white noise](../random_signals/white_noise.ipynb) and $H(\mathrm{e}^{\,\mathrm{j}\,\Omega})$ denotes the transfer function of the system. In general, the random signal $x[k]$ will be correlated as a result of the processing of the uncorrelated input signal $n[k]$ by the system $H(\mathrm{e}^{\,\mathrm{j}\,\Omega})$. Due to the white noise assumption $\Phi_{nn}(\mathrm{e}^{\,\mathrm{j}\,\Omega}) = N_0$, the PSD of the random process is given as

\begin{equation}
\Phi_{xx}(\mathrm{e}^{\,\mathrm{j}\,\Omega}) = N_0 \cdot | H(\mathrm{e}^{\,\mathrm{j}\,\Omega}) |^2
\end{equation}

Parametric methods model the system $H(\mathrm{e}^{\,\mathrm{j}\,\Omega})$ by a limited number of parameters. These parameters are then estimated from $x[k]$, providing an estimate $\hat{H}(\mathrm{e}^{\,\mathrm{j}\,\Omega})$ of the transfer function. This estimate is then used to calculate the desired estimate $\hat{\Phi}_{xx}(\mathrm{e}^{\,\mathrm{j}\,\Omega})$ of the PSD.

#### Autoregressive model

The [autoregressive](https://en.wikipedia.org/wiki/Autoregressive_model) (AR) model assumes a recursive system with a direct path. Its output relation is given as

\begin{equation}
x[k] = \sum_{n=1}^{N} a_n \cdot x[k-n] + n[k]
\end{equation}

where $a_n$ denote the coefficients of the recursive path and $N$ the order of the model. Its system function $H(z)$ is derived by $z$-transformation of the output relation

\begin{equation}
H(z) = \frac{1}{1 - \sum_{n=1}^{N} a_n z^{-n}}
\end{equation}

Hence, the AR model is a pole-only model of the system.

#### Moving average model

The [moving average](https://en.wikipedia.org/wiki/Moving-average_model) (MA) model assumes a non-recursive system. The output relation is given as

\begin{equation}
x[k] = \sum_{m=0}^{M-1} b_m \cdot n[k-m] = h[k] * n[k]
\end{equation}

with the impulse response of the system $h[k] = [ b_0, b_1, \dots, b_{M-1} ]$. The MA model is a finite impulse response (FIR) model of the random process. Its system function is given as

\begin{equation}
H(z) = \mathcal{Z} \{ h[k] \} = \sum_{m=0}^{M-1} b_m \; z^{-m}
\end{equation}

#### Autoregressive moving average model

The [autoregressive moving average](https://en.wikipedia.org/wiki/Autoregressive%E2%80%93moving-average_model) (ARMA) model is a combination of the AR and MA model. It constitutes a general linear process model. Its output relation is given as

\begin{equation}
x[k] = \sum_{n=1}^{N} a_n \cdot x[k-n] + \sum_{m=0}^{M-1} b_m \cdot n[k-m]
\end{equation}

Its system function reads

\begin{equation}
H(z) = \frac{\sum_{m=0}^{M-1} b_m \; z^{-m}}{1 - \sum_{n=1}^{N} a_n z^{-n}}
\end{equation}

### Parametric Spectral Estimation

The models above describe the synthesis of the samples $x[k]$ from the white noise $n[k]$. For spectral estimation only the random signal $x[k]$ is known and we are aiming at estimating the parameters of the model. This can be achieved by determining an analyzing system $G(\mathrm{e}^{\,\mathrm{j}\,\Omega})$ such to decorrelate the signal $x[k]$

where $e[k]$ should be white noise. Due to its desired operation, the filter $G(\mathrm{e}^{\,\mathrm{j}\,\Omega})$ is also denoted as *whitening filter*. The optimal filter $G(\mathrm{e}^{\,\mathrm{j}\,\Omega})$ is given by the inverse system $\frac{1}{H(\mathrm{e}^{\,\mathrm{j}\,\Omega})}$. However, $H(\mathrm{e}^{\,\mathrm{j}\,\Omega})$ is in general not known. But this nevertheless implies that our linear process model of $H(\mathrm{e}^{\,\mathrm{j}\,\Omega})$ also applies to $G(\mathrm{e}^{\,\mathrm{j}\,\Omega})$. Various techniques have been developed to estimate the parameters of the filter $G(\mathrm{e}^{\,\mathrm{j}\,\Omega})$ such that $e[k]$ becomes decorrelated. For instance, by expressing the auto-correlation function (ACF) $\varphi_{xx}[\kappa]$ in terms of the model parameters and solving with respect to these. The underlying set of equations are known as [Yule-Walker equations](https://en.wikipedia.org/wiki/Autoregressive_model#Yule-Walker_equations).

Once the model parameters have been estimated, these can be used to calculate an estimate $\hat{G}(\mathrm{e}^{\,\mathrm{j}\,\Omega})$ of the analysis system. The desired estimate of the PSD is then given as

\begin{equation}
\hat{\Phi}_{xx}(\mathrm{e}^{\,\mathrm{j}\,\Omega}) = \frac{\Phi_{ee}(\mathrm{e}^{\,\mathrm{j}\,\Omega})}{|\hat{G}(\mathrm{e}^{\,\mathrm{j}\,\Omega})|^2}
\end{equation}

where if $e[k]$ is white noise, $\Phi_{ee}(\mathrm{e}^{\,\mathrm{j}\,\Omega}) = N_0$.

### Example

In the following example $n[k]$ is drawn from normal distributed white noise with $N_0 = 1$. The Yule-Walker equations are used to estimate the parameters of an AR model of $H(\mathrm{e}^{\,\mathrm{j}\,\Omega})$. The implementation provided by `statsmodels.api.regression.yule_walker` returns the estimated AR coefficients of the system $H(\mathrm{e}^{\,\mathrm{j}\,\Omega})$. These parameters are then used to numerically evaluate the estimated transfer function, resulting in $\hat{\Phi}_{xx}(\mathrm{e}^{\,\mathrm{j}\,\Omega}) = 1 \cdot \vert \hat{H}(\mathrm{e}^{\,\mathrm{j}\,\Omega}) \vert^2$.

```python
import numpy as np
import matplotlib.pyplot as plt
import statsmodels.api as sm
import scipy.signal as sig

K = 4096  # length of random signal
N = 3  # order of AR model
a = np.array((1, -1, .5))  # coefficients of AR model

# generate random signal n[k]
np.random.seed(2)
n = np.random.normal(size=K)

# AR model for random signal x[k]
x = np.zeros(K)
for k in np.arange(3, K):
    x[k] = a[0]*x[k-1] + a[1]*x[k-2] + a[2]*x[k-3] + n[k]

# estimate AR parameters by Yule-Walker method
rho, sigma = sm.regression.yule_walker(x, order=N, method='mle')

# compute true and estimated transfer function
Om, H = sig.freqz(1, np.insert(-a, 0, 1), worN=256)
Om, He = sig.freqz(1, np.insert(-rho, 0, 1), worN=256)
# compute PSD by Welch method
Om2, Pxx = sig.welch(x, nperseg=511, return_onesided=True)

# plot PSDs
plt.figure(figsize=(10, 5))
plt.plot(Om, np.abs(H)**2, label=r'$\Phi_{xx}(e^{j\Omega})$')
plt.plot(Om2*2*np.pi, .5*Pxx, 'k-', alpha=.3,
         label=r'$\hat{\Phi}_{xx}(e^{j\Omega})$ (Welch)')
plt.plot(Om, np.abs(He)**2,
         label=r'$\hat{\Phi}_{xx}(e^{j\Omega})$ (parametric)')

plt.xlabel(r'$\Omega$')
plt.axis([0, np.pi, 0, 20])
plt.legend()
plt.grid()

# compute bias/variance of the estimators
print('Bias of the Welch estimate: \t\t {0:1.4f}'.format(
    np.mean(Pxx-np.abs(H)**2)))
print('Variance of the Welch estimate: \t {0:1.4f}'.format(np.var(Pxx)))
print('Bias of the parametric estimate: \t {0:1.4f}'.format(
    np.mean(np.abs(H)**2-np.abs(He)**2)))
print('Variance of the parametric estimate: \t {0:1.4f}'.format(
    np.var(np.abs(He)**2)))
```

    Bias of the Welch estimate: 		 2.8932
    Variance of the Welch estimate: 	 62.0748
    Bias of the parametric estimate: 	 -0.0291
    Variance of the parametric estimate: 	 15.3476

**Exercise**

* Change the order `N` of the AR model used for estimation by the Yule-Walker equations. What happens if the order is smaller or higher than the order of the true system? Why?

* Change the number of samples `K`. Is the estimator consistent?

Solution: Choosing the order of the estimated AR model differently from the true process results in a mismatch of the model with the consequence of potentially large deviations between the estimated PSD and true PSD. These deviations are typically larger when choosing the order smaller since the model does definitely not fit to the true process. However, choosing the order larger is typically not that problematic since some of the AR coefficients are estimated as approximately zero due to the lower order of the process generating the random signal. Increasing the number of samples seems to lower the bias and variance of the estimated PSD, the estimator can therefore be assumed to be consistent.

**Copyright**

This notebook is provided as [Open Educational Resource](https://en.wikipedia.org/wiki/Open_educational_resources). Feel free to use the notebook for your own purposes. The text is licensed under [Creative Commons Attribution 4.0](https://creativecommons.org/licenses/by/4.0/), the code of the IPython examples under the [MIT license](https://opensource.org/licenses/MIT). Please attribute the work as follows: *Sascha Spors, Digital Signal Processing - Lecture notes featuring computational examples*.