```
%load_ext autoreload
%autoreload 2
```

```
from __future__ import division

import numpy as np
import matplotlib.pyplot as plt

import pydy
import sympy as sym
import sympy.physics.mechanics as me

from scipy.linalg import solve, lstsq
from scipy.integrate import odeint

import time
import functools

me.Vector.simp = False  # to increase computation speed

from sympy.physics.vector import init_vprinting
init_vprinting()

from matplotlib import animation
from matplotlib.patches import Ellipse, Rectangle

from IPython.html import widgets
from IPython.html.widgets import interact, interactive

import nchain

# %matplotlib inline
```

```
from IPython.display import Image
Image(filename='snake-schematic.png')
```

```
## 2-snake ##
n = 2

# dynamic variables
q0, q1 = me.dynamicsymbols('q0 q1')
u0, u1 = me.dynamicsymbols('u0 u1')

q3, q4 = me.dynamicsymbols('q3 q4')
u3, u4 = me.dynamicsymbols('u3 u4')

f1 = me.dynamicsymbols('f1')
f1d = me.dynamicsymbols('f1', 1)
f1dd = me.dynamicsymbols('f1', 2)

# variables for bodies
g, t = sym.symbols('g t')
m0, m1 = sym.symbols('m_0 m_1')
l0, l1 = sym.symbols('l_0 l_1')
Izz0, Izz1 = sym.symbols('I_zz_0 I_zz_1')

# inertial reference frame
frN = me.ReferenceFrame('fr_N')

# body-fixed frames
fr0 = frN.orientnew('fr_0', 'Axis', [q0, frN.z])
fr1 = fr0.orientnew('fr_1', 'Axis', [q1, fr0.z])
#fr2 = fr1.orientnew('fr_2', 'Axis', [q2, fr1.z])

# angular velocities of frames
fr0.set_ang_vel(frN, u0 * frN.z)
fr1.set_ang_vel(fr0, u1 * fr0.z)

# origin of inertial frame
Nor = me.Point('N_or')

# joints
jt0 = Nor.locatenew('jt_0', q3 * frN.x + q4 * frN.y)
jt1 = jt0.locatenew('jt_1', l0 * fr0.x)
jt2 = jt1.locatenew('jt_2', l1 * fr1.x)

# mass centers
mc0 = jt0.locatenew('mc_0', l0 / 2 * fr0.x)
mc1 = jt1.locatenew('mc_1', l1 / 2 * fr1.x)

# constrained point velocities
Nor.set_vel(frN, 0)
jt0.set_vel(frN, u3 * frN.x + u4 * frN.y)

# unconstrained point velocities
jt1.v2pt_theory(jt0, frN, fr0)
jt2.v2pt_theory(jt1, frN, fr1)

mc0.v2pt_theory(jt0, frN, fr0)
mc1.v2pt_theory(jt1, frN, fr1)

# inertia dyads
in0 = (me.inertia(fr0, 0, 0, Izz0), mc0)
in1 = (me.inertia(fr1, 0, 0, Izz1), mc1)

# bodies
bd0 = me.RigidBody('bd_0', mc0, fr0, m0, in0)
bd1 = me.RigidBody('bd_1', mc1, fr1, m1, in1)

# kinematic differential equations
kd = [q0.diff(t) - u0,
      q1.diff(t) - u1,
      q3.diff(t) - u3,
      q4.diff(t) - u4]

# forces
fl = [(mc0, -m0 * g * frN.z),
      (mc1, -m1 * g * frN.z)]

# fl = [(mc0, -m0 * g * frN.y),
#       (mc1, -m1 * g * frN.y)]

# bodies
bl = [bd0, bd1]

# q_ind and u_ind
qind = [q0, q3, q4]
uind = [u0, u3, u4]

qdep = [q1]
udep = [u1]

qcons = [q1 - f1]
ucons = [u1 - f1d]

# equations of motion using Kane's method
kane = me.KanesMethod(frame=frN,
                      q_ind=qind,
                      u_ind=uind,
                      kd_eqs=kd,
                      q_dependent=qdep,
                      configuration_constraints=qcons,
                      u_dependent=udep,
                      velocity_constraints=ucons)

(fr, frstar) = kane.kanes_equations(fl, bl)
kanezero = fr + frstar
```

```

```

```
n = 3

# group variables
x, y, theta = me.dynamicsymbols('x y theta')  # generalized coordinates
xd, yd, thetad = me.dynamicsymbols('x y theta', 1)
us, vs, omega = me.dynamicsymbols('u v omega')  # generalized speeds

# shape or configuration variables
q = me.dynamicsymbols('q:' + str(n - 1))
u = me.dynamicsymbols('u:' + str(n - 1))

# constraints
cq = me.dynamicsymbols('c:' + str(n - 1))  # holonomic constraint
cu = me.dynamicsymbols('c:' + str(n - 1), 1)  # non-holonomic constraint
cs = me.dynamicsymbols('c:' + str(n - 1), 2)  # used in matrix substitution later

# forces and torques
F = me.dynamicsymbols('F:' + str(n + 1))  # n + 1 joints to apply forces/torques
T = me.dynamicsymbols('T:' + str(n + 1))

# variables for bodies
g, t = sym.symbols('g t')
m = sym.symbols('m:' + str(n))
l = sym.symbols('l:' + str(n))
Izz = sym.symbols('I_zz_:' + str(n))

# frames
frames = [me.ReferenceFrame('fr_N')]
for i in range(n):
    name = 'fr_' + str(i)
    if i == 0:
#         frame = frames[i].orientnew(name, 'Axis', [theta, frames[i].z])
        frame = frames[i].orientnew(name, 'Body', [0, 0, theta], '123')
        frame.set_ang_vel(frames[i], omega * frames[i].z)
    else:
#         frame = frames[i].orientnew(name, 'Axis', [q[i - 1], frames[i].z])
        frame = frames[i].orientnew(name, 'Body', [0, 0, q[i - 1]], '123')
        frame.set_ang_vel(frames[i], u[i - 1] * frames[i].z)
    frames.append(frame)

# origin of inertial frame
joints = [me.Point('N_or')]
joints[0].set_vel(frames[0], 0)

# joints
for i in range(n + 1):
    name = 'jt_' + str(i)
    if i == 0:
        joint = joints[i].locatenew(name, x * frames[0].x + y * frames[0].y)
        joint.set_vel(frames[0], us * frames[0].x + vs * frames[0].y)
    else:
        joint = joints[i].locatenew(name, l[i - 1] * frames[i].x)
        joint.v2pt_theory(joints[i], frames[0], frames[i])
    joints.append(joint)

# mass centers
mc = []
for i in range(n):
    name = 'mc_' + str(i)
    mcent = joints[i + 1].locatenew(name, l[i] / 2 * frames[i + 1].x)
    mcent.v2pt_theory(joints[i + 1], frames[0], frames[i + 1])
    mc.append(mcent)

# inertia dyads
dyads = []
for i in range(n):
    dyad = (me.inertia(frames[i + 1], 0, 0, Izz[i]), mc[i])
    dyads.append(dyad)

# bodies
bodies = []
for i in range(n):
    name = 'bd_' + str(i)
    body = me.RigidBody(name, mc[i], frames[i + 1], m[i], dyads[i])
    bodies.append(body)

# kinematic differential equations
kd = [xd - us, yd - vs, thetad - omega]
for i in range(n - 1):
    kd.append(q[i].diff(t) - u[i])

# independent coordinates and speeds
qind = [x, y, theta]
uind = [us, vs, omega]

# dependent coordinates and speeds
qdep = q
udep = u

# constraints (they must equal zero)
# Note: qcons (holonomic constraints) only matter for linearization
qcons = []
ucons = []
for i in range(n - 1):
    qcons.append(q[i] - cq[i])
    ucons.append(u[i] - cu[i])

# gravitational forces
#TODO: add aerodynamic forces
forces = []
for i in range(n):
    forces.append((mc[i], -m[i] * g * frames[0].z))

torques = []
for i in range(n):
    torques.append((frames[i + 1], (T[i] - T[i + 1]) * frames[0].z))

fl = forces #+ torques

# setup dictionaries of values to return
dynamic = dict(qind=qind, uind=uind,
               qdep=qdep, udep=udep,
               qcons=qcons, ucons=ucons,
               q=qind + qdep, u=uind + udep,
               kd=kd, T=T, F=F,
               cq=cq, cu=cu, cs=cs)
symbol = dict(m=m, l=l, Izz=Izz, g=g, t=t)
setup = dict(frames=frames, joints=joints, mc=mc, dyads=dyads)
fbd = dict(fl=fl, bodies=bodies)
```

```
mc
```

    [mc_0, mc_1, mc_2]

```
mc0 = mc[0]
```

```
frames
```

    [fr_N, fr_0, fr_1, fr_2]

vel0 = mc0.vel(frames[0]).express(frames[1])
vel0

```
vel0.to_matrix(frames[0]).simplify()
```

```
fr1 = frames[1]
```

```
fr1.dcm(frames[0])
```

```
frames[2].dcm(frames[0]).simplify()
```

```
vel0.args
```

```
sym.atan2?
```

```
mc0.vel(frames[0]).express(frames[1]).simplify()
```

```
vel0 = mc0.vel(frames[0])#.express(frames[0]).simplify()
vel0
```

```
vel0.dot(frames[1].x)
```

```
vel0.dot(frames[1].y)
```

```
vmag1 = mc[1].vel(frames[0]).dot(frames[2].y).simplify()
vmag1
```

```
CD = sym.symbols('C_D')
```

```
aerof1 = CD * vmag1**2
```

```
CD = 0
```

```
af1 = aerof1.subs({CD: 0})
```

```
af1
```

```
# equations of motion using Kane's method
kane = me.KanesMethod(frame=setup['frames'][0],
                      q_ind=dynamic['qind'],
                      u_ind=dynamic['uind'],
                      kd_eqs=dynamic['kd'],
                      q_dependent=dynamic['qdep'],
                      configuration_constraints=dynamic['qcons'],
                      u_dependent=dynamic['udep'],
                      velocity_constraints=dynamic['ucons'])

# kane = me.KanesMethod(frame=frames[0],
#                       q_ind=qind,
#                       u_ind=uind,
#                       kd_eqs=kd,
#                       q_dependent=qdep,
#                       configuration_constraints=qcons,
#                       u_dependent=udep,
#                       velocity_constraints=ucons)

(fr, frstar) = kane.kanes_equations(fl, bodies)
kanezero = fr + frstar
mass = kane.mass_matrix_full
forcing = kane.forcing_full

eom = dict(kane=kane, fr=fr, frstar=frstar, mass=mass, forcing=forcing, kanezero=kanezero)
```

```
nchain.simp(kanezero[0])
```

```
nchain.simp(kanezero[0])
```

```
nchain.simp(kanezero[1])
```

```
nchain.simp(kanezero[2])
```

```
me.angular_momentum(joints[1], frames[0], *bodies).express(frames[0]).simplify()
```

```
me.angular_momentum(joints[0], frames[0], *bodies).express(frames[0]).simplify()
```

```
nchain.simp(mass)
```

```
nchain.simp(forcing)
```

```
# get values for the snake
snake = nchain.equal_snake(n)

# symbols used in the simulation
constants = [g]
for i in range(n):
    constants = constants + [symbol['m'][i], symbol['l'][i], symbol['Izz'][i]]

# acutal values for the simulation
constant_vals = [9.81]
for i in range(n):
    constant_vals = constant_vals + [snake['m'][i], snake['l'][i], snake['I'][i]]

consts = dict(names=constants, vals=np.array(constant_vals))
```

```
#F_func, M_func, ffunc, mfunc, dummy_dict = nchain.callable_matrices(dynamic, eom, consts)

# dummy symbols used for dynamamic variables
dyn_symbols = dynamic['cs'] + dynamic['q'] + dynamic['u']
dummy_symbols = [sym.Dummy() for i in dyn_symbols]
dummy_dict = dict(zip(dyn_symbols, dummy_symbols))

# substitute dummy symbols into mass matrix and forcing vector
kd_dict = eom['kane'].kindiffdict()  # solved kinematical differential equations
F = eom['forcing'].subs(kd_dict).subs(dummy_dict)  # substitute into the forcing vector
M = eom['mass'].subs(kd_dict).subs(dummy_dict)  # substitute into the mass matrix

# callable matrices
F_func = sym.lambdify(consts['names'] + dummy_symbols, F)
M_func = sym.lambdify(consts['names'] + dummy_symbols, M)

# partially evaluate matrices to run faster (maybe 15% or so)
ffunc = functools.partial(F_func, *consts['vals'])
mfunc = functools.partial(M_func, *consts['vals'])
```

```
def generate_trajectory(thi, thf, ti, tf, n=50, end=False):

    tfnorm = tf - ti

    a0 = thi
    a1 = 0
    a2 = 3 / tfnorm**2 * (thf - thi)
    a3 = -2 / tfnorm**3 * (thf - thi)

    t = np.linspace(0, tfnorm, n, endpoint=end)
    th = a0 + a1 * t + a2 * t**2 + a3 * t**3
    dth = a1 + 2 * a2 * t + 3 * a3 * t**2
    ddth = 2 * a2 + 6 * a3 * t

    return np.c_[t + ti, th, dth, ddth]

def quintic_trajectory(thi, thf, ti, tf, dthi=0, dthf=0, ddthi=0, ddthf=0, n=50, end=False):
    """Quinitic trajectory from Craig, p. 209. This should
    be smoother.
    """

    tfn = tf - ti

    a0 = thi
    a1 = dthi
    a2 = ddthi / 2
    a3 = (20 * thf - 20 * thi - (8 * dthf + 12 * dthi) * tfn - (3 * ddthi - ddthf) * tfn**2) / (2 * tfn**3)
    a4 = (30 * thi - 30 * thf + (14 * dthf + 16 * dthi) * tfn + (3 * ddthi - 2 * ddthf) * tfn**2) / (2 * tfn**4)
    a5 = (12 * thf - 12 * thi - (6 * dthf + 6 * dthi) * tfn - (ddthi - ddthf) * tfn**2) / (2 * tfn**5)

    t = np.linspace(0, tfn, n, endpoint=end)
    #t = np.linspace(ti, tf, n, endpoint=end)
    th = a0 + (a1 * t) + (a2 * t**2) + (a3 * t**3) + (a4 * t**4) + (a5 * t**5)
    dth = a1 + (2 * a2 * t) + (3 * a3 * t**2) + (4 * a4 * t**3) + (5 * a5 * t**4)
    ddth = 2 * a2 + (6 * a3 * t) + (12 * a4 * t**2) + (20 * a5 * t**3)

    return np.c_[ti + t, th, dth, ddth]

def path_gen(traj_gen, angles, times, npts):
    """Generate a quintic path.
    """

    assert len(angles) == len(times)

    nparts = len(angles)
    traj = []

    for i in range(nparts - 1):
        if i == nparts - 1:
            end = True
        else:
            end = False

        part = traj_gen(angles[i], angles[i+1], times[i], times[i+1], n=npts, end=end)
        traj.append(part)

    tr = np.array(traj)
    tr = tr.reshape(-1, 4)

    return tr

def path_repeat(path, ntimes):
    """Repeat a path ntimes.
    """

    print('this is not finished yet.')
    return

    tf, ti = path[-1, 0], path[0, 0]
    dt = path[1, 0] - ti

    path = np.tile(path.T, ntimes).T
    path[:, 0] = np.linspace(ti, ntimes * tf, len(path), endpoint=False)

    return path

_test = generate_trajectory(15, 75, 1, 3, end=True)
_test = quintic_trajectory(15, 75, 1, 3, end=True)
tm, th, dth, ddth = _test.T
```

```
fig, ax = plt.subplots(figsize=(6, 4))
ax.plot(tm, th)
ax.plot(tm, dth)
ax.plot(tm, ddth)

display(fig)
```

```
mass.shape
```

```
plt.close('all')
```

```
def rhs(x, t, args):

#     x[3] = np.interp(t, ts, data0[:, 1])  # q0
#     x[4] = np.interp(t, ts, data1[:, 1])  # q1
#     x[8] = np.interp(t, ts, data0[:, 2])  # u0
#     x[9] = np.interp(t, ts, data1[:, 2])  # u1

    ts, ddq0, ddq1 = args
    c0 = np.interp(t, ts, ddq0)  # ddc0
    c1 = np.interp(t, ts, ddq1)  # ddc1

    x = np.hstack((c0, c1, x))
    dx = np.array(np.linalg.solve(mfunc(*x), ffunc(*x))).flatten()

#     dx[3] = np.interp(t, ts, data0[:, 2])  # dq0
#     dx[4] = np.interp(t, ts, data1[:, 2])  # dq1
#     dx[8] = np.interp(t, ts, data0[:, 3])  # du0
#     dx[9] = np.interp(t, ts, data1[:, 3])  # du1

    return dx

npts = 50000
quintic = True

angles0 = np.r_[0, 65, 0, -65, 0]
times0 = np.r_[0, .25, .5, .75, 1]

angles1 = np.r_[0, 65, 85, 65, 0]
times1 = np.r_[0, .25, .5, .75, 1]

path_fun = quintic_trajectory if quintic else generate_trajectory
data0 = path_gen(path_fun, angles0, times1, npts)
data1 = path_gen(path_fun, angles1, times1, npts)

# store the degree version
d0 = data0.copy()
d1 = data1.copy()

# get in radians
data0[:, 1:] = np.deg2rad(data0[:, 1:])
data1[:, 1:] = np.deg2rad(data1[:, 1:])

ts = data0[:, 0]

# initial conditions
qstart = np.zeros(n + 2)
ustart = np.zeros(n + 2)

qstart[2] = -np.pi / 2
qstart[-2:] = data0[0, 1], data1[0, 1]
ustart[-2:] = data0[0, 2], data1[0, 2]

x0 = np.hstack((qstart, ustart))
```

```
#np.allclose(data1[1999, 3], interp(ts[1999], ts, data1[:, 3]))
```

```
fig, axs = plt.subplots(2, 3)

for i, ii in zip(range(3), range(1, 4)):
    axs[0, i].plot(np.rad2deg(data0[:, ii]), np.rad2deg(data1[:, ii]))
    axs[0, i].axis('equal')
    axs[1, i].plot(d0[:, 0], d0[:, ii])
    axs[1, i].plot(d1[:, 0], d1[:, ii])

fig.tight_layout()
```

```
plt.close('all')
```

```
now = time.time()
args = (ts, data0[:, 3], data1[:, 3])
y, info = odeint(rhs, x0, ts, args=(args,), full_output=True, rtol=1e-14)
print('Simulation time: {0:.2f} sec'.format(time.time() - now))
```

    Simulation time: 3.17 sec

```
shp0 = np.rad2deg(y[:, [3, 8]])
shp1 = np.rad2deg(y[:, [4, 9]])
```

```
fig, axs = plt.subplots(2, 3)

for i, ii in zip(range(3), range(1, 4)):
    axs[0, i].plot(d0[:, ii], d1[:, ii], color='k')
    axs[1, i].plot(d0[:, 0], d0[:, ii], alpha=.75, color='b')
    axs[1, i].plot(d1[:, 0], d1[:, ii], alpha=.75, color='g')

    if i == 0 or i ==1:
        axs[0, i].plot(shp0[:, i], shp1[:, i], 'b')
        axs[1, i].plot(ts, shp0[:, i], '--r')
        axs[1, i].plot(ts, shp1[:, i], '--k')

    axs[0, i].axis('equal')

fig.tight_layout()
```

```
plt.close('all')
```

```
def error(exact, approx):
    """Absolute error.
    """

    return np.abs(exact - approx)
```

```
ii = 0

fig, ax = plt.subplots()
ax.semilogy(error(d0[:, ii+1], shp0[:, ii]))
ax.semilogy(error(d1[:, ii+1], shp1[:, ii]))
```

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

```
import prettyplotlib as ppl
```

```
fig, ax = plt.subplots(figsize=(8, 6))

ax.fill_between(np.rad2deg(data0[:, 1]), np.rad2deg(data1[:, 1]), alpha=.5, lw=2)
ax.set_xlabel(r'$q_0$, deg', fontsize='large')
ax.set_ylabel(r'$q_1$, deg', fontsize='large')
ppl.plot(ax, [], [])
ax.axis('equal')

fig.set_facecolor('w')
#fig.tight_layout()
```

```
fig, (ax1, ax2) = plt.subplots(2, 1, sharex=True)
iq = n + 2

lines = ax1.plot(ts, y[:, :iq], lw=1.5)
#lab = xlabel('Time [sec]')
leg = ax1.legend(dynamic['q'])#, frameon=False)
leg.get_frame().set_alpha(.5)

lines = ax2.plot(ts, y[:, iq:], lw=1.5)
#lab = xlabel('Time [sec]')
leg = ax2.legend(dynamic['u'])#, frameon=False)
leg.get_frame().set_alpha(.5)

#fig.tight_layout()
```

```
specified = []
```

```
constants

now = time.time()
ode_fun = generate_ode_function(mass, forcing, constants, kane._q, kane._u, specified)#, generator='lambdify')
print('Elapsed time: {0:.2f} sec'.format(time.time() - now))
```

```

```

```
import prettyplotlib as ppl
```

```
rads, degs, jtx, jty, mcx, mcy, comx, comy = organize_data(y, n, snake)
```

```
skip = 100
interval = 1
hst = len(y)

limx = (-1, 1)
limy = (-1, 1)

limx = (-.25, .25)
limy = (-.75, .05)

# plt.close('all')
anim = n_movie_maker(n, snake, ts[::skip], y[::skip, :], limx=limx, limy=limy, hst=hst, interval=interval)
#anim.save('3-snake-shape.mp4')
```

```
plt.close('all')
```

```
fig, ax = plt.subplots()

ax.plot(comx, comy)
```

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

```
time_fmt = '{0:2.3f} sec'
time_fmt.format(2)
```

    '2.000 sec'

```
def organize_data(y, n, snake_dict):
    """Organize the data so it can be animated with ellipses.
    """

    ms = snake_dict['m']
    ls = snake_dict['l']
    npts = y.shape[0]
    angs = y[:, 2:n + 2]
    rads = angs.cumsum(axis=1)
    degs = np.rad2deg(rads)

    jtx, jty = y[:, :2].T
    jtx = jtx.reshape(-1, 1)
    jty = jty.reshape(-1, 1)

    mcx, mcy = [], []
    for i in range(n):
        dx = ls[i] * np.cos(rads[:, i])
        dy = ls[i] * np.sin(rads[:, i])

        mcx.append((jtx[:, i] + dx / 2).flatten())
        mcy.append((jty[:, i] + dy / 2).flatten())

        x = (jtx[:, i] + dx).reshape(-1, 1)
        y = (jty[:, i] + dy).reshape(-1, 1)

        jtx = np.hstack((jtx, x))
        jty = np.hstack((jty, y))

    mcx = np.array(mcx).T
    mcy = np.array(mcy).T

    # center of mass
    mms = np.array(ms)
    comx = (mcx * mms).sum(axis=1) / mms.sum()
    comy = (mcy * mms).sum(axis=1) / mms.sum()

    if False:
        jtx = (jtx.T - np.abs(comx)).T
        jty = (jty.T - np.abs(comy)).T
        mcx = (mcx.T - np.abs(comx)).T
        mcy = (mcy.T - np.abs(comy)).T
        comx = comx - np.abs(comx)
        comy = comy - np.abs(comy)

    return rads, degs, jtx, jty, mcx, mcy, comx, comy

def n_movie_maker(n, snake_dict, ts, y, limx=(-1, 1), limy=(-1, 1), hst=50, interval=100):

    rads, degs, jtx, jty, mcx, mcy, comx, comy = organize_data(y, n, snake_dict)

    ms = snake_dict['m']
    ls = snake_dict['l']
    ws = snake_dict['w'] * 2.5

    ii = 0
    st = 0

    fig, ax = plt.subplots(1, 1, sharex=True, sharey=True, figsize=(9, 9))
    ppl.plot(ax, [], [])
    ax.axis('image')
    ax.set_xlim(limx)
    ax.set_ylim(limy)

    fig.set_facecolor('w')
    #fig.tight_layout()

    time_fmt = '{0:2.3f} sec'
    time_text = ax.text(0.05, 0.9, '', transform=ax.transAxes, fontsize='large')

    ellipse_params = dict(alpha=.5, color='green')
    patches = []
    for i in range(n):
        e = Ellipse(xy=(mcx[ii, i], mcy[ii, i]),
                    width=ws[i], height=1.05 * ls[i],
                    angle=degs[ii, i] - 90, **ellipse_params)
        ax.add_patch(e)
        patches.append(e)

    head, = ax.plot(jtx[ii, 0], jty[ii, 0], 'go', alpha=.6, markersize=7)
    jlines, mclines = [], []
    for i in range(n + 1):
        jline, = ax.plot(jtx[st:ii, i], jty[st:ii, i], 'b--', alpha=.5)
        jlines.append(jline)
    for i in range(n):
        mcline, = ax.plot(mcx[st:ii, i], mcy[st:ii, i], 'r', alpha=.5)
        mclines.append(mcline)

    coml, = ax.plot(comx[st:ii], comy[st:ii], 'k-', lw=2, alpha=.9)

    def init():
        ii = 0
        head.set_data(jtx[ii, 0], jty[ii, 0])
        for i in range(n):
            patches[i].set_alpha(.3)
        time_text.set_text('')
        return [head, time_text] + patches

    def animate(ii):

        st = ii - hst
        if st < 0:
            st = 0

        head.set_data(jtx[ii, 0], jty[ii, 0])
        for i in range(n):
            patches[i].set_visible(True)
            patches[i].set_alpha(.5)
            patches[i].center = (mcx[ii, i], mcy[ii, i])
            patches[i].angle = degs[ii, i] - 90
            jlines[i].set_data(jtx[st:ii, i], jty[st:ii, i])
            mclines[i].set_data(mcx[st:ii, i], mcy[st:ii, i])
        for i in range(n + 1):
            jlines[i].set_data(jtx[st:ii, i], jty[st:ii, i])

        time_text.set_text(time_fmt.format(ts[ii]))

        coml.set_data(comx[st:ii], comy[st:ii])

        return patches + [head, coml, time_text] + jlines + mclines

    anim = animation.FuncAnimation(fig, animate, frames=len(ts),
                interval=interval, blit=False, repeat=False, init_func=init)

    return anim
```

```

```

```

```

```
import nchain

mm = nchain.simp(mass)
mm
```

```
ff = nchain.simp(forcing)
ff
```

```
import nchain

mm = nchain.simp(kane.mass_matrix_full)
ff = nchain.simp(kane.forcing_full)
```

```
mm
```

```
ff
```

```
# get values for the snake
snake = nchain.equal_snake(n)

# symbols used in the simulation
constants = [g, m0, l0, Izz0, m1, l1, Izz1]

# acutal values for the simulation
constant_vals = [9.81]
for i in range(n):
    constant_vals = constant_vals + [snake['m'][i], snake['l'][i], snake['I'][i]]

consts = dict(names=constants, vals=np.array(constant_vals))
```

```
dynamic = dict(q=[q0, q1, q3, q4], u=[u0, u1, u3, u4])
eom = dict(kane=kane, fr=fr, frstar=frstar, mass=mm, forcing=ff, kanezero=kanezero)
```

```
#F_func, M_func, ffunc, mfunc, dummy_dict = nchain.callable_matrices(dynamic, eom, consts)

# dummy symbols used for dynamamic variables
dyn_symbols = [f1dd] + [q0, q3, q4, q1] + [u0, u3, u4, u1]  # dynamic['q'] + dynamic['u']
dummy_symbols = [sym.Dummy() for i in dyn_symbols]
dummy_dict = dict(zip(dyn_symbols, dummy_symbols))

# substitute dummy symbols into mass matrix and forcing vector
kd_dict = eom['kane'].kindiffdict()                              # solved kinematical differential equations
F = eom['forcing'].subs(kd_dict).subs(dummy_dict)      # substitute into the forcing vector
M = eom['mass'].subs(kd_dict).subs(dummy_dict)  # substitute into the mass matrix

# callable matrices
F_func = sym.lambdify(consts['names'] + dummy_symbols, F)
M_func = sym.lambdify(consts['names'] + dummy_symbols, M)

# partially evaluate matrices to run faster (maybe 15% or so)
ffunc = functools.partial(F_func, *consts['vals'])
mfunc = functools.partial(M_func, *consts['vals'])
```

```
def rhs(x, t, args):

    fintp = np.interp(t, ts, ss1a)
    x[3] = np.interp(t, ts, qq1)
    x[7] = np.interp(t, ts, uu1a)

    x = np.hstack((fintp, x))
    dx = np.array(np.linalg.solve(mfunc(*x), ffunc(*x))).flatten()

    # we were getting some errors, so specify the derivatives
    dx[3] = np.interp(t, ts, uu1a)
    dx[7] = np.interp(t, ts, ss1a)

    return dx

args = ()

npts = 1000
ts, dt = linspace(0, 5, npts, retstep=True)

# initial conditions
qstart = np.zeros(n + 2)
ustart = np.zeros(n + 2)

q0_0 = np.pi / 2
q1_0 = np.pi / 2

f = 8

#TODO leave a little extra, so the derivatives don't have higher errors
#on the end points.
qq1 = np.sin(f * ts + q1_0)
uu1a = f * np.cos(f * ts + q1_0)
ss1a = -f * f * np.sin(f * ts + q1_0)
uu1g = np.gradient(qq1, dt)
ss1g = np.gradient(uu1a, dt)

qstart[[0, 3]] = q0_0, qq1[0]
ustart[3] = uu1a[0]
ustart[0] = -2 * uu1a[0]

# ustart[3] = 0

x0 = np.hstack((qstart, ustart));
```

```
uu1a[0]
```

```
fig, ax = plt.subplots()

ax.plot(ts, qq1)
ax.plot(ts, uu1g)
ax.plot(ts, ss1g)
ax.plot(ts, uu1a, 'g--')
ax.plot(ts, ss1a, 'r--')
```

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

```
plt.close('all')
```

```
now = time.time()
y = odeint(rhs, x0, ts, args=(args,))
print('Simulation time: {0:.2f} sec'.format(time.time() - now))
```

    Simulation time: 3.78 sec

```
# reorgainze the data like we expect...
yc = np.zeros(y.shape)
yc = y[:, [0, 3, 1, 2, 4, 7, 5, 6]]
y = yc
```

```
fig, (ax1, ax2) = plt.subplots(2, 1, sharex=True)
iq = n + 2

lines = ax1.plot(ts, y[:, :iq], lw=2)
#lab = xlabel('Time [sec]')
leg = ax1.legend(dynamic['q'])#, frameon=False)
leg.get_frame().set_alpha(.5)

lines = ax2.plot(ts, y[:, iq:], lw=2)
#lab = xlabel('Time [sec]')
leg = ax2.legend(dynamic['u'])#, frameon=False)
leg.get_frame().set_alpha(.5)
```

```
fig, ax = plt.subplots()

ax.plot(ts, qq1, 'b')
ax.plot(ts, y[:, 1], 'b--')

ax.plot(ts, uu1a, 'g')
ax.plot(ts, y[:, 5], 'g--')
```

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

```
def err(want, got):
    return (want - got) / got * 100

errq = err(qq1, y[:, 1])
erru = err(uu1a, y[:, 5])
```

```
fig, ax = plt.subplots()

ax.plot(ts, errq)
ax.plot(ts, erru)
```

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

```
plt.close('all')
```

```
interval = 50
limx = (-1, 1)
limy = (-1, 1)

limx = (-.5, .5)
limy = (-.2, .75)

anim = nchain.n_movie_maker(n, snake, ts[:], y[:, :], limx=limx, limy=limy, hst=100, interval=interval)
#anim.save('2-snake-H=0.mp4')
```

```
sh = np.where(ts <= .8)[0]
```

```
rads, degs, jtx, jty, mcx, mcy, comx, comy = nchain.organize_data(y, n, snake)
```

```
fig, ax = plt.subplots()

ax.plot(comx, comy)
```

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

```
comx
```

    array([-0.07215614, -0.07215614, -0.07215614, ..., -0.07216757,
           -0.07216757, -0.07216757])

```
comy
```

    array([ 0.30358092,  0.30358092,  0.30358092, ...,  0.30362107,
            0.30362107,  0.30362107])

```
fig, ax = plt.subplots()

ax.plot(y[:, 0], y[:, 1])
```

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

```
fig, ax = plt.subplots()

ax.plot(jtx[:, 0], jty[:, 0])
ax.plot(jtx[:, 1], jty[:, 1])
ax.plot(jtx[:, 2], jty[:, 2])
```

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

```
exp = me.linear_momentum(frN, *[bd0, bd1]).express(frN)
exp.simplify()
```

```
me.angular_momentum(Nor, frN, *[bd0, bd1]).express(frN).simplify()
```

```

```

```

```

```

```

```

```

```

```

```

```

```
# number of links (not currently used)
n = 3

# dynamic variables
q0, q1, q2 = me.dynamicsymbols('q0 q1 q2')
u0, u1, u2 = me.dynamicsymbols('u0 u1 u2')

q3, q4 = me.dynamicsymbols('q3 q4')
u3, u4 = me.dynamicsymbols('u3 u4')

# variables for bodies
g, t = sym.symbols('g t')
m0, m1, m2 = sym.symbols('m_0 m_1 m_2')
l0, l1, l2 = sym.symbols('l_0 l_1 l_2')
Izz0, Izz1, Izz2 = sym.symbols('I_zz_0 I_zz_1 I_zz_2')

# inertial reference frame
frN = me.ReferenceFrame('fr_N')

# body-fixed frames
fr0 = frN.orientnew('fr_0', 'Axis', [q0, frN.z])
fr1 = fr0.orientnew('fr_1', 'Axis', [q1, fr0.z])
fr2 = fr1.orientnew('fr_2', 'Axis', [q2, fr1.z])

# angular velocities of frames
fr0.set_ang_vel(frN, u0 * frN.z)
fr1.set_ang_vel(fr0, u1 * fr0.z)
fr2.set_ang_vel(fr1, u2 * fr1.z)

# origin of inertial frame
Nor = me.Point('N_or')

# joints
jt0 = Nor.locatenew('jt_0', q3 * frN.x + q4 * frN.y)
jt1 = jt0.locatenew('jt_1', l0 * fr0.x)
jt2 = jt1.locatenew('jt_2', l1 * fr1.x)
jt3 = jt2.locatenew('jt_3', l2 * fr2.x)

# mass centers
mc0 = jt0.locatenew('mc_0', l0 / 2 * fr0.x)
mc1 = jt1.locatenew('mc_1', l1 / 2 * fr1.x)
mc2 = jt2.locatenew('mc_2', l2 / 2 * fr2.x)

# constrained point velocities
Nor.set_vel(frN, 0)
jt0.set_vel(frN, u3 * frN.x + u4 * frN.y)

# unconstrained point velocities
jt1.v2pt_theory(jt0, frN, fr0)
jt2.v2pt_theory(jt1, frN, fr1)
jt3.v2pt_theory(jt2, frN, fr2)

mc0.v2pt_theory(jt0, frN, fr0)
mc1.v2pt_theory(jt1, frN, fr1)
mc2.v2pt_theory(jt2, frN, fr2)

# inertia dyads
in0 = (me.inertia(fr0, 0, 0, Izz0), mc0)
in1 = (me.inertia(fr1, 0, 0, Izz1), mc1)
in2 = (me.inertia(fr2, 0, 0, Izz2), mc2)

# bodies
bd0 = me.RigidBody('bd_0', mc0, fr0, m0, in0)
bd1 = me.RigidBody('bd_1', mc1, fr1, m1, in1)
bd2 = me.RigidBody('bd_2', mc2, fr2, m2, in2)

# kinematic differential equations
kd = [q0.diff(t) - u0,
      q1.diff(t) - u1,
      q2.diff(t) - u2,
      q3.diff(t) - u3,
      q4.diff(t) - u4]

# forces
fl = [(mc0, -m0 * g * frN.z),
      (mc1, -m1 * g * frN.z),
      (mc2, -m2 * g * frN.z)]

# fl = [(mc0, -m0 * g * frN.y),
#       (mc1, -m1 * g * frN.y),
#       (mc2, -m2 * g * frN.y)]

# bodies
bl = [bd0, bd1, bd2]

# q_ind and u_ind
qind = [q0, q3, q4]
uind = [u0, u3, u4]

qdep = [q1, q2]
udep = [u1, u2]

qcons = [q1, q2]
ucons = [u1, u2]

# # try adding some configuration level and configuration velocity constraints
# zero = jt0.pos_from(Nor)
# conlist_coor = [zero & frN.x, zero & frN.y]

# # Define the velocity level constraints
# dzero = me.time_derivative(zero, frN)
# conlist_speed = [dzero & frN.x, dzero & frN.y]

# q3, q4 = me.dynamicsymbols('q3 q4')
# u3, u4 = me.dynamicsymbols('u3 u4')

# equations of motion using Kane's method
kane = me.KanesMethod(frame=frN,
                      q_ind=qind,
                      u_ind=uind,
                      kd_eqs=kd,
                      q_dependent=qdep,
                      configuration_constraints=qcons,
                      u_dependent=udep,
                      velocity_constraints=ucons)

(fr, frstar) = kane.kanes_equations(fl, bl)
kanezero = fr + frstar

# these take to long...
# solve Kane's equations for the derivatives of the speeds
#eom = sym.solvers.solve(kanezero, q0d, q1d, q2d)

# add the kinematical differential equations to get the equations of motion
#eom.update(kane.kindiffdict())

# print the results
#me.mprint(eom)

#rhs = kane.mass_matrix.inv() * kane.forcing
#me.mprint(rhs)

#j3pos = jt3.pos_from(jt0).express(frN).simplify()
#sym.trigsimp(kanezero)
```

```
#kanezero
```

```
kz = sym.trigsimp(kanezero)
```

```
type(kz)
```

    sympy.matrices.immutable.ImmutableMatrix

```
kz[0]
```

```
kz[3]
```

```
sym.trigsimp(frstar[3])
```

```

```

```

```

```
# parameter values
ls = .25                          # segment lengths
ms = .15                          # segment masses
ws_ratio = .1                     # width is a function of length
ws = ls * ws_ratio                # segment widths
Izzs = ms * (ls**2 + ws**2) / 12  # flat plate about com

# parameter arrays for numerical simulation
param_names = [g, m0, l0, Izz0, m1, l1, Izz1, m2, l2, Izz2]
param_vals = [9.81, ms, ls, Izzs, ms, ls, Izzs, ms, ls, Izzs]

# dummy symbols used for dynamamic variables
dyn_symbols = coords + speeds
dummy_symbols = [sym.Dummy() for i in dyn_symbols]
dummy_dict = dict(zip(dyn_symbols, dummy_symbols))

# substitute dummy symbols into mass matrix and forcing vector
kd_dict = kane.kindiffdict()                              # solved kinematical differential equations
F = kane.forcing_full.subs(kd_dict).subs(dummy_dict)      # substitute into the forcing vector
M = kane.mass_matrix_full.subs(kd_dict).subs(dummy_dict)  # substitute into the mass matrix

# callable matrices
F_func = sym.lambdify(param_names + dummy_symbols, F)
M_func = sym.lambdify(param_names + dummy_symbols, M)

# partially evaluate matrices to run faster (maybe 15% or so)
ffunc = functools.partial(F_func, *param_vals)
mfunc = functools.partial(M_func, *param_vals)

def rhs(x, t, args):
    arguments = np.hstack((args, x))
    dx = np.array(solve(M_func(*arguments), F_func(*arguments))).flatten()
    return dx

def rhs_partial(x, t, args):

    #x[1] = np.interp(t, ts, theta1)
    #x[2] = np.interp(t, ts, theta2)

    dx = np.array(solve(mfunc(*x), ffunc(*x))).flatten()
    return dx

# initial conditions
qstart = (np.pi / 2, np.pi / 2, np.pi / 2, 2, 2)
ustart = (2, 0, 2, 0, 0)  # (0.5, 0.5, 0.5)
#ustart = (0, -1.5, 1.5)

# stable ICs
# qstart = (-np.pi / 2, 0, 0)
# ustart = (0, 0, 0)

ts, dt = linspace(0, 30, 5001, retstep=True)

# theta1 = np.cos(ts)
# theta2 = np.cos(ts + np.pi/8)
# qstart = (np.pi / 4, theta1[0], theta2[0])

x0 = np.hstack((qstart, ustart))
```

```
# numerically integrate
now = time.time()
y1 = odeint(rhs_partial, x0, ts, args=(None,), full_output=False)
y1 = y1.T
print('Elapsed time: {0:.2f} sec'.format(time.time() - now))
```

    Elapsed time: 1.24 sec

```
np.diff(y1[0]) / dt
```

    array([ 2.0090272 ,  2.02728354,  2.0458476 , ...,  3.48340694,
            3.48886136,  3.49414025])

```
u0 = y1[0]
dq0 = np.gradient(y1[0]) / dt
abs_diff = (u0 - dq0) / dq0
```

```
np.rad2deg((u0 - dq0).max())
```

```
fig, ax = plt.subplots()
ax.plot(abs_diff)
```

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

```
fig, (ax1, ax2) = plt.subplots(2, 1, sharex=True, figsize=(6, 6))

ax1.plot(y1[0])
ax1.plot(y1[1])
ax1.plot(y1[2])

ax2.plot(y1[5])
ax2.plot(y1[6])
ax2.plot(y1[7])

# ax1.plot(y2[0])
# ax1.plot(y2[1])
# ax1.plot(y2[2])

# ax2.plot(y2[3])
# ax2.plot(y2[4])
# ax2.plot(y2[5])

# ax1.set_ylim(-15, 15)
# ax2.set_ylim(-50, 50)

ax2.set_xlabel('iteration')
ax1.set_ylabel('q', rotation='horizontal')
ax2.set_ylabel('u', rotation='horizontal')

fig.tight_layout()

#display(fig)
```

```
#%matplotlib inline

def plot_chain(ii=0, hst=100):

    fig, ax = plt.subplots(1, 1, sharex=True, sharey=True, figsize=(9, 9))

    ax.axis('image')
    ax.set_xlim(-.8, 2.5)
    ax.set_ylim(-4, 2.5)
    ax.set_ylim(0, 2.5)

    fig.tight_layout()

    time_text = ax.text(0.05, 0.9, '{0:.3} sec'.format(ts[ii]), transform=ax.transAxes)

    ellipse_params = dict(alpha=.5, edgecolor='None', color='green')
    e0 = Ellipse(xy=c0[ii], width=ws, height=1.05 * ls, angle=dg0[ii] - 90, **ellipse_params)
    e1 = Ellipse(xy=c1[ii], width=ws, height=1.05 * ls, angle=dg1[ii] - 90, **ellipse_params)
    e2 = Ellipse(xy=c2[ii], width=ws, height=1.05 * ls, angle=dg2[ii] - 90, **ellipse_params)
    [ax.add_patch(e) for e in [e0, e1, e2]]

    st = ii - hst
    #st if st > 0 else 0
    if st < 0:
        st = 0

    ax.plot(j1[st:ii, 0], j1[st:ii, 1], 'b--')
    ax.plot(j2[st:ii, 0], j2[st:ii, 1], 'g--')
    ax.plot(j3[st:ii, 0], j3[st:ii, 1], 'r--')

    ax.plot(c0[st:ii, 0], c0[st:ii, 1], 'b')
    ax.plot(c1[st:ii, 0], c1[st:ii, 1], 'g')
    ax.plot(c2[st:ii, 0], c2[st:ii, 1], 'r')

    ax.plot(com[st:ii, 0], com[st:ii, 1], 'k-', lw=2, alpha=.9)
```

```
plt.close('all')
```

```
# organize the data
th0, th1, th2 = y1[:3]
th1 = th1 + th0
th2 = th2 + th1

dg0, dg1, dg2 = np.rad2deg(y1[:3])
dg1 = dg1 + dg0
dg2 = dg2 + dg1

j0 = np.c_[0, 0]
j0 = y1[3:5].T
j1 = j0 + np.c_[ls   * np.cos(th0), ls   * np.sin(th0)]
j2 = j1 + np.c_[ls   * np.cos(th1), ls   * np.sin(th1)]
j3 = j2 + np.c_[ls   * np.cos(th2), ls   * np.sin(th2)]
c0 = j0 + np.c_[ls/2 * np.cos(th0), ls/2 * np.sin(th0)]
c1 = j1 + np.c_[ls/2 * np.cos(th1), ls/2 * np.sin(th1)]
c2 = j2 + np.c_[ls/2 * np.cos(th2), ls/2 * np.sin(th2)]

# center of mass
mms = np.array([ms, ms, ms])
cx = np.c_[c0[:, 0], c1[:, 0], c2[:, 0]]
cy = np.c_[c0[:, 1], c1[:, 1], c2[:, 1]]
com = np.c_[(cx * mms).sum(axis=1), (cy * mms).sum(axis=1)] / mms.sum()

# make it interactive!
interactive(plot_chain, ii=(0, len(y1.T)-1), hst=(0, 500, 10))
```

```
fig, ax = plt.subplots()

ax.plot(j0[:, 0], j0[:, 1], 'r')
ax.plot(j3[:, 0], j3[:, 1], 'g')
ax.plot(com[:, 0], com[:, 1], 'k')
```

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

```
M = 3 * ms
1 / M * np.sum()
```

```
fig, ax = plt.subplots(1, 1, sharex=True, sharey=True, figsize=(6, 6))

fig.tight_layout()

# display the current time
time_text = ax.text(0.05, 0.9, 'time here', transform=ax.transAxes)

# create a rectangular cart
ell = [Ellipse(xy=(0, 0), width=.05*ls, height=1.05 * ls, angle=100 * np.random.random(),
                    alpha=.5, edgecolor='None', color='green')
            for i in range(n)]
[ax.add_patch(ellis) for ellis in ell]

lim = 3 * ls
ax.set_xlim(-lim, lim)
ax.set_ylim(-lim, lim)

th0, th1, th2 = y1[:3]
dg0, dg1, dg2 = np.rad2deg(y1[:3])

j0 = np.c_[0, 0]
c0 = j0 + np.c_[ls/2 * np.cos(th0), ls/2 * np.sin(th0)]

j1 = j0 + np.c_[ls   * np.cos(th0), ls   * np.sin(th0)]
c1 = j1 + np.c_[ls/2 * np.cos(th1), ls/2 * np.sin(th1)]

j2 = j1 + np.c_[ls   * np.cos(th1), ls   * np.sin(th1)]
c2 = j2 + np.c_[ls/2 * np.cos(th2), ls/2 * np.sin(th2)]

j3 = j2 + np.c_[ls   * np.cos(th2), ls   * np.sin(th2)]

ii = 100

ell[0].center = c0[ii]
ell[1].center = c1[ii]
ell[2].center = c2[ii]

ell[0].angle = dg0[ii] - 90
ell[1].angle = dg1[ii] - 90
ell[2].angle = dg2[ii] - 90

ax.axis('image')
ax.set_xlim(-.7, .7)
ax.set_ylim(-.7, .7)

ax.plot(j1[:ii, 0], j1[:ii, 1], 'b--')
ax.plot(j2[:ii, 0], j2[:ii, 1], 'g--')
ax.plot(j3[:ii, 0], j3[:ii, 1], 'r--')

ax.plot(c0[:ii, 0], c0[:ii, 1], 'b')
ax.plot(c1[:ii, 0], c1[:ii, 1], 'g')
ax.plot(c2[:ii, 0], c2[:ii, 1], 'r')
```

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

```
-dg2[:20] + 90
```

    array([ 0.        , -0.05208   , -0.09370436, -0.12483333, -0.14542379,
           -0.15542785, -0.15479371, -0.14346486, -0.12138054, -0.08847643,
           -0.0446831 ,  0.01007321,  0.0758705 ,  0.15279123,  0.24092226,
            0.34035504,  0.45118562,  0.5735147 ,  0.70744765,  0.85309458])

```
fig, ax = plt.subplots(figsize=(7, 7))
ax.plot(j3[:, 0], j3[:, 1], alpha=.4)
ax.plot(j2[:, 0], j2[:, 1], alpha=.7)
ax.plot(j1[:, 0], j1[:, 1], alpha=.7)
ax.plot(j0[:, 0], j0[:, 1], alpha=.7)

ax.axis('equal')

fig.tight_layout()
```

```
    # the number of pendulum bobs
    numpoints = states.shape[1] / 2

    # first set up the figure, the axis, and the plot elements we want to animate
    fig = plt.figure()

    # some dimesions
    cart_width = 0.4
    cart_height = 0.2

    # set the limits based on the motion
    xmin = around(states[:, 0].min() - cart_width / 2.0, 1)
    xmax = around(states[:, 0].max() + cart_width / 2.0, 1)

    # create the axes
    ax = plt.axes(xlim=(xmin, xmax), ylim=(-1.1, 1.1), aspect='equal')

    # display the current time
    time_text = ax.text(0.04, 0.9, '', transform=ax.transAxes)

    # create a rectangular cart
    rect = Rectangle([states[0, 0] - cart_width / 2.0, -cart_height / 2],
        cart_width, cart_height, fill=True, color='red', ec='black')
    ax.add_patch(rect)

    # blank line for the pendulum
    line, = ax.plot([], [], lw=2, marker='o', markersize=6)

    # initialization function: plot the background of each frame
    def init():
        time_text.set_text('')
        rect.set_xy((0.0, 0.0))
        line.set_data([], [])
        return time_text, rect, line,

    # animation function: update the objects
    def animate(i):
        time_text.set_text('time = {:2.2f}'.format(t[i]))
        rect.set_xy((states[i, 0] - cart_width / 2.0, -cart_height / 2))
        x = hstack((states[i, 0], zeros((numpoints - 1))))
        y = zeros((numpoints))
        for j in arange(1, numpoints):
            x[j] = x[j - 1] + length * cos(states[i, j])
            y[j] = y[j - 1] + length * sin(states[i, j])
        line.set_data(x, y)
        return time_text, rect, line,

    # call the animator function
    anim = animation.FuncAnimation(fig, animate, frames=len(t), init_func=init,
            interval=t[-1] / len(t) * 1000, blit=True, repeat=False)

    # save the animation if a filename is given
    if filename is not None:
        anim.save(filename, fps=30, codec='libx264')

##############

from matplotlib import animation
ani = animation.FuncAnimation(fig, updatefig, frames=1000, interval=75,
                              repeat=True, blit=True)

# video save information
metadata = dict(author='Isaac J. Yeaton',
                description='Standard and Otsu threholded images.')
Writer = animation.writers['avconv']
writer = Writer(fps=15)  # , extra_args=['-vcodec', 'libx264'], metadata=metadata)

if True:
    #ani.save('/home/isaac/Desktop/tmp1.mp4', writer=writer)
    ani.save('/home/isaac/Desktop/processed_shadowgraph_fields.mp4',
             writer=writer)
```

```

```

```
# check that the partial evaulation works
xx = x0
#xx = y.T[696]
arguments = np.hstack((param_vals, xx))

mm = M_func(*arguments)
ff = F_func(*arguments)

# this does not work...
# aargs = dict(zip(param_names, dummy_symbols, arguments))
# mm = M_func(*aargs)
# ff = F_func(*aargs)

print solve(mm, ff).flatten()
print lstsq(mm, ff)[0].flatten()

#part_args = dict(zip(param_names, param_vals))

print solve(mfunc(*xx), ffunc(*xx)).flatten()
```

    [  0.           0.           0.         -58.78688199  88.03408696
     -48.64785093]
    [  0.           0.           0.         -58.78688199  88.03408696
     -48.64785093]
    [  0.           0.           0.         -58.78688199  88.03408696
     -48.64785093]

```
frame0 = I.orientnew('frame0', 'Axis', [q[0], I.z])

mc0 = O.locatenew('mc0', q[1] * frame0.x)
mc0.v2pt_theory(O, I, frame0)

in0 = me.inertia(frame0, 0, 0, Izz[0])
body0 = me.RigidBody('body0', mc0, frame0, m[0], (in0, mc0))

frame1 = frame0.orientnew('frame1', 'Axis', [q[2], I.z])
frame2 = frame1.orientnew('frame2', 'Axis', [q[3], I.z])
frame1.set_ang_vel(I, u[2] * I.z)
frame2.set_ang_vel(I, u[3] * I.z)

in1 = me.inertia(frame1, 0, 0, Izz[1])
in2 = me.inertia(frame2, 0, 0, Izz[2])

# mc1 = mc0.locatenew('mc1', l[0] * frame0.x +
#                            l[1] * sym.cos(q[2]) * frame1.x +
#                            l[1] * sym.sin(q[2]) * frame1.y)
# mc2 = mc1.locatenew('mc2', l[1] * frame1.x +
#                            l[2] * sym.cos(q[3]) * frame2.x +
#                            l[2] * sym.sin(q[3]) * frame2.y)

# after looking at the n-pendulum example
mc1 = mc0.locatenew('mc1', l[0] * frame0.x + l[1] * frame1.x)
mc2 = mc1.locatenew('mc2', l[1] * frame1.x + l[2] * frame2.x)
mc1.v2pt_theory(mc0, I, frame1)
mc2.v2pt_theory(mc1, I, frame2)

body1 = me.RigidBody('body1', mc1, frame1, m[1], (in1, mc1))
body2 = me.RigidBody('body2', mc2, frame2, m[2], (in2, mc2))

# each one might need four entries...
frames = [I, frame0, frame1, frame2]
points = [O, mc0, mc1, mc2]
bodies = [body0, body1, body2]
forces = [0, 0, 0]
kindiffs = [qq.diff(t) - uu for qq, uu in zip(q, u)]

# need to figure out forces and how to setup me.KanesMethod

# frames = [I]                              # List to hold the n + 1 frames
# points = [P0]                             # List to hold the n + 1 points
# particles = [Pa0]                         # List to hold the n + 1 particles
# forces = [(P0, f * I.x - m[0] * g * I.y)] # List to hold the n + 1 applied forces, including the input force, f
# kindiffs = [q[0].diff(t) - u[0]]          # List to hold kinematic ODE's
# for i in range(n):
#     Bi = I.orientnew('B' + str(i), 'Axis', [q[i + 1], I.z])   # Create a new frame
#     Bi.set_ang_vel(I, u[i + 1] * I.z)                         # Set angular velocity
#     frames.append(Bi)                                         # Add it to the frames list

#     Pi = points[-1].locatenew('P' + str(i + 1), l[i] * Bi.x)  # Create a new point
#     Pi.v2pt_theory(points[-1], I, Bi)                         # Set the velocity
#     points.append(Pi)                                         # Add it to the points list

#     Pai = Particle('Pa' + str(i + 1), Pi, m[i + 1])           # Create a new particle
#     particles.append(Pai)                                     # Add it to the particles list

#     forces.append((Pi, -m[i + 1] * g * I.y))                  # Set the force applied at the point

#     kindiffs.append(q[i + 1].diff(t) - u[i + 1])              # Define the kinematic ODE:  dq_i / dt - u_i = 0
```


# 基于MindQuantum的Grover搜索算法

[](https://gitee.com/mindspore/docs/blob/master/docs/mindquantum/docs/source_zh_cn/grover_search_algorithm.ipynb)

## 概述

如果你听过量子计算，那么你一定听说过Grover搜索算法。1996年，Lov Grover \[1\] 提出了Grover搜索算法，它是一种利用量子状态的叠加性进行并行计算并实现加速的算法。Grover搜索算法被公认为是继Shor算法后的第二大量子算法，也是第一个被完整的实验实现的量子算法，它解决的是无序数据库搜索问题。1997年，Bennett \[2\] 等人证明，对于非结构化的量子搜索问题，至少需要$\Omega(\sqrt{N})$次量子查询，因此Grover搜索算法对于该问题是渐进意义下的最优算法。

无序数据库搜索问题（Unordered  Database Search problem）就是从一个海量元素的无序数据库中，找到某些满足要求的元素。由于数据库中元素的数量是巨大的且这些元素是无序排列的，所以，要验证给定的元素是否满足要求很容易，但反过来，要找到这些元素却不是一件容易的事。

求解无序数据库搜索问题（不妨假设只有一个目标搜索数据），经典算法所需的时间复杂度为$\mathcal{O}(N)$，而Grover搜索算法所需的时间复杂度仅为$\mathcal{O}(\sqrt{N})$，相比经典算法具有平方加速，展示了量子计算的强大性能。此外，Grover搜索算法中用到的振幅扩大技巧，对许多启发式的经典搜索算法可以实现加速，因而具有广泛的应用。

本文档将会介绍Grover搜索算法的基本原理，以及通过两个具体的小例子来展示如何利用MindQuantum实现该算法。

## 问题描述

我们需要在一组无序的$N$元素集合（数据库）中进行搜索。将数据库中的元素与索引（从$0$到$N-1$之间的整数）建立一一对应，我们关注于搜索这些元素的索引。考虑将该搜索问题表示为一个关于输入$x$的函数$f(x)$，其中$x$为$0$到$N-1$之间的整数。那么，函数$f$定义为：

$$
\begin{equation}
f(x)=\begin{cases}0,x\neq x_{target}\\\\
1,x=x_{target}
\end{cases}
\end{equation}.
$$

不失一般性，假设$N=2^n$，那么在量子系统中，索引以量子态$|0\rangle,|1\rangle,...,|N-1\rangle$（或$|00...0\rangle,|00...1\rangle,...,|11...1\rangle$）表示，也即我们可以使用$n$个量子比特存储这些索引。

同时假设搜索问题只有一个目标态$|\omega\rangle$。Grover搜索算法的目标就是以极大的概率将$|\omega\rangle$搜索出来。

## Grover搜索算法的基本原理

Grover搜索算法的基本原理：首先通过`Hadamard`门产生均匀叠加态，然后反复调用Grover迭代（或称为$G$算子），以放大目标项的概率振幅同时抑制非目标项的概率振幅（该方法称之为振幅放大），最后对末态进行测量，那么就能以极大的概率得到目标态$|\omega\rangle$。

Grover搜索算法主要包括以下步骤：

- Step 1：数据库初始化

  对$|0\rangle^{\otimes n}$执行$H^{\otimes n}$操作，使得数据库被初始为一个均匀叠加态，即

  $$
  |\psi_0\rangle=H^{\otimes n}|0\rangle^{\otimes n}=\frac{1}{\sqrt{N}}\sum_{i=0}^{N-1}|i\rangle.
  $$

- Step 2：Grover迭代

  Grover迭代又可以分解为四步：

  1. 执行Oracle算子$U_{\omega}$，翻转目标态$|\omega \rangle$的相位

    为了将需要寻找的数据和其它的数据区别开，最简单的方法就是翻转目标态的相位（增加一个负号），此时我们需要构造一个Oracle算子$U_{\omega}$，其作用如下：

    $$
    \begin{equation}
    U_{\omega}|x\rangle=\begin{cases}
    &|x\rangle,x\neq \omega&\\\\
    -&|x\rangle,x=\omega&
    \end{cases}
    \end{equation}.
    $$

    由于当$x=\omega$时，$f(\omega)=1$，那么$U_{\omega}$的作用还可以表示成：

    $$
    U_{\omega}|x\rangle=(-1)^{f(x)}|x\rangle,
    $$

    其矩阵表达式为

    $$
    \begin{equation}
    U_{\omega}=
    \left[
    \begin{array}{ccc}
         (-1)^{f(0)} & 0           & \dots  & 0            \\\\
         0           & (-1)^{f(1)} & \dots  & 0            \\\\
         \vdots      & \vdots      & \ddots & \vdots       \\\\
         0           & 0           & \dots  & (-1)^{f(N-1)}
    \end{array}
    \right]
    \end{equation}.
    $$

  2. 执行$H^{\otimes n}$操作

    对$n$位量子比特执行$H^{\otimes n}$操作。

  3. 执行条件相移算子$P$

    条件相移算子$P$能使$|0\rangle$态以外的每个态的相位都翻转，其作用如下：

    $$
    \begin{equation}
    P|x\rangle=\begin{cases}&|0\rangle,x= 0&\\\\
    -&|x\rangle,x\neq0&
    \end{cases}
    \end{equation}.
    $$

    其矩阵表达式为

    $$
    \begin{equation}
    P = 2(|0\rangle\langle0|)^{\otimes n} - I_n =
    \left[
    \begin{array}{ccc}
         1      & 0      & \dots  & 0            \\\\
         0      & -1     & \dots  & 0            \\\\
         \vdots & \vdots & \ddots & \vdots       \\\\
         0      & 0      & \dots  & -1
    \end{array}
    \right]
    \end{equation}.
    $$

  4. 再次执行$H^{\otimes n}$操作

  至此，完整的$G$算子可以表示为

  $$
  G = H^{\otimes n} [2(|0\rangle\langle0|)^{\otimes n} - I_n] H^{\otimes n} U_{\omega}.
  $$

  注意：$G$算子需要迭代的次数为

  $$
  r = \left[ \frac{\pi}{4} \sqrt{\frac{N}{M}} \right] \sim O(\sqrt{N}),
  $$

  其中，M表示目标态的个数。

- Step 3：测量

  对末态进行$\\{|0\rangle,|1\rangle\\}$基测量，就能以极大的概率得到目标态$|\omega \rangle$。

Grover搜索算法的完整量子线路模型如下所示：

## 构造翻转量子比特相位的酉算子

通过上述介绍，我们发现，Grover搜索算法中最关键的部分就是存在可以翻转量子比特相位的酉算子，Oracle算子$U_{\omega}$可以翻转目标态的相位，条件相移算子$P$可以翻转$|0\rangle$态以外的每个态的相位。

接下来，我们将构造可以翻转某一位量子比特相位的酉算子，定义如下：

```python
def bitphaseflip_operator(phase_inversion_qubit, n_qubits):   # 定义可以翻转某一位量子比特相位的函数
    s = [1 for i in range(1 << n_qubits)]
    for i in phase_inversion_qubit:
        s[i] = -1
    if s[0] == -1:
        for i in range(len(s)):
            s[i] = -1 * s[i]
    circuit = Circuit()
    length = len(s)
    cz = []
    for i in range(length):
        if s[i] == -1:
            cz.append([])
            current = i
            t = 0
            while current != 0:
                if (current & 1) == 1:
                    cz[-1].append(t)
                t += 1
                current = current >> 1
            for j in range(i + 1, length):
                if i & j == i:
                    s[j] = -1 * s[j]
    for i in cz:
        if i:
            if len(i) > 1:
                circuit += Z.on(i[-1], i[:-1])
            else:
                circuit += Z.on(i[0])

    return circuit
```

现在， `bitphaseflip_operator()` 函数就可以实现翻转某一位量子比特的相位，只需要输入需要翻转相位的目标量子态和量子比特总数即可。

举个例子，我们现在生成3量子比特的均匀叠加态，运行如下代码：

```python
# pylint: disable=W0104
from mindquantum import Circuit, UN, H, Z
from mindquantum.simulator import Simulator

n_qubits = 3                                 # 设定量子比特数为3
sim = Simulator('projectq', n_qubits)        # 使用projectq模拟器，命名为sim

circuit = Circuit()                          # 初始化量子线路，命名为circuit
circuit += UN(H, n_qubits)                   # 每位量子比特上执行H门操作

sim.apply_circuit(circuit)                   # 通过模拟器sim运行搭建好的量子线路circuit

circuit                                      # 打印此时的量子线路circuit
```

q0: ──H──

q1: ──H──

q2: ──H──

```python
print(sim.get_qs(True))                      # 打印模拟器sim中运行量子线路circuit后的末态
```

    √2/4¦000⟩
    √2/4¦001⟩
    √2/4¦010⟩
    √2/4¦011⟩
    √2/4¦100⟩
    √2/4¦101⟩
    √2/4¦110⟩
    √2/4¦111⟩

从运行的结果看到此时的量子线路，以及我们成功生成了3量子比特的均匀叠加态。

假设我们需要翻转$|4\rangle$态的相位，只需调用我们定义好的`bitphaseflip_operator()`函数即可，运行如下代码：

```python
# pylint: disable=W0104
sim.reset()                                                      # 重置模拟器sim维护好的量子态，使得初始化的量子态为|000>

phase_inversion_qubit = [4]                                      # 翻转|4>态的相位
operator = bitphaseflip_operator(phase_inversion_qubit, n_qubits)# 调用我们定义好的bitphaseflip_operator()函数

circuit += operator                                              # 在量子线路circuit中添加翻转|4>态的相位所需的量子门

sim.apply_circuit(circuit)                                       # 通过模拟器sim再次运行搭建好的量子线路circuit

circuit                                                          # 打印此时的量子线路circuit
```

q0: ──H─────────●─────────●──
                │         │
q1: ──H─────────┼────●────●──
                │    │    │
q2: ──H────Z────Z────Z────Z──

```python
print(sim.get_qs(True))                                          # 打印模拟器sim中运行量子线路circuit后的末态
```

    √2/4¦000⟩
    √2/4¦001⟩
    √2/4¦010⟩
    √2/4¦011⟩
    -√2/4¦100⟩
    √2/4¦101⟩
    √2/4¦110⟩
    √2/4¦111⟩

从运行的结果看到此时的量子线路，以及$|100\rangle$的相位翻转为-1，运行如下代码：

```python
print(int('100', 2))
```

    4

从运行的结果看到，发生相位翻转的$|100\rangle$态即为我们希望相位翻转的$|4\rangle$态。

假设我们需要翻转除$|0\rangle$态以外的每个态的相位，运行如下代码：

```python
# pylint: disable=W0104
n_qubits = 3                                                     # 设定量子比特数为3
sim1 = Simulator('projectq', n_qubits)                           # 使用projectq模拟器，命名为sim1

operator1 = bitphaseflip_operator([i for i in range(1, pow(2, n_qubits))], n_qubits) # 调用我们定义好的bitphaseflip_operator()函数，翻转除|0>态以外的每个态的相位，命名为operator1

circuit1 = Circuit()                                             # 初始化量子线路，命名为circuit1
circuit1 += UN(H, n_qubits)                                      # 每位量子比特上执行H门操作
circuit1 += operator1                                            # 在量子线路circuit1中添加翻转除|0>态以外的每个态的相位所需的量子门

sim1.apply_circuit(circuit1)                                     # 通过模拟器sim1运行搭建好的量子线路circuit1

circuit1                                                         # 打印此时的量子线路circuit1
```

q0: ──H────Z────●────●─────────●──
                │    │         │
q1: ──H────Z────Z────┼────●────●──
                     │    │    │
q2: ──H────Z─────────Z────Z────Z──

```python
print(sim1.get_qs(True))                                         # 打印模拟器sim1中运行量子线路circuit1后的末态
```

    √2/4¦000⟩
    -√2/4¦001⟩
    -√2/4¦010⟩
    -√2/4¦011⟩
    -√2/4¦100⟩
    -√2/4¦101⟩
    -√2/4¦110⟩
    -√2/4¦111⟩

从运行的结果看到此时的量子线路，以及我们成功翻转除$|0\rangle$态以外的每个态的相位。

也就是说，我们定义的函数`bitphaseflip_operator()`可以实现Grover搜素算法中的Oracle算子$U_{\omega}$和条件相移算子$P$。

## 利用MindQuantum实现Grover搜素算法实例

### 实例1：$n=3$，$|\omega\rangle=|2\rangle$（单目标）

首先，我们需要定义$G$算子，运行如下代码：

```python
def G(phase_inversion_qubit, n_qubits):           # 定义Grover搜索算法中的G算子
    operator = bitphaseflip_operator(phase_inversion_qubit, n_qubits)
    operator += UN(H, n_qubits)
    operator += bitphaseflip_operator([i for i in range(1, pow(2, n_qubits))], n_qubits)
    operator += UN(H, n_qubits)
    return operator
```

然后，我们根据Grover搜索算法的量子线路模型在MindQuantum中搭建对应的量子线路：

```python
# pylint: disable=W0104
from numpy import pi, sqrt

n_qubits = 3                                      # 设定量子比特数为3
phase_inversion_qubit = [2]                       # 设定需要翻转相位的目标态，在这里翻转|2>态的相位

N = 2 ** (n_qubits)                               # 计算出数据库中元素的总个数
M = len(phase_inversion_qubit)                    # 计算出目标态的总个数

r = int(pi / 4 * sqrt(N / M))                     # 设定G算子迭代次数为r

sim2 = Simulator('projectq', n_qubits)            # 使用projectq模拟器，命名为sim2

circuit2 = Circuit()                              # 初始化量子线路，命名为circuit2
circuit2 += UN(H, n_qubits)                       # 每位量子比特上执行H门操作

for i in range(r):                                # 循环执行G算子r次
    circuit2 += G(phase_inversion_qubit, n_qubits)

sim2.apply_circuit(circuit2)                      # 通过模拟器sim2运行搭建好的量子线路circuit2

circuit2                                          # 打印此时的量子线路circuit2
```

q0: ──H─────────●─────────●────H────Z────●────●─────────●────H─────────●─────────●────H────Z────●────●─────────●────H──
                │         │              │    │         │              │         │              │    │         │
q1: ──H────Z────Z────●────●────H────Z────Z────┼────●────●────H────Z────Z────●────●────H────Z────Z────┼────●────●────H──
                     │    │                   │    │    │                   │    │                   │    │    │
q2: ──H──────────────Z────Z────H────Z─────────Z────Z────Z────H──────────────Z────Z────H────Z─────────Z────Z────Z────H──

```python
print(sim2.get_qs(True))                          # 打印模拟器sim2中运行量子线路circuit2后的末态
```

    -√2/16¦000⟩
    -√2/16¦001⟩
    0.9722718241315036¦010⟩
    -√2/16¦011⟩
    -√2/16¦100⟩
    -√2/16¦101⟩
    -√2/16¦110⟩
    -√2/16¦111⟩

从运行的结果看到，$|010\rangle$态的振幅为0.9722718241315036，相比于其它的量子态，这是极大的振幅，也就是说，若我们测量此时的状态，将会以极大的概率得到目标态$|010\rangle$，运行如下代码进行测量：

```python
# pylint: disable=W0104
from mindquantum import Measure

sim2.reset()                                      # 重置模拟器sim2维护好的量子态，使得初始化的量子态为|000>

circuit2 += UN(Measure(), circuit2.n_qubits)      # 对量子线路circuit2中的每一位量子比特添加测量门

result = sim2.sampling(circuit2, shots=1000)      # 通过模拟器sim2对量子线路circuit2进行1000次的采样
result                                            # 打印采样结果
```

shots: 1000
Keys: q2 q1 q0│0.00     0.2         0.4         0.6         0.8         1.0
──────────────┼───────────┴───────────┴───────────┴───────────┴───────────┴
           000│▒
              │
           001│▒
              │
           010│▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
              │
           011│▒
              │
           100│▒
              │
           101│▒
              │
           110│▒
              │
           111│▒
              │
{'000': 9, '001': 10, '010': 947, '011': 4, '100': 8, '101': 8, '110': 8, '111': 6}

从运行的结果看到，1000次采样中有947次的采样结果为`010`，将其转化为10进制数，运行如下代码：

```python
print(int('010', 2))
```

    2

从运行的结果看到，我们成功地搜索出$|2\rangle$态。

### 实例2：$n=5$，$|\omega\rangle=|5\rangle$和$|11\rangle$（多目标）

实例1中实现的是单目标搜索，现在我们尝试实现多目标搜索。首先，$G$算子已经定义好了，我们只需设定量子比特数和需要翻转相位的目标态，然后搭建对应的量子线路即可，运行如下代码：

```python
# pylint: disable=W0104
n_qubits = 5                                      # 设定量子比特数为5
phase_inversion_qubit = [5, 11]                   # 设定需要翻转相位的目标态，在这里翻转|5>态和|11>态的相位

N = 2 ** (n_qubits)                               # 计算出数据库中元素的总个数
M = len(phase_inversion_qubit)                    # 计算出目标态的总个数

r = int(pi / 4 * sqrt(N / M))                     # 设定G算子迭代次数为r

sim3 = Simulator('projectq', n_qubits)            # 使用projectq模拟器，命名为sim3

circuit3 = Circuit()                              # 初始化量子线路，命名为circuit3
circuit3 += UN(H, n_qubits)                       # 每位量子比特上执行H门操作

for i in range(r):                                # 循环执行G算子r次
    circuit3 += G(phase_inversion_qubit, n_qubits)

sim3.apply_circuit(circuit3)                      # 通过模拟器sim3运行搭建好的量子线路circuit3

circuit3                                          # 打印此时的量子线路circuit3
```

q0: ──H────●────●────●────●────●────●────●────●────H────Z────●────●─────────●────●─────────●─────────●─────────●────●─────────●─────────●─────────●─────────●─────────●─────────●─────────●────H────●────●────●────●────●────●────●────●────H────Z────●────●─────────●────●─────────●─────────●─────────●────●─────────●─────────●─────────●─────────●─────────●─────────●─────────●────H────●────●────●────●────●────●────●────●────H────Z────●────●─────────●────●─────────●─────────●─────────●────●─────────●─────────●─────────●─────────●─────────●─────────●─────────●────H──
           │    │    │    │    │    │    │    │              │    │         │    │         │         │         │    │         │         │         │         │         │         │         │         │    │    │    │    │    │    │    │              │    │         │    │         │         │         │    │         │         │         │         │         │         │         │         │    │    │    │    │    │    │    │              │    │         │    │         │         │         │    │         │         │         │         │         │         │         │
q1: ──H────┼────●────●────┼────┼────●────●────┼────H────Z────Z────┼────●────●────┼────●────●─────────┼────●────●────┼────●────●─────────┼────●────●─────────┼────●────●─────────┼────●────●────H────┼────●────●────┼────┼────●────●────┼────H────Z────Z────┼────●────●────┼────●────●─────────┼────●────●────┼────●────●─────────┼────●────●─────────┼────●────●─────────┼────●────●────H────┼────●────●────┼────┼────●────●────┼────H────Z────Z────┼────●────●────┼────●────●─────────┼────●────●────┼────●────●─────────┼────●────●─────────┼────●────●─────────┼────●────●────H──
           │    │    │    │    │    │    │    │                   │    │    │    │    │    │         │    │    │    │    │    │         │    │    │         │    │    │         │    │    │         │    │    │    │    │    │    │    │                   │    │    │    │    │    │         │    │    │    │    │    │         │    │    │         │    │    │         │    │    │         │    │    │    │    │    │    │    │                   │    │    │    │    │    │         │    │    │    │    │    │         │    │    │         │    │    │         │    │    │
q2: ──H────Z────Z────┼────●────●────●────┼────●────H────Z─────────Z────Z────Z────┼────┼────┼────●────●────●────●────┼────┼────┼────●────●────●────●─────────┼────┼────┼────●────●────●────●────H────Z────Z────┼────●────●────●────┼────●────H────Z─────────Z────Z────Z────┼────┼────┼────●────●────●────●────┼────┼────┼────●────●────●────●─────────┼────┼────┼────●────●────●────●────H────Z────Z────┼────●────●────●────┼────●────H────Z─────────Z────Z────Z────┼────┼────┼────●────●────●────●────┼────┼────┼────●────●────●────●─────────┼────┼────┼────●────●────●────●────H──
                     │    │    │    │    │    │                                  │    │    │    │    │    │    │    │    │    │    │    │    │    │         │    │    │    │    │    │    │                   │    │    │    │    │    │                                  │    │    │    │    │    │    │    │    │    │    │    │    │    │         │    │    │    │    │    │    │                   │    │    │    │    │    │                                  │    │    │    │    │    │    │    │    │    │    │    │    │    │         │    │    │    │    │    │    │
q3: ──H──────────────Z────Z────┼────┼────●────●────H────Z────────────────────────Z────Z────Z────Z────Z────Z────Z────┼────┼────┼────┼────┼────┼────┼────●────●────●────●────●────●────●────●────H──────────────Z────Z────┼────┼────●────●────H────Z────────────────────────Z────Z────Z────Z────Z────Z────Z────┼────┼────┼────┼────┼────┼────┼────●────●────●────●────●────●────●────●────H──────────────Z────Z────┼────┼────●────●────H────Z────────────────────────Z────Z────Z────Z────Z────Z────Z────┼────┼────┼────┼────┼────┼────┼────●────●────●────●────●────●────●────●────H──
                               │    │    │    │                                                                     │    │    │    │    │    │    │    │    │    │    │    │    │    │    │                             │    │    │    │                                                                     │    │    │    │    │    │    │    │    │    │    │    │    │    │    │                             │    │    │    │                                                                     │    │    │    │    │    │    │    │    │    │    │    │    │    │    │
q4: ──H────────────────────────Z────Z────Z────Z────H────Z───────────────────────────────────────────────────────────Z────Z────Z────Z────Z────Z────Z────Z────Z────Z────Z────Z────Z────Z────Z────H────────────────────────Z────Z────Z────Z────H────Z───────────────────────────────────────────────────────────Z────Z────Z────Z────Z────Z────Z────Z────Z────Z────Z────Z────Z────Z────Z────H────────────────────────Z────Z────Z────Z────H────Z───────────────────────────────────────────────────────────Z────Z────Z────Z────Z────Z────Z────Z────Z────Z────Z────Z────Z────Z────Z────H──

```python
print(sim3.get_qs(True))                          # 打印模拟器sim3中运行量子线路circuit3后的末态
```

    -0.03590776623212942¦00000⟩
    -0.03590776623212939¦00001⟩
    -0.03590776623212932¦00010⟩
    -0.03590776623212946¦00011⟩
    -0.03590776623212935¦00100⟩
    0.6932961018664991¦00101⟩
    -0.035907766232129434¦00110⟩
    -0.03590776623212945¦00111⟩
    -0.035907766232129434¦01000⟩
    -0.03590776623212945¦01001⟩
    -0.03590776623212935¦01010⟩
    0.6932961018664991¦01011⟩
    -0.03590776623212932¦01100⟩
    -0.03590776623212946¦01101⟩
    -0.03590776623212939¦01110⟩
    -0.03590776623212939¦01111⟩
    -0.0359077662321294¦10000⟩
    -0.03590776623212941¦10001⟩
    -0.035907766232129414¦10010⟩
    -0.035907766232129434¦10011⟩
    -0.03590776623212944¦10100⟩
    -0.035907766232129434¦10101⟩
    -0.03590776623212944¦10110⟩
    -0.035907766232129434¦10111⟩
    -0.03590776623212943¦11000⟩
    -0.035907766232129434¦11001⟩
    -0.03590776623212943¦11010⟩
    -0.035907766232129434¦11011⟩
    -0.0359077662321294¦11100⟩
    -0.035907766232129434¦11101⟩
    -0.035907766232129414¦11110⟩
    -0.03590776623212941¦11111⟩

从运行的结果看到，$|00101\rangle$和$|01011\rangle$态的振幅均为0.6932961018664989，相比于其它的量子态，这是极大的振幅，也就是说，若我们测量此时的状态，将会以极大的概率得到目标态$|00101\rangle$和$|01011\rangle$态，运行如下代码进行测量：

```python
# pylint: disable=W0104
sim3.reset()                                      # 重置模拟器sim3维护好的量子态，使得初始化的量子态为|00000>

circuit3 += UN(Measure(), circuit3.n_qubits)      # 对量子线路circuit3中的每一位量子比特添加测量门

result1 = sim3.sampling(circuit3, shots=1000)     # 通过模拟器sim3对量子线路circuit3进行1000次的采样
result1                                           # 打印采样结果
```

shots: 1000
Keys: q4 q3 q2 q1 q0│0.00   0.126       0.251       0.377       0.503       0.629
────────────────────┼───────────┴───────────┴───────────┴───────────┴───────────┴
               00000│▒
                    │
               00001│▒
                    │
               00100│▒
                    │
               00101│▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
                    │
               00110│▒
                    │
               00111│▒
                    │
               01000│▒
                    │
               01001│▒
                    │
               01010│▒
                    │
               01011│▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
                    │
               01100│▒
                    │
               01101│▒
                    │
               01111│▒
                    │
               10000│▒
                    │
               10001│▒
                    │
               10011│▒
                    │
               10100│▒
                    │
               10101│▒
                    │
               10111│▒
                    │
               11000│▒
                    │
               11011│▒
                    │
               11100│▒
                    │
               11101│▒
                    │
               11111│▒
                    │
{'00000': 2, '00001': 2, '00100': 1, '00101': 463, '00110': 2, '00111': 3, '01000': 1,
'01001': 1, '01010': 1, '01011': 503, '01100': 1, '01101': 1, '01111': 1, '10000': 1,
'10001': 1, '10011': 2, '10100': 2, '10101': 2, '10111': 1, '11000': 3, '11011': 1, '11100':
2, '11101': 2, '11111': 1}

从运行的结果看到，1000次采样中有463次的采样结果为`00101`和503次的采样结果为`01011`，将其转化为10进制数，运行如下代码：

```python
print(int('00101', 2))
print(int('01011', 2))
```

    5
    11

从运行的结果看到，我们成功地搜索出$|5\rangle$和$|11\rangle$态。

至此，我们介绍了Grover搜索算法的基本原理，以及通过两个具体的小例子来展示如何利用MindQuantum实现该算法！赶紧动手体验一下量子编程的乐趣吧！

若想查询更多关于MindQuantum的API，请点击：[https://mindspore.cn/mindquantum/](https://mindspore.cn/mindquantum/)。

### **参考文献：**

\[1\] L. K. Grover, A fast quantum mechanical algorithm for database search\[C\]// Proceedings of the twenty-eighth annual ACM symposium on Theory of computing. ACM, 1996: 212-219.

\[2\] G. Brassard, P. Hoyer, M. Mosca, et al. Quantum amplitude amplification and estimation\[J\]. Contemporary Mathematics, 2002, 305: 53-74.


## GeostatsPy: Monte Carlo Simulation for Subsurface Data Analytics in Python

### Michael Pyrcz, Associate Professor, University of Texas at Austin

#### [Twitter](https://twitter.com/geostatsguy) | [GitHub](https://github.com/GeostatsGuy) | [Website](http://michaelpyrcz.com) | [GoogleScholar](https://scholar.google.com/citations?user=QVZ20eQAAAAJ&hl=en&oi=ao) | [Book](https://www.amazon.com/Geostatistical-Reservoir-Modeling-Michael-Pyrcz/dp/0199731446) | [YouTube](https://www.youtube.com/channel/UCLqEr-xV-ceHdXXXrTId5ig)  | [LinkedIn](https://www.linkedin.com/in/michael-pyrcz-61a648a1)

### PGE 383 Exercise: Monte Carlo Simulation for Subsurface Data Analytics in Python

Here's a simple workflow, demonstration of Monte Carlo simulation for subsurface uncertainty modeling workflows. This should help you get started with building subsurface models that integrate uncertainty sources.

#### Monte Carlo Simulation

Definition: random sampling from a distribution

Procedure:

1. Model the representative distribution (CDF)
2. Draw a random value from a uniform [0,1] distribution (p-value)
3. Apply the inverse of the CDF to calculate the associated realization

In practice, Monte Carlo simulation refers to the workflow with multiple realizations drawn to buld an uncertainty model.

\begin{equation}
X^\ell = F_x(p^\ell),  \, \forall \, \ell = 1,\ldots, L
\end{equation}

where $X^\ell$ is the realization of the variable $X$ drawn from its CDF, $F_x$, with cumulative probability, p-value, $p^\ell$.

It would be trivial to apply Monte Carlo simulation to a single variable, after many realizations one would get back the original distribution. The general approach is to:

1. Model all distributions for the input, variables of interest $F_{x_1},\ldots,F_{x_m}$.
2. For each realization draw $p^\ell_{1},\ldots,p^\ell_{m}$, p-values
3. Apply the inverse of each distribution to calculate a realization of each variable, $X^\ell_j = F_{x^\ell_j}^{-1}(p^\ell_j),  \, \forall \, j = 1,\ldots$, $m$ variables.
4. Apply each set of variables for a $\ell$ realization to the transfer function to calculate the ouptput realization, $Y^\ell = F(X_1^\ell,\ldots,X_m^\ell)$.

Monte Carlo Simulation (MCS) is extremely powerful

* Possible to easily simulate uncertainty models for complicated systems
* Simulations are conducted by drawing values at random from specified uncertainty distributions for each variable
* A single realization of each variable, $X_1^\ell, X_2^\ell,\ldots,X_m^\ell$ is applied to the transfer function to calculate the realization of the variable of interest (output, decision criteria):

\begin{equation}
Y^\ell = F(X_1^\ell,\ldots,X_m^\ell), \, \forall \, \ell = 1,\ldots, L
\end{equation}

* The MCS method builds empirical uncertainty models by random sampling

Let’s take a simple example, $OIP$ is oil-in-place calculated as the product of reservoir volume, $V$, average porosity, $\overline{\phi}$, and oil saturaton, $\overline{s_o}$:

\begin{equation}
OIP^\ell = V^\ell \cdot \overline{\phi}^\ell \cdot \overline{s_o}^\ell, \, \forall \, \ell = 1,\ldots, L
\end{equation}

It would be difficult to directly calculate the OIP distribution as a combination of all these different distributions.
* The distributions could all have different forms (parametric or non-parametric)
* We use MCS to empirically work this out by sampling
* Repeat to calculate enough realizations for analysis.

How many realizations, $L$?

The answer is enough! If the MCS computational cost is low then **many** is the right answer. If too few realizations are calculated then the summary statistics and the entire CDF of the output, decision criteria may be incorrect. This is caused by fluctuations due to not enough samples (see the 'Law of Small Numbers').

The MCS method is very powerful. You can simulate output distributions that could not be calculated analytically.

#### Limitations

The MCS method above assumes:
1. **representativity** - the distribution is representative
2. **independence** - the variables are independent of eachother
3. **stationarity** - all realizations for each variable are from the same distribution

#### Objective

In the PGE 383: Stochastic Subsurface Modeling class I want to provide hands-on experience with building subsurface modeling workflows. Python provides an excellent vehicle to accomplish this. I have coded a package called GeostatsPy with GSLIB: Geostatistical Library (Deutsch and Journel, 1998) functionality that provides basic building blocks for building subsurface modeling workflows.

The objective is to remove the hurdles of subsurface modeling workflow construction by providing building blocks and sufficient examples. This is not a coding class per se, but we need the ability to 'script' workflows working with numerical methods.

#### Getting Started

Here's the steps to get setup in Python with the GeostatsPy package:

1. Install Anaconda 3 on your machine (https://www.anaconda.com/download/).
2. From Anaconda Navigator (within Anaconda3 group), go to the environment tab, click on base (root) green arrow and open a terminal.
3. In the terminal type: pip install geostatspy.
4. Open Jupyter and in the top block get started by copy and pasting the code block below from this Jupyter Notebook to start using the geostatspy functionality.

There are examples below with these functions. You can go here to see a list of the available functions, https://git.io/fh4eX, other example workflows and source code.

```python
import geostatspy.GSLIB as GSLIB          # GSLIB utilies, visualization and wrapper
import geostatspy.geostats as geostats    # GSLIB methods convert to Python
```

We will also need some standard packages. These should have been installed with Anaconda 3.

```python
import numpy as np                        # ndarrys for gridded data
import pandas as pd                       # DataFrames for tabular data
import os                                 # set working directory, run executables
import matplotlib.pyplot as plt           # for plotting
from scipy import stats                   # summary statistics
import math                               # trig etc.
import random
```

#### Set the working directory

I always like to do this so I don't lose files and to simplify subsequent read and writes (avoid including the full address each time).

```python
os.chdir("c:/PGE383")                     # set the working directory
```

#### Simple Monte Carlo Simulation Example

Let's work out the OIP uncertainty example above. Let's work with parametric distributions for average porosity, $apor$, volume, $vol$ and oil saturation, $so$. Here's some parameters for these distributions. Note, these parameters could originate from a bootstrap workflow, analogs, etc.

```python
apor_mean = 0.15; apor_stdev = 0.02       # Gaussian mean and standard deviation porosity (fraction)
vol_mu = 13.0; vol_sigma = 0.5            # LogNormal mu and sigma volume (m^3)
so_min = 0.2; so_max = 0.4                # Uniform minimum and maximum oil saturation (fraction)
L = 1000                                  # Number of MCS realizations
```

Let's set the minimum and maximum values for plotting.

```python
apor_min = 0.1; apor_max = 0.2            # average porosity min and max
vol_min = 0.0; vol_max = 4000000          # vol min and max
```

In the *NumPy* package we have handy methods for Monte Carlo simulation from parametric distributions. We can actually draw all $L$ realizations at once for each variable and store them in ndarrays (each ndarray with realizations $\ell = 1,\ldots,L$).

```python
apor = np.random.normal(apor_mean, apor_stdev, size=L) # average porosity MCS simulation L times and store in array
vol = np.random.lognormal(vol_mu, vol_sigma, size=L)   # volume ...
so = np.random.uniform(so_min, so_max, size=L)         # saturation oil
```

Let's plot the distributions of the realizations of each variable to make sure the match the form of the parametric distributions that we selected.

```python
plt.subplot(131)
GSLIB.hist_st(apor,apor_min,apor_max,log=False,cumul=False,bins=50,weights=None,xlabel="Average Porosity (fraction)",title="Average Porosity Realizations")
plt.ylim(0.0,60)

plt.subplot(132)
GSLIB.hist_st(vol,vol_min,vol_max,log=False,cumul=False,bins=50,weights=None,xlabel="Volume (m^3)",title="Average Volume Realizations")
plt.ylim(0.0,200)

plt.subplot(133)
GSLIB.hist_st(so,so_min,so_max,log=False,cumul=False,bins=50,weights=None,xlabel="Saturation Oil (fraction)",title="Average Saturation Oil Realizations")
plt.ylim(0.0,40)

plt.subplots_adjust(left=0.0, bottom=0.0, right=2.0, top=1.2, wspace=0.2, hspace=0.2)
plt.show()
```

This looks good, the shapes are Gaussian, lognormal and uniform and the central tendency and dispersion make sense given the parameters that we selected.

Now we can use broadcast methods to calculate the output realizations of $OIP$, based on this equation.

\begin{equation}
OIP^\ell = V^\ell \cdot \overline{\phi}^\ell \cdot \overline{s_o}^\ell \cdot 6.29 \quad \forall \, \ell = 1,\ldots, L
\end{equation}

where 6.26 $bbls/m^3$.

```python
OIP = apor * vol * so * 6.29

plt.subplot(111)
GSLIB.hist_st(OIP,0.0,1000000,log=False,cumul=False,bins=50,weights=None,xlabel="Reservoir OIP (bbls)",title="Reservoir OIP Realizations")
plt.ylim(0.0,175)
plt.subplots_adjust(left=0.0, bottom=0.0, right=1.0, top=1.2, wspace=0.2, hspace=0.2)
plt.show()
```

It's not a bad idea to check your calculation. Let's compare the first and last realizations of $OIP$ to the results from performing the calculation by hand.

```python
print('Check realization 0, broadcast method = ' + str(round(OIP[0],3)) + ' compare to by-hand ' + str(round(apor[0]*vol[0]*so[0]*6.29,3)))
print('Check realization L-1, broadcast method = ' + str(round(OIP[L-1],3)) + ' compare to by-hand ' + str(round(apor[L-1]*vol[L-1]*so[L-1]*6.29,3)))
```

    Check realization 0, broadcast method = 117394.232 compare to by-hand 117394.232
    Check realization L-1, broadcast method = 90446.182 compare to by-hand 90446.182

The results check out.

Now let's look at what happens if too few realizations are used!  Set the $L$ smaller, calculate 3 sets of $L$ realizations.

```python
L = 100                                                # set L small
# First set of realizations
apor = np.random.normal(apor_mean, apor_stdev, size=L) # average porosity MCS simulation L times and store in array
vol = np.random.lognormal(vol_mu, vol_sigma, size=L)   # volume ...
so = np.random.uniform(so_min, so_max, size=L)         # saturation oil
OIP1 = apor * vol * so * 6.29                          # calculate OIP

# Second set of realizations
apor = np.random.normal(apor_mean, apor_stdev, size=L) # average porosity MCS simulation L times and store in array
vol = np.random.lognormal(vol_mu, vol_sigma, size=L)   # volume ...
so = np.random.uniform(so_min, so_max, size=L)         # saturation oil
OIP2 = apor * vol * so * 6.29                          # calculate OIP

# Third set of realizations
apor = np.random.normal(apor_mean, apor_stdev, size=L) # average porosity MCS simulation L times and store in array
vol = np.random.lognormal(vol_mu, vol_sigma, size=L)   # volume ...
so = np.random.uniform(so_min, so_max, size=L)         # saturation oil
OIP3 = apor * vol * so * 6.29                          # calculate OIP
```

Let's look at the distributions, and calculate and compare the average for each of the 3 sets.

```python
plt.subplot(131)
GSLIB.hist_st(OIP1,0.0,1000000,log=False,cumul=False,bins=20,weights=None,xlabel="Reservoir OIP (bbls)",title="Reservoir OIP Realizations, Set 1")
plt.ylim(0.0,40)

plt.subplot(132)
GSLIB.hist_st(OIP2,0.0,1000000,log=False,cumul=False,bins=20,weights=None,xlabel="Reservoir OIP (bbls)",title="Reservoir OIP Realizations, Set 2")
plt.ylim(0.0,40)

plt.subplot(133)
GSLIB.hist_st(OIP3,0.0,1000000,log=False,cumul=False,bins=20,weights=None,xlabel="Reservoir OIP (bbls)",title="Reservoir OIP Realizations, Set 3")
plt.ylim(0.0,40)

plt.subplots_adjust(left=0.0, bottom=0.0, right=2.0, top=1.2, wspace=0.2, hspace=0.2)
plt.show()

print('The average OIP for large number of realizations = ' + str(round(np.average(OIP),3)))
print('The average OIP for small number of realizations = ' + str(round(np.average(OIP1),3)) + ', ' + str(round(np.average(OIP2),3)) + ',' + str(round(np.average(OIP3),3)))
```

Try setting the number of realizations, $L$, larger and you will see that the 3 sets of realizations converge, they become stable. This is why we set $L$ large.

#### Non-Parametric Monte Carlos Simulation

I have demonstrated the case of MCS with parametric distributions. Of course, we may have non-parametric distributions. In this case we are working with a list of values (typically in an 1D ndarray). For this case we simply substitute in the command:

```p
apor = np.random.choice(array_average_porosity,size=L)
```

The result is a ndarray with $L$ Monte Carlo simulations from the array 'array_average_porosity'.  This is simply $L$ random draws with replacement (so the same value may be selected again), from the list of values in the ndarray.

#### Comments

This was a basic demonstration of Monte Carlo simulation for uncertainty analysis. A lot more could be done, for example, more complicated transfer functions and a combination of non-parametric and parametric distributions. Also, one could integrate relationships between the variables (we assumed independent here).

I have other demonstrations on the basics of working with DataFrames, ndarrays, univariate statistics, plotting data, declustering, data transformations, trend modeling, multivariate analysis and many other workflows available at https://github.com/GeostatsGuy/PythonNumericalDemos and https://github.com/GeostatsGuy/GeostatsPy.

I hope this was helpful,

*Michael*

Michael Pyrcz, Ph.D., P.Eng. Associate Professor The Hildebrand Department of Petroleum and Geosystems Engineering, Bureau of Economic Geology, The Jackson School of Geosciences, The University of Texas at Austin

#### More Resources Available at: [Twitter](https://twitter.com/geostatsguy) | [GitHub](https://github.com/GeostatsGuy) | [Website](http://michaelpyrcz.com) | [GoogleScholar](https://scholar.google.com/citations?user=QVZ20eQAAAAJ&hl=en&oi=ao) | [Book](https://www.amazon.com/Geostatistical-Reservoir-Modeling-Michael-Pyrcz/dp/0199731446) | [YouTube](https://www.youtube.com/channel/UCLqEr-xV-ceHdXXXrTId5ig)  | [LinkedIn](https://www.linkedin.com/in/michael-pyrcz-61a648a1)


## Topics covered in this notebook:
1. Types of Classifiers - Generative vs. Discriminative.
2. Bayes Rule.
3. Bayes Classifiers.
4. Naive Bayes.
5. Naive Bayes vs. KNN.
6. Revisit Bayes Classifier.
7. References.

## 1. Types of Classifiers:
Given inputs **x** and classes **y** we can do classification in several ways:
1. **Discriminative Classifiers**: These classifiers estimate parameters of decision boundary/class sepratoe from labeled examples.
    1. Learn mappings directly from space of inputs X to class labels {0, 1, 2,....., K}. For example:
        1. Linear Regression as a classifier.
        2. Neural Networks.
    2. Learn p(y|x) directly. For example:
        1. Logistic Regression.
2. **Generative Classifiers**: Model the distribution of inputs characteristic of the class.
    1. These classifiers try to model p(x|y).
    2. Apply Bayesrile for classification. Thus called **Bayes Classifiers**.

```javascript
%%javascript
MathJax.Hub.Config({
    TeX: { equationNumbers: { autoNumber: "AMS" } }
});
```

    <IPython.core.display.Javascript object>

## 2. Bayes Rule:
Given **x** we want to compute class(C) probabilities using Bayes Rule:

\begin{equation}
 \large p(C \,|\, x) \,=\, \frac {p(x\,|\,C)p(C)}{p(x)}
\end{equation}

More formally:

\begin{equation}
 \large posterior \,=\, \frac {Class likelihood \,\,*\,\, prior}{Evidence}
\end{equation}

## 3. Bayes Classifier:

1. Determine if an email is spam:
    1. Can look at words like **'free','pills','money'**, etc.
    2. Find:
        1. p(money|spam).
        2. p(money|not spam).

How do we find these?

Discrete probabilities are just counts. For example,

$$ \large p(money\,|\,spam) = \frac {\large count\,\,(spam\,\,messages\,\,containing\,\,'money\,')} {\large\,count\,(spam\,\,messages)}$$

Similarly we can calculate p(money|not spam). This looks a lot like a Bayes rule.

### What makes this Bayesian?
Let's consider a spam classifier as an example.
1. We want p(spam|X):
    1. Apply Bayes rule.
    2. **p(spam|X) = p(X|spam) * p(spam)/ p(X)** where p(spam) = class prior, p(X|spam) is likelihood & p(spam|X) is posterior.
    3. Similarly we calculate p(not spam|X).
        1. We classify based on what is bigger.
        2. p(spam|X) > p(not spam|X) -> Spam!
        3. p(spam|X)
 Not spam!
    4. Y = argmax{p(C|X)} = argmax{p(X|C) * p(C)}, where p(X) can be ignored as it is independent of C.
        1. For example: 10 spam emails, 20 not spam emails:
            1. p(spam) = 1/3.
            2. p(not spam) = 2/3.

Before looking more into Bayes classifiers let's look with Naive Bayes.

## 4. Naive Bayes(NB):

Consider **p(cash|spam)**. Is it correlated with **p(money|spam)**?
Probably. But if we assume those are independent it is called '**Naive Bayes**'.

### Modeling P(X|C)

P(X|C) = P(words|C). All words are independent hence we can multiply the probabilities.

P(words|C) = P(w|C) * (1 - P(w|c)) where w is each word from the set words.

### NB Implementation - Use Gaussian Distribution.

1. We won't use full covaraince matrix since all dimensions are independent in NB.

2. Cov(i,j) = E[ (x_i - mu_i)(x_j - mu_j) ] ( =0 if x_i is independent of x_j).

3. Cov(i,i) = var(x_i) = sigma^2.
    1. This is called axis aligned elliptical covariance.
    2. Instead of DxD covariance matrix store a D sized vector.
    3. Scipy allows us to pass in either.

4. Effectively still doing:
    1. p(X|C) = p(x1|C) p(x2|C)......p(xn|C)
    2. p(X|C) = N(x1; mu1, var1-sq) N(x2; mu2, var2-sq).....N(xn; mun, varn-sq) ->multivariate gaussian.

5. Exponential slows down. Hence use log probabilities. Scipy has a function to calculate log probabilites too.
    1. Prediction = argmax{p(X|C) p(C)} --> argmax{log p(X|C) + log p(C)}.
6. Smoothing:
    1. Singular covariance problem - matrix equivalent of divison by zero.
    2. Add smoothing.
        1. MLE = transpose((X - mu)) * (X - mu)/(N-1).
        2. Smoothed MLE = transpose((X - mu)) * (X - mu)/(N-1) + a * I, where is a very small number (0.0001). -> Adds numerical
           stability.

## 5. Naive Bayes vs. KNN:

1. Concept is almost opposite of KNN.
    1. KNN: We approximate some function f(words in document) -> spam/not spam.
    2. NB : Assume data arises/ produced from the target label.
        1. Spam -> Spammy document -> model p(document|spam).

## 6. Revisit Bayes Classifier:

In section 4 where NB implementation is discussed we assumed that the off-diagonal elements in the covariance matrix is zero. If it is non-zero it is referred to as **Bayes Classifier**.

### Advantages:
1. Grounded in probability, which can be powerful.
2. Each variable is modeled explicitly, change model of p(x|C) if the result is poor.
    1. You know exactly how each variable affects results.

### Disadvantages:
1. Historically discrimative models have worked better. Ex. Deep learning.

## 7. References:
1. An Introduction to Statistical Learning Textbook by Gareth James, Daniela Witten, Trevor Hastie and Robert Tibshirani.
2. University of Michigan EECS 445 - Machine Learning Course (https://github.com/eecs445-f16/umich-eecs445-f16).

3. University of Toronto CSC 411 - Intro. to Machine Learning (http://www.cs.toronto.edu/~urtasun/courses/CSC411_Fall16/CSC411_Fall16.html).

4. Stanford CS109 - Intro. to proabability for computer scientists (https://web.stanford.edu/class/archive/cs/cs109/cs109.1166/).

5. Few online courses on Udemy, Coursera etc.