# Equation tearing

```python

```

# Déclaration des variables #

```python
import numpy as np
import scipy as sc
import matplotlib.pyplot as plt
from scipy.integrate import odeint

from mpl_toolkits.mplot3d import Axes3D

import matplotlib as mpl
mpl.rcParams['text.usetex'] = True

import sympy as sp

import itertools
```

On définit ici les variables utiles :

$N$ le nombre de points de la simulation, $r_{min}$ le premier point considéré à l'intérieur
,$r_{max}$ le dernier point considéré à l'extérieur
,$n$ le nombre d'onde poloïdal, $m$ le nombre d'onde toroïdal, $B_0$, $R_0$, $q_s = \frac{m}{n}$ le facteur de sécurité sur la surface de résonance.

```python
N = 1000
rmin = 1/N
rmax = 1-1/N
step = rmax/N
r0 = 0.5
psi0 = 1.
B0 = 1.
R0 = 1.
mu0 = 4*np.pi*10E-7
j0prime = -1
q0 = (-2*B0)/(mu0*j0prime*r0*R0)
n = 1.
m = 2.
qs = m/n
#vecteur des positions
v_r = np.linspace(rmin, rmax, N)
```

###Profils de courant et de facteur de sécurité

## Profil type step function ##

On modélise les profils de type fonction porte

Pour modéliser la fonction en saut de courant, on utilise respectivement une fonction tangente hyperbolique et une fonction lorentzienne pour $j_{eq}(r)$ et $\frac{d j_{eq}}{dr}$.

```python
def lor(x, gamma, x0):
    return gamma/(2*np.pi*(gamma**2/4+(x-x0)**2))
```

En résolvant l'équation de Maxwell-Ampère, on trouve :
\begin{equation}
\frac{1}{r} \frac{d}{dr}\left \frac{r^2}{q(r)} \right) = \frac{\mu_0 R_0 j_{eq}}{B_0}
\end{equation}
D'où l'expression analytique de $q(r)$ obtenue en intégrant :

\begin{equation}
q(r) = q_0
q(r) = q_0 \left(\frac{r}{r_0} \right)^2

\end{equation}

```python
def q(r, r0, j0p):
    q0 = (-2*B0)/(r0*R0*mu0*j0p)
    if r < r0 :
        return q0
    elif r > r0 :
        return q0*r**2/r0**2
```

```python
def profil_step(r0,j0p) :
   v_jeq = np.tanh(N*(-v_r+r0))*(-r0*j0p)+ np.tanh(N*(r0))*(-r0*j0p)
   v_djeq = lor(v_r,1/N,r0)/np.max(lor(v_r,1/N,r0))*j0p*r0
   v_q = list(map(q, v_r, [r0 for i in range (N)], [j0p for i in range (N)]))
   return np.array([v_q,v_jeq, v_djeq])
```

```python
def rres(profil):
    "Renvoie la position radiale de la surface de résonnance"
    v_q = profil[0]
    return np.interp(qs,v_q,v_r)
```

```python
r0 = 0.5
j0p = -250000
profil = profil_step(r0,j0p)

def plot_pr(profil):
    v_q = profil[0]
    v_jeq = profil[1]
    v_djeq = profil[2]

    rs = rres(profil)

    plt.figure()
    plt.plot(v_r,v_q)
    #plt.plot(v_r,v_s)
    plt.axvline(x=rs, color = 'black',linestyle = '--',linewidth=0.5)
    plt.xlabel('r')
    plt.ylabel('$q$')
    plt.title('$q(r)$')
    plt.show()

    plt.figure()
    plt.plot(v_r,v_jeq)
    plt.axvline(x=rs, color = 'black',linestyle = '--',linewidth=0.5)
    plt.xlabel('$r$')
    plt.ylabel('$j_{eq}$')
    plt.title('$j_{eq}$')
    plt.show()

    plt.figure()
    plt.plot(v_r,v_djeq)
    plt.axvline(x=rs, color = 'black',linestyle = '--',linewidth=0.5)
    plt.xlabel('$r$')
    plt.ylabel('$ d j_{eq}/dr$')
    plt.title('$ d j_{eq}/dr$')
    plt.show()

plot_pr(profil)
```

### Profil de Wesson

De même pour les profils type Wesson :
    $q(r) = q_a \cdot\frac{r^2}{1-(1-r^2)^(\nu+1)}$

```python
def wess(r, qa, nu):
    return qa*r**2/(1-(1 - r**2)**{(nu+1)})
```

On calcule ensuite les vecteurs correspondant à $q(r)$, $j_{eq}(r)$,$\frac{dj_{eq}}{dr}$ en utilisant la fonction gradient de Python.

```python
def profil_wesson(qa,nu) :
    '''Renvoie les vecteurs q, jeq et djeq selon le profil type Wesson'''
    v_q = wess(v_r,qa,nu)
    v_jeq = (np.gradient(v_r**2/v_q)*B0)/(mu0*R0*v_r*step)
    v_djeq = np.gradient(v_jeq)/step

    #Correction pour les premiers termes des dérivées
    v_jeq[0] = np.interp(v_r[0],v_r[1:],v_jeq[1:])
    v_djeq[1] = np.interp(v_r[1],v_r[2:],v_djeq[2:])
    v_djeq[0] = np.interp(v_r[0],v_r[2:],v_djeq[2:])
    return np.array([v_q,v_jeq, v_djeq])
```

```python
qa = 3.5
nu = 1.8
```

```python
profil = profil_wesson(qa,nu)
plot_pr(profil)
```

# Résolution de l'équation tearing

Pour résoudre l'équation tearing, on part de chaque extrémité du rayon.

On utilise un schéma de Runge-Kutta d'ordre 2.

```python
def ode2_stepRK(h,b,c,v_i):
     " solves y''+by' +c = 0 for one iteration with step h, Runge Kutta method. v_i must be of the form [y',y]"
     A = np.array([[-b,-c],[1,0]])
     return v_i + h*A.dot(v_i)+ h**2/2*(A.dot(A).dot(v_i))
```

```python
v_q = profil[0]
v_jeq = profil[1]
v_djeq = profil[2]
rs = rres(profil)
```

```python
def solve_tear_part(profil) :
    "Résolution de l'équation kink-tearing et tracé des courbes, utilisation de Runge-Kutta d'ordre 2"
    v_sol = np.zeros((2,N))
    v_sol[:,0] = [1,0] #ici 1 est le psi prime initial (arbitraire et on ajuste)

    #Maillage équidistant à droite et à gaude de rs
    b = np.arange(rs+step/2,rmax,step)
    a = -np.arange(-(rs-step/2),-rmin,step)
    a = np.sort(a)
    v_r = np.concatenate((a,b))

    #Forward
    i_f = 0
    while v_r[i_f+1] < rs : #lorsqu'on s'intéresse à i_f on calcule le terme suivant
        djeq = v_djeq[i_f]
        r = v_r[i_f]
        q = v_q[i_f]
        c = - m**2/r**2 + mu0*q*qs*R0*djeq/((q-qs)*r*B0)
        b = 1/r
        v_i = v_sol[:,i_f]
        v_sol[:,i_f+1] = ode2_stepRK(step,b,c,v_i)
        i_f = i_f +1

     #Backward
    i_b = len(v_r)-1 #On devrait partir de N-2 car la condition aux limites est sur v_r[N-1]
    v_sol[:,i_b] = [1,0] #CL à l'extrémité droite
    while v_r[i_b] > rs :
        djeq = v_djeq[i_b]
        r = v_r[i_b]
        q = v_q[i_b]
        c = - m**2/r**2 + mu0*q*qs*R0*djeq/((q-qs)*r*B0)
        b = 1./r
        v_i = v_sol[:,i_b]
        v_sol[:,i_b-1] = ode2_stepRK(step,b,c,v_i)
        i_b = i_b - 1
        v_sol[0,i_b:] = - v_sol[0,i_b:] #vraies dérivées par rapport au sens de parcours r croissant

        #Psi, Dpsi
        psi = v_sol[1,:]
        dpsi = v_sol[0,:]
        #Figures
        plt.figure()
        plt.plot(v_r[:i_f], psi[:i_f])
        plt.show()

        plt.figure()
        plt.plot(v_r[i_f:], psi[i_f:])
        plt.show()
```

```python
solve_tear_part(profil)
```

```python
def solve_tear(profil) :
    "Résolution de l'équation kink-tearing et tracé des courbes, utilisation de Runge-Kutta d'ordre 2"
    v_sol = np.zeros((2,N))
    v_sol[:,0] = [1,0] #ici 1 est le psi prime initial (arbitraire et on ajuste)

    #Maillage équidistant à droite et à gaude de rs
    b = np.arange(rs+step/2,rmax,step)
    a = -np.arange(-(rs-step/2),-rmin,step)
    a = np.sort(a)
    v_r = np.concatenate((a,b))

    #Forward
    i_f = 0
    while v_r[i_f+1] < rs : #lorsqu'on s'intéresse à i_f on calcule le terme suivant
        djeq = v_djeq[i_f]
        r = v_r[i_f]
        q = v_q[i_f]
        c = - m**2/r**2 + mu0*q*qs*R0*djeq/((q-qs)*r*B0)
        b = 1/r
        v_i = v_sol[:,i_f]
        v_sol[:,i_f+1] = ode2_stepRK(step,b,c,v_i)
        i_f = i_f +1

     #Backward
    i_b = len(v_r)-1 #On devrait partir de N-2 car la condition aux limites est sur v_r[N-1]
    v_sol[:,i_b] = [1,0] #CL à l'extrémité droite
    while v_r[i_b] > rs :
        djeq = v_djeq[i_b]
        r = v_r[i_b]
        q = v_q[i_b]
        c = - m**2/r**2 + mu0*q*qs*R0*djeq/((q-qs)*r*B0)
        b = 1./r
        v_i = v_sol[:,i_b]
        v_sol[:,i_b-1] = ode2_stepRK(step,b,c,v_i)
        i_b = i_b - 1
    v_sol[0,i_b:] = - v_sol[0,i_b:] #vraies dérivées par rapport au sens de parcours r croissant

    #Raccordement
    v_sol[:,:i_f] = v_sol[:,:i_f]/v_sol[1,i_f-1]
    v_sol[:,i_f:] = v_sol[:,i_f:]/v_sol[1,i_f]

    #Calculs et tracés des tangentes
    v_r = np.concatenate(([0],v_r)) #On rajoute le point de départ à 0
    t_g = v_sol[0,i_f-1]*(v_r - v_r[i_f-1]) + v_sol[1,i_f-1] #gauche
    t_d = v_sol[0,i_f]*(v_r - v_r[i_f]) + v_sol[1,i_f] #droite

    #Delta prime
    deltap = v_sol[0,i_f] - v_sol[0,i_f+1]

    #Plots
    plt.figure() #Dpsi
    plt.axvline(x=rs, color = 'black',linestyle = ':')
    plt.plot(v_r,v_sol[0,:len(v_r)])
    plt.title(str(deltap) )

    plt.figure() #Psi
    plt.axvline(x=rs, color = 'black',linestyle = ':')
    plt.plot(v_r[i_f-30:i_f+1],t_g[i_f-30:i_f+1],'g--')
    plt.plot(v_r[i_b:i_b+30],t_d[i_b:i_b+30],'g--')
    plt.plot(v_r, v_sol[1,:len(v_r)])
    plt.title(str(deltap) )

    return (v_sol, i_f, deltap, v_r) #i_f = indice du premier point de la solution backward
```

```python
solve_tear(profil)
```

```python
def f(x):
    k =-( m**2/v_r - mu0*qs*v_q*R0*v_djeq/B0*(v_q - qs))
    return psis*(k*np.log(abs(x))+k+k**2*x*np.log(abs(x))+1/2*k**2*x- 3*k**2*x/2)

def g(x):
    k = -( m**2/v_r - mu0*qs*v_q*R0*v_djeq/B0*(v_q - qs))
    return 1+k*x+3*k**2*x**3/12
```

```python
x = v_r - rs
```

```python
f(x)
```

```python

```


## Predator-Prey Population Differential Equations

In this notebook we will a simple population equation [1]

```python
## Library
import numpy as np
import math
import matplotlib.pyplot as plt
```

## Predators and prey
The cyclical relationship of Predators and Prey in the wild  means that when there is an abundance of prey, the predators will eat more and their population will increase. On the other hand, if there is a shortage in the number of prey, the number of predators will decrease. One of the of the most famous examples of this was observed by the fur traders  Hudson's Bay Company of Canada. The company collected data about the number of lynx and hare pelts they purchased over a 90 year period. Their data showed the cylical nature of the predator prey relationship (Figure below).

 Figure 1: The Lynx-Hare Cycle  [1].
This kind of behaviour has been observed in many pairs of predators and prey from mammals to fish to beyond.

## Snow Hares and Lynxes
In a balanced eco system the relationship between snow hares (prey) and lynxes (predator) can be described mathematically using a Loketta-Volterra model [2-4]
\begin{equation}
\begin{array}{cl}
\frac{d H}{dt}=a_{Birth} H-b_{Con}LH,\\
\frac{d L}{dt}=-c_{Death}L+d_{Food} L H,\\
\end{array}
\end{equation}
this set of equations models the interaction between snow hares (H) and lynxes (L). The lynes are the predators who eat the snow hares to survive. Where there parameters
* $a_{Birth}$ is the birth rate of the snow hares;
* $b_{Con}$ is the consumation rate of snow hares by lynxes (interaction);
* $c_{Death}$ is the death rate of lynxes if there is no food source;
* $d_{Food}$ is the rate that lynxes eat the snow hares to survive (interaction).

```python
a_Birth=4.0
b_Con=0.1
c_Death=0.9
d_Food=0.01

```

## Discrete Domain
To numiercally approximate the solution we discretise the continuous domain using a step size, $h=1$,
which gives
\begin{equation}
t_i=0+i h,
\end{equation}
for $i=0,...10$.
The figure below illustrate the discrete domain.

```python
h=0.001
t=np.arange(1850,1925+h/2,h)
fig = plt.figure(1,figsize=(5,4))
ax = fig.add_subplot(1,1,1)
ax.plot(t,0*t,'o:',color='k')
ax.set_xlabel('Time (yrs)')
plt.title('Discrete Time Domain')
plt.show()
```

```python
N=len(t) # Number of Steps
H=np.zeros(N) # Numerical Solution Hares
H[0]=24
L=np.zeros(N) # Numerical Solution Lynxes
L[0]=6

```

We numerically approximate the differential equation using Eulers method to give the difference equation
\begin{equation}
\text{ INSERT DIFFERENCE EQUATION HERE,}
\end{equation}
where $h$ is the stepsize and where $P_i$ is the numerical approximation of $p(t_i)$ at distance $t_i$, for $i=0,...,N$

```python
for i in range (1,N):
    H[i]=H[i-1]+# INSERT EQUATION HERE
    L[i]=L[i-1]+# INSERT EQUATION HERE

```

## Results
Figure 2 shows a simulation of the populations of salmon and small fish from 2000 to 2100, assuming that nothing is added to enviroment.  The salmon population fluxuates between 45 and 55 billion while the small fish population fluctuates from 25 to 85 billion. The equation illustates a relative equilibrium between the two populations ensuring neither get too big or too small.

```python
## Plotting Figure
fig = plt.figure(1,figsize=(10,4))

ax = fig.add_subplot(1,1,1)

ax.plot(t,H,':',color='green',label='Hares')
ax.plot(t,L,':',color='red',label='Lynxes')

ax.legend()
ax.set_xlabel('Time (yrs)')
ax.set_ylabel('Population (tens of thousands)')
#ax.legend(loc='best')
ax.set_title('Predator Prey Relationship')
plt.tight_layout()
plt.subplots_adjust(top=0.85)
plt.show()

```

## Phase Plane Plot

```python
## Plotting Figure
fig = plt.figure(1,figsize=(5,5))

ax = fig.add_subplot(1,1,1)

ax.plot(L,H,'-',color='black')

ax.set_xlabel('Population (tens of thousands)')
ax.set_ylabel('Population (tens of thousands)')
#ax.legend(loc='best')
ax.set_title('Predator Prey Relationship')
plt.tight_layout()
plt.subplots_adjust(top=0.85)
plt.xlim(0,900)
plt.ylim(0,900)

plt.show()
```

# References

[1] Stover, Christopher and Weisstein, Eric W. "Population Growth." From MathWorld--A Wolfram Web Resource. http://mathworld.wolfram.com/PopulationGrowth.html

```python

```


# Introduction

This Notebook gives a quick overview of the capabilities of the visiualisation module.

- Easy drawing of plain geometric primitives (rectangles, circles)
- Easy access to interactions (useful for kinematic visualisation)
- Easy access to animation generation (time-dependent functions and configuration space)

```python
%load_ext ipydex.displaytools
import sympy as sp
from sympy import sin, cos
import numpy as np
import scipy.integrate
import symbtools as st
import symbtools.modeltools as mt
import matplotlib.pyplot as plt
import symbtools.visualisation as vt
sp.init_printing()

import importlib
```

```python
theta1, theta2 = theta = st.symb_vector('theta1:3')
dtheta1, dtheta2 = dtheta = st.time_deriv(theta, theta)
tau = sp.symbols('tau')
params = st.symb_vector('m1 l1 J1 mu1 m2 r2 J2 mu2')
st.make_global(params)
```

```python
p0 = sp.Matrix([0, 0])
p1 = sp.Matrix([l1/2*cos(theta1), l1/2*sin(theta1)])
p2 = sp.Matrix([l1*cos(theta1), l1*sin(theta1)])
p3 = p2 + sp.Matrix([r2*cos(theta1+theta2), r2*sin(theta1+theta2)])
dp1 = st.time_deriv(p1, theta)
dp2 = st.time_deriv(p2, theta)
```

```python
T_rot = J1 * dtheta1**2 / 2 + J2 * (dtheta1 + dtheta2)**2 / 2
T_trans = (m1 * dp1.T * dp1 / 2 + m2 * dp2.T * dp2 / 2)[0]
T = T_rot + T_trans
V = p2[1]*9.81*m2 + p1[1]*9.81*m1
friction1 = dtheta1 * mu1
friction2 = dtheta2 * mu2

model = mt.generate_symbolic_model(T, V, theta, [0 - friction1, tau - friction2])
model.tau = [tau]
model.calc_state_eq()

f = model.f
g = model.g
```

```python
m1_val = 0.1
m2_val = 1
l1_val = 0.5
r2_val = 0.2
J1_val = 1/12 * m1_val * l1_val**2
J2_val = 1/2 * m2_val * r2_val**2*5
mu1_val = 0.01
mu2_val = 0.001

param_subs = st.lzip(params, [m1_val, l1_val, J1_val, mu1_val, m2_val, r2_val, J2_val, mu2_val])

f_fun = st.expr_to_func(model.x, f.subs(param_subs))
g_fun = st.expr_to_func(model.x, g.subs(param_subs))

x_init = np.array([-np.pi/2, 0, 0, 0])
tf = 5.0
dt = 1/30
ts = np.arange(0.0, tf, dt)
samples = len(ts)
ys = np.empty((samples, len(model.x)))
ys[0, :] = x_init

T_pulse = 1

# input trajectory
def tau_fun(t, x):

    if t<T_pulse:
        return np.sin(2*np.pi*t/T_pulse)*4
    else:
        return 0

# right hand side of the ode
def ode_fun(t, x):
    return f_fun(x[0], x[1], x[2], x[3]) + tau_fun(t, x) * g_fun(x[0], x[1], x[2], x[3])

# get state trajectory
for i in range(samples - 1):
    result = scipy.integrate.solve_ivp(ode_fun, (ts[i], ts[i+1]), ys[i, :])
    ys[i+1, :] = result.y[:, -1]

# get input trajectory after the fact
us = np.empty((samples, 1))
for i in range(samples):
    us[i, 0] = tau_fun(ts[i], ys[i, :])
```

```python
importlib.reload(vt)
```

    <module 'symbtools.visualisation' from '/media/workcard/workstickdir/projekte/rst_python/symbtools-TUD-RST-Account/symbtools/visualisation.py'>

```python
vis = vt.Visualiser(theta, xlim=(-1, 1), ylim=(-1, 1))
vis.add_linkage([p0.subs(param_subs), p2.subs(param_subs)], color="#1f77b4")
vis.add_disk([p2.subs(param_subs), p3.subs(param_subs)], color="#ff7f0e", lw=2)
```

```python
vis.plot([0.0, 0.0])
```

```python
vis.interact(theta2=(-3.14, 3.14, 0.01))
```

    interactive(children=(FloatSlider(value=0.0, description='theta1', max=5.0, min=-5.0), FloatSlider(value=0.0, …

```python
simanim = vt.SimAnimation(model.x, ts, ys)
simanim.add_visualiser(vis)
simanim.display_frame()
```

```python
simanim = vt.SimAnimation(model.x, ts, ys, figsize=(8, 4))
simanim.add_visualiser(vis, 122)
simanim.add_graph(theta, 121)
simanim.display_frame()
```

```python
from matplotlib.gridspec import GridSpec

gs = GridSpec(4, 3, hspace=0.3)

simanim = vt.SimAnimation(model.x, ts, ys, start_pause=0.5, end_pause=0.5, figsize=(12, 10))
simanim.add_visualiser(vis, gs[:, 1:])
simanim.add_graph(theta, gs[0, 0], ax_kwargs=dict(title='Angles')) # most common options can probably be exposed easier
simanim.add_graph([dtheta1, dtheta2], gs[1, 0], ax_kwargs=dict(title='Velocities'))
simanim.add_graph(p3.subs(param_subs), gs[2, 0], ax_kwargs=dict(title='x-y Position'), plot_kwargs=dict(ls='--'))
simanim.add_graph(us[:, 0], gs[3, 0], ax_kwargs=dict(title='Input torque'))
simanim.display_frame()  # render just one frame to check the layout
```

```python
# simanim.display()  # show the whole animation (increases filesize of jupyter notebook)
```

```python
# this probably needs to be adapted on other systems
# (anaconda version of ffmpeg might lead to an error)

plt.rcParams['animation.ffmpeg_path'] = '/usr/bin/ffmpeg'

fname = 'demo_animation.mp4'
simanim.save(fname, dpi=50)  # save to file
```

```python
vt.display_video_file(fname)
```

## Create an onion plot (experimental)

```python
indices1 = np.linspace(0, 15, 5, dtype=int)
frame_states1 = ys[indices1, :2]

indices2 = np.linspace(18, 38, 10, dtype=int)
frame_states2 = ys[indices2, :2]
```

```python
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 5));
ax1.axis("equal")
ax1.set_xlim(-1, 1)
ax2.axis("equal")
ax2.set_xlim(-1, 1)

vis.plot_onion_skinned(frame_states1, axes=ax1, change_alpha=True, max_lightness=0.75)
vis.plot_onion_skinned(frame_states2, axes=ax2, change_alpha=True, max_lightness=0.75)
```


# ***Introduction to Radar Using Python and MATLAB***
## Andy Harrison - Copyright (C) 2019 Artech House

# Ovals of Cassini
***

Referring to Figure 4.8, The Cassini ovals are a family of quartic curves, sometimes referred to as Cassini ellipses, described by a point such that the product of its distances from two fixed points a distance apart is a constant [8]. For bistatic systems, the system performance may be analyzed by plotting the Cassini ovals for various signal-to-noise ratios.  The Cassini ovals are governed by (Equation 4.65)

\begin{equation}\label{eq:bistatic_range_product_polar}
    (r_t\, r_r)^2 = \Big(\rho^2 + (D/2)^2\Big)^2 - \rho^2\, D^2\, \cos^2\theta
\end{equation}
***

Begin by getting the library path

```python
import lib_path
```

Set the separation distance, ***D***, between the transmitting and receiving radars (m)

```python
separation_distance = 10e3
```

Set the system temperature (K), bandwidth (Hz), noise_figure (dB), transmitting and receiving losses (dB), peak transmit power (W), transmitting and receiving antenna gain (dB), operating frequency (Hz), and bistatic target RCS (dBsm)

```python
system_temperature = 290

bandwidth = 10e6

noise_figure = 3

transmit_losses = 4

receive_losses = 6

peak_power = 100e3

transmit_antenna_gain = 30

receive_antenna_gain = 28

frequency = 1e9

bistatic_target_rcs = 10
```

Set the number of points for plotting the Cassini ovals

```python
number_of_points = 100000
```

Set the parameters for the Cassini ovals equation
$$ r ^ 4 + a ^ 4 - 2 a ^ 2 r ^ 2(1 + cos(2 \theta)) = b ^ 4 $$

Import the `linspace` and `log10` routines along with some constants from `scipy` for the angle sweep

```python
from numpy import linspace, log10
from scipy.constants import pi, c, k
```

```python
# Parameter "a"

a = 0.5 * separation_distance

# Full angle sweep

t = linspace(0, 2.0 * pi, number_of_points)
```

Calculate the bistatic range factor and use this along with the separation distance to calculate SNR0 (where the factors ***a*** and ***b*** are equal)

```python
# Calculate the wavelength (m)

wavelength = c / frequency

# Calculate the bistatic radar range factor

bistatic_range_factor = (peak_power * transmit_antenna_gain * receive_antenna_gain * wavelength ** 2 * 10.0 ** (bistatic_target_rcs / 10.0)) / ((4.0 * pi) ** 3 * k * system_temperature * bandwidth * 10.0 ** (noise_figure / 10.0) * transmit_losses * receive_losses)

# Calculate the signal to noise ratio at which a = b

SNR_0 = 10.0 * log10(16.0 * bistatic_range_factor / separation_distance ** 4)
```

Create a list of the signal to noise ratios to plot

```python
SNR = [SNR_0 - 6, SNR_0 - 3, SNR_0, SNR_0 + 3]
```

Import the `matplotlib` routines for plotting the Cassini ovals

```python
from matplotlib import pyplot as plt
```

Import `sqrt`, `sin`, `cos`, `real`, and `imag` from `scipy` for plotting the Cassini ovals

```python
from numpy import sqrt, sin, cos, real, imag
```

Display the resulting Cassini ovals

```python
# Set the figure size

plt.rcParams["figure.figsize"] = (15, 10)

# Loop over all the desired signal to noise ratios

for s in SNR:

    # Convert to linear units

    snr = 10.0 ** (s / 10.0)

    # Parameter for Cassini ovals

    b = (bistatic_range_factor / snr) ** 0.25

    if a > b:

         # Calculate the +/- curves

         r1 = sqrt(a ** 2 * (cos(2.0 * t) + sqrt(cos(2 * t) ** 2 - 1.0 + (b / a) ** 4)))

         r2 = sqrt(a ** 2 * (cos(2.0 * t) - sqrt(cos(2 * t) ** 2 - 1.0 + (b / a) ** 4)))

         # Find the correct indices for imaginary parts = 0

         i1 = imag(r1) == 0

         i2 = imag(r2) == 0

         r1 = real(r1)

         r2 = real(r2)

         # Plot both parts of the curve

         label_text = "SNR = {:.1f}".format(s)

         plt.plot(r1[i1] * cos(t[i1]), r1[i1] * sin(t[i1]), 'k.', label=label_text)

         plt.plot(r2[i2] * cos(t[i2]), r2[i2] * sin(t[i2]), 'k.')

    else:

         # Calculate the range for the continuous curves

         r = sqrt(a ** 2 * cos(2 * t) + sqrt(b ** 4 - a ** 4 * sin(2.0 * t) ** 2))

         # Plot the continuous parts

         label_text = "SNR = {:.1f}".format(s)

         plt.plot(r * cos(t), r * sin(t), '.', label=label_text)

    # Add the text for Tx/Rx locations

    plt.text(-a, 0, 'Tx')

    plt.text(a, 0, 'Rx')

    # Set the plot title and labels

    plt.title('Ovals of Cassini', size=14)

    plt.xlabel('Range (km)', size=12)

    plt.ylabel('Range (km)', size=12)

    # Set the tick label size

    plt.tick_params(labelsize=12)

    # Turn on the grid

    plt.grid(linestyle=':', linewidth=0.5)

    # Add the legend

    plt.legend(loc='upper left', prop={'size': 10})
```


```python
import os, sys
import h5py
import numpy as np
from scipy.io import loadmat
import cv2
import matplotlib
%matplotlib inline
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from numpy import matrix as mat
from sympy import *
from numpy import linalg as la
```

```python
def getFx(para, frame):   # para中为(11*frame+1)的一套参数(Rt+bc)，frame传进来是为了确定循环次数
    #先写出参数表达式，ABDCDEF六个点的齐次坐标
    K = Matrix([[1149.67569986785, 0.0, 508.848621645943],
                     [0.0, 1147.59161666764, 508.064917088557],
                     [0.0, 0.0, 1.0]])
    r11, r12, r13, r14, r21, r22, r23, r24, r31, r32, r33 = symbols('r11 r12 r13 r14 r21 r22 r23 r24 r31 r32 r33')
    Rt = Matrix([[r11, r12, r13, r14], [r21, r22, r23, r24], [r31, r32, r33, 1]])
    b, c = symbols('b c')
    ub, vb, wb, ud, vd, wd, ue, ve, we, uf, vf, wf = symbols('ub vb wb ud vd wd ue ve we uf vf wf')
    f = Symbol('f')
    #XA = Matrix([[-a * c * cos(th) * cos(al)], [c-a * c * sin(th)], [-a * c *  cos(th) * sin(al)], [1]])
    XB = Matrix([[0], [c], [0], [1]])
    #XC = Matrix([[a * c * cos(th) * cos(al)], [c+a * c * sin(th)], [a * c * cos(th) * sin(al)], [1]])
    XD = Matrix([[-b * c], [0], [0], [1]])
    XE = Matrix([[0], [0], [0], [1]])
    XF = Matrix([[b * c], [0], [0], [1]])
    #ua, va, wa = K[0,:] * (Rt * XA), K[1,:] * (Rt * XA), K[2,:] * (Rt * XA)
    ub, vb, wb = K[0,:] * (Rt * XB), K[1,:] * (Rt * XB), K[2,:] * (Rt * XB)
    #uc, vc, wc = K[0,:] * (Rt * XC), K[1,:] * (Rt * XC), K[2,:] * (Rt * XC)
    ud, vd, wd = K[0,:] * (Rt * XD), K[1,:] * (Rt * XD), K[2,:] * (Rt * XD)
    ue, ve, we = K[0,:] * (Rt * XE), K[1,:] * (Rt * XE), K[2,:] * (Rt * XE)
    uf, vf, wf = K[0,:] * (Rt * XF), K[1,:] * (Rt * XF), K[2,:] * (Rt * XF)
    #根据每一帧的循环，提取出Rt的参数，K是公用的，代入参数写出3D坐标，并计算出u/w，v/w
    #写成f的形式，即按照六个点分块，每块里面有M帧
    getfx = mat(np.zeros((4*frame*2,1)))
    for i in range(4):
        for j in range(frame):
            if i == 0 :
                f = Matrix([ub/wb, vb/wb])
            elif i == 1 :
                f = Matrix([ud/wd, vd/wd])
            elif i == 2 :
                f = Matrix([ue/we, ve/we])
            else:
                f = Matrix([uf/wf, vf/wf])
            f_value = f.subs({r11:para[11*j], r12:para[11*j+1], r13:para[11*j+2], r14:para[11*j+3],
                        r21:para[11*j+4], r22:para[11*j+5], r23:para[11*j+6], r24:para[11*j+7],
                        r31:para[11*j+8], r32:para[11*j+9], r33:para[11*j+10], b:para[-1], c:-0.095})
            getfx[i*frame*2+j*2] = f_value[0]
            getfx[i*frame*2+j*2+1] = f_value[1]
    #返回getfx值，2*frame*6 by 1
    return getfx
```

```python
def getJacobian(point, frame, para):
    # 用参数表示K，R矩阵
    focalx, focaly, px, py = symbols('focalx focaly px py')
    r11, r12, r13, r14, r21, r22, r23, r24, r31, r32, r33 = symbols('r11 r12 r13 r14 r21 r22 r23 r24 r31 r32 r33')
    Rt = Matrix([[r11, r12, r13, r14], [r21, r22, r23, r24], [r31, r32, r33, 1]])
    K = Matrix([[focalx, 0, px], [0, focaly, py], [0, 0, 1]])
    # KRt = K * Rt
    # 用参数表示ABCDEF六个点坐标
    b, c = symbols('b c')
    ub, vb, wb, ud, vd, wd, ue, ve, we, uf, vf, wf = symbols('ub vb wb ud vd wd ue ve we uf vf wf')
    f = Symbol('f')
    if point == 0 :
        XB = Matrix([[0], [c], [0], [1]])
        ub, vb, wb = K[0,:] * (Rt * XB), K[1,:] * (Rt * XB), K[2,:] * (Rt * XB)
        f = Matrix([ub/wb, vb/wb])
    elif point == 1 :
        XD = Matrix([[-b * c], [0], [0], [1]])
        ud, vd, wd = K[0,:] * (Rt * XD), K[1,:] * (Rt * XD), K[2,:] * (Rt * XD)
        f = Matrix([ud/wd, vd/wd])
    elif point == 2 :
        XE = Matrix([[0], [0], [0], [1]])
        ue, ve, we = K[0,:] * (Rt * XE), K[1,:] * (Rt * XE), K[2,:] * (Rt * XE)
        f = Matrix([ue/we, ve/we])
    elif point == 3:
        XF = Matrix([[b * c], [0], [0], [1]])
        uf, vf, wf = K[0,:] * (Rt * XF), K[1,:] * (Rt * XF), K[2,:] * (Rt * XF)
        f = Matrix([uf/wf, vf/wf])
    args = Matrix([r11, r12, r13, r14, r21, r22, r23, r24, r31, r32, r33, b])
    f_X1 = f[0,:].jacobian(args)
    f_X2 = f[1,:].jacobian(args)
    JA = Matrix([f_X1, f_X2])   # 2 by 16 matrix
    JA_value = JA.subs({focalx:1149.676, focaly:1147.592, px:508.849, py:508.065, r11:para[11*frame], r12:para[11*frame+1],
                                 r13:para[11*frame+2], r14:para[11*frame+3], r21:para[11*frame+4], r22:para[11*frame+5],
                                 r23:para[11*frame+6], r24:para[11*frame+7], r31:para[11*frame+8], r32:para[11*frame+9],
                                 r33:para[11*frame+10], b:para[-1], c:-0.095})
    #JA_value = JA_value.subs({f:1149.68})
    return JA_value
```

```python
def getJ(para, frame):
    getj = mat(np.zeros((4*frame*2, 11*frame+1)))
    for m in range(4):
        for n in range(frame):
            JA_value = getJacobian(m, n, para)
            #print(JA_value)
            getj[2*(m*frame+n):2*(m*frame+n+1), 11*n:11*n+11] = JA_value[:, 0:11]
            getj[2*(m*frame+n):2*(m*frame+n+1), -1:] = JA_value[:, -1:]
    return getj
```

```python
def getE(getfx, frame, k, xkp):
    E = mat(np.zeros((4*frame*2,1)))
    for i in range(4):
        for j in range(frame):
            if i==0 :
                E[(i*frame+j)*2] = getfx[i*frame*2+j*2] - xkp[i*3+k,0]
                E[(i*frame+j)*2+1] = getfx[i*frame*2+j*2+1] - xkp[i*3+k, 1]
            elif i==1 :
                E[(i*frame+j)*2] = getfx[i*frame*2+j*2] - xkp[i*3+k,0]
                E[(i*frame+j)*2+1] = getfx[i*frame*2+j*2+1] - xkp[i*3+k, 1]
            elif i==2 :
                E[(i*frame+j)*2] = getfx[i*frame*2+j*2] - xkp[i*3+k,0]
                E[(i*frame+j)*2+1] = getfx[i*frame*2+j*2+1] - xkp[i*3+k, 1]
            elif i==3 :
                E[(i*frame+j)*2] = getfx[i*frame*2+j*2] - xkp[i*3+k,0]
                E[(i*frame+j)*2+1] = getfx[i*frame*2+j*2+1] - xkp[i*3+k, 1]
    return E
```

```python
def LM_opti(frame, ith, x2d, x_para, u=1, v=2, step_max=500):
    J = mat(np.zeros((4*frame*2, 11*frame+1)))
    E = mat(np.zeros((4*frame*2,1)))   # E = f(X) - b ;
    E_temp = mat(np.zeros((4*frame*2,1)))   # E_temp compare with E in L-M
    x_k = mat(x_para.copy())   #parameter initialization
    step = 0   # iteration steps
    mse_last = 0   # mse value after iteration each time

    # L-M Algorithm obtain optimal parameters
    while(step < step_max):
        step += 1
        mse, mse_temp = 0, 0

        # generate Jacobian Matrix and calculate E
        getfx = mat(np.zeros((4*frame*2,1)))
        getfx = getFx(x_k, frame)
        E = getE(getfx, frame, ith, x2d)   #单帧模式，第几帧，GT

        for i in range(4*frame*2):
            mse += E[i]**2
        mse /= 4*frame*2

        # get new J
        J = mat(np.zeros((4*frame*2, 11*frame+1)))
        J = getJ(x_k, frame)

        # delta X = ...
        #print(J.T * J)
        dx = mat(np.zeros((11*frame+1,1)))
        LM = u * mat(np.eye(11*frame+1))
        dx = -(J.T * J + LM).I * J.T * E
        x_k_temp = x_k.copy()
        x_k_temp += dx

        #R的更新不能简单赋值
        #get R meet R.T*R=I
        #U * D * V.T = R --> R+ = U * V.T
        '''
        R_old = mat([[x_k_temp[0,0], x_k_temp[1,0], x_k_temp[2,0]],
                     [x_k_temp[4,0], x_k_temp[5,0], x_k_temp[6,0]],
                     [x_k_temp[8,0], x_k_temp[9,0], x_k_temp[10,0]]])
        U, sigma, VT = la.svd(R_old)
        R_new = U * VT
        x_k_temp[0,0], x_k_temp[1,0], x_k_temp[2,0] = R_new[0,0], R_new[0,1], R_new[0,2]
        x_k_temp[4,0], x_k_temp[5,0], x_k_temp[6,0] = R_new[1,0], R_new[1,1], R_new[1,2]
        x_k_temp[8,0], x_k_temp[9,0], x_k_temp[10,0] = R_new[2,0], R_new[2,1], R_new[2,2]
        '''
        ###########

        # calculate E_temp with x_k_temp
        # copy from E with x_k
        getfx_temp = mat(np.zeros((4*frame*2,1)))
        getfx_temp = getFx(x_k_temp, frame)
        E_temp = getE(getfx_temp, frame, ith, x2d)

        for i in range(4*frame*2):
            mse_temp += E_temp[i]**2
        mse_temp /= 4*frame*2

        # segma value to choose optimization model
        segma = (mse - mse_temp)/((dx.T * (u * dx - J.T * E))[0,0])

        # calculate new u
        if segma > 0:
            s = 1.0/3.0
            v = 2
            x_k = x_k_temp
            mse = mse_temp
            u = u * max(s, 1-pow(2*segma,3))
            u = u[0,0]
        else:
            u = u * v
            v = v * 2
            x_k = x_k_temp

        print("step = %d, abs(mse-mse_last) = %.12f" %(step, abs(mse-mse_last)))
        if abs(mse-mse_last)<0.000000001:
            break
        mse_last = mse

    print("step = ", step)
    print("mse = ", mse_last)
    #print("parameter = ", x_k)

    return x_k
```

```python
frame = 3
m = loadmat("valid.mat")

# camera intrinsic matrix
K = m["annot"][0][0][4]
K_cam = K[0][0].tolist()

# key point 3D groundtruth
gt = m["annot"][0][0][3]
img_gt = np.zeros((frame, 3, 17))
img1_gt = gt[135]   # array 3 by 17
img2_gt = gt[136]   # array 3 by 17
img3_gt = gt[137]   # array 3 by 17
img_gt[0] = img1_gt
img_gt[1] = img2_gt
img_gt[2] = img3_gt
#print(img_gt)

# key point 2D groundtruth
kp = np.zeros((frame, 17, 2))
for m in range(frame):
    for i in range(17):
        u = K_cam[0] * mat([img_gt[m][0][i], img_gt[m][1][i], img_gt[m][2][i]]).T
        v = K_cam[1] * mat([img_gt[m][0][i], img_gt[m][1][i], img_gt[m][2][i]]).T
        w = K_cam[2] * mat([img_gt[m][0][i], img_gt[m][1][i], img_gt[m][2][i]]).T
        kp[m][i][0] = u/w
        kp[m][i][1] = v/w
#print(kp)

# show image
img1 = cv2.imread("S9_Posing_1.55011271_000676.jpg")
img2 = cv2.imread("S9_Posing_1.55011271_000681.jpg")
img3 = cv2.imread("S9_Posing_1.55011271_000686.jpg")
plt.figure(figsize = (15, 45)) # 图像窗口名称
plt.subplot(131)
plt.imshow(img1[:,:,[2,1,0]])
plt.subplot(132)
plt.imshow(img2[:,:,[2,1,0]])
plt.subplot(133)
plt.imshow(img3[:,:,[2,1,0]])
plt.axis('on') # 关掉坐标轴为 off
plt.title('image') # 图像题目
plt.show()

# visualize key points
"""
txt = ['1','2','3','4','5','6','7','8','9','10','11','12','13','14','15','16','17']
plt.figure()
plt.subplot(131)
img_kp = plt.scatter(kp[0][:,0], kp[0][:,1], s = 80, c = 'g', marker = 'X')
for i in range(17):
    plt.annotate(txt[i], xy = (kp[0][i,0], kp[0][i,1]), xytext = (kp[0][i,0]+0.1, kp[0][i,1]+0.1))
plt.subplot(132)
img_kp = plt.scatter(kp[1][:,0], kp[1][:,1], s = 80, c = 'g', marker = 'X')
for i in range(17):
    plt.annotate(txt[i], xy = (kp[1][i,0], kp[1][i,1]), xytext = (kp[1][i,0]+0.1, kp[1][i,1]+0.1))
plt.subplot(133)
img_kp = plt.scatter(kp[2][:,0], kp[2][:,1], s = 80, c = 'g', marker = 'X')
for i in range(17):
    plt.annotate(txt[i], xy = (kp[2][i,0], kp[2][i,1]), xytext = (kp[2][i,0]+0.1, kp[2][i,1]+0.1)) # 这里xy是需要标记的坐标，xytext是对应的标签坐标
plt.axis('on') # 关掉坐标轴为 off
plt.title('keypoint of image') # 图像题目
"""

# visualize torso joints A-F
plt.figure(figsize = (5,15))
plt.subplot(311)
for i in {0,1,4,8}:
    img_kp = plt.scatter(kp[0][i,0], kp[0][i,1], s = 80, c = 'g', marker = 'X')
plt.subplot(312)
for i in {0,1,4,8}:
    img_kp = plt.scatter(kp[1][i,0], kp[1][i,1], s = 80, c = 'g', marker = 'X')
plt.subplot(313)
for i in {0,1,4,8}:
    img_kp = plt.scatter(kp[2][i,0], kp[2][i,1], s = 80, c = 'g', marker = 'X')
plt.axis('on') # 关掉坐标轴为 off
plt.title('torso keypoint of image') # 图像题目
plt.show()

# save 2D keypoint coordinate
x2d = np.zeros((4 * frame,2))   #[AAABBBCCCDDDEEEFFF].T
for i in range(4):
    for j in range(frame):
        if i==0 :
            x2d[i*frame+j, :] = kp[j][8, :]
        elif i==1 :
            x2d[i*frame+j, :] = kp[j][4, :]
        elif i==2 :
            x2d[i*frame+j, :] = kp[j][0, :]
        elif i==3 :
            x2d[i*frame+j, :] = kp[j][1, :]
print(x2d)
```

```python
# parameter initialization for first frame
x_para = np.zeros((11+1,1))
x_para[0] = -1   # r11
x_para[5] = 1   # r22
x_para[10] = -1   # r33
x_para[3] = 0.0047   #tx(cal)
x_para[7] = -0.0997   #ty(cal)
x_para[-1] = 0.25   # b
#x_para[-1] = -0.095  # c(cal)
getfx_ini = getFx(x_para, 1)
print(getfx_ini)
```

    [[514.25209744]
     [284.62882932]
     [486.94729956]
     [393.65003291]
     [514.25209744]
     [393.65003291]
     [541.55689531]
     [393.65003291]]

```python
# visualize first frame after initilization
plt.figure()
img_kp = plt.scatter(kp[0][8,0], kp[0][8,1], s = 80, c = 'g', marker = 'X')
img_kp = plt.scatter(getfx_ini[0,0], getfx_ini[1,0], s = 80, c = 'r', marker = 'X')
img_kp = plt.scatter(kp[0][4,0], kp[0][4,1], s = 80, c = 'g', marker = 'X')
img_kp = plt.scatter(getfx_ini[2,0], getfx_ini[3,0], s = 80, c = 'r', marker = 'X')
img_kp = plt.scatter(kp[0][0,0], kp[0][0,1], s = 80, c = 'g', marker = 'X')
img_kp = plt.scatter(getfx_ini[4,0], getfx_ini[5,0], s = 80, c = 'r', marker = 'X')
img_kp = plt.scatter(kp[0][1,0], kp[0][1,1], s = 80, c = 'g', marker = 'X')
img_kp = plt.scatter(getfx_ini[6,0], getfx_ini[7,0], s = 80, c = 'r', marker = 'X')
plt.axis('on') # 关掉坐标轴为 off
plt.title('image_kp_ABCDEF_original_opt') # 图像题目
plt.show()
```

```python
# optimization for each frame
X_para = np.zeros((11*frame+1,1))
X_para[-1] = 0.25   # b
#X_para[-1] = -0.095  # c(cal)
para_data = np.zeros((frame,11+1))
x_k_para = x_para.copy()
for i in range(frame):
    x_k = LM_opti(1, i, x2d, x_k_para)   #单帧优化，第几帧，GT，初始化参数

    # R matrix adjustment

    R_old = mat([[x_k[0,0], x_k[1,0], x_k[2,0]],
             [x_k[4,0], x_k[5,0], x_k[6,0]],
             [x_k[8,0], x_k[9,0], x_k[10,0]]])
    U, sigma, VT = la.svd(R_old)
    R_new = U * VT
    x_k[0,0], x_k[1,0], x_k[2,0] = R_new[0,0], R_new[0,1], R_new[0,2]
    x_k[4,0], x_k[5,0], x_k[6,0] = R_new[1,0], R_new[1,1], R_new[1,2]
    x_k[8,0], x_k[9,0], x_k[10,0] = R_new[2,0], R_new[2,1], R_new[2,2]

    print()
    print("For the No.", i+1, " frame : x_k = ", x_k)
    para_data[i] = mat(x_k).T
    # for next frame initilization
    x_k_para = x_k
    X_para[11*i:11*i+11] = x_k[0:11,0]
print(para_data)
print(X_para)
```

    step = 1, abs(mse-mse_last) = 0.008731445448
    step = 2, abs(mse-mse_last) = 0.008695778688
    step = 3, abs(mse-mse_last) = 0.000000019889
    step = 4, abs(mse-mse_last) = 0.000000010621
    step = 5, abs(mse-mse_last) = 0.000000006870
    step = 6, abs(mse-mse_last) = 0.000000004416
    step = 7, abs(mse-mse_last) = 0.000000002820
    step = 8, abs(mse-mse_last) = 0.000000001789
    step = 9, abs(mse-mse_last) = 0.000000001127
    step = 10, abs(mse-mse_last) = 0.000000000706
    step =  10
    mse =  [[3.56192279e-05]]

    For the No. 1  frame : x_k =  [[-0.99784371]
     [-0.06550969]
     [ 0.00404998]
     [ 0.00647636]
     [-0.06552933]
     [ 0.99783849]
     [-0.00492444]
     [-0.09969072]
     [-0.00371862]
     [-0.00517922]
     [-0.99997967]
     [ 0.2289857 ]]
    step = 1, abs(mse-mse_last) = 0.000260490626
    step = 2, abs(mse-mse_last) = 0.000093970383
    step = 3, abs(mse-mse_last) = 0.000045866662
    step = 4, abs(mse-mse_last) = 0.000030173750
    step = 5, abs(mse-mse_last) = 0.000019713976
    step = 6, abs(mse-mse_last) = 0.000012788374
    step = 7, abs(mse-mse_last) = 0.000008235547
    step = 8, abs(mse-mse_last) = 0.000005264767
    step = 9, abs(mse-mse_last) = 0.000003340930
    step = 10, abs(mse-mse_last) = 0.000002104543
    step = 11, abs(mse-mse_last) = 0.000001315992
    step = 12, abs(mse-mse_last) = 0.000000816875
    step = 13, abs(mse-mse_last) = 0.000000503343
    step = 14, abs(mse-mse_last) = 0.000000307873
    step = 15, abs(mse-mse_last) = 0.000000186925
    step = 16, abs(mse-mse_last) = 0.000000112652
    step = 17, abs(mse-mse_last) = 0.000000067386
    step = 18, abs(mse-mse_last) = 0.000000040006
    step = 19, abs(mse-mse_last) = 0.000000023572
    step = 20, abs(mse-mse_last) = 0.000000013783
    step = 21, abs(mse-mse_last) = 0.000000007997
    step = 22, abs(mse-mse_last) = 0.000000004604
    step = 23, abs(mse-mse_last) = 0.000000002630
    step = 24, abs(mse-mse_last) = 0.000000001490
    step = 25, abs(mse-mse_last) = 0.000000000838
    step =  25
    mse =  [[3.56265657e-05]]

    For the No. 2  frame : x_k =  [[-9.97137295e-01]
     [-5.12529517e-02]
     [ 5.55909110e-02]
     [ 2.79336071e-04]
     [-5.17774281e-02]
     [ 9.98626327e-01]
     [-8.03472673e-03]
     [-9.97724193e-02]
     [-5.51027438e-02]
     [-1.08900801e-02]
     [-9.98421301e-01]
     [ 2.28266735e-01]]
    step = 1, abs(mse-mse_last) = 0.001395246373
    step = 2, abs(mse-mse_last) = 0.000663140758
    step = 3, abs(mse-mse_last) = 0.000223301079
    step = 4, abs(mse-mse_last) = 0.000155089462
    step = 5, abs(mse-mse_last) = 0.000106549543
    step = 6, abs(mse-mse_last) = 0.000072347543
    step = 7, abs(mse-mse_last) = 0.000048535661
    step = 8, abs(mse-mse_last) = 0.000032172889
    step = 9, abs(mse-mse_last) = 0.000021078613
    step = 10, abs(mse-mse_last) = 0.000013655416
    step = 11, abs(mse-mse_last) = 0.000008751659
    step = 12, abs(mse-mse_last) = 0.000005551502
    step = 13, abs(mse-mse_last) = 0.000003487095
    step = 14, abs(mse-mse_last) = 0.000002169837
    step = 15, abs(mse-mse_last) = 0.000001337987
    step = 16, abs(mse-mse_last) = 0.000000817832
    step = 17, abs(mse-mse_last) = 0.000000495637
    step = 18, abs(mse-mse_last) = 0.000000297871
    step = 19, abs(mse-mse_last) = 0.000000177548
    step = 20, abs(mse-mse_last) = 0.000000104970
    step = 21, abs(mse-mse_last) = 0.000000061561
    step = 22, abs(mse-mse_last) = 0.000000035813
    step = 23, abs(mse-mse_last) = 0.000000020667
    step = 24, abs(mse-mse_last) = 0.000000011831
    step = 25, abs(mse-mse_last) = 0.000000006718
    step = 26, abs(mse-mse_last) = 0.000000003783
    step = 27, abs(mse-mse_last) = 0.000000002113
    step = 28, abs(mse-mse_last) = 0.000000001171
    step = 29, abs(mse-mse_last) = 0.000000000643
    step =  29
    mse =  [[3.60398157e-05]]

    For the No. 3  frame : x_k =  [[-0.98335745]
     [-0.04898483]
     [ 0.17495318]
     [-0.0073453 ]
     [-0.05136947]
     [ 0.99863803]
     [-0.00912496]
     [-0.10002261]
     [-0.17426792]
     [-0.01796035]
     [-0.98453447]
     [ 0.22703675]]
    [[-9.97843714e-01 -6.55096897e-02  4.04997707e-03  6.47635868e-03
      -6.55293338e-02  9.97838492e-01 -4.92444351e-03 -9.96907233e-02
      -3.71862425e-03 -5.17921730e-03 -9.99979674e-01  2.28985702e-01]
     [-9.97137295e-01 -5.12529517e-02  5.55909110e-02  2.79336071e-04
      -5.17774281e-02  9.98626327e-01 -8.03472673e-03 -9.97724193e-02
      -5.51027438e-02 -1.08900801e-02 -9.98421301e-01  2.28266735e-01]
     [-9.83357448e-01 -4.89848269e-02  1.74953185e-01 -7.34530014e-03
      -5.13694703e-02  9.98638029e-01 -9.12495633e-03 -1.00022611e-01
      -1.74267919e-01 -1.79603462e-02 -9.84534468e-01  2.27036746e-01]]
    [[-9.97843714e-01]
     [-6.55096897e-02]
     [ 4.04997707e-03]
     [ 6.47635868e-03]
     [-6.55293338e-02]
     [ 9.97838492e-01]
     [-4.92444351e-03]
     [-9.96907233e-02]
     [-3.71862425e-03]
     [-5.17921730e-03]
     [-9.99979674e-01]
     [-9.97137295e-01]
     [-5.12529517e-02]
     [ 5.55909110e-02]
     [ 2.79336071e-04]
     [-5.17774281e-02]
     [ 9.98626327e-01]
     [-8.03472673e-03]
     [-9.97724193e-02]
     [-5.51027438e-02]
     [-1.08900801e-02]
     [-9.98421301e-01]
     [-9.83357448e-01]
     [-4.89848269e-02]
     [ 1.74953185e-01]
     [-7.34530014e-03]
     [-5.13694703e-02]
     [ 9.98638029e-01]
     [-9.12495633e-03]
     [-1.00022611e-01]
     [-1.74267919e-01]
     [-1.79603462e-02]
     [-9.84534468e-01]
     [ 2.50000000e-01]]

```python
def visual2D(kp, getfx):
    plt.figure()
    img_kp = plt.scatter(kp[8,0], kp[8,1], s = 80, c = 'g', marker = 'X')
    img_kp = plt.scatter(getfx[0,0], getfx[1,0], s = 80, c = 'r', marker = 'X')
    img_kp = plt.scatter(kp[4,0], kp[4,1], s = 80, c = 'g', marker = 'X')
    img_kp = plt.scatter(getfx[2,0], getfx[3,0], s = 80, c = 'r', marker = 'X')
    img_kp = plt.scatter(kp[0,0], kp[0,1], s = 80, c = 'g', marker = 'X')
    img_kp = plt.scatter(getfx[4,0], getfx[5,0], s = 80, c = 'r', marker = 'X')
    img_kp = plt.scatter(kp[1,0], kp[1,1], s = 80, c = 'g', marker = 'X')
    img_kp = plt.scatter(getfx[6,0], getfx[7,0], s = 80, c = 'r', marker = 'X')
    plt.axis('on') # 关掉坐标轴为 off
    plt.title('image_kp_ABCDEF_original_opt') # 图像题目
    plt.show()
```

```python
visual = np.zeros((11+1,1))
for i in range(frame):
    visual = mat(para_data[i]).T
    getfx_visual = getFx(visual, 1)
    visual2D(kp[i], getfx_visual)
```

```python
def visual3D(img_gt, para):
    K = Matrix([[1149.67569986785, 0.0, 508.848621645943],
                 [0.0, 1147.59161666764, 508.064917088557],
                 [0.0, 0.0, 1.0]])
    r11, r12, r13, r14, r21, r22, r23, r24, r31, r32, r33 = symbols('r11 r12 r13 r14 r21 r22 r23 r24 r31 r32 r33')
    Rt = Matrix([[r11, r12, r13, r14], [r21, r22, r23, r24], [r31, r32, r33, 1]])
    b, c = symbols('b c')
    ub, vb, wb, ud, vd, wd, ue, ve, we, uf, vf, wf = symbols('ub vb wb ud vd wd ue ve we uf vf wf')
    #XA = Matrix([[-a * c * cos(th) * cos(al)], [c-a * c * sin(th)], [-a * c *  cos(th) * sin(al)], [1]])
    XB = Matrix([[0], [c], [0], [1]])
    #XC = Matrix([[a * c * cos(th) * cos(al)], [c+a * c * sin(th)], [a * c * cos(th) * sin(al)], [1]])
    XD = Matrix([[-b * c], [0], [0], [1]])
    XE = Matrix([[0], [0], [0], [1]])
    XF = Matrix([[b * c], [0], [0], [1]])
    #A3D = Rt * XA
    B3D = Rt * XB
    #C3D = Rt * XC
    D3D = Rt * XD
    E3D = Rt * XE
    F3D = Rt * XF
    j = 0
    Y = mat([img_gt[0][0], img_gt[1][0], img_gt[2][0]]).T
    s = Y[2,0]
    print(Y)
    B3D_value = B3D.subs({r11:para[0], r12:para[1], r13:para[2], r14:para[3],
        r21:para[4], r22:para[5], r23:para[6], r24:para[7],
        r31:para[8], r32:para[9], r33:para[10], b:para[-1], c:-0.095}) * s
    D3D_value = D3D.subs({r11:para[0], r12:para[1], r13:para[2], r14:para[3],
        r21:para[4], r22:para[5], r23:para[6], r24:para[7],
        r31:para[8], r32:para[9], r33:para[10], b:para[-1], c:-0.095}) * s
    E3D_value = E3D.subs({r11:para[0], r12:para[1], r13:para[2], r14:para[3],
        r21:para[4], r22:para[5], r23:para[6], r24:para[7],
        r31:para[8], r32:para[9], r33:para[10], b:para[-1], c:-0.095}) * s
    F3D_value = F3D.subs({r11:para[0], r12:para[1], r13:para[2], r14:para[3],
        r21:para[4], r22:para[5], r23:para[6], r24:para[7],
        r31:para[8], r32:para[9], r33:para[10], b:para[-1], c:-0.095}) * s
    print(B3D_value,D3D_value,E3D_value,F3D_value)

    #X1 = mat([img_gt[0][14], img_gt[1][14], img_gt[2][14]]).T
    X2 = mat([img_gt[0][8], img_gt[1][8], img_gt[2][8]]).T
    #X3 = mat([img_gt[0][11], img_gt[1][11], img_gt[2][11]]).T
    X4 = mat([img_gt[0][4], img_gt[1][4], img_gt[2][4]]).T
    X5 = mat([img_gt[0][0], img_gt[1][0], img_gt[2][0]]).T
    X6 = mat([img_gt[0][1], img_gt[1][1], img_gt[2][1]]).T
    print(X2, X4, X5, X6)

    # 绘制散点图
    fig = plt.figure()
    ax = Axes3D(fig)
    #ax.scatter(X1[0], X1[1], X1[2], s = 80, c = 'g', marker = 'X')
    ax.scatter(X2[0], X2[1], X2[2], s = 80, c = 'g', marker = 'X')
    #ax.scatter(X3[0], X3[1], X3[2], s = 80, c = 'g', marker = 'X')
    ax.scatter(X4[0], X4[1], X4[2], s = 80, c = 'g', marker = 'X')
    ax.scatter(X5[0], X5[1], X5[2], s = 80, c = 'g', marker = 'X')
    ax.scatter(X6[0], X6[1], X6[2], s = 80, c = 'g', marker = 'X')
    #ax.scatter(A3D_value[0], A3D_value[1], A3D_value[2], s = 80, c = 'r', marker = 'X')
    ax.scatter(B3D_value[0], B3D_value[1], B3D_value[2], s = 80, c = 'r', marker = 'X')
    #ax.scatter(C3D_value[0], C3D_value[1], C3D_value[2], s = 80, c = 'r', marker = 'X')
    ax.scatter(D3D_value[0], D3D_value[1], D3D_value[2], s = 80, c = 'r', marker = 'X')
    ax.scatter(E3D_value[0], E3D_value[1], E3D_value[2], s = 80, c = 'r', marker = 'X')
    ax.scatter(F3D_value[0], F3D_value[1], F3D_value[2], s = 80, c = 'r', marker = 'X')

    ax.set_zlabel('Z', fontdict={'size': 15, 'color': 'red'})
    ax.set_ylabel('Y', fontdict={'size': 15, 'color': 'red'})
    ax.set_xlabel('X', fontdict={'size': 15, 'color': 'red'})
    plt.show()

    # 计算3D error
    #d1 = A3D_value-X1
    #e1 = sum(map(lambda x:x**2, d1))
    d2 = B3D_value-X2
    e2 = sum(map(lambda x:x**2, d2))
    #d3 = C3D_value-X3
    #e3 = sum(map(lambda x:x**2, d3))
    d4 = D3D_value-X4
    e4 = sum(map(lambda x:x**2, d4))
    d5 = E3D_value-X5
    e5 = sum(map(lambda x:x**2, d5))
    d6 = F3D_value-X6
    e6 = sum(map(lambda x:x**2, d6))
    error = (e2**(0.5)+e4**(0.5)+e5**(0.5)+e6**(0.5))/4
    print(error)
    print(d2,d4,d5,d6)
```

```python
visual3d = np.zeros((13+3,1))
for i in range(frame):
    visual3d = mat(para_data[i]).T
    visual3D(img_gt[i], visual3d)
```

```python

```

annot/BA and LM --- mF6P (m=3) two steps#1-1.ipynb


# The Hessian
### This tutorial covers the guessing and transformations of the Hessian in geometry optimizations.

Newton-Raphson and Newton-Raphson-like optimization methods require an energy second derivative (called the "Hessian").  If internal coordinates are used, then the starting Hessian in internal coordinates is either estimated or produced by transformation of a Cartesian Hessian.  The Cartesian Hessian is computed via analytic second derivative methods, or by finite differences of gradients or energies.  To reduce computational expense, this Cartesian Hessian may be computed at a lower level of theory than that at which the optimization is being performed.

However, the common empirical formulas for "guessing" the stretch, bend, and torsion force constants generally are sufficient.  In fact, for ordinary bonding situations initial computation of the Hessian results in little reduction in the number of geometry steps to convergence.  Each internal coordinate type has a method used to guess its own force constant using a variety of published methods, here we use the default 'SIMPLE' method.  Lets see what a "simple" guess Hessian for water looks like.

```python
import psi4
from psi4 import *
from psi4.core import *
import numpy as np
import os
sys.path.append('os.getcwd()')
from opt_helper import stre, bend, tors, intcosMisc, linearAlgebra
```

```python
mol = psi4.geometry("""
O
H 1 0.9
H 1 0.9 2 104
""")
mol.update_geometry()
Natom = mol.natom()
Z = [int(mol.Z(i)) for i in range(Natom)]
xyz = np.array(mol.geometry())

# Manually create a list including both O-H stretches and
# the H-O-H bend.
intcos = [stre.STRE(0,1), stre.STRE(0,2), bend.BEND(1,0,2)]
Nintco = len(intcos)
print("Internal Coordinates")
for intco in intcos:
    print(intco)

# Build a diagonal guess Hessian from a simple, empirical rule.
H = np.zeros((Nintco,Nintco), float)
for i,intco in enumerate(intcos):
    H[i,i] = intco.diagonalHessianGuess(xyz, Z, guessType="SIMPLE")
print("\nSimple Guess Hessian for Water (in au)")
print(H)
```

You can see that the very simple rule is 0.5 au for bond stretches and 0.2 au for angles (also, 0.1 au for dihedrals).  For many molecular configurations, this Hessian works nearly as well in optimizations as one determined by a second derivative computation!  However, there are commonly used atomic number- and geometry- dependent formulas that are generally more effective and have better asymptotic behavior (e.g., at long distances).  Here are the corresponding guesses using the formulae from Schlegel [_Theor. Chim. Acta_, 66, 333 (1984)] and from Fischer and Almlof [_J. Phys. Chem._, 96, 9770 (1992)].

The off-diagonal elements, being difficult to estimate, are typically set at zero.  Some have advocated a small non-zero value for them, but in any event, the Hessian update schemes (see other tutorial) will change them.

```python
for i,intco in enumerate(intcos):
    H[i,i] = intco.diagonalHessianGuess(xyz, Z, guessType="SCHLEGEL")
print("Schlegel Guess Hessian for Water (in au)")
print(H)

for i,intco in enumerate(intcos):
    H[i,i] = intco.diagonalHessianGuess(xyz, Z, guessType="FISCHER")
print("\nFischer Guess Hessian for Water (in au)")
print(H)
```

Psi4 and other quantum chemistry programs can compute Cartesian Hessians using either analytic second derivatives, or finite differences of first derivatives (or energies).  Here, we assume the program has provided the Hessian in Cartesian coordinates.  Assuming we wish to carry out our optimization in internal coordinates, then we need to transform this Hessian into internal coordinates for use in our Newton-Raphson like algorithm.

Starting with the definition of the hessian in Cartesian coordinates
$$ \textbf H_{ab} = \frac{\partial ^2 E}{\partial x_a\partial x_b}$$

we use the B-matrix elements and the chain rule
\begin{align}
\textbf H_{ab} &= \frac{\partial}{\partial x_a}\Big( \frac{\partial E}{\partial q_i}\frac{\partial q_i}{\partial x_b}\Big ) \\
 &= \frac{\partial ^2 E}{\partial x_a \partial q_i} \frac{\partial q_i}{\partial x_b} + \frac{\partial E}{\partial q_i}\frac{\partial ^2 q_i}{\partial x_a \partial x_b} \\
 &= \Big(\frac{\partial}{\partial x_a} \cdot \frac{\partial E}{\partial q_i}\Big) \frac{\partial q_i}{\partial x_b} + \frac{\partial E}{\partial q_i}\frac{\partial ^2 q_i}{\partial x_a \partial x_b}\\
 &= \Big(\frac{\partial q_j}{\partial x_a} \cdot \frac{\partial}{\partial q_j} \cdot \frac{\partial E}{\partial q_i}\Big) \frac{\partial q_i}{\partial x_b} + \frac{\partial E}{\partial q_i}\frac{\partial ^2 q_i}{\partial x_a \partial x_b}\\
 &= \frac{\partial q_j}{\partial x_a}\frac{\partial ^2 E}{\partial q_j \partial q_i} \frac{ \partial q_i}{\partial x_b} + \frac{\partial E }{\partial q_i} \frac {\partial ^2 q_i}{\partial x_a \partial x_b}\\
\end{align}

With the introduction of the derivative B-matrix for internal coordinate $i$ as $\textbf B^i$, we can write the above equation in matrix form for the transformation of the internal coordinate Hessian into Cartesian coordinates.

$$ \textbf{H}_{\rm{cart}} = \textbf{B}^T \textbf{H}_{\rm{int}} \textbf{B} + g_i \textbf{B}^i$$

At stationary points where the gradient is zero, the second term vanishes. The contribution of this term is generally small, and it may not be worth the expense of computing when generating Hessians for stationary point searches.

We still need to derive the formula for the inverse transformation.  See the first tutorial for the introduction of the $\mathbf A^T$ matrix, the generalized left-inverse of $ \mathbf{B}^T $, where

$$ \textbf A^T = (\textbf{B} \textbf{u} \textbf {B}^T)^{-1} \textbf {B} \textbf{u}$$

so that

$$ \textbf {A}^T \textbf{H}_{\rm{cart}} \textbf {A} = \textbf{A}^T \textbf{B}^T \textbf{H}_{\rm{int}} \textbf{BA} + \textbf {A}^T g_i \textbf{B}^i \textbf A$$

and

$$ \textbf{H}_{\rm{int}} = \textbf A^T \textbf {H}_{\mathrm{cart}} \textbf A - \textbf A^T  g_i \textbf {B}^i \textbf A $$

We can factor the terms to get the following:

$$ \textbf{H}_{\rm{int}} = \textbf A^T ( \textbf {H}_{\mathrm{cart}} - g_i \textbf {B}^i ) \textbf A $$

Now lets convert a Psi4 Cartesian Hessian into internals!

```python
# Compute B and A matrices.
B = intcosMisc.Bmat(intcos, xyz)
G = np.dot(B, B.T)
Ginv = linearAlgebra.symmMatInv(G)
Atranspose = np.dot(Ginv, B)

# We'll use cc-pVDZ RHF.
psi4.set_options({"basis": "cc-pvdz"})

# Get gradient in cartesian coordinates, then convert to internals.
g_x = np.reshape( np.array( psi4.gradient('scf')), (3*Natom))
g_q = np.dot(Atranspose, g_x)

print("Gradient in internal coordinates")
print(g_q)

# Get Hessian in Cartesian coordinates.
H_cart = np.array( psi4.hessian('scf') )
# print("Hessian in Cartesian coordinates")
# print(H_cart)
```

```python
# Convert Cartesian Hessian to internals.
# A^t (Hxy - Kxy) A;    K_xy = sum_q ( grad_q[I] d^2(q_I)/(dx dy) )
Ncart = 3 * mol.natom()
H_int = np.zeros( (Nintco,Nintco), float)
dq2dx2 = np.zeros((Ncart,Ncart), float)

for I, q in enumerate(intcos):
    dq2dx2[:] = 0
    q.Dq2Dx2(xyz, dq2dx2)   # d^2(q_I)/ dx_i dx_j

    for a in range(Ncart):
        for b in range(Ncart):
            H_cart[a,b] -= g_q[I] * dq2dx2[a,b]

H_int = np.dot(Atranspose, np.dot(H_cart, Atranspose.T))
print("Hessian in internal coordinates")
print(H_int)
```

This result may be compared with the guess Hessians from the formulas above.  It is verified that in these intuitive coordinates (stretches, bends, etc.) the Hessian is strongly diagonal, and it also may be readily estimated.

During the course of the optimization, the Hessian may be re-calculated at each step (or every fixed number of steps).  However, the computation of the Hessian is computationally costly, even when possible.  In most cases, updating the Hessian with first derivative information works nearly as well as recomputing the Hessian and requires no additional computation.  For Hessian updating, see another tutorial.


# Insurance and Incentives

*By Sebastian Graves and Thomas Sargent*

This notebook computes optimal contracts for the three examples that lead off chapter 21 of
**Recursive Macroeconomic Theory, Fourth edition** by Lars Ljungqvist and Thomas Sargent.

The examples illustrate different sorts of tradeoffs between insurance and incentives that emerge under different
limits on enforcement and information.

In each of the three economies, a planner or money-lender designs an efficient contract to supply insurance to a risk-averse consumer who receives an exogenous  random stream of a non-storable endowment.

The only way that the consumer to smooth consumption across states and time is to interact with the planner.

The three models differ in the constraints that they impose  on the planner.

These constraints express the planner's  limited ability either to enforce a contract or to observe the consumer's endowment

Each of the examples uses a version of what we have nicknamed **dynamic programming squared**

In a dynamic programming squared problem, a value function from one Bellman equation is an argument of another Bellman equation.

In the examples below, a planner or money lender's value function will have as an argument the value of a villager
that satisfies a Bellman equation

### Three models of a villager and a money lender

Imagine a  village with a large number of ex ante
identical households. Each household has preferences over
consumption streams that are ordered by
$$ E_{-1}\sum_{t=0}^\infty \beta^t u(c_t),  $$
where $u(c)$ is an increasing, strictly concave, and twice
continuously differentiable  function,
 $\beta \in (0,1)$ is a discount factor, and $E_{-1}$ is the mathematical expectation
 not conditioning on any information available at time $0$ or later.

Each household
receives a stochastic endowment stream $\{y_t\}_{t=0}^\infty$,
where for each $t \geq 0$, $y_t$ is independently and
identically distributed according to the discrete
probability distribution ${\rm Prob} (y_t = \overline y_s) = \Pi_s,$
where $s \in \{1, 2, \ldots ,S\}\equiv {\bf S}$ and
$\overline y_{s+1}>\overline y_s$.

The consumption
good is not storable.

At time $t \geq 1$, the
household has received a history of endowments
$h_t = (y_t, y_{t-1}, \ldots, y_0).$

Endowment processes are distributed independently and identically
 both across time and
across households.

##### Competitive equilibrium

In  this setting, if there were a competitive equilibrium with
complete markets, at date
$0$ households would trade history- and date-contingent claims.

Since  households are  ex ante
identical, each household would consume the per capita
endowment in every period, and its lifetime utility would be

$$ v_{\rm pool} = \sum_{t=0}^\infty
\beta^t \, u\!\left(\sum_{s=1}^S \Pi_s \overline y_s\right) =
 {1 \over 1-\beta}\, u\!\left(\sum_{s=1}^S \Pi_s \overline y_s\right) .
                                                         $$

 Households would thus insure away all
risks from their individual endowment processes.

But the
 incentive constraints that we are about to specify make
this allocation unattainable.

For each specification of incentive
constraints, we shall solve a planning problem for an efficient
allocation that respects those constraints.

   Following a tradition started by
Edward Green (1987) [*Lending and the Smoothing of Uninsurable
 Income*, in Edward C. Prescott and Neil Wallace, editors, **Contractual Arrangements for
 Intertemporal Trade**, Minnesota Studies in Macroeconomics series, Vol.
 1, Minneapolis: University of Minnesota Press, pp. 3--25], we assume that a *moneylender* or *planner* is
the only person in the  village who has access to
a risk-free loan market outside the village.

The moneylender can borrow or lend at a constant one-period
risk-free gross interest rate  $R=\beta^{-1}$.

Households cannot borrow or lend with each other,
and can  trade only with the moneylender.

Furthermore,
we assume that the moneylender is committed   to honor  his
promises.

We will study three distinct environments in which there are three alternative types  of incentive constraints.

**Enviroment a.** Both the money lender and the household observe   the household's  history of endowments at each  time $t$.
Although the moneylender can commit to honor a
contract, households  cannot commit and at any time are
  free to walk away from an arrangement
with the moneylender
and live in perpetual autarky thereafter.  They must be induced not to do so
by the structure of
the contract.
This is a model of *one-sided commitment* in which the
contract must be *self-enforcing*.  That is, it must be structured to induce   the household to  prefer to
conform to it.

**Environment b.** Households *can* make commitments  and enter
into enduring and   binding contracts with the moneylender,
but they have private
information about their own incomes. The moneylender
can see neither their income nor their consumption. Instead,
 exchanges between the moneylender and a household must
be based on the household's own reports about income
realizations. An incentive-compatible contract  induces
a household to report its income truthfully.

**Environment c.** The environment is the same as  b except  that now households have access to a storage technology that
cannot be observed by the moneylender.
Households can store nonnegative amounts of goods at a risk-free
gross return of $R$ equal to the interest rate that
the moneylender faces in the outside credit market.
Since the moneylender can both borrow and lend at the interest
rate $R$ outside of the village,
the private storage technology does not change the economy's
aggregate resource constraint, but it does  affect the set of
incentive-compatible contracts between the moneylender and the
households.

#### Preview

When we compute efficient allocations for each of these three
environments, we  find that the dynamics of the implied
consumption allocations differ dramatically.

 We shall see that the dynamics
of consumption outcomes evidently differ substantially across the
three environments, increasing monotonically and then flattening out in environment a,
stochastically heading south in environment b, and stochastically heading north in
environment c.
These sample path properties will reflect how the optimal  contracts cope with the three different frictions that we have put into the environment.

Chapter 21 of RMT4  explains why  sample paths of consumption differ
so much across these three settings.

### Three computed contracts

For all three environments discussed, consumers have a utility function:

$$u(c) = - \gamma^{-1} \exp(-\gamma c)$$

We set $\gamma = 0.7$, and the discount factor, $\beta$ to 0.8.

The consumers receive an iid endowment  that  can take any integer in the range $[\bar y_1,...,\bar y_{5}] = [6,...,10]$.

The probability of each realisation is $\Pi_s = \frac{1-\lambda}{1-\lambda^{5}}\lambda^{s-1}$ with $\lambda = 0.4$.

As mentioned above, an interesting  benchmark case is a complete markets environment.

Because  all households are *ex ante* identical,  in a complete markets economy each household would consumer the per capita endowment in every period, and its lifetime utility would be:

$$ v_{pool} = \frac{1}{1-\beta} u \left( \sum_{s=1}^S \Pi_s \bar y_s \right) =  \frac{u(c_{pool})}{1-\beta} $$

Later we will compare the consumption paths for each enviroment to that which would occur in the complete markets environment.

In each environment, we compute allocations  for the situation in which  the planner or money lender just breaks even.

## Environment a

The first environment is one in which the planner is able to commit, but households are not.

At any time households are free to walk away from an arrangement with the planner, and live in perpetual autarky thereafter.

RMT4 shows how this problem can be written in a recursive form.

Equations 21.3.4 to 21.3.8 in RMT4 express  the planners's problem as:

\begin{align}
	&P(v) = \max_{c_s,w_s} \sum_{s=1}^S \Pi_s \left[ (\bar y_s - c_s) + \beta P(w_s) \right] \\
	&\text{subject to} \\
	&\sum_{s=1}^S \Pi_s \left[ u(c_s) + \beta w_s \right] \geq v \\
	&u(c_s) + \beta w_s \geq u(\bar y_s) + \beta v_{aut} \text{ , s = 1,...,S} \\
	&c_s \in [c_{min},c_{max}] \\
	&w_s \in [v_{aut},\bar v]
\end{align}

where $w_s$ is the promised value with which the consumer will enter the next period, given that $y = \bar y_s$ this period.

The first constraint is a promise keeping constraint, while the second set of constraints are participation constraints. $[c_{min},c_{max}]$ is a bounded set, while $\bar v$ just needs to be a very large number.

The value of autarky to the households is:

$$ v_{aut} = \frac{1}{1-\beta} \sum_{s=1}^S \Pi_s u(\bar y_s) $$

Below we solve the moneylender's problem in this environment by approximating $P(v)$ using Chebyshev polynomials.

```python
import numpy as np
from scipy.optimize import minimize, fsolve
from scipy.interpolate import UnivariateSpline
import matplotlib.pyplot as plt
import numpy.polynomial.chebyshev as cheb
%matplotlib inline
```

```python
# Parameter values
gamma = 0.7
beta = 0.8
lamb = 0.4
S = 5
y_grid = np.linspace(6,5+S,S)
prob_grid = np.zeros(S)
for i in range(S):
    prob_grid[i] = (1 - lamb)/(1-lamb**S)*lamb**(i)

# Utility function
u = lambda c: -gamma**(-1)*np.exp(-gamma*c)
u_inv = lambda u: np.log(-gamma*u)/(-gamma)

# Calculate complete markets consumption
c_pool = np.dot(prob_grid,y_grid)

# Calculate value of autarky
v_aut = 1/(1-beta)*np.dot(prob_grid, u(y_grid))
```

```python
# Functions used in each environment

# Nodes and basis matrix for Chebyshev approximation

def Cheb_basis(order,lb,ub):
    # Calculate roots of Chebyshev polynomial
    k = np.linspace(order, 1, order)
    roots = np.cos((2*k - 1)*np.pi/(2*order))
    # Scale to approximation space
    s = lb + (roots - -1)/2*(ub-lb)
    # Create basis matrix
    Phi = cheb.chebvander(roots, order-1)
    return s, Phi

# Value Function Iteration

def Bellman_Iterations(s, Phi, P_fun, x_store, coeff, tolc=1e-6, bnds=None, cons=(), max_iters=100):
    global x, c
    c = coeff
    order = Phi.shape[1]
    iters = 0
    diff = 1

    while diff > tolc:
        # 1. Maximization, given value function guess
        P_iter = np.zeros(order)
        for i in range(order):
            x = s[i]
            res = minimize(P_fun, x_store[i], method = 'SLSQP', bounds = bnds, constraints=cons, tol=1e-15)
            x_store[i] = res.x
            P_iter[i] = -P_fun(res.x)
        # 2. Bellman updating of Value Function coefficients
        c1 = np.linalg.solve(Phi, P_iter)
        # 3. Compute distance and update
        diff = max(abs(c1 - c))
        print(diff)
        c = np.copy(c1)
        iters = iters + 1

        if iters >= max_iters:
            print('Convergence failed after {} iterations'.format(iters))
            break

    if diff < tolc:
        print('Convergence achieved after {} iterations'.format(iters))

    return c
```

```python
# Value Function Approximation
# Set bounds and approximation order
v_min = v_aut
v_max = -0.065
c_min = 0
c_max = 50
order = 70

# Calculate nodes and basis matrix
s, Phi = Cheb_basis(order, v_min, v_max)

# Bounds for Maximisation
lb = np.concatenate([np.ones(S)*c_min, np.ones(S)*v_min], axis=0)
ub = np.concatenate([np.ones(S)*c_max, np.ones(S)*v_max], axis=0)

# Initialize Value Function coefficients and goess for c,w
y = (c_pool - u_inv(s*(1-beta)))/(1-beta)
c = np.linalg.solve(Phi, y)
x_init = np.concatenate([np.ones(S)*c_min, np.ones(S)*v_min], axis=0)

# Function to minimize and constraints
def P_fun(x):
    scale = -1 + 2*(x[S:2*S] - v_min)/(v_max - v_min)
    P = np.dot(cheb.chebvander(scale,order-1),c)
    P_fun = - prob_grid.dot((y_grid - x[0:S]) + beta*P)
    return P_fun

def cons12(y):
    global x
    return prob_grid.dot(u(y[0:S]) + beta*y[S:2*S]) - x

cons1 = ({'type': 'ineq', 'fun': lambda y: u(y[0:S]) + beta*y[S:2*S] - u(y_grid) - beta*v_aut},
        {'type': 'ineq', 'fun': cons12})

bnds1 = np.concatenate([lb.reshape(2*S, 1), ub.reshape(2*S, 1)], axis = 1)

# Bellman Iterations
NBell = 5
tolc = 1e-6
diff = 1
iters = 1
x_store = {}
for i in range(order):
    x_store[i] = x_init

c = Bellman_Iterations(s, Phi, P_fun, x_store, c, bnds=bnds1, cons=cons1)
```

    0.0204645602011
    0.0142041769769
    0.00791931614057
    0.00436248729285
    0.00245216228324
    0.00141817326182
    0.000845016310537
    0.000518068121308
    0.00032590006325
    0.000209616393398
    0.000137341501445
    9.13501553881e-05
    6.14960266619e-05
    4.17986937108e-05
    2.86310087581e-05
    1.97356652833e-05
    1.36760163048e-05
    9.52010226041e-06
    6.6540138931e-06
    4.66813374589e-06
    3.28641338077e-06
    2.32320410931e-06
    1.66681169639e-06
    1.19994614806e-06
    8.66741583838e-07
    Convergence achieved after 25 iterations

```python
# Time Series Simulation
T = 100
np.random.seed(2)
y_series = np.random.choice(y_grid,size = T,p = prob_grid)
c_series = np.zeros(T)
w_series = np.zeros(T)
resid_series = np.zeros(T)
pval_series = np.zeros(T)

# Initialize v such that P(v) = 0
v_find = lambda v: cheb.chebvander(-1 + 2*(v - v_min)/(v_max - v_min),order-1).dot(c)
x = fsolve(v_find,v_max)

res = minimize(P_fun,x_init,method = 'SLSQP',bounds = bnds1,constraints = cons1,tol=1e-15)
c_series[0] = res.x[np.where(y_grid == y_series[0])[0][0]]
w_series[0] = res.x[S + np.where(y_grid == y_series[0])[0][0]]

# Simulate
for t in range(1,T):
    x = w_series[t-1]
    res = minimize(P_fun, x_init,method = 'SLSQP',bounds = bnds1, constraints = cons1, tol=1e-15)
    c_series[t] = res.x[np.where(y_grid == y_series[t])[0][0]]
    w_series[t] = res.x[S + np.where(y_grid == y_series[t])[0][0]]

plt.plot(c_series, label = 'Environment (a)')
plt.plot(np.ones(T)*c_pool, label = 'Complete Markets')
plt.ylabel('Consumption')
plt.xlabel('Time')
plt.legend(loc = 'best');
plt.title('Environment (a)');
```

The above simulation is equivalent to Figure 21.2.1.a in RMT.

The discussion in RMT4 confirms that the household's consumption ratchets upwards over time.

The consumption level is constant after the first time that the household receives the highest possible endowment.

## Environment b

The second environment is one in which households *can* make commitments to enter into binding contracts with the planner, but they have private information about their incomes.

Consequently, incentive compatability constraints are required to ensure that households truthfully report their incomes.

Equations 21.5.1 to 21.5.5 in RMT4 express the planners's problem.

\begin{align}
	&P(v) = \max_{b_s,w_s} \sum_{s=1}^S \Pi_s \left[ -b_s + \beta P(w_s) \right] \\
	&\text{s.t.} \\
	&\sum_{s=1}^S \Pi_s \left[ u(\bar y_s + b_s) + \beta w_s \right] = v \\
	& C_{s,k} \equiv u(\bar y_s + b_s) + \beta w_s - [ u(\bar y_s + b_k) + \beta w_k ] \geq 0 \hspace{2mm} \forall \hspace{2mm} s,k \in S \times S\\
	&b_s \in [a - \bar y_s,\infty ] \\
	&w_s \in [- \infty, v_{max}]
\end{align}

Here $b_s$ is the transfer that the moneylender gives to a household who reports income $y_s$ if their promised value was $v$.

The promise keeping constraint remains, while the participation constraint has been replaced by a large set of incentive compatibility constraints.

RMT4 shows that we can discard many of the incentive compatibility constraints.

In solving the model below, we keep only the local upward and downward incentive compatibility constraints.

```python
# Set bounds and approximation order
b_min = -20
b_max = 20
w_min = -150;
w_max = -0.04;
v_min = -150;
v_max = -0.04;
v_pool = u(c_pool)/(1-beta)
order = 70

# Calculate nodes and basis matrix
s, Phi = Cheb_basis(order,v_min,v_max)

# Bounds for Maximisation
lb = np.concatenate([np.ones(S)*b_min,np.ones(S)*w_min], axis=0)
ub = np.concatenate([np.ones(S)*b_max,np.ones(S)*w_max], axis=0)

# For initial guess, use upper bound given in RMT:
cbar = np.zeros(order)
upper = np.zeros(order)
for i in range(order):
    cbar[i] = u_inv((1-beta)*s[i])
    upper[i] = np.dot(prob_grid,(y_grid - cbar[i])/(1-beta))
c = np.linalg.solve(Phi,upper)

# Function to minimize and constraints
def P_fun2(x):
    scale = -1 + 2*(x[S:2*S] - v_min)/(v_max - v_min)
    P = np.dot(cheb.chebvander(scale,order-1),c)
    P_fun = - prob_grid.dot(-x[0:S] + beta*P)
    return P_fun

def cons23(y):
    global x
    return prob_grid.dot(u(y_grid + y[0:S]) + beta*y[S:2*S]) - x

cons2 = ({'type': 'ineq', 'fun': lambda x:  u(y_grid[1:S] + x[1:S]) + beta*x[S+1:2*S] - u(y_grid[1:S] + x[0:S-1]) - beta*x[S:2*S-1]},
         {'type': 'ineq', 'fun': lambda x:  u(y_grid[0:S-1] + x[0:S-1]) + beta*x[S:2*S-1] - u(y_grid[0:S-1] + x[1:S]) - beta*x[S+1:2*S]},
         {'type': 'eq',  'fun': cons23})

bnds2 = np.concatenate([lb.reshape(2*S,1),ub.reshape(2*S,1)], axis = 1)

x_store = {}
for i in range(order):
    x_store[i] = np.concatenate([np.zeros(S),np.ones(S)*s[i]], axis=0)

c = Bellman_Iterations(s, Phi, P_fun2, x_store, c, tolc, bnds=bnds2, cons=cons2)
```

    0.0350240851831
    0.0300811372099
    0.0240588059915
    0.0191012599867
    0.0152208985956
    0.0121155794117
    0.0096303683039
    0.00767435325304
    0.00610585227705
    0.00486335223165
    0.00387483961155
    0.00308567148961
    0.00245937341067
    0.00195969539885
    0.00156158095348
    0.00124481831615
    0.000992077327268
    0.000790723902348
    0.000630314378078
    0.00050235084457
    0.000400382590769
    0.000319108663433
    0.000254285928506
    0.000202627378542
    0.000161446982077
    0.000128610576674
    0.00010244431143
    8.15872761208e-05
    6.49622635578e-05
    5.17170794225e-05
    4.11623696621e-05
    3.27532323467e-05
    2.6056017056e-05
    2.07217051269e-05
    1.64742195921e-05
    1.30932222646e-05
    1.04020309593e-05
    8.2607218701e-06
    6.55745155598e-06
    5.2028427433e-06
    4.12600281408e-06
    3.27025740887e-06
    2.59042128903e-06
    2.05060830893e-06
    1.62215066268e-06
    1.28222875873e-06
    1.01270852326e-06
    7.99118105022e-07
    Convergence achieved after 48 iterations

```python
# Time Series Simulation
T = 800
np.random.seed(2)
y_series = np.random.choice(y_grid,size = T+1, p = prob_grid)
c_series = np.zeros(T)
w_series = np.zeros(T)

# Initialize v such that P(v) = 0
v_find = lambda v: cheb.chebvander(-1 + 2*(v - v_min)/(v_max - v_min),order-1).dot(c)
x = fsolve(v_find,v_aut)

x_init = np.concatenate([np.zeros(S),np.ones(S)*x],axis=0)
res = minimize(P_fun2,x_init,method = 'SLSQP',bounds = bnds2, constraints = cons2,tol=1e-10)
c_series[0] = y_series[0] + res.x[np.where(y_grid == y_series[0])[0][0]]
w_series[0] = res.x[S  + np.where(y_grid == y_series[0])[0][0]]
x_init = res.x

# Simulate
for t in range(1,T):
    x = w_series[t-1]
    res = minimize(P_fun2,x_init,method = 'SLSQP',bounds = bnds2,constraints = cons2,tol=1e-10)
    c_series[t] = y_series[t] + res.x[np.where(y_grid == y_series[t])[0][0]]
    w_series[t] = res.x[S + np.where(y_grid == y_series[t])[0][0]]
    x_init = res.x

# Plot
plt.plot(c_series, label = 'Environment (b)')
plt.plot(np.ones(T)*c_pool, label = 'Complete Markets')
plt.ylabel('Consumption')
plt.xlabel('Time')
plt.title('Environment (b)')
plt.legend(loc = 'best');
```

This simulation reported in the graph above confirms that in environment **b** the incentive compatibility constraints induce  the planner to introduce a downward tilt into consumption paths.

## Environment c

The third environment is the same as in (b), except for the additional assumption that households have access to a storage technology.

A household  can store nonnegative amounts that cannot be observed by the planner.

The text of RMT4 chaper 21 shows that the solution to this problem is the same as in an economy in which  each household can lend *or borrow* at the risk-free gross interest rate R, subject to the natural debt limit.

Thus, the planner enables the household to relax the no-borrowing constraint implied by  the restriction that it can store only nonnegative amounts

We can find the natural debt limit by iterating forward on the households budget constraint:

\begin{equation}
	c + k' = y + Rk
\end{equation}
This iteration gives:
\begin{equation}
	k = \frac{1}{R} \sum_{j=0}^\infty \frac{c - y}{R^j}
\end{equation}

Imposing non-negativity on consumption:

\begin{equation}
	k \geq  - \frac{1}{R} \sum_{j=0}^\infty \frac{y}{R^j}
\end{equation}

Finally, the natural debt limit is found by choosing the lowest possible value of the endowment, so that for any possible endowment stream the household can always pay back its debts:

\begin{equation}
	k \geq  - \frac{1}{R} \sum_{j=0}^\infty \frac{\bar y_{min}}{R^j} = - \frac{\bar y_{min}}{R-1} \equiv \phi
\end{equation}

A recursive presentation of the household's problem is then:
\begin{align}
	&V(k,y) = \max_{c,k'} u(c) + \beta E [V(k',y')] \\
	&\text{s.t.} \\
	&c + k' = y + Rk \\
	& k' \geq \phi
\end{align}

As income is iid, we can re-write the household's problem with only one state.

Define a = k + y.

Then
\begin{align}
	&V(a) = \max_{c,k'} u(c) + \beta E [V(Rk' + y')] \\
	&\text{subject to} \\
	&c + k' = a \\
	& k' \geq \phi
\end{align}

Below we solve this latter problem using Value Function Iteration, again with Chebyshev polynomials.

```python
# Update parameter values
# Set bounds and approximation order
R = 1/beta
k_min = - y_grid[0]/(R - 1)
k_max = 100
a_min = R*k_min + min(y_grid)
a_max = R*k_max + max(y_grid)
order = 150

# Calculate nodes and basis matrix
s, Phi = Cheb_basis(order,a_min,a_max)

# Create bounds
bnds3 = np.array([[k_min,k_max]])

# Value function
def P_fun3(kprime):
    global x,c
    # Function to minimize
    scale = -1 + 2*(R*kprime + y_grid - a_min)/(a_max - a_min)
    P_fun = -(u(x - kprime) + beta * prob_grid.dot(cheb.chebval(scale, c)))
    return P_fun

# Initialize guess and VF coefficients
c = np.zeros(order)

x_store = {}
for i in range(order):
    x_store[i] = k_min

c = Bellman_Iterations(s, Phi, P_fun3, x_store, c, bnds=bnds3)
```

    0.1517596119
    0.170870776064
    0.15630207769
    0.135311210421
    0.113867025846
    0.0942270076256
    0.0771461647085
    0.0627032329456
    0.0507038100777
    0.0408488277402
    0.0328188929678
    0.0263172199151
    0.0210656876565
    0.01684431429
    0.0134563756853
    0.0107422804559
    0.00857080837168
    0.00683544020647
    0.00544960980611
    0.00434409820094
    0.00346154363019
    0.00275808662142
    0.00219733675542
    0.0017504497032
    0.00139434855846
    0.00111064350103
    0.000884641978496
    0.000704617702793
    0.000561208934367
    0.000446984730973
    0.000356007761605
    0.000283548279735
    0.000225837927469
    0.000179874534723
    0.000143266924586
    0.000114110788738
    9.08891841436e-05
    7.23940289429e-05
    5.76632006384e-05
    4.59304062566e-05
    3.65853380537e-05
    2.91419530103e-05
    2.32132535181e-05
    1.8490938219e-05
    1.47294785324e-05
    1.17333277083e-05
    9.34674434783e-06
    7.44568716349e-06
    5.93136137361e-06
    4.72507908911e-06
    3.76416625159e-06
    2.9987029877e-06
    2.38892743343e-06
    1.90316850213e-06
    1.51619906852e-06
    1.207924615e-06
    9.62338748245e-07
    Convergence achieved after 57 iterations

```python
# Time Series Simulation
T = 800
np.random.seed(2)
y_series = np.random.choice(y_grid, size = T+1, p = prob_grid)

a_series = np.zeros(T+1)
c_series = np.zeros(T)

# Initialise at v_aut
def k_find(k):
    scale = -1 + 2 * (R * k + y_grid - a_min)/(a_max - a_min)
    return prob_grid.dot(cheb.chebval(scale,c)) - v_aut
k0 = fsolve(k_find,0)
a_series[0] = k0 + y_series[0]

# Simulate
for t in range(T):
    x = a_series[t]
    res = minimize(P_fun3, k_min,method='SLSQP',  bounds=bnds3,tol=1e-15)
    c_series[t] = a_series[t] - res.x
    a_series[t+1] = R * res.x + y_series[t+1]

# Plot
plt.plot(c_series, label = 'Environment (c)')
plt.plot(np.ones(T)*c_pool, label = 'Complete Markets')
plt.ylabel('Consumption')
plt.xlabel('Time')
plt.title('Environment (c)')
plt.legend(loc = 'best')
```

Notice that the introduction of a storage technology for the household means that the consumption path now has an upward trend.

This occurs because our parameter values satisfy  $\beta R = 1$.


# The $K$-means clustering algorithm

This notebook is a modified version of the one created by [Jake Vanderplas](https://github.com/jakevdp) for PyCon 2015.

Source and license info of the original notebook are on [GitHub](https://github.com/jakevdp/sklearn_pycon2015/).

```python
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
from scipy import stats

from scipy.spatial.distance import cdist
from fig_code import plot_kmeans_interactive
from sklearn.datasets import make_blobs, load_digits, load_sample_image
from sklearn.decomposition import PCA
from sklearn.metrics import confusion_matrix
from sklearn.cluster import KMeans

# use seaborn plotting defaults
import seaborn as sns; sns.set()
```

## 1. Clustering algorithms

Clustering algorithms try to split a set of data points $\mathcal{S} = \{{\bf x}_0,\ldots,{\bf x}_{L-1}\}$, into mutually exclusive clusters or groups, $\mathcal{G}_0,\ldots, \mathcal{G}_{K-1}$, such that every sample in $\mathcal{S}$ is assigned to one and only one group.

Clustering algorithms belong to the more general family of **unsupervised methods**: clusters are constructed using the data attributes alone. No labels or target values are used. This makes the difference between a clustering algorithm and a *supervised* classification algorithm.

There is not a unique formal definition of the clustering problem. Different algorithms group data into clusters following different criteria. The appropriate choice of the clustering algorithm may depend on the particular application scenario.

The image below, taken from the scikit-learn site, shows that different algorithms follow different grouping criteria, clustering the same datasets in different forms.

In any case, all clustering algorithms share a set of common characteristics. A clustering algorithm makes use of some distance or similarity measure between data points to group data in such a way that:

* Points in some cluster should lie close to each other
* Points in different clusters should be far away
* Clusters should be separated by regions of low density of points
* Clusters may preserve some kind of *connectivity*
* Clusters may get represented by a representative or centroid

## 2. The $K$-means algorithm

$K$-Means is a proximity-based clustering algorithm. It searches for cluster centers or **centroids** which are representative of all points in a cluster. Representativenes is measured by proximity: "good" clusters are those such that all data points are close to its centroid.

Given a dataset $\mathcal{S} = \{{\bf x}_0,\ldots,{\bf x}_{L-1}\}$, $K$-means tries to minimize the following **distortion function**:

$$D = \sum_{k=0}^{K-1} \sum_{{\bf x} \in {\cal{G}}_k}\|{\bf x}-\boldsymbol{\mu}_k\|_2^2$$

where $\boldsymbol{\mu}_k$ is the centroid of cluster $\mathcal{G}_k$.

Note that, in this notebook, we will used $k$ as the index to count groups and centroids, and $K$ for the number of centroides. To avoid any confusion, we will index data samples as ${\bf x}_\ell$ when needed, and the number of samples will be denoted as $L$.

The minimization should be carried out over both the partition $\{{\cal G}_0,\ldots, {\cal G}_{K-1}\}$ of ${\cal S}$ (i.e., the assignment problem) and their respective centroids $\{\boldsymbol{\mu}_0,\ldots,\boldsymbol{\mu}_{K-1}\}$ (i.e. the estimation problem). This joint assignment-estimation problem is what makes optimization difficult (it is an NP-hard problem).

The $K$-means algorithm is based on the fact that, given that one of both problems is solved, the solution to the other is straightworward:

* **Assignment**: For fixed centroids $\boldsymbol{\mu}_0,\ldots,\boldsymbol{\mu}_{K-1}$, the optimal partition is given by the following
$${\cal G}_k^* = \left\{{\bf x} \, \left| \, k \in \arg\min_{k'} \|{\bf x}-\boldsymbol{\mu}_{k'}\|^2\right. \right\}$$
(i.e. each sample is assigned to the group with the closest centroid).
* **Estimation**: For a fixed partition $\{{\cal G}_0,\ldots, {\cal G}_{K-1}\}$, the optimal centroids can be computed easily by differentiation
\begin{equation}
\boldsymbol{\mu}_k^* = \frac{1}{\left|{\cal G}_k\right|} \sum_{{\bf x} \in {\cal{G}}_k} {\bf x}
\end{equation}
where $\left|{\cal G}_k\right|$ is the cardinality of ${\cal G}_k$.

$K$ means is a kind of coordinate descent algorithm that applies ciclycally and iteratively the estimation and assigning steps, fixing the solution of the previous optimization at each time.

**Exercise**: Derive the equation for the optimal centroids.

#### Solution:

[comment]: # (<SOL>)

Since $D$ is differentiable with respect to $\boldsymbol{\mu}_k$, the optimal centroid for a given partition $\{{\cal G}_0,\ldots, {\cal G}_{K-1}\}$ can be found by differentiation:
\begin{align}
\nabla_{\boldsymbol{\mu}_k}{D}
    &= \nabla_{\boldsymbol{\mu}_k}{\left(
            \sum_{m=0}^{K-1} \sum_{{\bf x} \in {\cal{G}}_m}\|{\bf x}-\boldsymbol{\mu}_m\|_2^2
            \right)} \\
    &= \nabla_{\boldsymbol{\mu}_k}{\left(
            \sum_{{\bf x} \in {\cal{G}}_k}\|{\bf x}-\boldsymbol{\mu}_k\|_2^2
            \right)} \\
    &= - 2 \sum_{{\bf x} \in {\cal{G}}_k} \left({\bf x}-\boldsymbol{\mu}_k \right)  \\
    &= 2 \left|{\cal G}_k\right| \boldsymbol{\mu}_k - 2 \sum_{{\bf x} \in {\cal{G}}_k} {\bf x}
\end{align}
This gradient is zero at a single point
\begin{equation}
\boldsymbol{\mu}_k^* = \frac{1}{\left|{\cal G}_k\right|} \sum_{{\bf x} \in {\cal{G}}_k} {\bf x}
\end{equation}

[comment]: # (</SOL>)

### 2.1. Steps of the algorithm

After initialization of centroids:
1. **Assignment**: Assign each data point to closest centroid
2. **Estimation**: Recalculate centroids positions
3. Go back to 1 until no further changes or max iterations achieved

#### 2.1.1. Initializations

$K$-means convergence is guaranteed ... but just to a local minimum of $D$.

Different initialization possibilities:
1. $K$-means$++$: To maximize inter-centroid distance
2. Random among training points
3. User-selected

Typically, different runs are executed, and the best one is kept.

Check out  the Scikit-Learn site for parameters, attributes, and methods.

#### 2.1.2. Stopping.

Since (1) the total number of possible assignments is finite, and (2) each step of the $K$-means algorithm reduces (or, at least, does not increase) the value of the distortion function, the algorithm will eventually converge to a fixed distortion value.

#### 2.1.3. Local convergence

Unfortunatelly, there is no guarantee that the final distortion is minimum. The quality of the solution obtained by the algorithm may critically depend on the initialization.

### 2.2. Example

Let's look at how KMeans operates on a synthetic example. To emphasize that this is unsupervised, we do not plot the colors of the clusters:

```python
X, y = make_blobs(n_samples=300, centers=4,
                  random_state=0, cluster_std=0.60)

plt.scatter(X[:, 0], X[:, 1], s=50);
plt.axis('equal')
plt.show()
```

By eye, it is relatively easy to pick out the four clusters. If you were to perform an exhaustive search for the different segmentations of the data, however, the search space would be exponential in the number of points. Fortunately, the $K$-Means algorithm implemented in Scikit-learn provides a much more convenient solution.

Exercise:

The following frament of code runs the $K$-means method on the toy example you just created. Modify it, so that you can try other settings for the parameter options implemented by the method. In particular:

   - Reduce the number of runs to check the consequences of a bad initialization
   - Test different kinds of initializations (k-means++ vs random)
   - Provide a user-generated initialization that you consider can result in very suboptimal performance
   - Test other selections of the number of parameters
   - Include in the plot the location of the cluster of each class

```python
est = KMeans(5)  # 4 clusters
est.fit(X)
y_kmeans = est.predict(X)
plt.scatter(X[:, 0], X[:, 1], c=y_kmeans, s=50, cmap='rainbow');
plt.axis('equal')
plt.show()
```

### 2.3. The K-Means Algorithm: Interactive visualization

The following fragment of code allows you to study the evolution of cluster centroids on one run of the algorithm, and to modify also the number of centroids.

```python
# WARNING: This command may fail (interactivity not working properly) depending on the python version.
plot_kmeans_interactive(min_clusters=2, max_clusters=6)
plt.show()
```

### 2.4. Determining the number of clusters

If the number of clusters, $K$, is not known, selecting the appropriate value becomes a major issue. Since the overal distortion $D$ decreases with $K$, the selection of the number o clusters cannot be based on the overal distorsion.

The best value of $K$ may be application dependent. Though we will not discuss specific algorithms in detail, we point out some possible solutions:

* **Penalization functions**: instead of minimizing $D$, we can train the clustering algorithm in order to minimize the functional $D' = D + \lambda f(K)$, where $f$ is an increasing function penalizing large values of $K$, and $\lambda$ is an hyperparameter. For instance, we can take
$$f(K)=\log(K)$$
$$f(K)=K$$
$$f(K)=K^2,$$
etc.

* **Cluster-based metrics**, like
    * Average silohuete coefficient. The Silhouette Coefficient is calculated using the mean intra-cluster distance (a) and the mean nearest-cluster distance (b) for each sample. The Silhouette Coefficient for a sample is (b - a) / max(a, b).
    *  Calinski-Harabaz score . It is defined as the ratio of the between-clusters dispersion mean and the within-cluster dispersion:
\begin{align}
s(K) = \frac{\mathrm{Trace}({\bf B}_K)}{\mathrm{Trace}({\bf W}_K)} \times \frac{L - K}{K - 1}
\end{align}
where ${\bf W}_K$ is the within-cluster dispersion matrix defined by
$$
{\bf W}_K = \sum_{k=0}^{K-1} \sum_{{\bf x} \in {\cal G}_k} (x - \boldsymbol{\mu}_k) ({\bf x} - \boldsymbol{\mu}_k)^T
$$
and ${\bf B}_K$ is the between group dispersion matrix, definded by
$$
{\bf B}_K = \sum_{k=0}^{K-1} \left|{\cal G}_k\right| (\boldsymbol{\mu}_k - \boldsymbol{\mu}) (\boldsymbol{\mu}_k - \boldsymbol{\mu})^T
$$
with $L$ be the number of points in our data and $\boldsymbol{\mu}$ be the average of all data points.

Exercise: Select the number of samples using any of the above metrics for the dataset in the previous examples.

## 3. Application of KMeans to Digits

For a closer-to-real-world example, let us take a look at a digit recognition dataset. Here we'll use KMeans to automatically cluster the data in 64 dimensions, and then look at the cluster centers to see what the algorithm has found.

```python
digits = load_digits()

print('Input data and label number are provided in the following two variables:')
print("digits['images']: {0}".format(digits['images'].shape))
print("digits['target']: {0}".format(digits['target'].shape))
```

    Input data and label number are provided in the following two variables:
    digits['images']: (1797, 8, 8)
    digits['target']: (1797,)

Next, we cluster the data into 10 groups, and plot the representatives (centroids of each group).  As with the toy example, you could modify the initialization settings to study the impact of initialization in the performance of the method

```python
est = KMeans(n_clusters=10)
clusters = est.fit_predict(digits.data)
est.cluster_centers_.shape
```

    (10, 64)

```python
fig = plt.figure(figsize=(8, 3))
for i in range(10):
    ax = fig.add_subplot(2, 5, 1 + i, xticks=[], yticks=[])
    ax.imshow(est.cluster_centers_[i].reshape((8, 8)), cmap=plt.cm.binary)
```

We see that *even without the labels*, KMeans is able to find clusters whose means are recognizable digits (with apologies to the number 8)!

### 3.1. Visualization via Dimensionality Reduction

The following fragment of code projects the data into the two "most representative" dimensions, so that we can somehow visualize the result of the clustering (note that we can not visualize the data in the original 64 dimensions). In order to do so, we use a method known as Principal Component Analysis (PCA). This is a method that allows you to obtain a 2-D representation of multidimensional data: we extract the two most relevant features (using PCA) and look at the true cluster labels and $K$-means cluster labels:

```python
X = PCA(2).fit_transform(digits.data)

kwargs = dict(cmap = plt.cm.get_cmap('rainbow', 10),
              edgecolor='none', alpha=0.6)
fig, ax = plt.subplots(1, 2, figsize=(8, 4))
ax[0].scatter(X[:, 0], X[:, 1], c=est.labels_, **kwargs)
ax[0].set_title('learned cluster labels')

ax[1].scatter(X[:, 0], X[:, 1], c=digits.target, **kwargs)
ax[1].set_title('true labels');
```

### 3.2. Classification performance

Just for kicks, let us see how accurate our $K$-means classifier is **with no label information**. In order to do so, we can work on the confussion matrix:

```python
conf = confusion_matrix(digits.target, est.labels_)
print(conf)

plt.imshow(conf, cmap='Blues', interpolation='nearest')
plt.colorbar()
plt.grid(False)
plt.ylabel('true')
plt.xlabel('Group index');

#And compute the number of right guesses if each identified group were assigned to the right class
print('Percentage of patterns that would be correctly classified: {0}'.format(
      np.sum(np.max(conf,axis=1)) * 100. / np.sum(conf)))
```

This is above 80% classification accuracy for an **entirely unsupervised estimator** which knew nothing about the labels.

## 4. Example: KMeans for Color Compression

One interesting application of clustering is in color image compression. For example, imagine you have an image with millions of colors. In most images, a large number of the colors will be unused, and conversely a large number of pixels will have similar or identical colors.

Scikit-learn has a number of images that you can play with, accessed through the datasets module. For example:

```python
china = load_sample_image("china.jpg")
plt.imshow(china)
plt.grid(False);
```

The image itself is stored in a 3-dimensional array, of size ``(height, width, RGB)``. For each pixel three values are necessary, each in the range 0 to 255. This means that each pixel is stored using 24 bits.

```python
print('The image dimensions are {0}'.format(china.shape))
print('The RGB values of pixel 2 x 2 are '.format(china[2,2,:]))
```

    The image dimensions are (427, 640, 3)
    The RGB values of pixel 2 x 2 are

We can envision this image as a cloud of points in a 3-dimensional color space. We'll rescale the colors so they lie between 0 and 1, then reshape the array to be a typical scikit-learn input:

```python
X = (china / 255.0).reshape(-1, 3)
print(X.shape)
```

    (273280, 3)

We now have 273,280 points in 3 dimensions.

Our task is to use KMeans to compress the $256^3$ colors into a smaller number (say, 64 colors). Basically, we want to find $N_{color}$ clusters in the data, and create a new image where the true input color is replaced by the color of the closest cluster. Compressing data in this way, each pixel will be represented using only 6 bits (**25 % of the original image size**)

```python
# reduce the size of the image for speed. Only for the K-means algorithm
image = china[::3, ::3]
n_colors = 128

X = (image / 255.0).reshape(-1, 3)

model = KMeans(n_colors)
model.fit(X)
labels = model.predict((china / 255.0).reshape(-1, 3))
#print labels.shape
colors = model.cluster_centers_
new_image = colors[labels].reshape(china.shape)
new_image = (255 * new_image).astype(np.uint8)

#For comparison purposes, we pick 64 colors at random
perm = np.random.permutation(range(X.shape[0]))[:n_colors]
colors = X[perm,:]

labels = np.argmin(cdist((china / 255.0).reshape(-1, 3),colors),axis=1)
new_image_rnd = colors[labels].reshape(china.shape)
new_image_rnd = (255 * new_image_rnd).astype(np.uint8)

# create and plot the new image
with sns.axes_style('white'):
    plt.figure()
    plt.imshow(china)
    plt.title('Original image')

    plt.figure()
    plt.imshow(new_image)
    plt.title('{0} colors'.format(n_colors))

    plt.figure()
    plt.imshow(new_image_rnd)
    plt.title('{0} colors'.format(n_colors) + ' (random selection)')

```

Compare the input and output image: we've reduced the $256^3$ colors to just 64. An additional image is created by selecting 64 colors at random from the original image. Try reducing the number of colors to 32, 16, 8, and compare the images in these cases.