# MathPhysicalConstants

This library contains Mathematical and Physical Constants to produce macro and micro-physics relations. It integrates with IJulia, outputting mathematical and Physical Constants to the notebook.

search: ⊻ ⊋ ⊊ ⊉ ⊈ ⊇ ⊆ ≥ ≤ ≢ ≡ ≠ ≉ ≈ ∪ ∩ ∛ √ ∘ ∌ ∋ ∉ ∈ ℯ π ÷ ~ | ^ \ > < : / - +

## Pre requisite

Add Conda

```julia
import Pkg;
Pkg.add("Conda")
```

      Updating registry at `/srv/julia/pkg/registries/General`
      Updating git-repo `https://github.com/JuliaRegistries/General.git`
     Resolving package versions...
      Updating `/srv/julia/pkg/environments/v0.7/Project.toml`
      [8f4d0f93] + Conda v1.3.0
      Updating `/srv/julia/pkg/environments/v0.7/Manifest.toml`
     [no changes]

set the environment variables

```julia
#ENV["PYTHON"] = "/srv/conda/bin/python3"
ENV["PYTHON"] = "/srv/julia/pkg/conda/3"
# ENV["PYTHON"] = "C:\\Python37-x64\\python.exe" # example for Windows
# ENV["PYTHON"] = "/usr/bin/python3.7"           # example for *nix
#Pkg.build("PyCall")
```

    "/srv/julia/pkg/conda/3"

Add PyCall

```julia
import Pkg; Pkg.add("PyCall")
```

     Resolving package versions...
     Installed MacroTools ─ v0.4.5
     Installed PyCall ───── v1.91.2
      Updating `/srv/julia/pkg/environments/v0.7/Project.toml`
      [438e738f] + PyCall v1.91.2
      Updating `/srv/julia/pkg/environments/v0.7/Manifest.toml`
      [1914dd2f] + MacroTools v0.4.5
      [438e738f] + PyCall v1.91.2
      Building PyCall → `/srv/julia/pkg/packages/PyCall/ttONZ/deps/build.log`

```julia
#Pkg.build("PyCall")
using Conda
#Conda.add("matplotlib", :my_env)
Conda.add("matplotlib", "/srv/julia/pkg/conda/3")

```

```julia
using Conda
#Conda.add("numpy", "/srv/julia/pkg/conda/3")
Conda.add("sympy", "/srv/julia/pkg/conda/3")
```

```julia
using Conda
#Conda.add("numpy", "/srv/julia/pkg/conda/3")
Conda.add("scipy", "/srv/julia/pkg/conda/3")
```

```julia
using Conda
Conda.add("PyLab", "/srv/julia/pkg/conda/3")
```

## Pkg Status

List all Conda modules

```julia
import Conda
Conda.list()
```

Check if MathPhysicalConstants is installed

```julia
import Pkg; Pkg.status()
```

        Status `/srv/julia/pkg/environments/v0.7/Project.toml`
      [49dc2e85] Calculus v0.4.1
      [8f4d0f93] Conda v1.3.0
      [e30172f5] Documenter v0.22.6
      [7073ff75] IJulia v1.21.1
      [eff96d63] Measurements v1.0.2
      [438e738f] PyCall v1.91.2
      [1fd47b50] QuadGK v2.3.1
      [3cdcf5f2] RecipesBase v0.5.0
      [ae029012] Requires v0.5.2
      [276daf66] SpecialFunctions v0.8.0
      [1986cc42] Unitful v0.17.0

## Pkg Installation / Pkg De-installation

To remove the package run

```julia
using Pkg
Pkg.rm("MathPhysicalConstants")
```

To install the package run

```julia
using Pkg
Pkg.clone("https://github.com/laguer/MathPhysicalConstants.jl")
Pkg.clone("https://github.com/laguer/PhysicalConstant.jl")
```

Install PyPlot

```julia
import Pkg; Pkg.add("PyPlot")
```

```julia
#import Pkg; Pkg.add("PyLab")
```

## Update

```julia
using Pkg
Pkg.update("MathPhysicalConstants")
```

In addition, you will need to install the following dependencies if you do not already have them on your system.
* Measurements. This is required by MathPhysicalConstants. On Ubuntu, you can get this by running `sudo apt-get install pdf2svg`. On Windows, you can download the binaries from http://www.cityinthesky.co.uk/opensource/pdf2svg/. Be sure to add pdf2svg to your path (and restart).
* Pgf (version 3.0 or later). Install using your latex package manager (e.g., texlive or miktex).

Once these things are installed, you should be able to run the following:

```julia
using MathPhysicalConstants.SI
using Measurements
using PyCall
#using scipy
using sympy as sy

```

```julia
@pyimport numpy
R_FMS=(SI.PlanckConstantHbar/(SI.MassElectron*SI.c))*numpy.exp(2^(6+(1/2)))/6
print(R_FMS)
```

    1.3075686776359582e26

```julia
@pyimport scipy.constants as constants
R_FMS=(SI.PlanckConstantHbar/(SI.MassElectron*constants.c))*numpy.exp(2^(6+(1/2)))/6
print(R_FMS)
```

    1.3075686776359582e26

# Set the constants

```julia
c=constants.c
print(c)
h=constants.h
print(h)
hbar=constants.hbar
print(hbar)
a=137.0359990836958
print(a)
d_e=1.00115965218128
print(d_e)
xi3=1.202056903159594
print(xi3)
xi2=np.pi^2/6
print(xi2)
#print(c,h,hbar,a,d_e,xi3,xi2)
e=np.e
print(e)
gamma=np.euler_gamma
print(gamma)
Gamma=(constants.fine_structure^-1*gamma)/np.pi
print(Gamma)
# F**(2/3)=2*137*Gamma
# Fermi's mass given by
F=(2*137*Gamma)^1.5
print(F)
lambdabare=constants.hbar/(constants.m_e*constants.c)
print(lambdabare)
lambdabar_F=lambdabare/F
print(lambdabar_F)
lambdabar_p=(constants.hbar/(constants.m_p*constants.c))
print(lambdabar_p)
H=1837.152646   # (0.06 ppb)
p=1836.15267343 # 1836.1526734400013 #(0.06 ppb)
n=1838.68366173 # 1838.6836617324586 #(0.5 ppb)
print(H,p,n)
m_e=constants.m_e
m_p=constants.m_p
m_n=constants.m_n
print(m_e,m_p,m_n)
beta=(H-p)^(-1)
print(beta)
a_w=F^2
print(a_w)
a_s=a_w/(2*np.pi*(p*H)^(3/2))
print(a_s)
j=(8*np.pi^2)/np.log(2)
print(j)
k_B=constants.Boltzmann
T_cmb=2.72582
t_k=9600.591457
print(k_B,T_cmb,t_k)
lambdabar_cmb=(constants.hbar*constants.c)/(k_B*T_cmb)
print(lambdabar_cmb)
# Planck's length CODATA2018 (hbar.G/c^3)^1/2 = 1.61639471.10^-35
# l_P=1.616255*10**(-35) CODATA2018
G_s=6.67545372*10^-11
print(G_s)
l_P=((constants.hbar*G_s)/constants.c^3)^0.5
print(l_P)
# Planck's mass
m_P=2.176434*10^(-8)
print(m_P)
l_wi=1.0630825*10^-3
print(l_wi)
l_K=9600.591457*c
print(l_K)
ƛ_w=(constants.hbar/(constants.m_e*constants.c))/F^2
print(ƛ_w)
lambdabar_w=(constants.hbar/(constants.m_e*constants.c))/F^2
P=lambdabare/l_P
print(P)
lambdabar_F=lambdabare/F
print(lambdabar_F)
#2.897 771 955... e-3 m.K - NIST CODATA 2018
b=2.897771955*10^(-3)
#lambdawien=(h*c)/(k_B*)
# b=lambda/lambdawien
print(b)
𝜔=(h*c)/(k_B*b)
print(𝜔)
l_ph=(lambdabare*(np.pi*(a^2))^2)
print(l_ph)
aprime=137.1106292
W=137^2*Gamma/3*d_e
print(W)
Z=178450
print(Z)
μ=206.7682869
print(μ)
τ=3477.441701
print(τ)
s_4=2*np.pi^2*a^3
print(s_4)
# Hydrogen Atom Radius
r_H=(lambdabare*aprime)
print(r_H)
r_e=(lambdabare/a)
print(r_e)
R_N=2*constants.hbar^2/(G_s*((a)*constants.m_e)^3)
print(R_N)
R_GC=R_N^2/(2*l_P)
M_U=(m_P^4)/(constants.m_e*constants.m_p*constants.m_n)
print(M_U)
n_ph=(4*np.pi/3)*(lambdabare*2^128/(1+(137^2 + np.pi^2 + e^2)/(H*p) )/l_ph )^3
print(n_ph)
R1=(np.exp(((a*((1+1/p))*(((np.pi^2)/6)-1))+1)-gamma))*lambdabare
print(R1)
E_3=e^e^e
print(E_3)
#E_4=e^e^e^e
#print(E_4)
Lucas=170141183460469231731687303715884105727 #2^127-1
print(Lucas)
OM=808017424794512875886459904961710757005754368000000000 #2^46 * 3^20 * 5^9 * 7^6 * 11^2 * 13^3 * 17 * 19 * 23 * 29 * 31 * 41 * 47 * 59 * 71 # 808017424794512875886459904961710757005754368000000000
print(OM)
OB=4154781481226426191177580544000000 #2^41 * 3^13 * 5^6 * 7^2 * 11 * 13 * 17 * 19 * 23 * 31 * 47 # 4154781481226426191177580544000000
print(OB)
OP=2663849798329448283764291471531459318169741293570162121768960000000000000 #2^8 * 3^7 * 5^6 * 7 * 11 * 31 * 37 * 67 * 2^9 * 3^4 * 5 * 7^3 * 11 * 19 * 31 * 2^14 * 3^3 * 5^3 * 7 * 13 * 29 * 2^21 * 3^3 * 5 * 7 * 11^3 * 23 * 29 * 31 * 37 * 43 * 2^7 * 3^5 * 5 * 17 * 19 * 2^3 * 3 * 5 * 7 * 11 * 19
print(OP)
A=a^a
print(A)
```

    2.99792458e86.62607015e-341.0545718176461565e-34137.03599908369581.001159652181281.2020569031595941.64493406684822642.7182818284590450.577215664901532925.178097241906573007.3649059753.8615926796089057e-136.739167620022749e-192.1030891033555923e-161837.1526461836.152673431838.683661739.1093837015e-311.67262192369e-271.67492749804e-271.0000274307524713.2833744023648914e118.434502897752472113.910634599900041.380649e-232.725829600.5914570.00084007180247071876.675453720000004e-111.616394711369936e-352.176434000000001e-80.00106308252.8781849111478315e121.1761048867371158e-242.389015908333495e226.739167620022749e-190.0028977719550000014.9651142320615540.0013440173951173593157705.2404352378178450206.76828693477.4417015.079649566995172e75.294654020152911e-112.817940326213412e-151.7128941663434092e268.792233511611579e520.01.4923654623153317e263.8142791047601975e6170141183460469231731687303715884105727808017424794512875886459904961710757005754368000000000415478148122642619117758054400000026638497983294482837642914715314593181697412935701621217689600000000000006.656929234280262e292

```julia
#big(2^127)
Measurements.value(big((2^127)-1))
```

    -1

```julia
OM*OP/(Lucas)^2*SI.ƛe
```

    8.31183380772036010185213696007727305596352834754248557886990870747262247595754e+113

```julia
R_N=2*constants.hbar^2/(G_s*((a)*constants.m_e)^3)
```

    1.7128941663434092e26

```julia
#R_N*a^a/OP
#Measurements.value(big(2^8*3^7*5^6*7*11*31*37*67*2^9*3^4*5*7^3*11*19*31*2^14*3^3*5^3*7*13*29*2^21*3^3*5*7*11^3*23*29*31*37*43*2^7*3^5*5*17*19*2^3*3*5*7*11*19))
big(1.7128941663434092*10^26*6.656929234280262*10^292)/2663849798329448283764291471531459318169741293570162121768960000000000000
# Result for R_N*a^a/OP 4.2805*10^246
```

    -0.0

```julia
#R_N*a^a/OP
1.7128941663434092e26*6.656929234280262e292 #/OP
```

    Inf

```julia
E_3
```

    3.8142791047601975e6

## Examples

```julia
using MathPhysicalConstants.SI
using Measurements
```

    ┌ Info: Precompiling MathPhysicalConstants [b5851bab-7749-5a5f-a75f-46072e3516e8]
    └ @ Base loading.jl:1187

Planck's h constant

```julia
SI.PlanckConstantH
```

    6.62607015e-34

Electron Compton Wavelength

```julia
SI.ƛe
```

    3.861592046068738e-13

Electron Compton Wavelength equation:

ƛ$_{e}$=$\hbar/m_{e}\cdot c$

```julia
# Reduced electron Compton wavelength
big(2.4263102367*10^-12)/big(2pi)
```

    3.861592676452718338399364915357181343914809630867925459864883008800725803939552e-13

```julia
SI.PlanckConstantHbar/(SI.MassElectron*SI.c)
```

    3.861592046068738e-13

ΔνCs
the unperturbed ground state hyperfine transition frequency of the caesium-133 atom in Hz

```julia
Measurements.value(big(SI.ΔνCs))
```

    9.19263177e+09

Electron Proton Hydrogen product

$m_e.m_p.m_H$

```julia
@time begin
using MathPhysicalConstants
big(SI.MassElectron*SI.MassProton*SI.MassHydrogen)
end
```

      0.003393 seconds (289 allocations: 15.672 KiB)

    2.550178071499845191383898081092660664856847100303471018620561488238203907897864e-84

```julia
Measurements.value(big((SI.e^128)-1))
```

    3.8877084059945686877402831236617906547557888982098378752e+55

```julia
using PyCall
@pyimport numpy
@pyimport sympy
R_197=(236*np.log(2)^3)/197 - 283/394*np.pi*np.log(2)^2 + 11/394*np.pi^2*np.log(2) + 209/394*np.log(np.sqrt(2) + 1)^3 - 5/197 + (16*sympy.catalan(49)*np.pi)/197
print(R_197)

```

    PyObject 1.30014667214333e+26

# How to calculate the visible Universe horizon radius

Using the regular CODATA2019 Gravitational Constant

```julia
SI.GravitationalConstant
```

    6.67408e-11

Using Sanchez's Gravitational Constant

```julia
SI.SanchezGravitationalConstant
```

    6.675453818e-11

## Let's first determine the gravitational coupling constant:

One determines the Gravitational Coupling Constant:
### $$a_{G}=\frac{\hbar.c}{G.m_{p}.m_{H}}$$

```julia
# $a_{G'}$ = $\hbar$$c$/$G'$$m_{p}$$m_{H}$
SI.PlanckConstantHbar*SI.c/SI.GravitationalConstant*SI.MassProton*SI.MassHydrogen
```

    1.32613243113089e-69

```julia
# $a_{G}$ = $\hbar$$c$/$G$$m_{p}$$m_{H}$
SI.PlanckConstantHbar*SI.c/SI.SanchezGravitationalConstant*SI.MassProton*SI.MassHydrogen
```

    1.3258595111686009e-69

## Visible Universe horizon radius calculus -distance- (method 1)

### $R$ $=$ $2a_{G}$ƛ$_{e}$

Using the regular CODATA2019 Gravitational Constant

```julia
# R1 = 2a_{G'} \lambdabar_{e}
R1 = 2*((SI.PlanckConstantHbar*SI.c)/(SI.GravitationalConstant*SI.MassProton*SI.MassHydrogen))*SI.ƛe
```

    1.3068334634480155e26

Using Francis M. Sanchez's Gravitational Constant

```julia
# R2 = 2a_{G} \lambdabar_{e}
R2 = 2*((SI.PlanckConstantHbar*SI.c)/(SI.SanchezGravitationalConstant*SI.MassProton*SI.MassHydrogen))*SI.ƛe
```

    1.3065645152410416e26

## Calculate the result in ly (LightYears) and Gly equivalent

Notice that one obtain a distance equivalent to 1.3813524253217268e10 ly or 13.813524253217268 Gly

Using the regular CODATA2019 Gravitational Constant

```julia
R1/SI.LightYear
```

    1.3813524253217268e10

```julia
R1/SI.GigaLightYear
```

    13.813524253217269

Using Francis M. Sanchez's Gravitational Constant

```julia
R2/SI.LightYear
```

    1.3810681410051859e10

```julia
R2/SI.GigaLightYear
```

    13.810681410051858

## Grand Cosmos Radius Calculus : $$R_{3mn}$$

```julia

```

$$\beta'=1-1/2a'^2$$ or $$\beta=1/(\sqrt(1-(1/a^2)))$$

$$m_H=m_p+\beta$$

$$a'=a(H/p)$$

```julia
a=SI.SanchezElectrConstant
```

    137.035999139

```julia
betaprime=1-(1/(2*a^2))
```

    0.9999733743227611

```julia
1/betaprime
```

    1.0000266263861843

```julia
beta=1/(np.sqrt(1-(1/a^2)))
```

    1.0000266267406759

```julia
m_p=SI.MassProton
```

    1.672621898e-27

```julia
m_p+beta
```

    1.0000266267406759

```julia
aprime=a*(m_H/m_p)
```

    8.193103780649673e28

## Universe horizon radius calculus (method 2) in 3 minutes: $$R_{3mn}$$

$$R_{3mn}=\frac{2.\hbar^2}{G.m_e.m_p.m_n}$$

```julia
R3mn=(2*SI.PlanckConstantHbar^2)/(SI.SanchezGravitationalConstant*SI.MassElectron*SI.MassProton*SI.MassNeutron)
```

    1.3056254088279212e26

```julia
1.3056254088279212e26/SI.LightYear
```

    1.3800754843601864e10

```julia
R3mn/SI.LightYear
```

    1.3800754843601864e10

```julia
R3mn/SI.GigaLightYear
```

    13.800754843601865

# Nambu Universe Radius calculus in 3 minutes (method 3)
$$R_N=\frac{2.\hbar^2}{G.(m_N)^3}$$

With Nambu mass $$m_N=m_e.a$$ and $$a=1/\alpha$$

```julia
R_N=2*SI.PlanckConstantHbar^2/(SI.GravitationalConstant*((SI.SanchezElectrConstant)*SI.MassElectron)^3)
print(R_N)
```

    1.7132461922203357e26

```julia
R_N/(SI.LightYear*10^9)
```

    18.109398396890033

# Eddington Universe Radius calculus in 3 minutes (method 4)
$$R_{Edd}=\frac{2.\hbar^2}{G.m_e.m_p^2}$$

```julia
R_Edd=2*SI.PlanckConstantHbar^2/(SI.GravitationalConstant*SI.MassElectron*SI.MassProton^2)
print(R_Edd)
```

    1.3076942324251935e26

```julia
R_Edd/(SI.GigaLightYear)
```

    13.822622775313032

# Francis M. Sanchez Universe Radius calculus in 3 minutes (method 5)
$$R_{Francis.M.Sanchez}=ƛ_e.g(6)$$

With $$g(k)=exp(2^{k+1/2})/k$$ when $$k=6$$
And $$ƛ_e=\frac{\hbar}{m_e.c}$$ as the reduced Compton electron wavelength radius

```julia
using PyCall
@pyimport numpy
R_FMS=(SI.PlanckConstantHbar/(SI.MassElectron*SI.c))*numpy.exp(2^(6+(1/2)))/6
print(R_FMS)
```

    1.3075686776359582e26

```julia
R_FMS/(SI.GigaLightYear)
```

    13.821295632892273

```julia

```

## Other MathPhysicalConstants Examples

```julia
SI.λe
```

    2.4263102367e-12

```julia
SI.ƛe
```

    3.861592046068738e-13

```julia
SI.a_G
```

    1.32613243113089e-69

```julia
SI.GravitationalConstant
```

    6.67408e-11

```julia
SI.SanchezGravitationalConstant
```

    6.675453818e-11

```julia
SI.KotovCosmicPeriodicity
```

    9600.061

```julia
SI.O_B
```

    4154781481226426191177580544000000

```julia
SI.O_P
```

```julia
R_N
```

    1.7128941663434092e26

```julia
#big(a^a)
a^a
```

    6.656929234280262e292

```julia
c
```

    2.99792458e8

```julia
# R_N.a^a/OP
(1.7128941663434092*10^26*6.656929234280262319338505195710627024485453090725789285697002238339569555154055*10^292)/2663849798329448283764291471531459318169741293570162121768960000000000000
```

    -0.0

```julia
#big(OP)
a^a/2663849798329448283764291471531459318169741293570162121768960000000000000
```

    2.498988208139569790796336006042090060318346705168905360223789924916938992808175e+220

```julia
big(a^a/OP)
```

    -1.443491427577785184694693971826619894560445108469416108779243834354330176795751e+274

```julia
SI.MassHydrogen
```

    1.6737236e-27

```julia
SI.MassTau
```

    3.16773502e-27

```julia
SI.c
```

    2.99792458e8

```julia
SI.μ0
```

    1.2566370614e-6

```julia
SI.ε0
```

    8.854187817e-12

```julia
SI.h
```

    6.62607015e-34

```julia
SI.PlanckConstantHbar
```

    1.05457162825e-34

```julia
SI.e
```

    2.718281828459045

```julia
SI.ℯ
```

    ℯ = 2.7182818284590...

```julia
SI.γ
```

    0.5772156649015329

```julia
SI.CmbTemperature
```

    2.7255

```julia
SI.SpeedOfLight
```

    2.99792458e8

```julia
# Happy family Product as seen in https://en.wikipedia.org/wiki/Sporadic_group
SI.HFP
```

    8.724905575508271e292

```julia
# 26 Sporadic Group https://en.wikipedia.org/wiki/Sporadic_group
SI.TSSGP
```

    Inf

With PyCall, Numpy, Scipy and PyPlot

```julia
import Pkg; Pkg.add("PyPlot")
```

     Resolving package versions...
     Installed Reexport ────────── v0.2.0
     Installed FixedPointNumbers ─ v0.5.3
     Installed LaTeXStrings ────── v1.0.3
     Installed PyPlot ──────────── v2.8.1
     Installed ColorTypes ──────── v0.7.5
     Installed Colors ──────────── v0.9.5
      Updating `~/Project.toml`
      [d330b81b] + PyPlot v2.8.1
      Updating `~/Manifest.toml`
      [3da002f7] + ColorTypes v0.7.5
      [5ae59095] + Colors v0.9.5
      [53c48c17] + FixedPointNumbers v0.5.3
      [b964fa9f] + LaTeXStrings v1.0.3
      [d330b81b] + PyPlot v2.8.1
      [189a3867] + Reexport v0.2.0

```julia
#python
#import numpy
#print(np.pi)
using PyCall
#math = pyimport("math")
@pyimport numpy
numpy.sin(numpy.pi / 4) # returns ≈ 1/√2 = 0.70710678...
#math.pi
```

    0.7071067811865475

```julia
## Examples of
```

```julia
using PyPlot
using PyCall
@pyimport math
@pyimport numpy as np

x = np.linspace(0,8*np.pi^2/log(2),30); y = np.sin(3*x +np.cos(x));
scatter(x, y, color="blue", linewidth=1.0, linestyle="-")
plot(x, y, color="red", linewidth=1.0, linestyle="--")
#plt.title("A logarithm modulated sinusoid")
plt.show()
```

```julia
using PyPlot
using PyCall
@pyimport math
@pyimport numpy as np

#x = rand(100)
#y = rand(10)
#plot(x=x,y=y)

x = [1:10;]
y = 10rand(10,1)

p = plot(x,y)
xlabel("X")
ylabel("Y")
title("Your Title Goes Here")
grid("on")
gcf() # Needed by IJulia to display plot

```

```julia
using PyCall
@pyimport numpy
println(numpy.pi)
```

    3.141592653589793

Gradually, more functionality will be added into this package.


# Práctica Background Subtraction

En esta segunda práctica del bloque de análisis de secuencias haremos trabajo con 3 técnicas de background subtraction.

En primer lugar debéis implementar vuestro propio método de eliminación de fondo basado en la idea que explicamos del filtro exponencial. Podéis hacer las adaptaciones que encontréis necesario a la idea general del método para mejorar su rendimiento.

En segundo lugar debéis de usar las implementaciones del MOG y del MOG2 que se encuentran en la librería
 [OpenCV](https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_video/py_bg_subtraction/py_bg_subtraction.html).

En tercer lugar lo que quiero es que hagáis una comparativa entre su método y los dos basados en Mixture de gausiano utilizando el dataset de la competición [changedetection.net](http://changedetection.net/) del año 2012.

En concreto debe usar una secuencia de cada categoría exceptuando la de *Thermal*.

Las métricas que a usar para cada secuencia son:

* **TP** : True Positive
* **FP** : False Positive
* **FN** : False Negative
* **TN** : True Negative
* **Re (Recall)** : TP / (TP + FN)
* **F-Measure** : (2 * Precision * Recall) / (Precision + Recall)
* **Precision** : TP / (TP + FP)

Finalmente se pide que hagáis un análisis de los resultados obtenidos por cada secuencia: problemas, carencias, puntos fuertes de los algoritmos ...

**Entrega:**

* Explicación del algoritmo implementado y código fuente.
* Resultados de las secuencias en formato imagen o video.
* Resultados de las secuencias en formato tabla resumen de las métricas.
* Análisis de los resultados

Secuencias a usar:
- baseline: todo
- cameraJitter: badminton, traffic
- dynamicBackground: canoe, fall, fountain02
- intermittentObjectMotion: sofa,
- shadow: busStation, copyMachine, cubicle, peopleInShade

### Métricas

```python
def recall(tp, fn):
    return (tp/(tp+fn))

def precision(tp, fp):
    return (tp/(tp+fp))

def f_measure(precision, recall):
    return ((2*precision*recall)/(precision+recall))

def all_metrics (resultPath, groundtruthPath, start_frame, end_frame):
    # Cargamos el resultado del filtro indicado
    result = loadImages(resultPath)
    groundTruth = loadImages(groundtruthPath)

    # ground truth empieza en start_frame, los frames están numerados de 1 hasta n, pero el array de frames va de 0 a n-1
    total_frames = end_frame-start_frame

    # Inicializamos un array para cada métrica donde se el resultado para cada frame
    tp =  np.zeros(total_frames)
    fp =  np.zeros(total_frames)
    fn =  np.zeros(total_frames)
    tn =  np.zeros(total_frames)

    # Calculamos las métricas True Positive, False Positive, False Negative, True Negative para cada frame
    for i in range(total_frames):
        tp[i], fp[i], fn[i], tn[i] = comparator(result[i+start_frame], groundTruth[i+start_frame])

    # Calculamos las métricas Recall, Precision y F-measure para cada frame
    recall_frame = recall(tp=tp, fn=fn)
    precision_frame = precision(tp=tp, fp=fp)
    fMeasure_frame = f_measure(precision=precision_frame, recall=recall_frame)

    # Calculamos las métricas anteriores para toda la secuencia en conjunto
    tp_secuencia = tp.sum()
    fp_secuencia = fp.sum()
    fn_secuencia = fn.sum()
    tn_secuencia = tn.sum()

    recall_secuencia = recall(tp=tp_secuencia, fn=fn_secuencia)
    precision_secuencia = precision(tp=tp_secuencia, fp=tp_secuencia)
    fMeasure_secuencia = f_measure(precision=precision_secuencia, recall=recall_secuencia)

    return tp, fp, fn, tn,\
           tp_secuencia, fp_secuencia, fn_secuencia, tn_secuencia,\
           recall_frame, precision_frame, fMeasure_frame,\
           recall_secuencia, precision_secuencia, fMeasure_secuencia
```

### Filtro exponencial para _background subtraction_
Parte de la idea de eliminación de fondo más básica: restar el fotograman **_fondo_** (_background_) del fotograma actual para obtener el **_primer plano_** (_foreground_).
El primer problema o tarea para realizar este algoritmo es conseguir ese fotograma **_fondo_**. Para ello se necesita una imagen que represente el fondo sin ningún otro objeto, pero esto no siempre es posible, por lo tanto hay que buscar otras soluciones. Una de ellas es estimar el fondo como la media o la mediana a partir de los primeros _n_ fotogramas.
El segundo problema que surge es que el fondo en los casos prácticos varía con el tiempo y por lo tanto es posible que ese fotograma _fondo_ que hemos estimado no nos sirve más tarde. Por lo tanto la mejora que se propone es estimar el fondo recursivamente respecto el fotograma anterior siguiendo la siguiente fórmula:
$$
\begin{align}
B_t=(1-{\alpha})B_{t-1}+{\alpha}I_t
\end{align}
$$
Donde $B_t$ es la estimación del fondo para el fotograma actual, $B_{t-1}$ es el fondo para el fotograma anterior, $I_t$ es el fotograma actual y $\alpha$ es el factor de aprendizaje que indica cuánto del fotograma actual se considerará fondo para el siguiente fotograma.

```python
from utils import *
from tqdm.notebook import tqdm
import matplotlib.pyplot as plt
```

Para mantener la limpieza del _notebook_ se han definidos las funciones para realizar el análisis dentro del archivo _utils.py_.
Las funciones que se encuentran son las siguientes:
- **comparator**: compara un _frame_ resultante de un modelo de _background subtraction_ con el resultado esperado (_groundtruth_)
- **loadImages**: devuelve una lista con las imágenes dentro de la carpeta especificada
- **exponentialFilter**: implementación del algoritmo basado en filtro exponencial
- **MOG**: implementación del algoritmo MOG disponible [aquí](https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_video/py_bg_subtraction/py_bg_subtraction.html)
- **MOG2**: implementación del algoritmo MOG2 disponible [aquí](https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_video/py_bg_subtraction/py_bg_subtraction.html)
- **im2vid**: genera video a partir de las imágenes ubicadas en una carpeta
- **showVideo**: muestra el video en una ventana flotante para visualizar mejor el resultado del modelo

Notar que, a pesar de tratarse de algoritmos que trabajan con video, el _dataset_ que se proporciona para realizar las pruebas son imágenes que representan los fotogramas de una secuencia. Por lo tanto, las funciones que se han realizado, se han pensado para trabajar con imágenes en vez de video.

Pasos a seguir para cada secuencia:
1. Cargar las imágenes en una variable (path de las imágenes)
2. Aplicar el filtro deseado EF, MOG y MOG2 (path donde se guardarán las imágenes aplicadas por el filtro)
3. Obtener las métricas con comparator (hacer bucle a lo largo de las imágenes resultado -> path del groundtruth)
4. Mostrar los resultados y videos
5. Comparar los resultados (hacer gráfica a lo largo del

```python
# dynamicBackground canoe dataset output paths
expFilter_canoe_path = 'DATA/dynamicBackground/results/canoe/expFilter/'
MOG_canoe_path = 'DATA/dynamicBackground/results/canoe/MOG/'
MOG2_canoe_path = 'DATA/dynamicBackground/results/canoe/MOG2'

# intermittentObject sofa dataset output paths
expFilter_sofa_path = 'DATA/intermittentObjectMotion/results/sofa/expFilter/'
MOG_sofa_path = 'DATA/intermittentObjectMotion/results/sofa/MOG'
MOG2_sofa_path = 'DATA/intermittentObjectMotion/results/sofa/MOG2'

# shadow copyMachine dataset output paths
expFilter_copyMachine_path = 'DATA/shadow/results/copyMachine/expFilter/'
MOG_copyMachine_path = 'DATA/shadow/results/copyMachine/MOG'
MOG2_copyMachine_path = 'DATA/shadow/results/copyMachine/MOG2'
```

# 1. Secuencia _highway_ del dataset _baseline_

Cargamos las imágenes de entrada y el ground truth con el que se calcularán las métricas.

```python
# baseline highway dataset input paths
gt_highway_path = 'DATA/baseline/baseline/highway/groundtruth/*.png'
in_highway_path = 'DATA/baseline/baseline/highway/input/*.jpg'

groundTruth = loadImages(gt_highway_path)
frames = loadImages(in_highway_path)
```

    Loading images: 100%|████████████████████████████████████████████████████████████| 1700/1700 [00:00<00:00, 2192.19it/s]
    Loading images: 100%|████████████████████████████████████████████████████████████| 1700/1700 [00:01<00:00, 1041.40it/s]

## 1.1. Algoritmo filtro exponencial

Aplicamos el algoritmo basado en el filtro exponencial que hemos implementado.

```python
# baseline highway dataset output path
expFilter_highway_path = 'DATA/baseline/results/highway/expFilter/'

exponentialFilter(frames, 0.3, expFilter_highway_path, 20, bgFilter ='MEDIAN')
```

    Algorithm application: 100%|██████████████████████████████████████████████████████| 1680/1680 [00:03<00:00, 551.56it/s]

Convertimos la secuencia de imágenes en video para poder visualizar mejor el resultado.

```python
im2vid(expFilter_highway_path+'*.jpg','testvid.mp4')
```

    Loading images: 100%|█████████████████████████████████████████████████████████████| 1700/1700 [00:06<00:00, 246.33it/s]
    Converting to video: 100%|███████████████████████████████████████████████████████| 1700/1700 [00:00<00:00, 1716.51it/s]

Mostramos el video en una ventana flotante.

```python
showVideo('testvid.mp4')
```

### 1.1.1. Cálculo de métricas
Calculamos las métricas comparando con el _ground truth_ propocionado. Para cada secuencia se proporciona una ROI (Region Of Interest) sobre la cual se realiza el groundtruth, para simplificar el cálculo se han seleccionado aquellas secuencias en las que la ROI sea toda la imagen. Por otro lado, también se especifica el _frame_ de inicio y final sobre los que se ha de realizar la verificación del algoritmo, por lo tanto solo se calcularán las métricas dentro de ese rango.

```python
tp1, fp1, fn1, tn1,\
tp_secuencia1, fp_secuencia1, fn_secuencia1, tn_secuencia1,\
recall1, precision1, fMeasure1,\
recall_secuencia1, precision_secuencia1, fMeasure_secuencia1 = all_metrics (expFilter_highway_path+'*.jpg', gt_highway_path, start_frame=469, end_frame=1699)
```

    Loading images: 100%|████████████████████████████████████████████████████████████| 1700/1700 [00:01<00:00, 1232.96it/s]
    Loading images: 100%|████████████████████████████████████████████████████████████| 1700/1700 [00:00<00:00, 2320.94it/s]

```python
plt.plot(tp1)

plt.show()
```

```python
# Cargamos el resultado del filtro exponencial en un array
result_expFilter = loadImages(expFilter_highway_path+'*.jpg')

# ground truth empieza en 470, los frames están numerados de 1 hasta 1700, pero el array de frames va de 0 a 1699
start_frame = 469
end_frame = 1699
total_frames = end_frame-start_frame

# Inicializamos un array para cada métrica donde se el resultado para cada frame
tp_expFilter =  np.zeros(total_frames)
fp_expFilter =  np.zeros(total_frames)
fn_expFilter =  np.zeros(total_frames)
tn_expFilter =  np.zeros(total_frames)

# Calculamos las métricas True Positive, False Positive, False Negative, True Negative para cada frame
for i in range(total_frames):
    tp_expFilter[i], fp_expFilter[i], fn_expFilter[i], tn_expFilter[i] = comparator(result_expFilter[i+start_frame],
                                                                                groundTruth[i+start_frame])

# Calculamos las métricas Recall, Precision y F-measure para cada frame
recall_expFilter = recall(tp=tp_expFilter, fn=fn_expFilter)
precision_expFilter = precision(tp=tp_expFilter, fp=fp_expFilter)
fMeasure_expFilter = f_measure(precision=precision_expFilter, recall=recall_expFilter)

# Calculamos las métricas anteriores para toda la secuencia en conjunto
tp_expFilter_secuencia = tp_expFilter.sum()
fp_expFilter_secuencia = fp_expFilter.sum()
fn_expFilter_secuencia = fn_expFilter.sum()
tn_expFilter_secuencia = tn_expFilter.sum()

recall_expFilter_secuencia = recall(tp=tp_expFilter_secuencia, fn=fn_expFilter_secuencia)
precision_expFilter_secuencia = precision(tp=tp_expFilter_secuencia, fp=tp_expFilter_secuencia)
fMeasure_expFilter_secuencia = f_measure(precision=precision_expFilter_secuencia, recall=recall_expFilter_secuencia)
```

    Loading images: 100%|████████████████████████████████████████████████████████████| 1700/1700 [00:01<00:00, 1331.68it/s]

```python
precision_expFilter
```

    array([0.05653631, 0.04029126, 0.01318373, ..., 0.62876909, 0.61493709,
           0.6044432 ])

```python
plt.plot(tp_expFilter)

plt.show()
```

## 1.2. Algoritmo MOG

Aplicamos el algoritmo MOG (Mixture Of Gaussians)

```python
# baseline highway dataset output path
MOG_highway_path = 'DATA/baseline/results/highway/MOG/'

MOG(frames,MOG_highway_path)
```

    Algorithm application: 100%|██████████████████████████████████████████████████████| 1700/1700 [00:04<00:00, 394.87it/s]

```python
im2vid(MOG_highway_path+'*.jpg','testvid.mp4')
```

```python
showVideo('testvid.mp4')
```

### 1.2.1. Cálculo de métricas

```python
# Cargamos el resultado del filtro exponencial en un array
result_MOG = loadImages(MOG_highway_path+'*.jpg')

# ground truth empieza en 470, los frames están numerados de 1 hasta 1700, pero el array de frames va de 0 a 1699
start_frame = 469
end_frame = 1699
total_frames = end_frame-start_frame

# Inicializamos un array para cada métrica donde se el resultado para cada frame
tp_MOG =  np.zeros(total_frames)
fp_MOG =  np.zeros(total_frames)
fn_MOG =  np.zeros(total_frames)
tn_MOG =  np.zeros(total_frames)

# Calculamos las métricas True Positive, False Positive, False Negative, True Negative para cada frame
for i in range(total_frames):
    tp_MOG[i], fp_MOG[i], fn_MOG[i], tn_MOG[i] = comparator(result_MOG[i+start_frame],
                                                            groundTruth[i+start_frame])

# Calculamos las métricas Recall, Precision y F-measure para cada frame
recall_MOG = recall(tp=tp_MOG, fn=fn_MOG)
precision_MOG = precision(tp=tp_MOG, fp=fp_MOG)
fMeasure_MOG = f_measure(precision=precision_MOG, recall=recall_MOG)

# Calculamos las métricas anteriores para toda la secuencia en conjunto
tp_MOG_secuencia = tp_MOG.sum()
fp_MOG_secuencia = fp_MOG.sum()
fn_MOG_secuencia = fn_MOG.sum()
tn_MOG_secuencia = tn_MOG.sum()

recall_MOG_secuencia = recall(tp=tp_MOG_secuencia, fn=fn_MOG_secuencia)
precision_MOG_secuencia = precision(tp=tp_MOG_secuencia, fp=tp_MOG_secuencia)
fMeasure_MOG_secuencia = f_measure(precision=precision_MOG_secuencia, recall=recall_MOG_secuencia)
```

    Loading images: 100%|█████████████████████████████████████████████████████████████| 1700/1700 [00:07<00:00, 232.64it/s]

## 1.3. Algoritmo MOG2

```python
# baseline highway dataset output path
MOG2_highway_path = 'DATA/baseline/results/highway/MOG2/'

MOG2(frames,MOG2_highway_path)
```

    Algorithm application: 100%|██████████████████████████████████████████████████████| 1700/1700 [00:04<00:00, 344.89it/s]

### 1.3.1. Cálculo de métricas

```python
# Cargamos el resultado del filtro exponencial en un array
result_MOG2 = loadImages(MOG2_highway_path+'*.jpg')

# ground truth empieza en 470, los frames están numerados de 1 hasta 1700, pero el array de frames va de 0 a 1699
start_frame = 469
end_frame = 1699
total_frames = end_frame-start_frame

# Inicializamos un array para cada métrica donde se el resultado para cada frame
tp_MOG2 =  np.zeros(total_frames)
fp_MOG2 =  np.zeros(total_frames)
fn_MOG2 =  np.zeros(total_frames)
tn_MOG2 =  np.zeros(total_frames)

# Calculamos las métricas True Positive, False Positive, False Negative, True Negative para cada frame
for i in range(total_frames):
    tp_MOG2[i], fp_MOG2[i], fn_MOG2[i], tn_MOG2[i] = comparator(result_MOG2[i+start_frame],
                                                            groundTruth[i+start_frame])

# Calculamos las métricas Recall, Precision y F-measure para cada frame
recall_MOG2 = recall(tp=tp_MOG2, fn=fn_MOG2)
precision_MOG2 = precision(tp=tp_MOG2, fp=fp_MOG2)
fMeasure_MOG2 = f_measure(precision=precision_MOG2, recall=recall_MOG2)

# Calculamos las métricas anteriores para toda la secuencia en conjunto
tp_MOG2_secuencia = tp_MOG2.sum()
fp_MOG2_secuencia = fp_MOG2.sum()
fn_MOG2_secuencia = fn_MOG2.sum()
tn_MOG2_secuencia = tn_MOG2.sum()

recall_MOG2_secuencia = recall(tp=tp_MOG2_secuencia, fn=fn_MOG2_secuencia)
precision_MOG2_secuencia = precision(tp=tp_MOG2_secuencia, fp=tp_MOG2_secuencia)
fMeasure_MOG2_secuencia = f_measure(precision=precision_MOG2_secuencia, recall=recall_MOG2_secuencia)
```

## Comparación resultados

# 2. Secuencia _badminton_ del dataset _cameraJitter_

Cargamos las imágenes de entrada y el ground truth con el que se calcularán las métricas.

```python
#camaraJitter  badminton dataset input
gt_badminton_path = 'DATA/cameraJitter/cameraJitter/badminton/groundtruth/*.png'
in_badminton_path = 'DATA/cameraJitter/cameraJitter/badminton/input/*.jpg'

groundTruth = loadImages(gt_badminton_path)
frames = loadImages(in_badminton_path)
```

    Loading images: 100%|█████████████████████████████████████████████████████████████| 1150/1150 [00:03<00:00, 296.71it/s]
    Loading images: 100%|█████████████████████████████████████████████████████████████| 1150/1150 [00:08<00:00, 129.45it/s]

## 2.1. Algoritmo filtro exponencial

```python
# camaraJitter badminton dataset output path
expFilter_badminton_path = 'DATA/cameraJitter/results/badminton/expFilter/'

```

### 2.1.1. Cálculo de métricas

## 2.2. Algoritmo MOG

```python
# camaraJitter badminton dataset output paths
MOG_badminton_path = 'DATA/cameraJitter/results/badminton/MOG/'
```

### 2.2.1. Cálculo de métricas

## 2.3. Algoritmo MOG2

```python
# camaraJitter badminton dataset output paths
MOG2_badminton_path = 'DATA/cameraJitter/results/badminton/MOG2'
```

### 2.3.1. Cálculo de métricas

## Comparación resultados

# 3. Secuencia _canoe_ del dataset _dynamicBackground_

```python
Cargamos las imágenes de entrada y el ground truth con el que se calcularán las métricas.
```

```python
# dynamicBackground  canoe dataset input
gt_canoe_path = 'DATA/dynamicBackground/dynamicBackground/canoe/groundtruth/*.png'
in_canoe_path = 'DATA/dynamicBackground/dynamicBackground/canoe/input/*.jpg'

groundTruth = loadImages(gt_canoe_path)
frames = loadImages(in_canoe_path)
```

```python
# dynamicBackground canoe dataset output path
expFilter_canoe_path = 'DATA/dynamicBackground/results/canoe/expFilter/'
MOG_canoe_path = 'DATA/dynamicBackground/results/canoe/MOG/'
MOG2_canoe_path = 'DATA/dynamicBackground/results/canoe/MOG2'
```

## 3.1. Algoritmo filtro exponencial

```python
# dynamicBackground canoe dataset output paths
expFilter_canoe_path = 'DATA/dynamicBackground/results/canoe/expFilter/'
```

### 3.1.1 Cálculo de métricas

## 3.2. Algoritmo MOG

```python
# dynamicBackground canoe dataset output path
MOG_canoe_path = 'DATA/dynamicBackground/results/canoe/MOG/'
```

### 3.2.1 Cálculo de métricas

## 3.3. Algoritmo MOG2

```python
# dynamicBackground canoe dataset output path
MOG2_canoe_path = 'DATA/dynamicBackground/results/canoe/MOG2'
```

### 3.3.1. Cálculo de métricas

## 3.4. Comparación resultados

# 4. Secuencia _sofa_ del dataset _intermittentObjectMotion_

```python
Cargamos las imágenes de entrada y el ground truth con el que se calcularán las métricas.
```

```python
# intermittentObjectMotion sofa dataset input
gt_sofa_path = 'DATA/intermittentObjectMotion/intermittentObjectMotion/sofa/groundtruth/*.png'
in_sofa_path = 'DATA/intermittentObjectMotion/intermittentObjectMotion/sofa/input/*.jpg'

groundTruth = loadImages(gt_sofa_path)
frames = loadImages(in_sofa_path)
```

## 4.1. Algoritmo filtro exponencial

### 4.1.1. Cálculo de métricas

## 4.2. Algoritmo MOG

### 4.2.1. Cálculo de métricas

## 4.3. Algoritmo MOG2

### 4.3.1. Cálculo de métricas

## 4.4. Comparación resultados

# 5. Secuencia _copyMachine_ del dataset _shadow_

```python
Cargamos las imágenes de entrada y el ground truth con el que se calcularán las métricas.
```

```python
# shadow copyMachine dataset input
gt_copyMachine_path = 'DATA/shadow/shadow/copyMachine/groundtruth/*.png'
in_copyMachine_path = 'DATA/shadow/shadow/copyMachine/input/*.jpg'

groundTruth = loadImages(gt_copyMachine_path)
frames = loadImages(in_copyMachine_path)
```

## 5.1. Algoritmo filtro exponencial

### 5.1.1. Cálculo de métricas

## 5.2. Algoritmo MOG

### 5.2.1. Cálculo de métricas

## 5.3. Algoritmo MOG2

### 5.3.1. Cálculo de métricas

## 5.4. Comparación resultados


# $M\Omega$, the orbital angular velocity, up to and including 3.5PN order

## This notebook constructs the orbital angular velocity

**Notebook Status:**  Validated

**Validation Notes:** All expressions in this notebook were transcribed twice by hand on separate occasions, and expressions were corrected as needed to ensure consistency with published work. Published work was cross-validated and typo(s) in published work were corrected. In addition, this tutorial notebook has been confirmed to be self-consistent with its corresponding NRPy+ module, as documented [below](#code_validation). **Additional validation tests may have been performed, but are as yet, undocumented.**

## Author: Zach Etienne

### This notebook exists as the following Python module:
1. [PN_MOmega.py](../../edit/NRPyPN/PN_MOmega.py)

### This notebook & corresponding Python module depend on the following NRPy+/NRPyPN Python modules:
1. [indexedexp.py](../../edit/indexedexp.py): [**documentation+tutorial**](../Tutorial-Indexed_Expressions.ipynb)
1. [NRPyPN_shortcuts.py](../../edit/NRPyPN/NRPyPN_shortcuts.py): [**documentation**](NRPyPN_shortcuts.ipynb)

# Table of Contents
$$\label{toc}$$

1. Part 1: [$M\Omega$](#momega), up to and including 3.5PN order, as derived in [Ramos-Buades, Husa, and Pratten (2018)](https://arxiv.org/abs/1810.00036)
1. Part 2: [Validation against second transcription and corresponding Python module](#code_validation)
1. Part 3: [Validation against trusted numerical values](#code_validationv2) (i.e., in Table V of [Ramos-Buades, Husa, and Pratten (2018)](https://arxiv.org/abs/1810.00036))
1. Part 4: [LaTeX PDF output](#latex_pdf_output): $\LaTeX$ PDF Output

# Part 1: $M\Omega$, up to and including 3.5PN order, as derived in [Ramos-Buades, Husa, and Pratten (2018)](https://arxiv.org/abs/1810.00036) \[Back to [top](#toc)\]
$$\label{momega}$$

As described in the [nonspinning Hamiltonian notebook](PN-Hamiltonian-Nonspinning.ipynb), the basic physical system assumes two point particles of mass $m_1$ and $m_2$ with corresponding momentum vectors $\mathbf{P}_1$ and $\mathbf{P}_2$, and displacement vectors $\mathbf{X}_1$ and $\mathbf{X}_2$ with respect to the center of mass. Here we also consider the spin vectors of each point mass $\mathbf{S}_1$ and $\mathbf{S}_2$, respectively.

To reduce possibility of copying error, the equation for $M\Omega$ is taken directly from the arXiv LaTeX source code of Eq A1 in [Ramos-Buades, Husa, and Pratten (2018)](https://arxiv.org/abs/1810.00036), and only mildly formatted to (1) improve presentation in Jupyter notebooks, (2) to ensure some degree of consistency in notation across different terms in other NRPyPN notebooks, and (3) to correct any errors. In particular, the boxed negative sign at 2.5PN order ($a_5$ below) was missing in the original equation. We will later show that this negative sign is necessary for consistency with other expressions in the same paper, as well as with the expression up to 3PN order in :
$$
M\Omega = \frac{1}{r^{3/2}}\left(1 + \sum_{k=2}^7 \frac{a_k}{r^{k/2}}\right),
$$
where all terms in boxes should be replaced by 1:
\begin{align}
a_2 &= -\left[ \frac{  \left(3 q^2+5 q+3\right)}{2 (q+1)^2}\right] \\
a_3 &= -\frac{(3 q+4) \chi _{1z}}{4 (q+1)^2 }-  \frac{q (4 q+3) \chi _{2z}}{4 (q+1)^2 } \\
a_4 &= -\frac{3 q^2 \chi _{2x}^2}{2 (q+1)^2}+ \frac{3 q^2 \chi _{2y}^2}{4 (q+1)^2 }
+\frac{3 q^2 \chi _{2z}^2}{4 (q+1)^2 }+\frac{24 q^4+103 q^3+164 q^2+103 q+24}{16 (q+1)^4 } \\
&\quad -\frac{3 \chi _{1x}^2}{2 (q+1)^2 }-\frac{3 q \chi _{1x}\chi _{2x}}{(q+1)^2 }+\frac{3 \chi _{1y}^2}{4 (q+1)^2 }+\frac{3 q \chi _{1y} \chi _{2y}}{2 (q+1)^2 }+\frac{3 \chi _{1z}^2}{4 (q+1)^2 }+\frac{3 q \chi _{1z} \chi _{2z}}{2 (q+1)^2} \\
a_5 &= \frac{3 \left(13 q^3+34 q^2+30 q+16\right) \chi _{1z}}{16 (q+1)^4}+  \frac{3 q \left(16 q^3+30 q^2+34 q+13\right) \chi _{2z}}{16 (q+1)^4 }\\
a_6 &=  \frac{\left(155 q^2+180 q+76\right) \chi _{1x}^2}{16 (q+1)^4 \boxed{r^3}}+\frac{q \left(120 q^2+187 q+120\right) \chi _{1x} \chi _{2x}}{8 (q+1)^4 \boxed{r^3}}-\frac{\left(55 q^2+85 q+43\right) \chi _{1y}^2}{8 (q+1)^4 \boxed{r^3}} \\
&  -\frac{q \left(54 q^2+95 q+54\right) \chi _{1y} \chi _{2y}}{4 (q+1)^4 \boxed{r^3}}-\frac{q \left(96 q^2+127 q+96\right) \chi _{1z} \chi _{2z}}{16 (q+1)^4 \boxed{r^3}}+\frac{q^2 \left(76 q^2+180 q+155\right) \chi _{2x}^2}{16 (q+1)^4 \boxed{r^3}} \\
&  -\frac{q^2 \left(43 q^2+85 q+55\right) \chi _{2y}^2}{8 (q+1)^4 \boxed{r^3}}-\frac{q^2 (2 q+5) (14 q+27) \chi _{2z}^2}{32 (q+1)^4 \boxed{r^3}} -\frac{(5 q+2) (27 q+14) \chi _{1z}^2}{32 (q+1)^4 \boxed{r^3}} \\
& +\frac{501 \pi ^2 q (q+1)^4-4 \left(120 q^6+2744 q^5+10049 q^4+14820 q^3+10049 q^2+2744 q+120\right)}{384 (q+1)^6 \boxed{r^3}} \\
a_7 &= \frac{3 (4 q+1) q^3 \chi _{2 x}^2 \chi _{2 z}}{2 (q+1)^4}-\frac{3 (4 q+1) q^3 \chi _{2 y}^2 \chi _{2 z}}{8 (q+1)^4}-\frac{3 (4 q+1) q^3 \chi _{2 z}^3}{8 (q+1)^4}+\chi _{1x} \left(\frac{9 (2 q+1) q^2 \chi _{2 x} \chi _{2 z}}{4 (q+1)^4}+\frac{9 (q+2) q \chi _{2 x} \chi _{\boxed{?}z}}{4 (q+1)^4}\right) \\
& +\chi _{1y} \left(\frac{9 q^2 \chi _{2 y} \chi _{1z}}{4 (q+1)^4}+\frac{9 q^2 \chi _{2 y} \chi _{2 z}}{4 (q+1)^4}\right) \\
& +\chi _{1z} \left(\frac{9 q^2 (2 q+3) \chi _{2 x}^2}{4 (q+1)^4}-\frac{9 q^2 (q+2) \chi _{2 y}^2}{4 (q+1)^4}-\frac{9 q^2 \chi _{2 z}^2}{4 (q+1)^3}-\frac{135 q^5+385 q^4+363 q^3+377 q^2+387 q+168}{32 (q+1)^6}\right) \\
& -\frac{\left(168 q^5+387 q^4+377 q^3+363 q^2+385 q+135\right) q \chi _{2 z}}{32 (q+1)^6}+\chi _{1x}^2 \left(\frac{3 (q+4) \chi _{1z}}{2 (q+1)^4}+\frac{9 q (3 q+2) \chi _{2 z}}{4 (q+1)^4}\right)\\
&+\chi _{1y}^2 \left(-\frac{3 (q+4) \chi _{1z}}{8 (q+1)^4}-\frac{9 q (2 q+1) \chi _{2 z}}{4 (q+1)^4}\right)-\frac{9 q \chi _{1z}^2 \chi _{2 z}}{4 (q+1)^3}-\frac{3 (q+4) \chi _{1z}^3}{8 (q+1)^4},
\end{align}

Let's divide and conquer, tackling the coefficients one at a time:
\begin{align}
a_2 &= -\left[ \frac{  \left(3 q^2+5 q+3\right)}{2 (q+1)^2}\right] \\
a_3 &= -\frac{(3 q+4) \chi _{1z}}{4 (q+1)^2 }-  \frac{q (4 q+3) \chi _{2z}}{4 (q+1)^2 } \\
a_4 &= -\frac{3 q^2 \chi _{2x}^2}{2 (q+1)^2}+ \frac{3 q^2 \chi _{2y}^2}{4 (q+1)^2 }
+\frac{3 q^2 \chi _{2z}^2}{4 (q+1)^2 }+\frac{24 q^4+103 q^3+164 q^2+103 q+24}{16 (q+1)^4 } \\
&\quad -\frac{3 \chi _{1x}^2}{2 (q+1)^2 }-\frac{3 q \chi _{1x}\chi _{2x}}{(q+1)^2 }+\frac{3 \chi _{1y}^2}{4 (q+1)^2 }+\frac{3 q \chi _{1y} \chi _{2y}}{2 (q+1)^2 }+\frac{3 \chi _{1z}^2}{4 (q+1)^2 }+\frac{3 q \chi _{1z} \chi _{2z}}{2 (q+1)^2}
\end{align}

```python
# Step 0: Add NRPy's directory to the path
# https://stackoverflow.com/questions/16780014/import-file-from-parent-directory
import os,sys                    # Standard Python modules for multiplatform OS-level functions
nrpy_dir_path = os.path.join("..")
if nrpy_dir_path not in sys.path:
    sys.path.append(nrpy_dir_path)
import sympy as sp               # SymPy: The Python computer algebra package upon which NRPy+ depends
import indexedexp as ixp         # NRPy+: Symbolic indexed expression (e.g., tensors, vectors, etc.) support
from NRPyPN_shortcuts import div # NRPyPN: shortcuts for e.g., vector operations

# Step 1: Construct terms a_2, a_3, and a_4, from
#  Eq A2 of Ramos-Buades, Husa, and Pratten (2018)
#    https://arxiv.org/abs/1810.00036
#  These terms have been independently validated
#    against the same terms in Eq 6 of
#  Healy, Lousto, Nakano, and Zlochower (2017)
#    https://arxiv.org/abs/1702.00872
def MOmega__a_2_thru_a_4(m1,m2, chi1x,chi1y,chi1z, chi2x,chi2y,chi2z):
    q = m2/m1 # It is assumed that q >= 1, so m2 >= m1.
    global a_2,a_3,a_4
    a_2 = -((3*q**2+5*q+3)/(2*(q+1)**2))
    a_3 = (-(3*q+4)*chi1z/(4*(q+1)**2) - q*(4*q+3)*chi2z/(4*(q+1)**2))
    a_4 = (-3*q**2*chi2x**2/(2*(q+1)**2)
           +3*q**2*chi2y**2/(4*(q+1)**2)
           +3*q**2*chi2z**2/(4*(q+1)**2)
           +(+24*q**4 + 103*q**3 + 164*q**2 + 103*q + 24)/(16*(q+1)**4)
           -3*chi1x**2/(2*(q+1)**2)
           -3*q*chi1x*chi2x/(q+1)**2
           +3*chi1y**2/(4*(q+1)**2)
           +3*q*chi1y*chi2y/(2*(q+1)**2)
           +3*chi1z**2/(4*(q+1)**2)
           +3*q*chi1z*chi2z/(2*(q+1)**2))
```

```python
# Second version, for validation purposes only.
def MOmega__a_2_thru_a_4v2(m1,m2, chi1x,chi1y,chi1z, chi2x,chi2y,chi2z):
    q = m2/m1 # It is assumed that q >= 1, so m2 >= m1.
    global a_2v2,a_3v2,a_4v2
    a_2v2 = - (3*q**2+5*q+3)/(2*(q+1)**2)
    a_3v2 = +(-(3*q+4)*chi1z/(4*(q+1)**2)
                  -q*(4*q+3)*chi2z/(4*(q+1)**2))
    a_4v2 = +(-(3*q**2*chi2x**2)/(2*(q+1)**2) + 3*q**2*chi2y**2/(4*(q+1)**2) + 3*q**2*chi2z**2/(4*(q+1)**2)
                  +(24*q**4+103*q**3+164*q**2+103*q+24)/(16*(q+1)**4) - 3*chi1x**2/(2*(q+1)**2)
                  -3*q*chi1x*chi2x/(q+1)**2 + 3*chi1y**2/(4*(q+1)**2) + 3*q*chi1y*chi2y/(2*(q+1)**2)
                  +3*chi1z**2/(4*(q+1)**2) + 3*q*chi1z*chi2z/(2*(q+1)**2))
```

Next, $a_5$ and $a_6$:
\begin{align}
a_5 &= \frac{3 \left(13 q^3+34 q^2+30 q+16\right) \chi _{1z}}{16 (q+1)^4}+  \frac{3 q \left(16 q^3+30 q^2+34 q+13\right) \chi _{2z}}{16 (q+1)^4 }\\
a_6 &=  \frac{\left(155 q^2+180 q+76\right) \chi _{1x}^2}{16 (q+1)^4 \boxed{r^3}}+\frac{q \left(120 q^2+187 q+120\right) \chi _{1x} \chi _{2x}}{8 (q+1)^4 \boxed{r^3}}-\frac{\left(55 q^2+85 q+43\right) \chi _{1y}^2}{8 (q+1)^4 \boxed{r^3}} \\
&  -\frac{q \left(54 q^2+95 q+54\right) \chi _{1y} \chi _{2y}}{4 (q+1)^4 \boxed{r^3}}-\frac{q \left(96 q^2+127 q+96\right) \chi _{1z} \chi _{2z}}{16 (q+1)^4 \boxed{r^3}}+\frac{q^2 \left(76 q^2+180 q+155\right) \chi _{2x}^2}{16 (q+1)^4 \boxed{r^3}} \\
&  -\frac{q^2 \left(43 q^2+85 q+55\right) \chi _{2y}^2}{8 (q+1)^4 \boxed{r^3}}-\frac{q^2 (2 q+5) (14 q+27) \chi _{2z}^2}{32 (q+1)^4 \boxed{r^3}} -\frac{(5 q+2) (27 q+14) \chi _{1z}^2}{32 (q+1)^4 \boxed{r^3}} \\
& +\frac{501 \pi ^2 q (q+1)^4-4 \left(120 q^6+2744 q^5+10049 q^4+14820 q^3+10049 q^2+2744 q+120\right)}{384 (q+1)^6 \boxed{r^3}} \\
\end{align}

```python
# Construct terms a_5 and a_6, from
#  Eq A1 of Ramos-Buades, Husa, and Pratten (2018)
#    https://arxiv.org/abs/1810.00036
#  These terms have been independently validated
#    against the same terms in Eq 6 of
#  Healy, Lousto, Nakano, and Zlochower (2017)
#    https://arxiv.org/abs/1702.00872
def MOmega__a_5_thru_a_6(m1,m2, chi1x,chi1y,chi1z, chi2x,chi2y,chi2z):
    q = m2/m1 # It is assumed that q >= 1, so m2 >= m1.
    global a_5,a_6
    a_5 = (+3*  (13*q**3 + 34*q**2 + 30*q + 16)*chi1z/(16*(q+1)**4)
           +3*q*(16*q**3 + 30*q**2 + 34*q + 13)*chi2z/(16*(q+1)**4))
    a_6 = (+(+155*q**2 + 180*q + 76)*chi1x**2/(16*(q+1)**4)
           +q*(+120*q**2 + 187*q + 120)*chi1x*chi2x/(8*(q+1)**4)
           -(+55*q**2 + 85*q + 43)*chi1y**2/(8*(q+1)**4)
           -q*(+54*q**2 + 95*q + 54)*chi1y*chi2y/( 4*(q+1)**4)
           -q*(+96*q**2 +127*q + 96)*chi1z*chi2z/(16*(q+1)**4)
           +q**2*(+76*q**2 + 180*q + 155)*chi2x**2/(16*(q+1)**4)
           -q**2*(+43*q**2 +  85*q +  55)*chi2y**2/( 8*(q+1)**4)
           -q**2*(+2*q+5)*(+14*q+27)*chi2z**2/(32*(q+1)**4)
           -     (+5*q+2)*(+27*q+14)*chi1z**2/(32*(q+1)**4)
           +(+501*sp.pi**2*q*(q+1)**4
             -4*(120*q**6 + 2744*q**5 + 10049*q**4 + 14820*q**3 + 10049*q**2 + 2744*q + 120))/(384*(q+1)**6))
```

```python
# Second version, for validation purposes only.
def MOmega__a_5_thru_a_6v2(m1,m2, chi1x,chi1y,chi1z, chi2x,chi2y,chi2z):
    q = m2/m1 # It is assumed that q >= 1, so m2 >= m1.
    pi = sp.pi
    global a_5v2,a_6v2
    a_5v2 = +(+3*  (13*q**3+34*q**2+30*q+16)*chi1z/(16*(q+1)**4)
                  +3*q*(16*q**3+30*q**2+34*q+13)*chi2z/(16*(q+1)**4))
    a_6v2 =+(+(155*q**2+180*q+76)*chi1x**2   /(16*(q+1)**4) + q*(120*q**2+187*q+120)*chi1x*chi2x/(8*(q+1)**4)
                  -( 55*q**2+ 85*q+43)*chi1y**2   /( 8*(q+1)**4) - q*( 54*q**2+ 95*q+ 54)*chi1y*chi2y/(4*(q+1)**4)
                  -q   *(96*q**2+127*q+ 96)*chi1z*chi2z/(16*(q+1)**4)
                  +q**2*(76*q**2+180*q+155)*chi2x**2   /(16*(q+1)**4)
                  -q**2*(43*q**2+ 85*q+ 55)*chi2y**2   /( 8*(q+1)**4)
                  -q**2*(2*q+5)*(14*q+27)  *chi2z**2   /(32*(q+1)**4)
                  -     (5*q+2)*(27*q+14)  *chi1z**2   /(32*(q+1)**4)
                  +(501*sp.pi**2*q*(q+1)**4 - 4*(120*q**6+2744*q**5+10049*q**4+14820*q**3+10049*q**2+2744*q+120))
                   /(384*(q+1)**6))
```

Finally, $a_7$:
\begin{align}
a_7 &= \frac{3 (4 q+1) q^3 \chi _{2 x}^2 \chi _{2 z}}{2 (q+1)^4}-\frac{3 (4 q+1) q^3 \chi _{2 y}^2 \chi _{2 z}}{8 (q+1)^4}-\frac{3 (4 q+1) q^3 \chi _{2 z}^3}{8 (q+1)^4}+\chi _{1x} \left(\frac{9 (2 q+1) q^2 \chi _{2 x} \chi _{2 z}}{4 (q+1)^4}+\frac{9 (q+2) q \chi _{2 x} \chi _{\boxed{?}z}}{4 (q+1)^4}\right) \\
& +\chi _{1y} \left(\frac{9 q^2 \chi _{2 y} \chi _{1z}}{4 (q+1)^4}+\frac{9 q^2 \chi _{2 y} \chi _{2 z}}{4 (q+1)^4}\right) \\
& +\chi _{1z} \left(\frac{9 q^2 (2 q+3) \chi _{2 x}^2}{4 (q+1)^4}-\frac{9 q^2 (q+2) \chi _{2 y}^2}{4 (q+1)^4}-\frac{9 q^2 \chi _{2 z}^2}{4 (q+1)^3}-\frac{135 q^5+385 q^4+363 q^3+377 q^2+387 q+168}{32 (q+1)^6}\right) \\
& -\frac{\left(168 q^5+387 q^4+377 q^3+363 q^2+385 q+135\right) q \chi _{2 z}}{32 (q+1)^6}+\chi _{1x}^2 \left(\frac{3 (q+4) \chi _{1z}}{2 (q+1)^4}+\frac{9 q (3 q+2) \chi _{2 z}}{4 (q+1)^4}\right)\\
&+\chi _{1y}^2 \left(-\frac{3 (q+4) \chi _{1z}}{8 (q+1)^4}-\frac{9 q (2 q+1) \chi _{2 z}}{4 (q+1)^4}\right)-\frac{9 q \chi _{1z}^2 \chi _{2 z}}{4 (q+1)^3}-\frac{3 (q+4) \chi _{1z}^3}{8 (q+1)^4}
\end{align}

```python
# Construct term a_7, from Eq A1 of
#  Ramos-Buades, Husa, and Pratten (2018)
#    https://arxiv.org/abs/1810.00036
def MOmega__a_7(m1,m2, chi1x,chi1y,chi1z, chi2x,chi2y,chi2z):
    q = m2/m1 # It is assumed that q >= 1, so m2 >= m1.
    global a_7
    a_7 = (+3*(4*q+1)*q**3*chi2x**2*chi2z/(2*(q+1)**4)
           -3*(4*q+1)*q**3*chi2y**2*chi2z/(8*(q+1)**4)
           -3*(4*q+1)*q**3*chi2z**3      /(8*(q+1)**4)
           +chi1x*(+9*(2*q+1)*q**2*chi2x*chi2z/(4*(q+1)**4)
                   +9*(1*q+2)*q   *chi2x*chi1z/(4*(q+1)**4))
           +chi1y*(+9*q**2*chi2y*chi1z/(4*(q+1)**4)
                   +9*q**2*chi2y*chi2z/(4*(q+1)**4))
           +chi1z*(+9*q**2*(2*q+3)*chi2x**2/(4*(q+1)**4)
                   -9*q**2*(  q+2)*chi2y**2/(4*(q+1)**4)
                   -9*q**2        *chi2z**2/(4*(q+1)**3)
                   -(135*q**5 + 385*q**4 + 363*q**3 + 377*q**2 + 387*q + 168)/(32*(q+1)**6))
           -(+168*q**5 + 387*q**4 + 377*q**3 + 363*q**2 + 385*q + 135)*q*chi2z/(32*(q+1)**6)
           +chi1x**2*(+3*(q+4)*chi1z/(2*(q+1)**4)
                      +9*q*(3*q+2)*chi2z/(4*(q+1)**4))
           +chi1y**2*(-3*(q+4)*chi1z/(8*(q+1)**4)
                      -9*q*(2*q+1)*chi2z/(4*(q+1)**4))
           -9*q*chi1z**2*chi2z/(4*(q+1)**3)
           -3*(q+4)*chi1z**3/(8*(q+1)**4))
```

```python
# Second version, for validation purposes only.
def MOmega__a_7v2(m1,m2, chi1x,chi1y,chi1z, chi2x,chi2y,chi2z):
    q = m2/m1 # It is assumed that q >= 1, so m2 >= m1.
    global a_7v2
    a_7v2  = +(+(3*(4*q+1)*q**3*chi2x**2*chi2z)/(2*(q+1)**4)
                  -(3*(4*q+1)*q**3*chi2y**2*chi2z)/(8*(q+1)**4)
                  -(3*(4*q+1)*q**3*chi2z**3)      /(8*(q+1)**4)
                  +chi1x*(+(9*(2*q+1)*q**2*chi2x*chi2z)/(4*(q+1)**4)
                          +(9*(1*q+2)*q   *chi2x*chi1z)/(4*(q+1)**4))
                  +chi1y*(+(9*q**2*chi2y*chi1z)/(4*(q+1)**4)
                          +(9*q**2*chi2y*chi2z)/(4*(q+1)**4))
                  +chi1z*(+(9*q**2*(2*q+3)*chi2x**2)/(4*(q+1)**4)
                          -(9*q**2*(1*q+2)*chi2y**2)/(4*(q+1)**4)
                          -(9*q**2        *chi2z**2)/(4*(q+1)**3)
                          -(135*q**5+385*q**4+363*q**3+377*q**2+387*q+168)/(32*(q+1)**6))
                  -(168*q**5+387*q**4+377*q**3+363*q**2+385*q+135)*q*chi2z/(32*(q+1)**6)
                  +chi1x**2*(+3*(q+4)*chi1z/(2*(q+1)**4) + 9*q*(3*q+2)*chi2z/(4*(q+1)**4))
                  +chi1y**2*(-3*(q+4)*chi1z/(8*(q+1)**4) - 9*q*(2*q+1)*chi2z/(4*(q+1)**4))
                  -9*q*chi1z**2*chi2z/(4*(q+1)**3) - 3*(q+4)*chi1z**3/(8*(q+1)**4))
```

Putting it all together, recall that
$$
M\Omega = \frac{1}{r^{3/2}}\left(1 + \sum_{k=2}^7 \frac{a_k}{r^{k/2}}\right),
$$
where $k/2$ is the post-Newtonian order.

```python
# Finally, sum the expressions for a_k to construct p_t as prescribed:
# MOmega = 1/r^(3/2) * (1 + \sum_{k=2}^7 (a_k/r^{k/2}))
def f_MOmega(m1,m2, chi1U,chi2U, r):
    a = ixp.zerorank1(DIM=10)
    MOmega__a_2_thru_a_4(m1,m2, chi1U[0],chi1U[1],chi1U[2], chi2U[0],chi2U[1],chi2U[2])
    a[2] = a_2
    a[3] = a_3
    a[4] = a_4
    MOmega__a_5_thru_a_6(m1,m2, chi1U[0],chi1U[1],chi1U[2], chi2U[0],chi2U[1],chi2U[2])
    a[5] = a_5
    a[6] = a_6
    MOmega__a_7(         m1,m2, chi1U[0],chi1U[1],chi1U[2], chi2U[0],chi2U[1],chi2U[2])
    a[7] = a_7
    global MOmega
    MOmega = 1 # Term prior to the sum in parentheses
    for k in range(8):
        MOmega += a[k]/r**div(k,2)
    MOmega *= 1/r**div(3,2)
```

```python
# Second version, for validation purposes only.
def f_MOmegav2(m1,m2, chi1U,chi2U, r):
    a = ixp.zerorank1(DIM=10)
    MOmega__a_2_thru_a_4v2(m1,m2, chi1U[0],chi1U[1],chi1U[2], chi2U[0],chi2U[1],chi2U[2])
    a[2] = a_2v2
    a[3] = a_3v2
    a[4] = a_4v2
    MOmega__a_5_thru_a_6v2(m1,m2, chi1U[0],chi1U[1],chi1U[2], chi2U[0],chi2U[1],chi2U[2])
    a[5] = a_5v2
    a[6] = a_6v2
    MOmega__a_7v2(         m1,m2, chi1U[0],chi1U[1],chi1U[2], chi2U[0],chi2U[1],chi2U[2])
    a[7] = a_7v2
    global MOmegav2
    MOmegav2 = 1 # Term prior to the sum in parentheses
    for k in range(8):
        MOmegav2 += a[k]/r**div(k,2)
    MOmegav2 *= 1/r**div(3,2)
```

# Part 2: Validation against second transcription and corresponding Python module \[Back to [top](#toc)\]
$$\label{code_validation}$$

As a code validation check, we verify agreement between
* the SymPy expressions transcribed from the cited published work on two separate occasions, and
* the SymPy expressions generated in this notebook, and the corresponding Python module.

```python
from NRPyPN_shortcuts import m1,m2, chi1U,chi2U, q # NRPyPN: Import needed input variables

f_MOmega(m1,m2, chi1U,chi2U, q)

def error(varname):
    print("ERROR: When comparing Python module & notebook, "+varname+" was found not to match.")
    sys.exit(1)

# Validation against second transcription of the expressions:
f_MOmegav2(m1,m2, chi1U,chi2U, q)
if sp.simplify(MOmega - MOmegav2) != 0: error("MOmegav2")

# Validation against corresponding Python module:
import PN_MOmega as MOm
MOm.f_MOmega(m1,m2, chi1U,chi2U, q)

if sp.simplify(MOmega - MOm.MOmega) != 0: error("MOm.MOmega")
print("ALL TESTS PASS")
```

    ALL TESTS PASS

# Part 3: Output this notebook to $\LaTeX$-formatted PDF file \[Back to [top](#toc)\]
$$\label{latex_pdf_output}$$

The following code cell converts this Jupyter notebook into a proper, clickable $\LaTeX$-formatted PDF file. After the cell is successfully run, the generated PDF may be found in the root NRPy+ tutorial directory, with filename
[PN-MOmega.pdf](PN-MOmega.pdf) (Note that clicking on this link may not work; you may need to open the PDF file through another means.)

```python
import os,sys                    # Standard Python modules for multiplatform OS-level functions
nrpy_dir_path = os.path.join("..")
if nrpy_dir_path not in sys.path:
    sys.path.append(nrpy_dir_path)
import cmdline_helper as cmd    # NRPy+: Multi-platform Python command-line interface
cmd.output_Jupyter_notebook_to_LaTeXed_PDF("PN-MOmega",location_of_template_file=os.path.join(".."))
```

    Created PN-MOmega.tex, and compiled LaTeX file to PDF file PN-MOmega.pdf


 INF285/ILI285 Computación Científica

 Tarea N°1, v1.00

## Instrucciones

* La tarea es individual.
* Las consultas sobre las tareas se deben realizar por medio de la plataforma Aula.
* La tarea debe ser realizada en `Jupyter Notebook` (`Python3`).
* Se evaluará la correcta utilización de librerias `NumPy`, `SciPy`, entre otras, así como la correcta implementación de algoritmos de forma vectorizada.
*  **El archivo de entrega debe denominarse ROL-tarea-numero.ipynb**. _De no respetarse este formato existirá un descuento de **50 puntos**_
* La fecha de entrega es el jueves 30 de Abril a las **18:00 hrs**.  Se aceptarán entregas hasta las 19:00 hrs sin descuento en caso de existir algun problema, posteriormente existirá un descuento lineal hasta las 20:00 hrs del mismo día.
* Las tareas que sean entregadas antes del jueves a mediodía recibirán una bonificación de 10 puntos
* Debe citar cualquier código ajeno utilizado (incluso si proviene de los Jupyter Notebooks del curso).

## Introducción

En esta primera tarea de INF/ILI-285, versión 2020-1, estudiaremos la importancia de los primeros temas estudiados en el curso, los cuales son: Representación de Punto Flotante, Pérdida de Significancia, Errores de Cancelación y Búsqueda de Ceros. El desarrollo de cada uno de esos temas se presenta en una serie de preguntas, donde ustedes deben ir decidiendo, pregunta a pregunta, como cada uno de los temas se aplica. En general, los temas no se analizan por separado, sino de manera acoplada. Es muy importante que cada uno de los problemas sea analizado teóricamente primero, para luego poner su conocimiento en acción. Cada problema puede ser desarrollado de diversas formas, sin embargo, es muy importante determinar al final si el camino elegido resuelve la pregunta presentada.

## Problemas

### 1. Simulador (50 ptos)

Dada la familia de polinomios de grado 3 con la forma:

\begin{equation}
f(x)=x^3 - A\,x^2 + A\,x - 1
\end{equation}

Se pide implementar un algoritmo que reciba como parámetros el valor de $A$ (con $|A|>>1$) y la cantidad de bits que tiene en la mantisa un computador ficticio, el manejo del exponente debe considerarse de las misma forma que lo maneja _double precision_.
Esta implementación debe calcular las raíces de $f$ sin perdida de significancia con la cantidad de bits disponibles para la mantisa.
Para obtener las raíces de $f$ usted debe encontrar de forma algebraica sus raíces y luego proponer un algoritmo basado en las _fórmulas_ obtenidas.

Considere que en ese computador ficticio cuenta con las operaciones matemáticas necesarias para obtener las raíces.  Considere el siguiente ejemplo:

```python
# Alg. Base
a = 9819824.624837
b = 148736.523476
c = a+b

# Alg. con Representación de Punto Flotante de 'bits_mant' bits en la mantisa.
am = f_new_rep(9819824.624837,bits_mant) # Aproximar el input en la nueva representación.
bm = f_new_rep(148736.523476,bits_mant) # Aproximar el input en la nueva representación.
cm = f_new_rep(m,exp,am+bm) # Aproximar el output de la suma y cada operación en la nueva representación.
```

### 1.1 Desarrollo
Para encontrar la raíz de la familia de polinomios, primero se procede a factorizar

\begin{equation}
x^3 - Ax^2 + Ax -1 \\
(x^3-1) - Ax(x-1) \\
(x-1)(x^2 +x + 1) - Ax(x-1) \\
(x-1)(x^2 - Ax + x + 1) \\
(x-1)(x^2 + (1-A)x + 1) \\
\end{equation}

Por lo tanto, se puede notar que una raíz del ejercicio siempre será 1, y se quiere estudiar la otra raíz. Se procede a aplicar la fórmula para encontrar 0s en raíces cuadráticas,
por lo que se procede a usar lo escrito en el libro [1]

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

```python
"""
input
x         : (double) valor a evaluar
bits_mant : (int) cantidad de bits de la mantisa
output
eval      : (double) resultado obtenido
"""
def f_new_rep(x: float, bits_mant: int):
    b = bs.pack('>d', x)

    if bits_mant >= 51:
        raise Exception('Cannot be represented')

    sign = b[0]
    exp = b[1:12].uint
    mantissa = b[12:]

    last_value = b[12 + bits_mant]
    # Cuando el bit siguiente es 0, todo el resto sera 0
    if not last_value:
        n_mantissa = b[12:12 + bits_mant].uint

    # En el caso de que el 53 sea 0, pero no todo el resto son 0, se suma 1 a la mantissa
    elif b[12 + bits_mant + 1:].uint > 0:
        n_mantissa = b[12:12 + bits_mant].uint + 1

    # En el caso de que todo el resto sea 0, pero el b52 es 1:
    elif b[12 + bits_mant - 1]:
        n_mantissa = b[12:12 + bits_mant].uint + 1

    # Si no se cumple ninguna de las anteriores, se trunca igual.
    else:
        n_mantissa = b[12:12 + bits_mant].uint

    if n_mantissa > 2**bits_mant - 1:
        n_mantissa = 0
        exp += 1

    n_mantissa = bs.pack(f'uint:{bits_mant}', n_mantissa)
    s_exp = bs.pack(f'uint:11', exp)
    s_mantissa = n_mantissa.bin + '0'*(52 - bits_mant)

    new_float = bs.BitArray(length=64)
    new_float.bin = str(int(sign)) + s_exp.bin + s_mantissa

#     print(new_float.bin[0], new_float.bin[1:12], new_float.bin[12:])
    return new_float.float

def f_find_roots(A, bits_mant):
    # Algoritmo para encontrar raíces de f(x).
    A = f_new_rep(A, bits_mant)
    a = f_new_rep(1., bits_mant)
    b = f_new_rep(1. - A, bits_mant)
    c = f_new_rep(1., bits_mant)
    print(A, b, c)
    det = np.power(b, 2) - 4*a*c
    # Ya que |A| >> 1, eso implica que b^2 >> 4|ab|, por lo
    # que segun el libro guia, esto podria llevar perdida de significancia.
    # Por lo tanto se dan dos soluciones alternativas dependiendo
    # del signo de b
    if b > 0:
        x1 = -(b+np.sqrt(det))/(2*a)
        x2 = -(2*c)/(b + np.sqrt(det))
    else:
        x1 = (-b + np.sqrt(det))/(2*a)
        x2 = (2*c)/(-b + np.sqrt(det))

    x1 = f_new_rep(x1, bits_mant)
    x2 = f_new_rep(x2, bits_mant)
    return [1, x1, x2]
```

### Polinomios de Legendre (50 puntos)
Dada la función compuesta $f$
\begin{equation}
f_{n,m}(x) = L_n(C_m(x)),
\end{equation}
donde $L_n$ es conocido como el polinomio de Legendre de grado $n$ definido de la siguiente forma:
\begin{equation}
L_{n}(x)=\frac{1}{2^{n}} \sum_{k=0}^{n}\left(\begin{array}{l}
n \\
k
\end{array}\right)^{2}(x-1)^{n-k}(x+1)^{k},
\end{equation}
y $C_m$ es el polinomio de Chebyshev
\begin{equation}
C_m(x) = \cos(m \cdot \arccos(x)).
\end{equation}

Utilizando el método de Bisección y Punto fijo se pide obtener la raíz de $f$ más cercana a $0.5$ dado un valor de $m$ y $n$

*Hint: Las raíces de Legendre son conocidas*

### Desarrollo

Para el caso de la Bisección, como las raíces de legendre son conocidas, bastaría con encontrar los 0s de la función $C_m(x) - r$ donde $r$ es una raíz de Legendre.
Luego, se pueden calcular las extremas del polinomio de Chebyshev al ser combinación de funciones sinusoidales, ocupando la fórmula presente en [2], se procede
a calcular las extremas, y luego se busca el 0 entre 2 extremas de chebyshev por cada raíz de Legendre, lo que curiosamente da un total de $n\times m$ raíces.

Para el caso de la iteración de punto fijo, se procede a hacer un desarrollo algebraico con el fin de encontrar una función de punto fijo que se pueda iterar:

\begin{align}
\cos(m \times \arccos(x)) - r &= 0 \\
m \times \arccos(x) &= \arccos(r) \\
\arccos(x) &= \frac{\arccos(r)}{m} \\
x &= \cos\left(\frac{\arccos(r)}{m}\right) \qquad \text{Luego, se debe llevar a la forma } x = g(x) \text{ Por lo tanto, se le suma +x} \\
2x &= \cos\left(\frac{\arccos(r)}{m}\right) + x \\
x &= \frac{1}{2}\left(\cos\left(\frac{\arccos(r)}{m}\right) + x\right)
\end{align}

Y como se puede apreciar, esta función es linealmente creciente por lo tanto no habría problemas al utilizar la iteración de punto fijo

```python
x = np.linspace(-1, 1, 1_000)
m = 3
n = 4

tf = lambda x: legendre(n)(chebyshev_pol(m, x))
fig, ax = plt.subplots(figsize=(10, 10))

for r in roots_legendre(n)[0]:
    dcheb = lambda x: x - m*np.sin(m*np.arccos(x))/np.sqrt(1-x**2)
    f = lambda x: x - (chebyshev_pol(m, x) - r)/dcheb(x)

    ax.plot(x, f(x), color='orange', ls='--')
    ax.plot(x, x)

    print(r)
    arr = fpi(tf, f, r, 1e-5)
#     print(arr)

    a, b = list(zip(*arr))
    ax.plot(a, b, color='black', lw=0.8, ls='--', alpha=1)
    print(b[-1], tf(b[-1]))
#     ax.scatter(a, b, marker='x', color=[cmap(i) for i in np.linspace(0, 1, len(a))])
#     ax.plot(x, chebyshev_pol(m, x), color='blue', ls='-')
    break

# ax.plot(x, tf(x), color='purple')

ax.axvline(0, color='black')
ax.axhline(0, color='black')
ax.set_ylim(-1, 1)
```

A continuación se presenta el código para ambos casos

```python
from scipy.special import legendre, roots_legendre
"""
input
n: (int) grado del polinomio de Legendre
m: (int) grado del polinomio de Chebyshev
tol: (double) tolerancia para la detención del algoritmo
output
root: (double) raiz obtenida
"""

# Codigo reutilizado del desafio 1 (codigo propio)
def bisection(f, a, b, tol):
    c_value = (a+b)/2
    if f(a)*f(b) > 0:
        raise Exception("There's no root")

    while np.abs(f(c_value)) > tol:
        c_value = (a+b)/2

        if f(a)*f(c_value) <= 0: b = c_value
        else: a = c_value

    return c_value

def chebyshev_pol(m, x):
    return np.cos(m*np.arccos(x))

def chebyshev_roots(n):
    arr = np.arange(n+1, dtype=float)
    return np.cos((2*arr + 1)*np.pi/2)

def chebyshev_extremas(n):
    arr = np.arange(n+1, dtype=float)
    calc = np.cos((arr/n)*np.pi)
    return calc

def legendre_nearest_root(n, value):
    roots = roots_legendre(n)[0]
    idx = (np.abs(roots - value)).argmin()
    nearest_value = roots[idx]
    return nearest_value

def f_Biseccion(n, m, tol):
    roots = []
    extremas = chebyshev_roots(m)
    for r in roots_legendre(n)[0]:
        f = lambda x: chebyshev_pol(m, x) - r
        for j in range(len(extremas) - 1):
            a, b = extremas[j:j+2]
            root = bisection(f, a, b, tol)
            roots.append(root)

    # Seleccionar el valor mas cercano a 0.5

    roots = np.array(roots)
    print('roots bis', roots)
    idx = (np.abs(roots - 0.5)).argmin()
    return roots[idx]

# Codigo reutilizado del desafio 1 (codigo propio)
def fpi(f, g, x_0, tol):
    x = []
    c_value = x_0
    i = 0
    while np.abs(f(c_value)) > tol:
        c_value = g(c_value)

        # Caso cuando el valor ya no se actualice, sea
        # muy grande, o simplemente nan o inf, se retorna antes
        # de agregar un nuevo valor.
        if len(x) > 0 and \
        (c_value == x[-1] or np.abs(c_value) > 1.7e100 or
         np.isnan(c_value) or np.isinf(c_value)): return x

        x.append(c_value)
    return c_value

# Lamentablemente, no logre encontrar la raiz mas cercana a 0.5
# Solo logre calcular algunas raices, no pude lograr que convergiera especificamente a la raiz cercana
# a 0.5
def f_FPI(n, m, tol):
    roots = []
    extremas = chebyshev_extremas(m)
    for r in roots_legendre(n)[0]:
#         f = lambda x: chebyshev_pol(m, x) - r
        f = lambda x: legendre(n)(chebyshev_pol(m, x))
        dcheb = lambda x: m*np.sin(m*np.arccos(x))/np.sqrt(1-x**2)
        g = lambda x: x - (chebyshev_pol(m, x) - r)/dcheb(x)

        for j in range(len(extremas) - 1):
            a, b = extremas[j:j+2]
            root = fpi(f, g, (a+b)/2, tol)
            roots.append(root)
    roots = np.array(roots)
#     print(roots)
    idx = (np.abs(roots - 0.5)).argmin()
    return roots

fig, ax = plt.subplots(nrows=5, ncols=5, figsize=(15, 15))
f = lambda x, n, m: legendre(n)(chebyshev_pol(m, x))
x = np.linspace(-1, 1, 1000)
for i in range(0, 5):
    for j in range(0, 5):
        roots = f_FPI(i+1, j+1, 1e-12)
        ax[i, j].plot(x, f(x, i+1, j+1))
        ax[i, j].set_title(f"n = {i+1}, m = {j+1}")
        ax[i, j].scatter(roots, np.zeros_like(roots), marker='x')

fig.tight_layout()

```

###  Determinantes (20 puntos)

Dada una matriz de dimensiones $ n \times n$ de la forma:
\begin{equation}
A
=
\begin{pmatrix}
a_{1,1} & a_{1,2}  & \dots & a_{1,n} \\
a_{2,1} & a_{2,2}  & \dots & a_{2,n} \\
\vdots &  \vdots   & \ddots & \vdots \\
a_{n,1} & a_{n,2}  & \dots & a_{n,n}
\end{pmatrix}
=
\begin{pmatrix}
\mathbf{r}_1 \\
\mathbf{r}_2 \\
\vdots \\
\mathbf{r}_n \\
\end{pmatrix},
\end{equation}
donde $\mathbf{r}_k$ representa la $k$-ésima fila de la matriz $A$.
Considere la siguiente matriz $C_{i,j}(\alpha)$,
\begin{equation}
C_{i,j}(\alpha)
=
\begin{pmatrix}
\mathbf{r}_1 \\
\vdots \\
\mathbf{r}_i\,(1-\alpha)+\mathbf{r}_j\,\alpha \\
\vdots \\
\mathbf{r}_j\,(1-\alpha)+\mathbf{r}_i\,\alpha \\
\vdots \\
\mathbf{r}_n \\
\end{pmatrix},
\end{equation}
de lo cual sabemos que $C_{i,j}(0)=A$ y que $C_{i,j}(1)$ es la matriz $A$ donde se intercambiaron las filas $i$ y $j$, es decir:
\begin{equation}
C_{i,j}(1)
=
\begin{pmatrix}
\mathbf{r}_1 \\
\vdots \\
\mathbf{r}_j \\
\vdots \\
\mathbf{r}_i \\
\vdots \\
\mathbf{r}_n \\
\end{pmatrix}.
\end{equation}
De las relaciones anteriores podemos concluir que el determinante de la matriz $A$, denominado $D=\det(A)$, es igual al determinante de $C_{i,j}(0)$, es decir $\det(C_{i,j}(0))=\det(A)=D$.
Por el otro lado, el determinante de $C_{i,j}(1)$ es $-D$, dado que es el intercambio de las filas $i$ y $j$ de la matriz $A$.
Por lo cual podemos concluir que $-D\leq \det(C_{i,j}(\alpha))\leq D$.

Utilizando el método de Bisección debe encontrar el valor de $\alpha$ con $p$ decimales de precisión que permitan que, dado los índices de las filas $i$, $j$, y $i\neq j$, el determinante de la matriz sea igual a $d$, donde $d\in[-D,D]$.

Para esto se debe implementar una función que reciba la matriz $A$, las filas $i$ y $j$, y $p$; y retorne $\widehat{\alpha}$ tal que $\det(C_{i,j}(\widehat{\alpha}))=d$.

### Desarrollo

Para este caso, simplemente había que transformar la función a $\det(C_{i,j}(x)) - d$ y encontrar sus raíces, para esto se reutilizó la función de bisección diseñada previamente

```python
"""
input
A: (array n x n) matriz
i: (int) índice de la fila "i".
j: (int) índice de la fila "j".
p: (int) cantidad de decimales de precision
d: (double) valor requerido del determinante de $C_{i,j}(\alpha)$, i.e. $\det(C_{i,j}(\widehat{\alpha}))=d$.
output
alpha_hat: (double) alpha_hat tal que det(C_{i,j}(alpha_hat))=d.
"""

def c_ij(x, i, j, a):
    x = x.copy()
    x[i,:], x[j,:] = (x[i,:]*(1 - a) + x[j,:]*a), (x[j,:]*(1 - a) + x[i,:]*a)
    return x

def find_alpha_hat(A, i, j, p, d):
    det = np.linalg.det(A)
    test_function = lambda x: np.linalg.det(c_ij(A, i, j, x)) - d
    alpha_hat = bisection(test_function, -det, det, 10**(-p))
    return alpha_hat

A = np.array([[25,100,100],[20,50,20],[40,40,40.]])
sol = find_alpha_hat(A, 0, 1, 10, 2)
print(np.linalg.det(c_ij(A, 0, 1, sol)))
```

    2.0000000000268408

# Referencias
[1] _Numerical Analysis_, _Sauer T._, page 18.

[2] Chebyshev Polynomials, Wikipedia, https://en.wikipedia.org/wiki/Chebyshev_polynomials#Roots_and_extrema