# 3 Degrees Of Freedome (DOF)

```python
%matplotlib inline
import sympy as sym
import sympy.physics.mechanics as me
from sympy import Abs,sin,cos,tan

from sympy import init_printing
init_printing(use_latex='mathjax')

from pydy.system import System
import numpy as np
import matplotlib.pyplot as plt

```

```python
x0,y0,psi,x,y,beta,x,y,u,v,r,V = me.dynamicsymbols(r'x0 y0 \psi x y \beta x y u v r V')
m,Izz,t,x_cg = sym.symbols(r'm Izz t x_{cg}')
```

```python
earth_frame = me.ReferenceFrame(name = 'E')
ship_frame = me.ReferenceFrame(name = 'S')
ship_frame.orient(parent = earth_frame,rot_type='Axis',amounts = (psi,earth_frame.z))
#ship_frame.set_ang_vel(otherframe=earth_frame,value = r*earth_frame.z)
```

```python
ship_frame.dcm(otherframe=earth_frame)
```

$$\left[\begin{matrix}\cos{\left (\psi{\left (t \right )} \right )} & \sin{\left (\psi{\left (t \right )} \right )} & 0\\- \sin{\left (\psi{\left (t \right )} \right )} & \cos{\left (\psi{\left (t \right )} \right )} & 0\\0 & 0 & 1\end{matrix}\right]$$

```python
O0 = me.Point('O0')
O0.set_vel(frame = earth_frame,value = 0)

O = me.Point('O')
#O.set_pos(otherpoint=O0,value = x0*earth_frame.x + y0*earth_frame.y)
O.set_pos(otherpoint=O0,value = x*ship_frame.x + y*ship_frame.y)

O.set_vel(frame = ship_frame,value = u*ship_frame.x + v*ship_frame.y)

O.v1pt_theory(otherpoint=O0,outframe=earth_frame,interframe=ship_frame)
```

$$(u - y \dot{\psi})\mathbf{\hat{s}_x} + (v + x \dot{\psi})\mathbf{\hat{s}_y}$$

```python
O.pos_from(O0)
```

$$x\mathbf{\hat{s}_x} + y\mathbf{\hat{s}_y}$$

```python
O.vel(earth_frame).express(earth_frame)
```

$$(\left(u - y \dot{\psi}\right) \operatorname{cos}\left(\psi\right) - \left(v + x \dot{\psi}\right) \operatorname{sin}\left(\psi\right))\mathbf{\hat{e}_x} + (\left(u - y \dot{\psi}\right) \operatorname{sin}\left(\psi\right) + \left(v + x \dot{\psi}\right) \operatorname{cos}\left(\psi\right))\mathbf{\hat{e}_y}$$

```python
O.acc(earth_frame).express(earth_frame)
```

$$(- \left(\left(u - y \dot{\psi}\right) \dot{\psi} + x \ddot{\psi} + \dot{\psi} \dot{x} + \dot{v}\right) \operatorname{sin}\left(\psi\right) + \left(- \left(v + x \dot{\psi}\right) \dot{\psi} - y \ddot{\psi} - \dot{\psi} \dot{y} + \dot{u}\right) \operatorname{cos}\left(\psi\right))\mathbf{\hat{e}_x} + (\left(\left(u - y \dot{\psi}\right) \dot{\psi} + x \ddot{\psi} + \dot{\psi} \dot{x} + \dot{v}\right) \operatorname{cos}\left(\psi\right) + \left(- \left(v + x \dot{\psi}\right) \dot{\psi} - y \ddot{\psi} - \dot{\psi} \dot{y} + \dot{u}\right) \operatorname{sin}\left(\psi\right))\mathbf{\hat{e}_y}$$

```python
CG = me.Point('CG')
CG.set_pos(O,value = x_cg*ship_frame.x)
CG.v2pt_theory(otherpoint=O,outframe=earth_frame,fixedframe=ship_frame)

CG.acc(earth_frame).express(earth_frame)
```

$$(\left(- \left(x_{cg} \dot{\psi} + v + x \dot{\psi}\right) \dot{\psi} - y \ddot{\psi} - \dot{\psi} \dot{y} + \dot{u}\right) \operatorname{cos}\left(\psi\right) - \left(x_{cg} \ddot{\psi} + \left(u - y \dot{\psi}\right) \dot{\psi} + x \ddot{\psi} + \dot{\psi} \dot{x} + \dot{v}\right) \operatorname{sin}\left(\psi\right))\mathbf{\hat{e}_x} + (\left(- \left(x_{cg} \dot{\psi} + v + x \dot{\psi}\right) \dot{\psi} - y \ddot{\psi} - \dot{\psi} \dot{y} + \dot{u}\right) \operatorname{sin}\left(\psi\right) + \left(x_{cg} \ddot{\psi} + \left(u - y \dot{\psi}\right) \dot{\psi} + x \ddot{\psi} + \dot{\psi} \dot{x} + \dot{v}\right) \operatorname{cos}\left(\psi\right))\mathbf{\hat{e}_y}$$

```python
kinematical_differential_equations = [u - x.diff(),
                                      v - y.diff(),
                                      r - psi.diff()]
coordinates = [x,y,psi,]
speeds = [u,v,r,]
```

```python
kane = me.KanesMethod(earth_frame,coordinates, speeds,kinematical_differential_equations)
```

## Manoeuver derivatives

```python
delta_R = sym.symbols('\delta_{R}')
eta = sym.symbols('\eta')

Xvv = sym.symbols('X_{vv}')
Xvr = sym.symbols('X_{vr}')
Xvveta = sym.symbols('X_{vv\eta}')
Xdd = sym.symbols('X_{\delta\delta}')
Xddetaeta = sym.symbols('X_{\delta\delta\eta\eta}')

# X
Xudot = sym.symbols('X_{\dot{u}}')
a_i = sym.symbols('a_{i}')
b_i = sym.symbols('b_{i}')
c_i = sym.symbols('c_{i}')
a1 = sym.symbols('a_{1}')

b1 = sym.symbols('b_{1}')
c1 = sym.symbols('c_{1}')
a2 = sym.symbols('a_{2}')
b2 = sym.symbols('b_{2}')
c2 = sym.symbols('c_{2}')
a3 = sym.symbols('a_{3}')
b3 = sym.symbols('b_{3}')
c3 = sym.symbols('c_{3}')
a4 = sym.symbols('a_{4}')
b4 = sym.symbols('b_{4}')
c4 = sym.symbols('c_{4}')

Xrr = sym.symbols('X_{rr}')
Xvv = sym.symbols('X_{vv}')
Xvr = sym.symbols('X_{vr}')
Xvveta = sym.symbols('X_{vv\eta}')
Xdeltadelta = sym.symbols('X_{\delta\delta}')
Xddetaeta = sym.symbols('X_{\delta\delta\eta\eta}')

# Y
Yvdot = sym.symbols('Y_{\dot{v}}')
Yrdot = sym.symbols('Y_{\dot{r}}')

Yv = sym.symbols('Y_{v}')
Yr = sym.symbols('Y_{r}')
Yvav = sym.symbols('Y_{v\abs{v}}')
Yvar = sym.symbols('Y_{v\abs{r}}')

Yveta = sym.symbols('Y_{v\eta}')
Yreta = sym.symbols('Y_{r\eta}')
Yvaveta = sym.symbols('Y_{v\abs{v}\eta}')

Ystar = sym.symbols('Y_{*}')
Ystareta = sym.symbols('Y_{*\eta}')

Ydelta = sym.symbols('Y_{\delta}')
Ydeltaar = sym.symbols('Y_{\delta\abs{r}}')
Ydeltaeta = sym.symbols('Y_{\delta\eta}')

# N
Nrdot = sym.symbols('N_{\dot{r}}')
Nvdot = sym.symbols('N_{\dot{v}}')

Nr = sym.symbols('N_{r}')
Nv = sym.symbols('N_{v}')
Nrar = sym.symbols('N_{r\abs{r}}')
Nvav = sym.symbols('N_{v\abs{v}}')
Nvar = sym.symbols('N_{v\abs{r}}')

Nreta = sym.symbols('N_{r\eta}')
Nveta = sym.symbols('N_{v\eta}')
Nvaveta = sym.symbols('N_{v\abs{v}\eta}')

Ndelta = sym.symbols('N_{\delta}')
Ndeltaar = sym.symbols('N_{\delta\abs{r}}')
Ndeltaeta = sym.symbols('N_{\delta\eta}')

Nstar = sym.symbols('N_{*}')
Nstareta = sym.symbols('N_{*\eta}')

```

## Fx

```python
equation_Fx = (
#Xudot*u.diff()
#+ (a_i + b_i*eta + c_i*eta**2)
+ (a1 + b1*eta + c1*eta**2)

+ Xrr*r**2 + Xvv*v**2 + Xvr*v*r
+ Xvveta*v**2*(eta-1)
+ Xdeltadelta*delta_R**2
+ Xddetaeta*(delta_R**2*eta**2)
)

a_i = a1  # Assume this now ...
b_i = b1
c_i = c1

equation_Fx
```

$$X_{\delta\delta\eta\eta} \delta_{R}^{2} \eta^{2} + X_{\delta\delta} \delta_{R}^{2} + X_{rr} r^{2}{\left (t \right )} + X_{vr} r{\left (t \right )} v{\left (t \right )} + X_{vv\eta} \left(\eta - 1\right) v^{2}{\left (t \right )} + X_{vv} v^{2}{\left (t \right )} + \eta^{2} c_{1} + \eta b_{1} + a_{1}$$

## Fy

```python
equation_Fy = (
#Yvdot*v.diff()
#+ Yrdot*r.diff()
+ Yv*v + Yr*r + Yvav*v*Abs(v) + Yvar*v*Abs(r)
+ (Yveta*v + Yreta*r + Yvaveta*v*Abs(v))*(eta - 1)
+ Ydelta*delta_R + Ydeltaar*delta_R*Abs(r) + Ydeltaeta*delta_R*(eta - 1)
+ Ystar + Ystareta*(eta - 1)

)
equation_Fy
```

$$Y_{*\eta} \left(\eta - 1\right) + Y_{*} + Y_{\deltabs{r}} \delta_{R} \left|{r{\left (t \right )}}\right| + Y_{\delta\eta} \delta_{R} \left(\eta - 1\right) + Y_{\delta} \delta_{R} + Y_{r} r{\left (t \right )} + Y_{vbs{r}} v{\left (t \right )} \left|{r{\left (t \right )}}\right| + Y_{vbs{v}} v{\left (t \right )} \left|{v{\left (t \right )}}\right| + Y_{v} v{\left (t \right )} + \left(\eta - 1\right) \left(Y_{r\eta} r{\left (t \right )} + Y_{vbs{v}\eta} v{\left (t \right )} \left|{v{\left (t \right )}}\right| + Y_{v\eta} v{\left (t \right )}\right)$$

## Mz

```python
equation_Mz = (
#Nrdot*r.diff() + Nvdot*v.diff()
+ Nr*r + Nv*v
+ Nrar*r*Abs(r) + Nvav*v*Abs(v) + Nvar*v*Abs(r)
+ (Nreta*r + Nveta*v + Nvaveta*v*Abs(v))*(eta - 1)
+ Ndelta*delta_R + Ndeltaar*delta_R*Abs(r) + Ndeltaeta*(eta - 1)
+ Nstar + Nstareta*(eta - 1)
)

equation_Mz
```

$$N_{*\eta} \left(\eta - 1\right) + N_{*} + N_{\deltabs{r}} \delta_{R} \left|{r{\left (t \right )}}\right| + N_{\delta\eta} \left(\eta - 1\right) + N_{\delta} \delta_{R} + N_{rbs{r}} r{\left (t \right )} \left|{r{\left (t \right )}}\right| + N_{r} r{\left (t \right )} + N_{vbs{r}} v{\left (t \right )} \left|{r{\left (t \right )}}\right| + N_{vbs{v}} v{\left (t \right )} \left|{v{\left (t \right )}}\right| + N_{v} v{\left (t \right )} + \left(\eta - 1\right) \left(N_{r\eta} r{\left (t \right )} + N_{vbs{v}\eta} v{\left (t \right )} \left|{v{\left (t \right )}}\right| + N_{v\eta} v{\left (t \right )}\right)$$

```python
Fx = (O,equation_Fx*ship_frame.x)
Fy = (O,equation_Fy*ship_frame.y)
Mz = (ship_frame, equation_Mz * ship_frame.z)
```

```python
forces = [Fx,Fy,Mz]

#mass = me.Particle('m', CG, m)
```

```python
inertia_dyadic = me.inertia(ship_frame, ixx = 0, iyy = 0, izz = Izz)
inertia_dyadic
```

$$Izz\mathbf{\hat{s}_z}\otimes \mathbf{\hat{s}_z}$$

```python
inertia = (inertia_dyadic, CG)
```

```python
ship = me.RigidBody('ship',masscenter=CG,frame = ship_frame,mass = m,inertia=inertia)
```

```python
fr, frstar = kane.kanes_equations(bodies = [ship],loads = forces)
```

```python
M = kane.mass_matrix_full
M
```

$$\left[\begin{matrix}1 & 0 & 0 & 0 & 0 & 0\\0 & 1 & 0 & 0 & 0 & 0\\0 & 0 & 1 & 0 & 0 & 0\\0 & 0 & 0 & m & 0 & - m y{\left (t \right )}\\0 & 0 & 0 & 0 & m & m \left(x_{cg} + x{\left (t \right )}\right)\\0 & 0 & 0 & - m y{\left (t \right )} & m \left(x_{cg} + x{\left (t \right )}\right) & Izz + m \left(\left(x_{cg} + x{\left (t \right )}\right)^{2} + y^{2}{\left (t \right )}\right)\end{matrix}\right]$$

```python
f = kane.forcing_full
f
```

$$\left[\begin{matrix}u{\left (t \right )}\\v{\left (t \right )}\\r{\left (t \right )}\\X_{\delta\delta\eta\eta} \delta_{R}^{2} \eta^{2} + X_{\delta\delta} \delta_{R}^{2} + X_{rr} r^{2}{\left (t \right )} + X_{vr} r{\left (t \right )} v{\left (t \right )} + X_{vv\eta} \left(\eta - 1\right) v^{2}{\left (t \right )} + X_{vv} v^{2}{\left (t \right )} + \eta^{2} c_{1} + \eta b_{1} + a_{1} - m \left(- \left(x_{cg} r{\left (t \right )} + r{\left (t \right )} x{\left (t \right )} + v{\left (t \right )}\right) r{\left (t \right )} - r{\left (t \right )} v{\left (t \right )}\right)\\Y_{*\eta} \left(\eta - 1\right) + Y_{*} + Y_{\deltabs{r}} \delta_{R} \left|{r{\left (t \right )}}\right| + Y_{\delta\eta} \delta_{R} \left(\eta - 1\right) + Y_{\delta} \delta_{R} + Y_{r} r{\left (t \right )} + Y_{vbs{r}} v{\left (t \right )} \left|{r{\left (t \right )}}\right| + Y_{vbs{v}} v{\left (t \right )} \left|{v{\left (t \right )}}\right| + Y_{v} v{\left (t \right )} - m \left(\left(- r{\left (t \right )} y{\left (t \right )} + u{\left (t \right )}\right) r{\left (t \right )} + r{\left (t \right )} u{\left (t \right )}\right) + \left(\eta - 1\right) \left(Y_{r\eta} r{\left (t \right )} + Y_{vbs{v}\eta} v{\left (t \right )} \left|{v{\left (t \right )}}\right| + Y_{v\eta} v{\left (t \right )}\right)\\N_{*\eta} \left(\eta - 1\right) + N_{*} + N_{\deltabs{r}} \delta_{R} \left|{r{\left (t \right )}}\right| + N_{\delta\eta} \left(\eta - 1\right) + N_{\delta} \delta_{R} + N_{rbs{r}} r{\left (t \right )} \left|{r{\left (t \right )}}\right| + N_{r} r{\left (t \right )} + N_{vbs{r}} v{\left (t \right )} \left|{r{\left (t \right )}}\right| + N_{vbs{v}} v{\left (t \right )} \left|{v{\left (t \right )}}\right| + N_{v} v{\left (t \right )} - m \left(x_{cg} + x{\left (t \right )}\right) \left(\left(- r{\left (t \right )} y{\left (t \right )} + u{\left (t \right )}\right) r{\left (t \right )} + r{\left (t \right )} u{\left (t \right )}\right) + m \left(- \left(x_{cg} r{\left (t \right )} + r{\left (t \right )} x{\left (t \right )} + v{\left (t \right )}\right) r{\left (t \right )} - r{\left (t \right )} v{\left (t \right )}\right) y{\left (t \right )} + \left(\eta - 1\right) \left(N_{r\eta} r{\left (t \right )} + N_{vbs{v}\eta} v{\left (t \right )} \left|{v{\left (t \right )}}\right| + N_{v\eta} v{\left (t \right )}\right) - \left(X_{\delta\delta\eta\eta} \delta_{R}^{2} \eta^{2} + X_{\delta\delta} \delta_{R}^{2} + X_{rr} r^{2}{\left (t \right )} + X_{vr} r{\left (t \right )} v{\left (t \right )} + X_{vv\eta} \left(\eta - 1\right) v^{2}{\left (t \right )} + X_{vv} v^{2}{\left (t \right )} + \eta^{2} c_{1} + \eta b_{1} + a_{1}\right) y{\left (t \right )} + \left(Y_{*\eta} \left(\eta - 1\right) + Y_{*} + Y_{\deltabs{r}} \delta_{R} \left|{r{\left (t \right )}}\right| + Y_{\delta\eta} \delta_{R} \left(\eta - 1\right) + Y_{\delta} \delta_{R} + Y_{r} r{\left (t \right )} + Y_{vbs{r}} v{\left (t \right )} \left|{r{\left (t \right )}}\right| + Y_{vbs{v}} v{\left (t \right )} \left|{v{\left (t \right )}}\right| + Y_{v} v{\left (t \right )} + \left(\eta - 1\right) \left(Y_{r\eta} r{\left (t \right )} + Y_{vbs{v}\eta} v{\left (t \right )} \left|{v{\left (t \right )}}\right| + Y_{v\eta} v{\left (t \right )}\right)\right) x{\left (t \right )}\end{matrix}\right]$$

```python
(M.inv() * f)
```

$$\left[\begin{matrix}u{\left (t \right )}\\v{\left (t \right )}\\r{\left (t \right )}\\- \frac{m^{2} \left(x_{cg} + x{\left (t \right )}\right) \left(Y_{*\eta} \left(\eta - 1\right) + Y_{*} + Y_{\deltabs{r}} \delta_{R} \left|{r{\left (t \right )}}\right| + Y_{\delta\eta} \delta_{R} \left(\eta - 1\right) + Y_{\delta} \delta_{R} + Y_{r} r{\left (t \right )} + Y_{vbs{r}} v{\left (t \right )} \left|{r{\left (t \right )}}\right| + Y_{vbs{v}} v{\left (t \right )} \left|{v{\left (t \right )}}\right| + Y_{v} v{\left (t \right )} - m \left(\left(- r{\left (t \right )} y{\left (t \right )} + u{\left (t \right )}\right) r{\left (t \right )} + r{\left (t \right )} u{\left (t \right )}\right) + \left(\eta - 1\right) \left(Y_{r\eta} r{\left (t \right )} + Y_{vbs{v}\eta} v{\left (t \right )} \left|{v{\left (t \right )}}\right| + Y_{v\eta} v{\left (t \right )}\right)\right) y{\left (t \right )}}{- m^{3} \left(x_{cg} + x{\left (t \right )}\right)^{2} + m \left(- m^{2} y^{2}{\left (t \right )} + m \left(Izz + m \left(\left(x_{cg} + x{\left (t \right )}\right)^{2} + y^{2}{\left (t \right )}\right)\right)\right)} + \frac{m^{2} \left(N_{*\eta} \left(\eta - 1\right) + N_{*} + N_{\deltabs{r}} \delta_{R} \left|{r{\left (t \right )}}\right| + N_{\delta\eta} \left(\eta - 1\right) + N_{\delta} \delta_{R} + N_{rbs{r}} r{\left (t \right )} \left|{r{\left (t \right )}}\right| + N_{r} r{\left (t \right )} + N_{vbs{r}} v{\left (t \right )} \left|{r{\left (t \right )}}\right| + N_{vbs{v}} v{\left (t \right )} \left|{v{\left (t \right )}}\right| + N_{v} v{\left (t \right )} - m \left(x_{cg} + x{\left (t \right )}\right) \left(\left(- r{\left (t \right )} y{\left (t \right )} + u{\left (t \right )}\right) r{\left (t \right )} + r{\left (t \right )} u{\left (t \right )}\right) + m \left(- \left(x_{cg} r{\left (t \right )} + r{\left (t \right )} x{\left (t \right )} + v{\left (t \right )}\right) r{\left (t \right )} - r{\left (t \right )} v{\left (t \right )}\right) y{\left (t \right )} + \left(\eta - 1\right) \left(N_{r\eta} r{\left (t \right )} + N_{vbs{v}\eta} v{\left (t \right )} \left|{v{\left (t \right )}}\right| + N_{v\eta} v{\left (t \right )}\right) - \left(X_{\delta\delta\eta\eta} \delta_{R}^{2} \eta^{2} + X_{\delta\delta} \delta_{R}^{2} + X_{rr} r^{2}{\left (t \right )} + X_{vr} r{\left (t \right )} v{\left (t \right )} + X_{vv\eta} \left(\eta - 1\right) v^{2}{\left (t \right )} + X_{vv} v^{2}{\left (t \right )} + \eta^{2} c_{1} + \eta b_{1} + a_{1}\right) y{\left (t \right )} + \left(Y_{*\eta} \left(\eta - 1\right) + Y_{*} + Y_{\deltabs{r}} \delta_{R} \left|{r{\left (t \right )}}\right| + Y_{\delta\eta} \delta_{R} \left(\eta - 1\right) + Y_{\delta} \delta_{R} + Y_{r} r{\left (t \right )} + Y_{vbs{r}} v{\left (t \right )} \left|{r{\left (t \right )}}\right| + Y_{vbs{v}} v{\left (t \right )} \left|{v{\left (t \right )}}\right| + Y_{v} v{\left (t \right )} + \left(\eta - 1\right) \left(Y_{r\eta} r{\left (t \right )} + Y_{vbs{v}\eta} v{\left (t \right )} \left|{v{\left (t \right )}}\right| + Y_{v\eta} v{\left (t \right )}\right)\right) x{\left (t \right )}\right) y{\left (t \right )}}{- m^{3} \left(x_{cg} + x{\left (t \right )}\right)^{2} + m \left(- m^{2} y^{2}{\left (t \right )} + m \left(Izz + m \left(\left(x_{cg} + x{\left (t \right )}\right)^{2} + y^{2}{\left (t \right )}\right)\right)\right)} + \frac{\left(- m^{3} \left(x_{cg} + x{\left (t \right )}\right)^{2} + m^{3} y^{2}{\left (t \right )} + m \left(- m^{2} y^{2}{\left (t \right )} + m \left(Izz + m \left(\left(x_{cg} + x{\left (t \right )}\right)^{2} + y^{2}{\left (t \right )}\right)\right)\right)\right) \left(X_{\delta\delta\eta\eta} \delta_{R}^{2} \eta^{2} + X_{\delta\delta} \delta_{R}^{2} + X_{rr} r^{2}{\left (t \right )} + X_{vr} r{\left (t \right )} v{\left (t \right )} + X_{vv\eta} \left(\eta - 1\right) v^{2}{\left (t \right )} + X_{vv} v^{2}{\left (t \right )} + \eta^{2} c_{1} + \eta b_{1} + a_{1} - m \left(- \left(x_{cg} r{\left (t \right )} + r{\left (t \right )} x{\left (t \right )} + v{\left (t \right )}\right) r{\left (t \right )} - r{\left (t \right )} v{\left (t \right )}\right)\right)}{m \left(- m^{3} \left(x_{cg} + x{\left (t \right )}\right)^{2} + m \left(- m^{2} y^{2}{\left (t \right )} + m \left(Izz + m \left(\left(x_{cg} + x{\left (t \right )}\right)^{2} + y^{2}{\left (t \right )}\right)\right)\right)\right)}\\- \frac{m^{2} \left(x_{cg} + x{\left (t \right )}\right) \left(X_{\delta\delta\eta\eta} \delta_{R}^{2} \eta^{2} + X_{\delta\delta} \delta_{R}^{2} + X_{rr} r^{2}{\left (t \right )} + X_{vr} r{\left (t \right )} v{\left (t \right )} + X_{vv\eta} \left(\eta - 1\right) v^{2}{\left (t \right )} + X_{vv} v^{2}{\left (t \right )} + \eta^{2} c_{1} + \eta b_{1} + a_{1} - m \left(- \left(x_{cg} r{\left (t \right )} + r{\left (t \right )} x{\left (t \right )} + v{\left (t \right )}\right) r{\left (t \right )} - r{\left (t \right )} v{\left (t \right )}\right)\right) y{\left (t \right )}}{- m^{3} \left(x_{cg} + x{\left (t \right )}\right)^{2} + m \left(- m^{2} y^{2}{\left (t \right )} + m \left(Izz + m \left(\left(x_{cg} + x{\left (t \right )}\right)^{2} + y^{2}{\left (t \right )}\right)\right)\right)} - \frac{m^{2} \left(x_{cg} + x{\left (t \right )}\right) \left(N_{*\eta} \left(\eta - 1\right) + N_{*} + N_{\deltabs{r}} \delta_{R} \left|{r{\left (t \right )}}\right| + N_{\delta\eta} \left(\eta - 1\right) + N_{\delta} \delta_{R} + N_{rbs{r}} r{\left (t \right )} \left|{r{\left (t \right )}}\right| + N_{r} r{\left (t \right )} + N_{vbs{r}} v{\left (t \right )} \left|{r{\left (t \right )}}\right| + N_{vbs{v}} v{\left (t \right )} \left|{v{\left (t \right )}}\right| + N_{v} v{\left (t \right )} - m \left(x_{cg} + x{\left (t \right )}\right) \left(\left(- r{\left (t \right )} y{\left (t \right )} + u{\left (t \right )}\right) r{\left (t \right )} + r{\left (t \right )} u{\left (t \right )}\right) + m \left(- \left(x_{cg} r{\left (t \right )} + r{\left (t \right )} x{\left (t \right )} + v{\left (t \right )}\right) r{\left (t \right )} - r{\left (t \right )} v{\left (t \right )}\right) y{\left (t \right )} + \left(\eta - 1\right) \left(N_{r\eta} r{\left (t \right )} + N_{vbs{v}\eta} v{\left (t \right )} \left|{v{\left (t \right )}}\right| + N_{v\eta} v{\left (t \right )}\right) - \left(X_{\delta\delta\eta\eta} \delta_{R}^{2} \eta^{2} + X_{\delta\delta} \delta_{R}^{2} + X_{rr} r^{2}{\left (t \right )} + X_{vr} r{\left (t \right )} v{\left (t \right )} + X_{vv\eta} \left(\eta - 1\right) v^{2}{\left (t \right )} + X_{vv} v^{2}{\left (t \right )} + \eta^{2} c_{1} + \eta b_{1} + a_{1}\right) y{\left (t \right )} + \left(Y_{*\eta} \left(\eta - 1\right) + Y_{*} + Y_{\deltabs{r}} \delta_{R} \left|{r{\left (t \right )}}\right| + Y_{\delta\eta} \delta_{R} \left(\eta - 1\right) + Y_{\delta} \delta_{R} + Y_{r} r{\left (t \right )} + Y_{vbs{r}} v{\left (t \right )} \left|{r{\left (t \right )}}\right| + Y_{vbs{v}} v{\left (t \right )} \left|{v{\left (t \right )}}\right| + Y_{v} v{\left (t \right )} + \left(\eta - 1\right) \left(Y_{r\eta} r{\left (t \right )} + Y_{vbs{v}\eta} v{\left (t \right )} \left|{v{\left (t \right )}}\right| + Y_{v\eta} v{\left (t \right )}\right)\right) x{\left (t \right )}\right)}{- m^{3} \left(x_{cg} + x{\left (t \right )}\right)^{2} + m \left(- m^{2} y^{2}{\left (t \right )} + m \left(Izz + m \left(\left(x_{cg} + x{\left (t \right )}\right)^{2} + y^{2}{\left (t \right )}\right)\right)\right)} + \frac{\left(- m^{2} y^{2}{\left (t \right )} + m \left(Izz + m \left(\left(x_{cg} + x{\left (t \right )}\right)^{2} + y^{2}{\left (t \right )}\right)\right)\right) \left(Y_{*\eta} \left(\eta - 1\right) + Y_{*} + Y_{\deltabs{r}} \delta_{R} \left|{r{\left (t \right )}}\right| + Y_{\delta\eta} \delta_{R} \left(\eta - 1\right) + Y_{\delta} \delta_{R} + Y_{r} r{\left (t \right )} + Y_{vbs{r}} v{\left (t \right )} \left|{r{\left (t \right )}}\right| + Y_{vbs{v}} v{\left (t \right )} \left|{v{\left (t \right )}}\right| + Y_{v} v{\left (t \right )} - m \left(\left(- r{\left (t \right )} y{\left (t \right )} + u{\left (t \right )}\right) r{\left (t \right )} + r{\left (t \right )} u{\left (t \right )}\right) + \left(\eta - 1\right) \left(Y_{r\eta} r{\left (t \right )} + Y_{vbs{v}\eta} v{\left (t \right )} \left|{v{\left (t \right )}}\right| + Y_{v\eta} v{\left (t \right )}\right)\right)}{- m^{3} \left(x_{cg} + x{\left (t \right )}\right)^{2} + m \left(- m^{2} y^{2}{\left (t \right )} + m \left(Izz + m \left(\left(x_{cg} + x{\left (t \right )}\right)^{2} + y^{2}{\left (t \right )}\right)\right)\right)}\\- \frac{m^{2} \left(x_{cg} + x{\left (t \right )}\right) \left(Y_{*\eta} \left(\eta - 1\right) + Y_{*} + Y_{\deltabs{r}} \delta_{R} \left|{r{\left (t \right )}}\right| + Y_{\delta\eta} \delta_{R} \left(\eta - 1\right) + Y_{\delta} \delta_{R} + Y_{r} r{\left (t \right )} + Y_{vbs{r}} v{\left (t \right )} \left|{r{\left (t \right )}}\right| + Y_{vbs{v}} v{\left (t \right )} \left|{v{\left (t \right )}}\right| + Y_{v} v{\left (t \right )} - m \left(\left(- r{\left (t \right )} y{\left (t \right )} + u{\left (t \right )}\right) r{\left (t \right )} + r{\left (t \right )} u{\left (t \right )}\right) + \left(\eta - 1\right) \left(Y_{r\eta} r{\left (t \right )} + Y_{vbs{v}\eta} v{\left (t \right )} \left|{v{\left (t \right )}}\right| + Y_{v\eta} v{\left (t \right )}\right)\right)}{- m^{3} \left(x_{cg} + x{\left (t \right )}\right)^{2} + m \left(- m^{2} y^{2}{\left (t \right )} + m \left(Izz + m \left(\left(x_{cg} + x{\left (t \right )}\right)^{2} + y^{2}{\left (t \right )}\right)\right)\right)} + \frac{m^{2} \left(X_{\delta\delta\eta\eta} \delta_{R}^{2} \eta^{2} + X_{\delta\delta} \delta_{R}^{2} + X_{rr} r^{2}{\left (t \right )} + X_{vr} r{\left (t \right )} v{\left (t \right )} + X_{vv\eta} \left(\eta - 1\right) v^{2}{\left (t \right )} + X_{vv} v^{2}{\left (t \right )} + \eta^{2} c_{1} + \eta b_{1} + a_{1} - m \left(- \left(x_{cg} r{\left (t \right )} + r{\left (t \right )} x{\left (t \right )} + v{\left (t \right )}\right) r{\left (t \right )} - r{\left (t \right )} v{\left (t \right )}\right)\right) y{\left (t \right )}}{- m^{3} \left(x_{cg} + x{\left (t \right )}\right)^{2} + m \left(- m^{2} y^{2}{\left (t \right )} + m \left(Izz + m \left(\left(x_{cg} + x{\left (t \right )}\right)^{2} + y^{2}{\left (t \right )}\right)\right)\right)} + \frac{m^{2} \left(N_{*\eta} \left(\eta - 1\right) + N_{*} + N_{\deltabs{r}} \delta_{R} \left|{r{\left (t \right )}}\right| + N_{\delta\eta} \left(\eta - 1\right) + N_{\delta} \delta_{R} + N_{rbs{r}} r{\left (t \right )} \left|{r{\left (t \right )}}\right| + N_{r} r{\left (t \right )} + N_{vbs{r}} v{\left (t \right )} \left|{r{\left (t \right )}}\right| + N_{vbs{v}} v{\left (t \right )} \left|{v{\left (t \right )}}\right| + N_{v} v{\left (t \right )} - m \left(x_{cg} + x{\left (t \right )}\right) \left(\left(- r{\left (t \right )} y{\left (t \right )} + u{\left (t \right )}\right) r{\left (t \right )} + r{\left (t \right )} u{\left (t \right )}\right) + m \left(- \left(x_{cg} r{\left (t \right )} + r{\left (t \right )} x{\left (t \right )} + v{\left (t \right )}\right) r{\left (t \right )} - r{\left (t \right )} v{\left (t \right )}\right) y{\left (t \right )} + \left(\eta - 1\right) \left(N_{r\eta} r{\left (t \right )} + N_{vbs{v}\eta} v{\left (t \right )} \left|{v{\left (t \right )}}\right| + N_{v\eta} v{\left (t \right )}\right) - \left(X_{\delta\delta\eta\eta} \delta_{R}^{2} \eta^{2} + X_{\delta\delta} \delta_{R}^{2} + X_{rr} r^{2}{\left (t \right )} + X_{vr} r{\left (t \right )} v{\left (t \right )} + X_{vv\eta} \left(\eta - 1\right) v^{2}{\left (t \right )} + X_{vv} v^{2}{\left (t \right )} + \eta^{2} c_{1} + \eta b_{1} + a_{1}\right) y{\left (t \right )} + \left(Y_{*\eta} \left(\eta - 1\right) + Y_{*} + Y_{\deltabs{r}} \delta_{R} \left|{r{\left (t \right )}}\right| + Y_{\delta\eta} \delta_{R} \left(\eta - 1\right) + Y_{\delta} \delta_{R} + Y_{r} r{\left (t \right )} + Y_{vbs{r}} v{\left (t \right )} \left|{r{\left (t \right )}}\right| + Y_{vbs{v}} v{\left (t \right )} \left|{v{\left (t \right )}}\right| + Y_{v} v{\left (t \right )} + \left(\eta - 1\right) \left(Y_{r\eta} r{\left (t \right )} + Y_{vbs{v}\eta} v{\left (t \right )} \left|{v{\left (t \right )}}\right| + Y_{v\eta} v{\left (t \right )}\right)\right) x{\left (t \right )}\right)}{- m^{3} \left(x_{cg} + x{\left (t \right )}\right)^{2} + m \left(- m^{2} y^{2}{\left (t \right )} + m \left(Izz + m \left(\left(x_{cg} + x{\left (t \right )}\right)^{2} + y^{2}{\left (t \right )}\right)\right)\right)}\end{matrix}\right]$$

## Create a PyDy system

```python
derivatives = {
#Xudot: -0.00086,
Ydeltaar:0.0,
Nvav:0.00754,
a1: -0.001331,
Xvr:0.01095,
Yvar:-0.0152,
Nr:-0.00294,
b1: -0.001011,
Xvv:0.00287,
#Yrdot:-0.00025,
Nrar:0.0,
c1:0.001827,
Xdeltadelta:-0.001,
Ydelta:0.00416,
Nreta: 0.0,
#a2:-0.000894,
Xrr:0.0,
Ydeltaeta:0.00416,
Nvar:-0.00495,
#b2:-0.000649,
Xvveta:0.0,
Yreta:0.00138,
#Nvdot:-0.00005,
#c2:0.001543,
Xddetaeta:-0.00135,
Yveta:-0.00266,
Ndelta:-0.00216,
#a3:-0.000894,
#Yvdot:-0.0146,
Yvaveta:0.0,
Ndeltaeta:-0.00216,
#b3:0.001016,
Ystar:0.000063,
Ystareta:0.000063,
Nveta:0.00138,
#c3:0.0000004,
Yv:-0.011,
#Nrdot:-0.000964,
Nvaveta:0.0,
#a4:-0.001722,
Yvav:-0.0398,
Nstar:-0.000033,
Nreta:-0.00072,
#b4:-0.000619,
Yr:0.00394,
Nv:-0.00798,
Nstareta:-0.000033,
#c4: -0.000813,
}
derivatives
```

$$\left \{ N_{*\eta} : -3.3e-05, \quad N_{*} : -3.3e-05, \quad N_{\delta\eta} : -0.00216, \quad N_{\delta} : -0.00216, \quad N_{rbs{r}} : 0.0, \quad N_{r\eta} : -0.00072, \quad N_{r} : -0.00294, \quad N_{vbs{r}} : -0.00495, \quad N_{vbs{v}\eta} : 0.0, \quad N_{vbs{v}} : 0.00754, \quad N_{v\eta} : 0.00138, \quad N_{v} : -0.00798, \quad X_{\delta\delta\eta\eta} : -0.00135, \quad X_{\delta\delta} : -0.001, \quad X_{rr} : 0.0, \quad X_{vr} : 0.01095, \quad X_{vv\eta} : 0.0, \quad X_{vv} : 0.00287, \quad Y_{*\eta} : 6.3e-05, \quad Y_{*} : 6.3e-05, \quad Y_{\deltabs{r}} : 0.0, \quad Y_{\delta\eta} : 0.00416, \quad Y_{\delta} : 0.00416, \quad Y_{r\eta} : 0.00138, \quad Y_{r} : 0.00394, \quad Y_{vbs{r}} : -0.0152, \quad Y_{vbs{v}\eta} : 0.0, \quad Y_{vbs{v}} : -0.0398, \quad Y_{v\eta} : -0.00266, \quad Y_{v} : -0.011, \quad a_{1} : -0.001331, \quad b_{1} : -0.001011, \quad c_{1} : 0.001827\right \}$$

```python
displacement = 355600*10**3  # [kg]
lpp = 349.8  # [m]
k_zz = 0.3  # Guess
r_zz = k_zz*lpp
Izz_ = displacement*r_zz**2
V = 7

rho = 1025
m_prime = displacement / (1/2*lpp**3)
Izz_prime = displacement*k_zz**2 / (1/2*lpp**5)

properties = {
    m:m_prime,
    Izz:Izz_prime,

}

controls = {
delta_R:0.4,
eta:1,
}

constants = {}
constants.update(properties)
constants.update(controls)
constants.update(derivatives)

initial_conditions = {
    x:0,
    y:0,
    psi:0,
    u:1,
    v:0,
    r:0,
}

times = np.linspace(0,1000000,1000)
times_prime = V*times/lpp

sys = System(kane,constants=constants,initial_conditions=initial_conditions,times = times_prime)
```

```python
result = sys.integrate()
```

```python
fig,ax = plt.subplots()
ax.plot(times,result[:,0])
ax.set_xlabel('time [s]')
ax.set_ylabel('x [m]')
```

```python
p = O.pos_from(O0).express(earth_frame)
x0_ = p.dot(ship_frame.x)
x0_
```

$$\left(x{\left (t \right )} \sin{\left (\psi{\left (t \right )} \right )} + y{\left (t \right )} \cos{\left (\psi{\left (t \right )} \right )}\right) \sin{\left (\psi{\left (t \right )} \right )} + \left(x{\left (t \right )} \cos{\left (\psi{\left (t \right )} \right )} - y{\left (t \right )} \sin{\left (\psi{\left (t \right )} \right )}\right) \cos{\left (\psi{\left (t \right )} \right )}$$

```python
xt,yt,psit = sym.symbols('xt yt psit')
x0_t = x0_.subs([(x,xt),(y,yt),(psi,psit)])
x0_t
```

$$\left(xt \sin{\left (psit \right )} + yt \cos{\left (psit \right )}\right) \sin{\left (psit \right )} + \left(xt \cos{\left (psit \right )} - yt \sin{\left (psit \right )}\right) \cos{\left (psit \right )}$$

```python
y0_ = p.dot(ship_frame.y)
y0_
```

$$\left(x{\left (t \right )} \sin{\left (\psi{\left (t \right )} \right )} + y{\left (t \right )} \cos{\left (\psi{\left (t \right )} \right )}\right) \cos{\left (\psi{\left (t \right )} \right )} - \left(x{\left (t \right )} \cos{\left (\psi{\left (t \right )} \right )} - y{\left (t \right )} \sin{\left (\psi{\left (t \right )} \right )}\right) \sin{\left (\psi{\left (t \right )} \right )}$$

```python
y0_t = y0_.subs([(x,xt),(y,yt),(psi,psit)])
y0_t
```

$$\left(xt \sin{\left (psit \right )} + yt \cos{\left (psit \right )}\right) \cos{\left (psit \right )} - \left(xt \cos{\left (psit \right )} - yt \sin{\left (psit \right )}\right) \sin{\left (psit \right )}$$

```python
f_x0 = sym.lambdify((xt,yt,psit),x0_t,"numpy")
f_y0 = sym.lambdify((xt,yt,psit),y0_t,"numpy")

```

```python
x0_sim = f_x0(result[:,0],result[:,1],result[:,2])
```

```python
fig,ax = plt.subplots()
ax.plot(times,x0_sim)
ax.set_xlabel('time [s]')
ax.set_ylabel('x0 [m]')
```

```python
y0_sim = f_y0(result[:,0],result[:,1],result[:,2])
```

```python
fig,ax = plt.subplots()
ax.plot(times,y0_sim)
ax.set_xlabel('time [s]')
ax.set_ylabel('y0 [m]')
```

```python
fig,ax = plt.subplots()
ax.plot(y0_sim,x0_sim)
ax.set_xlabel('y0 [m]')
ax.set_ylabel('x0 [m]')
a = ax.axis('equal')
```

```python
N = 100
x_ = np.ones(N,)
y_ = np.zeros(N,)
psi_ = np.linspace(0,2*np.pi,N)

x0_ = f_x0(x_,y_,psi_)
y0_ = f_y0(x_,y_,psi_)

fig,ax = plt.subplots()
ax.plot(y0_,x0_)
ax.set_xlabel('y0 [m]')
ax.set_ylabel('x0 [m]')
a = ax.axis('equal')

```

```python
dcm = ship_frame.dcm(otherframe=earth_frame)
dcm
```

$$\left[\begin{matrix}\cos{\left (\psi{\left (t \right )} \right )} & \sin{\left (\psi{\left (t \right )} \right )} & 0\\- \sin{\left (\psi{\left (t \right )} \right )} & \cos{\left (\psi{\left (t \right )} \right )} & 0\\0 & 0 & 1\end{matrix}\right]$$

```python
xs = result[:,0]
ys = result[:,1]
zs = np.zeros(len(xs))
psis = result[:,2]
```

```python
x0s = []
y0s = []

for x,y,z,psi in zip(xs,ys,zs,psis):

    p = np.array([[x],[y]])
    M = np.array([[np.cos(psi),np.sin(psi)],[-np.sin(psi),np.cos(psi)]])
    p0 = M.dot(p)
    x0 = p0[0]
    y0 = p0[1]
    x0s.append(x0)
    y0s.append(y0)

```

```python
fig,ax = plt.subplots()
ax.plot(y0s,x0s)
ax.set_xlabel('y0 [m]')
ax.set_ylabel('x0 [m]')
a = ax.axis('equal')
```

```python
fig,ax = plt.subplots()
ax.plot(times_prime,psis)
```

```python

```


# COMP 135 day03 Lab: Beta distributions and the Beta-Bernoulli model for coin tosses

## Outline

* **Part 1: Beta Distribution visualization**
* * Examine plot of Beta PDF at various parameters a>0, b>0.
* * Exercises 1a-1c: Inspect samples from several possible Beta distributions. Make connections to the PDF plots.

* **Part 2: Beta-Bernoulli Posteriors**
* * Exercise 2a: Write function to determine the posterior parameters after seeing an array of binary obervations
* * Exercise 2b: Show the posterior after seeing 1, 10, 100, 1000, 10000 examples of a coin
* * Exercise 2d: Show the posterior after seeing 1, 10, 100, 1000, 10000 examples, with a different (misinformed) prior

## Takeaways

* Scipy.stats has *many* great resources for evaluating PDFs and for sampling
* * Evaluate pdf with `scipy.stats.<distribution>(hyperparameters).pdf(values)` (can use the `logpdf` instead of `pdf`)
* * Draw samples with `scipy.stats.<distribution>(hyperparameters).rvs(size=(nrows,ncols), random_state=SEED)`
* Visualization is an excellent way to understand the behavior of distributions
* Analyzing "fake data" drawn from the model is a great debugging strategy.

```python
import numpy as np
import scipy.stats
```

```python
import matplotlib.pyplot as plt
import seaborn as sns
sns.set_style("whitegrid")
sns.set_context("notebook", font_scale=1.25)
```

# Part 1: Beta Distribution Visualized

## Beta distribution as a PDF

#### Formal definition:

$$
\text{BetaPDF}(\mu | a, b) = c(a, b) \cdot \mu^{a-1} (1-\mu)^{b-1}
$$

#### Standard Numerical implementations

Thankfully, you can use the built-in functions from SciPy to compute this function:

* `stats.beta.pdf(\cdot)` computes the pdf$
* `stats.beta.logpdf(\cdot)` computes the natural log of the pdf
* * Will often be more numerically stable than doing `np.log(stats.beta.pdf(\cdot))`

See <https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.beta.html>

## Visualization: PDF across different values of hyperparameters a, b

We'll make a grid plot, where each grid cell shows the PDF for a single $a, b$ pair.

```python
agrid_G = np.asarray([0.01, 0.1, 0.5, 1.0, 2.0, 10.0, 99.0])
bgrid_H = np.asarray([0.01, 0.1, 0.5, 1.0, 2.0, 10.0, 99.0])
G = agrid_G.size
H = bgrid_H.size

mu_vals = np.linspace(0.0001, 0.9999, 101)

fig, axgrid = plt.subplots(nrows=len(agrid_G), ncols=len(bgrid_H), sharex=True, sharey=True, figsize=(12, 12));
axgrid = axgrid[::-1] # flip order of rows

for gg in range(G):
    for hh in range(H):
        # Evaluate Beta PDF at each candidate value of mu in "mu_vals"
        axgrid[gg, hh].plot(mu_vals, scipy.stats.beta(agrid_G[gg], bgrid_H[hh]).pdf(mu_vals), '-')
        if hh == 0:
            axgrid[gg, hh].set_ylabel('a = %.2g\n\n' % agrid_G[gg]);
        if gg == G - 1:
            axgrid[gg, hh].set_title('b = %.2g' % bgrid_H[hh]);
        if gg == 0:
            axgrid[gg, hh].set_xlabel('$\mu$');

        if agrid_G[gg] == 1.0:
            axgrid[gg, hh].spines['left'].set_linewidth(3)
            axgrid[gg, hh].spines['left'].set_color('darkgray')
        if bgrid_H[hh] == 1.0:
            axgrid[gg, hh].spines['bottom'].set_linewidth(3)
            axgrid[gg, hh].spines['bottom'].set_color('darkgray')

H = 3.0
B = 0.05
sns.despine(fig)
axgrid[0,0].set_ylim(0, H); axgrid[0,0].set_yticks(np.arange(0, H, 1.0));
axgrid[0,0].set_xlim(-B, 1+ B); axgrid[0,0].set_xticks([0, 0.5, 1.0]); axgrid[0,0].set_xticklabels(['0', '0.5', '1']);

#plt.savefig('day03-beta_pdf_viz.pdf', bbox_inches='tight', pad_inches=0);
```

## Discussion 1a: Inspect 20 samples from a Beta(10, 10). What do you notice about them?

```python
a = 10
b = 10
mu_samples = scipy.stats.beta(a,b).rvs(size=20, random_state=0)

np.set_printoptions(precision=3, suppress=True)
print(mu_samples)
```

    [0.597 0.707 0.459 0.549 0.509 0.625 0.238 0.762 0.504 0.603 0.502 0.534
     0.494 0.377 0.395 0.464 0.477 0.584 0.327 0.546]

TODO what do you notice? How does what you see agree or disagree with the PDF visualizations we created above?

## Exercise 1b: Draw 20 samples from a Beta(0.1, 0.1). What do you notice?

TODO write code to draw samples, and then discuss

### Exercise 1c: Draw 20 samples from a Beta (0.001, 0.001). What do you notice?

TODO write code to draw samples, and then discuss

# Part 2: Posterior visualization in the Beta-Bernoulli model

We'll assume the model discussed in class

\begin{align}
p(\mu) &= \text{Beta}( \mu | a, b)
\\
p(x_1, \ldots x_N | \mu) &= \prod_{n=1}^N \text{BernPMF}( x_n | \mu)
\end{align}

## Exercise 2a: Calculating posterior parameters

Using the formulas discussed in the textbook / video lecture, complete the function below to determine the parameters of the Beta posterior distribution after seeing N coin flip binary outcomes in array `x_N`

```python
def calc_posterior_parameters(x_N, a, b):
    ''' Compute posterior parameters

    Args
    ----
    x_N : 1D array, size N
        Contains binary observations for each of N coin tosses
    a : float, positive
        Prior parameter, number of heads "pseudocounts"
    b : float, positive
        Prior parameter, number of tails "pseudocounts"

    Returns
    -------
    ahat : positive scalar
    bhat : positive scalar
    '''
    ahat = a # TODO fixme
    bhat = b # TODO fixme
    return ahat, bhat
```

## Draw 'fake' data from model: a coin parameter mu and N observed flips

```python
# Define a random generator object, with specific seed for reproducibility
prng = np.random.RandomState(101)

# Settings
N = 10000   # num coin tosses to observe
a = 1      # prior # heads
b = 1      # prior # tails

# Sample a "true" coin frequency from our Beta prior
true_mu = scipy.stats.beta(a,b).rvs(random_state=prng)

# Sample N examples from Bernoulli given mu
x_N = scipy.stats.bernoulli(true_mu).rvs(size=N, random_state=prng)
# OR...
# x_N = prng.rand(N) < true_mu # another equivalent way to draw N samples from a Bernoulli with parameter 'true_mu'
```

## Exercise 2b: Visualizing Posterior given the *true*, uniform prior

Complete the code below, to visualize the posterior over mu for our "fake data" `x_N`

```python
Npartial_list = [0, 3, 10, 33, 100, 333, 1000, 10000]
L = len(Npartial_list)

fig, axgrid = plt.subplots(
    nrows=1, ncols=L,
    sharex=True, sharey=False,
    figsize=(3 * L, 3))

# Define a grid of mu values at which we'll evaluate the PDF
G = 201
mu_grid_G = np.linspace(0.0001, 0.9999, G)
for aa, Npartial in enumerate(Npartial_list):
    # Compute the parameters of the Beta posterior distribiution, using only first Npartial observations
    ahat, bhat = calc_posterior_parameters(x_N[:Npartial], a, b)

    # Call the PDF function built into scipy.stats.beta, using ahat and bhat as parameters
    # Make sure you evaluate the PDF for each of the G values in the mu_grid_G array
    pdf_grid_G = np.zeros(G) # TODO fix me.

    axgrid[aa].plot(mu_grid_G, pdf_grid_G, 'b-')
    axgrid[aa].set_title("after N = %d coins" % Npartial)
plt.tight_layout();
```

## Discussion 2c : As N gets larger, what happens to the posterior? How does this compare to what happens to the ML estimator? How does this relate to the "true" mu value that generated the data?

TODO discuss with your group!

## Exercise 2d: Visualizing Posterior given a misinformed prior

Repeat Exercise 2b using the *same* fake data, but act as if your prior was instead $a=20, b=3$ (in other words, quite confident in prior belief that the coin would be heads rather than tails.

Visualize the posterior over mu for our "fake data" `x_N`, after seeing each of the Npartial examples as in 2b.

```python
abad = 20
bbad =  3

# TODO adapt code from Exercise 2b, produce a row plot of 7 subplots, one for each Npartial value
```

```python

```

## Discussion 2e: Does having a misinformed prior matter? Discuss how your answer might depend on the amount of available data.

```python

```

```python

```


```python
import sys
sys.path.append('..')
import numpy as np
from IPython.display import display, HTML
```

# Visualizing Automatic Differentiation

Understanding how Automatic Differentation works can be daunting at first. To help the user understand Automatic Differentiation better, we have implemented a way to visualize each step of the algorithm. The user can visualize the algorithm in two ways: Computational Graph and Computational Table.

In this example, we will quickly outline how you can visualize the algorithm using these functions.

The examples are taken from the Harvard CS207 Course Repository[https://github.com/IACS-CS-207/cs207-F18/blob/master/lectures/L09/L9-Solutions.ipynb].

# Example 1:

Consider the example function
\begin{align}
f(x) = x - exp(-2sin^2(4x))
\end{align}

The user can initialize the function using our AD package as following:

```python
from autodiff import *

x = Variable("x")
f = x - exp(-2*sin(4*x) ** 2)
```

One way to visualize what is going on is to represent the evaluation trace with a graph:

```python
graph_f = f.get_comp_graph()
graph_f
```

Note that the triangular nodes represent constants, the square nodes represent the variables, and the circular nodes represent operators.

Let's find $f(\frac{\pi}{16})$. We can find the value by using the AD package again.

```python
f(x = np.pi/16).value()
```

    -0.17152990032208032

We can also examine the evaluation trace:

```python
table_f = f.get_comp_table()
table_f
```

      Trace
      Elementary Function
      Current Value
      Grad x value

      0
      x_1
      x
      0.196350
      1.00

      1
      x_2
      2
      2.000000
      0.00

      2
      x_3
      4
      4.000000
      0.00

      3
      x_4
      Multiplication(x_3,x_1)
      0.785398
      4.00

      4
      x_5
      Sine(x_4)
      0.707107
      2.83

      5
      x_6
      Power(x_5,x_2)
      0.500000
      4.00

      6
      x_7
      -2
      -2.000000
      0.00

      7
      x_8
      Multiplication(x_7,x_6)
      -1.000000
      -8.00

      8
      x_9
      Exponential(x_8)
      0.367879
      -2.94

      9
      x_10
      Subtraction(x_1,x_9)
      -0.171530
      3.94

Note that the value for the last row in the table is equal to our value from the AD package.

# Example 2:

Consider the example function
\begin{align}
f(x, y) = exp(-(sin(x) - cos(y))^2)
\end{align}

1. Draw the computational graph for the function provided.
2. Evaluate $f\left(\dfrac{\pi}{2}, \dfrac{\pi}{3}\right)$.
3. Compute $\dfrac{\partial f}{\partial x}\left(\dfrac{\pi}{2}, \dfrac{\pi}{3}\right)$ and $\dfrac{\partial f}{\partial y}\left(\dfrac{\pi}{2}, \dfrac{\pi}{3}\right)$ using the forward mode of AD.

```python
x = Variable("x")
y = Variable("y")
f = exp(-(sin(x) - cos(y))**2)
```

### 1. Draw the computational graph for the function provided.

```python
graph_f = f.get_comp_graph()
graph_f
```

### 2, Evaluate $f\left(\dfrac{\pi}{2}, \dfrac{\pi}{3}\right)$.

```python
f(x = np.pi/2, y = np.pi/3).value()
```

    0.778800783071405

### 3. Compute $\frac{\partial f}{\partial x}\left(\frac{\pi}{2}, \frac{\pi}{3}\right)$ and $\frac{\partial f}{\partial y}\left(\frac{\pi}{2}, \frac{\pi}{3}\right)$ using the forward mode of AD.

```python
gradients = f(x = np.pi/2, y = np.pi/3).derivative()
```

```python
gradients["x"]
```

    -4.7687794308092406e-17

```python
gradients["y"]
```

    -0.6744612626270503

We can compare this with our computational table.

```python
f(x = np.pi/2, y = np.pi/3).get_comp_table()
```

      Trace
      Elementary Function
      Current Value
      Grad x value
      Grad y value

      0
      x_1
      x
      1.570796
      1.0
      0.00

      1
      x_2
      y
      1.047198
      0.0
      1.00

      2
      x_3
      2
      2.000000
      0.0
      0.00

      3
      x_4
      Cosine(x_2)
      0.500000
      0.0
      -0.87

      4
      x_5
      Sine(x_1)
      1.000000
      0.0
      0.00

      5
      x_6
      Subtraction(x_5,x_4)
      0.500000
      0.0
      0.87

      6
      x_7
      Power(x_6,x_3)
      0.250000
      0.0
      0.87

      7
      x_8
      Negation(x_7)
      -0.250000
      -0.0
      -0.87

      8
      x_9
      Exponential(x_8)
      0.778801
      -0.0
      -0.67

Note that the value and gradients are equal to that from the computational table.


# Photon Statistics
> Photon Statistics and the unintuitive way photons behave when they arrive sparsely

- toc: true
- badges: false
- comments: true
- permalink: /photon-statistics
- image: images/photon-statistics.png
- sectionnr: 4

Because of the inherent probability quantum mechanics entails, photons arriving at some detector follow statistical properties. In this chapter I will discuss these *Photon Statistics* and the unintuitive way photons behave when they arrive sparsely.

## Photon Noise

In order to quantify the sensitivity of a DESHIMA type spectrometer, we need to know the amount of noise that is generated by the aforementioned atmospheric and optical loading and compare this to the signal that an observation will impart on the detector. For our purposes this noise consists of photon noise and quasiparticle recombination noise, the first of which can be split up into two categories: thermal noise and shot noise.

>An observed astronomical object also emits random photons that reach the detector, therefore contributing to photon noise{% cite perley_2008 %}. While this won't be included in the model, as the model is designed such that it calculates the noise when off-source, the following topics in this chapter are equally valid for photon noise emitted and caused by the astronomical source.

### Thermal Noise

In radio receivers, a big part of the noise is generated by thermal agitation of electrons. In most uses this can be classified as white noise: the power is constant across its frequency spectrum {% cite turner_2007 %}, but in extremely high frequencies or low temperatures this approximation doesn't hold. The power $P$ transmitted by the noise is given as Johnson-Nyquist Noise and is approximated for small bandwidths $\Delta\nu$ as{% cite endo\_session\_4 %}:

$$\begin{equation}
P=\int_{\nu_0}^{\nu_1}\frac{h\nu}{e^{h\nu/k_BT}-1}d\nu \approx \frac{h\nu}{e^{h\nu/k_BT}-1}\Delta\nu = P_\nu\Delta\nu
\end{equation}$$

With $P_\nu$ the power spectrum in $\mathrm{[WHz^{-1}]}$. Plotting the normalized ($P_\nu/k_BT$) power spectrum supports my earlier statements that, in  most cases, thermal noise can be approximated as white

```python
#hide
!pip install -q galspec numpy pandas plotly opensimplex scipy

import numpy as np
import pandas as pd
import plotly.express as px

import warnings
warnings.filterwarnings('ignore')

from thesis_plot import plot

# Page specific
import random as rng
from opensimplex import OpenSimplex
from scipy.stats import cauchy
from scipy.special import gamma
```

```python
#hide
h = 6.626e-34
k_B = 1.381e-23
```

```python
#hide_input
nu=np.logspace(1,13,100)[np.newaxis].T
T=np.array([1,10,50,100,273])

P = h*nu/(np.exp(h*nu/(k_B*T))-1)

df = pd.DataFrame(P/(T*k_B))
df.columns = T
df['nu']=nu#*1e-9
df.set_index('nu',inplace=True)

plot(df,axis_labels=('$\\nu\:\mathrm{[Hz]}$','$\\frac{P_\\nu}{k_BT}$','$T\:\mathrm{[K]}$'),hover_labels=('nu [Hz]','normalized PSD','T [K]'),plot_type='linlog')

```

***Comparing the normalized Johnson-Nyquist noise for different temperatures shows that it is white for low frequencies***

Unfortunately, this white noise approximation starts to break down in atmospheric temperature for the DESHIMA range. Once more, it is also not the only form of noise we have to deal with.

### Shot noise

When the source is very dim, photons arrive one at a time. This means that we are dealing with shot noise{% cite perley_2008%}. Here the detection rate of a photon is characterized by Poisson statistics and the famous Poisson distribution {% cite dekking_2011%}:

$$\begin{equation}
f(x)=\frac{\lambda^xe^{-\lambda}}{x!}
\end{equation}$$

Hence we also call this noise Poisson noise.

```python
#hide_input
x=np.linspace(0,20,100)[np.newaxis].T
l=np.array([1,2,5,10])

f = np.power(l,x)*np.exp(-l)/gamma(x+1)

df = pd.DataFrame(f)
df.columns = l
df['x']=x
df.set_index('x',inplace=True)

plot(df,axis_labels=('$x$','$f(x)$','$\lambda$'),hover_labels=('x','f(x)','lambda'),plot_type='linlin',e_notation=[False,False])
```

***Typical Poisson curves for different parameters.***

### Uncertainty

From Poisson statistics we know that the uncertainty $\sigma_N$ over some number of counts or occurrences $N$ is equal to the square root of the number of counts, since it's an uncorrelated process {% cite perley_2008%}{% cite dekking_2011%}. Because our power arrives with single photons hitting the detector, the power follows the same uncertainty

$$\begin{equation}
\sigma_N=\sqrt{N}
\end{equation}$$
$$\begin{equation}
\sigma_P \propto \sqrt{P}
\end{equation}$$

However, if we look at Johnson-Nyquist noise and approximate for lower frequencies we get{% cite perley_2008%}{% cite endo_session_4%}:

$$\begin{equation}
P \approx \frac{h\nu}{e^{h\nu/k_BT}-1}\Delta\nu \approx k_BT\Delta\nu
\end{equation}$$$$\begin{equation}
\sigma_P \propto P
\end{equation}$$

Hence we have found two proportionality relations for the uncertainty, which is it?

#### Particle-Wave duality

The seemingly paradoxical nature of this uncertainty can be explained as a caveat of the particle-wave duality. In the wave domain the Johnson-Nyquist noise dominates, whereas in the particle domain we get mainly Poisson noise. The general case of the uncertainty in the *rate* of photons arriving within a detection time of $\tau$ is{% cite endo_memo_2020%}{% cite paul_2004%}:

$$\begin{equation}
\sigma_\mathrm{ph}= \frac{1}{\sqrt{\tau}}\sqrt{n^2+n}\label{simple_sigma_ph}
\end{equation}$$

with $n$, the photon number, being the number of photons arriving per time per bandwidth. Here both extremes are more obvious: for $n\gg 1$ we get the thermal noise from the wave domain and for $n \ll 1$ the Poisson uncertainty falls out.

***An analogy for the detection of photons in the particle and wave limit. Image taken from {% cite perley_2008 %}***

The photon number for a thermal blackbody radiator is given by the Bose-Einstein equation {% cite zmuidzinas_2003%}{% cite paul_2004%}

$$\begin{equation}
n_\mathrm{th}\left(\nu,T\right)=\frac{1}{e^{h\nu/k_BT}-1}
\end{equation}$$

Which means that in the region between $220\:{\mathrm{GHz}}$ and $440\:{\mathrm{GHz}}$ the photon occupation number is neither fully in the wave limit nor the particle limit.

```python
#hide_input
nu=np.linspace(1.2e11,5.4e11,100)[np.newaxis].T
#T=np.array([2, 5,10,25, 50])

n = 1/(np.exp(h*nu/(k_B*T))-1)

df = pd.DataFrame(n)
df.columns = T
df['nu']=nu*1e-9
df.set_index('nu',inplace=True)

plot(df,axis_labels=('$\\nu\:\mathrm{[GHz]}$','$n_\mathrm{th}$','$T\:\mathrm{[K]}$'),hover_labels=('nu [GHz]','n','T [K]'),plot_type='linlog')
```

***The photon number for different temperatures is neither fully in the wave limit nor fully in the particle limit in the DESHIMA range***

In the wave domain, where the photon number is higher, photons tend to come clumped together in a process known as photon bunching{% cite paul_2004%}, which explains the increase in noise.

## Photon Bunching

Quantum Electrodynamics has taught us that photons behave stochastically, so it is reasonable to assume that photon detection also occurs randomly. While this is (obviously) true to some degree, in the previous section I stated photons can arrive 'clumped' together at the detector in a phenomenon known as photon bunching. This means that detecting one photon will result in a higher chance of another photon being detected within a specified time, called the coherence time $t_{\mathrm{coh}}$ {%cite paul_2004%}. In this chapter I will first present an intuitive, *qualitative* explanation as to why photon bunching occurs. Then I will go deeper into the mathematics and quantum mechanics of photon bunching and the specific case of the DESHIMA system.

### An analogy

To understand photon bunching, first think of photons arriving at the detector like raindrops falling on a piece of paper. While it's raining with constant intensity, the chance of a raindrop falling on a specific area within, say, the next second (expressed by $P(\mathrm{drop})$) is constant. In the figure below I have set $P(\mathrm{drop}) = 0.3$:

```python
#hide

T = 10000
T_sel = 100
t = np.arange(0,T,1)
np.random.seed(1)
rand = np.random.rand(t.size)

def noise_array(length, scale):
    array = np.empty(length)
    tmp = OpenSimplex(seed=0)#rng.getrandbits(32))
    for i in range(length):
        array[i] = tmp.noise2d(x=i*scale, y=10)
    return array
```

```python
#hide_input
def plot_single(p_rnd):
    global t
    global rand
    global T_sel

    fig = px.scatter(pd.DataFrame((1+(rand[:T_sel]>p_rnd[:T_sel])*-2)*0.5),
                     labels = {'index': '$t$','value': '$P(\mathrm{drop})$'},
                     template = 'plotly_white')
    fig.update_traces(marker={'size': 20, 'opacity': 0.75},
                      hovertemplate= '<b>drop</b><br>Time: %{x}<extra></extra>')

    fig.add_scatter(x=t[:T_sel], y=p_rnd[:T_sel],line_shape='spline', hovertemplate= '<b>P(drop)</b><br>Time: %{x}<br>Probability: %{y:.2f}<extra></extra>')

    fig.update_layout(yaxis = {'range': [0,1.1], 'fixedrange': True},
                     showlegend=False,
                     hoverlabel={'bgcolor': "white", 'font_size': 14})
    fig.show()
```

```python
#hide_input
def random_drops(T):

    p_rnd = np.ones(T)*0.35
    return p_rnd

random = random_drops(T)
plot_single(random)
```

***Plotting occurrences and the underlying probability for a constant chance.***

The raindrops fall uncorrelated: every second a new chance of $P(\mathrm{drop})$ means a raindrop falling from the sky might land in our defined area. This is how unbunched, or random, photons behave.

Now let's assume the chance of a raindrop occurring is not constant over time, but rather varies relatively slowly over time. This could be because of varying wind speeds or varying intensity in the rain, but whatever the case the rain drops are no longer uncorrelated. There are moments of high intensity, where the chance of a raindrop is high and similarly there are moments of low intensity. I have simulated this using open simplex noise {% cite opensimplex %} in the following graph.

```python
#hide_input
def noise_drops(T):
    p_rnd = noise_array(T,0.05)*0.35 + 0.35
    return p_rnd

noise = noise_drops(T)
plot_single(noise)
```

***Plotting occurrences and the underlying probability for a varying chance.***

As is clear from the graph, the raindrops are clumped together. This is the same underlying principle as photon bunching in photons from astronomical sources. The random motion of the exciting atoms, for example Brownian motion, means that the intensity also fluctuates over time{% cite paul_2004 %}. It is therefore not the act of detecting a photon that increases the chance of another detection, but rather an underlying change in probability of detecting, easily explained by probabilistics.

This means that at the point where a raindrop falls or a photon gets emitted, it is still a simple stochastic process. Should our sampling interval be much smaller than the time over which the rain varies, we'd still see 'stochastic regions', where the probability function stays roughly constant, like in the first example. This is illustrated in the graph below with stretched noise:

```python
#hide_input
def incoherent_noise_drops(T):
    p_rnd = noise_array(T,0.01)*.35 + 0.35
    return p_rnd

incoherent = incoherent_noise_drops(T)
plot_single(incoherent)
```

***Plotting occurrences and the underlying probability for a slowly varying chance.***

The droplets appear less bunched, because the time scale in which the measurements take place is much smaller than the time scale in which the intensity changes. The very first example I spoke about, of raindrops falling at a constant intensity, is a situation in which this discrepancy occurs. Of course it won't rain forever and when the rain dies down the chance of a raindrop falling is *much* lower than while it's still raining. So while the droplets look purely random when it rains, they aren't over the course of the entire day. I will call this behavior *bunched (slow timescale)*, because the sampling time is much faster than the coherence time, which I shall later elaborate on further.

Let's also take a look at what happens when the sampling time is much slower than the underlying probability fluctuations.

```python
#hide_input
def fast_noise_drops(T):
    p_rnd = noise_array(T,1)*.35 + 0.35
    return p_rnd

fast = fast_noise_drops(T)
plot_single(fast)
```

***Plotting occurrences and the underlying probability for a quickly varying chance.***

As expected, the detection rate looks much like the first detection rate with constant $P\left(\mathrm{drop}\right)$. Although the underlying probability changes, it changes too fast between detections for bunching to occur.

#### Intermezzo: Antibunching

While this report focuses on photon bunching, the opposite also occurs in, for example, single photon sources {% cite Krottenmüller_2013 %}. Now that I have discussed a framework with which to explain photon bunching, I thought it would be remiss to leave out an explanation for antibunching.

As one would expect, in antibunching the opposite of bunching happens: detecting a raindrop would mean that, at least for the next period of time, the chance of a raindrop falling becomes smaller. A good allegory for this is a dripping faucet. A small flow of water exits the faucet and, due to surface tension, pools up in a small droplet at the end. As the droplet gets bigger, the chance of it falling increases, but once it drips down it takes all water with it and the process starts anew.

```python
#hide_input
def anti_drops(T):
    global rand

    p_slope=0.05

    p_rnd = np.zeros(T)

    p_rnd[0]=0
    for i in range(0,T-1):
        #print(rand[i])
        if rand[i]<p_rnd[i]:
           p_rnd[i+1] = 0
        else:
            p_rnd[i+1] = p_rnd[i] + p_slope
            #print(p_rnd)
    return p_rnd

anti = anti_drops(T)
plot_single(anti)
```

***An analogy for anti-bunching.***

This behavior corresponds with antibunching behavior in single photon sources{% cite Krottenmüller_2013%}. Atoms are excited through an energy pump process. When this energy increases sufficiently they emit a photon and lose energy, ready to get excited again{% cite paul_2004 %}.

Again, the detection of such single photons will display antibunching, but it is not the detection that triggers this *cool-down* period, it is the emission.

```python
#hide_input
drops = pd.DataFrame({'bunched': (1+(rand[:T_sel]>noise[:T_sel])*-2)*1.05, 'bunched (long timescale)': (1+(rand[:T_sel]>incoherent[:T_sel])*-2)*0.95,'bunched (short timescale)': (1+(rand[:T_sel]>fast[:T_sel])*-2)*0.85, 'random': (1+(rand[:T_sel]>random[:T_sel])*-2)*0.75, 'anti-bunched': (1+(rand[:T_sel]>anti[:T_sel])*-2)*0.65})

fig = px.scatter(drops,
                 labels = {'index': '$t$','value': '$P(\mathrm{drop})$', 'variable': 'Photon-mode'},
                 hover_data={'value': False},
                 template = 'plotly_white')

fig.update_traces(marker={'size': 20, 'opacity': 0.75},
                  hovertemplate= 'Time: %{x}<extra></extra>')

# Hacky update all traces because can't print trace name I guess
for trace in fig.data:
    trace['hovertemplate']= '<b>'+trace['name']+'</b><br>Time: %{x}<extra></extra>'

fig.add_scatter(x=t[:T_sel],
                y=noise[:T_sel],
                line_shape='spline',
                line = {'color': fig.data[0].marker.color},
                legendgroup = fig.data[0].legendgroup,
                showlegend=False,
                hovertemplate= '<b>'+fig.data[0].name+'</b><br>Time: %{x}<br>Probability: %{y:.2f}<extra></extra>')

fig.add_scatter(x=t[:T_sel],
                y=incoherent[:T_sel],
                line_shape='spline',
                line = {'color': fig.data[1].marker.color},
                legendgroup = fig.data[1].legendgroup,
                showlegend=False,
                hovertemplate= '<b>'+fig.data[1].name+'</b><br>Time: %{x}<br>Probability: %{y:.2f}<extra></extra>')

fig.add_scatter(x=t[:T_sel],
                y=fast[:T_sel],
                line_shape='spline',
                line = {'color': fig.data[2].marker.color},
                legendgroup = fig.data[2].legendgroup,
                showlegend=False,
                hovertemplate= '<b>'+fig.data[2].name+'</b><br>Time: %{x}<br>Probability: %{y:.2f}<extra></extra>')

fig.add_scatter(x=t[:T_sel],
                y=random[:T_sel],line_shape='spline',
                line = {'color': fig.data[3].marker.color},
                legendgroup = fig.data[3].legendgroup,
                showlegend=False,
               hovertemplate= '<b>'+fig.data[3].name+'</b><br>Time: %{x}<br>Probability: %{y:.2f}<extra></extra>')

fig.add_scatter(x=t[:T_sel],
                y=anti[:T_sel],
                line = {'color': fig.data[4].marker.color},
                legendgroup = fig.data[4].legendgroup,
                showlegend=False,
               hovertemplate= '<b>'+fig.data[4].name+'</b><br>Time: %{x}<br>Probability: %{y:.2f}<extra></extra>')

fig.update_layout(yaxis = {'range': [0,1.1], 'fixedrange': True},
                  hoverlabel = {'bgcolor': "white", 'font_size': 14})
fig.show()
```

***All different probability and occurrences for the discussed probabilities. These are toggleable By selecting modes in the legend.***

### Coherence

If the underlying probability function is unknown (and quantum mechanically it is), how would we be able to quantify whether our detections are bunched. A possible heuristic we can use comes from signal processing. We could take the autocorrelation of the signals and compare them. Since the autocorrelation is the cross-correlation between the signal and a time-shifted copy, we'd likely see the bunched signal display some sort of correlation for a delay that is around the timescale of the oscillations in the probability function. In the figure below I have correlated the detections described above with $t\in[0,10000]$ and plotted these values for different time delays $h$.

```python
#hide_input
max_lag = 50
result = np.empty((5,max_lag))
for i, mode in enumerate([noise,incoherent,fast,random,anti]):
    autocor = np.empty(max_lag)
    series = pd.Series((rand>mode)*1.0)
    for h in range(1,max_lag):
        autocor[h] = series.autocorr(lag=h)
    result[i]=autocor

df = pd.DataFrame(result.T)
df.columns=['bunched','bunched (long timescale)', 'bunched (short timescale)', 'random','anti-bunched']
df.rename_axis('h',inplace=True)

fig = px.scatter(df,
                 labels={'value': '$\mathrm{R_{XX}}(h)$', 'h': '$h$', 'variable': 'photon-mode'},
                 template='plotly_white',
                 trendline='rolling',trendline_options={'window': 3})

for trace in fig.data:
    trace['hovertemplate']= '<b>'+trace['name']+'</b><br>Lag: %{x}<br>Autocorrelation: %{y:.2f}<extra></extra>'

fig.update_layout(hoverlabel = {'bgcolor': "white", 'font_size': 14})
fig.show()
```

***The autocorrelation for the different photon modes.***

As is clear from the graph, the bunched photons show some autocorrelation, with the longer timescale showing longer (bigger time shifts) autocorrelation. This can be explained further by signal analysis. We are working in the discrete domain and smaller sampling steps relative to the variation in the underlying probability will mean more correlation.

What this shows is temporal coherence, a property of light that describes the predictability over a timescale{% cite perley_2008 %}.

### Bandwidth

Up until now we have only looked at raindrops as analogous detections, but when talking about photons we need to take frequency into account. Photons obey the Heisenberg uncertainty relation where the uncertainty in position $\Delta x$ and momentum $\Delta p_x$ must obey {% cite perley_2008 %}:

$$\begin{equation}
\Delta x\Delta p_x = \frac{\hbar}{2}
\end{equation}$$

Our photons are flying at the detector, so its uncertainty in longitudinal ($z$, towards the detector) position can be equated as it's uncertainty in detection time and it's momentum can be expressed as $h/\lambda$:

$$\begin{equation}
\Delta z \cdot\Delta p_z= c\Delta t\cdot\frac{h\Delta\nu}{c} = h\Delta t\Delta\nu = \frac{\hbar}{2}
\end{equation}$$

and finally we arrive at

$$\begin{equation}
\Delta\nu \propto \frac{1}{\Delta t}
\end{equation}$$

In other words: a shorter coherence time means more accurate knowledge on where the photons are, meaning their uncertainty in momentum and therefore bandwidth is bigger.

This relation can also be seen experimentally. In Morgan and Mandel's 1966 paper{% cite morgan_1966%}, the authors explore the autocorrelation between two different light sources: a Hg198 source with a very narrow bandwidth (a) and a tungsten light bulb with a broad spectrum thermal source (b). By designing an apparatus that counts the number of times two photon arrive within a defined time period, they show the autocorrelation of the two light sources

***The number of times per second two photons arrived after a specified time delay for a a) Hg198 light source and a b) tungsten incandescent light source. Image taken from {% cite morgan_1966%}***

Because the tungsten light has a wide bandwidth, it has a short coherence time and the reverse is true for the Hg198 light. Therefore, if we take the short timescale bunched photons as an analogy for the tungsten light and the longer timescale bunched lights as analogous for the mercury light, we get something that looks very similar.

```python
#hide_input
fig = px.scatter(df[['bunched', 'bunched (short timescale)']],
                 labels={'value': '$\mathrm{R_{XX}}(h)$', 'h': '$h$', 'variable': 'photon-mode'},
                 template='plotly_white',
                 trendline='rolling',trendline_options={'window': 3})

for trace in fig.data:
    trace['hovertemplate']= '<b>'+trace['name']+'</b><br>Lag: %{x}<br>Autocorrelation: %{y:.2f}<extra></extra>'

fig.update_layout(hoverlabel = {'bgcolor': "white", 'font_size': 14})
fig.show()
```

***The autocorrelation of just the slow and short bunched timescale photons behave like the spectral and tungsten source respectively.***

With a more thorough understanding of photon bunching, it is time to return to photon noise and discuss noise-equivalent power.

## Noise Equivalent power

The noise equivalent power ($\mathrm{NEP}$) is a way to express the sensitivity of a photon detector. It is defined as *the input signal power that results in a signal-to-noise-ratio of 1 in a 1 Hz output bandwidth*. {% cite richards\_1994 %}. For our purposes this means multiplying the uncertainty in photon count $\sigma_\mathrm{ph}$ with the photon energy{% cite leclercq\_2007%} and setting the $\tau$ in eq. \eqref{simple_sigma_ph} to $\tau=0.5\:\mathrm{s}$. From {% cite zmuidzinas_2003%} we have an equation for the electrical (detected) photon noise

$$\begin{equation}
\sigma_\mathrm{ph}^2=\frac{1}{\tau}\int_0^\infty\eta(\nu)n(\nu)\left[1+\eta(\nu)n(\nu)\right]d\nu
\end{equation}$$

Where $\eta\left(\nu\right)$ is the *quantum efficiency*, which for our purposes is the efficiency of the filter. Multiplying this integral with the photon energy, we get the electrical Noise Equivalent Power added by photon noise for a specified integration time

$$\begin{equation}
\mathrm{NEP}_{\tau,\mathrm{ph}}^2=\frac{1}{\tau}\int_0^\infty\left(h\nu\right)^2\eta(\nu)n(\nu)\left[1+\eta(\nu)n(\nu)\right]d\nu
\end{equation}$$

> This integral implicitly assumes an infinite coherence time, since it integrates over infinitely small bandwidths $d\nu$. Another side effect of this is that photons only bunch with photons that share the exact same frequency. These two paradoxical conclusions make this integral therefore inherently not physical and we will need to make some assumptions in order to work with it.

If we were to create a perfect box filter, and approximate the photon number as constant over this filter $\eta\left(\nu\right)$ falls out of the integral. Let's first define this box function

$$\begin{equation} \Pi^{\Delta\nu}_{\nu_0}(\nu)=   \left\{
\begin{array}{ll}
      1 & |\nu-\nu_0|\le\frac{\Delta\nu}{2} \\
      0 & \mathrm{elsewhere} \\
\end{array}
\right.
\end{equation}$$

And then set $\eta(\nu)=\eta_0\Pi^{\Delta\nu}_{\nu_0}(\nu)$. When we assume we have a constant photon occupation number over this range, the equation simplifies to:

$$\begin{equation}
\begin{split}
\mathrm{NEP}_{\tau,\mathrm{ph}}^2&=\frac{1}{\tau}\int_0^\infty\left(h\nu\right)^2\eta_0\Pi^{\Delta\nu}_{\nu_0}(\nu)n(\nu)\left[1+\eta_0\Pi^{\Delta\nu}_{\nu_0}(\nu)n(\nu)\right]d\nu \\
&= \frac{1}{\tau}\int_{\nu_0-\frac{\Delta\nu}{2}}^{\nu_0+\frac{\Delta\nu}{2}}\left(h\nu\right)^2\eta_0n\left[1+\eta_0n\right]d\nu
\end{split}
\end{equation}$$

Which, for $\Delta\nu\ll\nu$ approximates as:
$$\begin{equation}
\mathrm{NEP}_{\tau,\mathrm{ph}}^2=\frac{1}{\tau}\left(h\nu_0\right)^2\eta_0n\left[1+\eta_0n\right]\Delta\nu
\end{equation}$$

Taking our definition of Noise Equivalent Power $\tau=0.5\:\mathrm{[s]}$, this results in a Noise Equivalent Power of:

$$\begin{equation}
\mathrm{NEP}_{\tau=0.5\mathrm{s},\mathrm{ph}}=h\nu_0\sqrt{2\eta_0n\left(1+\eta_0n\right)\Delta\nu}
\end{equation}$$

### DESHIMA

In actual measurements the photon occupation number isn't known, but we are able to deduce it from the power spectral density{% cite paul_2004 %}:

$$\begin{equation}
n_\mathrm{ph}=\frac{\mathrm{PSD}}{h\nu}
\end{equation}$$

Inserting this in the equation for $\mathrm{NEP}$ we get:

$$\begin{equation}
\mathrm{NEP}_{\tau=0.5\mathrm{s},\mathrm{ph}}=\sqrt{2\eta_0\mathrm{PSD}h\nu_0\left(1+\eta_0\frac{\mathrm{PSD}}{h\nu_0}\right)\Delta\nu}
\end{equation}$$

If we assume the $\mathrm{PSD}$ to be flat, for a box filter the power spectrum multiplied by the bandwidth and the efficiency is obviously the power on the detector.

$$\begin{equation}
P_\mathrm{KID}=\eta_0\mathrm{PSD}\Delta\nu_0
\end{equation}$$

rearranging we get:

$$\begin{equation}
\mathrm{NEP}_{\tau=0.5\mathrm{s},\mathrm{ph}}=\sqrt{2P_\mathrm{KID}h\nu_0 + 2\frac{P^2_\mathrm{KID}}{\Delta\nu}}
\end{equation}$$

Which is in agreement with {% cite endo_2019%}.

## Generation-Recombination Noise

Besides photon noise (both Poisson and bunching), another type of noise adds to our $\mathrm{NEP}$: recombination noise. This is a type of noise that occurs in superconductor-based pair-breaking photon detectors. As mentioned before, incoming photons generate quasiparticles in the detector, which later recombine in a Cooper pair {% cite visser\_2014 %}. For our purposes we will take the noise equivalent power of recombination noise $\mathrm{NEP_{\tau,R}}$ as given by {% cite endo_2019%}:

$$\begin{equation}
\mathrm{NEP_{\tau,R}}=\frac{1}{\sqrt{\tau}}\sqrt{2\Delta_\mathrm{Al}\frac{P_\mathrm{KID}}{\eta_\mathrm{pb}}}
\end{equation}$$

Even though the amount of quasiparticles generated is proportional to the incoming power, the recombination noise happens randomly and is therefore an uncorrelated process. This means we can add the $\mathrm{NEP_R}$ and $\mathrm{NEP_{ph}}$ together by quadrature addition, creating a total noise equivalent power given by:

$$\begin{equation}
\mathrm{NEP}_{\tau=0.5\mathrm{s}} = \sqrt{\mathrm{NEP}_{\tau=0.5\mathrm{s},\mathrm{ph}}^2 + \mathrm{NEP_{\tau=0.5s,R}}^2}
\end{equation}$$

$$\begin{equation}
\mathrm{NEP}_{\tau=0.5\mathrm{s}}=\sqrt{2P_\mathrm{KID}h\nu_0 + 2\frac{P_\mathrm{KID}^2}{\Delta\nu}+4\Delta_\mathrm{Al}\frac{P_\mathrm{KID}}{\eta_\mathrm{pb}}}
\end{equation}$$

Finally, quasiparticles also generate randomly without any incoming photons, eg when the detector is kept in total darkness{%cite visser_2014_2%}. This is called *Generation-Recombination noise* and is negligible compared to the photon noise and photon-induced recombination noise as can be seen in the figure below:

***The noise induced by spontaneous Generation and recombination of quasiparticles is negligible compared to photon noise. Image taken from {%cite visser_2014_2%}***

Because this effect is negligible, it won't be included in the model.

Previous Chapter

Next Chapter

## Bibliography
{% bibliography --cited_in_order %}


# The Quality of Finite-Size Data Collapse

# Finite-size scaling

The finite-size scaling ansatz

$$
A_L(\varrho) = L^{\zeta/\nu} \tilde{f}\left(L^{1/\nu} (\varrho - \varrho_c)
\right)
$$

postulates how a physical quantity $A_L(\varrho)$ observed in a system of
finite size scales with system size $L$ and paramater $\varrho$ according to a
scaling function $\tilde{f}$, the critical parameter $\varrho_c$, the
critical exponent $\zeta$ of the quantity itself, and the critical exponent
$\nu$ of the correlation length $\xi$
([Newman & Barkema, 1999]),
([Binder & Heermann, 2010]).

[Binder & Heermann, 2010]: http://dx.doi.org/10.1007/978-3-642-03163-2

Finite-size scaling analysis concerns experimental data $a_{L_i, \varrho_j}$ at
system sizes $L_i$ and parameter values $\varrho_j$.
Plotting $L_i^{-\zeta/\nu} a_{L_i, \varrho_j}$ against $L_i^{1/nu} (\varrho -
\varrho_c)$ with the right choice of $\varrho_c, \nu, \zeta$ should let the
data collapse onto a single curve.
The single curve of course is the scaling function $\tilde{f}$ from the
finite-size scaling ansatz.
In the following, we present a measure by Houdayer & Hartmann
([Houdayer & Hartmann, 2004])
for the quality of the data collapse.
Melchert ([Melchert, 2009])
refers to some alternative measures, for example ([Bhattacharjee & Seno, 2001]),
([Wenzel et al., 2008]), and
to some applications of these measures in the literature.

[Wenzel et al., 2008]: http://dx.doi.org/10.1016/j.nuclphysb.2007.10.024

[Bhattacharjee & Seno, 2001]: http://dx.doi.org/10.1088/0305-4470/34/33/302

[Melchert, 2009]: http://arxiv.org/abs/0910.5403

[Houdayer & Hartmann, 2004]: http://dx.doi.org/10.1103/physrevb.70.014418

# The quality function

Houdayer & Hartmann ([Houdayer &
Hartmann, 2004]) refine a method proposed by Kawashima & Ito
([Kawashima & Ito, 1993]).
They define the quality as the reduced $\chi^2$ statistic

\begin{equation}
S = \frac{1}{\mathcal{N}} \sum_{i,j} \frac{(y_{ij} -
Y_{ij})^2}{dy_{ij}^2+dY_{ij}^2},
\end{equation}

where the values $y_{ij}, dy_{ij}$ are the scaled observations and its standard
errors at $x_{ij}$, and the values $Y_{ij}, dY_{ij}$ are the estimated value of
the master curve and its standard error at $x_{ij}$.

[Kawashima & Ito, 1993]: http://dx.doi.org/10.1143/jpsj.62.435

The quality $S$ is the mean square of the weighted deviations from the master
curve.
As we expect the individual deviations $y_{ij} - Y_{ij}$ to be of the order of
the individual error $\sqrt{dy_{ij}^2 + dY_{ij}^2}$ for an optimal fit, the
quality $S$ should attain its minimum $S_{\min}$ at around $1$ and be much
larger otherwise ([Bevington & Robinson,
2003]).

Let $i$ enumerate the system sizes $L_i$, $i = 1, \ldots, k$ and let $j$
enumerate the parameters $\varrho_j$, $j = 1, \ldots, n$ with $\varrho_1 <
\varrho_2 < \ldots < \varrho_n$.
The scaled data are

\begin{align}
y_{ij} & := L_i^{-\zeta/\nu} a_{L_i, \varrho_j} \\
dy_{ij} & := L_i^{-\zeta/\nu} da_{L_i, \varrho_j} \\
x_{ij}  & := L_i^{1/\nu}(\varrho_j - \varrho_c).
\end{align}

The sum in the quality function $S$ only involves terms for which the estimated
value $Y_{ij}$ of the master curve at $x_{ij}$ is defined. The number of such
terms is $\mathcal{N}$.

The master curve itself depends on the scaled data. For a given $i$, $L_i$, we
estimate the master curve at $x_{ij}$ by the two respective data from all the
other system sizes which respectively enclose $x_{ij}$:
for each $i \neq i$, let $j'$ be such that $x_{i'j'} \leq x_{ij} \leq
x_{i'(j'+1)}$, and select the points $(x_{i'j'}, y_{i'j'}, dy_{i'j'}),
(x_{i'(j'+1)}, y_{i'(j'+1)}, dy_{i'(j'+1)})$.
Do not select points for some $i'$, if there is no such $j'$. If there is no
such $j'$ for all $i'$, the master curve remains undefined at $x_{ij}$.

Given the selected points $(x_l, y_l, dy_l)$, the local approximation of the
master curve is the linear fit

$$
y = mx + b
$$

with weighted least squares (Strutz,
2011).
The weights $w_l$ are the reciprocal variances, $w_l := 1/dy_{ij}^2$.
The estimates and (co)variances of the slope $m$ and intercept $b$ are

\begin{align*}
\hat{b} &= \frac{1}{\Delta} (K_{xx}K_y - K_xK_{xy}) \\
\hat{m} &= \frac{1}{\Delta} (K K_{xy} - K_x K_y)
\end{align*}

$$
\hat{\sigma}_b^2 = \frac{K_{xx}}{\Delta} , \hat{\sigma}_m^2 = \frac{K}{\Delta},
\hat{\sigma}_{bm} = - \frac{K_x}{\Delta}
$$

with $K_{nm} := \sum w_l x_l^n y_l^m$, $K := K_{00}$, $K_x := K_{10}$, $K_y :=
K_{01}$, $K_{xx} := K_{20}$, $K_{xy} := K_{11}$, $\Delta := KK_{xx} - K_x^2$.

Hence, the estimated value of the master curve at $x_{ij}$ is

$$
Y_{ij} = \hat{m} x_{ij} + \hat{b}
$$

with error propagation

$$
dY_{ij}^2 = \hat{\sigma}^2 x_{ij}^2 + 2 \hat{\sigma}_{bm} x_{ij} +
\hat{\sigma}_b^2.
$$

> *[This work](http://notebooks.asorge.de) is licensed under a [Creative
Commons Attribution 4.0 International
License](http://creativecommons.org/licenses/by/4.0/).*


Markdown macros

$\newcommand{\I}{\mathrm{i}}
\newcommand{\E}{\mathrm{e}}
\newcommand{\D}{\mathop{}\!\mathrm{d}}
\newcommand{\bra}[1]{\langle{#1}|}
\newcommand{\ket}[1]{|{#1}\rangle}
\newcommand{\braket}[1]{\langle{#1}\rangle}
\newcommand{\bm}[1]{\boldsymbol{#1}}$

# Imports

```python
# numpy
import numpy as np
```

```python
# matplotlib
import matplotlib
import matplotlib.pylab as plt
```

```python
%matplotlib inline
```

```python
# sympy
import sympy as sy
from sympy import symbols
from sympy import simplify, expand, collect, cancel, apart, trigsimp
from sympy import integrate, diff, series
sy.init_printing(use_unicode = True)
```

# Centre de masse

Pour un ensemble de $N$ particules de masse $m_n$ la défintion de $\bm R$ est:
$$\sum_{n = 1}^N m_n (\bm r_n - \bm R) = 0$$
où $\bm r_n$ sont les positions:
$$\bm R = \frac{1}{M}\sum_{n = 1}^N m_n \bm r_n, \quad M = \sum_{n = 1}^N m_n$$

Pour une distribution de masse continue de densité $\rho(\bm r)$, la formule équivalente est
$$ \bm R = \frac{1}{M}\int_V \D \bm r\, \bm r \rho(\bm r), \quad  M = \int_V \D \bm r\, \rho(\bm r)$$
avec $V$ le volume du corps.

# Aire et volume

L'élément $\D V$ d'un volume $V$ défini par les formules paramétriques
$$x = x(u, v, w), \; y = y(u, v, w), \; z = z(u, v, w)$$
est,
$$\D V = \D x \D y \D z= J \D u \D v \D w$$
avec le jacobien,
$$J = \begin{vmatrix}
\frac{\partial x}{\partial u} & \frac{\partial x}{\partial v} & \frac{\partial x}{\partial w} \\
\frac{\partial y}{\partial u} & \frac{\partial y}{\partial v} & \frac{\partial y}{\partial w} \\
\frac{\partial z}{\partial u} & \frac{\partial z}{\partial v} & \frac{\partial z}{\partial w}
\end{vmatrix}$$

On obtient l'élément d'aire $\D A$ d'une façon équivalente:
$$dA = \D x \D y = J \D u \D v, \quad J = \left| \frac{\partial \bm r}{\partial u} \times \frac{\partial \bm r}{\partial v} \right|$$
où $\bm r = (x(u,v), y(u,v), z(u,v))$.

### Surface, forme explicite

Soit $\bm r = (x, y, f(x,y)$ (les deux paramètres sont les coordonnées $(x,y)$):
$$A = \int_S \D x \D y\, \left| (1,0,f_x) \times (0,1,f_y) \right|  = \int_S \D x \D y\, \sqrt{1 + f_x^2 + f_y^2}$$

## Exemples: sphère, triangle, ...

```python
# Demi-sphère:
a = symbols("a", positive=True)
rho = symbols("rho", positive=True)
r, theta, phi = symbols("r, theta, phi", real=True)
M = rho*integrate(1, (phi,0,2*sy.pi*))*\
        integrate(sy.sin(theta), (theta, 0, sy.pi/2))*\
        integrate(r**2, (r,0,a))
M
```

```python
R_x = (rho/M)*integrate(sy.sin(phi), (phi,0,2*sy.pi))*\
        integrate(sy.sin(theta)*sy.sin(theta), (theta, 0, sy.pi/2))*\
        integrate(r**3, (r,0,a))

R_y = (rho/M)*integrate(sy.cos(phi), (phi,0,2*sy.pi))*\
        integrate(sy.sin(theta)*sy.sin(theta), (theta, 0, sy.pi/2))*\
        integrate(r**3, (r,0,a))

R_z = (rho/M)*integrate(1, (phi,0,2*sy.pi))*\
        integrate(sy.cos(theta)*sy.sin(theta), (theta, 0, sy.pi/2))*\
        integrate(r**3, (r,0,a))
```

```python
(R_x, R_y, R_z)
```

```python
# Demi-sphère:
a = symbols("a", positive=True)
sigma = symbols("sigma", positive=True)
R, theta, phi = symbols("R, theta, phi", real=True)
M = sigma * R**2 * integrate(1, (phi,0,2*sy.pi))*\
        integrate(sy.sin(theta), (theta, 0, sy.pi/2))
M
```

```python
R_z = (sigma * R**2/M) * integrate(1, (phi,0,2*sy.pi))*\
        integrate(R * sy.cos(theta)*sy.sin(theta), (theta, 0, sy.pi/2))
```

```python
R_z
```

```python
# Triangle
a, b = symbols("a, b", positive=True)
x, y = symbols("x, y", positive=True)
rho = symbols("rho", positive=True)
M = rho * integrate(integrate(1, (y, 0, (b/a)*x)), (x, 0, a))
M
```

```python
R_x = (rho/M) * integrate(integrate(x, (y, 0, (b/a)*x)), (x, 0, a))
R_y = (rho/M) * integrate(integrate(y, (y, 0, (b/a)*x)), (x, 0, a))
```

```python
R_x, R_y
```

# Moment d'inertie

On consirère un corps de densité $\rho = \rho(\bm r$, en général non homogène, de volume $V$. Le moment d'inertie est construit sur les moments d'ordre deux de la densité:
$$I_{ij} = \int_V \D \bm r \, (r^2 \delta_{i,j} - x_i x_j) \rho(\bm r)$$
avec $i,j = 1, 2, 3$ correspondant aux coordonnées $\bm r = (x, y, z)$.

On peut représenter le moment d'inertie par une matrice:
$$I = \int_V \D \bm r \, \rho \begin{pmatrix} y^2 + z^2 & -xy & -xz \\  -yx & x^2 + z^2 & -yz \\  -zx & -zy & x^2 + y^2 \end{pmatrix}$$

Un transformation de coordonnées permet de rendre la matrice diagonale. Les éléments de la diagonale:
$$I = \mathrm{diag}(I_x, I_y, I_z)$$
sont les moments "principaux" d'inertie.

## Choix des coordonnées

L'avantage d'écrire $I$ comme un tenseur c'est qu'on peut calculer ses élément dans n'importe quelle base. Cependant le calcul de $I_{ij}$ est grandement simplifié si on choisi un système de coordonnées qui respecte les symétries de l'objet.

Si on note $I^G$ le tenseur d'inertie calculé sur le centre de masse, le tenseur $I$ calculé sur un point $\bm d = (d_x, d_y, d_z)$, avec $d$ la distance du nouvel origine au centre de masse, on a la formule de changement de coordonnées:
$$I_{ij} = I^G_{ij} + M(d^2 \delta_{ij} - d_id_j)$$

Dans le cas d'une rotation $R_{\bm n}(\varphi)$ du solide autour de centre de masse, la matrice $I^G$ change selon la formule:
$$I = R_{\bm n}(\varphi) I^G R_{\bm n}(\varphi)^T$$

Considérons un exemple. Un carré de côté $a$, avec l'origine en $(0,0)$ et en $(a/2,0)$:

```python
a, x, y = sy.symbols("a, x, y", real=True)
M = sy.symbols("M", real=True)
def I_carre_0(f):
    return sy.integrate(sy.integrate(f, (y, 0, a)), (x, 0, a))

A = I_carre_0(1)
rho = M/A
A
```

```python
# ORIGINE(0,0)
I_xx = rho * I_carre_0(y**2)
I_yy = rho * I_carre_0(x**2)
I_xy = rho * I_carre_0(-x*y)
I_zz = I_xx + I_yy
I_0 = sy.Matrix([[I_xx, I_xy, 0], [I_xy, I_yy, 0], [0, 0, I_zz]])
I_0
```

```python
# ORIGINE (a/2,0)
def I_carre_1(f):
    return sy.integrate(sy.integrate(f, (y, 0, a)), (x, -a/2, a/2))

I_xx = rho * I_carre_1(y**2)
I_yy = rho * I_carre_1(x**2)
I_xy = rho * I_carre_1(-x*y)
I_zz = I_xx + I_yy
I_1 = sy.Matrix([[I_xx, I_xy, 0], [I_xy, I_yy, 0], [0, 0, I_zz]])
I_1
```

```python
# Centre de masse (a/2,a/2)
def I_carre_G(f):
    return sy.integrate(sy.integrate(f, (y, -a/2, a/2)), (x, -a/2, a/2))

I_xx = rho * I_carre_G(y**2)
I_yy = rho * I_carre_G(x**2)
I_xy = rho * I_carre_G(-x*y)
I_zz = I_xx + I_yy
I_G = sy.Matrix([[I_xx, I_xy, 0], [I_xy, I_yy, 0], [0, 0, I_zz]])
I_G
```

```python
# I_0: on a d = (-a/2,-a/2)
I_d0 = sy.Matrix([[M*a**2/4, -M*a**2/4 , 0], [-M*a**2/4, M*a**2/4, 0], [0, 0,  M*a**2/2]])
I_G + I_d0
```

```python
# I_1: on a d = (0,-a/2)
I_d1 = sy.Matrix([[M*a**2/4, 0 , 0], [0, 0, 0], [0, 0,  M*a**2/4]])
I_G + I_d1
```

## Axes principaux

Comme le tenseur d'inertie est symétrique, on peut toujours diagonaliser sa matrice, et donc l'ecrire comme
$$I = \mathrm{diag}(I_x, I_y, I_z)$$
Les trois valeurs de la diagonale sont les racines du polynôme caractéristique:
$$P(\lambda) = \det\left(I - \lambda 1_3\right) = 0$$
($1_3$ est la matrice identité de dimension 3); les vecteurs propres de la transformation sont les axes principaux du corps. Ils coincident avec les axes de symétrie du corps, si la densité est uniforme.

```python
# Carre O=(0,0)
l = sy.symbols("lambda", real=True)
p = sy.det(I_0 - l*sy.eye(3))
sy.solve(sy.Eq(p,0),l)
```

```python
I_0.eigenvects()
```

on voit qu'une rotation de 45 degrés permet de diagonaliser la matrice (un axe passant par le centre du carrée).

# Cinématique du solide

## Vitesse angulaire

Dans un repère fixe $(X,Y,Z)$ un point $\bm X$ du solide $\mathcal{V}$ varie selon le mouvement du centre de masse $\bm R$ et de sa propre rotation d'angle $\varphi$ et d'axe $\bm n$, $\D \bm \varphi = \bm n \D \varphi$:
$$\D \bm X = \D \bm R + \D \bm \varphi \times \bm r$$
où $\bm r$ est un point appartenant à $\mathcal{V}$, mesuré dans le repère $(x,y,z)$ d'origine $O$ (le centre de masse) fixé au solide: $\bm X = \bm R + \bm r$ à chaque instant $t$.

Les coordonnées $(X,Y,Z)$ sont fixes, tandis que les coordonnées $(x,y,z)$ évoluent dans le temps:
$$\frac{\D \bm X}{\D t} = \bm v, \; \frac{\D \bm R}{\D t} = \bm V, \; \frac{\D \bm a}{\D t} = \bm \Omega$$
On a donc la transformation de la vitesse entre le repère fixe $\bm v$ et le mouvement du corps:
$$\bm v = \bm V + \bm \Omega \times \bm r$$
Le vecteur $\bm r$ ne dépend pas du temps puisqu'il est un point d'un corps solide (indéformable).

Si l'origine de coordonnées du corps n'est pas sur son centre de masse, mais il est déplacé de $\bm d$, on a la transformation suivante:
$$\bm v = \bm V + \bm \Omega \times \bm d + \bm \Omega \times \bm r'$$
avec $\bm r = \bm r' + \bm d$,
$$\bm V' = \bm V + \bm \Omega \times \bm d, \; \bm \Omega' = \bm \Omega$$
le centre de masse tourne, mais la vitesse angulaire du corps est invariante.

## Énergie cinétique

Elle est donnée par la formule habituelle
$$T = \frac{1}{2} \sum_n m_n \bm v_n^2$$
En y substituant l'expression de $\bm v$ on obtient:
$$T = \frac{1}{2} \sum_n m_n \bm V^2 + \sum_n m_n \bm \Omega \times \bm r_n + \frac{1}{2} \sum_n m_n \left( \Omega \times \bm r_n \right)^2$$
On note que le terme du milieu est nul, par définition du centre de masse, on a donc:
$$T = \frac{M V^2}{2} + \frac{1}{2} \sum_n m_n \left( \Omega \times \bm r_n \right)^2=  \frac{M V^2}{2} +  \frac{1}{2} \sum_n m_n (\Omega_l^2 x_{nl}^2 - \Omega_i x_{ni} \Omega_j x_{nj})$$
où nous avons introduit la notation en coordonnées (avec la convention que l'on fait la somme sur les indices répétés). En notant que $\Omega_l^2 x_l^2 = \Omega_i \Omega_j \delta_{ij} x_l^2$, on peut écrire:
$$T = \frac{M V^2}{2} + \frac{1}{2} \sum_n m_n (x_{nl}^2 \delta_{ij} - x_{ni} x_{nj}) \Omega_i \Omega_j$$
On trouve donc
$$T = \frac{M V^2}{2} + \frac{1}{2} I_{ij} \Omega_i \Omega_j$$
avec
$$I_{ij} =  \sum_n m_n (x_{nl}^2 \delta_{ij} - x_{ni} x_{nj})$$
le tenseur d'inertie.

## Vitesse angulaire en fonction d'angles d'Euler

Dans le repère du centre de masse $(x,y,z)$:
\begin{align*}
\Omega_x &= \dot{\psi} \sin \theta \sin \varphi + \dot{\theta} \cos \varphi \\
\Omega_y &= \dot{\psi} \sin \theta \cos \varphi - \dot{\theta} \sin \varphi \\
\Omega_z &= \dot{\psi} \cos \theta + \dot{\varphi}
\end{align*}

```python
phi, theta, psi = symbols("phi, theta, psi", real=True)

def R_x(phi):
    c = sy.cos(phi)
    s = sy.sin(phi)
    return sy.Matrix([[1,  0, 0],
                  [0, c, -s],
                  [0, s,  c]])
def R_y(phi):
    c = sy.cos(phi)
    s = sy.sin(phi)
    return sy.Matrix([[ c, 0, s],
                  [ 0, 1, 0],
                  [-s, 0, c]])

def R_z(phi):
    c = sy.cos(phi)
    s = sy.sin(phi)
    return sy.Matrix([[c, -s, 0],
                  [s,  c, 0],
                  [0,  0, 1]])
```

```python
# d phi/d t est dirigé selon ez(mobile)
# d theta/d t est dirigé selon la ligne nodale eN
# d psi/d t est dirigé selon eZ (fixe)
psi_t, theta_t, phi_t = sy.symbols("psi_t, theta_t, phi_t", real=True)
#
ez = sy.Matrix([0,0,1])
ex = sy.Matrix([1,0,0])

eZ = R_z(-phi)*R_x(-theta)*ez
eN = R_z(-phi)*R_x(-theta)*ex
Omega = phi_t*ez + theta_t*eN + psi_t*eZ
Omega
```

## Équation d'Euler

La variation d'un vector quelconque $\bm A$ se décompose dans une partie de variation intrinsèque (dans le repère mobile) qu'on note avec une prime, et une autre partie due à la rotation de vitesse angulaire $\bm \Omega$:
$$\frac{\D \bm A}{\D t} = \frac{\D' \bm A}{\D t} + \Omega \times \bm A$$
On peut aussi noter explicitement les repères:
$$\left. \frac{\D \bm A}{\D t} \right|_{(X,Y,Z)} = \left. \frac{\D \bm A}{\D t} \right|_{(x,y,x)} + \left. \Omega \times \bm A \right|_{(x,y,z)}$$

## Moment cinétique

Pour un ensemble de points matériels le moment cinétique (ou moment angulaire) $\bm L$:
$$\bm L = \sum_n m_n \bm r_n \times \bm v_n$$
Un changement du centre de coordonnées par une translation $\bm d$, $\bm r = \bm r' + \bm d$ transforme le moment
$$\bm L = \sum_n (\bm r'_n \times \bm p_n + \bm d \times \bm p_n) = \bm L' + \bm d \times \bm P$$
Si deux systèmes ont une vitesse relative $\bm V$: $\bm v_n = \bm v'_n + \bm V$, le moment cinétique change selon la loi:
$$\bm L = \bm L' + M \bm R \times V$$

### Solide

La généralisation à un corps solide consiste à remplacer la vitesse des particules par la vitesse angulaire du solide $\bm \Omega \times \bm r$:
$$L_i = I_{ij} \Omega_j$$
(toujours on somme sur les indices répétés).
Notez:
$$\bm r \times (\bm \Omega \times \bm r) = \bm \Omega r^2 - \bm r (\Omega \cdot \bm r)$$
ce qui s'écrit comme le produit du tenseur d'inertie par le vecteur de vitesse angulaire.

Il convient de faire un changement de coordonnées pour exprimer le tenseur d'inertie selon ses axes principales:
$$ L_x = I_x \Omega_x,\; L_y = I_y \Omega_y, \; L_z = I_z \Omega_z$$

### Variation de $\bm L$

La variation "instantanée" du moment $\bm L$ est donnée par la formule générale, applicable à tout vecteur:
$$\left. \frac{\D \bm L}{\D t} \right|_{(X,Y,Z)} = \left. \frac{\D \bm L}{\D t} \right|_{(x,y,x)} + \left. \Omega \times \bm L \right|_{(x,y,z)}$$
où $(X,Y,Z)$ est le repère fixe, et $(x,y,z)$ le repére fixé au solide, mobile par rapport à $(X,Y,Z)$.

Dans le cas où le tenseur $I$ est diagonal, on a
\begin{align*}
\dot{L}_x &= I_x \frac{\D \Omega_x}{\D t} + (I_z-I_y)\Omega_y \Omega_z \\
\dot{L}_y &= I_y \frac{\D \Omega_y}{\D t} + (I_z-I_x)\Omega_x \Omega_z \\
\dot{L}_z &= I_z \frac{\D \Omega_z}{\D t} + (I_y-I_x)\Omega_x \Omega_y \\
\end{align*}

# Triangle

```python
N = 65
x = np.linspace(-1,1,N)
y = -1.5*np.abs(x) + 1.5
fig, ax = plt.subplots(figsize = (4,4))
ax.plot(x, y, color = '0.35')
ax.plot([-1,1], [0,0], color = '0.35')
ax.plot([0,0], [0,1.5], ":", color = '0.35')
ax.plot([0,0], [0,0], ".", color = '#800', ms = 18)
ax.set_xticks([-1, 0, 1])
ax.set_xticklabels(["$-a$", "$0$", "$a$"], fontsize = 18)
ax.set_yticks([0, 1.5])
ax.set_yticklabels(["$0$", "$h$"], fontsize = 18)
```

On a un triangle $y=-(b/a) |x| + h$, et on veut calculer son moment d'inertie (en prennant $(0,0)$ comme centre de coordonnées associées au corps).

```python
x, y = sy.symbols("x, y", real=True)
a, h = sy.symbols("a h", real=True)
rho, M = sy.symbols("rho M", real=True)
```

```python
# intégrale double sur le triangle
def I_triangle(f):
    return sy.integrate(sy.integrate(f, (y, 0,  (h/a)*x + h)), (x, -a, 0)) +\
           sy.integrate(sy.integrate(f, (y, 0, -(h/a)*x + h)), (x, 0, a))
```

```python
# aire du triangle A, on a M = rho A
A = I_triangle(1)
A
```

```python
# inertie
# I_xx, I_yy, I_zz = I_xx + I_yy, I_xy = I_yx = 0
I_xx = (M/A) * I_triangle(y**2)
I_yy = (M/A) * I_triangle(x**2)
I_xy = (M/A) * I_triangle(x*y)
I_zz = sy.simplify(I_xx + I_yy)

I = sy.Matrix([[I_xx, I_xy, 0], [I_xy, I_yy, 0], [0, 0, I_zz]])
I
```

```python
# vitesse angulaire (xyz)
w = Omega.subs([(phi,0), (phi_t,0)])
w_x, w_y, w_z = w[0], w[1], w[2]
w
```

```python
# Angular momentum (xyz)
L_xyz = sy.Matrix([I_xx * w_x, I_yy * w_y, I_zz * w_z])
L_xyz
```