# The Effective One-Body Cartesian Hamiltonian

### Authors: Tyler Knowles & Zach Etienne

**The Hamiltonian expression in this module has been validated against a trusted code: the Mathematica expressions for the LALSuite SEOBNRv3_opt code.**

This module documents and splits up the reduced spinning effective one-body Hamiltonian derived in [Barausse and Buonanno (2010)](https://arxiv.org/abs/0912.3517) and [Pan, Buonanno, Buchman, et. al (2010)](https://arxiv.org/abs/0912.3466) for use in efficient gravitational waveform generation software.  This Cartesian formulation is used to generate gravitational waveforms in LALSuite's SEOBNRv3 approximant.

* Something to be checked: should the constants (like k0,...,k5) be symbols since we compute them?  Also, why are we *recomputing* them?  They're computed elsewhere and available in an array.

```python
# import sympy as sp
# x,y,z,s1x,s1y,s1z,s2x,s2y,s2z = sp.symbols("x y z s1x s1y s1z s2x s2y s2z")
# blah = x+y
# g = sp.sin(blah)
# print(sp.mathematica_code(g))
```

```python
import sympy as sp
m1,m2,x,y,z,px,py,pz,s1x,s1y,s1z,s2x,s2y,s2z,eta = sp.symbols("m1 m2 x y z px py pz s1x s1y s1z s2x s2y s2z eta",real=True)
c0k2,c1k2,c0k3,c1k3,c0k4,c1k4,c2k4,c0k5,c1k5,c2k5 = sp.symbols("c0k2 c1k2 c0k3 c1k3 c0k4 c1k4 c2k4 c0k5 c1k5 c2k5",real=True)
KK,k5l,b3,bb3,d1,d1v2,dheffSS,dheffSSv2 = sp.symbols("KK k5l b3 bb3 d1 d1v2 dheffSS dheffSSv2",real=True)
tortoise,copysignresult = sp.symbols("tortoise copysignresult",real=True)

sigmaKerr0 = s1x + s2x
sigmaKerr1 = s1y + s2y
sigmaKerr2 = s1z + s2z
invm1m2 = 1/(m1*m2)
m2overm1 = m2*m2*invm1m2
m1overm2 = m1*m1*invm1m2
sigmaStar0 = (m2overm1)*s1x + (m1overm2)*s2x
sigmaStar1 = (m2overm1)*s1y + (m1overm2)*s2y
sigmaStar2 = (m2overm1)*s1z + (m1overm2)*s2z
s1dots1 = s1x*s1x + s1y*s1y + s1z*s1z
s2dots2 = s2x*s2x + s2y*s2y + s2z*s2z
r2 = x*x + y*y + z*z
r = sp.sqrt(r2)
u = 1/r
u2 = u*u
u3 = u2*u
u4 = u2*u2
u5 = u4*u
etau3 = eta*u3
etau4 = eta*u4
nx = x*u
ny = y*u
nz = z*u
sKerrUSCOREx = sigmaKerr0
sKerrUSCOREy = sigmaKerr1
sKerrUSCOREz = sigmaKerr2
sStarUSCOREx = sigmaStar0
sStarUSCOREy = sigmaStar1
sStarUSCOREz = sigmaStar2
a2 = sKerrUSCOREx*sKerrUSCOREx + sKerrUSCOREy*sKerrUSCOREy + sKerrUSCOREz*sKerrUSCOREz
a4 = a2*a2
a = sp.sqrt(a2)
inva = 1/a
m1PlusetaKK = -1 + eta*KK
invm1PlusetaKK = 1/m1PlusetaKK
k0 = KK*(m1PlusetaKK - 1)
k1 = -2*(k0 + KK)*m1PlusetaKK
k2 = c0k2 + c1k2*a2
k3 = c0k3 + c1k3*a2
k4 = c0k4 + c1k4*a2 + c2k4*a4
k5 = c0k5 + c1k5*a2 + c2k5*a4
e3USCOREx = sKerrUSCOREx*inva
e3USCOREy = sKerrUSCOREy*inva
e3USCOREz = sKerrUSCOREz*inva
costheta = e3USCOREx*nx + e3USCOREy*ny + e3USCOREz*nz
xi2 = 1 - costheta*costheta
xiUSCOREx = -e3USCOREz*ny + e3USCOREy*nz
xiUSCOREy =  e3USCOREz*nx - e3USCOREx*nz
xiUSCOREz = -e3USCOREy*nx + e3USCOREx*ny

#vx = -nz*xiUSCOREy + ny*xiUSCOREz
vx = sp.symbols('vx')

#vy =  nz*xiUSCOREx - nx*xiUSCOREz
vy = sp.symbols('vy')

#vz = -ny*xiUSCOREx + nx*xiUSCOREy
vz = sp.symbols('vz')

w2 = r2 + a2
rho2 = r2 + a2*costheta*costheta
bulk = invm1PlusetaKK*(invm1PlusetaKK + 2*u) + a2*u2
logu = sp.log( u )
logarg = k1*u + k2*u2 + k3*u3 + k4*u4 + k5*u5 + k5l*u5*logu
onepluslogarg = (1 + logarg)
invonepluslogarg = 1/onepluslogarg
logTerms = 1 + eta*k0 + eta*sp.log(onepluslogarg)
deltaU = bulk*logTerms

#deltaT = r2*deltaU
deltaT = sp.symbols('deltaT')

deltaUUSCOREupt7 = k5 + k5l*logu
deltaUUSCOREupt6 = 4*k4 + 5*deltaUUSCOREupt7*u
deltaUUSCOREupt5 = 3*k3 + u*deltaUUSCOREupt6
deltaUUSCOREupt4 = 2*k2 + u*deltaUUSCOREupt5
deltaUUSCOREupt3 = k1 + u*deltaUUSCOREupt4
deltaUUSCOREupt2 = invm1PlusetaKK + a2*u
deltaUUSCOREupt1 = bulk*eta*deltaUUSCOREupt3
deltaUUSCOREu = 2*deltaUUSCOREupt2*logTerms + deltaUUSCOREupt1*invonepluslogarg

#deltaTUSCOREr = 2*r*deltaU - deltaUUSCOREu
deltaTUSCOREr = sp.symbols('deltaTUSCOREr')

#Lambda = w2*w2 - a2*deltaT*xi2
Lambda = sp.symbols('Lambda')

rho2xi2Lambda = rho2*xi2*Lambda
invrho2xi2Lambda = 1/rho2xi2Lambda
invrho2 = xi2*Lambda*invrho2xi2Lambda
invxi2 = rho2*Lambda*invrho2xi2Lambda
invLambda = xi2*rho2*invrho2xi2Lambda
invLambdasq = invLambda*invLambda
rho2invLambda = rho2*invLambda

#expnu = sp.sqrt(deltaT*rho2invLambda)
expnu = sp.symbols('expnu')

expMU = sp.sqrt(rho2)

#expMUexpnu = expMU*expnu
expMUexpnu = sp.symbols('expMUexpnu')

expMUsq = expMU*expMU
expnusq = expnu*expnu
expMUsqexpnusq = expMUsq*expnusq
invexpnuexpMU = 1/expMUexpnu

#invexpMU = expnu*invexpnuexpMU
invexpMU = sp.symbols('invexpMU')

invexpMUsq = invexpMU*invexpMU
expnuinvexpMU2 = expnu*invexpMUsq
invexpMUcubinvexpnu = invexpMUsq*invexpnuexpMU
DD = 1 +  sp.log(1 + 6*eta*u2 + 2*(26 - 3*eta)*etau3)
deltaR = deltaT*DD
qq = 2*eta*(4 - 3*eta)
ww = 2*a*r + b3*eta*a2*a*u + bb3*eta*a*u
B = sp.sqrt(deltaT)
sqrtdeltaT = B
sqrtdeltaR = sp.sqrt(deltaR)
deltaTsqrtdeltaR = deltaT*sqrtdeltaR
sqrtdeltaTdeltaTsqrtdeltaR = sqrtdeltaT*deltaTsqrtdeltaR
invdeltaTsqrtdeltaTsqrtdeltaR = 1./sqrtdeltaTdeltaTsqrtdeltaR

#invdeltaT = sqrtdeltaT*(sqrtdeltaR*invdeltaTsqrtdeltaTsqrtdeltaR)
invdeltaT = sp.symbols('invdeltaT')

invsqrtdeltaT = deltaTsqrtdeltaR*invdeltaTsqrtdeltaTsqrtdeltaR
invsqrtdeltaR = deltaT*sqrtdeltaT*invdeltaTsqrtdeltaTsqrtdeltaR
w = ww*invLambda
LambdaUSCOREr = 4*r*w2 - a2*deltaTUSCOREr*xi2
wwUSCOREr = 2*a - (a2*a*b3*eta)*u2 - bb3*eta*a*u2

#BR = (-deltaT*invsqrtdeltaR + deltaTUSCOREr*sp.Rational(1,2))*invsqrtdeltaT
BR = sp.symbols('BR')

#wr = (-LambdaUSCOREr*ww + Lambda*wwUSCOREr)*(invLambdasq)
wr = sp.symbols('wr')

nurpt2 = w2*(-4.*r*deltaT + w2*deltaTUSCOREr)
nurpt1 = nurpt2*invdeltaT
nur = r*invrho2 + sp.Rational(1,2)*invLambda*nurpt1
mur = r*invrho2 - invsqrtdeltaR
a2costheta = a2*costheta
wcospt2 = deltaT*ww
wcospt1 = invLambdasq*wcospt2

#wcos  = -2*(a2costheta)*wcospt1
wcos = sp.symbols('wcos')

nucospt3 = invrho2*invLambda
nucospt2 = w2*nucospt3
nucospt1 = a2costheta*nucospt2
nucos = (w2 - deltaT)*nucospt1
mucos = a2costheta*invrho2
etaover12r = eta*sp.Rational(1,12)*u
csi = sp.sqrt(deltaT*deltaR)/w2
csi1  =  1 + (1-sp.Abs(1-tortoise)) * (csi - 1)
#csi2  =  1 + (sp.Rational(1,2)-copysign(0.5, 1.5-tortoise)) * (csi - 1)
csi2  =  1 + (sp.Rational(1,2)-copysignresult) * (csi - 1)

#prT = (px*nx + py*ny + pz*nz)*csi2
prT = sp.symbols('prT')

#prTtimesoneminuscsi1inv = prT*(1 - 1/csi1)
prTtimesoneminuscsi1inv = sp.symbols('prTtimesoneminuscsi1inv')

tmppx = px - nx*prTtimesoneminuscsi1inv
tmppy = py - ny*prTtimesoneminuscsi1inv
tmppz = pz - nz*prTtimesoneminuscsi1inv

#pxir = (tmppx*xiUSCOREx + tmppy*xiUSCOREy + tmppz*xiUSCOREz)*r
pxir = sp.symbols('pxir')

pvr  = (tmppx*vx + tmppy*vy + tmppz*vz)*r
pvrsq = pvr*pvr
pn   =  tmppx*nx + tmppy*ny + tmppz*nz
pnsq = pn*pn
pr = pn
prsq = pr*pr

#pf = pxir
pf = sp.symbols('pf')

pxirsq = pxir*pxir
ptheta2 = pvrsq*invxi2
prT4=prT*prT*prT*prT
Hnspt7 = deltaR*invrho2
Hnspt6 = rho2invLambda*invxi2
Hnspt5 = qq*u2

#Hnspt4 = (1 + prT4*Hnspt5 + ptheta2*invrho2 + pf*pf*Hnspt6 + prsq*Hnspt7)
Hnspt4 = sp.symbols('Hnspt4')

Hnspt3 = deltaT*Hnspt4
Hnspt2 = rho2*Hnspt3
Hnspt1 = pf*ww

#Hns = sp.sqrt(Hnspt2*invLambda) + invLambda*Hnspt1
Hns = sp.symbols('Hns')

Qpt3 = deltaR*invrho2
Qpt2 = rho2invLambda*invxi2
Qpt1 = invrho2*invxi2

#Q = 1 + pvrsq*Qpt1 + pxirsq*Qpt2 + pnsq*Qpt3
Q = sp.symbols('Q')

#pn2 = prsq*deltaR*invrho2
pn2 = sp.symbols('pn2')

pp  = Q - 1
sKerrmultfact = (-8 - 3*r*(12*pn2 - pp))
sStarmultfact = (14 + (- 30*pn2 + 4*pp)*r)
deltaSigmaStarUSCOREx1=etaover12r*(sKerrmultfact*sKerrUSCOREx + sStarmultfact*sStarUSCOREx)
deltaSigmaStarUSCOREy1=etaover12r*(sKerrmultfact*sKerrUSCOREy + sStarmultfact*sStarUSCOREy)
deltaSigmaStarUSCOREz1=etaover12r*(sKerrmultfact*sKerrUSCOREz + sStarmultfact*sStarUSCOREz)
pn2pp = pn2*pp
pp2 = pp*pp
pn2u2 = pn2*u2
ppu2 = pp*u2
pn2ppu2 = pn2pp*u2
sMultiplier1pt6 = -360*pn2*pn2 + 126*pn2pp + 3*pp2
sMultiplier1pt5 = -96*pn2pp + 23*pp2
sMultiplier1pt4 = -120*pp + 324*pn2 + sMultiplier1pt6*r
sMultiplier1pt3 = 206*pp - 282*pn2 + sMultiplier1pt5*r
sMultiplier1pt2 = 54 + sMultiplier1pt4*r
sMultiplier1pt1 = -706 + sMultiplier1pt3*r + sMultiplier1pt2*eta

#sMultiplier1 = sMultiplier1pt1*eta*u2*sp.Rational(-1,72)
sMultiplier1 = sp.symbols('sMultiplier1')

sMultiplier2pt6 = sp.Rational(45,8)*pn2*pn2u2 - sp.Rational(13,8)*pn2ppu2
sMultiplier2pt5 = pn2ppu2/4 - sp.Rational(5,16)*pp2*u2

#sMultiplier2pt4 = sp.Rational(-49,8)*pn2u2 + sp.Rational(17,12)*ppu2 + sMultiplier2pt6*r
sMultiplier2pt4 = sp.symbols('sMultiplier2pt4')

sMultiplier2pt3 = sp.Rational(-2,3)*pn2u2 - sp.Rational(109,36)*ppu2 + sMultiplier2pt5*r
sMultiplier2pt2 = sp.Rational(-7,3)*u2 + sMultiplier2pt4*r
sMultiplier2pt1 = sp.Rational(-56,9)*u2 + sMultiplier2pt3*r + sMultiplier2pt2*eta

#sMultiplier2 = sMultiplier2pt1*eta
sMultiplier2 = sp.symbols('sMultiplier2')

deltaSigmaStarUSCOREx2 = deltaSigmaStarUSCOREx1 + sMultiplier1*sigmaStar0 + sMultiplier2*sigmaKerr0
deltaSigmaStarUSCOREy2 = deltaSigmaStarUSCOREy1 + sMultiplier1*sigmaStar1 + sMultiplier2*sigmaKerr1
deltaSigmaStarUSCOREz2 = deltaSigmaStarUSCOREz1 + sMultiplier1*sigmaStar2 + sMultiplier2*sigmaKerr2
deltaSigmaStarUSCOREx3 = deltaSigmaStarUSCOREx2 + d1*sigmaStar0*etau3
deltaSigmaStarUSCOREy3 = deltaSigmaStarUSCOREy2 + d1*sigmaStar1*etau3
deltaSigmaStarUSCOREz3 = deltaSigmaStarUSCOREz2 + d1*sigmaStar2*etau3
deltaSigmaStarUSCOREx = deltaSigmaStarUSCOREx3 + d1v2*sigmaKerr0*etau3
deltaSigmaStarUSCOREy = deltaSigmaStarUSCOREy3 + d1v2*sigmaKerr1*etau3
deltaSigmaStarUSCOREz = deltaSigmaStarUSCOREz3 + d1v2*sigmaKerr2*etau3

#sx = sStarUSCOREx + deltaSigmaStarUSCOREx
sx = sp.symbols('sx')

#sy = sStarUSCOREy + deltaSigmaStarUSCOREy
sy = sp.symbols('sy')

#sz = sStarUSCOREz + deltaSigmaStarUSCOREz
sz = sp.symbols('sz')

#sxi = sx*xiUSCOREx + sy*xiUSCOREy + sz*xiUSCOREz
sxi = sp.symbols('sxi')

#sv = sx*vx + sy*vy + sz*vz
sv = sp.symbols('sv')

sn  = sx*nx + sy*ny + sz*nz
s3 = sx*e3USCOREx + sy*e3USCOREy + sz*e3USCOREz
sqrtQ = sp.sqrt(Q)
oneplus2sqrtQ = 1 + 2*sqrtQ
oneplus1sqrtQ = oneplus2sqrtQ - sqrtQ
twoB1psqrtQsqrtQ = (2*B*oneplus1sqrtQ*sqrtQ)
invtwoB1psqrtQsqrtQ = 1/twoB1psqrtQsqrtQ
expMUsqsqrtQplusQ = (expMUsq)*(sqrtQ + Q)
Hwrpt4a = pxirsq*sv

#Hwrpt4 = expMUsqexpnusq*Hwrpt4a
Hwrpt4 = sp.symbols('Hwrpt4')

Hwrpt3c = pxir*sxi

#Hwrpt3b = pvr*Hwrpt3c
Hwrpt3b = sp.symbols('Hwrpt3b')

Hwrpt3a = expMUexpnu*Hwrpt3b
Hwrpt3 = B*Hwrpt3a
Hwrpt2g = sv*deltaR
Hwrpt2f = sn*sqrtdeltaR
Hwrpt2e = pvr*Hwrpt2f
Hwrpt2d = pnsq*Hwrpt2g
Hwrpt2c = pn*Hwrpt2e
Hwrpt2b = expMUsqsqrtQplusQ*sv
Hwrpt2a = xi2*(Hwrpt2b + Hwrpt2c - Hwrpt2d)

#Hwrpt2 = deltaT*Hwrpt2a
Hwrpt2 = sp.symbols('Hwrpt2')

Hwrpt1b = invtwoB1psqrtQsqrtQ*invxi2
Hwrpt1a = sqrtdeltaR*Hwrpt1b

#Hwrpt1 = invexpMUcubinvexpnu*Hwrpt1a
Hwrpt1 = sp.symbols('Hwrpt1')

#Hwr = (Hwrpt4 - Hwrpt3 + Hwrpt2)*Hwrpt1
Hwr = sp.symbols('Hwr')

Hwcospt9 = pxir*sxi
Hwcospt8 = pvr*sv
Hwcospt7 = (B*Hwcospt8 - (expMUexpnu)*Hwcospt9)
Hwcospt6 = sqrtdeltaR*Hwcospt7

#Hwcospt5 = (pvrsq - expMUsqsqrtQplusQ*xi2)
Hwcospt5 = sp.symbols('Hwcospt5')

Hwcospt4 = pn*Hwcospt6

#Hwcospt3 = -(expMUsqexpnusq*pxirsq) + deltaT*Hwcospt5
Hwcospt3 = sp.symbols('Hwcospt3')

Hwcospt2 = sn*Hwcospt3 - B*Hwcospt4
Hwcospt1 = invexpMUcubinvexpnu*Hwcospt2

#Hwcos = invtwoB1psqrtQsqrtQ*Hwcospt1
Hwcos = sp.symbols('Hwcos')

deltaTsqrtQ = deltaT*sqrtQ
invdeltatTsqrtQ = 1/deltaTsqrtQ

#HSOLpt5 = (-B + (expMUexpnu))*pxir
HSOLpt5 = sp.symbols('HSOLpt5')

HSOLpt4 = invexpMU*HSOLpt5
HSOLpt3 = expnusq*HSOLpt4
HSOLpt2 = (HSOLpt3*s3)
HSOLpt1 = HSOLpt2*invxi2

#HSOL = HSOLpt1*invdeltatTsqrtQ
HSOL = sp.symbols('HSOL')

deltaTsqrtQplusQ = (deltaT*(sqrtQ + Q))
invdeltaTsqrtQplusQ = 1/deltaTsqrtQplusQ

#HSONLmult2 = invxi2*invdeltaTsqrtQplusQ
HSONLmult2 = sp.symbols('HSONLmult2')

#HSONLmult = expnuinvexpMU2*HSONLmult2
HSONLmult = sp.symbols('HSONLmult')

HSONLpt1b = pn*xi2
HSONLpt1a = (mur*pvr - nur*pvr + (-mucos + nucos)*HSONLpt1b)

#HSONLpt1 = mur*pvr - (mucos*HSONLpt1b) + sqrtQ*HSONLpt1a
HSONLpt1 = sp.symbols('HSONLpt1')

HSONLpt2d = nur*pxir
HSONLpt2c = oneplus2sqrtQ*HSONLpt2d
HSONLpt2b = B*sxi
HSONLpt2a = expMUexpnu*HSONLpt2c

#HSONLpt2 = (sv*HSONLpt2a + HSONLpt1*HSONLpt2b)
HSONLpt2 = sp.symbols('HSONLpt2')

HSONLpt3c = sv*pxir
HSONLpt3b = oneplus1sqrtQ*HSONLpt3c
HSONLpt3a = expMUexpnu*HSONLpt3b
HSONLpt3 = -BR*HSONLpt3a + B*HSONLpt2
HSONLpt4e = sn*xi2
HSONLpt4d = oneplus2sqrtQ*HSONLpt4e
HSONLpt4c = pxir*HSONLpt4d
HSONLpt4b = nucos*HSONLpt4c
HSONLpt4a = expMUexpnu*HSONLpt4b

#HSONLpt4 = (-(B*HSONLpt4a) + HSONLpt3*sqrtdeltaR)
HSONLpt4 = sp.symbols('HSONLpt4')

HSONL = HSONLmult*HSONLpt4

#Hs = w*s3 + Hwr*wr + Hwcos*wcos + HSOL + HSONL
Hs = sp.symbols('Hs')

Hsspt1 = sp.Rational(-1,2)*(sx*sx + sy*sy + sz*sz - 3*sn*sn)

#Hss = u3*Hsspt1
Hss = sp.symbols('Hss')

sKerrdotsStar = (sKerrUSCOREx*sStarUSCOREx + sKerrUSCOREy*sStarUSCOREy + sKerrUSCOREz*sStarUSCOREz)
Hpt1 = etau4*(s1dots1 + s2dots2)
H = Hns + Hs + Hss + (dheffSS*sKerrdotsStar + dheffSSv2)*Hpt1
Hreal = sp.sqrt(1 + 2*eta*(H - 1))

#print(sp.mathematica_code(expnu))
```

```python
from outputC import *
import time
start = time.time()

#outstring = "test"
outstring = outputC(Hreal,"Hrealtest","returnstring","outCverbose=False,SIMD_enable=True,enable_TYPE=False,includebraces=False")

end = time.time()

print("Finished in " + str(end - start) + " seconds.")
```

    Finished in 0.61027598381 seconds.

```python
print("""(* Part 1: SIMD function definitions *)
MulSIMD[x_, y_] = x*y;
DivSIMD[x_, y_] = x/y;
AddSIMD[x_, y_] = x + y;
FusedMulAddSIMD[x_, y_, z_] = x*y + z;
FusedMulSubSIMD[x_, y_, z_] = x*y - z;
SqrtSIMD[x_] = Sqrt[x];
LogSIMD[x_] = Log[x];
SubSIMD[x_, y_] = x - y;
AbsSIMD[x_] = Abs[x];
PowSIMD[x_, y_] = x^y;

""")
print("(* Part 2: SEOBNR Hamiltonian *)")
print(outstring.replace("_Rational","Rational").replace("_Integer","Integer").replace("_","USCR")).replace("(","[").replace(")","]").replace(".000000000000000000000000000000000","").replace(".00000000000000000000000000000000","").replace("000000000000000000000000000000000","").replace("00000000000000000000000000000000","").replace(".0000000000000000000000000000000","").replace("000000000000000000000000000000","")
# replace(";","").
```

    (* Part 1: SIMD function definitions *)
    MulSIMD[x_, y_] = x*y;
    DivSIMD[x_, y_] = x/y;
    AddSIMD[x_, y_] = x + y;
    FusedMulAddSIMD[x_, y_, z_] = x*y + z;
    FusedMulSubSIMD[x_, y_, z_] = x*y - z;
    SqrtSIMD[x_] = Sqrt[x];
    LogSIMD[x_] = Log[x];
    SubSIMD[x_, y_] = x - y;
    AbsSIMD[x_] = Abs[x];
    PowSIMD[x_, y_] = x^y;

    (* Part 2: SEOBNR Hamiltonian *)
    IntegerUSCR1 = 1;
    IntegerUSCR2 = 2;
    IntegerUSCRm1 = -1;
    tmp0 = DivSIMD[m2, m1];
    tmp1 = DivSIMD[m1, m2];
    Hrealtest = SqrtSIMD[FusedMulAddSIMD[AddSIMD[IntegerUSCRm1, AddSIMD[Hss, AddSIMD[Hs, FusedMulAddSIMD[FusedMulAddSIMD[s2y, s2y, FusedMulAddSIMD[s2x, s2x, FusedMulAddSIMD[s1z, s1z, FusedMulAddSIMD[s1y, s1y, FusedMulAddSIMD[s2z, s2z, MulSIMD[s1x, s1x]]]]]], MulSIMD[FusedMulAddSIMD[dheffSS, FusedMulAddSIMD[AddSIMD[s1y, s2y], FusedMulAddSIMD[tmp1, s2y, MulSIMD[tmp0, s1y]], FusedMulAddSIMD[AddSIMD[s1z, s2z], FusedMulAddSIMD[tmp1, s2z, MulSIMD[tmp0, s1z]], MulSIMD[AddSIMD[s1x, s2x], FusedMulAddSIMD[tmp1, s2x, MulSIMD[tmp0, s1x]]]]], dheffSSv2], DivSIMD[eta, MulSIMD[FusedMulAddSIMD[y, y, FusedMulAddSIMD[z, z, MulSIMD[x, x]]], FusedMulAddSIMD[y, y, FusedMulAddSIMD[z, z, MulSIMD[x, x]]]]]], Hns]]]], MulSIMD[eta, IntegerUSCR2], IntegerUSCR1]];

```python
bigstring = """
sigmaKerr0 = s1x + s2x
sigmaKerr1 = s1y + s2y
sigmaKerr2 = s1z + s2z
invm1m2 = 1/(m1*m2)
m2overm1 = m2*m2*invm1m2
m1overm2 = m1*m1*invm1m2
sigmaStar0 = (m2overm1)*s1x + (m1overm2)*s2x
sigmaStar1 = (m2overm1)*s1y + (m1overm2)*s2y
sigmaStar2 = (m2overm1)*s1z + (m1overm2)*s2z
s1dots1 = s1x*s1x + s1y*s1y + s1z*s1z
s2dots2 = s2x*s2x + s2y*s2y + s2z*s2z
r2 = x*x + y*y + z*z
r = sp.sqrt(r2)
u = 1/r
u2 = u*u
u3 = u2*u
u4 = u2*u2
u5 = u4*u
etau3 = eta*u3
etau4 = eta*u4
nx = x*u
ny = y*u
nz = z*u
sKerrUSCOREx = sigmaKerr0
sKerrUSCOREy = sigmaKerr1
sKerrUSCOREz = sigmaKerr2
sStarUSCOREx = sigmaStar0
sStarUSCOREy = sigmaStar1
sStarUSCOREz = sigmaStar2
a2 = sKerrUSCOREx*sKerrUSCOREx + sKerrUSCOREy*sKerrUSCOREy + sKerrUSCOREz*sKerrUSCOREz
a4 = a2*a2
a = sp.sqrt(a2)
inva = 1/a
m1PlusetaKK = -1 + eta*KK
invm1PlusetaKK = 1/m1PlusetaKK
k0 = KK*(m1PlusetaKK - 1)
k1 = -2*(k0 + KK)*m1PlusetaKK
k2 = c0k2 + c1k2*a2
k3 = c0k3 + c1k3*a2
k4 = c0k4 + c1k4*a2 + c2k4*a4
k5 = c0k5 + c1k5*a2 + c2k5*a4
e3USCOREx = sKerrUSCOREx*inva
e3USCOREy = sKerrUSCOREy*inva
e3USCOREz = sKerrUSCOREz*inva
costheta = e3USCOREx*nx + e3USCOREy*ny + e3USCOREz*nz
xi2 = 1 - costheta*costheta
xiUSCOREx = -e3USCOREz*ny + e3USCOREy*nz
xiUSCOREy =  e3USCOREz*nx - e3USCOREx*nz
xiUSCOREz = -e3USCOREy*nx + e3USCOREx*ny
vx = -nz*xiUSCOREy + ny*xiUSCOREz
vy =  nz*xiUSCOREx - nx*xiUSCOREz
vz = -ny*xiUSCOREx + nx*xiUSCOREy
w2 = r2 + a2
rho2 = r2 + a2*costheta*costheta
bulk = invm1PlusetaKK*(invm1PlusetaKK + 2*u) + a2*u2
logu = sp.log( u )
logarg = k1*u + k2*u2 + k3*u3 + k4*u4 + k5*u5 + k5l*u5*logu
onepluslogarg = (1 + logarg)
invonepluslogarg = 1/onepluslogarg
logTerms = 1 + eta*k0 + eta*sp.log(onepluslogarg)
deltaU = bulk*logTerms
deltaT = r2*deltaU
deltaUUSCOREupt7 = k5 + k5l*logu
deltaUUSCOREupt6 = 4*k4 + 5*deltaUUSCOREupt7*u
deltaUUSCOREupt5 = 3*k3 + u*deltaUUSCOREupt6
deltaUUSCOREupt4 = 2*k2 + u*deltaUUSCOREupt5
deltaUUSCOREupt3 = k1 + u*deltaUUSCOREupt4
deltaUUSCOREupt2 = invm1PlusetaKK + a2*u
deltaUUSCOREupt1 = bulk*eta*deltaUUSCOREupt3
deltaUUSCOREu = 2*deltaUUSCOREupt2*logTerms + deltaUUSCOREupt1*invonepluslogarg
deltaTUSCOREr = 2*r*deltaU - deltaUUSCOREu
Lambda = w2*w2 - a2*deltaT*xi2
rho2xi2Lambda = rho2*xi2*Lambda
invrho2xi2Lambda = 1/rho2xi2Lambda
invrho2 = xi2*Lambda*invrho2xi2Lambda
invxi2 = rho2*Lambda*invrho2xi2Lambda
invLambda = xi2*rho2*invrho2xi2Lambda
invLambdasq = invLambda*invLambda
rho2invLambda = rho2*invLambda
expnu = sp.sqrt(deltaT*rho2invLambda)
expMU = sp.sqrt(rho2)
expMUexpnu = expMU*expnu
expMUsq = expMU*expMU
expnusq = expnu*expnu
expMUsqexpnusq = expMUsq*expnusq
invexpnuexpMU = 1/expMUexpnu
invexpMU = expnu*invexpnuexpMU
invexpMUsq = invexpMU*invexpMU
expnuinvexpMU2 = expnu*invexpMUsq
invexpMUcubinvexpnu = invexpMUsq*invexpnuexpMU
DD = 1 +  sp.log(1 + 6*eta*u2 + 2*(26 - 3*eta)*etau3)
deltaR = deltaT*DD
qq = 2*eta*(4 - 3*eta)
ww = 2*a*r + b3*eta*a2*a*u + bb3*eta*a*u
B = sp.sqrt(deltaT)
sqrtdeltaT = B
sqrtdeltaR = sp.sqrt(deltaR)
deltaTsqrtdeltaR = deltaT*sqrtdeltaR
sqrtdeltaTdeltaTsqrtdeltaR = sqrtdeltaT*deltaTsqrtdeltaR
invdeltaTsqrtdeltaTsqrtdeltaR = 1./sqrtdeltaTdeltaTsqrtdeltaR
invdeltaT = sqrtdeltaT*(sqrtdeltaR*invdeltaTsqrtdeltaTsqrtdeltaR)
invsqrtdeltaT = deltaTsqrtdeltaR*invdeltaTsqrtdeltaTsqrtdeltaR
invsqrtdeltaR = deltaT*sqrtdeltaT*invdeltaTsqrtdeltaTsqrtdeltaR
w = ww*invLambda
LambdaUSCOREr = 4*r*w2 - a2*deltaTUSCOREr*xi2
wwUSCOREr = 2*a - (a2*a*b3*eta)*u2 - bb3*eta*a*u2
BR = (-deltaT*invsqrtdeltaR + deltaTUSCOREr*sp.Rational(1,2))*invsqrtdeltaT
wr = (-LambdaUSCOREr*ww + Lambda*wwUSCOREr)*(invLambdasq)
nurpt2 = w2*(-4.*r*deltaT + w2*deltaTUSCOREr)
nurpt1 = nurpt2*invdeltaT
nur = r*invrho2 + sp.Rational(1,2)*invLambda*nurpt1
mur = r*invrho2 - invsqrtdeltaR
a2costheta = a2*costheta
wcospt2 = deltaT*ww
wcospt1 = invLambdasq*wcospt2
wcos  = -2*(a2costheta)*wcospt1
nucospt3 = invrho2*invLambda
nucospt2 = w2*nucospt3
nucospt1 = a2costheta*nucospt2
nucos = (w2 - deltaT)*nucospt1
mucos = a2costheta*invrho2
etaover12r = eta*sp.Rational(1,12)*u
csi = sp.sqrt(deltaT*deltaR)/w2
csi1  =  1 + (1-sp.Abs(1-tortoise)) * (csi - 1)
#csi2  =  1 + (sp.Rational(1,2)-copysign(0.5, 1.5-tortoise)) * (csi - 1)
csi2  =  1 + (sp.Rational(1,2)-copysignresult) * (csi - 1)
prT = (px*nx + py*ny + pz*nz)*csi2
prTtimesoneminuscsi1inv = prT*(1 - 1/csi1)
tmppx = px - nx*prTtimesoneminuscsi1inv
tmppy = py - ny*prTtimesoneminuscsi1inv
tmppz = pz - nz*prTtimesoneminuscsi1inv
pxir = (tmppx*xiUSCOREx + tmppy*xiUSCOREy + tmppz*xiUSCOREz)*r
pvr  = (tmppx*vx + tmppy*vy + tmppz*vz)*r
pvrsq = pvr*pvr
pn   =  tmppx*nx + tmppy*ny + tmppz*nz
pnsq = pn*pn
pr = pn
prsq = pr*pr
pf = pxir
pxirsq = pxir*pxir
ptheta2 = pvrsq*invxi2
prT4=prT*prT*prT*prT
Hnspt7 = deltaR*invrho2
Hnspt6 = rho2invLambda*invxi2
Hnspt5 = qq*u2
Hnspt4 = (1 + prT4*Hnspt5 + ptheta2*invrho2 + pf*pf*Hnspt6 + prsq*Hnspt7)
Hnspt3 = deltaT*Hnspt4
Hnspt2 = rho2*Hnspt3
Hnspt1 = pf*ww
Hns = sp.sqrt(Hnspt2*invLambda) + invLambda*Hnspt1
Qpt3 = deltaR*invrho2
Qpt2 = rho2invLambda*invxi2
Qpt1 = invrho2*invxi2
Q = 1 + pvrsq*Qpt1 + pxirsq*Qpt2 + pnsq*Qpt3
pn2 = prsq*deltaR*invrho2
pp  = Q - 1
sKerrmultfact = (-8 - 3*r*(12*pn2 - pp))
sStarmultfact = (14 + (- 30*pn2 + 4*pp)*r)
deltaSigmaStarUSCOREx1=etaover12r*(sKerrmultfact*sKerrUSCOREx + sStarmultfact*sStarUSCOREx)
deltaSigmaStarUSCOREy1=etaover12r*(sKerrmultfact*sKerrUSCOREy + sStarmultfact*sStarUSCOREy)
deltaSigmaStarUSCOREz1=etaover12r*(sKerrmultfact*sKerrUSCOREz + sStarmultfact*sStarUSCOREz)
pn2pp = pn2*pp
pp2 = pp*pp
pn2u2 = pn2*u2
ppu2 = pp*u2
pn2ppu2 = pn2pp*u2
sMultiplier1pt6 = -360*pn2*pn2 + 126*pn2pp + 3*pp2
sMultiplier1pt5 = -96*pn2pp + 23*pp2
sMultiplier1pt4 = -120*pp + 324*pn2 + sMultiplier1pt6*r
sMultiplier1pt3 = 206*pp - 282*pn2 + sMultiplier1pt5*r
sMultiplier1pt2 = 54 + sMultiplier1pt4*r
sMultiplier1pt1 = -706 + sMultiplier1pt3*r + sMultiplier1pt2*eta
sMultiplier1 = sMultiplier1pt1*eta*u2*sp.Rational(-1,72)
sMultiplier2pt6 = sp.Rational(45,8)*pn2*pn2u2 - sp.Rational(13,8)*pn2ppu2
sMultiplier2pt5 = pn2ppu2/4 - sp.Rational(5,16)*pp2*u2
sMultiplier2pt4 = sp.Rational(-49,8)*pn2u2 + sp.Rational(17,12)*ppu2 + sMultiplier2pt6*r
sMultiplier2pt3 = sp.Rational(-2,3)*pn2u2 - sp.Rational(109,36)*ppu2 + sMultiplier2pt5*r
sMultiplier2pt2 = sp.Rational(-7,3)*u2 + sMultiplier2pt4*r
sMultiplier2pt1 = sp.Rational(-56,9)*u2 + sMultiplier2pt3*r + sMultiplier2pt2*eta
sMultiplier2 = sMultiplier2pt1*eta
deltaSigmaStarUSCOREx2 = deltaSigmaStarUSCOREx1 + sMultiplier1*sigmaStar0 + sMultiplier2*sigmaKerr0
deltaSigmaStarUSCOREy2 = deltaSigmaStarUSCOREy1 + sMultiplier1*sigmaStar1 + sMultiplier2*sigmaKerr1
deltaSigmaStarUSCOREz2 = deltaSigmaStarUSCOREz1 + sMultiplier1*sigmaStar2 + sMultiplier2*sigmaKerr2
deltaSigmaStarUSCOREx3 = deltaSigmaStarUSCOREx2 + d1*sigmaStar0*etau3
deltaSigmaStarUSCOREy3 = deltaSigmaStarUSCOREy2 + d1*sigmaStar1*etau3
deltaSigmaStarUSCOREz3 = deltaSigmaStarUSCOREz2 + d1*sigmaStar2*etau3
deltaSigmaStarUSCOREx = deltaSigmaStarUSCOREx3 + d1v2*sigmaKerr0*etau3
deltaSigmaStarUSCOREy = deltaSigmaStarUSCOREy3 + d1v2*sigmaKerr1*etau3
deltaSigmaStarUSCOREz = deltaSigmaStarUSCOREz3 + d1v2*sigmaKerr2*etau3
sx = sStarUSCOREx + deltaSigmaStarUSCOREx
sy = sStarUSCOREy + deltaSigmaStarUSCOREy
sz = sStarUSCOREz + deltaSigmaStarUSCOREz
sxi = sx*xiUSCOREx + sy*xiUSCOREy + sz*xiUSCOREz
sv  = sx*vx + sy*vy + sz*vz
sn  = sx*nx + sy*ny + sz*nz
s3 = sx*e3USCOREx + sy*e3USCOREy + sz*e3USCOREz
sqrtQ = sp.sqrt(Q)
oneplus2sqrtQ = 1 + 2*sqrtQ
oneplus1sqrtQ = oneplus2sqrtQ - sqrtQ
twoB1psqrtQsqrtQ = (2*B*oneplus1sqrtQ*sqrtQ)
invtwoB1psqrtQsqrtQ = 1/twoB1psqrtQsqrtQ
expMUsqsqrtQplusQ = (expMUsq)*(sqrtQ + Q)
Hwrpt4a = pxirsq*sv
Hwrpt4 = expMUsqexpnusq*Hwrpt4a
Hwrpt3c = pxir*sxi
Hwrpt3b = pvr*Hwrpt3c
Hwrpt3a = expMUexpnu*Hwrpt3b
Hwrpt3 = B*Hwrpt3a
Hwrpt2g = sv*deltaR
Hwrpt2f = sn*sqrtdeltaR
Hwrpt2e = pvr*Hwrpt2f
Hwrpt2d = pnsq*Hwrpt2g
Hwrpt2c = pn*Hwrpt2e
Hwrpt2b = expMUsqsqrtQplusQ*sv
Hwrpt2a = xi2*(Hwrpt2b + Hwrpt2c - Hwrpt2d)
Hwrpt2 = deltaT*Hwrpt2a
Hwrpt1b = invtwoB1psqrtQsqrtQ*invxi2
Hwrpt1a = sqrtdeltaR*Hwrpt1b
Hwrpt1 = invexpMUcubinvexpnu*Hwrpt1a
Hwr = (Hwrpt4 - Hwrpt3 + Hwrpt2)*Hwrpt1
Hwcospt9 = pxir*sxi
Hwcospt8 = pvr*sv
Hwcospt7 = (B*Hwcospt8 - (expMUexpnu)*Hwcospt9)
Hwcospt6 = sqrtdeltaR*Hwcospt7
Hwcospt5 = (pvrsq - expMUsqsqrtQplusQ*xi2)
Hwcospt4 = pn*Hwcospt6
Hwcospt3 = -(expMUsqexpnusq*pxirsq) + deltaT*Hwcospt5
Hwcospt2 = sn*Hwcospt3 - B*Hwcospt4
Hwcospt1 = invexpMUcubinvexpnu*Hwcospt2
Hwcos = invtwoB1psqrtQsqrtQ*Hwcospt1
deltaTsqrtQ = deltaT*sqrtQ
invdeltatTsqrtQ = 1/deltaTsqrtQ
HSOLpt5 = (-B + (expMUexpnu))*pxir
HSOLpt4 = invexpMU*HSOLpt5
HSOLpt3 = expnusq*HSOLpt4
HSOLpt2 = (HSOLpt3*s3)
HSOLpt1 = HSOLpt2*invxi2
HSOL = HSOLpt1*invdeltatTsqrtQ
deltaTsqrtQplusQ = (deltaT*(sqrtQ + Q))
invdeltaTsqrtQplusQ = 1/deltaTsqrtQplusQ
HSONLmult2 = invxi2*invdeltaTsqrtQplusQ
HSONLmult = expnuinvexpMU2*HSONLmult2
HSONLpt1b = pn*xi2
HSONLpt1a = (mur*pvr - nur*pvr + (-mucos + nucos)*HSONLpt1b)
HSONLpt1 = mur*pvr - (mucos*HSONLpt1b) + sqrtQ*HSONLpt1a
HSONLpt2d = nur*pxir
HSONLpt2c = oneplus2sqrtQ*HSONLpt2d
HSONLpt2b = B*sxi
HSONLpt2a = expMUexpnu*HSONLpt2c
HSONLpt2 = (sv*HSONLpt2a + HSONLpt1*HSONLpt2b)
HSONLpt3c = sv*pxir
HSONLpt3b = oneplus1sqrtQ*HSONLpt3c
HSONLpt3a = expMUexpnu*HSONLpt3b
HSONLpt3 = -BR*HSONLpt3a + B*HSONLpt2
HSONLpt4e = sn*xi2
HSONLpt4d = oneplus2sqrtQ*HSONLpt4e
HSONLpt4c = pxir*HSONLpt4d
HSONLpt4b = nucos*HSONLpt4c
HSONLpt4a = expMUexpnu*HSONLpt4b
HSONLpt4 = (-(B*HSONLpt4a) + HSONLpt3*sqrtdeltaR)
HSONL = HSONLmult*HSONLpt4
Hs = w*s3 + Hwr*wr + Hwcos*wcos + HSOL + HSONL
Hsspt1 = sp.Rational(-1,2)*(sx*sx + sy*sy + sz*sz - 3*sn*sn)
Hss = u3*Hsspt1
sKerrdotsStar = (sKerrUSCOREx*sStarUSCOREx + sKerrUSCOREy*sStarUSCOREy + sKerrUSCOREz*sStarUSCOREz)
Hpt1 = etau4*(s1dots1 + s2dots2)
H = Hns + Hs + Hss + (dheffSS*sKerrdotsStar + dheffSSv2)*Hpt1
Hreal = sp.sqrt(1 + 2*eta*(H - 1))
"""
```

```python
from outputC import *

# Split bigstring by carriage returns:
blah = bigstring.splitlines()

# Create "lr" array, which will store each left-hand side and right-hand side as strings.
lr = []
# Loop over each line in bigstring
for i in range(len(blah)):
    # Ignore lines with 2 or fewer characters and those starting with #
    if len(blah[i]) > 2 and blah[i][0] != "#":
        # Split each line by its equals sign.
        splitblah = blah[i].split("=")
        # Append to the "lr" array, removing spaces, "sp." prefixes, and replacing Lambda->Lamb
        #       (Lambda is a protected keyword):
        #lhs =
        lr.append(lhrh(lhs=splitblah[0].replace(" ","").replace("Lambda","Lamb"),
                       rhs=splitblah[1].replace(" ","").replace("sp.","").replace("Lambda","Lamb")))

# print(lr[1].lhs)

xx = sp.Symbol('xx')
func = []
lhss = []
rhss = []
for i in range(len(lr)):
    func.append(sp.sympify(sp.Function(lr[i].lhs)(xx)))
#     print(i,lr[i].rhs)
    lhss.append(sp.sympify(lr[i].lhs))
    rhss.append(sp.sympify(lr[i].rhs))

# Next get a list of all the "free symbols" in the RHS expressions.
full_symbol_list_with_dups = []
for i in range(len(lr)):
    for var in rhss[i].free_symbols:
        full_symbol_list_with_dups.append(var)

full_symbol_list = superfast_uniq(full_symbol_list_with_dups)

# Next declare independent variables:
# x,y,z,px,py,pz,s1x,s1y,s1z,s2x,s2y,s2z = sp.symbols("x y z px py pz s1x s1y s1z s2x s2y s2z",real=True)
# independent_variables = [x,y,z,px,py,pz,s1x,s1y,s1z,s2x,s2y,s2z]

# Next declare input constants:
m1,m2,eta = sp.symbols("m1 m2 eta",real=True)
c0k2,c1k2,c0k3,c1k3,c0k4,c1k4,c2k4,c0k5,c1k5,c2k5 = sp.symbols("c0k2 c1k2 c0k3 c1k3 c0k4 c1k4 c2k4 c0k5 c1k5 c2k5",real=True)
KK,k5l,b3,bb3,d1,d1v2,dheffSS,dheffSSv2 = sp.symbols("KK k5l b3 bb3 d1 d1v2 dheffSS dheffSSv2",real=True)
tortoise,copysignresult = sp.symbols("tortoise copysignresult",real=True)
input_constants = [m1,m2,eta,
                   c0k2,c1k2,c0k3,c1k3,c0k4,c1k4,c2k4,c0k5,c1k5,c2k5,
                   KK,k5l,b3,bb3,d1,d1v2,dheffSS,dheffSSv2,
                   tortoise,copysignresult]

# Derivatives of input constants will always be zero, so
# print(full_symbol_list)
for inputconst in input_constants:
    for symbol in full_symbol_list:
        if str(symbol) == str(inputconst):
            full_symbol_list.remove(symbol)

full_function_list = []
for symb in full_symbol_list:
    func = sp.sympify(sp.Function(str(symb))(xx))
    full_function_list.append(func)
    for i in range(len(rhss)):
        for var in rhss[i].free_symbols:
            if str(var) == str(symb):
                rhss[i] = rhss[i].subs(var,func)

lhss_deriv = []
rhss_deriv = []
for i in range(len(rhss)):
    lhss_deriv.append(sp.sympify(str(lhss[i])+"prm"))
    newrhs = sp.sympify(str(sp.diff(rhss[i],xx)).replace("(xx)","").replace(", xx","prm").replace("Derivative",""))
    rhss_deriv.append(newrhs)
    #     rhss_deriv.append(sp.diff(rhss[i],xx))

def simplify_deriv(lhss_deriv,rhss_deriv):
    lhss_deriv_simp = []
    rhss_deriv_simp = []
    for i in range(len(rhss_deriv)):
        lhss_deriv_simp.append(lhss_deriv[i])
        rhss_deriv_simp.append(rhss_deriv[i])
    for i in range(len(rhss_deriv_simp)):
        if rhss_deriv_simp[i] == 0:
            for j in range(i+1,len(rhss_deriv_simp)):
                for var in rhss_deriv_simp[j].free_symbols:
                    if str(var) == str(lhss_deriv_simp[i]):
                        rhss_deriv_simp[j] = rhss_deriv_simp[j].subs(var,0)
    zero_elements_to_remove = []
    for i in range(len(rhss_deriv_simp)):
        if rhss_deriv_simp[i] == sp.sympify(0):
            zero_elements_to_remove.append(i)
    count = 0
    for i in range(len(zero_elements_to_remove)):
        del lhss_deriv_simp[zero_elements_to_remove[i]+count]
        del rhss_deriv_simp[zero_elements_to_remove[i]+count]
        count -= 1
    return lhss_deriv_simp,rhss_deriv_simp

lhss_deriv_simp,rhss_deriv_simp = simplify_deriv(lhss_deriv,rhss_deriv)
lhss_deriv = lhss_deriv_simp
rhss_deriv = rhss_deriv_simp

def deriv_onevar(lhss_deriv,rhss_deriv,
                 xprm=0,yprm=0,zprm=0,pxprm=0,pyprm=0,pzprm=0,s1xprm=0,s1yprm=0,s1zprm=0,s2xprm=0,s2yprm=0,s2zprm=0):

    lhss_deriv_new = []
    rhss_deriv_new = []
    for i in range(len(rhss_deriv)):
        lhss_deriv_new.append(lhss_deriv[i])
        rhss_deriv_new.append(rhss_deriv[i])
    for i in range(len(rhss_deriv_new)):
        for var in rhss_deriv_new[i].free_symbols:
            if str(var)=="xprm":
                rhss_deriv_new[i] = rhss_deriv_new[i].subs(var,xprm)
            elif str(var)=="yprm":
                rhss_deriv_new[i] = rhss_deriv_new[i].subs(var,yprm)
            elif str(var)=="zprm":
                rhss_deriv_new[i] = rhss_deriv_new[i].subs(var,zprm)
            elif str(var)=="pxprm":
                rhss_deriv_new[i] = rhss_deriv_new[i].subs(var,pxprm)
            elif str(var)=="pyprm":
                rhss_deriv_new[i] = rhss_deriv_new[i].subs(var,pyprm)
            elif str(var)=="pzprm":
                rhss_deriv_new[i] = rhss_deriv_new[i].subs(var,pzprm)
            elif str(var)=="s1xprm":
                rhss_deriv_new[i] = rhss_deriv_new[i].subs(var,s1xprm)
            elif str(var)=="s1yprm":
                rhss_deriv_new[i] = rhss_deriv_new[i].subs(var,s1yprm)
            elif str(var)=="s1zprm":
                rhss_deriv_new[i] = rhss_deriv_new[i].subs(var,s1zprm)
            elif str(var)=="s2xprm":
                rhss_deriv_new[i] = rhss_deriv_new[i].subs(var,s2xprm)
            elif str(var)=="s2yprm":
                rhss_deriv_new[i] = rhss_deriv_new[i].subs(var,s2yprm)
            elif str(var)=="s2zprm":
                rhss_deriv_new[i] = rhss_deriv_new[i].subs(var,s2zprm)
    lhss_deriv_simp,rhss_deriv_simp = simplify_deriv(lhss_deriv_new,rhss_deriv_new)
    return lhss_deriv_simp,rhss_deriv_simp

lhss_deriv_x,rhss_deriv_x = deriv_onevar(lhss_deriv,rhss_deriv, \
                            xprm=1,yprm=0,zprm=0,pxprm=0,pyprm=0,pzprm=0,s1xprm=0,s1yprm=0,s1zprm=0,s2xprm=0,s2yprm=0,s2zprm=0)
lhss_deriv_y,rhss_deriv_y = deriv_onevar(lhss_deriv,rhss_deriv,
                            xprm=0,yprm=1,zprm=0,pxprm=0,pyprm=0,pzprm=0,s1xprm=0,s1yprm=0,s1zprm=0,s2xprm=0,s2yprm=0,s2zprm=0)
lhss_deriv_z,rhss_deriv_z = deriv_onevar(lhss_deriv,rhss_deriv,
                            xprm=0,yprm=0,zprm=1,pxprm=0,pyprm=0,pzprm=0,s1xprm=0,s1yprm=0,s1zprm=0,s2xprm=0,s2yprm=0,s2zprm=0)
lhss_deriv_px,rhss_deriv_px = deriv_onevar(lhss_deriv,rhss_deriv,
                            xprm=0,yprm=0,zprm=0,pxprm=1,pyprm=0,pzprm=0,s1xprm=0,s1yprm=0,s1zprm=0,s2xprm=0,s2yprm=0,s2zprm=0)
lhss_deriv_py,rhss_deriv_py = deriv_onevar(lhss_deriv,rhss_deriv,
                            xprm=0,yprm=0,zprm=0,pxprm=0,pyprm=1,pzprm=0,s1xprm=0,s1yprm=0,s1zprm=0,s2xprm=0,s2yprm=0,s2zprm=0)
lhss_deriv_pz,rhss_deriv_pz = deriv_onevar(lhss_deriv,rhss_deriv,
                            xprm=0,yprm=0,zprm=0,pxprm=0,pyprm=1,pzprm=1,s1xprm=0,s1yprm=0,s1zprm=0,s2xprm=0,s2yprm=0,s2zprm=0)
lhss_deriv_s1x,rhss_deriv_s1x = deriv_onevar(lhss_deriv,rhss_deriv,
                            xprm=0,yprm=0,zprm=0,pxprm=0,pyprm=0,pzprm=0,s1xprm=1,s1yprm=0,s1zprm=0,s2xprm=0,s2yprm=0,s2zprm=0)
lhss_deriv_s1y,rhss_deriv_s1y = deriv_onevar(lhss_deriv,rhss_deriv,
                            xprm=0,yprm=0,zprm=0,pxprm=0,pyprm=0,pzprm=0,s1xprm=0,s1yprm=1,s1zprm=0,s2xprm=0,s2yprm=0,s2zprm=0)
lhss_deriv_s1z,rhss_deriv_s1z = deriv_onevar(lhss_deriv,rhss_deriv,
                            xprm=0,yprm=0,zprm=0,pxprm=0,pyprm=0,pzprm=0,s1xprm=0,s1yprm=0,s1zprm=1,s2xprm=0,s2yprm=0,s2zprm=0)
lhss_deriv_s2x,rhss_deriv_s2x = deriv_onevar(lhss_deriv,rhss_deriv,
                            xprm=0,yprm=0,zprm=0,pxprm=0,pyprm=0,pzprm=0,s1xprm=0,s1yprm=0,s1zprm=0,s2xprm=1,s2yprm=0,s2zprm=0)
lhss_deriv_s2y,rhss_deriv_s2y = deriv_onevar(lhss_deriv,rhss_deriv,
                            xprm=0,yprm=0,zprm=0,pxprm=0,pyprm=0,pzprm=0,s1xprm=0,s1yprm=0,s1zprm=0,s2xprm=0,s2yprm=1,s2zprm=0)
lhss_deriv_s2z,rhss_deriv_s2z = deriv_onevar(lhss_deriv,rhss_deriv,
                            xprm=0,yprm=0,zprm=0,pxprm=0,pyprm=0,pzprm=0,s1xprm=0,s1yprm=0,s1zprm=0,s2xprm=0,s2yprm=0,s2zprm=1)

# CSE_results = sp.cse(rhss_deriv_px, sp.numbered_symbols("tmp"), order='canonical')
# for commonsubexpression in CSE_results[0]:
#     print("  "+str(commonsubexpression[0])+" = "+str(commonsubexpression[1]))
# for i,result in enumerate(CSE_results[1]):
#     print("rhss_deriv_px = "+str(result))

#for commonsubexpression in CSE_results[0]:

outstring = "/* SEOBNR Hamiltonian expression: */\n"
outstringsp = ""
outsplhs = []
outsprhs = []
for i in range(len(lr)):
    outstring += outputC(sp.sympify(lr[i].rhs),lr[i].lhs,"returnstring","outCverbose=False,includebraces=False,CSE_enable=False")
    outstringsp += lr[i].lhs+" = "+lr[i].rhs+"\n"
    outsplhs.append(sp.sympify(lr[i].lhs))
    outsprhs.append(sp.sympify(lr[i].rhs))
outstring += "\n\n\n/* SEOBNR \partial_x H expression: */\n"
for i in range(len(lhss_deriv_x)):
    outstring += outputC(rhss_deriv_x[i],str(lhss_deriv_x[i]),"returnstring","outCverbose=False,includebraces=False,CSE_enable=False")
    outstringsp += str(lhss_deriv_x[i])+" = "+str(rhss_deriv_x[i])+"\n"
    outsplhs.append(lhss_deriv_x[i])
    outsprhs.append(rhss_deriv_x[i])

# for i in range(len(outsplhs)):
#     for j in range(i+1,len(outsplhs)):
#         outsprhs[j] = outsprhs[j].subs(outsplhs[i],outsprhs[i])
with open("/tmp/sympy_expression.py","w") as file:
    file.write("""
import sympy as sp
from outputC import *

m1,m2,x,y,z,px,py,pz,s1x,s1y,s1z,s2x,s2y,s2z,eta = sp.symbols("m1 m2 x y z px py pz s1x s1y s1z s2x s2y s2z eta",real=True)
c0k2,c1k2,c0k3,c1k3,c0k4,c1k4,c2k4,c0k5,c1k5,c2k5 = sp.symbols("c0k2 c1k2 c0k3 c1k3 c0k4 c1k4 c2k4 c0k5 c1k5 c2k5",real=True)
KK,k5l,b3,bb3,d1,d1v2,dheffSS,dheffSSv2 = sp.symbols("KK k5l b3 bb3 d1 d1v2 dheffSS dheffSSv2",real=True)
tortoise,copysignresult = sp.symbols("tortoise copysignresult",real=True)

""")
    for i in range(len(lr)):
        file.write(lr[i].lhs+" = "+"sp.symbols(\""+lr[i].lhs+"\")\n")
    file.write("\n")
    for i in range(len(lhss_deriv_x)):
        file.write(str(lhss_deriv_x[i]).replace("prm","prm_x")+" = "+str(rhss_deriv_x[i]).replace("sqrt(","sp.sqrt(").replace("log(","sp.log(").replace("Abs(","sp.Abs(").replace("prm","prm_x")+"\n")
    for i in range(len(lhss_deriv_y)):
        file.write(str(lhss_deriv_y[i]).replace("prm","prm_y")+" = "+str(rhss_deriv_y[i]).replace("sqrt(","sp.sqrt(").replace("log(","sp.log(").replace("Abs(","sp.Abs(").replace("prm","prm_y")+"\n")
    for i in range(len(lhss_deriv_z)):
        file.write(str(lhss_deriv_z[i]).replace("prm","prm_z")+" = "+str(rhss_deriv_z[i]).replace("sqrt(","sp.sqrt(").replace("log(","sp.log(").replace("Abs(","sp.Abs(").replace("prm","prm_z")+"\n")

    for i in range(len(lhss_deriv_px)):
        file.write(str(lhss_deriv_px[i]).replace("prm","prm_px")+" = "+str(rhss_deriv_px[i]).replace("sqrt(","sp.sqrt(").replace("log(","sp.log(").replace("Abs(","sp.Abs(").replace("prm","prm_px")+"\n")
    for i in range(len(lhss_deriv_py)):
        file.write(str(lhss_deriv_py[i]).replace("prm","prm_py")+" = "+str(rhss_deriv_py[i]).replace("sqrt(","sp.sqrt(").replace("log(","sp.log(").replace("Abs(","sp.Abs(").replace("prm","prm_py")+"\n")
    for i in range(len(lhss_deriv_pz)):
        file.write(str(lhss_deriv_pz[i]).replace("prm","prm_pz")+" = "+str(rhss_deriv_pz[i]).replace("sqrt(","sp.sqrt(").replace("log(","sp.log(").replace("Abs(","sp.Abs(").replace("prm","prm_pz")+"\n")

    for i in range(len(lhss_deriv_s1x)):
        file.write(str(lhss_deriv_s1x[i]).replace("prm","prm_s1x")+" = "+str(rhss_deriv_s1x[i]).replace("sqrt(","sp.sqrt(").replace("log(","sp.log(").replace("Abs(","sp.Abs(").replace("prm","prm_s1x")+"\n")
    for i in range(len(lhss_deriv_s1y)):
        file.write(str(lhss_deriv_s1y[i]).replace("prm","prm_s1y")+" = "+str(rhss_deriv_s1y[i]).replace("sqrt(","sp.sqrt(").replace("log(","sp.log(").replace("Abs(","sp.Abs(").replace("prm","prm_s1y")+"\n")
    for i in range(len(lhss_deriv_s1z)):
        file.write(str(lhss_deriv_s1z[i]).replace("prm","prm_s1z")+" = "+str(rhss_deriv_s1z[i]).replace("sqrt(","sp.sqrt(").replace("log(","sp.log(").replace("Abs(","sp.Abs(").replace("prm","prm_s1z")+"\n")

    for i in range(len(lhss_deriv_s2x)):
        file.write(str(lhss_deriv_s2x[i]).replace("prm","prm_s2x")+" = "+str(rhss_deriv_s2x[i]).replace("sqrt(","sp.sqrt(").replace("log(","sp.log(").replace("Abs(","sp.Abs(").replace("prm","prm_s2x")+"\n")
    for i in range(len(lhss_deriv_s2y)):
        file.write(str(lhss_deriv_s2y[i]).replace("prm","prm_s2y")+" = "+str(rhss_deriv_s2y[i]).replace("sqrt(","sp.sqrt(").replace("log(","sp.log(").replace("Abs(","sp.Abs(").replace("prm","prm_s2y")+"\n")
    for i in range(len(lhss_deriv_s2z)):
        file.write(str(lhss_deriv_s2z[i]).replace("prm","prm_s2z")+" = "+str(rhss_deriv_s2z[i]).replace("sqrt(","sp.sqrt(").replace("log(","sp.log(").replace("Abs(","sp.Abs(").replace("prm","prm_s2z")+"\n")
    file.write("""
sp.cse(Hrealprm_x)
# outputC([Hrealprm_x,Hrealprm_y,Hrealprm_z,Hrealprm_px,Hrealprm_py,Hrealprm_pz,
#         Hrealprm_s1x,Hrealprm_s1y,Hrealprm_s1z,Hrealprm_s2x,Hrealprm_s2y,Hrealprm_s2z],
#        ["Hrealprm_x","Hrealprm_y","Hrealprm_z","Hrealprm_px","Hrealprm_py","Hrealprm_pz",
#         "Hrealprm_s1x","Hrealprm_s1y","Hrealprm_s1z","Hrealprm_s2x","Hrealprm_s2y","Hrealprm_s2z"],
#         "/tmp/outC.h","outCverbose=False,includebraces=False,SIMD_enable=True")
    """)

```

```python
# !cp /tmp/sympy_expression.py .
# !pypy sympy_expression.py
```

```python
!cp /tmp/sympy_expression.py .
!pypy sympy_expression.py
```

```python
# with open("/tmp/outC.h","r") as file:
#     file.read()
```

```python

```


# US Production Data for RBC Modeling

```python
import pandas as pd
import numpy as np
import fredpy as fp
import matplotlib.pyplot as plt
plt.style.use('classic')
%matplotlib inline
```

```python
# Load API key
fp.api_key = fp.load_api_key('fred_api_key.txt')

# Download nominal GDP, nominal personal consumption expenditures, nominal
# gross private domestic investment, the GDP deflator, and an index of hours
# worked in the nonfarm business sector produced by the BLS. All data are
# from FRED and are quarterly.
gdp = fp.series('GDP')
cons = fp.series('PCEC')
invest = fp.series('GPDI')
hours = fp.series('HOANBS')
defl = fp.series('GDPDEF')

# Make sure that all of the downloaded series have the same data ranges
gdp,cons,invest,hours,defl = fp.window_equalize([gdp,cons,invest,hours,defl])

# Compute real GDP, real consumption, real investment
gdp.data = gdp.data/defl.data*100
cons.data = cons.data/defl.data*100
invest.data = invest.data/defl.data*100
```

Next, compute the quarterly capital stock series for the US using the perpetual inventory method. The discrete-time Solow growth model is given by:

\begin{align}
Y_t & = A_tK_t^{\alpha}L_t^{1-\alpha} \tag{1}\\
C_t & = (1-s)Y_t \tag{2}\\
Y_t & = C_t + I_t \tag{3}\\
K_{t+1} & = I_t + (1-\delta)K_t \tag{4}\\
A_{t+1} & = (1+g)A_t \tag{5}\\
L_{t+1} & = (1+n)L_t \tag{6}.
\end{align}

Here the model is assumed to be quarterly so $n$ is the *quarterly* growth rate of labor hours, $g$ is the *quarterly* growth rate of TFP, and $\delta$ is the *quarterly* rate of depreciation of the capital stock. Given a value of the quarterly depreciation rate $\delta$, an investment series $I_t$, and an initial capital stock $K_0$, the law of motion for the capital stock, Equation (4), can be used to compute an implied capital series. But we don't know $K_0$ or $\delta$ so we'll have to *calibrate* these values using statistics computed from the data that we've already obtained.

Let lowercase letters denote a variable that's been divided by $A_t^{1/(1-\alpha)}L_t$. E.g.,

\begin{align}
y_t = \frac{Y_t}{A_t^{1/(1-\alpha)}L_t}\tag{7}
\end{align}

Then (after substituting consumption from the model), the scaled version of the model can be written as:

\begin{align}
y_t & = k_t^{\alpha} \tag{8}\\
i_t & = sy_t \tag{9}\\
k_{t+1} & = i_t + (1-\delta-n-g')k_t,\tag{10}
\end{align}

where $g' = g/(1-\alpha)$ is the growth rate of $A_t^{1/(1-\alpha)}$. In the steady state:

\begin{align}
k & = \left(\frac{s}{\delta+n+g'}\right)^{\frac{1}{1-\alpha}} \tag{11}
\end{align}

which means that the ratio of capital to output is constant:

\begin{align}
\frac{k}{y} & = \frac{s}{\delta+n+g'} \tag{12}
\end{align}

and therefore the steady state ratio of depreciation to output is:

\begin{align}
\overline{\delta K/ Y} & = \frac{\delta s}{\delta + n + g'} \tag{13}
\end{align}

where $\overline{\delta K/ Y}$ is the long-run average ratio of depreciation to output. We can use Equation (13) to calibrate $\delta$ given $\overline{\delta K/ Y}$, $s$, $n$, and $g'$.

Furthermore, in the steady state, the growth rate of output is constant:

\begin{align}
\frac{\Delta Y}{Y} & = n + g' \tag{14}
\end{align}

1. Assume $\alpha = 0.35$.
2. Calibrate $s$ as the average of ratio of investment to GDP.
3. Calibrate $n$ as the average quarterly growth rate of labor hours.
4. Calibrate $g'$ as the average quarterly growth rate of real GDP minus n.
5. Calculate the average ratio of depreciation to GDP $\overline{\delta K/ Y}$ and use the result to calibrate $\delta$. That is, find the average ratio of Current-Cost Depreciation of Fixed Assets (FRED series ID: M1TTOTL1ES000) to GDP (FRED series ID: GDPA). Then calibrate $\delta$ from the following steady state relationship:
\begin{align}
\delta & = \frac{\left( \overline{\delta K/ Y} \right)\left(n + g' \right)}{s - \left( \overline{\delta K/ Y} \right)} \tag{15}
\end{align}
6. Calibrate $K_0$ by asusming that the capital stock is initially equal to its steady state value:
\begin{align}
K_0 & = \left(\frac{s}{\delta + n + g'}\right) Y_0 \tag{16}
\end{align}

Then, armed with calibrated values for $K_0$ and $\delta$, compute $K_1, K_2, \ldots$ recursively. See Timothy Kehoe's notes for more information on the perpetual inventory method:

http://users.econ.umn.edu/~tkehoe/classes/GrowthAccountingNotes.pdf

```python
# Set the capital share of income
alpha = 0.35

# Average saving rate
s = np.mean(invest.data/gdp.data)

# Average quarterly labor hours growth rate
n = (hours.data[-1]/hours.data[0])**(1/(len(hours.data)-1)) - 1

# Average quarterly real GDP growth rate
g = ((gdp.data[-1]/gdp.data[0])**(1/(len(gdp.data)-1)) - 1) - n

# Compute annual depreciation rate
depA = fp.series('M1TTOTL1ES000')
gdpA = fp.series('gdpa')

gdpA = gdpA.window([gdp.dates[0],gdp.dates[-1]])
gdpA,depA = fp.window_equalize([gdpA,depA])

deltaKY = np.mean(depA.data/gdpA.data)
delta = (n+g)*deltaKY/(s-deltaKY)

# print calibrated values:
print('Avg saving rate:        ',round(s,5))
print('Avg annual labor growth:',round(4*n,5))
print('Avg annual gdp growth:  ',round(4*g,5))
print('Avg annual dep rate:    ',round(4*delta,5))

# Construct the capital series. Note that the GPD and investment data are reported on an annualized basis
# so divide by 4 to get quarterly data.
capital = np.zeros(len(gdp.data))
capital[0] = gdp.data[0]/4*s/(n+g+delta)

for t in range(len(gdp.data)-1):
    capital[t+1] = invest.data[t]/4 + (1-delta)*capital[t]

# Save in a fredpy series
capital = fp.toFredSeries(data = capital,dates =gdp.dates,units = gdp.units,title='Capital stock of the US',frequency='Quarterly')

# plot the computed capital series
plt.plot(capital.datetimes,capital.data,'-',lw=3,alpha = 0.7)
plt.ylabel(capital.units)
plt.title(capital.title)
plt.grid()
```

```python
# Compute TFP
tfp = gdp.data/capital.data**alpha/hours.data**(1-alpha)
tfp = fp.toFredSeries(data = tfp,dates =gdp.dates,units = gdp.units,title='TFP of the US',frequency='Quarterly')

# Plot the computed capital series
plt.plot(tfp.datetimes,tfp.data,'-',lw=3,alpha = 0.7)
plt.ylabel(tfp.units)
plt.title(tfp.title)
plt.grid()
```

```python
# Convert each series into per capita using civilian pop 16 and over
gdp = gdp.percapita(civ_pop=True)
cons = cons.percapita(civ_pop=True)
invest = invest.percapita(civ_pop=True)
hours = hours.percapita(civ_pop=True)
capital = capital.percapita(civ_pop=True)

# Put GDP, consumption, and investment in units of thousands of dollars per person
gdp.data = gdp.data*1000
cons.data = cons.data*1000
invest.data = invest.data*1000
capital.data = capital.data*1000

# Scale hours per person to equal 100 on October (Quarter III) of 2009
hours.data = hours.data/hours.data[hours.dates.index('2009-10-01')]*100

# Make sure TFP series has same length as the rest (since the .percapita() function may affect the date range.
tfp,gdp = fp.window_equalize([tfp,gdp])

# Compute and plot log real GDP, log consumption, log investment, log hours
gdp_log = gdp.log()
cons_log = cons.log()
invest_log = invest.log()
hours_log = hours.log()
capital_log = capital.log()
tfp_log = tfp.log()

# HP filter to isolate trend and cyclical components
gdp_log_cycle,gdp_log_trend = gdp_log.hpfilter()
cons_log_cycle,cons_log_trend = cons_log.hpfilter()
invest_log_cycle,invest_log_trend = invest_log.hpfilter()
hours_log_cycle,hours_log_trend = hours_log.hpfilter()
capital_log_cycle,capital_log_trend = capital_log.hpfilter()
tfp_log_cycle,tfp_log_trend = tfp_log.hpfilter()
```

```python
# Create a DataFrame with actual and trend data
data = pd.DataFrame({
        'gdp':gdp.data,
        'gdp_trend':np.exp(gdp_log_trend.data),
        'consumption':cons.data,
        'consumption_trend':np.exp(cons_log_trend.data),
        'investment':invest.data,
        'investment_trend':np.exp(invest_log_trend.data),
        'hours':hours.data,
        'hours_trend':np.exp(hours_log_trend.data),
        'capital':capital.data,
        'capital_trend':np.exp(capital_log_trend.data),
        'tfp':tfp.data,
        'tfp_trend':np.exp(tfp_log_trend.data),
    },index = pd.DatetimeIndex(gdp.dates))

columns_ordered =[]
names = ['gdp','consumption','investment','hours','capital','tfp']
for name in names:
    columns_ordered.append(name)
    columns_ordered.append(name+'_trend')

data[columns_ordered].to_csv('../Csv/rbc_data.csv')
```


```python
from sympy import *
init_printing() # For Sympy
from IPython.display import display
```

## Simple Pade form for the Jastrow factor.

The value of $A$ is fixed by the cusp condition at $r=0$.

```python
A, B, r = symbols('A B r')
u = A*r/(1.0 + B*r) - A/B
sym_u = Symbol('u')
display(Eq(sym_u,u))
```

```python
# Symbolic derivatives
du = diff(u, r)
du = simplify(du)
sym_du = Symbol('du')/Symbol('dr')
display(Eq(sym_du, du))
ddu = simplify(diff(u, r, 2))
sym_ddu = Symbol('d^2')*Symbol('u')/Symbol('dr^2')
display(Eq(sym_ddu, ddu))

```

```python
# Substitute concrete values to evaluate
vals = {A:-0.25, B: 0.1, r:1.0}
display(Eq(sym_u, u.subs(vals)))
display(Eq(sym_du, du.subs(vals)))
display(Eq(sym_ddu, ddu.subs(vals)))
```

```python

```


```python
# For this coding problem, we are about to simulate a deep learning
# training process.
# In computer vision field, classification is a very important
# and traditional field. Assume that we have a some pseudo data input
# for a mlp (multi-layer perceptron) model.

# Q1: How should we arrange the sequence of fully-connected layer, activation
# layer, batch normalization layer? Why?
# x - relu - fc - bn
# x - relu - bn - fc
# x - bn - fc - relu
# x - bn - relu - fc
# x - fc - relu - bn
# x - fc - bn - relu

# Q2: What is the role for batch normalization playing for?

# Q3: Assume we need to define a 2-layer mlp.
# We have our pseudo data  x in shape of [batch_size, input_size]
# We have our pseudo label y in shape of [batch_size, num_classes](one-hot vector)
# We need you to !!!only!!! use numpy to build a training pipeline,
# 1. Use Sigmoid as your activation function
# 2. Use cross entropy loss
# 3. No batch normalization
# 4. Write your own back propogation progess to update your weights of fc layers.
# 5. Using SGD optimizer algorithm
# 6. Don't forget to do softmax in final output
# 7. Finish the api we given to you

import numpy as np

# ============================== Question ==============================
class MyMLP():
    def __init__(
        self,
        input_size: int,
        hidden_size: int,
        num_classes: int,
        lr: float,
        batch_size: int = 1,
    ):
        self.input_size = input_size
        self.num_classes = num_classes
        self.batch_size = batch_size
        self.lr = lr

        self.w1 = np.random.randn(hidden_size, input_size)
        self.w2 = np.random.randn(num_classes, hidden_size)

    def generate_data(self):
        x = np.random.randn(self.batch_size, self.input_size) # [bs, input_size]
        y = np.random.randint(0, self.num_classes, (self.batch_size, )) # [bs,]
        y = np.eye(self.num_classes)[y] # [bs, num_cls]
        return x, y

    def sigmoid(self, x: np.ndarray) -> np.ndarray:
        # y = sigmoid(x)
        return None

    def partial_sigmoid(self, x: np.ndarray) -> np.ndarray:
        # y' = d(sigmoid(x))/d(x)
        return None

    def softmax(self, x: np.ndarray) -> np.ndarray:
        # x = [batch_size, dim] -> do softmax in column-wise dimension
        return None

    def forward(self, x: np.ndarray) -> np.ndarray:
        # Two layer mlp
        # x -> fc -> sigmoid -> fc -> softmax
        # Notice that we use the formulation y = x W.T (without bias)
        self.a1 = None# fc1 output
        self.h1 = None# sigmoid output
        self.a2 = None# fc2 output
        self.out = None# softmax output
        return None

    def loss(self, y_hat: np.ndarray, y: np.ndarray) -> np.ndarray:
        # yhat
        # cross entropy loss
        return None

    def bp(self, x: np.ndarray, y:np.ndarray) -> np.ndarray:
        # Update w1 and w2
        self.dl_da2 = None # partial loss to partial a2
        self.dl_dw2 = None# partial loss to partial w2
        self.dl_dh1 = None# partial loss to partial h1
        self.dl_da1 = None# partial loss to partial a1
        self.d1_dw1 = None# partial loss to partial w1

        # SGD Update w1 and w2:
        self.w1 = None
        self.w2 = None
        return None

    def matrix_differential_propogation(self, x: np.ndarray, y: np.ndarray) -> np.ndarray:
        # Bonus
        # Matrix-Matrix function derivatives
        # Double check for gradient
        return None

if __name__ == '__main__':
    np.random.seed(2022)
    mymlp = MyMLP(input_size=4, hidden_size=10, num_classes=3, lr=0.01, batch_size=1)
    x, y = mymlp.generate_data()

    # Step 1
    # check your forward result:
    out = mymlp.forward(x)
    assert np.linalg.norm(out - np.array([[0.36235674, 0.40728367, 0.23035959]])) < 1e-7

    # Step 2
    # check your loss
    loss = mymlp.loss(out, y)
    assert np.linalg.norm(loss - np.array([[-1.01512607, -0. ,        -0.,        ]])) < 1e-7

    w1, w2 = mymlp.bp(x, y)
    # Step 2
    # check your gradients:
    assert np.linalg.norm(mymlp.dl_da2 - np.array([[-0.63764326,  0.40728367,  0.23035959]])) < 1e-7
    assert np.linalg.norm(mymlp.dl_dw2 - np.array([[-0.40732181, -0.47931036, -0.15217421, -0.03860381, -0.09738246, -0.36582423,
        -0.57306758, -0.36619898, -0.53017721, -0.32491802],
        [ 0.2601698,   0.30615125,  0.09719866,  0.02465752,  0.06220137,  0.23366394,
        0.36603706,  0.2339033,   0.33864158,  0.2075358 ],
        [ 0.14715201,  0.17315911,  0.05497555,  0.01394629,  0.03518109,  0.13216029,
        0.20703051,  0.13229568,  0.19153563,  0.11738222]])) < 1e-7
    assert np.linalg.norm(mymlp.dl_dh1 - np.array([[-0.22072836,  0.54220196,  0.2838984,  -0.24103344, -1.74668689, -0.65096623,
        -0.33832413, -0.18948642,  0.76756801,  0.07541716]]))
    assert np.linalg.norm(mymlp.dl_da1 - np.array([[-0.05093011,  0.10120303,  0.05158341, -0.01370905, -0.22601836, -0.15920446,
        -0.03079302, -0.04632553,  0.1075607,   0.0188474 ]]))
    assert np.linalg.norm(mymlp.dl_dw1 - np.array([[ 0.03771251, -0.04810758, -0.05592065, -0.02520829],
        [-0.07493837,  0.09559438,  0.11111971,  0.05009129],
        [-0.03819625,  0.04872466,  0.05663796,  0.02553164],
        [ 0.01015122, -0.0129493,  -0.01505237, -0.00678541],
        [ 0.16736108, -0.21349247, -0.24816545, -0.11186968],
        [ 0.11788702, -0.15038138, -0.17480459, -0.07879958],
        [ 0.02280148, -0.02908648, -0.03381036, -0.01524126],
        [ 0.03430292, -0.04375818, -0.05086487, -0.02292921],
        [-0.07964608,  0.10159971,  0.11810036,  0.05323807],
        [-0.01395604,  0.01780288,  0.02069421,  0.00932868]])) < 1e-7

    # Step 3
    # check your updated weight result
    assert np.linalg.norm(w1 - np.array([[-0.00090502, -0.27442035, -0.13872636,  1.98493824],
            [ 0.28285871,  0.75985271,  0.29987041,  0.53979636],
            [ 0.37387925,  0.37732615, -0.09077957, -2.30619859],
            [ 1.14265851, -1.53552479, -0.86360149,  1.01661279],
            [ 1.03229027, -0.8223573,   0.02138651, -0.38222486],
            [-0.30536435,  0.99879532, -0.12552579, -1.47509791],
            [-1.94113434,  0.83393979, -0.56687978,  1.17463937],
            [ 0.3187258,   0.19130801,  0.36977883, -0.10091857],
            [-0.94101303, -1.40515771,  2.07946601, -0.12084862],
            [ 0.75993144,  1.82725411, -0.66093403, -0.80789955]])) < 1e-7
    assert np.linalg.norm(w2 - np.array([[ 0.89187333, -0.21265435, -0.93800277,  0.59992435,  2.22408652,  1.0036637,
        1.15540522, -0.1519143,  -1.64527408, -1.45186499],
        [ 0.31815989,  0.80828812, -0.24177658,  0.16487451, -0.03412259,  0.08552794,
        1.03048616, -1.06316559, -1.01697087, -0.42230754],
        [ 0.93068173,  0.31559234, -0.943021,    0.32112784, -1.36990554, -0.21448964,
        -0.11681657,  0.62104636,  0.55332894, -2.95860606]])) < 1e-7

    dl_dw2 = mymlp.matrix_differential_propogation(x, y)
    assert np.linalg.norm(dl_dw2 - np.array([[-0.40732181, -0.47931036, -0.15217421, -0.03860381, -0.09738246, -0.36582423,
        -0.57306758, -0.36619898, -0.53017721, -0.32491802],
        [ 0.2601698,   0.30615125,  0.09719866,  0.02465752,  0.06220137,  0.23366394,
        0.36603706,  0.2339033,   0.33864158,  0.2075358 ],
        [ 0.14715201,  0.17315911,  0.05497555,  0.01394629,  0.03518109,  0.13216029,
        0.20703051,  0.13229568,  0.19153563,  0.11738222]])) < 1e-7

```

Some reference for matrix derivates:
   $$
   \begin{align}
   &d(X\pm Y) = dX \pm dY,d(XY)=d XY+X dY \\
   &d X\odot Y=d X \odot Y+X\odot dY \\
   &d(\sigma(X))=\sigma'(X)\odot dX \\
   &tr(AB) = tr(BA) \\
   &tr(A^T(B\odot C))=tr((A\odot B)^TC), A,B,C\in \mathbb R^{n\times n} \\
   &df=tr(\frac{\partial f}{\partial X}^T dX) \\
   \end{align}
   $$

$$
\begin{align}
&\text{vec}(A + B)=\text{vec}(A)+\text{vec}(B) \\
&\text{vec}(AXB) = (B^T \otimes A)\text{vec}(X) \\
&\text{vec}(A^T)=K_{mn}\text{vec}(A),A\in \mathbb R^{m\times n},K_{mn}\in \mathbb R^{mn\times mn}, \quad K_{mn} \text{  is commutation matrix} \\
&\text{vec}(A\odot X)=\text{dial}(A)\text{vec}(X), \text{\quad diag}(A)\in \mathbb R^{mn\times mn} \text{  is a diagonal use elements in A, oredered by columns} \\
&(A\otimes B)^T=A^T\otimes B^T \\
&\text{vec}(ab^T)=b\otimes a \\
&(A\otimes B)(C\otimes D)=(AC)\otimes(BD) \\
&K_{mn}=K^T_{nm}, K_{mn}K_{nm}=I \\
&K_{pm}(A\otimes B)K_{nq}=B\otimes A, A\in \mathbb R^{m\times n}, B\in \mathbb R^{p\times q} \\
\end{align}
$$


# Quadcopter 2D
## Summary
This notebook outlines a position controller for a quadcopter in 2D space.

## Goals
The ultimate goal is to apply the control system to a simulated environment - for this I have chosen Python and specifially [pymunk](http://www.pymunk.org) as the 2D physics simulator and [pyglet](http://www.pyglet.org) as the game engine to render the results.

### Control System Requirements
The design criteria for the quadrotor is to reach a step input (1, 1):
* Settling time for y, z and Θ of less than 5 seconds
* Rise time for y, z and Θ of less than 2 seconds
* Overshoot of y, and z less than 5%

## System Description
We will use the following diagram to derive the equations of motion:

* $\theta$ is 0 radians when the quadcopter is level and is positive in the CCW direction
* $y, z$ are the quadcopter's center of mass position
* $F_1, F_2$ are the force from the right and left rotors respectively
* $u_1$ is the sum of thrusts and is equal to $F_1 + F_2$
* $u_2$ is the sum of moments and is equal to $l(F_1 - F_2)$
* $I$ is the quadcopter moment of inertia
* $m$ is the mass of the quadcopter, in kg
* $l$ is the distance of the rotors with respect to the center of mass
* $g$ is gravity

### Equations of Motion
The equations of motion for this system are derived using the Lagrangian and Euler equations. This derivation references the following sources:
* [Brilliant: Lagrangian Mechanics](https://brilliant.org/wiki/lagrangian-formulation-of-mechanics/)

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

sympy.init_printing()
```

```python
# create our functions for y, z and Θ against time, t
t = sympy.symbols('t')
y = sympy.Function('y')(t)
z = sympy.Function('z')(t)
Θ = sympy.Function('Θ')(t)

# constants: length from CoM to rotor, mass, inertia, gravity, force from left rotor, force from right rotor
l, m, I, g, F1, F2 = sympy.symbols('l m I g F1 F2')

# velocity of the drone is made up of velocity in y and z directions
v = sympy.sqrt(y.diff(t)**2 + z.diff(t)**2)

# kinetic energy of the drone subtract potential energy
L = sympy.Rational(1, 2)*(m*v**2 + I*Θ.diff(t)**2) - m*g

# print out the Lagrangian
L
```

```python
# euler equations
euler1 = L.diff(y.diff(t)).diff(t) - L.diff(y)
euler2 = L.diff(z.diff(t)).diff(t) - L.diff(z)
euler3 = L.diff(Θ.diff(t)).diff(t) - L.diff(Θ)

# create the equations, first arg is LHS, second is RHS
eq1 = sympy.Eq(euler1, -(F1 + F2)*sympy.sin(Θ))
eq2 = sympy.Eq(euler2, (F1 + F2)*sympy.cos(Θ) - m*g)
eq3 = sympy.Eq(euler3, (F1-F2)*l)

# print out the equations
eq1, eq2, eq3
```

```python
# linearize our system for hover
# sin(Θ) = Θ, cos(Θ) = 1, (d/dt(Θ))**2 = 0
linearize = [(sympy.sin(Θ), Θ), (sympy.cos(Θ), 1), (Θ.diff(t)**2, 0)]
eq1_lin = eq1.subs(linearize)
eq2_lin = eq2.subs(linearize)
eq3_lin = eq3.subs(linearize)

# print out the linearized equations
eq1_lin, eq2_lin, eq3_lin
```

```python
# solve our linear system for linear and angular accelerations
eq_of_mot = sympy.linsolve([eq1_lin, eq2_lin, eq3_lin], [y.diff(t, t), z.diff(t, t), Θ.diff(t, t)])

eq_of_mot
```

#### State Space Representation

*NOTE: linearized equations for hover*

$x(t) =
\begin{bmatrix}
x_1 \\
x_2 \\
x_3 \\
x_4 \\
x_5 \\
x_6 \\
\end{bmatrix} =
\begin{bmatrix}
y \\
z \\
\theta \\
\dot{y} \\
\dot{z} \\
\dot{\theta} \\
\end{bmatrix}$

$\dot{x(t)} =
\begin{bmatrix}
\dot{x_1} \\
\dot{x_2} \\
\dot{x_3} \\
\dot{x_4} \\
\dot{x_5} \\
\dot{x_6} \\
\end{bmatrix} =
\begin{bmatrix}
x_4 \\
x_5 \\
x_6 \\
\ddot{y} \\
\ddot{z} \\
\ddot{\theta} \\
\end{bmatrix} =
\begin{bmatrix}
\dot{y} \\
\dot{z} \\
\dot{\theta} \\
\dfrac{-u_1\theta}{m} \\
\dfrac{u_1}{m} - g \\
\dfrac{u_2}{I}
\end{bmatrix}=
\begin{bmatrix}
x_4 \\
x_5 \\
x_6 \\
\dfrac{-u_1x_3}{m} \\
\dfrac{u_1}{m} - g \\
\dfrac{u_2}{I}
\end{bmatrix}$

where:

$u =
\begin{bmatrix}
u_1 \\
u_2
\end{bmatrix} =
\begin{bmatrix}
F_1 + F_2 \\
l(F_1 - F_2)
\end{bmatrix}$

```python
# define our ivp function using our state space representation
def quadcopter(t, y, F1, F2, constants):
    u1 = F1 + F2
    u2 = constants['l'] * (F1 - F2)
    x1dot = y[3]
    x2dot = y[4]
    x3dot = y[5]
    x4dot = (-u1 * y[2]) / constants['m']
    x5dot = (u1 / constants['m']) - constants['g']
    x6dot = u2 / constants['I']
    return [x1dot, x2dot, x3dot, x4dot, x5dot, x6dot]
```

```python
from scipy.integrate import solve_ivp

y0 = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]

F1, F2 = 0.0, 0.0
constants = dict(
    l = 0.15,  # 15 cm from CoM to rotor
    m = 0.2,   # 200 g
    I = 0.001, # kg*m^2
    g = 9.81   # gravity, m/s^2
)
args = (F1, F2, constants)
sol = solve_ivp(quadcopter, [0, 10], y0, args=args, dense_output=True)
```

```python
def plot_all(title, t, z):
    plt.figure(figsize=(20, 12))

    plt.subplot(2, 3, 1)
    plt.plot(t, z.T[:,0], 'r')
    plt.xlabel('time (s)')
    plt.ylabel('Y position (m)')
    plt.title(title)
    plt.grid(True)

    plt.subplot(2, 3, 2)
    plt.plot(t, z.T[:,1], 'b')
    plt.xlabel('time (s)')
    plt.ylabel('Z position (m)')
    plt.grid(True)

    plt.subplot(2, 3, 3)
    plt.plot(t, z.T[:,2], 'g')
    plt.xlabel('time (s)')
    plt.ylabel('Θ angle (radians)')
    plt.grid(True)

    plt.subplot(2, 3, 4)
    plt.plot(t, z.T[:,3], 'y')
    plt.xlabel('time (s)')
    plt.ylabel('Y velocity (m/s)')
    plt.grid(True)

    plt.subplot(2, 3, 5)
    plt.plot(t, z.T[:,4], 'm')
    plt.xlabel('time (s)')
    plt.ylabel('Z velocity (m/s)')
    plt.grid(True)

    plt.subplot(2, 3, 6)
    plt.plot(t, z.T[:,5], 'c')
    plt.xlabel('time (s)')
    plt.ylabel('Θ angular velocity (radians/s)')
    plt.grid(True)

    plt.xlabel('time (s)')
    plt.show()
```

```python
t = np.linspace(0, 10, 100)
z = sol.sol(t)

# we expect the quadcopter to fall from the sky, z decreasing exponentially, and zdot (velocity) increasing linearly, all other states should be 0
plot_all('Quadcopter Falling', t, z)
```

```python
# each rotor will exert half of the exact force to keep it hovering
hover_force = constants['g'] * constants['m'] / 2

# F1, F2, l, m, I, g
args = (hover_force, hover_force, constants)
sol = solve_ivp(quadcopter, [0, 10], y0, args=args, dense_output=True)

t = np.linspace(0, 10, 100)
z = sol.sol(t)

# we expect all states to be 0 in this case, as the quadcopter is hovering at its start position
plot_all('Quadcopter Hovering', t, z)
```

```python
# set F2 to slightly more
# F1, F2, l, m, I, g
args = (hover_force, hover_force+0.00001, constants)
sol = solve_ivp(quadcopter, [0, 10], y0, args=args, dense_output=True)

t = np.linspace(0, 10, 100)
z = sol.sol(t)

# in this case, we expect the quadcopter to to start moving in the y direction, gain some height, and start rotating CW
plot_all('Quadcopter - Constant Uneven Rotor Force', t, z)
```

#### Control Equations - PD
Design a controller that hover's the quadcopter at a given position. In this case, the desired accelerations and velocities will be zero at steady state.

We will ignore any error at this point - we assume that the quadcopter will not be affected by external sources, such as wind.

From our previous derivation, we know the following:

$\ddot{y}=\dfrac{-u_1\theta}{m}\\
\ddot{z}=\dfrac{u_1}{m} - g\\
\ddot{\theta}=\dfrac{u_2}{I}$

To create a PD control equation, we can apply a proportional gain to the distance error and a derivative gain to the velocity error. Since we are going to command the system to a location, we will want the reference velocity to be zero, and we can specify the derivative and proportional gains as $K_d, K_p$, and our location reference with a subscript $_r$:
$e_z(t) = K_{dz}(\dot{z_r}-\dot{z}) + K_{pz}(z_r - z)\\
e_y(t) = K_{dy}(\dot{y_r}-\dot{y}) + K_{py}(y_r - y)\\
e_Θ(t) = K_{dΘ}(\dot{Θ_r}-\dot{Θ}) + K_{pΘ}(Θ_r - Θ)$

and we can now solve for $u_1$ and substitute in our PD equation:
$u_1 = m(g + \ddot{z_r} + K_{dz}(\dot{z_r}-\dot{z}) + K_{pz}(z_r - z))\\
\theta_r = \dfrac{-\ddot{y}}{g} = \dfrac{-1}{g}(\ddot{y_r} + K_{dy}(\dot{y_r}-\dot{y}) + K_{py}(y_r - y))\\
u_2 = K_{dΘ}(-\dot{Θ}) + K_{pΘ}(Θ_r - Θ)$

**NOTE:** for a step input with hover equilibrium, we can zero out the reference accelerations and velocities, but if using a trajectory generator that computes these values, then you can use them. See the python code in quadcopter2d.py where the reference accelerations and velocites are used.

```python
# define our ivp function using the control equations, and pass in our controller PD variables for each state
# pd objects are tuple where element 0 is proportional, and element 1 is derivative
# ref is a 2 element tuple represent y, z
def quadcopter_pid(t, y, constants, pdy, pdz, pdΘ, ref):
    u1 = constants['m']*(constants['g'] - y[4]*pdz[1] + (ref[1] - y[1])*pdz[0])
    Θc = (-1 / constants['g']) * (-pdy[1]*y[3] + (ref[0] - y[0])*pdy[0])
    u2 = -pdΘ[1]*y[5] + pdΘ[0]*(Θc - y[2])
    x1dot = y[3]
    x2dot = y[4]
    x3dot = y[5]
    x4dot = (-u1 * y[2]) / constants['m']
    x5dot = (u1 / constants['m']) - constants['g']
    x6dot = u2 / constants['I']
    return [x1dot, x2dot, x3dot, x4dot, x5dot, x6dot]
```

```python
# PD gains for y, z, and Θ and finally the reference point
pdy, pdz, pdΘ, ref = (12, 6.5), (12, 6.5), (50, 3), (1, 1)
args = (constants, pdy, pdz, pdΘ, ref)
sol = solve_ivp(quadcopter_pid, [0, 10], y0, args=args, dense_output=True)

t = np.linspace(0, 10, 100)
z = sol.sol(t)

# in this case we expect the PD controllers to move the quad copter to the reference point
plot_all(f'Quadcopter moving from (0,0) to {ref}', t, z)
```

#### Applying this to Code
The gains above will not work for a discrete system - we would need to simulate our system using a discrete model - which I will try to do in future.

In this case, I manually tuned the discrete gains in the code and ran the simulation until I was happy (eyeballed it). In the control loop, we will sample our current state, and apply the gains in much the same way as the formulae above. Once we have the value for u1, and u2, we can then determine the actual forces that need to be applied by the rotors. We have 2 unknowns and 2 equations, and can use linear algebra to solve the system of equations:

$Ax = b$

where:

$A = \begin{bmatrix}
  1 & 1 \\
  l & -l \\
\end{bmatrix} \\
x = \begin{bmatrix}
F_1 \\
F_2
\end{bmatrix} \\
b = \begin{bmatrix}
u_1 \\
u_2
\end{bmatrix}$

and we can solve the system by inverting A and matrix multiplying it to b:

$x = A^-1b$

so we can pre-compute the inverse of A ahead of time, since it will be constant:
```python
A = np.array([[1, 1], [arm_length, -arm_length]])
A_inv = linalg.inv(A)
```

and in our code, we can calculate $u_1, u_2$ and then calculate our rotor forces:
```python
u1 = qc_mass * (-space.gravity[1] - velz * pdz[1] + (ref[1] - posz) * pdz[0])
thetac = (-1 / -space.gravity[1]) * (-pdy[1] * vely + (ref[0] - posy) * pdy[0])
u2 = -pdtheta[1] * angv + pdtheta[0] * (thetac - ang)

f = A_inv.dot(np.array([[u1], [u2]]))

# clamp the forces to the allowable range [0, MAX_FORCE]
f1 = max(0, min(f[0][0], MAX_FORCE))
f2 = max(0, min(f[1][0], MAX_FORCE))
```

```python

```


# Quantum Fourier Transform

https://en.wikipedia.org/wiki/Quantum_Fourier_transform

## Imports

```python
%matplotlib inline
```

```python
from sympy import init_printing
init_printing(use_latex=True)
```

```python
from ipywidgets import interact, interactive
from IPython.display import clear_output, display, HTML, Audio
```

```python
from sympy import sqrt, symbols, Rational
from sympy import expand, Eq, Symbol, simplify, exp, sin
from sympy.physics.quantum import *
from sympy.physics.quantum.qubit import *
from sympy.physics.quantum.gate import *
from sympy.physics.quantum.grover import *
from sympy.physics.quantum.qft import QFT, IQFT, Fourier
from sympy.physics.quantum.circuitplot import circuit_plot
```

## QFT Gate and Circuit

The Quantum Fourier Transform (QFT) is useful for a quantum algorithm for factoring numbers which is exponentially faster than what is thought to be possible on a classical machine. The transform does a DFT on the state of a quantum system. There is a simple decomposition of the QFT in terms of a few elementary gates.

Build a 3 qubit QFT and decompose it into primitive gates:

```python
fourier = QFT(0,3).decompose()
fourier
```

```python
circuit_plot(fourier, nqubits=3);
```

```python
def plot_qft(n):
    circuit_plot(QFT(0,n).decompose(), nqubits=n)
```

```python
interact(plot_qft, n=(2,8));
```

The QFT circuit can be represented in various symbolic forms.

```python
m = represent(QFT(0,3), nqubits=3)
m
```

```python
represent(Fourier(0,3), nqubits=3)*4/sqrt(2)
```

## QFT in action

Build a 3 qubit state to take the QFT of:

```python
state = (Qubit('000') + Qubit('010') + Qubit('100') + Qubit('110'))/sqrt(4)
state
```

Perform the QFT:

```python
qapply(fourier*state)
```

```python
def apply_qft(n):
    state = Qubit(IntQubit(n, 3))
    result = qapply(QFT(0,3).decompose()*state)
    display(state)
    display(result)
```

```python
interact(apply_qft, n=(0,7));
```


---

# Chapter 4: Linear Algebra

---

## Vector
Vector may be a list of one dimension numbers with each index stored the same symantics in my understanding

\begin{equation}\vec{v_1} = \begin{bmatrix}2 \\ 1 \end{bmatrix}
=\begin{bmatrix}v_1 \\ v_2 \end{bmatrix}
=\begin{bmatrix}v_i \\ v_j \end{bmatrix}
=\begin{bmatrix}v_x \\ v_y \end{bmatrix}
\end{equation}

\begin{equation}\vec{v_2} = \begin{bmatrix}4 \\ -3 \\2 \end{bmatrix}
=\begin{bmatrix}v_1 \\ v_2 \\ v_3 \end{bmatrix}
=\begin{bmatrix}v_i \\ v_j \\ v_k \end{bmatrix}
=\begin{bmatrix}v_x \\ v_y \\ v_z \end{bmatrix}
\end{equation}

\begin{equation}\overrightarrow{grades} = \begin{bmatrix}exam_1 \\ exam_2 \\ exam_3 \\ exam_4 \end{bmatrix}
=\begin{bmatrix}95 \\ 80 \\ 75 \\ 62 \end{bmatrix}
\end{equation}

Simple built in ``list`` is satisfied but most of the time we use numpy. In this chapter the book leads us to build our own vector data type from scratch. Create data type ``Vector`` from ``typing`` using ``List``. We will use the ``Vector`` to define the function arguements and return value. ``height_weight_age`` is a vector store heigh,weight, and age, while ``grades`` the score from exams 4 times of a one person.

```python
from typing import List

Vector = List[float]

height_weight_age = [70,  # inches,
                     170, # pounds,
                     40 ] # years

grades = [95,   # exam1
          80,   # exam2
          75,   # exam3
          62 ]  # exam4

```

### Add/Subtract
To add/subract vector is, add/subract each element together. The vector must be the same dimension.

\begin{equation}
\vec{v} = \begin{bmatrix}3 \\ -1 \end{bmatrix} \;\;\; \vec{s} = \begin{bmatrix}2 \\ 4 \end{bmatrix}\end{equation}

\begin{equation}
\begin{aligned}
\vec{t}= \vec{v} + \vec{s} &= \begin{bmatrix}v_x + s_x  \\ v_y + s_y \end{bmatrix} \\
&= \begin{bmatrix}3 +2 \\ -1 + 4 \end{bmatrix} \\
&= \begin{bmatrix}5 \\ 3\end{bmatrix} \\
\end{aligned}
\tag{Vector plus Vecotr}\label{Vector plus Vecotr}
\end{equation}

```python
def add(v: Vector, w: Vector) -> Vector:
    """Adds corresponding elements"""
    assert len(v) == len(w), "vectors must be the same length"

    return [v_i + w_i for v_i, w_i in zip(v, w)]

assert add([1, 2, 3], [4, 5, 6]) == [5, 7, 9]

def subtract(v: Vector, w: Vector) -> Vector:
    """Subtracts corresponding elements"""
    assert len(v) == len(w), "vectors must be the same length"

    return [v_i - w_i for v_i, w_i in zip(v, w)]

assert subtract([5, 7, 9], [4, 5, 6]) == [1, 2, 3]

grades2 = [45,   # exam1
          50,   # exam2
          47,   # exam3
          42 ]  # exam4
add(grades,grades2), subtract(grades,grades2)
```

    ([140, 130, 122, 104], [50, 30, 28, 20])

### Sum of Vectors
Interesting technic to assert and summary in python style, ``all()`` and using the  list comprehension to sum the list of  vectors instead loop call function ``add``.

```python

def vector_sum(vectors: List[Vector]) -> Vector:
    """Sums all corresponding elements"""
    # Check that vectors is not empty
    assert vectors, "no vectors provided!"

    # Check the vectors are all the same size
    num_elements = len(vectors[0])
    assert all(len(v) == num_elements for v in vectors), "different sizes!"

    # the i-th element of the result is the sum of every vector[i]
    return [sum(vector[i] for vector in vectors)
            for i in range(num_elements)]

assert vector_sum([[1, 2], [3, 4], [5, 6], [7, 8]]) == [16, 20]

```

### Vector scalar multiplication and mean

Vector can multiply by scalar as decribe below.

\begin{equation}
\begin{aligned}
\lambda\vec{v}&= \lambda \times \begin{bmatrix}v_x \\ v_y \end{bmatrix} = \begin{bmatrix}\lambda\times v_x  \\ \lambda\times v_y \end{bmatrix} \\
\vec{v_2}&= 3 \times \vec{v_1} =  3 \times \begin{bmatrix}v_x \\ v_y \end{bmatrix} = \begin{bmatrix}3\times 2  \\ 3\times 1 \end{bmatrix} = \begin{bmatrix}6  \\ 3 \end{bmatrix} \\
 &= \vec{v_1} + \vec{v_1} + \vec{v_1} = \begin{bmatrix}v_x \\ v_y \end{bmatrix} + \begin{bmatrix}v_x \\ v_y \end{bmatrix} + \begin{bmatrix}v_x \\ v_y \end{bmatrix} = \begin{bmatrix} 2+2+2  \\ 1+1+1 \end{bmatrix}  = \begin{bmatrix}6  \\ 3 \end{bmatrix}
\end{aligned}
\tag{Scalar Multiply}\label{Scalar Multiply}
\end{equation}

```python
def scalar_multiply(c: float, v: Vector) -> Vector:
    """Multiplies every element by c"""
    return [c * v_i for v_i in v]

assert scalar_multiply(2, [1, 2, 3]) == [2, 4, 6]

def vector_mean(vectors: List[Vector]) -> Vector:
    """Computes the element-wise average"""
    n = len(vectors)
    return scalar_multiply(1/n, vector_sum(vectors))

assert vector_mean([[1, 2], [3, 4], [5, 6]]) == [3, 4]

```

### DOT and sum sqaure

Vector DOT products of two vectors define by the equations, note that the dot produc is commutative

\begin{equation}
\begin{aligned}
\vec{v} \cdot \vec{s} = \vec{s} \cdot \vec{v} = \mathbf{v}^\mathbf{T}\mathbf{s}  &=\sum_{i=0}^{i=n} {v_i \cdot s_i}  \\
&= (v_{1} \cdot s_{1}) + (v_{2} \cdot s_{2}) ... + \; (v_{n} \cdot s_{n}) \\
&= (v_{x} \cdot s_{x}) + (v_{y} \cdot s_{y}) ... + \; (v_{n} \cdot s_{n}) \\
&= (3 \cdot 2 ) + (-1 \cdot  4) \\
&= (3 \times 2) + (-1 \times 4) \\
&= 2 \\
\end{aligned}
\tag{Dot Product}\label{Dot Product}
\end{equation}

Dot product of a vector with itself equivalent to the sum square of all elements.

\begin{equation}
\begin{aligned}
\vec{v} \cdot \vec{v}  &=\sum_{i=0}^{i=n} {v_i \cdot v_i}  \\
&= (v_{1} \cdot v_{1}) + (v_{2} \cdot v_{2}) ... + \; (v_{n} \cdot v_{n}) \\
&= (1 \cdot  1) + (2 \cdot  2) + (3 \cdot  3) \\
&= (1 \times 1) + (2 \times 2) + (3 \times 3) \\
&= (1^2) + (2^2) + (3^2) \\
&= 14 \\
\end{aligned}
\tag{Sum of Sqaure}\label{Sum of Square}
\end{equation}

```python
def dot(v: Vector, w: Vector) -> float:
    """Computes v_1 * w_1 + ... + v_n * w_n"""
    assert len(v) == len(w), "vectors must be same length"

    return sum(v_i * w_i for v_i, w_i in zip(v, w))

assert dot([1, 2, 3], [4, 5, 6]) == 32  # 1 * 4 + 2 * 5 + 3 * 6

def sum_of_squares(v: Vector) -> float:
    """Returns v_1 * v_1 + ... + v_n * v_n"""
    return dot(v, v)

assert sum_of_squares([1, 2, 3]) == 14  # 1 * 1 + 2 * 2 + 3 * 3

```

### Vector magnitude and Distances
Magnitude of the vector is the square root of the sum of square of all elements

\begin{equation}
\begin{aligned}
\|\vec{v}\| &= \sqrt{v_{1}^{2} + v_{2}^{2} ... + v_{n}^{2}}  \\
\|\vec{v}\| &= \sqrt{v_{1}^{2} + v_{2}^{2}} \\
            &= \sqrt{3^{2} + 4^{2}} \\
            &= \sqrt{25} \; = 5
\end{aligned}
\tag{Magnitude}\label{Magnitude}
\end{equation}

Calculate the distance of vectors in the cartesian system or points co-ordinate

$$
\vec{v}=\begin{bmatrix}3 \\ 4 \end{bmatrix} \;\;\; \vec{w} = \begin{bmatrix}8 \\ 6 \end{bmatrix} \\ \\
$$

\begin{equation}
\begin{aligned}
Square\;Distance(\vec{v},\vec{w}) &=  \sum_{i=0}^{i=n} {(v_i - w_i)^2}  \\
    &=(v_{1} - w_{1})^{2} + (v_{2} - w_{2})^{2} \\
    &=(3 - 8)^{2} + (4 -6)^{2} \\
    &=-5^{2} + -2^{2} \\
    &=29
\end{aligned}
\tag{Sqaure Disctance}\label{Sqaure Disctance}
\end{equation}

\begin{equation}
\begin{aligned}
Distance(\vec{v},\vec{w}) &= \sqrt{ \sum_{i=0}^{i=n} {(v_i - w_i)^2}}  \\
    &= \sqrt{ (v_{1} - w_{1})^{2} + (v_{2} - w_{2})^{2}} \\
    &= \sqrt{ (3 - 8)^{2} + (4 -6)^{2}} \\
    &= \sqrt{-5^{2} + -2^{2}} \\
    &= \sqrt{29} \; \approx 5.385
\end{aligned}
\tag{Distance}\label{Distance}
\end{equation}

```python
import math

def magnitude(v: Vector) -> float:
    """Returns the magnitude (or length) of v"""
    return math.sqrt(sum_of_squares(v))   # math.sqrt is square root function

assert magnitude([3, 4]) == 5

def squared_distance(v: Vector, w: Vector) -> float:
    """Computes (v_1 - w_1) ** 2 + ... + (v_n - w_n) ** 2"""
    return sum_of_squares(subtract(v, w))
print(squared_distance([3, 4],[8,6]))

def distance(v: Vector, w: Vector) -> float:
    """Computes the distance between v and w"""
    return math.sqrt(squared_distance(v, w))
print(distance([3, 4],[8,6]))

def distance(v: Vector, w: Vector) -> float:  # type: ignore
    return magnitude(subtract(v, w))
print(distance([3, 4],[8,6]))
```

    29
    5.385164807134504
    5.385164807134504

---

## Matrix
Two dimensions row and column  data.

\begin{align*}
A_{M\times N} &=
\begin{bmatrix}
        a_{11} & a_{12} & \dots   & a_{1N} \\
        a_{21} & a_{22} & \dots   & a_{2N} \\
        \vdots & \vdots & \ddots & \vdots \\
        a_{M1} & a_{M2} & \dots  & a_{MN}
\end{bmatrix}
\\ \\
A_{3\times3} &=
\begin{bmatrix}
    v_1 & v_2 & v_3 \\
    v_4 & v_5 & v_6 \\
    v_7 & v_8 & v_9
    \end{bmatrix}
= \begin{bmatrix}
    1 & 2 & 3 \\
    4 & 5 & 6 \\
    7 & 8 & 9
    \end{bmatrix}
\end{align*}

Note that if a dimension is one, the matrix can be considerd as vector. If the row is fixed to one, we call **row vector** and if the vector is fixed column to be one, we call it is a **column vector**.

\begin{align*}\text{Column vector} &= \begin{bmatrix}2 \\ 1 \\ 3 \end{bmatrix} \\
\text{Row vector} &= \begin{bmatrix}2 & 1 & 3 \end{bmatrix}
\end{align*}

And column vector can be **Transpose** to row vector and vice versa. One dimension which one element may be considered as **Saclar**, isnt't it?

In python usually use **numpy** but simple ``list`` or ``List`` also implement the matrix, in the book it intruduce the Matrix type from ``List``.

```python
# Another type alias
Matrix = List[List[float]]

A = [[1, 2, 3],  # A has 2 rows and 3 columns
     [4, 5, 6]]

B = [[1, 2],     # B has 3 rows and 2 columns
     [3, 4],
     [5, 6]]
ROW_VECTOR = [[1,2,3]]
COL_VECTOR = [[1],[2],[3]]
```

### Matrix Dimension
Dimension of the matrix determine by the shape of the matrix list (or numpy array)

```python
from typing import Tuple

def shape(A: Matrix) -> Tuple[int, int]:
    """Returns (# of rows of A, # of columns of A)"""
    num_rows = len(A)
    num_cols = len(A[0]) if A else 0   # number of elements in first row
    return num_rows, num_cols

assert shape([[1, 2, 3], [4, 5, 6]]) == (2, 3)  # 2 rows, 3 columns

def get_row(A: Matrix, i: int) -> Vector:
    """Returns the i-th row of A (as a Vector)"""
    return A[i]             # A[i] is already the ith row

def get_column(A: Matrix, j: int) -> Vector:
    """Returns the j-th column of A (as a Vector)"""
    return [A_i[j]          # jth element of row A_i
            for A_i in A]   # for each row A_i

print(shape(A))
print(shape(B))
print('Row vector ',ROW_VECTOR, ' shape ',shape(ROW_VECTOR))
print('Row vector ',COL_VECTOR, ' shape ',shape(COL_VECTOR))
```

    (2, 3)
    (3, 2)
    Row vector  [[1, 2, 3]]  shape  (1, 3)
    Row vector  [[1], [2], [3]]  shape  (3, 1)

### Identity Matrix

Matrix that all diagonal element ($a_{1 1},a_{2 2}, \dots a_{n n}$) is one and the remains are all zero called **identity matrix**

\begin{bmatrix}
      1 & 0 & 0 & 0 \\
      0 & 1 & 0 & 0 \\
      0 & 0 & 1 & 0 \\
      0 & 0 & 0 & 1
\end{bmatrix}

Function ``make_metrix(num_rows, num_cols, entry_fn)`` accepts number of rows and columns with a ``Callabel[[int,int]],float`` to return a matrix. The function ``identity_matrix(n:int)`` that retrun $ N\times N$ identity matrix, noticed the ``lambda`` send to ``make_matrix`` as a callable object.

```python
from typing import Callable

def make_matrix(num_rows: int,
                num_cols: int,
                entry_fn: Callable[[int, int], float]) -> Matrix:
    """
    Returns a num_rows x num_cols matrix
    whose (i,j)-th entry is entry_fn(i, j)
    """
    return [[entry_fn(i, j)             # given i, create a list
             for j in range(num_cols)]  #   [entry_fn(i, 0), ... ]
            for i in range(num_rows)]   # create one list for each i

def identity_matrix(n: int) -> Matrix:
    """Returns the n x n identity matrix"""
    return make_matrix(n, n, lambda i, j: 1 if i == j else 0)

assert identity_matrix(5) == [[1, 0, 0, 0, 0],
                              [0, 1, 0, 0, 0],
                              [0, 0, 1, 0, 0],
                              [0, 0, 0, 1, 0],
                              [0, 0, 0, 0, 1]]
```

Remains code from the book is about matrix representation for data from chapter one.

```python
data = [[70, 170, 40],
        [65, 120, 26],
        [77, 250, 19],
        # ....
       ]

friendships = [(0, 1), (0, 2), (1, 2), (1, 3), (2, 3), (3, 4),
               (4, 5), (5, 6), (5, 7), (6, 8), (7, 8), (8, 9)]

#            user 0  1  2  3  4  5  6  7  8  9
#
friend_matrix = [[0, 1, 1, 0, 0, 0, 0, 0, 0, 0],  # user 0
                 [1, 0, 1, 1, 0, 0, 0, 0, 0, 0],  # user 1
                 [1, 1, 0, 1, 0, 0, 0, 0, 0, 0],  # user 2
                 [0, 1, 1, 0, 1, 0, 0, 0, 0, 0],  # user 3
                 [0, 0, 0, 1, 0, 1, 0, 0, 0, 0],  # user 4
                 [0, 0, 0, 0, 1, 0, 1, 1, 0, 0],  # user 5
                 [0, 0, 0, 0, 0, 1, 0, 0, 1, 0],  # user 6
                 [0, 0, 0, 0, 0, 1, 0, 0, 1, 0],  # user 7
                 [0, 0, 0, 0, 0, 0, 1, 1, 0, 1],  # user 8
                 [0, 0, 0, 0, 0, 0, 0, 0, 1, 0]]  # user 9

assert friend_matrix[0][2] == 1, "0 and 2 are friends"
assert friend_matrix[0][8] == 0, "0 and 8 are not friends"

# only need to look at one row
friends_of_five = [i
                   for i, is_friend in enumerate(friend_matrix[5])
                   if is_friend]

```

```python

```

```python

```

```python

```

```python

```

```python
import numpy as np
a = np.array([1, 2, 3])
display(a)
display(a.transpose())
display(a.dot(a.transpose()))

```

    array([1, 2, 3])

    array([1, 2, 3])

    14

```python
a.shape = (3,1)
display(a)
display(a.transpose())
display(a.dot(a.transpose()))

```

    array([[1],
           [2],
           [3]])

    array([[1, 2, 3]])

    array([[1, 2, 3],
           [2, 4, 6],
           [3, 6, 9]])

```python

```


# Simulation of a Computer Network

## Router as a Queuing System

Delay/Jitter:
- Transmission
- Service
- Processing
- Waiting

Quality of Service:
- Realtime
- Lossless

Representation:
- $A\left(t\right)$
- $D\left(t\right)$
- $W\left(t\right)$
- $W_Q\left(t\right)$
- $T_n$
- $S_n$
- $L_n$
- $L_{Qn}$

## Little's Law

\begin{equation}
\mathbb E\left[L\right]=\lambda\mathbb E\left[W\right]
\end{equation}
\begin{equation}
\mathbb E\left[L_Q\right]=\lambda\mathbb E\left[W_Q\right]
\end{equation}

## Steady State Probabilities

\begin{equation}
P_n=\lim_{t\rightarrow\infty}\mathbb P\left\{L(t)=n\right\}
\end{equation}

Stability
\begin{equation}
a_n=d_n
\end{equation}

Poisson arrivals always see time averages (PASTA):
\begin{equation}
P_n=a_n
\end{equation}

## MM1

Exponential Arrivals / Exponential Services ($\lambda$ / $\mu$)

Markov Chain

A technical remark: if one event occurs at an exponential rate $\lambda$, and another independent event at an exponential rate $\mu$, then together they occur at an exponential rate $\lambda+\mu$.

Steady State

\begin{equation}
\begin{cases}
P_0=1-\frac{\lambda}{\mu}\\
P_n=\left(1-\frac{\lambda}{\mu}\right)\left(\frac{\lambda}{\mu}\right)^n,\quad n>0
\end{cases}
\end{equation}

\begin{equation}
\mathbb E\left[L\right]=\frac{\lambda}{\mu-\lambda}
\end{equation}

\begin{equation}
\mathbb E\left[W\right]=\frac{1}{\lambda}\mathbb E\left[L\right]=\frac{1}{\mu-\lambda}
\end{equation}

\begin{equation}
\mathbb E\left[W_Q\right]=\mathbb E\left[W\right]-\mathbb E\left[S\right]=\frac{\lambda}{\mu\left(\mu-\lambda\right)}
\end{equation}

\begin{equation}
\mathbb E\left[L_Q\right]=\lambda\mathbb E\left[W_Q\right]=\frac{\lambda^2}{\mu\left(\mu-\lambda\right)}
\end{equation}

Finite Capacity?

Exponential Arrivals?

Exponential Services?

Network of Queues?

## GG1

Virtual Waiting Time: $V\left(t\right)$
\begin{equation}
\mathbb E\left[V\right]=\lambda\mathbb E\left[S\right]E\left[W_Q\right]+\frac{\lambda}{2}\mathbb E\left[S^2\right]
\end{equation}

## MG1

Pollaczek–Khintchine formula
\begin{equation}
\mathbb E\left[W_Q\right] = \frac{\lambda\mathbb E\left[S^2\right]}{2\left(1-\lambda\mathbb E\left[S\right]\right)}
\end{equation}

## Statistical Processing

Sample Mean and Sample Variance

\begin{equation}
\bar X = \sum_{i=1}^n X_i
\end{equation}

\begin{equation}
\mathbb E\left[\bar X\right]= \mu
\end{equation}

To determine the “worth” of $\bar X$ as an estimator of the population mean $\mu$, we consider its mean square error:
\begin{equation}
\mathbb E\left[\left(\bar X - \mu\right)^2\right]= \frac{\sigma^2}{n}
\end{equation}

\begin{equation}
S^2=\frac{\sum_{i=1}^n\left(X-\bar x\right)^2}{n-1}
\end{equation}

\begin{equation}
\mathbb E\left[S^2\right]=\sigma^2
\end{equation}

$n$ sufficient large: central limit theorema
\begin{equation}
\mathbb P\left\{\left|X − \mu\right|>c\frac{\sigma}{\sqrt n}\right\} \approx \mathbb P\left\{\left|Z\right|>c\right\}
\end{equation}
where $Z$ is a standard normal

Runtime

Number of Runs

The Regenerative Approach

Stationnarity vs Ergodicity


# Simple pendulum using Lagrange's equation

Defines a LagrangianPendulum class that is used to generate basic pendulum plots from solving Lagrange's equations.

* Last revised 17-Mar-2019 by Dick Furnstahl (furnstahl.1@osu.edu).

## Euler-Lagrange equation

For a simple pendulum, the Lagrangian with generalized coordinate $\phi$ is

$\begin{align}
  \mathcal{L} = \frac12 m L^2 \dot\phi^2 - mgL(1 - \cos\phi)
\end{align}$

The Euler-Lagrange equation is

$\begin{align}
 \frac{d}{dt}\frac{\partial\mathcal{L}}{\partial \dot\phi} = \frac{\partial\mathcal L}{\partial\phi}
 \quad\Longrightarrow\quad
 m L^2 \ddot \phi = -mgL\sin\phi
  \ \mbox{or}\ \ddot\phi = - \omega_0^2\sin\phi = 0
  \;.
\end{align}$

## Hamilton's equations

The generalized momentum corresponding to $\phi$ is

$\begin{align}
   \frac{\partial\mathcal{L}}{\partial \dot\phi} = m L^2 \dot\phi \equiv p_\phi
   \;.
\end{align}$

We can invert this equation to find $\dot\phi = p_\phi / m L^2$.
Constructing the Hamiltonian by Legendre transformation we find

$\begin{align}
  \mathcal{H} &= \dot\phi p_\phi  - \mathcal{L} \\
              &= \frac{p_\phi^2}{m L^2} - \frac12 m L^2 \dot\phi^2 + mgL(1 - \cos\phi) \\
              &= \frac{p_\phi^2}{2 m L^2} + mgL(1 - \cos\phi)
              \;.
\end{align}$

Thus $\mathcal{H}$ is simply $T + V$.  Hamilton's equations are

$\begin{align}
  \dot\phi &= \frac{\partial\mathcal{H}}{\partial p_\phi} = \frac{p_\phi}{m L^2} \\
  \dot p_\phi &= -\frac{\partial\mathcal{H}}{\partial \phi} = -mgL \sin\phi
  \;.
\end{align}$

```python
%matplotlib inline
```

```python
import numpy as np
from scipy.integrate import odeint, solve_ivp

import matplotlib.pyplot as plt
```

```python
# The dpi (dots-per-inch) setting will affect the resolution and how large
#  the plots appear on screen and printed.  So you may want/need to adjust
#  the figsize when creating the figure.
plt.rcParams['figure.dpi'] = 100.    # this is the default for notebook

# Change the common font size (smaller when higher dpi)
font_size = 10
plt.rcParams.update({'font.size': font_size})
```

## Pendulum class and utility functions

```python
class LagrangianPendulum():
    """
    Pendulum class implements the parameters and Lagrange's equations for
     a simple pendulum (no driving or damping).

    Parameters
    ----------
    L : float
        length of the simple pendulum
    g : float
        gravitational acceleration at the earth's surface
    omega_0 : float
        natural frequency of the pendulum (\sqrt{g/l} where l is the
        pendulum length)
    mass : float
        mass of pendulum

    Methods
    -------
    dy_dt(t, y)
        Returns the right side of the differential equation in vector y,
        given time t and the corresponding value of y.
    """
    def __init__(self, L=1., mass=1., g=1.
                ):
        self.L = L
        self.g = g
        self.omega_0 = np.sqrt(g/L)
        self.mass = mass

    def dy_dt(self, t, y):
        """
        This function returns the right-hand side of the diffeq:
        [dphi/dt d^2phi/dt^2]

        Parameters
        ----------
        t : float
            time
        y : float
            A 2-component vector with y[0] = phi(t) and y[1] = dphi/dt

        Returns
        -------

        """
        return [y[1], -self.omega_0**2 * np.sin(y[0]) ]

    def solve_ode(self, t_pts, phi_0, phi_dot_0,
                  abserr=1.0e-9, relerr=1.0e-9):
        """
        Solve the ODE given initial conditions.
        Specify smaller abserr and relerr to get more precision.
        """
        y = [phi_0, phi_dot_0]
        solution = solve_ivp(self.dy_dt, (t_pts[0], t_pts[-1]),
                             y, t_eval=t_pts,
                             atol=abserr, rtol=relerr)
        phi, phi_dot = solution.y

        return phi, phi_dot
```

```python
class HamiltonianPendulum():
    """
    Hamiltonian Pendulum class implements the parameters and Hamilton's equations for
     a simple pendulum (no driving or damping).

    Parameters
    ----------
    L : float
        length of the simple pendulum
    g : float
        gravitational acceleration at the earth's surface
    omega_0 : float
        natural frequency of the pendulum (\sqrt{g/l} where l is the
        pendulum length)
    mass : float
        mass of pendulum

    Methods
    -------
    dy_dt(t, y)
        Returns the right side of the differential equation in vector y,
        given time t and the corresponding value of y.
    """
    def __init__(self, L=1., mass=1., g=1.
                ):
        self.L = L
        self.g = g
        self.omega_0 = np.sqrt(g*L)
        self.mass = mass

    def dy_dt(self, t, y):
        """
        This function returns the right-hand side of the diffeq:
        [dphi/dt d^2phi/dt^2]

        Parameters
        ----------
        t : float
            time
        y : float
            A 2-component vector with y[0] = phi(t) and y[1] = dphi/dt

        Returns
        -------

        """
        return [y[1], -self.mass * self.omega_0**2 * np.sin(y[0]) ]

    def solve_ode(self, t_pts, phi_0, p_phi_0,
                  abserr=1.0e-9, relerr=1.0e-9):
        """
        Solve the ODE given initial conditions.
        Specify smaller abserr and relerr to get more precision.
        """
        y = [phi_0, p_phi_0]
        solution = solve_ivp(self.dy_dt, (t_pts[0], t_pts[-1]),
                             y, t_eval=t_pts,
                             atol=abserr, rtol=relerr)
        phi_dot, p_phi_dot = solution.y

        return phi_dot, p_phi_dot
```

```python
class Hamiltonian2Pendulum():
    """
    Hamiltonian Pendulum class implements the parameters and Hamilton's equations for
     a simple pendulum (no driving or damping).

    Parameters
    ----------
    L : float
        length of the simple pendulum
    g : float
        gravitational acceleration at the earth's surface
    omega_0 : float
        natural frequency of the pendulum (\sqrt{g/l} where l is the
        pendulum length)
    mass : float
        mass of pendulum

    Methods
    -------
    dy_dt(t, y)
        Returns the right side of the differential equation in vector y,
        given time t and the corresponding value of y.
    """
    def __init__(self, k=1., mass=1.
                ):
        self.k = k
        #self.g = g
        #self.omega_0 = np.sqrt(g*L)
        self.mass = mass

    def dy_dt(self, t, y):
        """
        This function returns the right-hand side of the diffeq:
        [dphi/dt d^2phi/dt^2]

        Parameters
        ----------
        t : float
            time
        y : float
            A 2-component vector with y[0] = phi(t) and y[1] = dphi/dt

        Returns
        -------

        """
        return [y[1], -self.mass * self.omega_0**2 * np.sin(y[0]) ]

    def solve_ode(self, t_pts, phi_0, p_phi_0,
                  abserr=1.0e-9, relerr=1.0e-9):
        """
        Solve the ODE given initial conditions.
        Specify smaller abserr and relerr to get more precision.
        """
        y = [phi_0, p_phi_0]
        solution = solve_ivp(self.dy_dt, (t_pts[0], t_pts[-1]),
                             y, t_eval=t_pts,
                             atol=abserr, rtol=relerr)
        phi_dot, p_phi_dot = solution.y

        return phi_dot, p_phi_dot
```

```python
def plot_y_vs_x(x, y, axis_labels=None, label=None, title=None,
                color=None, linestyle=None, semilogy=False, loglog=False,
                ax=None):
    """
    Generic plotting function: return a figure axis with a plot of y vs. x,
    with line color and style, title, axis labels, and line label
    """
    if ax is None:        # if the axis object doesn't exist, make one
        ax = plt.gca()

    if (semilogy):
        line, = ax.semilogy(x, y, label=label,
                            color=color, linestyle=linestyle)
    elif (loglog):
        line, = ax.loglog(x, y, label=label,
                          color=color, linestyle=linestyle)
    else:
        line, = ax.plot(x, y, label=label,
                    color=color, linestyle=linestyle)

    if label is not None:    # if a label if passed, show the legend
        ax.legend()
    if title is not None:    # set a title if one if passed
        ax.set_title(title)
    if axis_labels is not None:  # set x-axis and y-axis labels if passed
        ax.set_xlabel(axis_labels[0])
        ax.set_ylabel(axis_labels[1])

    return ax, line
```

```python
def start_stop_indices(t_pts, plot_start, plot_stop):
    start_index = (np.fabs(t_pts-plot_start)).argmin()  # index in t_pts array
    stop_index = (np.fabs(t_pts-plot_stop)).argmin()  # index in t_pts array
    return start_index, stop_index
```

```python

```

## Make simple pendulum plots

```python
# Labels for individual plot axes
phi_vs_time_labels = (r'$t$', r'$\phi(t)$')
phi_dot_vs_time_labels = (r'$t$', r'$d\phi/dt(t)$')
state_space_labels = (r'$\phi$', r'$d\phi/dt$')

# Common plotting time (generate the full time then use slices)
t_start = 0.
t_end = 50.
delta_t = 0.001

t_pts = np.arange(t_start, t_end+delta_t, delta_t)

L = 1.
g = 1.
mass = 1.

# Instantiate a pendulum
p1 = HamiltonianPendulum(L=L, g=g, mass=mass)

```

```python
# both plots: same initial conditions
phi_0 = (3./4.)*np.pi
p_phi_dot_0 = mass*g*L*np.cos(phi_0)
phi_dot_0 = 0.
phi_dot , p_phi_dot = p1.solve_ode(t_pts, phi_dot_0, p_phi_dot_0)

# start the plot!
fig = plt.figure(figsize=(15,5))
overall_title = 'Simple pendulum from Lagrangian:  ' + \
                rf' $\omega_0 = {p1.omega_0:.2f},$' + \
                rf'  $\phi_0 = {phi_0:.2f},$' + \
                rf' $\dot\phi_0 = {phi_dot_0:.2f}$' + \
                '\n'     # \n means a new line (adds some space here)
fig.suptitle(overall_title, va='baseline')

# first plot: phi plot
ax_a = fig.add_subplot(1,3,1)

start, stop = start_stop_indices(t_pts, t_start, t_end)
plot_y_vs_x(t_pts[start : stop], phi[start : stop],
            axis_labels=phi_vs_time_labels,
            color='blue',
            label=None,
            title=r'$\phi(t)$',
            ax=ax_a)

# second plot: phi_dot plot
ax_b = fig.add_subplot(1,3,2)

start, stop = start_stop_indices(t_pts, t_start, t_end)
plot_y_vs_x(t_pts[start : stop], phi_dot[start : stop],
            axis_labels=phi_vs_time_labels,
            color='blue',
            label=None,
            title=r'$\dot\phi(t)$',
            ax=ax_b)

# third plot: state space plot from t=30 to t=50
ax_c = fig.add_subplot(1,3,3)

start, stop = start_stop_indices(t_pts, t_start, t_end)
plot_y_vs_x(phi[start : stop], phi_dot[start : stop],
            axis_labels=state_space_labels,
            color='blue',
            label=None,
            title='State space',
            ax=ax_c)

fig.tight_layout()
fig.savefig('simple_pendulum_Lagrange.png', bbox_inches='tight')
```

Now trying the power spectrum, plotting only positive frequencies and cutting off the lower peaks:

```python
start, stop = start_stop_indices(t_pts, t_start, t_end)
signal = phi[start:stop]
power_spectrum = np.abs(np.fft.fft(signal))**2
freqs = np.fft.fftfreq(signal.size, delta_t)
idx = np.argsort(freqs)

fig_ps = plt.figure(figsize=(5,5))
ax_ps = fig_ps.add_subplot(1,1,1)
ax_ps.semilogy(freqs[idx], power_spectrum[idx], color='blue')
ax_ps.set_xlim(0, 1.)
ax_ps.set_ylim(1.e5, 1.e11)
ax_ps.set_xlabel('frequency')
ax_ps.set_title('Power Spectrum')

fig_ps.tight_layout()
```

```python

```