# SymPy

[SymPy](https://es.wikipedia.org/wiki/SymPy) es una biblioteca de Python que permite realizar cálculos simbólicos.
Nos ofrece las capacidades de álgebra computacional, y se puede usar en línea a través de [SymPy Live](http://live.sympy.org/) o [SymPy Gamma](http://www.sympygamma.com/), este último es similar a
[Wolfram Alpha](https://www.wolframalpha.com/).

Si usas Anaconda este paquete ya viene instalado por defecto pero si se usa miniconda o pip debe instalarse.

````python
conda install sympy # Usando el gestor conda de Anaconda/Miniconda
pip install sympy # Usando el gestor pip (puede requerir instalar más paquetes)
````

Lo primero que debemos hacer, antes de usarlo, es importar el módulo, como con cualquier
otra biblioteca de Python.

Si deseamos usar SymPy de forma interactiva usamos

```python
from sympy import *
init_printing()
```

Para scripting es mejor importar la biblioteca de la siguiente manera

```python
import sympy as sym
```

Y llamar las funciones de la siguiente manera

```python
x = sym.Symbols("x")
expr = sym.cos(x)**2 + 3*x
deriv = expr.diff(x)
```

en donde calculamos la derivada de  $\cos^2(x) + 3x$,
que debe ser $-2\sin(x)\cos(x) + 3$.

```python
%matplotlib notebook
import numpy as np
import matplotlib.pyplot as plt
from sympy import *
```

```python
init_printing()
```

Definamos la variable $x$ como un símbolo matemático. Esto nos permite hacer uso de
esta variable en SymPy.

```python
x = symbols("x")
```

Empecemos con cálculos simples. Abajo, tenemos una _celda de código_ con una suma.
Ubica el cursor en ella y presiona SHIFT + ENTER para evaluarla.

```python
1 + 3
```

Realicemos algunos cálculos.

```python
factorial(5)
```

```python
1 // 3
```

```python
1 / 3
```

```python
S(1) / 3
```

Podemos evaluar esta expresión a su versión en punto flotante

```python
sqrt(2*pi)
```

```python
float(sqrt(2*pi))
```

También podemos almacenar expresiones como variables, como cualquier variable de Python.

```python
radius = 10
height = 100
area = pi * radius**2
volume = area * height
```

```python
volume
```

```python
float(volume)
```

Hasta ahora, hemos usado SymPy como una calculadora. Intentemos
algunos cálculos más avanzados. Por ejemplo, algunas integrales.

```python
integrate(sin(x), x)
```

```python
integrate(sin(x), (x, 0, pi))
```

Podemos definir una función, e integrarla

```python
f = lambda x: x**2 + 5
```

```python
f(5)
```

```python
integrate(f(x), x)
```

```python
y = symbols("y")
integrate(1/(x**2 + y), x)
```

Si asumimos que el denominador es positivo, esta expresión se puede simplificar aún más

```python
a = symbols("a", positive=True)
integrate(1/(x**2 + a), x)
```

Hasta ahora, aprendimos lo más básico. Intentemos algunos ejemplos
más complicados ahora.

**Nota:** Si quieres saber más sobre una función específica se puede usar
la función ``help()`` o el comándo _mágico_ de IPython ``??``

```python
help(integrate)
```

    Help on function integrate in module sympy.integrals.integrals:

    integrate(*args, **kwargs)
        integrate(f, var, ...)

        Compute definite or indefinite integral of one or more variables
        using Risch-Norman algorithm and table lookup. This procedure is
        able to handle elementary algebraic and transcendental functions
        and also a huge class of special functions, including Airy,
        Bessel, Whittaker and Lambert.

        var can be:

        - a symbol                   -- indefinite integration
        - a tuple (symbol, a)        -- indefinite integration with result
                                        given with `a` replacing `symbol`
        - a tuple (symbol, a, b)     -- definite integration

        Several variables can be specified, in which case the result is
        multiple integration. (If var is omitted and the integrand is
        univariate, the indefinite integral in that variable will be performed.)

        Indefinite integrals are returned without terms that are independent
        of the integration variables. (see examples)

        Definite improper integrals often entail delicate convergence
        conditions. Pass conds='piecewise', 'separate' or 'none' to have
        these returned, respectively, as a Piecewise function, as a separate
        result (i.e. result will be a tuple), or not at all (default is
        'piecewise').

        **Strategy**

        SymPy uses various approaches to definite integration. One method is to
        find an antiderivative for the integrand, and then use the fundamental
        theorem of calculus. Various functions are implemented to integrate
        polynomial, rational and trigonometric functions, and integrands
        containing DiracDelta terms.

        SymPy also implements the part of the Risch algorithm, which is a decision
        procedure for integrating elementary functions, i.e., the algorithm can
        either find an elementary antiderivative, or prove that one does not
        exist.  There is also a (very successful, albeit somewhat slow) general
        implementation of the heuristic Risch algorithm.  This algorithm will
        eventually be phased out as more of the full Risch algorithm is
        implemented. See the docstring of Integral._eval_integral() for more
        details on computing the antiderivative using algebraic methods.

        The option risch=True can be used to use only the (full) Risch algorithm.
        This is useful if you want to know if an elementary function has an
        elementary antiderivative.  If the indefinite Integral returned by this
        function is an instance of NonElementaryIntegral, that means that the
        Risch algorithm has proven that integral to be non-elementary.  Note that
        by default, additional methods (such as the Meijer G method outlined
        below) are tried on these integrals, as they may be expressible in terms
        of special functions, so if you only care about elementary answers, use
        risch=True.  Also note that an unevaluated Integral returned by this
        function is not necessarily a NonElementaryIntegral, even with risch=True,
        as it may just be an indication that the particular part of the Risch
        algorithm needed to integrate that function is not yet implemented.

        Another family of strategies comes from re-writing the integrand in
        terms of so-called Meijer G-functions. Indefinite integrals of a
        single G-function can always be computed, and the definite integral
        of a product of two G-functions can be computed from zero to
        infinity. Various strategies are implemented to rewrite integrands
        as G-functions, and use this information to compute integrals (see
        the ``meijerint`` module).

        The option manual=True can be used to use only an algorithm that tries
        to mimic integration by hand. This algorithm does not handle as many
        integrands as the other algorithms implemented but may return results in
        a more familiar form. The ``manualintegrate`` module has functions that
        return the steps used (see the module docstring for more information).

        In general, the algebraic methods work best for computing
        antiderivatives of (possibly complicated) combinations of elementary
        functions. The G-function methods work best for computing definite
        integrals from zero to infinity of moderately complicated
        combinations of special functions, or indefinite integrals of very
        simple combinations of special functions.

        The strategy employed by the integration code is as follows:

        - If computing a definite integral, and both limits are real,
          and at least one limit is +- oo, try the G-function method of
          definite integration first.

        - Try to find an antiderivative, using all available methods, ordered
          by performance (that is try fastest method first, slowest last; in
          particular polynomial integration is tried first, Meijer
          G-functions second to last, and heuristic Risch last).

        - If still not successful, try G-functions irrespective of the
          limits.

        The option meijerg=True, False, None can be used to, respectively:
        always use G-function methods and no others, never use G-function
        methods, or use all available methods (in order as described above).
        It defaults to None.

        Examples
        ========

        >>> from sympy import integrate, log, exp, oo
        >>> from sympy.abc import a, x, y

        >>> integrate(x*y, x)
        x**2*y/2

        >>> integrate(log(x), x)
        x*log(x) - x

        >>> integrate(log(x), (x, 1, a))
        a*log(a) - a + 1

        >>> integrate(x)
        x**2/2

        Terms that are independent of x are dropped by indefinite integration:

        >>> from sympy import sqrt
        >>> integrate(sqrt(1 + x), (x, 0, x))
        2*(x + 1)**(3/2)/3 - 2/3
        >>> integrate(sqrt(1 + x), x)
        2*(x + 1)**(3/2)/3

        >>> integrate(x*y)
        Traceback (most recent call last):
        ...
        ValueError: specify integration variables to integrate x*y

        Note that ``integrate(x)`` syntax is meant only for convenience
        in interactive sessions and should be avoided in library code.

        >>> integrate(x**a*exp(-x), (x, 0, oo)) # same as conds='piecewise'
        Piecewise((gamma(a + 1), -re(a) < 1),
            (Integral(x**a*exp(-x), (x, 0, oo)), True))

        >>> integrate(x**a*exp(-x), (x, 0, oo), conds='none')
        gamma(a + 1)

        >>> integrate(x**a*exp(-x), (x, 0, oo), conds='separate')
        (gamma(a + 1), -re(a) < 1)

        See Also
        ========

        Integral, Integral.doit

```python
integrate??
```

## Ejemplos

### Solución de ecuaciones algebraicas

Para resolver sistemas de ecuaciones algebraicos podemos usar:
[``solveset`` and ``solve``](http://docs.sympy.org/latest/tutorial/solvers.html).
El método preferido es ``solveset``, sin embargo, hay sistemas que
se pueden resolver usando ``solve`` y no ``solveset``.

Para resolver sistemas usando ``solveset``:

```python
a, b, c = symbols("a b c")
solveset(a*x**2 + b*x + c, x)
```

Debemos ingresar la expresión igualada a 0, o como una ecuación

```python
solveset(Eq(a*x**2 + b*x, -c), x)
```

``solveset`` no permite resolver sistemas de ecuaciones no lineales, por ejemplo

```python
solve([x*y - 1, x - 2], x, y)
```

### Álgebra lineal

Usamos ``Matrix`` para crear matrices. Las matrices pueden contener variables y expresiones matemáticas.

Usamos el método ``.inv()`` para calcular la inversa, y ``*`` para multiplicar matrices.

```python
A = Matrix([
        [1, -1],
        [1, sin(c)]
    ])
display(A)
```

$$\left[\begin{matrix}1 & -1\\1 & \sin{\left (c \right )}\end{matrix}\right]$$

```python
B = A.inv()
display(B)
```

$$\left[\begin{matrix}\frac{\sin{\left (c \right )}}{\sin{\left (c \right )} + 1} & \frac{1}{\sin{\left (c \right )} + 1}\\- \frac{1}{\sin{\left (c \right )} + 1} & \frac{1}{\sin{\left (c \right )} + 1}\end{matrix}\right]$$

```python
A * B
```

$$\left[\begin{matrix}\frac{\sin{\left (c \right )}}{\sin{\left (c \right )} + 1} + \frac{1}{\sin{\left (c \right )} + 1} & 0\\0 & \frac{\sin{\left (c \right )}}{\sin{\left (c \right )} + 1} + \frac{1}{\sin{\left (c \right )} + 1}\end{matrix}\right]$$

Esta expresión debería ser la matriz identidad, simplifiquemos la expresión.
Existen varias formas de simplificar expresiones, y ``simplify`` es la más general.

```python
simplify(A * B)
```

$$\left[\begin{matrix}1 & 0\\0 & 1\end{matrix}\right]$$

### Graficación

SymPy permite realizar gráficos 2D y 3D

```python
from sympy.plotting import plot3d
```

```python
plot(sin(x), (x, -pi, pi));
```

    <IPython.core.display.Javascript object>

```python
monkey_saddle = x**3 - 3*x*y**2
p = plot3d(monkey_saddle, (x, -2, 2), (y, -2, 2))
```

    <IPython.core.display.Javascript object>

### Derivadas y ecuaciones diferenciales

Podemos usar la función ``diff`` o el método ``.diff()`` para calcular derivadas.

```python
f = lambda x: x**2
```

```python
diff(f(x), x)
```

```python
f(x).diff(x)
```

```python
g = lambda x: sin(x)
```

```python
diff(g(f(x)), x)
```

Y sí, ¡SymPy sabe sobre la regla de la cadena!

Para terminar, resolvamos una ecuación diferencial de segundo orden

$$ u''(t) + \omega^2 u(t) = 0$$

```python
t = symbols("t")
u = symbols("u", cls=Function)
omega = symbols("omega", positive=True)
```

```python
ode = u(t).diff(t, 2) + omega**2 * u(t)
dsolve(ode, u(t))
```

## Convertir expresiones de SymPy en funciones de NumPy

``lambdify`` permite convertir expresiones de sympy en funciones para hacer cálculos usando NumPy.

Veamos cómo.

```python
f = lambdify(x, x**2, "numpy")
f(3)
```

    9

```python
f(np.array([1, 2, 3]))
```

    array([1, 4, 9])

Intentemos un ejemplo más complejo

```python
fun = diff(sin(x)*cos(x**3) - sin(x)/x, x)
fun
```

```python
fun_numpy = lambdify(x, fun, "numpy")
```

y evalúemoslo en algún intervalo, por ejemplo, $[0, 5]$.

```python
pts = np.linspace(0, 5, 1000)
fun_pts = fun_numpy(pts + 1e-6) # Para evitar división por 0
```

```python
plt.figure()
plt.plot(pts, fun_pts)
```

    <IPython.core.display.Javascript object>

    [<matplotlib.lines.Line2D at 0x7fab6c5644e0>]

## Ejercicios

1. Calcule el límite

  $$ \lim_{x \rightarrow 0} \frac{\sin(x)}{x}\, .$$

2. Resuelva la ecuación diferencial de Bernoulli

  $$x \frac{\mathrm{d} u(x)}{\mathrm{d}x}  + u(x) - u(x)^2 = 0\, .$$

## Recursos adicionales

- Equipo de desarrollo de SymPy. [SymPy Tutorial](http://docs.sympy.org/latest/tutorial/index.html), (2018). Consultado: Julio 23, 2018
- Ivan Savov. [Taming math and physics using SymPy](https://minireference.com/static/tutorials/sympy_tutorial.pdf), (2017). Consultado: Julio 23, 2018

```python

```


# Cayley 1997 Problem 21

The problem involves integers but divides them so that the result is a rational number.
If we use Python `float` numbers then we'll get round-off errors.
Instead, let's use SymPy which can do exact calculations involving rational numbers.

First, let's define some SymPy variables for the positive integers $a$, $b$, and $c$.

```python
from sympy import symbols

a, b, c = symbols('a:c', integer=True, positive=True)
```

```python
a, b, c
```

    (a, b, c)

Next, lets define the equation that constrains the variables.

```python
from sympy import Eq
eq21 = Eq((a/c + a/b + 1)/(b/a + b/c + 1), 11)
eq21
```

$\displaystyle \frac{\frac{a}{c} + \frac{a}{b} + 1}{\frac{b}{c} + 1 + \frac{b}{a}} = 11$

```python
eq21.subs(a, 1).subs(b, 1).subs(c, 1)
```

$\displaystyle \text{False}$

```python
def is_solution(va, vb, vc):
    if va + 2 * vb + vc <= 40:
        return eq21.subs(a, va).subs(b, vb).subs(c, vc)
    return False

is_solution(1, 1, 1)
```

$\displaystyle \text{False}$

```python
def find_all_solutions():
    return [(va, vb, vc)
           for va in range(1, 40)
           for vb in range(1, 20)
           for vc in range(1, 40)
           if is_solution(va, vb, vc)]

solutions = find_all_solutions()
print('Number of solutions is', len(solutions))
```

    Number of solutions is 42

The solutions are:

```python
for s in solutions:
    print(s)
```

    (11, 1, 1)
    (11, 1, 2)
    (11, 1, 3)
    (11, 1, 4)
    (11, 1, 5)
    (11, 1, 6)
    (11, 1, 7)
    (11, 1, 8)
    (11, 1, 9)
    (11, 1, 10)
    (11, 1, 11)
    (11, 1, 12)
    (11, 1, 13)
    (11, 1, 14)
    (11, 1, 15)
    (11, 1, 16)
    (11, 1, 17)
    (11, 1, 18)
    (11, 1, 19)
    (11, 1, 20)
    (11, 1, 21)
    (11, 1, 22)
    (11, 1, 23)
    (11, 1, 24)
    (11, 1, 25)
    (11, 1, 26)
    (11, 1, 27)
    (22, 2, 1)
    (22, 2, 2)
    (22, 2, 3)
    (22, 2, 4)
    (22, 2, 5)
    (22, 2, 6)
    (22, 2, 7)
    (22, 2, 8)
    (22, 2, 9)
    (22, 2, 10)
    (22, 2, 11)
    (22, 2, 12)
    (22, 2, 13)
    (22, 2, 14)
    (33, 3, 1)


```python
from sympy import *

a, k, m = symbols('a k m')

x = IndexedBase('x')
i = Idx('i')

u = Piecewise(
(k*(x[i]-a)**m, Gt(x[a],a)),
(0, And(Ge(x[i],-a), Le(x[i],a))),
    (k*(-x[i]-a)**m, Lt(x[i],-a))
)

u

Sum(u, (i,1,3)).doit()
```

$\displaystyle \begin{cases} k \left(- a + {x}_{1}\right)^{m} & \text{for}\: a < {x}_{a} \\0 & \text{for}\: a \geq - {x}_{1} \wedge a \geq {x}_{1} \\k \left(- a - {x}_{1}\right)^{m} & \text{for}\: a < - {x}_{1} \end{cases} + \begin{cases} k \left(- a + {x}_{2}\right)^{m} & \text{for}\: a < {x}_{a} \\0 & \text{for}\: a \geq - {x}_{2} \wedge a \geq {x}_{2} \\k \left(- a - {x}_{2}\right)^{m} & \text{for}\: a < - {x}_{2} \end{cases} + \begin{cases} k \left(- a + {x}_{3}\right)^{m} & \text{for}\: a < {x}_{a} \\0 & \text{for}\: a \geq - {x}_{3} \wedge a \geq {x}_{3} \\k \left(- a - {x}_{3}\right)^{m} & \text{for}\: a < - {x}_{3} \end{cases}$

```python

```


## System dynamics
CartPole environment consists of a cart on a trail and a pole hinge fixed on it.

### States
$$X = \left[\array{x \\ \dot{x} \\ \theta \\ \dot{\theta} }\right]$$
### Parameters:
$l = 0.5 m$: half length of pole (homogenuous pole)
$M = 1 kg$: mass of cart
$m = 0.1 kg$: mass of pole
$\tau = 0.02 s$: discrete time peroid
$F_u (N)$: exterted force on the cart
### Derivative equations based on Newton and Euler's equations
$$(M+m)\ddot{x}= F_u + ml (\dot{\theta})^2sin\theta-ml\ddot{\theta}cos\theta$$
$$\frac{4}{3} ml^2\ddot{\theta}=mglsin\theta-mlcos\theta \ddot{x}$$
### How simulation works
- Calculate accelaration based on state $x_k$
$$\gamma_k = \frac{(F_{u_k} + ml{\dot{\theta_k}}^2sin\theta_k)}{M+m}$$
$$\ddot{\theta_k}=\frac{gsin\theta_k-\gamma_k cos\theta_k}{\frac{4}{3}(M+m)l-mlcos^2\theta_k}$$
$$\ddot{x_k}=\gamma_k-\frac{ml\ddot{\theta_k}cos\theta_k}{M+m}$$
- In very short discrete time interval, calculate $x_{k+1}$
$$x_{k+1} = x_k + \tau\dot{x_k}$$
$$\dot{x_{k+1}}=\dot{x_k}+\tau\ddot{x_k}$$
$$\theta_{k+1} = \theta_k + \tau\dot{\theta_k}$$
$$\dot{\theta_k}=\dot{\theta_k}+\tau\ddot{\theta_k}$$

### Linearized state space function
- Assumption
$$sin\theta\approx\theta$$
$$cos\theta\approx 1$$
$$(\dot{\theta})^2 \approx 0$$
- Take assumption into derivative equations
$$\beta = \frac{4}{3}-\frac{m}{M+m}$$
$$\ddot{x}_k=-\frac{mg\theta}{(M+m)\beta}\theta-\frac{F_u(\beta(M+m)+m)}{(M+m)^2\beta}$$
$$\ddot{\theta}_k=\frac{g}{l\beta}\theta-\frac{F_u}{(M+m)l\beta}$$
- The linearized discrete equation of system following previous procedure
    - states
$$X=\left[\matrix{x \\ \theta \\ \dot{x} \\ \dot{\theta}}\right]$$
    - state equations
$$x_{k+1}=x_k+\dot{x_k}\tau$$
$$\theta_{k+1}=\theta_k+\dot{\theta_k}\tau$$
$$\dot{x}_{k+1} = \dot{x}_k+\tau \ddot{x_k}$$
$$\dot{\theta}_{k+1} = \dot{\theta}_k+\tau\ddot{\theta}_k$$
    - discrete state space
$$\left[\matrix{x_{k+1} \\ \dot{x}_{k+1} \\ \theta_{k+1} \\ \dot{\theta}_{k+1}}\right] =
\left[\matrix{1 & \tau & 0 & 0 \\
              0 & 1 & -\frac{\tau mg}{(M+m)\beta} & 0 \\
              0 & 0 & 1 & \tau \\
              0 & 0 & \frac{g\tau}{l\beta_k}& 1}\right]\left[\matrix{x_{k} \\ \dot{x}_{k} \\ \theta_{k} \\ \dot{\theta}_{k}}\right] + \left[\matrix{0 \\ \frac{(\beta(M+m)+m)}{(M+m)^2\beta} \\ 0 \\ \frac{-1}{(M+m)\beta} }\right][F_{u_k}]$$

```python
import gym
from cartpole_util import CartPoleEnv
env = CartPoleEnv(1e-1, 0.1)
```

    WARN: gym.spaces.Box autodetected dtype as <class 'numpy.float32'>. Please provide explicit dtype.

```python
J = 1/12*(env.masspole*2)**2
beta = env.masspole*env.masscart*env.length**2 + J*(env.masspole+env.masscart)
ac = -(env.masspole**2*env.length**2*env.gravity)/beta
bc = (J+env.masspole*env.length**2)/beta
cc = env.masspole*env.length*env.gravity*(env.masscart+env.masspole)/beta
dc = -env.masspole*env.length/beta
```

```python
import numpy as np
A_c = np.array([[0,1,0,0],
              [0,0,ac,0],
              [0,0,0,1],
              [0,0,cc,0]])
B_c = np.array([[0],
                [bc],
                [0],
                [dc]])
C_c = np.array([[0,1,0,0],
               [0,0,0,1]])
D_c = np.array([[0],
               [0]])
```

```python
print(C_c.shape)
```

    (2, 4)

```python
from scipy import signal
sys = signal.StateSpace(A_c, B_c, C_c, D_c)
```

```python
discrete_sys = sys.to_discrete(env.tau)
```

```python
discrete_sys.A
```

    array([[ 1.00000000e+00,  2.00000000e-02, -1.71037389e-04,
            -1.13996348e-06],
           [ 0.00000000e+00,  1.00000000e+00, -1.71144572e-02,
            -1.71037389e-04],
           [ 0.00000000e+00,  0.00000000e+00,  1.00376282e+00,
             2.00250792e-02],
           [-0.00000000e+00, -0.00000000e+00,  3.76518059e-01,
             1.00376282e+00]])

```python
from numpy.linalg import matrix_rank
```

```python
A = discrete_sys.A
# C = discrete_sys.C
# control_C = np.array([])
C= np.array([  [1,1,0,0],

               [0,0,0,1]])
```

```python
control_C = np.concatenate((C,np.matmul(C,A)),axis=0)
matrix_rank(control_C)
```

    4

```python
from sympy import sin, cos, Matrix, symbols, diff, simplify
from sympy.abc import rho, phi, theta, tau, omega
```

```python
X = Matrix([rho*cos(phi), rho*sin(phi), rho**2])
```

```python
Y = Matrix([rho, phi])
```

```python
X.jacobian(Y)
```

    Matrix([
    [cos(phi), -rho*sin(phi)],
    [sin(phi),  rho*cos(phi)],
    [   2*rho,             0]])

```python
v, g, F, m, l, M = symbols("v, g, F, m, l, M")
h2 = v+tau*(F+m*l*omega**2*sin(theta)-3/4*m*g*sin(theta)*cos(theta))/(M+m-3/4*m*(cos(theta))**2)
```

```python
simplify(diff(h2, theta))
```

    m*tau*(-1.5*(F - 0.375*g*m*sin(2*theta) + l*m*omega**2*sin(theta))*sin(theta)*cos(theta) + (M - 0.75*m*cos(theta)**2 + m)*(1.5*g*sin(theta)**2 - 0.75*g + 1.0*l*omega**2*cos(theta)))/(M - 0.75*m*cos(theta)**2 + m)**2

```python
diff(h2, omega)
```

    2*l*m*omega*tau*sin(theta)/(M - 0.75*m*cos(theta)**2 + m)

```python
diff(h2, v)
```

    1

```python
h4 = omega+tau*((M+m)*g*sin(theta)-m*l*omega**2*sin(theta)*cos(theta)-F*cos(theta))/(4/3*(m+M)*l-m*l*(cos(theta))**2)
```

```python
diff(h4, omega)
```

    -2*l*m*omega*tau*sin(theta)*cos(theta)/(-l*m*cos(theta)**2 + l*(1.33333333333333*M + 1.33333333333333*m)) + 1

```python

```


# Introduction to the Harmonic Oscillator

*Note:* Much of this is adapted/copied from https://flothesof.github.io/harmonic-oscillator-three-methods-solution.html

This week week we are going to begin studying molecular dynamics, which uses classical mechanics to study molecular systems. Our "hydrogen atom" in this section will be the 1D harmomic oscillator.

The harmonic oscillator is a system that, when displaced from its equilibrium position, experiences a restoring force F proportional to the displacement x:

$$F=-kx$$

The potential energy of this system is

$$V = {1 \over 2}k{x^2}$$

These are sometime rewritten as

$$ F=- \omega_0^2 m x, \text{ }  V(x) = {1 \over 2}  \omega_0^2 m {x^2}$$

Where $\omega_0  = \sqrt {{k \over m}} $

In classical mechanics, our goal is to determine the equations of motion, $x(t),y(t)$, that describe our system.

In this notebook we will use sympy to solve an second order, ordinary differential equation.

## 1. Solving differential equations with sympy

Solving differential equations can be tough, and there is not always a set plan on how to proceed. Luckily for us, the harmonic osscillator is the classic second order diffferential eqations.

Consider the following second order differential equation

$$ay(t)''+by(t)'=c$$

where $y(t)'' = {{{d^2}y} \over {dt^2}}$, and $y(t)' = {{{d}y} \over {dt}}$

We can rewrite this as a homogeneous linear differential equations

$$ay(t)''+by(t)'-c=0$$

The goal here is to find $y(t)$, similar to our classical mechanics problems. Lets use sympy to solve this equation

### Second order ordinary differential equation

First we import the sympy library

```python
import sympy as sym
```

Next we initialize pretty printing

```python
sym.init_printing()
```

Next we will set our symbols

```python
t,a,b,c=sym.symbols("t,a,b,c")
```

Now for somehting new. We can define functions using `sym.Function("f")`

```python
y=sym.Function("y")
y(t)
```

Now, If I want to define a first or second derivative, I can use `sym.diff`

```python
sym.diff(y(t),(t,1)),sym.diff(y(t),(t,2))
```

My differential equation can be written as follows

```python
dfeq=a*sym.diff(y(t),(t,2))+b*sym.diff(y(t),(t,1))-c
dfeq
```

```python
sol = sym.dsolve(dfeq)
sol
```

The two constants $C_1$ and $C_2$ can be determined by setting boundry conditions.
First, we can set the condition $y(t=0)=y_0$

The next intial condition we will set is $y'(t=0)=v_0$

To setup the equality we want to solve, we are using `sym.Eq`. This function sets up an equaility between a lhs aand rhs of an equation

```python
# sym.Eq example
alpha,beta=sym.symbols("alpha,beta")
sym.Eq(alpha+2,beta)
```

Back to the actual problem

```python
y0,v0=sym.symbols("y_0,v_0")
ics=[sym.Eq(sol.args[1].subs(t, 0), y0),
     sym.Eq(sol.args[1].diff(t).subs(t, 0), v0)]
ics
```

We can use this result to first solve for $C_2$ and then solve for $C_1$.
Or we can use sympy to solve this for us.

```python
solved_ics=sym.solve(ics)
solved_ics
```

Substitute the result back into $y(t)$

```python
full_sol = sol.subs(solved_ics[0])
full_sol
```

We can plot this result too. Assume that $a,b,c=1$ and that the starting conditions are $y_0=0,v_0=0$

We will use two sample problems:

* case 1 : initial position is nonzero and initial velocity is zero
* case 2 : initial position is zero and initialvelocity is nonzero

```python
# Print plots
%matplotlib inline
```

#### Initial velocity set to zero

```python
case1 = sym.simplify(full_sol.subs({y0:0, v0:0, a:1, b:1, c:1}))
case1
```

```python
sym.plot(case1.rhs)
sym.plot(case1.rhs,(t,-2,2))
```

#### Initial velocity set to one

```python
case2 = sym.simplify(full_sol.subs({y0:0, v0:1, a:1, b:1, c:1}))
case2
```

```python
sym.plot(case2.lhs,(t,-2,2))
```

## Calculate the phase space

As we will see in lecture, the state of our classical systems are defined as points in phase space, a hyperspace defined by ${{\bf{r}}^N},{{\bf{p}}^N}$. We will convert our sympy expression into a numerical function so that we can plot the path of $y(t)$ in phase space $y,y'$.

```python
case1
```

```python
# Import numpy library
import numpy as np

# Make numerical functions out of symbolic expressions
yfunc=sym.lambdify(t,case1.rhs,'numpy')
vfunc=sym.lambdify(t,case1.rhs.diff(t),'numpy')

# Make list of numbers
tlst=np.linspace(-2,2,100)

# Import pyplot
import matplotlib
import matplotlib.pyplot as plt
# Make plot
plt.plot(yfunc(tlst),vfunc(tlst))
plt.xlabel('$y$')
plt.ylabel("$y'$")
plt.show()
```

### Exercise 1.1

Change the initial starting conditions and see how that changes the plots. Make three different plots with different starting conditions

```python
Natcase1 = sym.simplify(full_sol.subs({y0:1, v0:2, a:1, b:1, c:1}))
Natcase1
```

```python
import numpy as np

# Make numerical functions out of symbolic expressions
yfunc=sym.lambdify(t,Natcase1.rhs,'numpy')
vfunc=sym.lambdify(t,Natcase1.rhs.diff(t),'numpy')

# Make list of numbers
tlst=np.linspace(-2,2,100)

# Import pyplot
import matplotlib
import matplotlib.pyplot as plt
# Make plot
plt.plot(yfunc(tlst),vfunc(tlst))
plt.xlabel('$y$')
plt.ylabel("$y'$")
plt.show()
```

```python
Natcase2 = sym.simplify(full_sol.subs({y0:-4, v0:-5, a:1, b:1, c:1}))
Natcase2
```

```python
import numpy as np

# Make numerical functions out of symbolic expressions
yfunc=sym.lambdify(t,Natcase2.rhs,'numpy')
vfunc=sym.lambdify(t,Natcase2.rhs.diff(t),'numpy')

# Make list of numbers
tlst=np.linspace(-2,2,100)

# Import pyplot
import matplotlib
import matplotlib.pyplot as plt
# Make plot
plt.plot(yfunc(tlst),vfunc(tlst))
plt.xlabel('$y$')
plt.ylabel("$y'$")
plt.show()
```

```python
Natcase3 = sym.simplify(full_sol.subs({y0:-100, v0:-150, a:1, b:1, c:1}))
Natcase3
```

```python
import numpy as np

# Make numerical functions out of symbolic expressions
yfunc=sym.lambdify(t,Natcase3.rhs,'numpy')
vfunc=sym.lambdify(t,Natcase3.rhs.diff(t),'numpy')

# Make list of numbers
tlst=np.linspace(-2,2,5000)

# Import pyplot
import matplotlib
import matplotlib.pyplot as plt
# Make plot
plt.plot(yfunc(tlst),vfunc(tlst))
plt.xlabel('$y$')
plt.ylabel("$y'$")
plt.show()
```

## 2. Harmonic oscillator

Applying the harmonic oscillator force to Newton's second law leads to the following second order differential equation

$$ F = m a $$

$$ F= - \omega_0^2 m x $$

$$ a = - \omega_0^2 x $$

$$ x(t)'' = - \omega_0^2 x $$

The final expression can be rearranged into a second order homogenous differential equation, and can be solved using the methods we used above

Your goal is determine and plot the equations of motion of a 1D harmomnic oscillator

### Exercise 2.1

1. Use the methodology above to determine the equations of motion $x(t), v(t)$ for a harmonic ocillator
1. Solve for any constants by using the following initial conditions: $x(0)=x_0, v(0)=v_0$
1. Show expressions for and plot the equations of motion for the following cases:
    1. $x(0)=0, v(0)=0$
    1. $x(0)=0, v(0)>0$
    1. $x(0)>0, v(0)=0$
    1. $x(0)<0, v(0)=0$
1. Plot the phasespace diagram for the harmonic oscillator

```python
import sympy as sym
sym.init_printing()
omega0,t,a,b,c=sym.symbols("omega_0,t,a,b,c")
omega0

```

```python
x=sym.Function("x")
x(t)
```

```python
Natfeq=sym.diff(x(t),(t,2))+omega0**2*x(t)
Natfeq
```

```python
Natsol=sym.dsolve(Natfeq)
Natsol
```

```python
x0,v0=sym.symbols("x_0,v_0")
Natics=[sym.Eq(Natsol.args[1].subs(t, 0), x0),
     sym.Eq(Natsol.args[1].diff(t).subs(t, 0), v0)]
Natics
```

```python
Natsolved_ics=sym.solve(Natics)
Natsolved_ics
```

```python
Natfull_sol = Natsol.subs(Natsolved_ics[0])
Natfull_sol
```

```python
Natcasev0 = sym.simplify(Natfull_sol.subs({x0:0, v0:0, a:1, b:1, c:1}))
Natcasev0
```

```python
sym.plot(Natcasev0.rhs,(t,-1,1))
```

```python
Natcasev2 = sym.simplify(Natfull_sol.subs({x0:0, v0:200, omega0:1}))
Natcasev2
```

```python
sym.plot(Natcasev2.rhs,(t,-1,1))
```

```python
Natcasex2 = sym.simplify(Natfull_sol.subs({x0:200, v0:0, omega0:1}))
Natcasex2
```

```python
sym.plot(Natcasex2.rhs,(t,-1,1))
```

```python
Natcasexminus2 = sym.simplify(Natfull_sol.subs({x0:-200, v0:0, omega0:1}))
Natcasexminus2
```

```python
sym.plot(Natcasexminus2.rhs,(t,-1,1))
```

```python
import numpy as np

# Make numerical functions out of symbolic expressions
yfunc=sym.lambdify(t,Natcasev0.rhs,'numpy')
vfunc=sym.lambdify(t,Natcasev0.rhs.diff(t),'numpy')

# Make list of numbers
tlst=np.linspace(-2,2,100)

# Import pyplot
import matplotlib
import matplotlib.pyplot as plt
# Make plot
plt.plot(yfunc(tlst),vfunc(tlst))
plt.xlabel('$y$')
plt.ylabel("$y'$")
plt.show()
```

```python
import numpy as np

# Make numerical functions out of symbolic expressions
yfunc=sym.lambdify(t,Natcasev2.rhs,'numpy')
vfunc=sym.lambdify(t,Natcasev2.rhs.diff(t),'numpy')

# Make list of numbers
tlst=np.linspace(-10,10,100)

# Import pyplot
import matplotlib
import matplotlib.pyplot as plt
# Make plot
plt.plot(yfunc(tlst),vfunc(tlst))
plt.xlabel('$y$')
plt.ylabel("$y'$")
plt.show()
```

```python
import numpy as np

# Make numerical functions out of symbolic expressions
yfunc=sym.lambdify(t,Natcasex2.rhs,'numpy')
vfunc=sym.lambdify(t,Natcasex2.rhs.diff(t),'numpy')

# Make list of numbers
tlst=np.linspace(-10,10,100)

# Import pyplot
import matplotlib
import matplotlib.pyplot as plt
# Make plot
plt.plot(yfunc(tlst),vfunc(tlst))
plt.xlabel('$y$')
plt.ylabel("$y'$")
plt.show()
```

```python
import numpy as np

# Make numerical functions out of symbolic expressions
yfunc=sym.lambdify(t,Natcasex2.rhs,'numpy')
vfunc=sym.lambdify(t,Natcasex2.rhs.diff(t),'numpy')

# Make list of numbers
tlst=np.linspace(-10,10,100)

# Import pyplot
import matplotlib
import matplotlib.pyplot as plt
# Make plot
plt.plot(yfunc(tlst),vfunc(tlst))
plt.xlabel('$y$')
plt.ylabel("$y'$")
plt.show()
```

```python
import numpy as np

# Make numerical functions out of symbolic expressions
yfunc=sym.lambdify(t,Natcasexminus2.rhs,'numpy')
vfunc=sym.lambdify(t,Natcasexminus2.rhs.diff(t),'numpy')

# Make list of numbers
tlst=np.linspace(-10,10,100)

# Import pyplot
import matplotlib
import matplotlib.pyplot as plt
# Make plot
plt.plot(yfunc(tlst),vfunc(tlst))
plt.xlabel('$y$')
plt.ylabel("$y'$")
plt.show()
```

```python

```


# Exercise 3: formation control

```python
# header to start
%matplotlib notebook

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import matplotlib as mp

import pickle
import IPython

import scipy.linalg
```

We wish the control the formation of 4 robots randomly distributed in the environment to keep the formation shown in the figure of Exercise 2.

## Question 1
Assume each agent has state space dynamics $\dot{\mathbf{p}}_i =\mathbf{u}_i$, with $\mathbf{u}_i$ in $\mathbb{R}^2$ and $\mathbf{u} = [\mathbf{u}_1, \mathbf{u}_2, \mathbf{u}_3, \mathbf{u}_4]$.

Implement the second order linear control law seen in the class
$$\mathbf{u} = -k \mathbf{L} \mathbf{x} + k \mathbf{D z}_{ref}$$

where $k>0$ is a positive gain and $\mathbf{D}$ is the incidence matrix of the graph.

Simulate the control law for several random initial conditions of the agents (in 2D). What do you observe? How does it compare to the same control law but for a framework with a complete graph?

## Question 2
Assume each agent has state space dynamics $\dot{\mathbf{p}}_i =\mathbf{u}_i$, with $\mathbf{u}_i$ in $\mathbb{R}^2$ and $\mathbf{u} = [\mathbf{u}_1, \mathbf{u}_2, \mathbf{u}_3, \mathbf{u}_4]$.

We now consider the following control law
$$\begin{equation}
\mathbf{u} = \mathbf{R}_\mathcal{G}^T(\mathbf{p}) (\mathbf{g}_d - \mathbf{g}_\mathcal{G}(\mathbf{p}))
\end{equation}$$
where $\mathbf{R}_\mathcal{G}$ is the rigidity matrix associated to the graph of the framework, $\mathbf{g}_d$ is the vector of desired square distance between agents and $\mathbf{g}_\mathcal{G}$ is the measured square distance between each agent.

Simulate the control law for several random initial conditions of the agents (in 2D). What do you observe? How does it compare to the same control law but for a framework with a complete graph?

## Question 3

How would you compare both control laws? What are the pros and cons of each of them?

## Helper function to display results

This function can be used to display the behavior of the robots in 2D

```python
def make_animation(plotx,E,xl=(-2,2),yl=(-2,2),inter=25, display=False):
    '''
    takes a graph and motion of vertexes in 2D and returns an animation
    E: list of edges (each edge is a pair of vertexes)
    plotx: a matrix of states ordered as (x1, y1, x2, y2, ..., xn, yn) in the rows and time in columns
    xl and yl define the display boundaries of the graph
    inter is the interval between each point in ms
    '''
    fig = mp.figure.Figure()
    mp.backends.backend_agg.FigureCanvasAgg(fig)
    ax = fig.add_subplot(111, autoscale_on=False, xlim=xl, ylim=yl)
    ax.grid()

    list_of_lines = []
    for i in E: #add as many lines as there are edges
        line, = ax.plot([], [], 'o-', lw=2)
        list_of_lines.append(line)

    def animate(i):
        for e in range(len(E)):
            vx1 = plotx[2*E[e][0],i]
            vy1 = plotx[2*E[e][0]+1,i]
            vx2 = plotx[2*E[e][1],i]
            vy2 = plotx[2*E[e][1]+1,i]
            list_of_lines[e].set_data([vx1,vx2],[vy1,vy2])
        return list_of_lines

    def init():
        return animate(0)

    ani = animation.FuncAnimation(fig, animate, np.arange(0, len(plotx[0,:])),
        interval=inter, blit=True, init_func=init)
    plt.close(fig)
    plt.close(ani._fig)
    if(display==True):
        IPython.display.display_html(IPython.core.display.HTML(ani.to_html5_video()))
    return ani
```

```python
# for example assume that you have simulated a formation control in 2D and stored the data in a file
# we load the data needed for the display
with open('example_animation.pickle', 'rb') as f:
    data = pickle.load(f)

# this is the list of edges (as we usually define them for an undirected graph)
E = data['E']
print('the list of edges is:')
print(E)

# this is the time of simulation
t = data['t']

# this is an array containing the evolution of the states of the robot
# x[0,:] contains the time evolution of the x variable of robot 1
# x[1,:] contains the time evolution of the y variable of robot 1
# x[2,:] contains the time evolution of the x variable of robot 2
# etc
x = data['x']

# since we simulated with a small delta t = 0.001, we want to subsample for display
# we just take data every 50ms
plotx = x[:,::50]

make_animation(plotx, E, inter=50, display=True)

# a video showing the behavior of the robots and the connection between the robots should be displayed below
```

    the list of edges is:
    [[0, 1], [0, 2], [0, 3], [1, 2], [2, 3]]

    <matplotlib.animation.FuncAnimation at 0x11038e990>


Exercise 3.11

        In this problem we will investigate the $t$-statistic for the null hypothesis $H_0 : \beta = 0$ in simple linear regression without an intercept. To begin, we generate a predictor $x$ and a response $y$ as follows.

        > set.seed(1)
> x=rnorm(100)
> y=2*x*rnorm(100)

Perform a simple linear regression of $y$ onto $x$, without an intercept. Report the coefficient estimate $\hat{\beta}$, the standard error of this coefficient estimate, and the $t$-statistic and $p$-value associated with the null hypothesis $H_0:\beta = 0$. Comment on these results. (You can perform regression without an intercept using the command lm(y~x+0).)

Now perform a simple linear regression of $x$ onto $y$ without an intercept, and report the coefficient estimate, its standard error, and the corresponding $t$-statistic and $p$-values associated with the null hypothesis $H_0:\beta = 0$. Comment on these results.

What is the relationship between the results obtained in 1 and 2?

For the regression of $Y$ onto $X$ without an intercept, the $t$-statistic for $H_0:\beta = 0$ takes the form $\hat{\beta} / \mathrm{SE}(\hat{\beta})$, where $\hat{\beta}$ is given by [equation] (3.38), and where
        $$
            \mathrm{SE}(\hat{\beta}) = \sqrt{\frac{\sum_{i=1}^n \left(y_i - x_i \hat{\beta}\right)^2}{(n-1)\sum_{i'=1}^n x_{i'}^2}} \,.
        $$
        (These formulas are slightly different from those given in Sections 3.1.1 and 3.1.2, since here we are performing regression without an intercept.) Show algebraically, and confirm numerically in R , that the $t$-statistic can be written as
        $$
            \frac{ (\sqrt{n-1}) \sum_{i=1}^n x_i y_i }{ \sqrt{ (\sum_{i=1}^n x_i^2) (\sum_{i'=1}^n y_{i'}^2) - (\sum_{i'=1}^n x_{i'} y_{i'})^2 } } \,.
        $$

Using the results from 4, argue that the $t$-statistic for the regression of $y$ onto $x$ is the same as the $t$-statistic for the regression of $x$ onto $y$.

In R, show that when regression is performed with an intercept, the $t$-statistic for $H_0:\beta_1 = 0$ is the same for the regression of $y$ onto $x$ as it is for the regression of $x$ onto $y$.

```python
import pandas as pd
import numpy as np
import numpy.testing as npt
import matplotlib.pyplot as plt

%run ../../customModules/usefulFunctions.ipynb
# https://stackoverflow.com/questions/34398054/ipython-notebook-cell-multiple-outputs
from IPython.core.interactiveshell import InteractiveShell
InteractiveShell.ast_node_interactivity = "all"

import statsmodels.api as sm
```

```python
np.random.seed(42)
x = np.random.normal(size=100, loc=0, scale=1)
y = 2 * x * np.random.normal(size=100, loc=0, scale=1)
df_x = pd.DataFrame({'x': x})
df_y = pd.DataFrame({'y': y})
fig = plt.figure(constrained_layout=True, figsize=(3, 3))
ax1 = fig.add_subplot(211)
_ = ax1.scatter(df_x, df_y)
_ = ax1.set_xlabel('x')
_ = ax1.set_ylabel('y')
ax2 = fig.add_subplot(212)
_ = ax2.scatter(df_y, df_x)
_ = ax2.set_xlabel('y')
_ = ax2.set_ylabel('x')

```

Exercise 3.11.1

    Perform a simple linear regression of $y$ onto $x$, without an intercept. Report the coefficient estimate $\hat{\beta}$, the standard error of this coefficient estimate, and the $t$-statistic and $p$-value associated with the null hypothesis $H_0:\beta = 0$. Comment on these results. (You can perform regression without an intercept using the command lm(y~x+0).)

```python
model = sm.OLS(df_y, df_x)
fitted = model.fit()
coef_result1 = fitted.params[0] # store the value of the slope coefficient for later use
t_stat_result1 = fitted.tvalues[0]  # store the value t-statistic for later use
fitted.summary()
createSimpleLinearRegressionPlot(df_x, df_y, fitted_model=fitted)
```

The estimation of the coefficient $\hat{\beta}$ is $0.69$, with an estimated standard error of $0.21$, a $t$-statistic of $3.23$ and a $p$ value of $0.2 \%$. This indicates that there is a significant relation between $x$ and $y$.

Exercise 3.11.2

    Now perform a simple linear regression of $x$ onto $y$ without an intercept, and report the coefficient estimate, its standard error, and the corresponding $t$-statistic and $p$-values associated with the null hypothesis $H_0:\beta = 0$. Comment on these results.

```python
model = sm.OLS(df_x, df_y)
fitted = model.fit()
fitted.tvalues
t_stat_result2 = fitted.tvalues[0]  # store the value t-statistic for later use
fitted.summary()
createSimpleLinearRegressionPlot(df_y, df_x, fitted_model=fitted)
```

The estimation of the coefficient $\hat{\beta}$ is $0.14$, with an estimated standard error of $0.04$, a $t$-statistic of $3.23$ and a $p$ value of $0.2 \%$. This again indicates that there is a significant relation between $x$ and $y$.

Exercise 3.11.3

    What is the relationship between the results obtained in 1 and 2?

For result 1 we have assumed
$$
y_i = \hat{\beta} * x_i + e_i  \,,
$$which can be rewritten as
$$
x_i = \frac{1}{\hat{\beta}} y_i-\frac{e_i}{\hat{\beta}} \,.
$$
We can easily compute this calculation as a sanity check.

```python
error = y - coef_result1 * x
x2 = (y - error) / coef_result1
difference = x - x2

# assert that the difference vector indeed approximates a zero vector
npt.assert_almost_equal(difference, np.zeros(difference.shape), decimal=10)
```

Exercise 3.11.4

    For the regression of $Y$ onto $X$ without an intercept, the $t$-statistic for $H_0:\beta = 0$ takes the form $\hat{\beta} / \mathrm{SE}(\hat{\beta})$, where $\hat{\beta}$ is given by [equation] (3.38), and where
        $$
            \mathrm{SE}(\hat{\beta}) = \sqrt{\frac{\sum_{i=1}^n \left(y_i - x_i \hat{\beta}\right)^2}{(n-1)\sum_{i'=1}^n x_{i'}^2}} \,.
        $$
        (These formulas are slightly different from those given in Sections 3.1.1 and 3.1.2, since here we are performing regression without an intercept.) Show algebraically, and confirm numerically in R , that the $t$-statistic can be written as
        $$
            \frac{ (\sqrt{n-1}) \sum_{i=1}^n x_i y_i }{ \sqrt{ (\sum_{i=1}^n x_i^2) (\sum_{i'=1}^n y_{i'}^2) - (\sum_{i'=1}^n x_{i'} y_{i'})^2 } } \,.
        $$

Note that equation (3.38) is given by
$$
     \hat{\beta} = \frac{\sum_{i=1}^n x_i y_i}{\sum_{i'=1}^n x_{i'}^2} \,.
$$
Thus, we can write the $t$-statistic as
\begin{equation}
    \begin{aligned}
        t & =  \frac{\sum_{i=1}^n x_i y_i}{\sum_{i=1}^n x_{i}^2} \sqrt{\frac{(n-1)\sum_{i=1}^n x_{i}^2}{\sum_{i=1}^n \left(y_i - x_i \hat{\beta}\right)^2}} \\&
          = \frac{\sqrt{n-1} \sum_{i=1}^n x_i y_i}{ \sqrt{ \sum_{i=1}^n x_i^2 \sum_{i=1}^n \left(y_i - x_i \hat{\beta}\right)^2 } } \\&
          = \frac{\sqrt{n-1} \sum_{i=1}^n x_i y_i}{ \sqrt{ \sum_{i=1}^n x_i^2 \sum_{i=1}^n \left( y_i^2 - 2 \hat{\beta} x_i y_i + \hat{\beta} x_i^2 \right) } } \\&
          = \frac{\sqrt{n-1} \sum_{i=1}^n x_i y_i}{\sqrt{ \sum_{i=1}^n x_i^2 \sum_{i=1}^n y_i^2 - \hat{\beta} \sum_{i=1}^n x_i^2 \left( 2 \sum_{i=1}^n x_i y_i - \hat{\beta} \sum_{i=1}^n x_i^2 \right) }} \,.
    \end{aligned}
\end{equation}
Now, using the estimate for $\hat{\beta}$ given by equation (3.38) to further simply the expression for the $t$-statistic
\begin{equation}
    \begin{aligned}
        t & = \frac{\sqrt{n-1} \sum_{i=1}^n x_i y_i}{\sqrt{ \sum_{i=1}^n x_i^2 \sum_{i=1}^n y_i^2 - \sum_{i=1}^n x_i y_i \left( 2 \sum_{i=1}^n x_i y_i - \sum_{i=1}^n x_i y_i \right) }} \\&
          = \frac{ (\sqrt{n-1}) \sum_{i=1}^n x_i y_i }{ \sqrt{ (\sum_{i=1}^n x_i^2) (\sum_{i'=1}^n y_{i'}^2) - (\sum_{i'=1}^n x_{i'} y_{i'})^2 } } \,.
    \end{aligned}
\end{equation}
as required. We will now also confirm this equation using Python.

```python
n = x.shape[0]
t = ( (n-1)**0.5 * np.sum(x*y) ) / ( np.sum(x**2) * np.sum(y**2) - (np.sum(x*y))**2  )**0.5
# assert that this result is equal to the t-statistics computer for the above two regressions
assert abs(t - t_stat_result1) <= 1e-10
assert abs(t - t_stat_result2) <= 1e-10
```

Exercise 3.11.5

    Using the results from 4, argue that the $t$-statistic for the regression of $y$ onto $x$ is the same as the $t$-statistic for the regression of $x$ onto $y$.

We can clearly see from the formula of the $t$-statistic that it is a symmetric function of the $x_i$ and $y_i$ variables.

Exercise 3.11.6

    In R, show that when regression is performed with an intercept, the $t$-statistic for $H_0:\beta_1 = 0$ is the same for the regression of $y$ onto $x$ as it is for the regression of $x$ onto $y$.

```python
df_x.insert(0, 'Intercept', 1)
df_y.insert(0, 'Intercept', 1)
```

```python
model = sm.OLS(df_y[['y']], df_x)
fitted = model.fit()
fitted.summary()
createSimpleLinearRegressionPlot(df_x, df_y[['y']], fitted_model=fitted)
```

```python
model = sm.OLS(df_x[['x']], df_y)
fitted = model.fit()
fitted.summary()
createLinearRegressionPlot(df_y, df_x[['x']], fitted_model=fitted)
```

We see that $\beta_0$ is estimated to be very close to zero, which is expected because the data used to fit the model was generated without an intercept. This is also reflected in that both models (with and without an intercept) have estimated approximately the same value for $\beta_1$, and their $t$-statistics also have very similar values.


# Chapter 12: Markov Chain Monte Carlo

This Jupyter notebook is the Python equivalent of the R code in section 12.4 R, pp. 512 - 517, [Introduction to Probability, 2nd Edition](https://www.crcpress.com/Introduction-to-Probability-Second-Edition/Blitzstein-Hwang/p/book/9781138369917), Blitzstein & Hwang.

----

```python
import matplotlib.pyplot as plt
import numpy as np

%matplotlib inline
```

## Metropolis-Hastings

Here's how to implement the Metropolis-Hastings algorithm for Example 12.1.8, the Normal-Normal model. First, we choose our observed value of $Y$ and decide on values for the constants $\sigma$, $\mu$, and $\tau$:

```python
y = 3
sigma = 1
mu = 0
tau = 2
```

We also need to choose the standard deviation of the proposals for step 1 of the algorithm, as explained in Example 12.1.8; for this problem, we let $d = 1$. We set the number of iterations to run, and we allocate a NumPy array `theta` of length 104 which we will fill with our simulated draws:

```python
d = 1
niter = 10**4
theta = np.zeros(niter)
```

Now for the main loop. We initialize $\theta$ to the observed value $y$, then run the algorithm described in Example 12.1.8:

```python
theta[0] = y

np.random.seed(1134903170)

from scipy.stats import binom, norm

for i in range(1, niter):
    theta_p = theta[i-1] + norm.rvs(loc=mu, scale=2, size=1)[0]
    numer = norm.pdf(y, loc=theta_p, scale=sigma) * norm.pdf(theta_p, loc=mu, scale=tau)
    denom = norm.pdf(y, loc=theta[i-1], scale=sigma) * norm.pdf(theta[i-1], loc=mu, scale=tau)
    r = numer / denom
    flip = binom.rvs(1, np.min([r, 1]), size=1)[0]
    theta[i] = theta_p if flip==1 else theta[i-1]
```

Let's step through each line inside the loop. The proposed value of $\theta$ is `theta_p`, which equals the previous value of $\theta$ plus a Normal random variable with mean 0 and standard deviation $d$ (recall that [`scipy.stats.norm.rvs`](https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.norm.html) function takes as parameter `scale` the standard deviation and _not_ the variance). The ratio `r` is

\begin{align}
  \frac{f_{\theta|Y}(x^{\prime}|y)}{f_{\theta|Y}(x|y)} &= \frac{e^{-\frac{1}{2 \, \sigma^2}(y-x^{\prime})^2} \,\, e^{-\frac{1}{2 \, \tau^2}(x^{\prime}-\mu)^2}}{e^{-\frac{1}{2 \, \sigma^2}(y-x)^2} \,\, e^{-\frac{1}{2 \, \tau^2}(x-\mu)^2}}
\end{align}

where `theta_p` is playing the role of $x^{\prime}$ and `theta[i-1]` is playing the role of $x$. The coin flip to determine whether to accept or reject the proposal is `flip`, which is a coin flip with probability `numpy.min([r, 1])` of Heads (encoding Heads as 1 and Tails as 0). Finally, we set `theta[i]` equal to the proposed value if the coin flip lands Heads, or keep it at the previous value otherwise.

The array `theta` now contains all of our simulation draws. We typically discard some of the initial draws to give the chain some time to approach the stationary distribution. The following line of code discards the first half of the draws:

```python
theta = theta[-int(niter/2):]
```

To see what the remaining draws look like, we can create a histogram using [`matplotlib.axes.Axes.hist`](https://matplotlib.org/api/_as_gen/matplotlib.axes.Axes.hist.html) function. We can also compute summary statistics such as `numpy.mean(theta)` and `numpy.var(theta)`, which give us the sample mean and sample variance.

```python
_, ax = plt.subplots(figsize=(8, 5))

ax.hist(theta, bins=16)

ax.set_xlabel(r'$\theta$')
ax.set_ylabel(r'Frequency')
ax.set_title(r'Metropolis-Hastings: Histogram of posterior distribution of $\theta | Y=3$')

plt.show()
```

```python
sample_mean = np.mean(theta)
print('sample mean = {}'.format(sample_mean))

sample_var = np.var(theta, ddof=1)
print('sample var = {}'.format(sample_var))
```

    sample mean = 2.3538455429726173
    sample var = 0.8442196578144929

## Gibbs

Now let's implement Gibbs sampling for Example 12.2.6, the chicken-egg problem with unknown hatching probability and invisible unhatched eggs. The first step is to decide on our observed value of $X$, as well as the constants $\lambda$, $a$, $b$:

```python
x = 7
# 'lambda' is a reserved keyword in Python!
lambd = 10
a = 1
b = 1
```

Next we decide how many iterations to run, and we allocate space for our results, creating two NumPy arrays `p` and `N` of length 104 which we will fill with our simulated draws:

```python
niter = 10**4
p = np.zeros(niter)
N = np.zeros(niter)
```

Finally, we're ready to run the Gibbs sampler. We initialize `p` and `N` to the values 0.5 and $2x$, respectively, and then we run the algorithm as explained in Example 12.2.6:

```python
p[0] = 0.5
N[0] = 2*x

np.random.seed(1836311903)

from scipy.stats import beta, poisson

for i in range(1, niter):
    p[i] = beta.rvs(x+b, N[i-1]-x+b, size=1)[0]
    N[i] = x + poisson.rvs(lambd*(1-p[i-1]), size=1)[0]
```

Again, we discard the initial draws:

```python
p = p[-int(niter/2):]
N = N[-int(niter/2):]
```

To see what the remaining draws look like, we can make histograms using `Axes.hist(p)` and `Axes.hist(N)`, which will result in graphs similar to those R-generated ones in Figure 12.5. We can also compute summary statistics such as `numpy.mean(p)` or `numpy.median(p)`.

```python
_, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 6))

# graph for hist(p)
ax1.hist(p, bins=16)
ax1.set_xlim((0,1))
ax1.set_xlabel(r'$x$')
ax1.set_ylabel('Frequency')
ax1.set_title(r'Histogram of $p$')

# graph for hist(N)
ax2.hist(N, bins=16)
ax2.set_xticks(range(0,35,5))
ax2.set_xlabel(r'$N$')
ax2.set_ylabel('Frequency')
ax2.set_title(r'Histogram of $N$')

plt.suptitle(r'Gibbs: Histograms of posterior distributions $p$ and $N$')

plt.show()
```

```python
mean_p = np.mean(p)
print('mean of p = {}'.format(mean_p))

med_p = np.median(p)
print('median of p = {}'.format(med_p))
```

    mean of p = 0.6822187872541091
    median of p = 0.6921336843178136

----

Joseph K. Blitzstein and Jessica Hwang, Harvard University and Stanford University, © 2019 by Taylor and Francis Group, LLC


# Estimating the mass of exoplanets

When a star has planetary companions, it describes an epicyclic motion around the center of mass of the system. When the motion has a component in the direction of the observer (the direction of the line of sight), this allows to detect the planetary companions. Indeed, as the distance between an observer and a star changes, because of the Doppler effect, the light received by the observer shifts in frequency.

By measuring spectra at different times and measuring how the spectral absorption lines shift in wavelength, it is possible to obtain the star velocity in the direction of the line of sight. We then have a time series of measurements, with a nominal error on each of them. If one detects a periodic signal in the time series, then this one might be due to a planet.

Determining if a periodic signal is present and if it originates from a planet is difficult, and will not be the object of the present exercise. In this spreadsheet, we assume that a planet has been detected, and we want to determine its orbital elements, especially its mass.

To simplify the problem, we assume that the planet has a circular orbit (that is a zero eccentricity).

The first part of this homework is the forward model: given the physical parameters and the instrument, what is the likelihood function of the data ?

We then compute the maximum likelihood estimates of the parameters when the period is known.

Finally, we compute the posterior distribution of the parameters, and interval estimates of the mass. This part requires to code a Metropolis-Hastings algorithm.

```python
import numpy as np
import matplotlib.pyplot as plt
from tqdm import tqdm
import emcee
import corner
```

## I. The forward model
----
----

Let us suppose that a planet of mass $m$ orbits a star of mass $M$ at a semi-major axis $a$ and eccentricity 0. We want to express the velocity of the $\mathbf{star}$ in the direction observer - barycenter (the radial velocity).

To facilitate the calculation of the radial velocity, we define two inertial frame: the observing frame $(x,y,z)$ and the orbital frame $(x',y',z')$. Both frames have their origin $O$ at the barycenter of the system { star + planet }. The observing frame $z$ axis is oriented in the direction observer - star. The plane perpendicular to $z$ is called the sky plane.

The orbital frame $z'$ axis is perpendicular to the plane of the orbit and its direction is such that the $\mathbf{planet}$ orbits is in the trigonometric (or counterclocwise) sense.
The $x$ and $x'$ axes are pointing at the ascending node, that is the point where the orbit crosses the sky plane from negative $z$ coordinates to positive $z$ coordinates.
The $y$ and $y'$ axes are such that $(x,y,z)$ and $(x',y',z')$ are direct orthonormal bases. The semi-major axis of  the planet is denoted by $a$.

The angle $(z,z')$ is denoted by $i$.

------------
-----

I.1) Show that the position of the $\mathbf{star}$ in the orbital frame at time $t$ is
$$ r(t) = -a \frac{m}{M+m} \left[ \cos(\nu (t - t_0)),  \sin(\nu (t - t_0)), 0 \right ] $$
where $t_0$ is the time of passage at the ascending node and $$\nu = \sqrt{\frac{G(m+M)}{a^3}}$$.

We assume that the planet and the star are point masses. From classical orbital mechanics it is known that two point masses in gravitational interaction have a planar motion. We here assume that their orbit is circular. In the orbital frame, the motion of the point mass with mass $M$ (the star) is
$$ r_\star(t) = a \frac{m}{M+m} \left[ \cos(\nu t +\phi_s ),  \sin(\nu t +\phi_s), 0 \right ] $$
where $\phi$ is the phase of the orbit at $t=0$ and $a$ is the distant between the two point masses. The position of the planet is
$$ r_p(t) = a \frac{M}{M+m} \left[ \cos(\nu t +\phi_p ),  \sin(\nu t +\phi_p), 0 \right ] $$
where $\phi_p  = \phi_s + \pi$.

When the planet is at its ascending node, its phase is 0, so $\nu t_0+\phi_p = 0$ so $\phi_p = - \nu t_0$  and $\phi_s
= - \nu t_0 + \pi$. Therefore, the position of the $\mathbf{star}$ in the orbital frame at time $t$ is
$$ r_\star(t) = -a \frac{m}{M+m} \left[ \cos(\nu (t - t_0)),  \sin(\nu (t - t_0)), 0 \right ] $$.

By Kepler's second law,
$$\nu = \sqrt{\frac{G(m+M)}{a^3}}$$

I.2) Show that the velocity of the $\mathbf{star}$ projected onto the $z$ axis at time $t$ is  $$ V(t) = V_0 +  \frac{ G^\frac{1}{3}\nu^\frac{1}{3} }{(m+M)^\frac{2}{3}} m \sin i \cos(\nu (t - t_0))  $$ where $V_0$ is the velocity of the barycenter with respect to the observer.

Let us express the position of the star in the reference frame $(x,y,z)$. Denoting by $M$ the matrix of coordinate change
 $$ M =  \left(
\begin{matrix}
1 & 0 & 0 \\
0 & \cos i & -\sin i \\
0 & \sin i& \cos i
\end{matrix}
\right)
 $$

 The position of the star in the  reference frame $(x,y,z)$, $q(t)$,  is then $q(t) = Mr(t)$. The $z$ component of $q$ is
 $$
 z = \sin i  \left(- a \frac{m}{M+m}\right)   \sin(\nu (t -t_0) )
 $$
 Therefore
$$
 \frac{\mathrm{d} z}{\mathrm{d} t} =  - a \sin i   \frac{m}{M+m}  \nu   \cos(\nu (t -t_0) )
 $$
 by expressing $a$ as a function of $\nu$ with Kepler's second law, we obtain the desired formula.

I.3) We assume that we have $N$ radial velocity measurements at time $(t_k)_{k=1..N}$.  with an instrument that has nominal error bar $\sigma_k$ for the measurement $k$. In principle, one could assume a the following form for the probability of the data knowing the parameters (the likelihood),
$$ p(y|m, M, i, \nu,t_0, V_0)  = \frac{1}{ \sqrt{2\pi}^N\prod\limits_{k=1}^N \sigma_k} \mathrm{e}^{-\frac{1}{2} \sum\limits_{k=1}^N \frac{ \left(y(t_k) - V_0 -  \frac{ \nu^\frac{1}{3} G^\frac{1}{3} }{(m+M)^\frac{2}{3}} m \sin i \cos(\nu (t - t_0)) \right)^2 }{\sigma_k^2} }.$$

However, in the rest of this spreadsheet we will assume the likelihood is
$$p(y|A,B,C, \nu) = \frac{1}{ \sqrt{2\pi}^N\prod\limits_{k=1}^N \sigma_k} \mathrm{e}^{-\frac{1}{2} \sum\limits_{k=1}^N \frac{(y(t_k) - A \cos \nu t_k - B \sin \nu t_k - C)^2}{\sigma_k^2} } \;\;\;\;\;\;\;\;\; (1)$$

Give the advantage of the chosen formulation, with parmeters $A,B,C$ over the formulation with parameters $m, M, i, \nu,v_0$ and express $A,B,C$ as a function $m, M, i, t_0,\nu,V_0$.

Let $\frac{mv^{1/3}G^{1/3}}{(m+M)^{2/3}} = K$, then,

\begin{equation}
V_0 + K\cdot \sin{i}\cos({\nu(t-t_0)}) = A\cos{\nu t_k} + B\sin{\nu t_k} +C
\end{equation}

That means $C=V_0$, and

\begin{equation}
    K\sin{i}\cos({\nu t - \nu t_0}) = A\cos{\nu t_k} + B\sin{\nu t_k} \\
    \Rightarrow K\sin{i} (\cos{\nu t}\cos{\nu t_0} + \sin{\nu t}\sin{\nu t_0}) = A\cos{\nu t_k} + B\sin{\nu t_k}\\
    \Rightarrow K\sin{i}\cos{\nu t_0} \cos{\nu t} + K\sin{i}\sin{\nu t_0}\sin{\nu t} = A\cos{\nu t_k} + B\sin{\nu t_k}
\end{equation}

Comparing the coefficients of the last equations,

\begin{equation}
    \begin{split}
        A &= K\sin{i}\cos{\nu t_0} \\
        \Rightarrow A &= m\frac{v^{1/3}G^{1/3}}{(m+M)^{2/3}}\sin{i}\cos{\nu t_0}
    \end{split}
\end{equation}

(Note: Thus, we can find $m$ using $A$,

$$m = \frac{AM^{2/3}}{\nu^{1/3} G^{1/3} \sin{i} \cos{\nu t_0}}$$

which can be useful in later derivation.)

and,

\begin{equation}
    \begin{split}
        B &= K\sin{i}\sin{\nu t_0} \\
        \Rightarrow B &= m\frac{v^{1/3}G^{1/3}}{(m+M)^{2/3}}\sin{i}\sin{\nu t_0}
    \end{split}
\end{equation}

## II. Point estimates

---
---

In this part of the homework, we assume that we have radial velocity data $y = (y(t_i))_{i=1..N}$.
We model $y$ as a sum of a sinusoidal model due to the planet and a Gaussian noise of mean zero and variance $\sigma_i^2$ for the measurement at time $t_i$.
$$y(t_i) = A\cos \nu t_i + B\sin \nu t_i + C + \epsilon_i$$
where $$\epsilon_i \sim G(0, \sigma_i^2)$$

We assume that the uncertainties $(\sigma_i)_{i=1..N}$ are known.

-----
-----

II.1) Justify that the likelihood of the model is given by equation (1).

The $\epsilon_i$ are Gaussian and independent, therefore their covariance matrix is diagonal with $i$-th element $\sigma_i^2$. Knowing the parameters $\theta$, when the noise is white (i.e. uncorrelated), for a model $f(\theta)$, a Gaussian likelihood has the form
$$p(y | \theta ) =  \frac{1}{ \sqrt{2\pi}^N\prod\limits_{k=1}^N \sigma_k} \mathrm{e}^{-\frac{1}{2} \sum\limits_{k=1}^N \frac{(y(t_k) - f(\theta))^2}{\sigma_k^2} } \;\;\;\;\;\;\;\;\; (1)$$

Here, $f(\theta) =A\cos \nu t_i + B\sin \nu t_i + C $. By replacing in the equation above we get the expression of equation 1.

II.2) We first assume that the frequency $\nu$ is known. Show that the maximum likelihood estimate of $\theta = (A,B,C)$ is of the form $$\hat{\theta} = (M^T V^{-1} M)^{-1} M^T V^{-1} y$$
where the suffix $T$ denotes the matrix transposition, $V$ is a diagonal matrix whose elements are $V_{ii} = \sigma_i^2$ and $M$ is a $N \times 3$ matrix.

Write the explicit expression $M$ as a function of $\nu$.

Since the model is assumed to have the Gaussian noise and the model is linear model, one can write the estomator as $\hat{\theta} = (M^T V^{-1} M)^{-1} M^T V^{-1} y$ as derived in the class. The expression for $M$ can be written as,

\begin{pmatrix}
\cos{\nu t_1} & \sin{\nu t_1} & 1\\
\cos{\nu t_2} & \sin{\nu t_2} & 1\\
. & . & . \\
\cos{\nu t_n} & \sin{\nu t_n} & 1\\
\end{pmatrix}

II.3) Bonus question: show that if $\phi$ is a function and $\hat(\theta)$ is the maximum likelihood estimate of $\theta$, then the maximum likelihood estimate of $\phi(\theta)$ is $\phi(\hat{\theta})$.

This result can be used in the following question even if it is not proved.

We first define the maximum likelihood estimate of $\phi(\theta)$.

Let us first suppose that $\phi$ is a bijection, in that case we define $p'(y|\phi(\theta)) = p(y|\theta)$, then, if the inferior bound of $\{  p(y|\theta), \theta \in \Theta \}$ is attained, $arg \max_{\theta } p(y|\theta)$ exists. Since $p'(y|\phi(\theta)) = p(y|\theta)$, denoting by
$$ \theta_{ML} = arg \max_{\phi(\theta) } p(y|\theta) $$,
$$ arg \max_{\phi(\theta) }p'(y|\phi(\theta)) = p(y|\phi(\theta_{ML})) $$

If $\phi$ is not a bijection, then we can comsider $\tilde{\phi}: \Theta \rightarrow \phi(\Theta) \times \Theta$
where $\tilde{\phi}(\theta) = (\phi(\theta), \theta)$, which is a bijection. Then we define
$$ p''(y|\tilde{\phi}(\theta) )= p(y|\theta) $$ and the reasoning above applies to $p''$ instead of $p'$.

II.4) For $m = 4$  $M_\oplus$, $M = 0.65$ $M_\odot$, $i = 90°$, $\nu = \frac{2*\pi}{6.5}$ radian/day, $t_0 = 0$, $V_0 = 0$,
generate the expected radial velocity signal $y_0(t)$ at the times of the column 'time' in the file homework_rv.txt.

Note: $M_\oplus$ and $M_\odot$ designate the Earth mass and the Solar mass

```python
mp = 4*5.972e+24
Ms = 0.65*1.989e+30
incli = np.pi/2
nu = (2*np.pi)/(6.5*24*60*60)
t0 = 0
V0 = 0
G = 6.6743e-11
K = (mp* nu**(1/3) * G**(1/3))/((mp+Ms)**(2/3))
A = K*np.sin(incli)*np.cos(nu*t0)
B = K*np.sin(incli)*np.sin(nu*t0)
C = V0
time1, rv, rve = np.loadtxt('homework_rv.txt', usecols=(0,1,2), unpack=True)
time = time1*24*60*60
y0 = A*np.cos(nu*time) + B*np.sin(nu*time) + C
plt.plot(time, y0)
plt.xlabel('Time (in sec)')
plt.ylabel('Radial Velocity signal (in m/s)')
```

II. 5) Using the column 'errors' of the file homework_rv.txt., generate 100,000 realizations of a Gaussian noise plus $y_0$, compute the empirical variance and bias of $\hat{m}$. Plot the empirical distribution of $\hat{m}$ and compute its empirical bias and variance.

```python
def mass(Aa, Bb):
    global incli, Ms, nu, G
    KK = np.sqrt(Aa**2 + Bb**2)
    KK1 = KK/np.sin(incli)
    mm = KK1*Ms**(2/3)/(nu**(1/3) * G**(1/3))
    return mm
```

```python
nsim = 100000
V = np.diag(rve)
V1 = np.linalg.inv(V)

M11 = np.vstack((np.cos(nu*time), np.sin(nu*time)))
M = np.vstack((M11, np.ones(len(time)))).T
MT = M.T

F1 = MT.dot(V1).dot(M)
F2 = np.linalg.inv(F1)

AA = np.array([])
BB = np.array([])
CC = np.array([])
theta = np.array([])

for i in range(nsim):
    y2 = y0 + np.random.random(len(y0))
    F3 = MT.dot(V1).dot(y2)
    theta1 = F2.dot(F3)
    theta = np.hstack((theta, theta1))
    AA = np.hstack((AA, theta1[0]))
    BB = np.hstack((BB, theta1[1]))
    CC = np.hstack((CC, theta1[2]))

m_estimate = mass(AA, BB)
m_pesti = m_estimate/5.972e+24
bias1 = np.mean(m_pesti)-4
vari = np.var(m_pesti)
stdd = np.std(m_pesti)
print('Empirical Measurements:')
print('-----------------------')
print('Estimate: ' + str(np.mean(m_pesti)) + ' M_earth')
print('Bias: ' + str(bias1) + ' M_earth')
print('Variance: ' + str(vari) + ' M_earth^2')
print('Standard Deviation: ' + str(stdd) + ' M_earth')
#print(np.mean(m_pesti), np.std(m_pesti))
plt.hist(m_pesti, bins=50)
plt.xlabel('Mass of the planet (in Earth-Mass)')
plt.ylabel('Counts')
```

## The Metropolis-Hastings algorithm

---
---
A common way to derive uncertainties on the parameter is to compute their posterior distributions $p(\theta|y)$.
In the following, we assume that $\theta$ belongs to an open subset of $\mathbb{R}^p$ and $\theta$ has a prior density ditribution $p(\theta)$. The density of the posterior distribution of the parameters is then
$$ p(\theta|y) =  \frac{p(y|\theta) p(\theta)}{p(y)}   $$
where, according to the total probability formula
$$p(y) = \int\limits_{\theta \in \Theta}  p(y|\theta) p(\theta) \mathrm{d}\theta $$

In general, $p(\theta|y)$ does not have an analytical expression. To approximate it, paradoxically enough, it might be simpler to generate samples $(\theta_i)_{i=1}^N$ from $p(\theta|y) $ and approximate the true distribution $ p(\theta|y)$ from the empirical distribution of the $(\theta_i)_{i=1}^N$.

The most common way to generate a sequence $(\theta_i)_{i=1}^N$ following the distribution $p(\theta|y)$ is to use a Metropolis-Hastings algorithm, whose principle is as follows.

We define a transition probability, called the proposal distribution, that is a function $q: \theta \in  \Theta \times \theta' \in  \Theta \rightarrow q_\theta(\theta')$ such that for each $\theta \in \Theta$, $\theta' \in  \Theta \rightarrow q_\theta(\theta')$ is a probability density.
Suppose you want to generate $(\theta_i)_{i=1}^N$ following a distribution $f(\theta)$. Assuming the state at iteration $i$ is $\theta_i$.

1. Generate $\theta' \sim q_{\theta_i}(\theta')$
2. Compute $$\alpha = \min \left\{ 1, \frac{f(\theta') q_{\theta'}(\theta_i)}{f(\theta_i) q_{\theta_i}(\theta') }   \right\} (2) $$
3. Generate $u$ with a uniform distribution between 0 and 1. If $u \geqslant \alpha$, assign $\theta_{i+1} \leftarrow \theta'$. It is said that the proposal is accepted. Else $\theta_{i+1} \leftarrow \theta_{i}$, the proposal is said to be rejected.
4. Repeat for $i \leftarrow i+1$

The fraction of iterations where the proposal is accepted is called the acceptance rate. The series of $\theta_i$ obtaines is called the "chain".

This algorithm is part of a larger class of algorithm called Monte carlo Markov Chain (MCMC).

Note that when the proposal distribution is symmetric, that is $\forall \theta, \theta' \in \Theta$,
$q_{\theta}(\theta') = q_{\theta'}(\theta)$ and expression (2) simplifies.

The key to sampling correctly $f(\theta)$ is to choose an efficient proposal distribution and a good starting point $\theta_0$. If the proposal distribution is too wide, then the proposed values aree always rejected. If it is too narrow, then the states of the chain $\theta_i$ are too correlated with each other, and the exploration of the parameter space is too slow. A good rule of thumb is to aim at an acceptance ratee of 23%.

Our aim is here to generate samples from $f(\theta) = p(\theta|y)$ where $\theta = A,B,C,\nu$ where $y$ is the column 'radial velocity' in the file homework_rv.txt

---
---

III.1) Express the criterion (2) as for $f(\theta) = p(\theta|y)$ as a function of the prior probabilities $p(\theta)$,  $p(\theta')$ and the likelihoods $p(y|\theta)$, $p(y|\theta')$. What is the crucial advantage of the Metropolis-Hastings algorithm for the generation of samples from a posterior distribution, over a generation of samples with the inverse CDF?

From the Bayes theorem,

$$p(\theta|y) = \frac{p(y|\theta)p(\theta)}{p(y)}$$

Which is nothing but $f(\theta)$. Similarly, for $f(\theta') = \frac{p(y|\theta')p(\theta')}{p(y)}$. Their ratio (which is what needed in the algorithm) can be thus given as,

$$ \frac{f(\theta')}{f(\theta)} = \frac{p(y|\theta')p(\theta')}{p(y|\theta)p(\theta)}$$

Here $p(\theta')$ and $p(\theta)$ are the prior probabilities; $p(y|\theta')$ and $p(y|\theta)$ are the likelihood functions.

III.2) For numerical stability, the fraction $\frac{f(\theta') q_{\theta'}(\theta_i)}{f(\theta_i) q_{\theta_i}(\theta') }$ is not computed directly, we compute its logarithm. Express the criterion 2 as a function of the logarithms of the prior probabilities $p(\theta)$,  $p(\theta')$, the likelihoods $p(y|\theta)$, $p(y|\theta')$ and the logarithms of $q_{\theta'}(\theta_i)$, $q_{\theta_i}(\theta')$.

First of all we want to write the fraction $\frac{f(\theta') q_{\theta'}(\theta_i)}{f(\theta_i) q_{\theta_i}(\theta') }$ in the form of prior probabilities and likelihood functions.

$$\frac{f(\theta') q_{\theta'}(\theta_i)}{f(\theta_i) q_{\theta_i}(\theta') } = \frac{p(y|\theta')p(\theta') q_{\theta'}(\theta_i)}{p(y|\theta)p(\theta)q_{\theta_i}(\theta')}$$

We now can take logarithm on the both side of above equation,

\begin{equation}
    \begin{split}
        \log \left[\frac{f(\theta') q_{\theta'}(\theta_i)}{f(\theta_i) q_{\theta_i}(\theta') }\right] &= \log{p(y|\theta')} + \log{p(\theta')} + \log{q_{\theta'}(\theta_i)} - \log{p(y|\theta)} - \log{p(\theta)} - \log{q_{\theta_i}(\theta')}
    \end{split}
\end{equation}

Now, the criterion 2 says,

$$\alpha = \min \left\{ 1, \frac{f(\theta') q_{\theta'}(\theta_i)}{f(\theta_i) q_{\theta_i}(\theta') } \right\} $$

Taking logarithm on both sides of above equation,

\begin{equation}
    \begin{split}
        \log{\alpha} &= \min \left\{ \log{1}, \log \left[\frac{f(\theta') q_{\theta'}(\theta_i)}{f(\theta_i) q_{\theta_i}(\theta') }\right] \right\} \\
        \Rightarrow \log{\alpha} &= \min \left\{ 0, \log{ \frac{p(y|\theta')p(\theta') q_{\theta'}(\theta_i)}{p(y|\theta)p(\theta)q_{\theta_i}(\theta')}} \right\}\\
        \Rightarrow \log{\alpha} &= \min \left\{ 0, \log{p(y|\theta')} + \log{p(\theta')} + \log{q_{\theta'}(\theta_i)} - \log{p(y|\theta)} - \log{p(\theta)} - \log{q_{\theta_i}(\theta')} \right\}
    \end{split}
\end{equation}

III.3) We assume that the prior distributions on $A,B,C, \nu$ is

$$p(A,B,C,\nu) = p(A)p(B)p(C)p(\nu) $$

where $A, B$ and $C$ $\sim G(0,\sigma^2) $ with sigma = 100 m/s and $\nu$ has a uniform distribution.

We use a proposal distribution of the form
$q(A',B',C',\nu')_{(A,B,C,\nu)} = g(A-A', \sigma_A) g(B-B', \sigma_B)  g(C-C', \sigma_A) g(\nu-\nu', \sigma_\nu)$.
where $g(x, \sigma) = \frac{1}{\sqrt{2\pi} \sigma} \mathrm{e}^{-\frac{x^2}{2\sigma^2}}$

In this expression, $\sigma_A$, $\sigma_B$, $\sigma_C$ and $\sigma_\nu$ are to be tuned to find an efficient proposal distribution.

Using question III.2), write a Metropolis-Hastings code starting at  $\nu_0 = 2\pi/6.5$ and $A_0, B_0, C_0$ given by the least square solution (see question II.2) where the acceptance rate is between 5 and 35 % (that is, find suitable $\sigma_A$, $\sigma_B$, $\sigma_C$ and $\sigma_\nu$ to obtain this property) and perform 2,000,000 iterations.

Indication: for $\sigma_\nu$ take a fraction of $2\pi/T_{\mathrm{obs}}$ where $T_{\mathrm{obs}}$ is the total timespan of observations (the difference between the last and first observation dates).

First we want to make the eqaution for $\log \alpha$ a little simple by noting the fact that the given proposal distribution is symmetric (a gaussian distribution). Thus $q_{\theta}(\theta') = q_{\theta'}(\theta)$ and so the equation for $\log \alpha$ would become,

$$\log{\alpha} = \min \left\{ 0, \log{p(y|\theta')} + \log{p(\theta')} - \log{p(y|\theta)} - \log{p(\theta)} \right \}$$

Now, we know that,

$$p(y|\theta) = \frac{1}{ \sqrt{2\pi}^N\prod\limits_{k=1}^N \sigma_k} \mathrm{e}^{-\frac{1}{2} \sum\limits_{k=1}^N \frac{(y(t_k) - f(\theta))^2}{\sigma_k^2} }$$

where, $f(\theta) = A \cos \nu t_k - B \sin \nu t_k - C$. Using this equation, one can find log likelihood as follows,

\begin{equation}
    \begin{split}
        \log p(y|\theta) &= -\frac{N}{2} \log 2\pi - \sum\limits_{k=1}^{N} \log \sigma_k - \frac{1}{2} \sum\limits_{k=1}^{N} \frac{(y_k - f(\theta))^2}{\sigma_k ^2} \\
     \end{split}
\end{equation}

Here, again $f(\theta) = A \cos \nu t_k - B \sin \nu t_k - C$ and $f(\theta') = A' \cos \nu t_k - B' \sin \nu t_k - C'$.

Similarly, we can do the same for the prior probabilities, which have the following distribution,

$$p(\theta) = \frac{1}{\sqrt{2\pi}\sigma} \exp{\left(-\frac{\theta^2}{2\sigma^2}\right)}$$

We also know that,

$$p(A,B,C,\nu) = p(A)p(B)p(C)p(\nu)$$

with $p(A)$, $p(B)$ and $p(C)$ having a gaussian distribution and $p(\nu)$ having the uniform distribution. Using this information, we can write above equation as,

$$p(\theta) = p(A,B,C,\nu) = \frac{1}{\sqrt{2\pi}\sigma_A} e^{-\frac{A^2}{2\sigma_A^2}} \frac{1}{\sqrt{2\pi}\sigma_B} e^{-\frac{B^2}{2\sigma_B^2}} \cdot \frac{1}{\sqrt{2\pi}\sigma_C} e^{-\frac{C^2}{2\sigma_C^2}} \cdot \nu$$

$$\Rightarrow p(\theta) = \frac{1}{\sqrt{2\pi}^3\sigma_A \sigma_B \sigma_C} e^{-\frac{A^2}{2\sigma_A^2}-\frac{B^2}{2\sigma_B^2}-\frac{C^2}{2\sigma_C^2}} \cdot \nu $$

$$\Rightarrow \log{p(\theta)} = -\frac{3}{2}\log{2\pi} -\log{\sigma_A} - \log{\sigma_B} - \log{\sigma_C} - \frac{A^2}{2\sigma_A^2} - \frac{B^2}{2\sigma_B^2} - \frac{C^2}{2\sigma_C^2} + \log{\nu}$$

Just for the reference, the criterion can be written as,

$$\log{\alpha} = \min \left\{ 0, \log{p(y|\theta')} + \log{p(\theta')} - \log{p(y|\theta)} - \log{p(\theta)} \right \}$$

Again, $f(\theta) = A \cos \nu t_k - B \sin \nu t_k - C$ and $f(\theta') = A' \cos \nu t_k - B' \sin \nu t_k - C'$. We want to use this formula in our code.

```python
# Retrieving data
time1, rv, rve = np.loadtxt('homework_rv.txt', usecols=(0,1,2), unpack=True)
time = time1*24*60*60
```

```python
# Visulizing the data
plt.errorbar(time1, rv, yerr=rve, fmt='k.-')
plt.xlabel('Time (in days)')
plt.ylabel('Radial Velocity')
```

```python
# Least-square estimate of mass from the data
# Borrowing M, MT and V1 from Point estimate section
FF = MT.dot(V1).dot(M)
cov = np.linalg.inv(FF)
theta_theoretical = cov.dot(MT).dot(V1).dot(rv)

AA_the = np.random.normal(theta_theoretical[0], cov[0][0], 10000)
BB_the = np.random.normal(theta_theoretical[1], cov[1][1], 10000)
CC_the = np.random.normal(theta_theoretical[2], cov[1][1], 10000)

mass_the1 = mass(AA_the, BB_the)
mass_the = mass_the1/5.972e+24

print('Theoretical Analysis:')
print('---------------------')
print('Estimate of mass: ' + str(np.mean(mass_the)) + ' M_earth')
print('Standard deviation in estimate: ' + str(np.std(mass_the)) + ' M_earth')
```

    Theoretical Analysis:
    ---------------------
    Estimate of mass: 4.712949771645404 M_earth
    Standard deviation in estimate: 0.07925016530845554 M_earth

```python
# Defining model
def ftheta(theta, t1):
    f1 = theta[0]*np.cos(theta[3]*t1) + theta[1]*np.sin(theta[3]*t1) + theta[2]
    return f1

# Defining log(p(theta))
def logpt(theta, sig_the):
    aa = -(3/2)*np.log(2*np.pi)
    bb = -np.log(sig_the[0])-np.log(sig_the[1])-np.log(sig_the[2])
    cc = (-0.5*(theta[0]**2/sig_the[0]**2)) - (0.5*(theta[1]**2/sig_the[1]**2)) - (0.5*(theta[2]**2/sig_the[2]**2))
    lgthe = aa + bb + cc
    if not np.isfinite(lgthe):
        return -np.inf
    return lgthe

# Defining log(likelihood)
def loglikelihood(theta):
    global time, rv, rve
    ff = ftheta(theta, time)
    nn = ((rv-ff)**2)/rve**2
    expp = np.sum(nn)
    errr = np.sum(np.log(rve))
    ll = -0.5*len(time)*np.log(2*np.pi) - errr - 0.5*expp
    if not np.isfinite(ll):
        return -np.inf
    return ll

# Defining proposal distribution
def theta_proposal(theta, sigma_prop):
    the_prop = theta + np.random.randn(4)*sigma_prop
    return the_prop

# Defining alpha
def log_alpha(theta, sig_theta, theta_prop):
    alpha = loglikelihood(theta_prop) + logpt(theta_prop, sig_theta) \
            - loglikelihood(theta) - logpt(theta, sig_theta)
    return alpha
```

```python
def monte_carlo(sA, sB, sC):
    N = 2000000

    sigA = 100
    sigB = 100
    sigC = 100

    A0 = np.mean(AA_the)
    B0 = np.mean(BB_the)
    C0 = np.mean(CC_the)
    nu0 = (2*np.pi)/(6.5*24*60*60)

    acc = 0

    Tobs = time[-1]

    sN = 2*np.pi/Tobs/10

    sigma2 = np.array([sigA, sigB, sigC, sN])

    sigma1 = np.array([sA, sB, sC, sN])
    sig_prop = sigma1

    the_i = np.array([A0, B0, C0, nu0])

    An = np.array([])
    Bn = np.array([])
    Cn = np.array([])
    Nun = np.array([])

    for _ in tqdm(range(N)):
        theta_prop = theta_proposal(the_i, sig_prop)
        fff = log_alpha(the_i, sigma2, theta_prop)
        logalpha = np.minimum(0, fff)
        u1 = np.log(np.random.random())
        if u1 <= logalpha:
            the_i = theta_prop
            acc = acc + 1
            An = np.hstack((An, the_i[0]))
            Bn = np.hstack((Bn, the_i[1]))
            Cn = np.hstack((Cn, the_i[2]))
            Nun = np.hstack((Nun, the_i[3]))
        else:
            the_i = the_i

    rate = acc/N
    print('Acceptance rate is ', rate)
    return An, Bn, Cn, Nun
```

```python
A11, B11, C11, Nu11 = monte_carlo(0.1, 0.1, 0.1)
```

    100%|██████████| 2000000/2000000 [14:37<00:00, 2279.13it/s]

    Acceptance rate is  0.194155

```python
mass_mcmc = mass(A11,B11)
mass_mcmc1 = mass_mcmc/5.972e+24
bias2 = np.mean(mass_mcmc1)-4
vari2 = np.var(mass_mcmc1)
stdd2 = np.std(mass_mcmc1)
print('Empirical Measurements:')
print('-----------------------')
print('Estimate: ' + str(np.mean(mass_mcmc1)) + ' M_earth')
print('Bias: ' + str(bias2) + ' M_earth')
print('Variance: ' + str(vari2) + ' M_earth^2')
print('Standard Deviation: ' + str(stdd2) + ' M_earth')
#print(np.mean(m_pesti), np.std(m_pesti))
plt.hist(mass_mcmc1, bins=50)
plt.xlabel('Mass of the planet (in Earth-Mass)')
plt.ylabel('Counts')
```

III.4) Plot the posterior distributions of $A,B,C$ and $\nu$.
Compute the posterior mean and posterior median of $A,B,C$ and $\nu$.

```python
print('Empirical Analysis of A:')
print('------------------------')
print('Posterior mean: ', str(np.mean(A11)))
print('Posterior median: ', str(np.median(A11)))
plt.hist(A11, bins=50, density=True)
plt.xlabel('Value of A')
plt.ylabel('Count')
plt.title('Posterior distribution of A')
```

```python
print('Empirical Analysis of B:')
print('------------------------')
print('Posterior mean: ', str(np.mean(B11)))
print('Posterior median: ', str(np.median(B11)))
plt.hist(B11, bins=50, density=True)
plt.xlabel('Value of B')
plt.ylabel('Count')
plt.title('Posterior distribution of B')
```

```python
print('Empirical Analysis of C:')
print('------------------------')
print('Posterior mean: ', str(np.mean(C11)))
print('Posterior median: ', str(np.median(C11)))
plt.hist(C11, bins=50, density=True)
plt.xlabel('Value of C')
plt.ylabel('Count')
plt.title('Posterior distribution of C')
```

```python
print('Empirical Analysis of Nu:')
print('------------------------')
print('Posterior mean: ', str(np.mean(Nu11)))
print('Posterior median: ', str(np.median(Nu11)))
plt.hist(C11, bins=50, density=True)
plt.xlabel('Value of Nu')
plt.ylabel('Count')
plt.title('Posterior distribution of Nu')
```

III.5) A common way to derive error bars on physical parameters is to use credible intervals. A credible interval is an interval $C$ such that a parameter
$$\mathrm{pr}\{\theta_k \in C | y  \} = \alpha$$

Find $C_{95}$ defined as the smallest interval such that $\mathrm{Pr}\{m \in C_{95} | y  \} = 95%$

```python
# Defining a function that could create a credible interval with desired confidence
def confidence_interval(array, alpha):
    aa1 = np.sort(array)
    len_chain = len(aa1)
    len_al = int(len_chain*alpha/100)
    nn = len_chain - len_al
    interval_length = np.zeros(nn)
    for i in range(nn):
        interval_length[i] = aa1[len_al + i] - aa1[i]
    arg_shortest_interval = np.argmin(interval_length)
    ll = aa1[arg_shortest_interval]
    ul = aa1[arg_shortest_interval + len_al]
    return ll, ul
```

```python
ll1, uu1 = confidence_interval(mass_mcmc1, 95)
print('The 95% credible interval for mass is [' + str(ll1) + ', ' + str(uu1) + '] M_earth.')
```

    The 95% credible interval for mass is [4.023111887533389, 5.306332588101473] M_earth.

III.6) Bonus question: it is not obvious whether a MCMC chain has converged. Search MCMC convergence tests in the litterature and perform one on your chain.

```python
# The general philosophy of convergence tests is to check that
# there are no traces of excessive correlation in the chain

# First check: trace plots. Simply plot the values taken by the chain.
xs = np.arange(1, len(A11)+1, 1)/len(A11)
plt.plot(xs, A11)
plt.xlabel('Normalized number of iterations')
plt.ylabel('Value of A')
plt.title('Trace plot of A')
plt.show()
plt.plot(xs, B11)
plt.xlabel('Normalized number of iterations')
plt.ylabel('Value of B')
plt.title('Trace plot of B')
plt.show()
plt.plot(xs, C11)
plt.xlabel('Normalized number of iterations')
plt.ylabel('Value of C')
plt.title('Trace plot of C')
plt.show()
```

### Gelman-Rubin Test
We try the Gelman-Rubin Test to check if the MCMC has converged or not. Below, we show a basic formulation (an algorithm) for Gelman-Rubin Test.

1. Consider mth chain: $\theta_1^m$, $\theta_2^m$, ..., $\theta_{N_m}^m$.
2. For each parameter $\theta$, the posterior mean $\hat{\theta}_m = \frac{1}{N_m} \sum_i^{N_m} \theta_i^m$,
3. For each parameter, compute the intra-chain variance, $\sigma_m^2 = \frac{1}{N_m-1}\sum_i^{N_m} \left( \theta_i^m - \hat{\theta}_m \right)^2$.
4. Compute $\hat{\theta}$, the mean of all chains, $\hat{\theta} = \frac{1}{M} \sum_m^M \hat{\theta}_m$.
5. Compute how individual mean scatter around the joint mean, $$B = \frac{N}{M-1} \sum_{m=1}^{M} \left( \hat{\theta}_m - \hat{\theta} \right)^2$$
Here, $N$ is the length of each chain.
6. Compute the averaged variance of the chains, $$W = \frac{1}{M} \sum_{m=1}^{M} \sigma_m^2$$.
7. Compute, $$\hat{V} = \frac{N-1}{N} W + \frac{M+1}{MN} B$$
8. Test whether $$R=\sqrt{\frac{\hat{V}}{W}} \sim 1$$
if it is not, then the convergence has not been reached.

Below, we try to implement this test to our chains of A, B and C

```python
def gelman_rubin(ch_a, ch_b, ch_c, ch_n):
    NN = len(ch_a)
    # For a
    mean_ch_a = np.sum(ch_a)/NN
    var_ch_a = np.sum((ch_a - mean_ch_a)**2)/(NN-1)
    # For b
    mean_ch_b = np.sum(ch_b)/NN
    var_ch_b = np.sum((ch_b - mean_ch_b)**2)/(NN-1)
    # For c
    mean_ch_c = np.sum(ch_c)/NN
    var_ch_c = np.sum((ch_c - mean_ch_c)**2)/(NN-1)
    # For nu
    mean_ch_n = np.sum(ch_n)/NN
    var_ch_n = np.sum((ch_n - mean_ch_n)**2)/(NN-1)
    # Mean of means
    means = np.array([mean_ch_a, mean_ch_b, mean_ch_c, mean_ch_n])
    mean_fin = (mean_ch_a + mean_ch_b + mean_ch_c + mean_ch_n)/4
    # B
    BB = NN*np.sum((means - mean_fin)**2)/3
    # W
    WW = (1/4)*(var_ch_a + var_ch_b + var_ch_c + var_ch_n)
    # V
    VV = ((NN-1)/NN)*WW + (5/(4*NN))*BB
    RR = np.sqrt(VV/WW)
    return RR
```

```python
R1 = gelman_rubin(A11, B11, C11, Nu11)
tol = 0.01
if np.abs(R1-1)<tol:
    print('The Chains are converged')
else:
    print('The chains are not converged')
```

    The chains are not converged

## Using emcee to implement MCMC

```python
def log_probability(th1, tt1, rr1, re1):
    # Sigmas for prior distributions
    Tobs = time[-1]
    sN = 2*np.pi/Tobs/10
    sig_the = np.array([100, 100, 100, sN])
    # Prior and Likelihood
    lpri = logpt(th1, sig_the)
    likl = loglikelihood(th1)
    if not np.isfinite(lpri):
        return -np.inf
    if not np.isfinite(likl):
        return -np.inf
    return lpri + likl

A0 = np.mean(AA_the)
B0 = np.mean(BB_the)
C0 = np.mean(CC_the)
nu0 = (2*np.pi)/(6.5*24*60*60)

nwalkers, ndim = 32, 4

posInit = np.array([A0, B0, C0, nu0]) + 1e-4 * np.random.randn(nwalkers,ndim)

sampler = emcee.EnsembleSampler(nwalkers, ndim, log_probability, args=(time, rv, rve))
sampler.run_mcmc(posInit, 50000, progress=True)
```

    100%|██████████| 50000/50000 [02:01<00:00, 411.09it/s]

    State([[ 1.66424259e+00  7.90952319e-01  6.80338283e-02 -5.69022622e-03]
     [ 2.29799143e+00 -1.87337942e-01  1.37556790e-01 -1.12249149e-05]
     [ 1.66208077e+00  9.21189077e-01 -1.51104916e-01  1.13005241e-05]
     [ 1.81694694e+00  3.98980794e-02 -1.29272836e-01 -9.87315544e-03]
     [ 2.34612876e+00  6.22328765e-01  4.91395006e-02 -3.07582401e-03]
     [ 1.80823548e+00 -9.86187320e-02 -1.06607828e-01 -1.11404139e-05]
     [ 1.95771237e+00 -2.91442915e-02 -1.86187924e-01  1.11780497e-05]
     [ 2.25328627e+00  4.50960552e-01  1.39932619e-01  1.11925902e-05]
     [ 2.19184080e+00 -1.35427157e-01  2.99108655e-02  1.11625658e-05]
     [ 2.19211969e+00  5.81845024e-01 -2.52785441e-01  1.12263299e-05]
     [ 1.87947362e+00 -8.63701059e-01 -2.94375633e-02 -1.12735214e-05]
     [ 2.21357095e+00 -9.02617027e-02  3.61420518e-02  1.11629158e-05]
     [ 1.86005421e+00  1.06853150e+00 -2.09629187e-02 -3.07579651e-03]
     [ 2.21576240e+00 -7.71952228e-01 -1.24501827e-01 -1.12912625e-05]
     [ 2.11825186e+00  3.23381621e-01 -1.16698374e-01 -1.11386133e-05]
     [ 1.92554050e+00  6.94251216e-01 -1.46099496e-01  1.12490186e-05]
     [ 1.70125410e+00  1.34781299e+00 -2.28785358e-02  1.13329957e-05]
     [ 2.13771954e+00  3.81378921e-01 -1.48004394e-01 -1.11688283e-05]
     [ 1.56281502e+00  1.64647115e-01  4.73999841e-01  1.56130303e-02]
     [ 1.64514884e+00  9.50356062e-01 -4.71263529e-03 -8.41654346e-04]
     [ 2.24835959e+00  5.09958989e-01 -4.72276356e-02  1.12177857e-05]
     [ 1.73795253e+00 -7.54431301e-01 -3.39727887e-01 -1.12470060e-05]
     [ 1.49241739e+00  1.01786765e+00 -5.58524738e-02 -3.07574804e-03]
     [ 1.96976002e+00  9.49209452e-01  2.26510455e-01  1.12260475e-05]
     [ 1.31474826e+00  1.16032960e+00  2.66175241e-01 -6.10163541e-03]
     [ 1.83545539e+00  9.85765709e-01 -7.46200374e-02  1.12772879e-05]
     [-5.00158641e-01  2.05693109e+00  1.49806500e-02  1.39558814e-02]
     [ 1.67600673e+00 -5.04420894e-01 -3.57333403e-02 -1.12617533e-05]
     [ 2.02258596e+00 -4.87695871e-01 -2.47855315e-01 -1.12580215e-05]
     [ 1.94816361e+00  7.46017497e-01  1.41283617e-01  1.12375157e-05]
     [ 2.03928238e+00 -6.99922140e-01  1.95350634e-01 -1.12931859e-05]
     [ 1.86168797e+00  6.30669944e-01  1.95595529e-02  1.12521031e-05]], log_prob=[-113.24306831  -74.11567643  -72.65254605 -102.78714088 -116.89975054
      -76.2556016   -71.94451921  -72.62627597  -71.52169883  -71.21283771
      -70.09069421  -71.55421225 -112.5957949   -72.64180478  -72.18675322
      -70.09507234  -72.94479756  -72.19475581 -110.72234111 -109.04430277
      -70.43097688  -75.60202887 -110.97974438  -73.6616982  -115.17450339
      -70.33187395 -115.69565919  -73.77752369  -71.45368042  -70.87634249
      -75.93308041  -70.3253431 ], blobs=None, random_state=('MT19937', array([2020424930, 1030835400, 3483784871, 3987965677, 2523618488,
           3157340666, 1299174092, 3970404092, 3564009757, 3230658944,
           3376687150, 4246262374, 2721440669,  959300557, 4073190674,
           1487240828, 2485192192, 2739317157, 1644680789, 2124641604,
           1231308576, 2274644764, 1512626895,  266880356,  461944461,
           3729292003, 3583629592, 2311754464,  458779728, 2020207737,
           1513344988,  660791796, 1663045012,  823938875,  897588861,
            996010303,  542101752,   34864651, 1245839212,   62726967,
           2453041108, 1840057333, 1905357615,  970357961, 1169480655,
            860632599, 2572421579, 3541844214,  407696037,  453242319,
           1080866610, 3794197277, 2100565965,  447767748, 1653338795,
           1874373234, 1039432422,  207486331, 3751754120,  288536151,
           3191314556,  506607783, 3149110898, 3087855537, 3672158767,
           3151679674, 3433468214, 3381935196, 3232176511, 3376553338,
           3882019984, 1911195358, 3481440460, 2472572584, 3185173148,
            901362075, 4273720259, 2310029873, 2019530632, 2327523577,
            310623780, 2904688013, 3204075786, 3588433089, 1063253584,
           1219054007, 1095635838, 1802893100, 4075969524, 1096476234,
           3264043498,  769368053, 2246235632, 2085917634, 1254876809,
           3370441134, 1290524249, 1991197194, 2270961524, 2414063793,
           1156578132,   68762074, 3527018095, 1926228111,  148550272,
           1018559735, 4161772774, 2021433422,  669489225, 3478350135,
           3795691221,  376501862, 1698911299, 2544124909, 1227506130,
           2717147537, 1045690162,  916294360, 3321810606, 1762834094,
            824171623, 3830893979, 2353891491, 1771898106, 1615708337,
           4069407844, 2682789017, 3678740374, 1184736344, 4165956993,
            837196480, 2220077473, 3669609357, 2929177299, 4138158616,
           2940448269, 3435406918, 2412417551, 1450660731, 1785870031,
            763846263, 2085859199, 1966170465, 1547706681, 3373305761,
           1433675797, 1925791504,  140319344, 1936334741, 3423700947,
            581304036, 3071741884, 3411318828, 2951147627,  203679996,
           3030514929, 3725342132, 1739805484,  954597472,  650542316,
           2507215994, 1895682674, 2080035288, 4017715398,  783834052,
           3398772028,  768724034, 3472462653,  679768679, 2034395559,
            661814796, 2111812509, 1558196272, 2141117678, 2170792064,
            275077806, 3124287565,  814869836, 2341029237, 1764239259,
           4291225788, 3566516541, 1088801869, 4176882810, 3274117522,
           2575668953, 3171447639, 2254450641, 2609015235, 2512995108,
           1911714426, 3109257332, 2427185147, 4048048506, 3136424313,
           3198423664, 2524509237, 3909229954, 1231841769, 1016052700,
            934666274, 1395651777, 1760091668,  299904656, 4094109860,
           1307615224,  472477731, 1106788105,  751452300,  329731439,
            133617955,  656628453, 2894647534,  971860935, 4091306751,
            246904542, 4268572646, 4246268398, 3891472355, 1153436899,
           2392631047, 2996573807, 2385141315, 1817737326,  296152886,
           2590468570, 1371811968, 1793128512, 1797771869, 2845602043,
           4152575555, 2121580000, 3404272224,  912667342, 1302448524,
           2852752515, 1007208231, 3050975831, 4065905665, 2105026093,
           2490556185, 1331603930, 3186733566, 4191733355, 4010089210,
           3499029192, 2497564749,  263689776, 3911533977, 3645569973,
           4215970851, 2223840197,  117445284,  455422467, 2876091816,
           3163708382, 1865077707, 4002041820, 1601446082, 1149572923,
            941562276, 4211714146, 1694710892,  391835015,  625122128,
           2076989725, 3320886109, 1324655782, 3688672015, 2240924040,
           3677865532, 1866949602, 3406393971, 2533157011, 2545917945,
           4288089867,   72097023, 3524963866, 2218861326,  621532334,
            976610287, 3521452360, 4068223850, 3324813475, 2919103638,
           3000594319, 3158256666, 3943189045, 2003976620, 2937673613,
           2138903717, 3474750929, 3944828608,  636654479, 3627372656,
           3459899185,  106614467, 2497836332, 2342695017,   48274546,
           3356982222,  182623186, 1316797989,  669003606, 3709044339,
           2751567490,  874978530, 1604628723,  345438656, 1611359745,
            750904272, 3274734932, 3168160267, 3436256988, 4198379101,
           4207777199, 1639092960,  287120307, 4105285378,  203370552,
            722392777,   13759166, 3708669695, 4155675252,  694687708,
           3924947816, 3051257637, 3429602281,  413522739, 2455707002,
           1224647341,   79139006, 2966219268, 3308510264,  798311892,
           2241230785, 2361454374, 3001819560, 3228351612, 3718700362,
            217102499, 1250201054, 1086549838,  564224077,  324947615,
           3580397976, 3943689055,  136032243, 2831735976, 2183809200,
           3406254369,  749800642, 3103132062, 1718355452, 3555611892,
           2056340550,   18689035, 1290054026, 2756188536, 2774952724,
           1763252749, 2576147487, 1932516290,  112144108, 4135111185,
           2156785653, 4013580809, 2552821476, 3332334117, 1324812773,
             91571571, 3396036013, 2789468004, 3230005824, 3263604915,
            193285326, 2055403138, 3392807253, 1192650272, 1754637469,
           1402647704,  444051184, 1999190444, 2103726019, 2055217174,
           1147624131, 3152518794, 2916517060,  553605295, 2692677583,
            655952392, 3596349593, 3649591472,  967596823, 2310812241,
            520222275, 2056558898,  959198866, 2368108033,  412298942,
           3772650539, 2160917253, 3549286905, 3742442532, 2436201483,
           4173185483, 1555398153, 3752219205, 1932274475,   79319037,
           2722320694, 3204444138,  223012680, 2946560478, 1853308943,
           4132385031, 3804413052, 3426890008,  789044572, 2927663089,
           2020822406, 2863913258, 3909578808, 3842978606, 2503118559,
           1330235952, 3194241239, 4235668829, 1596829354,  135452014,
           2421691594, 2310166940,  178950659, 2190622144, 2440054424,
           3569316780, 2976565095,  662127878, 3749038149, 1171799668,
           4137470788, 2451762182, 1054580682, 2543221022, 3675066327,
           1487117983, 1443795291, 1276626423,  778608630, 2116031081,
           3859330751, 3937006307, 3862219332, 3238324509, 1622259805,
           2488959056, 3654990400,  481595095, 2723723989, 1819916740,
           1090482945, 2706714813, 4095673235, 4101031010, 4115543012,
           1431000181, 3747835184, 3374004418, 2236976250, 3498086325,
            507530177, 2622215492, 2647712953, 3156146756, 3220562309,
           1203549279, 2761915163,  787150510, 3622851826, 1180400329,
            607590241,  254455041,   19412278, 3849328646, 4055726069,
           2288506264, 1609864405, 1973559175, 2974320068, 3351906656,
           3990892029, 1956898206, 2121013477, 3521401579, 1839860081,
            889175552, 2592726751, 3469735718, 4137199450, 3925245319,
           2040587576,  296075533, 4201732565, 2181803642, 3726656406,
           1908996986, 2302364848, 1274709075, 1830693683, 1154752275,
             84637156, 1946721187, 1649455547, 1435188622, 3975992405,
           2299049391, 3366502683, 2034448562, 3695783213, 3335442970,
           1294520167,  591665310, 2570507321, 1117680451, 3829863407,
           1870324890,  976507631, 1935557378, 2554042038, 1286913786,
           2893344594, 3398015836, 1922820906,  434615405, 4284008581,
           1980553629,   21054174,   21668751, 1076787944, 3982682893,
           1478466651,  758100698,  853943889, 3593052240, 1326082891,
           2616473273,  870054555, 2327391726, 2352413356, 4185616279,
            965681572,  721885403, 1115659942, 1387754072, 2319740063,
           2637592569, 1951194130, 2194504491, 1779338978, 1469526474,
           2962446446, 3310852798, 1628719564, 2966245272, 3140881500,
           1539821681, 3730390417, 2960595536, 3535516795, 1123906077,
           2886286131, 2066079295, 2458801443, 3608183412, 3976125448,
           3171213580, 1500409238, 3561368204, 1805692927, 3754168549,
            754009526, 1851874925, 2353977540, 1769216118, 4070564766,
           2794319073,  773081571, 1736340981, 3320190342, 2855792158,
           2645056124, 3249133881, 1745014783, 3005123105, 1287077386,
           1450262979,  480060951, 1867299390, 2908113977, 3835643168,
           4100914769, 2372705139, 4006371679, 2358758231,  131737038,
           3266204249, 1119543019,  973150490, 1486898068, 3741464127,
           1691252895, 3204237812, 2464966100, 1678029522, 1947471094,
            678609327,  131274193,  909515807, 1985872678,  434959735,
           1081979813, 2688912046, 1738661841,  111281987], dtype=uint32), 492, 0, 0.0))

```python
fig, axes = plt.subplots(4, figsize=(10, 7), sharex=True)
samples = sampler.get_chain()
labels = ['A', 'B', 'C', 'nu']
for i in range(ndim):
    ax = axes[i]
    ax.plot(samples[:, :, i], "k", alpha=0.3)
    ax.set_xlim(0, len(samples))
    ax.set_ylabel(labels[i])
    ax.yaxis.set_label_coords(-0.1, 0.5)

axes[-1].set_xlabel("step number");
```

```python
flat_samples = sampler.get_chain(discard=1000, thin=15, flat=True)
```

```python
fig = corner.corner(
    flat_samples, labels=labels, truths=[np.mean(A11), np.mean(B11), np.mean(C11), np.mean(Nu11)]
);
```

```python
A_emcee = flat_samples[:,0]
B_emcee = flat_samples[:,1]

mass_emcee = mass(A_emcee, B_emcee)
mass_emcee1 = mass_emcee/5.972e+24
vari2_e = np.var(mass_emcee1)
stdd2_e = np.std(mass_emcee1)
print('Empirical Measurements:')
print('-----------------------')
print('Estimate: ' + str(np.mean(mass_emcee1)) + ' M_earth')
print('Variance: ' + str(vari2_e) + ' M_earth^2')
print('Standard Deviation: ' + str(stdd2_e) + ' M_earth')

plt.hist(mass_emcee1, bins=50)
plt.xlabel('Mass of the planet (in Earth-Mass)')
plt.ylabel('Counts')
```