```python
from sympy import init_session
init_session()
```

    IPython console for SymPy 1.5.1 (Python 3.6.9-64-bit) (ground types: gmpy)

    These commands were executed:
    >>> from __future__ import division
    >>> from sympy import *
    >>> x, y, z, t = symbols('x y z t')
    >>> k, m, n = symbols('k m n', integer=True)
    >>> f, g, h = symbols('f g h', cls=Function)
    >>> init_printing()

    Documentation can be found at https://docs.sympy.org/1.5.1/

```python
init_printing?
```

```python
# this is for rac[3,1]
a, b, d, k, l0 = symbols('a b d k, l0')
```

```python
f = k**2 + 2*a**2*k + a**4 + b**2
g = 1 + d**2*k
h = a**4 + b**2 + 2*a**2*k + d**2*a**4*k + d**2*b**2*k
y = l0*f*g/h
y
```

```python
# implicit
diff(y,a)
```

```python
# explicit:
dfda=diff(f,a)
dhda=diff(h,a)
dyda = l0 * (dfda*g*h - dhda*f*g)/h**2
dyda.factor()
```

```python
diff(f,l0)
```

```python
da=diff(f,a)
da.factor()
```

```python
db=diff(f,b)
db.factor()
```

$\displaystyle - \frac{2 b k^{2} l_{0} \left(d^{2} k + 1\right) \left(2 a^{2} d^{2} + d^{2} k + 1\right)}{\left(a^{4} d^{2} k + a^{4} + 2 a^{2} k + b^{2} d^{2} k + b^{2}\right)^{2}}$

```python
dd=diff(f,d)
dd.factor()
```

$\displaystyle \frac{4 a^{2} d k^{2} l_{0} \left(a^{4} + 2 a^{2} k + b^{2} + k^{2}\right)}{\left(a^{4} d^{2} k + a^{4} + 2 a^{2} k + b^{2} d^{2} k + b^{2}\right)^{2}}$

```python
printing.sstrrepr(f.factor())
```

    'l0*(d**2*k + 1)*(a**4 + 2*a**2*k + b**2 + k**2)/(a**4*d**2*k + a**4 + 2*a**2*k + b**2*d**2*k + b**2)'

```python
printing.sstrrepr(diff(f,l0).factor())
```

    '(d**2*k + 1)*(a**4 + 2*a**2*k + b**2 + k**2)/(a**4*d**2*k + a**4 + 2*a**2*k + b**2*d**2*k + b**2)'

```python
printing.sstrrepr(da.factor())
```

    '-4*a*k**2*l0*(d**2*k + 1)*(a**4*d**2 + a**2*d**2*k + a**2 - b**2*d**2 + k)/(a**4*d**2*k + a**4 + 2*a**2*k + b**2*d**2*k + b**2)**2'

```python
printing.sstrrepr(db.factor())
```

    '-2*b*k**2*l0*(d**2*k + 1)*(2*a**2*d**2 + d**2*k + 1)/(a**4*d**2*k + a**4 + 2*a**2*k + b**2*d**2*k + b**2)**2'

```python
printing.sstrrepr(dd.factor())
```

    '4*a**2*d*k**2*l0*(a**4 + 2*a**2*k + b**2 + k**2)/(a**4*d**2*k + a**4 + 2*a**2*k + b**2*d**2*k + b**2)**2'

```python

```


```python
from astropy.cosmology import WMAP5
from astropy.cosmology import FlatLambdaCDM
import astropy.units as u
import numpy as np
import matplotlib.pyplot as plt
import sympy as smp
WMAP5.H(0)
```

$70.2 \; \mathrm{\frac{km}{Mpc\,s}}$

```python
WMAP_5 = dict()
```

```python
WMAP_5['ombh2'] = 0.02273   ## Omega_b * h**2
WMAP_5['omch2'] = 0.1099  ## Omega_c * h**2
WMAP_5['ln1010As'] = 3.0448  ## ln(10**10 * As), scalar amplitude
WMAP_5['ns'] = 0.96305       ## spectral index
WMAP_5['ommh2'] = 0.14314    ##   Omega_m * h**2   , total matter
WMAP_5['H0'] = 70.2   ##   H0 = 100h
WMAP_5['sigma8'] = 0.796    ## amplitude of density fluctuations
WMAP_5['tau'] = 0.087      ## Optical depth
WMAP_5['age_Gyr'] = 13.69   ## Age of the Universe
```

```python
WMAP_5['h'] = WMAP_5['H0']/100
WMAP_5['Om'] = WMAP_5['ommh2']/WMAP_5['h']**2
WMAP_5['Ob'] = WMAP_5['ombh2']/WMAP_5['h']**2
WMAP_5['Oc'] = WMAP_5['omch2']/WMAP_5['h']**2
WMAP_5['As'] = np.exp(WMAP_5['ln1010As'])/np.power(10,10)    ## As, scalar amplitude
```

```python
WMAP_5['Om']
```

    0.29046030470531886

```python
WMAP_5['h'] = 0.719 # km/Mpc s
```

```python
WMAP_5['Or'] = 0.0000930479
```

```python
WMAP_5['Ol'] = 1-np.array([WMAP_5[oo] for oo in ['Oc','Ob','Om']]).sum()  ## Ol = Omega_Lambda
WMAP_5['Ol']
```

    0.44040632787071554

```python
cosmo = FlatLambdaCDM(H0=70.2 * u.km / u.s / u.Mpc, Om0=0.3)
```

```python
def a_of_z(z):
    a=1/(1+z)
    return a
```

```python
def Omega_L(Omega_c, Omega_b, Omega_r):
    """
    Function for Omega_Lambda, dark energy.

    For a flat Universe:
        Omega_Lambda = 1-Omega_c-Omega_b-Omega_r

    """
    oL = 1 - Omega_c - Omega_b - Omega_r
    return oL
```

```python
def cosmological_parameters(cosmo_pars=dict()):
    H0 = cosmo_pars.get('H0', WMAP_5['H0'])  # WMAP5 cosmological parameters as default
    Oc = cosmo_pars.get('Oc', WMAP_5['Oc'])
    Ob = cosmo_pars.get('Ob', WMAP_5['Ob'])
    Or = cosmo_pars.get('Or', WMAP_5['Or'])
    Om = Ob+Oc
    OL = Omega_L(Oc, Ob, Or)
    return H0, Oc, Ob, Or, Om, OL
```

```python
cosmological_parameters()
```

    (70.2,
     0.22300955349388393,
     0.04612381393008173,
     9.30479e-05,
     0.26913336742396565,
     0.7307735846760344)

```python
def Hubble(z, cosmo_pars=dict()):
    H0, Oc, Ob, Or, Om, OL = cosmological_parameters(cosmo_pars)
    H = H0 * np.sqrt(Om*(1+z)**3 + Or*(1+z)**4 + OL)
    return H
```

```python
z_arr = np.linspace(0.,10, 100)
fig, ax = plt.subplots(1, 1, sharey='row', sharex='col', figsize=(10,8))  #all plots in the same row, share the y-axis.

# once you specify an axis, it is in this instance where plots are performed
ax.semilogx(z_arr, Hubble(z_arr), '-', label='WMAP5', color='orange', lw=3)
ax.legend(fontsize=26)
ax.set_xlabel('redshift $z$', fontsize=26)
ax.set_ylabel(r'$H(z)$ in km/s/Mpc', fontsize=26);
```

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

```python
h = 0.719
```

```python
[P0, c500, gamma, alpha, beta] = [(8.130*(h/0.7)**(-3/2)),1.156,0.3292,1.0620,5.4807]
```

```python
Y500,n,r500,r,x,z = smp.symbols('Y500 n r500 r x z')
```

```python
v = (c500)**(alpha/beta-gamma)
```

```python
v
```

    0.9805588759001774

```python
w = 1 + (1/x**alpha)       #Numerator
```

```python
w.subs(alpha,1.0620)
```

$\displaystyle x^{-1.062} + 1$

```python
y =  c500 + (1/x**alpha)     #Demominator
```

```python
y.subs(alpha,1.0620)
```

$\displaystyle x^{-1.062} + 1.156$

```python
v = w/y
v
```

$\displaystyle \frac{x^{-1.062} + 1}{x^{-1.062} + 1.156}$

```python
x = (-0.865051903114187)**(1/alpha)
```

```python
x
```

    (-0.8577720337616777+0.15910988366956264j)

```python
x = ((0.8577720337616777)**2 +(0.15910988366956264)**2 )**(1/2)
x
```

    0.8724040445716574

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

```python
def E(z):
    return ((Cm/L500) * ((M500*h/0.7)/(3*1e14*M0))**delta)**(-3/7)
```

```python
# Let's get the value of E
L500,Cm,delta =smp.symbols("L500 Cm delta")
L500 = 1e44 # 'erg/s'
delta = 1.61  # alpha(m)
```

```python
Cm = (smp.exp(0.295) * 1e44) / (h/0.7)**2  #erg/s
Cm
```

$\displaystyle 1.27307846386144 \cdot 10^{44}$

```python
#rho = WMAP5.critical_density(0)
rho = 9.2565426 * 1e-30   # critical density at z = 0 in g/cm^3
```

```python
M0 = (h * (4/3) * smp.pi * rho * 500 * r500 **3)/(3*1e14) # combining two reations of M500
```

```python
M500 = (4/3) * smp.pi * rho * 500 * r500**3
M500
```

$\displaystyle 6.1710284 \cdot 10^{-27} \pi r_{500}^{3}$

```python
P500 = (1.65 * 1e-3) * (E(z)**smp.Rational(8,3) * (h/0.7)**2 * (M500 * (h/0.7)/((3 * 1e14) * M0))**smp.Rational(2,3))
P500
```

$\displaystyle 0.000869279684917396$

```python
P500.subs([(h,0.719)])
```

$\displaystyle 0.000869279684917396$

```python
r_s = r500/c500
x = r/r500 * 1.156
x = 0.8724040445716578 # by calculations from the paper2
```

```python
def P(r):
    return (P0 * P500) / ((x**gamma) * (1+ x**alpha)**((beta-gamma)/alpha))
P(r)
```

$\displaystyle 0.000345354134103889$

```python
P(r).subs([(alpha,1.0620), (beta,5.4807), (gamma,0.3292),(P0,8.463243203779394),(h,0.719)])
```

$\displaystyle 0.000345354134103889$

```python
from sympy import integrate
```

```python
f = P(r) * (4 * smp.pi *r **2)
f
```

$\displaystyle 0.00138141653641556 \pi r^{2}$

```python
a = smp.integrate(f,(r,0,n * r500))
a
```

$\displaystyle 0.000460472178805185 \pi n^{3} r_{500}^{3}$

```python
b = smp.integrate(f,(r,0,r500))
b
```

$\displaystyle 0.000460472178805185 \pi r_{500}^{3}$

```python
def Y_nr500(a):
    return Y500 * (a/b)
Y_nr500(a)
```

$\displaystyle 1.0 Y_{500} n^{3}$

```python
a1 = a.subs([(alpha,1.0620), (beta,5.4807), (gamma,0.3292),(P0,8.463),(n,1),(h,0.719)])
a1
```

$\displaystyle 0.000460472178805185 \pi r_{500}^{3}$

```python
(Y_nr500(a = a1)/Y500).subs([(alpha,1.0620), (beta,5.4807), (gamma,0.3292),(P0,8.463),(n,1),(h,0.719)]) #n=1
```

$\displaystyle 1.0$

```python
a2 = a.subs([(alpha,1.0620), (beta,5.4807), (gamma,0.3292),(P0,8.463),(n,2),(h,0.719)])
a2
```

$\displaystyle 0.00368377743044148 \pi r_{500}^{3}$

```python
(Y_nr500(a = a2)/Y500).subs([(alpha,1.0620), (beta,5.4807), (gamma,0.3292),(P0,8.463),(n,2),(h,0.719)]) #n=2
```

$\displaystyle 8.0$

```python
a3 = a.subs([(alpha,1.0620), (beta,5.4807), (gamma,0.3292),(P0,8.463),(n,3),(h,0.719)])
a3
```

$\displaystyle 0.01243274882774 \pi r_{500}^{3}$

```python
(Y_nr500(a = a3)/Y500).subs([(alpha,1.0620), (beta,5.4807), (gamma,0.3292),(P0,8.463),(n,3),(h,0.719)]) #n=3
```

$\displaystyle 27.0$

```python

```


# Implementing Shor's algorithm in Perceval

This notebook presents a simulation in Perceval of a 4-qubit 12-modes optical circuit performing Shor's algorithm, based on Alberto Politi, Jonathan C.F. Matthews, and Jeremy L. O'brien. "Shor’s quantum factoring algorithm on a photonic chip." Science 325.5945 (2009): 1221-1221. https://www.science.org/doi/10.1126/science.1173731

## Shor's algorithm

The purpose of Shor's algorithm is to find nontrivial factors of a given number $N$ in polynomial time, yielding an near-exponential speedup compared to state of the art classical algortihms.

The main routine of Shor's algorithm consists in finding the order $r$ of a number $a \in \mathbb{Z}_N$, i.e. the smallest integer $r$ such that $a^r = 1 \pmod N$.

If the order of a randoly chosen $a$ which is coprime with $N$ is even, then $(a^{r/2} - 1)(a^{r/2} + 1) = k N$. If none of these factors are multiples of $N$, then $\gcd(N, a^{r/2} - 1)$ and $\gcd(N, a^{r/2} + 1)$ are nontrivial factors of $N$.

## Preliminaries

```python
import sys
print("Python version:", sys.version)
```

    Python version: 3.9.7 | packaged by conda-forge | (default, Sep 29 2021, 19:20:46)
    [GCC 9.4.0]

```python
from IPython import display
from collections import Counter
from tabulate import tabulate
from tqdm.auto import tqdm

import sympy as sp
import numpy as np
from scipy.optimize import minimize

import perceval as pcvl
import quandelibc as qc
import perceval.lib.phys as phys
import perceval.lib.symb as symb

print("Perceval version:", pcvl.__version__)
```

    Perceval version: 0.4.1

### Path encoding functions

The following functions allow for conversion between the qubit and Fock state representations.

```python
def toFockState(qubitState):
    # path encoding
    pe = {0:[1,0],  1:[0,1]}
    return [0] + pe[qubitState[0]] + pe[qubitState[2]] + [0, 0] + pe[qubitState[1]] + pe[qubitState[3]] + [0]
```

```python
def toQubitState(fockState):
    # qubit modes
    x1 = [1, 2]
    f1 = [3, 4]
    x2 = [7, 8]
    f2 = [9, 10]
    # auxiliary modes
    am1 = [0, 5]
    am2 = [6, 11]

    # auxiliary modes
    for i in am1 + am2:
        if fockState[i]!=0:
            return None
    L=[]
    # qubit modes
    for q in [x1, x2, f1, f2]:
        if fockState[q[0]]+fockState[q[1]] != 1:
            return None
        else:
            L.append(fockState[q[1]])
    return L
```

```python
def strState(state):
    return str(pcvl.BasicState(state))
```

## The circuit

### Quantum circuit

The quantum circuit has been optimized after choosing parameters $N = 15$ and $a = 2$, and aims to calculate $r=4$.
It features 5 qubits labelled $x_0, x_1, x_2$ and $f_1, f_2$. Qubits $x_i$ and $f_1$ are initially in state $|0\rangle$, and $f_2$ in state $|1\rangle$.
In the non-optimised Shor algorithm, qubits $x_i$ encode a binary number representing a pre-image of the Modular Exponentiation Function (MEF) $x \mapsto a^x \pmod N$, while qubits $f_i$ hold the image obtained after applying the MEF to qubits $x_i$. Applying the MEF when qubits $x_i$ hold a superposition of different pre-images (obtained with H gates on qubits $x_i$) allows to efficiently compute the order $r$ of parameter $a$ modulo $N$.

The circuit consists of $\mathsf{H}$ gates being first applied to each $x_i$ qubit, followed by $\mathsf{CNOT}$ gates applied on $x_1, f_1$ and $x_2, f_2$ pairs, where $x_i$ are control qubits; finally the inverse $\mathsf{QFT}$ algorithm is applied on qubits $x_i$.

$\mathsf{CNOT}$ gates on $x_i, f_i$ pairs ($x_i$ being the control) are implemented using $\mathsf{H}$ and $\mathsf{CZ}$ gates: the $\mathsf{CZ}$ gate is sandwiched between two applications of $\mathsf{H}$ on $f_i$.

The input state of the circuit after optimisation is $|0\rangle_{x_0}|0\rangle_{x_1}|0\rangle_{x_2}|0\rangle_{f_1}|1\rangle_{f_2}$.

The expected output state is then $\frac{1}{2} |0\rangle_{x_0} \otimes \left ( |0\rangle_{x_1}|0\rangle_{f_1} + |1\rangle_{x_1}|1\rangle_{f_1} \right ) \otimes \left ( |0\rangle_{x_2}|1\rangle_{f_2} + |1\rangle_{x_2}|0\rangle_{f_2} \right )$.

### Photonic circuit

The optical circuit from the result by Politi et al features twelve modes (ordered from 0 to 11 from top to bottom).

During the execution, qubit $x_0$ remains unentangled from the other qubits. It can therefore be removed from the optical implementation.

The qubits $x_1, x_2, f_1, f_2$ are path encoded as modes $(1, 2)$, $(3, 4)$, $(7, 8)$, $(9, 10)$ respectively. The four remaining modes are used as auxiliary modes to implement the $\mathsf{CZ}$ gates.

With path encoding each $\mathsf{H}$ gate in the quantum circuit is implemented with a beam splitter with reflectivity $R=1/2$ between the two pathes corresponding to the qubit. In our implementation in Perceval, phase shifters are added to properly tune the phase between each path.

$\mathsf{CZ}$ gates are implemented with three beam splitters with reflectivity $R=2/3$ acting on six modes: one inner BS creates interference between the two qubits, and two outer BS balance detection probability using auxiliary modes.
This optical implementation succesfully yields the output state produced by a $\mathsf{CZ}$ gate with probability 1/9; otherwise it creates a dummy state, which can be removed by post-selection.

In the case $r=4$ the QFT can be performed classically and doesn't need to be implemented in the photonic circuit.

## In perceval

### Implementing the circuit

```python
circ = pcvl.Circuit(12)

# qubit modes
# for qubit states 0, 1
x1 = [1, 2]
f1 = [3, 4]
x2 = [7, 8]
f2 = [9, 10]
# auxiliary modes
am1 = [0, 5]
am2 = [6, 11]

# H gates
for q in [x1, f1, x2, f2]:
    circ.add(q, symb.BS(R=1/2, phi=sp.pi/2))
    circ.add(q[1], symb.PS(phi=sp.pi))

# CZ gates
for x, f, am in [(x1, f1, am1), (x2, f2, am2)]:
    circ.add((am[0], x[0]), symb.BS(R=2/3)) # T = 1/3
    circ.add((x[1],  f[0]), symb.BS(R=2/3))
    circ.add((f[1], am[1]), symb.BS(R=2/3))

# H gates
for q in [f1, f2]:
    circ.add(q, symb.BS(R=1/2, phi=sp.pi/2))
    circ.add(q[1], symb.PS(phi=sp.pi))
```

```python
print("Optical circuit for Shor's algorithm")
pcvl.pdisplay(circ)
```

    Optical circuit for Shor's algorithm

<svg width='462.000000' height='650.000000' viewBox='-6.000000 0.000000 456.000000 650.000000'><path d="M 10 25 L 25 25" fill="none" stroke="black" stroke-width="2.000000" stroke-linejoin="miter"/>
<text x="0.000000" y="25.000000" font-size="6.000000" text-anchor="start">0</text>
<path d="M 10 75 L 25 75" fill="none" stroke="black" stroke-width="2.000000" stroke-linejoin="miter"/>
<text x="0.000000" y="75.000000" font-size="6.000000" text-anchor="start">1</text>
<path d="M 10 125 L 25 125" fill="none" stroke="black" stroke-width="2.000000" stroke-linejoin="miter"/>
<text x="0.000000" y="125.000000" font-size="6.000000" text-anchor="start">2</text>
<path d="M 10 175 L 25 175" fill="none" stroke="black" stroke-width="2.000000" stroke-linejoin="miter"/>
<text x="0.000000" y="175.000000" font-size="6.000000" text-anchor="start">3</text>
<path d="M 10 225 L 25 225" fill="none" stroke="black" stroke-width="2.000000" stroke-linejoin="miter"/>
<text x="0.000000" y="225.000000" font-size="6.000000" text-anchor="start">4</text>
<path d="M 10 275 L 25 275" fill="none" stroke="black" stroke-width="2.000000" stroke-linejoin="miter"/>
<text x="0.000000" y="275.000000" font-size="6.000000" text-anchor="start">5</text>
<path d="M 10 325 L 25 325" fill="none" stroke="black" stroke-width="2.000000" stroke-linejoin="miter"/>
<text x="0.000000" y="325.000000" font-size="6.000000" text-anchor="start">6</text>
<path d="M 10 375 L 25 375" fill="none" stroke="black" stroke-width="2.000000" stroke-linejoin="miter"/>
<text x="0.000000" y="375.000000" font-size="6.000000" text-anchor="start">7</text>
<path d="M 10 425 L 25 425" fill="none" stroke="black" stroke-width="2.000000" stroke-linejoin="miter"/>
<text x="0.000000" y="425.000000" font-size="6.000000" text-anchor="start">8</text>
<path d="M 10 475 L 25 475" fill="none" stroke="black" stroke-width="2.000000" stroke-linejoin="miter"/>
<text x="0.000000" y="475.000000" font-size="6.000000" text-anchor="start">9</text>
<path d="M 10 525 L 25 525" fill="none" stroke="black" stroke-width="2.000000" stroke-linejoin="miter"/>
<text x="0.000000" y="525.000000" font-size="6.000000" text-anchor="start">10</text>
<path d="M 10 575 L 25 575" fill="none" stroke="black" stroke-width="2.000000" stroke-linejoin="miter"/>
<text x="0.000000" y="575.000000" font-size="6.000000" text-anchor="start">11</text>
<path d="M 25 75 C 42 75 45 81 51 86 C 56 93 57 98.5 75 98.5 C 93 98.5 94 93 100 86 C 105 81 108 75 125 75" fill="none" stroke="black" stroke-width="2.000000" stroke-linejoin="miter"/>
<path d="M 25 125 C 42 125 45 119 51 114 C 56 107 57 101.5 75 101.5 C 93 101.5 94 107 100 114 C 105 119 108 125 125 125" fill="none" stroke="black" stroke-width="2.000000" stroke-linejoin="miter"/>
<text x="75.000000" y="88.000000" font-size="7.000000" text-anchor="middle">phi=pi/2</text>
<polyline points="125 125 175 125" fill="transparent"stroke="black" stroke-width="2.000000" stroke-linejoin="miter" />
<polyline points="130 117 170 117 170 133 130 133 130 117" fill="white" stroke="black" stroke-width="2.000000" stroke_linejoin="miter" />
<text x="150.000000" y="128.000000" font-size="10.000000" text-anchor="middle">pi</text>
<path d="M 25 175 C 42 175 45 181 51 186 C 56 193 57 198.5 75 198.5 C 93 198.5 94 193 100 186 C 105 181 108 175 125 175" fill="none" stroke="black" stroke-width="2.000000" stroke-linejoin="miter"/>
<path d="M 25 225 C 42 225 45 219 51 214 C 56 207 57 201.5 75 201.5 C 93 201.5 94 207 100 214 C 105 219 108 225 125 225" fill="none" stroke="black" stroke-width="2.000000" stroke-linejoin="miter"/>
<text x="75.000000" y="188.000000" font-size="7.000000" text-anchor="middle">phi=pi/2</text>
<polyline points="125 225 175 225" fill="transparent"stroke="black" stroke-width="2.000000" stroke-linejoin="miter" />
<polyline points="130 217 170 217 170 233 130 233 130 217" fill="white" stroke="black" stroke-width="2.000000" stroke_linejoin="miter" />
<text x="150.000000" y="228.000000" font-size="10.000000" text-anchor="middle">pi</text>
<path d="M 25 375 C 42 375 45 381 51 386 C 56 393 57 398.5 75 398.5 C 93 398.5 94 393 100 386 C 105 381 108 375 125 375" fill="none" stroke="black" stroke-width="2.000000" stroke-linejoin="miter"/>
<path d="M 25 425 C 42 425 45 419 51 414 C 56 407 57 401.5 75 401.5 C 93 401.5 94 407 100 414 C 105 419 108 425 125 425" fill="none" stroke="black" stroke-width="2.000000" stroke-linejoin="miter"/>
<text x="75.000000" y="388.000000" font-size="7.000000" text-anchor="middle">phi=pi/2</text>
<polyline points="125 425 175 425" fill="transparent"stroke="black" stroke-width="2.000000" stroke-linejoin="miter" />
<polyline points="130 417 170 417 170 433 130 433 130 417" fill="white" stroke="black" stroke-width="2.000000" stroke_linejoin="miter" />
<text x="150.000000" y="428.000000" font-size="10.000000" text-anchor="middle">pi</text>
<path d="M 25 475 C 42 475 45 481 51 486 C 56 493 57 498.5 75 498.5 C 93 498.5 94 493 100 486 C 105 481 108 475 125 475" fill="none" stroke="black" stroke-width="2.000000" stroke-linejoin="miter"/>
<path d="M 25 525 C 42 525 45 519 51 514 C 56 507 57 501.5 75 501.5 C 93 501.5 94 507 100 514 C 105 519 108 525 125 525" fill="none" stroke="black" stroke-width="2.000000" stroke-linejoin="miter"/>
<text x="75.000000" y="488.000000" font-size="7.000000" text-anchor="middle">phi=pi/2</text>
<polyline points="125 525 175 525" fill="transparent"stroke="black" stroke-width="2.000000" stroke-linejoin="miter" />
<polyline points="130 517 170 517 170 533 130 533 130 517" fill="white" stroke="black" stroke-width="2.000000" stroke_linejoin="miter" />
<text x="150.000000" y="528.000000" font-size="10.000000" text-anchor="middle">pi</text>
<polyline points="25 25 125 25" fill="transparent"stroke="black" stroke-width="2.000000" stroke-linejoin="miter" />
<path d="M 125 25 C 142 25 145 31 151 36 C 156 43 157 48.5 175 48.5 C 193 48.5 194 43 200 36 C 205 31 208 25 225 25" fill="none" stroke="black" stroke-width="2.000000" stroke-linejoin="miter"/>
<path d="M 125 75 C 142 75 145 69 151 64 C 156 57 157 51.5 175 51.5 C 193 51.5 194 57 200 64 C 205 69 208 75 225 75" fill="none" stroke="black" stroke-width="2.000000" stroke-linejoin="miter"/>
<text x="175.000000" y="38.000000" font-size="7.000000" text-anchor="middle">R=2/3</text>
<polyline points="125 175 175 175" fill="transparent"stroke="black" stroke-width="2.000000" stroke-linejoin="miter" />
<path d="M 175 125 C 192 125 195 131 201 136 C 206 143 207 148.5 225 148.5 C 243 148.5 244 143 250 136 C 255 131 258 125 275 125" fill="none" stroke="black" stroke-width="2.000000" stroke-linejoin="miter"/>
<path d="M 175 175 C 192 175 195 169 201 164 C 206 157 207 151.5 225 151.5 C 243 151.5 244 157 250 164 C 255 169 258 175 275 175" fill="none" stroke="black" stroke-width="2.000000" stroke-linejoin="miter"/>
<text x="225.000000" y="138.000000" font-size="7.000000" text-anchor="middle">R=2/3</text>
<polyline points="25 275 175 275" fill="transparent"stroke="black" stroke-width="2.000000" stroke-linejoin="miter" />
<path d="M 175 225 C 192 225 195 231 201 236 C 206 243 207 248.5 225 248.5 C 243 248.5 244 243 250 236 C 255 231 258 225 275 225" fill="none" stroke="black" stroke-width="2.000000" stroke-linejoin="miter"/>
<path d="M 175 275 C 192 275 195 269 201 264 C 206 257 207 251.5 225 251.5 C 243 251.5 244 257 250 264 C 255 269 258 275 275 275" fill="none" stroke="black" stroke-width="2.000000" stroke-linejoin="miter"/>
<text x="225.000000" y="238.000000" font-size="7.000000" text-anchor="middle">R=2/3</text>
<polyline points="25 325 125 325" fill="transparent"stroke="black" stroke-width="2.000000" stroke-linejoin="miter" />
<path d="M 125 325 C 142 325 145 331 151 336 C 156 343 157 348.5 175 348.5 C 193 348.5 194 343 200 336 C 205 331 208 325 225 325" fill="none" stroke="black" stroke-width="2.000000" stroke-linejoin="miter"/>
<path d="M 125 375 C 142 375 145 369 151 364 C 156 357 157 351.5 175 351.5 C 193 351.5 194 357 200 364 C 205 369 208 375 225 375" fill="none" stroke="black" stroke-width="2.000000" stroke-linejoin="miter"/>
<text x="175.000000" y="338.000000" font-size="7.000000" text-anchor="middle">R=2/3</text>
<polyline points="125 475 175 475" fill="transparent"stroke="black" stroke-width="2.000000" stroke-linejoin="miter" />
<path d="M 175 425 C 192 425 195 431 201 436 C 206 443 207 448.5 225 448.5 C 243 448.5 244 443 250 436 C 255 431 258 425 275 425" fill="none" stroke="black" stroke-width="2.000000" stroke-linejoin="miter"/>
<path d="M 175 475 C 192 475 195 469 201 464 C 206 457 207 451.5 225 451.5 C 243 451.5 244 457 250 464 C 255 469 258 475 275 475" fill="none" stroke="black" stroke-width="2.000000" stroke-linejoin="miter"/>
<text x="225.000000" y="438.000000" font-size="7.000000" text-anchor="middle">R=2/3</text>
<polyline points="25 575 175 575" fill="transparent"stroke="black" stroke-width="2.000000" stroke-linejoin="miter" />
<path d="M 175 525 C 192 525 195 531 201 536 C 206 543 207 548.5 225 548.5 C 243 548.5 244 543 250 536 C 255 531 258 525 275 525" fill="none" stroke="black" stroke-width="2.000000" stroke-linejoin="miter"/>
<path d="M 175 575 C 192 575 195 569 201 564 C 206 557 207 551.5 225 551.5 C 243 551.5 244 557 250 564 C 255 569 258 575 275 575" fill="none" stroke="black" stroke-width="2.000000" stroke-linejoin="miter"/>
<text x="225.000000" y="538.000000" font-size="7.000000" text-anchor="middle">R=2/3</text>
<path d="M 275 175 C 292 175 295 181 301 186 C 306 193 307 198.5 325 198.5 C 343 198.5 344 193 350 186 C 355 181 358 175 375 175" fill="none" stroke="black" stroke-width="2.000000" stroke-linejoin="miter"/>
<path d="M 275 225 C 292 225 295 219 301 214 C 306 207 307 201.5 325 201.5 C 343 201.5 344 207 350 214 C 355 219 358 225 375 225" fill="none" stroke="black" stroke-width="2.000000" stroke-linejoin="miter"/>
<text x="325.000000" y="188.000000" font-size="7.000000" text-anchor="middle">phi=pi/2</text>
<polyline points="375 225 425 225" fill="transparent"stroke="black" stroke-width="2.000000" stroke-linejoin="miter" />
<polyline points="380 217 420 217 420 233 380 233 380 217" fill="white" stroke="black" stroke-width="2.000000" stroke_linejoin="miter" />
<text x="400.000000" y="228.000000" font-size="10.000000" text-anchor="middle">pi</text>
<path d="M 275 475 C 292 475 295 481 301 486 C 306 493 307 498.5 325 498.5 C 343 498.5 344 493 350 486 C 355 481 358 475 375 475" fill="none" stroke="black" stroke-width="2.000000" stroke-linejoin="miter"/>
<path d="M 275 525 C 292 525 295 519 301 514 C 306 507 307 501.5 325 501.5 C 343 501.5 344 507 350 514 C 355 519 358 525 375 525" fill="none" stroke="black" stroke-width="2.000000" stroke-linejoin="miter"/>
<text x="325.000000" y="488.000000" font-size="7.000000" text-anchor="middle">phi=pi/2</text>
<polyline points="375 525 425 525" fill="transparent"stroke="black" stroke-width="2.000000" stroke-linejoin="miter" />
<polyline points="380 517 420 517 420 533 380 533 380 517" fill="white" stroke="black" stroke-width="2.000000" stroke_linejoin="miter" />
<text x="400.000000" y="528.000000" font-size="10.000000" text-anchor="middle">pi</text>
<polyline points="225 25 425 25" fill="transparent"stroke="black" stroke-width="2.000000" stroke-linejoin="miter" />
<polyline points="225 75 425 75" fill="transparent"stroke="black" stroke-width="2.000000" stroke-linejoin="miter" />
<polyline points="275 125 425 125" fill="transparent"stroke="black" stroke-width="2.000000" stroke-linejoin="miter" />
<polyline points="375 175 425 175" fill="transparent"stroke="black" stroke-width="2.000000" stroke-linejoin="miter" />
<polyline points="275 275 425 275" fill="transparent"stroke="black" stroke-width="2.000000" stroke-linejoin="miter" />
<polyline points="225 325 425 325" fill="transparent"stroke="black" stroke-width="2.000000" stroke-linejoin="miter" />
<polyline points="225 375 425 375" fill="transparent"stroke="black" stroke-width="2.000000" stroke-linejoin="miter" />
<polyline points="275 425 425 425" fill="transparent"stroke="black" stroke-width="2.000000" stroke-linejoin="miter" />
<polyline points="375 475 425 475" fill="transparent"stroke="black" stroke-width="2.000000" stroke-linejoin="miter" />
<polyline points="275 575 425 575" fill="transparent"stroke="black" stroke-width="2.000000" stroke-linejoin="miter" />
<path d="M 425 25 L 440 25" fill="none" stroke="black" stroke-width="2.000000" stroke-linejoin="miter"/>
<text x="450.000000" y="25.000000" font-size="6.000000" text-anchor="end">0</text>
<path d="M 425 75 L 440 75" fill="none" stroke="black" stroke-width="2.000000" stroke-linejoin="miter"/>
<text x="450.000000" y="75.000000" font-size="6.000000" text-anchor="end">1</text>
<path d="M 425 125 L 440 125" fill="none" stroke="black" stroke-width="2.000000" stroke-linejoin="miter"/>
<text x="450.000000" y="125.000000" font-size="6.000000" text-anchor="end">2</text>
<path d="M 425 175 L 440 175" fill="none" stroke="black" stroke-width="2.000000" stroke-linejoin="miter"/>
<text x="450.000000" y="175.000000" font-size="6.000000" text-anchor="end">3</text>
<path d="M 425 225 L 440 225" fill="none" stroke="black" stroke-width="2.000000" stroke-linejoin="miter"/>
<text x="450.000000" y="225.000000" font-size="6.000000" text-anchor="end">4</text>
<path d="M 425 275 L 440 275" fill="none" stroke="black" stroke-width="2.000000" stroke-linejoin="miter"/>
<text x="450.000000" y="275.000000" font-size="6.000000" text-anchor="end">5</text>
<path d="M 425 325 L 440 325" fill="none" stroke="black" stroke-width="2.000000" stroke-linejoin="miter"/>
<text x="450.000000" y="325.000000" font-size="6.000000" text-anchor="end">6</text>
<path d="M 425 375 L 440 375" fill="none" stroke="black" stroke-width="2.000000" stroke-linejoin="miter"/>
<text x="450.000000" y="375.000000" font-size="6.000000" text-anchor="end">7</text>
<path d="M 425 425 L 440 425" fill="none" stroke="black" stroke-width="2.000000" stroke-linejoin="miter"/>
<text x="450.000000" y="425.000000" font-size="6.000000" text-anchor="end">8</text>
<path d="M 425 475 L 440 475" fill="none" stroke="black" stroke-width="2.000000" stroke-linejoin="miter"/>
<text x="450.000000" y="475.000000" font-size="6.000000" text-anchor="end">9</text>
<path d="M 425 525 L 440 525" fill="none" stroke="black" stroke-width="2.000000" stroke-linejoin="miter"/>
<text x="450.000000" y="525.000000" font-size="6.000000" text-anchor="end">10</text>
<path d="M 425 575 L 440 575" fill="none" stroke="black" stroke-width="2.000000" stroke-linejoin="miter"/>
<text x="450.000000" y="575.000000" font-size="6.000000" text-anchor="end">11</text></svg>

```python
print("The associated matrix")
pcvl.pdisplay(circ.U)
```

    The associated matrix

$\left[\begin{array}{cccccccccccc}\frac{\sqrt{3}}{3} & \frac{\sqrt{3} i}{3} & \frac{\sqrt{3} i}{3} & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\frac{\sqrt{6} i}{3} & \frac{\sqrt{6}}{6} & \frac{\sqrt{6}}{6} & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\0 & \frac{\sqrt{6}}{6} & - \frac{\sqrt{6}}{6} & \frac{\sqrt{3} i}{3} & \frac{\sqrt{3} i}{3} & 0 & 0 & 0 & 0 & 0 & 0 & 0\\0 & \frac{\sqrt{6} i}{6} & - \frac{\sqrt{6} i}{6} & \frac{\sqrt{3}}{3} & 0 & \frac{\sqrt{3} i}{3} & 0 & 0 & 0 & 0 & 0 & 0\\0 & \frac{\sqrt{6} i}{6} & - \frac{\sqrt{6} i}{6} & 0 & \frac{\sqrt{3}}{3} & - \frac{\sqrt{3} i}{3} & 0 & 0 & 0 & 0 & 0 & 0\\0 & 0 & 0 & \frac{\sqrt{3} i}{3} & - \frac{\sqrt{3} i}{3} & \frac{\sqrt{3}}{3} & 0 & 0 & 0 & 0 & 0 & 0\\0 & 0 & 0 & 0 & 0 & 0 & \frac{\sqrt{3}}{3} & \frac{\sqrt{3} i}{3} & \frac{\sqrt{3} i}{3} & 0 & 0 & 0\\0 & 0 & 0 & 0 & 0 & 0 & \frac{\sqrt{6} i}{3} & \frac{\sqrt{6}}{6} & \frac{\sqrt{6}}{6} & 0 & 0 & 0\\0 & 0 & 0 & 0 & 0 & 0 & 0 & \frac{\sqrt{6}}{6} & - \frac{\sqrt{6}}{6} & \frac{\sqrt{3} i}{3} & \frac{\sqrt{3} i}{3} & 0\\0 & 0 & 0 & 0 & 0 & 0 & 0 & \frac{\sqrt{6} i}{6} & - \frac{\sqrt{6} i}{6} & \frac{\sqrt{3}}{3} & 0 & \frac{\sqrt{3} i}{3}\\0 & 0 & 0 & 0 & 0 & 0 & 0 & \frac{\sqrt{6} i}{6} & - \frac{\sqrt{6} i}{6} & 0 & \frac{\sqrt{3}}{3} & - \frac{\sqrt{3} i}{3}\\0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & \frac{\sqrt{3} i}{3} & - \frac{\sqrt{3} i}{3} & \frac{\sqrt{3}}{3}\end{array}\right]$

### Input state

In the preliminaries we provided functions to map qubit states to the corresponding Fock states and vice-versa.

A *computational basis qubit state* on qubits $x_1, f_1, x_2, f_2$ is represented with a list of 4 boolean values.

A *Fock state* is represented with a list of twelve integer values.
As described above, for Fock states, the modes are enumerated as follows:
* mode pairs $(1,2), (3,4), (7,8), (9,10)$ respectively correspond to states $0,1$ for qubits $x_1, f_1, x_2, f_2$
* modes $0,5,6,11$ are auxiliary modes used for CZ gates

The input state of the circuit is $|0\rangle_{x_1}|0\rangle_{x_2}|0\rangle_{f_1}|1\rangle_{f_2}$.
With path encoding this corresponds to sending 4 photons in total in the optical circuit, in the input modes corresponding to the inital state of each qubit.

```python
qubit_istate = [0,0,0,1]
istate = toFockState(qubit_istate)

print("Input qubit state:", strState(qubit_istate))
print("Corresponding input Fock state:", strState(istate))
```

    Input qubit state: |0,0,0,1>
    Corresponding input Fock state: |0,1,0,1,0,0,0,1,0,0,1,0>

## Simulation

```python
backend = pcvl.BackendFactory().get_backend("Naive")
simulator = backend(circ)
```

### Computing the output state

Using perceval we compute the output state obtained with the optical circuit.

#### Amplitudes

We first decompose the output state in the computational basis and plot the corresponding amplitudes.

```python
output_qubit_states = [
    [x1,x2,f1,f2]
    for x1 in [0,1] for x2 in [0,1] for f1 in [0,1] for f2 in [0,1]
]

print("Output state amplitudes: (post-selected on qubit states, not renormalized)")
print("|x1,x2,f1,f2>")
for oqstate in output_qubit_states:
    ostate = toFockState(oqstate)
    a = simulator.probampli(pcvl.BasicState(pcvl.BasicState(istate)), pcvl.BasicState(ostate))
    print(strState(oqstate), a)
```

    Output state amplitudes: (post-selected on qubit states, not renormalized)
    |x1,x2,f1,f2>
    |0,0,0,0> 1.925929944387236e-34j
    |0,0,0,1> -7.703719777548943e-34j
    |0,0,1,0> -3.851859888774472e-34j
    |0,0,1,1> 3.851859888774472e-34j
    |0,1,0,0> (-1.734723475976807e-18+6.938893903907228e-18j)
    |0,1,0,1> (-1.734723475976807e-18+7.806255641895632e-18j)
    |0,1,1,0> (-1.734723475976807e-18+1.734723475976807e-18j)
    |0,1,1,1> (-1.734723475976807e-18+3.469446951953614e-18j)
    |1,0,0,0> (-1.734723475976807e-18+4.336808689942018e-18j)
    |1,0,0,1> (-1.734723475976807e-18-1.734723475976807e-18j)
    |1,0,1,0> (-1.734723475976807e-18+4.336808689942018e-18j)
    |1,0,1,1> (-1.734723475976807e-18+0j)
    |1,1,0,0> (-0.05555555555555561+4.5102810375396984e-17j)
    |1,1,0,1> (-0.0555555555555556+4.5102810375396984e-17j)
    |1,1,1,0> (-0.05555555555555559+4.5102810375396984e-17j)
    |1,1,1,1> (-0.055555555555555594+5.0306980803327406e-17j)

The amplitudes obtained with perceval correspond to the expected output state
$$\frac{1}{2} \left ( |0\rangle_{x_1}|0\rangle_{f_1} + |1\rangle_{x_1}|1\rangle_{f_1} \right ) \otimes \left ( |0\rangle_{x_2}|1\rangle_{f_2} + |1\rangle_{x_2}|0\rangle_{f_2} \right )$$
up to numerical precision, without renormalization.

#### Distribution

We now compute the probabilities to obtain each outcome corresponding to measuring the expected output state in the computational basis.

```python
input_states = {
    pcvl.BasicState(pcvl.BasicState(istate)): strState(qubit_istate)
}

expected_output_states = {
    pcvl.BasicState(toFockState([x1,x2,x1,1-x2])): strState([x1,x2,x1,1-x2])
    for x1 in [0,1] for x2 in [0,1]
}

ca = pcvl.CircuitAnalyser(simulator, input_states, expected_output_states)
ca.compute()

print("Output state distribution: (post-selected on expected qubit states, not renormalized)")
print("|x1,x2,f1,f2>")
pcvl.pdisplay(ca)
```

    Output state distribution: (post-selected on expected qubit states, not renormalized)
    |x1,x2,f1,f2>

<table>
<thead>
<tr><th>         </th><th style="text-align: right;">  |0,0,0,1></th><th style="text-align: right;">  |0,1,0,0></th><th style="text-align: right;">  |1,0,1,1></th><th style="text-align: right;">  |1,1,1,0></th></tr>
</thead>
<tbody>
<tr><td>|0,0,0,1></td><td style="text-align: right;">          0</td><td style="text-align: right;">          0</td><td style="text-align: right;">          0</td><td style="text-align: right;">   0.003086</td></tr>
</tbody>
</table>

The distribution computed with Perceval is uniform over each outcome, which corresponds to the expected distribution obtained in the paper when $x_0 = 0$.

### Interpretation of the outcomes

For each outcome we consider the values of qubits $x_2, x_1, x_0$ (where $x_0$ is 0) which represent a binary number between 0 and 7, here corresponding to 0, 4, 2 and 6 in the order of the table above.
After sampling the circuit, obtainig outcomes 2 or 6 allows to successfully compute the order $r=4$.
Obtaining outcome 0 is an expected failure of the quantum circuit, inherent to Shor's algorithm.
Outcome 4 is an expected failure as well, as it only allows to compute the trivial factors 1 and 15.

Since the distribution is uniform the circuit successfully yields a successful outcome with probability 1/2. This probability can be amplified exponentially close to $1$ by sampling the circuit multiple times.


```python
import numpy as np
from scipy.integrate import odeint
from scipy.integrate import ode

import numpy as np
from sympy import symbols,sqrt,sech,Rational,lambdify,Matrix,exp,cosh,cse,simplify,cos,sin
from sympy.vector import CoordSysCartesian

from theano.scalar.basic_sympy import SymPyCCode
from theano import function
from theano.scalar import floats

from IRI import *
from Symbolic import *
from ENUFrame import ENU

import astropy.coordinates as ac
import astropy.units as au
import astropy.time as at

#from multiprocessing import Pool
import dill
dill.settings['recurse'] = True
#import sys
#sys.setrecursionlimit(30000)

import threading
from time import sleep,time

import cloudpickle

class Fermat(object):
    def __init__(self,nFunc=None,neFunc=None,frequency = 120e6,type='s'):
        self.type = type
        self.frequency = frequency#Hz
        if nFunc is not None and neFunc is not None:
            self.nFunc = nFunc
            self.neFunc = neFunc
            self.eulerLambda, self.jacLambda = self.generateEulerEqnsSym(self.nFunc)
            return
        if nFunc is not None:
            self.nFunc = nFunc
            self.neFunc = self.n2ne(nFunc)
            self.eulerLambda, self.jacLambda = self.generateEulerEqnsSym(self.nFunc)
            return
        if neFunc is not None:
            self.neFunc = neFunc
            self.nFunc = self.ne2n(neFunc)
            self.eulerLambda, self.jacLambda = self.generateEulerEqnsSym(self.nFunc)
            return
    def loadFunc(self,filename):
        '''Load symbolic functions'''
        file = np.load(filename)
        if 'neFunc' in file.keys():
            self.neFunc = file['neFunc']
            self.nFunc = self.ne2n(neFunc)
            self.eulerLambda, self.jacLambda = self.generateEulerEqnsSym(self.nFunc)
            return
        if 'nFunc' in file.keys():
            self.nFunc = file['nFunc']
            self.neFunc = self.n2ne(nFunc)
            self.eulerLambda, self.jacLambda = self.generateEulerEqnsSym(self.nFunc)
            return

    def ne2n(self,neFunc):
        '''Analytically turn electron density to refractive index. Assume ne in m^-3'''
        self.neFunc = neFunc
        #wp = 5.63e4*np.sqrt(ne/1e6)/2pi#Hz^2 m^3 lightman p 226
        fp2 = 8.980**2 * neFunc
        self.nFunc = sqrt(Rational(1) - fp2/self.frequency**2)
        return self.nFunc

    def n2ne(self,nFunc):
        """Get electron density in m^-3 from refractive index"""
        self.nFunc = nFunc
        self.neFunc = (Rational(1) - nFunc**2)*self.frequency**2/8.980**2
        return self.neFunc

    def euler(self,px,py,pz,x,y,z,s,t=0):
        N = np.size(px)
        euler = np.zeros([7,N])
        i = 0
        while i < 7:
            euler[i,:] = self.eulerLambda[i](px,py,pz,x,y,z,s,t)
            i += 1
        return euler

    def eulerODE(self,y,t,*args):
        '''return pxdot,pydot,pzdot,xdot,ydot,zdot,sdot'''
        #print(y)
        e = self.euler(y[0],y[1],y[2],y[3],y[4],y[5],y[6],args[0]).flatten()
        #print(e)
        return e

    def jac(self,px,py,pz,x,y,z,s,t=0):
        N = np.size(px)
        jac = np.zeros([7,7,N])
        i = 0
        while i < 7:
            j = 0
            while j < 7:
                jac[i,j,:] = self.jacLambda[i][j](px,py,pz,x,y,z,s,t)
                j += 1
            i += 1
        return jac

    def jacODE(self,y,t,*args):
        '''return d ydot / d y'''
        j = self.jac(y[0],y[1],y[2],y[3],y[4],y[5],y[6],args[0]).reshape([7,7])

        #print('J:',j)
        return j

    def generateEulerEqnsSym(self,nFunc=None):
        '''Generate function with call signature f(t,y,*args)
        and accompanying jacobian jac(t,y,*args), jac[i,j] = d f[i] / d y[j]'''
        if nFunc is None:
            nFunc = self.nFunc
        x,y,z,px,py,pz,s,t = symbols('x y z px py pz s t')
        if self.type == 'z':
            sdot = nFunc / pz
            pxdot = nFunc.diff('x')*nFunc/pz
            pydot = nFunc.diff('y')*nFunc/pz
            pzdot = nFunc.diff('z')*nFunc/pz

            xdot = px / pz
            ydot = py / pz
            zdot = Rational(1)

        if self.type == 's':
            sdot = Rational(1)
            pxdot = nFunc.diff('x')
            pydot = nFunc.diff('y')
            pzdot = nFunc.diff('z')

            xdot = px / nFunc
            ydot = py / nFunc
            zdot = pz / nFunc

        eulerEqns = (pxdot,pydot,pzdot,xdot,ydot,zdot,sdot)

        euler = [lambdify((px,py,pz,x,y,z,s,t),eqn,"numpy") for eqn in eulerEqns]
        self.eulerLambda = euler
        jac = []

        for eqn in eulerEqns:
            jac.append([lambdify((px,py,pz,x,y,z,s,t),eqn.diff(var),"numpy") for var in (px,py,pz,x,y,z,s)])
        self.jacLambda = jac

        return self.eulerLambda, self.jacLambda
    def integrateRay(self,X0,direction,tmax,time = 0,N=100):
        '''Integrate rays from x0 in initial direction where coordinates are (r,theta,phi)'''
        direction /= np.linalg.norm(direction)
        x0,y0,z0 = X0
        xdot0,ydot0,zdot0 = direction
        sdot = np.sqrt(xdot0**2 + ydot0**2 + zdot0**2)
        px0 = xdot0/sdot
        py0 = ydot0/sdot
        pz0 = zdot0/sdot
        init = [px0,py0,pz0,x0,y0,z0,0]
        if self.type == 'z':
            tarray = np.linspace(z0,tmax,N)
        if self.type == 's':
            tarray = np.linspace(0,tmax,N)
        #print("Integrating at {0} from {1} in direction {2} until {3}".format(time,X0,direction,tmax))
        #print(init)
        #print("Integrating from {0} in direction {1} until {2}".format(x0,directions,tmax))
        Y,info =  odeint(self.eulerODE, init, tarray, args=(time,),Dfun = self.jacODE, col_deriv = 0, full_output=1)
        #print(info['hu'].shape,np.sum(info['hu']),info['hu'])
        #print(Y)
        x = Y[:,3]
        y = Y[:,4]
        z = Y[:,5]
        s = Y[:,6]
        return x,y,z,s

def synchronized(func):
    func.__lock__ = threading.Lock()
    def synced_func(*args, **kws):
        with func.__lock__:
            return func(*args, **kws)
    return synced_func

class FermatIntegrationThread (threading.Thread):
    '''Only works with one thread running per time.'''
    def __init__(self, fermat, threadId):
        super(FermatIntegrationThread,self).__init__()
        self.fermat = fermat
        self.stop = False
        self.threadId = threadId
        self.numJobs = 0
        self.jobIdx = 0
        self.jobs = {}
        self.resultMap = {}
        self.results = {}
        self.totalTime = 0.

    def addJob(self,X0,direction,tmax,time = 0,N=100,resultIdx=None):
        '''Add a ray job return the result index'''
        self.jobs[self.numJobs] = (X0,direction,tmax,time,N)
        if resultIdx is None:
            resultIdx = self.numJobs
        self.resultMap[self.numJobs] = resultIdx
        self.numJobs += 1
        return self.numJobs - 1

    def kill(self):
        self.stop = True

    def isEmpty(self):
        return self.jobIdx == self.numJobs
    def getAverageIntegrationTime(self):
        if self.numJobs > 0:
            return self.totalTime/self.numJobs
        else:
            return 0.

    def run(self):
        print ("Starting thread-{0}".format(self.threadId))
        while not self.stop or not self.isEmpty():
            while self.jobIdx < self.numJobs:
                tic = time()
                x,y,z,s = self.fermat.integrateRay(*self.jobs[self.jobIdx])
                self.totalTime += time() - tic
                self.results[self.resultMap[self.jobIdx]] = {'x':x,'y':y,'z':z,'s':s}
                self.jobIdx += 1
            #sleep(self.getAverageIntegrationTime())
        print ("Stopping thread-{0}".format(self.threadId))
        print("Number of jobs done: {0}".format(self.numJobs))
        print("Integration time: {0} s [total] | {1} s [average]".format(self.totalTime,self.getAverageIntegrationTime()))

def testThreadedFermat():
    sol = SolitonModel(5)
    neFunc = sol.generateSolitonsModel()
    f =  Fermat(neFunc = neFunc,type = 's')
    n = 1
    threads = []
    for i in range(n):
        threads.append(FermatIntegrationThread(f,i))
        threads[i].start()

    count = 0

    theta = np.linspace(-np.pi/8.,np.pi/8.,5)
    #phi = np.linspace(0,2*np.pi,6)
    rays = []
    origin = ac.ITRS(sol.enu.location).cartesian.xyz.to(au.km).value
    for t in theta:
        for p in theta:
            direction = ac.SkyCoord(np.sin(t),
                                    np.sin(p),
                                    1.,frame=sol.enu).transform_to('itrs').cartesian.xyz.value
            threads[count % n].addJob(origin,direction,1000,0.,100,resultIdx = count)
            count += 1
    for i in range(n):
        threads[i].kill()
    #print('waiting for completion')
    for i in range(n):
        threads[i].join()
        #pass

    #plotWavefront(f.nFunc.subs({'t':0}),rays,*getSolitonCube(sol))

if __name__=='__main__':
    np.random.seed(1234)
    #testSquare()
    #testSweep()
    testThreadedFermat()
    #testSmoothify()
    #testcseLam()

```

    Generated solitons symbolic function with 40 params
    Starting thread-0
    Integrating at 0.0 from [ 3826.81561935   459.21861553  5064.85501047] in direction [ 0.83321342 -0.23898555  0.49863946] until 1000
    Integrating at 0.0 from [ 3826.81561935   459.21861553  5064.85501047] in direction [ 0.73397746 -0.26606299  0.62489005] until 1000
    Integrating at 0.0 from [ 3826.81561935   459.21861553  5064.85501047] in direction [ 0.60174521 -0.28776134  0.74504773] until 1000
    Integrating at 0.0 from [ 3826.81561935   459.21861553  5064.85501047] in direction [ 0.45001998 -0.30013794  0.84107029] until 1000
    Integrating at 0.0 from [ 3826.81561935   459.21861553  5064.85501047] in direction [ 0.30006919 -0.30296296  0.90452856] until 1000
    Integrating at 0.0 from [ 3826.81561935   459.21861553  5064.85501047] in direction [ 0.84996359 -0.07854343  0.52095377] until 1000
    Integrating at 0.0 from [ 3826.81561935   459.21861553  5064.85501047] in direction [ 0.74850794 -0.09959196  0.65560453] until 1000
    Integrating at 0.0 from [ 3826.81561935   459.21861553  5064.85501047] in direction [ 0.61044294 -0.11960092  0.78297831] until 1000
    Integrating at 0.0 from [ 3826.81561935   459.21861553  5064.85501047] in direction [ 0.45059343 -0.13534176  0.88241043] until 1000
    Integrating at 0.0 from [ 3826.81561935   459.21861553  5064.85501047] in direction [ 0.29296095 -0.14538385  0.94500657] until 1000
    Integrating at 0.0 from [ 3826.81561935   459.21861553  5064.85501047] in direction [ 0.84224831  0.10106996  0.52953059] until 1000
    Integrating at 0.0 from [ 3826.81561935   459.21861553  5064.85501047] in direction [ 0.73929233  0.08871522  0.66751513] until 1000
    Integrating at 0.0 from [ 3826.81561935   459.21861553  5064.85501047] in direction [ 0.59870714  0.07184497  0.79773934] until 1000
    Integrating at 0.0 from [ 3826.81561935   459.21861553  5064.85501047] in direction [ 0.43596551  0.05231595  0.89844149] until 1000
    Integrating at 0.0 from [ 3826.81561935   459.21861553  5064.85501047] in direction [ 0.27607536  0.0331291   0.96056486] until 1000
    Integrating at 0.0 from [ 3826.81561935   459.21861553  5064.85501047] in direction [ 0.8072492   0.27740928  0.52095377] until 1000
    Integrating at 0.0 from [ 3826.81561935   459.21861553  5064.85501047] in direction [ 0.70369406  0.27385648  0.65560453] until 1000
    Integrating at 0.0 from [ 3826.81561935   459.21861553  5064.85501047] in direction [ 0.5648149   0.26063209  0.78297831] until 1000
    Integrating at 0.0 from [ 3826.81561935   459.21861553  5064.85501047] in direction [ 0.40577955  0.23810668  0.88241043] until 1000
    Integrating at 0.0 from [ 3826.81561935   459.21861553  5064.85501047] in direction [ 0.25024656  0.21056886  0.94500657] until 1000
    Integrating at 0.0 from [ 3826.81561935   459.21861553  5064.85501047] in direction [ 0.75301503  0.42933327  0.49863946] until 1000
    Integrating at 0.0 from [ 3826.81561935   459.21861553  5064.85501047] in direction [ 0.65019017  0.43216337  0.62489005] until 1000
    Integrating at 0.0 from [ 3826.81561935   459.21861553  5064.85501047] in direction [ 0.51657846  0.4219604   0.74504773] until 1000
    Integrating at 0.0 from [ 3826.81561935   459.21861553  5064.85501047] in direction [ 0.36623269  0.39808842  0.84107029] until 1000
    Integrating at 0.0 from [ 3826.81561935   459.21861553  5064.85501047] in direction [ 0.2198708   0.36535586  0.90452856] until 1000
    Stopping thread-0
    Number of jobs done: 25
    Integration time: 0.912000179291 s [total] | 0.0364800071716 s [average]

```python
import numpy as np

import dill
dill.settings['recurse'] = True

from sympy import symbols,sqrt,Rational,lambdify,Matrix,exp,cosh,cse,simplify,cos,sin
from sympy.vector import CoordSysCartesian

def createInitSolitonParam():
    '''Create an initial random param for a soliton'''
    #initial amp
    amp = 10**np.random.uniform(low = 9.5, high = 10.5)#electron / m^3
    #initial velcoity
    maxVel = 350./3600.#100km/hour in km/s pi*(6300+350)*2/24.*0.2 (20% of solar pressure field movement)
    initc = [np.random.uniform(low=-maxVel,high=maxVel),
             np.random.uniform(low=-maxVel,high=maxVel),
             np.random.uniform(low=-maxVel,high=maxVel)]

    #initial  location of blobs
    initx = [np.random.uniform(low=-100,high=100),
             np.random.uniform(low=-100,high=100),
             np.random.uniform(low=50,high=800)]
    b = np.random.uniform(low = 10.,high=100.)

    return {"A":np.sqrt(amp),
                       "cx":initc[0],
                       "cy":initc[1],
                       "cz":initc[2],
                       "x0":initx[0],
                       "y0":initx[1],
                       "z0":initx[2],
                       "b":b
                      }

solitonsFunc = None
initSolitonsParams = {}
i = 0
while i < 100:
    A = symbols ('A_{0}'.format(i))
    cx = symbols ('cx_{0}'.format(i))
    cy = symbols ('cy_{0}'.format(i))
    cz = symbols ('cz_{0}'.format(i))
    x0 = symbols ('x0_{0}'.format(i))
    y0 = symbols ('y0_{0}'.format(i))
    z0 = symbols ('z0_{0}'.format(i))
    b = symbols ('b_{0}'.format(i))
    i += 1
    init = createInitSolitonParam()

    initSolitonsParams[A.name] = init['A']
    initSolitonsParams[cx.name] = init['cx']
    initSolitonsParams[cy.name] = init['cy']
    initSolitonsParams[cz.name] = init['cz']
    initSolitonsParams[x0.name] = init['x0']
    initSolitonsParams[y0.name] = init['y0']
    initSolitonsParams[z0.name] = init['z0']
    initSolitonsParams[b.name] = init['b']

    x,y,z,t = symbols('x,y,z,t')

    N = CoordSysCartesian('N')
    c = cx*N.i + cy*N.j + cz*N.k
    X = x*N.i + y*N.j + z*N.k
    X0 = x0*N.i + y0*N.j + z0*N.k
    xx0 = X - t*c - X0
    func = A*A* exp(-xx0.dot(xx0)/b**Rational(2))
    if solitonsFunc is None:
        solitonsFunc = func
    else:
        solitonsFunc += func

print("Function: {0}".format(solitonsFunc))
solitonsFunc = solitonsFunc.subs(initSolitonsParams)
print("Lambdifying")
lam = lambdify(symbols('x,y,z,t'),solitonsFunc,"numpy")
dill.dump(lam,file("lambdified_func",'wb'))

```

    Function: A_0**2*exp((-(-cx_0*t + x - x0_0)**2 - (-cy_0*t + y - y0_0)**2 - (-cz_0*t + z - z0_0)**2)/b_0**2) + A_1**2*exp((-(-cx_1*t + x - x0_1)**2 - (-cy_1*t + y - y0_1)**2 - (-cz_1*t + z - z0_1)**2)/b_1**2) + A_10**2*exp((-(-cx_10*t + x - x0_10)**2 - (-cy_10*t + y - y0_10)**2 - (-cz_10*t + z - z0_10)**2)/b_10**2) + A_11**2*exp((-(-cx_11*t + x - x0_11)**2 - (-cy_11*t + y - y0_11)**2 - (-cz_11*t + z - z0_11)**2)/b_11**2) + A_12**2*exp((-(-cx_12*t + x - x0_12)**2 - (-cy_12*t + y - y0_12)**2 - (-cz_12*t + z - z0_12)**2)/b_12**2) + A_13**2*exp((-(-cx_13*t + x - x0_13)**2 - (-cy_13*t + y - y0_13)**2 - (-cz_13*t + z - z0_13)**2)/b_13**2) + A_14**2*exp((-(-cx_14*t + x - x0_14)**2 - (-cy_14*t + y - y0_14)**2 - (-cz_14*t + z - z0_14)**2)/b_14**2) + A_15**2*exp((-(-cx_15*t + x - x0_15)**2 - (-cy_15*t + y - y0_15)**2 - (-cz_15*t + z - z0_15)**2)/b_15**2) + A_16**2*exp((-(-cx_16*t + x - x0_16)**2 - (-cy_16*t + y - y0_16)**2 - (-cz_16*t + z - z0_16)**2)/b_16**2) + A_17**2*exp((-(-cx_17*t + x - x0_17)**2 - (-cy_17*t + y - y0_17)**2 - (-cz_17*t + z - z0_17)**2)/b_17**2) + A_18**2*exp((-(-cx_18*t + x - x0_18)**2 - (-cy_18*t + y - y0_18)**2 - (-cz_18*t + z - z0_18)**2)/b_18**2) + A_19**2*exp((-(-cx_19*t + x - x0_19)**2 - (-cy_19*t + y - y0_19)**2 - (-cz_19*t + z - z0_19)**2)/b_19**2) + A_2**2*exp((-(-cx_2*t + x - x0_2)**2 - (-cy_2*t + y - y0_2)**2 - (-cz_2*t + z - z0_2)**2)/b_2**2) + A_20**2*exp((-(-cx_20*t + x - x0_20)**2 - (-cy_20*t + y - y0_20)**2 - (-cz_20*t + z - z0_20)**2)/b_20**2) + A_21**2*exp((-(-cx_21*t + x - x0_21)**2 - (-cy_21*t + y - y0_21)**2 - (-cz_21*t + z - z0_21)**2)/b_21**2) + A_22**2*exp((-(-cx_22*t + x - x0_22)**2 - (-cy_22*t + y - y0_22)**2 - (-cz_22*t + z - z0_22)**2)/b_22**2) + A_23**2*exp((-(-cx_23*t + x - x0_23)**2 - (-cy_23*t + y - y0_23)**2 - (-cz_23*t + z - z0_23)**2)/b_23**2) + A_24**2*exp((-(-cx_24*t + x - x0_24)**2 - (-cy_24*t + y - y0_24)**2 - (-cz_24*t + z - z0_24)**2)/b_24**2) + A_25**2*exp((-(-cx_25*t + x - x0_25)**2 - (-cy_25*t + y - y0_25)**2 - (-cz_25*t + z - z0_25)**2)/b_25**2) + A_26**2*exp((-(-cx_26*t + x - x0_26)**2 - (-cy_26*t + y - y0_26)**2 - (-cz_26*t + z - z0_26)**2)/b_26**2) + A_27**2*exp((-(-cx_27*t + x - x0_27)**2 - (-cy_27*t + y - y0_27)**2 - (-cz_27*t + z - z0_27)**2)/b_27**2) + A_28**2*exp((-(-cx_28*t + x - x0_28)**2 - (-cy_28*t + y - y0_28)**2 - (-cz_28*t + z - z0_28)**2)/b_28**2) + A_29**2*exp((-(-cx_29*t + x - x0_29)**2 - (-cy_29*t + y - y0_29)**2 - (-cz_29*t + z - z0_29)**2)/b_29**2) + A_3**2*exp((-(-cx_3*t + x - x0_3)**2 - (-cy_3*t + y - y0_3)**2 - (-cz_3*t + z - z0_3)**2)/b_3**2) + A_30**2*exp((-(-cx_30*t + x - x0_30)**2 - (-cy_30*t + y - y0_30)**2 - (-cz_30*t + z - z0_30)**2)/b_30**2) + A_31**2*exp((-(-cx_31*t + x - x0_31)**2 - (-cy_31*t + y - y0_31)**2 - (-cz_31*t + z - z0_31)**2)/b_31**2) + A_32**2*exp((-(-cx_32*t + x - x0_32)**2 - (-cy_32*t + y - y0_32)**2 - (-cz_32*t + z - z0_32)**2)/b_32**2) + A_33**2*exp((-(-cx_33*t + x - x0_33)**2 - (-cy_33*t + y - y0_33)**2 - (-cz_33*t + z - z0_33)**2)/b_33**2) + A_34**2*exp((-(-cx_34*t + x - x0_34)**2 - (-cy_34*t + y - y0_34)**2 - (-cz_34*t + z - z0_34)**2)/b_34**2) + A_35**2*exp((-(-cx_35*t + x - x0_35)**2 - (-cy_35*t + y - y0_35)**2 - (-cz_35*t + z - z0_35)**2)/b_35**2) + A_36**2*exp((-(-cx_36*t + x - x0_36)**2 - (-cy_36*t + y - y0_36)**2 - (-cz_36*t + z - z0_36)**2)/b_36**2) + A_37**2*exp((-(-cx_37*t + x - x0_37)**2 - (-cy_37*t + y - y0_37)**2 - (-cz_37*t + z - z0_37)**2)/b_37**2) + A_38**2*exp((-(-cx_38*t + x - x0_38)**2 - (-cy_38*t + y - y0_38)**2 - (-cz_38*t + z - z0_38)**2)/b_38**2) + A_39**2*exp((-(-cx_39*t + x - x0_39)**2 - (-cy_39*t + y - y0_39)**2 - (-cz_39*t + z - z0_39)**2)/b_39**2) + A_4**2*exp((-(-cx_4*t + x - x0_4)**2 - (-cy_4*t + y - y0_4)**2 - (-cz_4*t + z - z0_4)**2)/b_4**2) + A_40**2*exp((-(-cx_40*t + x - x0_40)**2 - (-cy_40*t + y - y0_40)**2 - (-cz_40*t + z - z0_40)**2)/b_40**2) + A_41**2*exp((-(-cx_41*t + x - x0_41)**2 - (-cy_41*t + y - y0_41)**2 - (-cz_41*t + z - z0_41)**2)/b_41**2) + A_42**2*exp((-(-cx_42*t + x - x0_42)**2 - (-cy_42*t + y - y0_42)**2 - (-cz_42*t + z - z0_42)**2)/b_42**2) + A_43**2*exp((-(-cx_43*t + x - x0_43)**2 - (-cy_43*t + y - y0_43)**2 - (-cz_43*t + z - z0_43)**2)/b_43**2) + A_44**2*exp((-(-cx_44*t + x - x0_44)**2 - (-cy_44*t + y - y0_44)**2 - (-cz_44*t + z - z0_44)**2)/b_44**2) + A_45**2*exp((-(-cx_45*t + x - x0_45)**2 - (-cy_45*t + y - y0_45)**2 - (-cz_45*t + z - z0_45)**2)/b_45**2) + A_46**2*exp((-(-cx_46*t + x - x0_46)**2 - (-cy_46*t + y - y0_46)**2 - (-cz_46*t + z - z0_46)**2)/b_46**2) + A_47**2*exp((-(-cx_47*t + x - x0_47)**2 - (-cy_47*t + y - y0_47)**2 - (-cz_47*t + z - z0_47)**2)/b_47**2) + A_48**2*exp((-(-cx_48*t + x - x0_48)**2 - (-cy_48*t + y - y0_48)**2 - (-cz_48*t + z - z0_48)**2)/b_48**2) + A_49**2*exp((-(-cx_49*t + x - x0_49)**2 - (-cy_49*t + y - y0_49)**2 - (-cz_49*t + z - z0_49)**2)/b_49**2) + A_5**2*exp((-(-cx_5*t + x - x0_5)**2 - (-cy_5*t + y - y0_5)**2 - (-cz_5*t + z - z0_5)**2)/b_5**2) + A_50**2*exp((-(-cx_50*t + x - x0_50)**2 - (-cy_50*t + y - y0_50)**2 - (-cz_50*t + z - z0_50)**2)/b_50**2) + A_51**2*exp((-(-cx_51*t + x - x0_51)**2 - (-cy_51*t + y - y0_51)**2 - (-cz_51*t + z - z0_51)**2)/b_51**2) + A_52**2*exp((-(-cx_52*t + x - x0_52)**2 - (-cy_52*t + y - y0_52)**2 - (-cz_52*t + z - z0_52)**2)/b_52**2) + A_53**2*exp((-(-cx_53*t + x - x0_53)**2 - (-cy_53*t + y - y0_53)**2 - (-cz_53*t + z - z0_53)**2)/b_53**2) + A_54**2*exp((-(-cx_54*t + x - x0_54)**2 - (-cy_54*t + y - y0_54)**2 - (-cz_54*t + z - z0_54)**2)/b_54**2) + A_55**2*exp((-(-cx_55*t + x - x0_55)**2 - (-cy_55*t + y - y0_55)**2 - (-cz_55*t + z - z0_55)**2)/b_55**2) + A_56**2*exp((-(-cx_56*t + x - x0_56)**2 - (-cy_56*t + y - y0_56)**2 - (-cz_56*t + z - z0_56)**2)/b_56**2) + A_57**2*exp((-(-cx_57*t + x - x0_57)**2 - (-cy_57*t + y - y0_57)**2 - (-cz_57*t + z - z0_57)**2)/b_57**2) + A_58**2*exp((-(-cx_58*t + x - x0_58)**2 - (-cy_58*t + y - y0_58)**2 - (-cz_58*t + z - z0_58)**2)/b_58**2) + A_59**2*exp((-(-cx_59*t + x - x0_59)**2 - (-cy_59*t + y - y0_59)**2 - (-cz_59*t + z - z0_59)**2)/b_59**2) + A_6**2*exp((-(-cx_6*t + x - x0_6)**2 - (-cy_6*t + y - y0_6)**2 - (-cz_6*t + z - z0_6)**2)/b_6**2) + A_60**2*exp((-(-cx_60*t + x - x0_60)**2 - (-cy_60*t + y - y0_60)**2 - (-cz_60*t + z - z0_60)**2)/b_60**2) + A_61**2*exp((-(-cx_61*t + x - x0_61)**2 - (-cy_61*t + y - y0_61)**2 - (-cz_61*t + z - z0_61)**2)/b_61**2) + A_62**2*exp((-(-cx_62*t + x - x0_62)**2 - (-cy_62*t + y - y0_62)**2 - (-cz_62*t + z - z0_62)**2)/b_62**2) + A_63**2*exp((-(-cx_63*t + x - x0_63)**2 - (-cy_63*t + y - y0_63)**2 - (-cz_63*t + z - z0_63)**2)/b_63**2) + A_64**2*exp((-(-cx_64*t + x - x0_64)**2 - (-cy_64*t + y - y0_64)**2 - (-cz_64*t + z - z0_64)**2)/b_64**2) + A_65**2*exp((-(-cx_65*t + x - x0_65)**2 - (-cy_65*t + y - y0_65)**2 - (-cz_65*t + z - z0_65)**2)/b_65**2) + A_66**2*exp((-(-cx_66*t + x - x0_66)**2 - (-cy_66*t + y - y0_66)**2 - (-cz_66*t + z - z0_66)**2)/b_66**2) + A_67**2*exp((-(-cx_67*t + x - x0_67)**2 - (-cy_67*t + y - y0_67)**2 - (-cz_67*t + z - z0_67)**2)/b_67**2) + A_68**2*exp((-(-cx_68*t + x - x0_68)**2 - (-cy_68*t + y - y0_68)**2 - (-cz_68*t + z - z0_68)**2)/b_68**2) + A_69**2*exp((-(-cx_69*t + x - x0_69)**2 - (-cy_69*t + y - y0_69)**2 - (-cz_69*t + z - z0_69)**2)/b_69**2) + A_7**2*exp((-(-cx_7*t + x - x0_7)**2 - (-cy_7*t + y - y0_7)**2 - (-cz_7*t + z - z0_7)**2)/b_7**2) + A_70**2*exp((-(-cx_70*t + x - x0_70)**2 - (-cy_70*t + y - y0_70)**2 - (-cz_70*t + z - z0_70)**2)/b_70**2) + A_71**2*exp((-(-cx_71*t + x - x0_71)**2 - (-cy_71*t + y - y0_71)**2 - (-cz_71*t + z - z0_71)**2)/b_71**2) + A_72**2*exp((-(-cx_72*t + x - x0_72)**2 - (-cy_72*t + y - y0_72)**2 - (-cz_72*t + z - z0_72)**2)/b_72**2) + A_73**2*exp((-(-cx_73*t + x - x0_73)**2 - (-cy_73*t + y - y0_73)**2 - (-cz_73*t + z - z0_73)**2)/b_73**2) + A_74**2*exp((-(-cx_74*t + x - x0_74)**2 - (-cy_74*t + y - y0_74)**2 - (-cz_74*t + z - z0_74)**2)/b_74**2) + A_75**2*exp((-(-cx_75*t + x - x0_75)**2 - (-cy_75*t + y - y0_75)**2 - (-cz_75*t + z - z0_75)**2)/b_75**2) + A_76**2*exp((-(-cx_76*t + x - x0_76)**2 - (-cy_76*t + y - y0_76)**2 - (-cz_76*t + z - z0_76)**2)/b_76**2) + A_77**2*exp((-(-cx_77*t + x - x0_77)**2 - (-cy_77*t + y - y0_77)**2 - (-cz_77*t + z - z0_77)**2)/b_77**2) + A_78**2*exp((-(-cx_78*t + x - x0_78)**2 - (-cy_78*t + y - y0_78)**2 - (-cz_78*t + z - z0_78)**2)/b_78**2) + A_79**2*exp((-(-cx_79*t + x - x0_79)**2 - (-cy_79*t + y - y0_79)**2 - (-cz_79*t + z - z0_79)**2)/b_79**2) + A_8**2*exp((-(-cx_8*t + x - x0_8)**2 - (-cy_8*t + y - y0_8)**2 - (-cz_8*t + z - z0_8)**2)/b_8**2) + A_80**2*exp((-(-cx_80*t + x - x0_80)**2 - (-cy_80*t + y - y0_80)**2 - (-cz_80*t + z - z0_80)**2)/b_80**2) + A_81**2*exp((-(-cx_81*t + x - x0_81)**2 - (-cy_81*t + y - y0_81)**2 - (-cz_81*t + z - z0_81)**2)/b_81**2) + A_82**2*exp((-(-cx_82*t + x - x0_82)**2 - (-cy_82*t + y - y0_82)**2 - (-cz_82*t + z - z0_82)**2)/b_82**2) + A_83**2*exp((-(-cx_83*t + x - x0_83)**2 - (-cy_83*t + y - y0_83)**2 - (-cz_83*t + z - z0_83)**2)/b_83**2) + A_84**2*exp((-(-cx_84*t + x - x0_84)**2 - (-cy_84*t + y - y0_84)**2 - (-cz_84*t + z - z0_84)**2)/b_84**2) + A_85**2*exp((-(-cx_85*t + x - x0_85)**2 - (-cy_85*t + y - y0_85)**2 - (-cz_85*t + z - z0_85)**2)/b_85**2) + A_86**2*exp((-(-cx_86*t + x - x0_86)**2 - (-cy_86*t + y - y0_86)**2 - (-cz_86*t + z - z0_86)**2)/b_86**2) + A_87**2*exp((-(-cx_87*t + x - x0_87)**2 - (-cy_87*t + y - y0_87)**2 - (-cz_87*t + z - z0_87)**2)/b_87**2) + A_88**2*exp((-(-cx_88*t + x - x0_88)**2 - (-cy_88*t + y - y0_88)**2 - (-cz_88*t + z - z0_88)**2)/b_88**2) + A_89**2*exp((-(-cx_89*t + x - x0_89)**2 - (-cy_89*t + y - y0_89)**2 - (-cz_89*t + z - z0_89)**2)/b_89**2) + A_9**2*exp((-(-cx_9*t + x - x0_9)**2 - (-cy_9*t + y - y0_9)**2 - (-cz_9*t + z - z0_9)**2)/b_9**2) + A_90**2*exp((-(-cx_90*t + x - x0_90)**2 - (-cy_90*t + y - y0_90)**2 - (-cz_90*t + z - z0_90)**2)/b_90**2) + A_91**2*exp((-(-cx_91*t + x - x0_91)**2 - (-cy_91*t + y - y0_91)**2 - (-cz_91*t + z - z0_91)**2)/b_91**2) + A_92**2*exp((-(-cx_92*t + x - x0_92)**2 - (-cy_92*t + y - y0_92)**2 - (-cz_92*t + z - z0_92)**2)/b_92**2) + A_93**2*exp((-(-cx_93*t + x - x0_93)**2 - (-cy_93*t + y - y0_93)**2 - (-cz_93*t + z - z0_93)**2)/b_93**2) + A_94**2*exp((-(-cx_94*t + x - x0_94)**2 - (-cy_94*t + y - y0_94)**2 - (-cz_94*t + z - z0_94)**2)/b_94**2) + A_95**2*exp((-(-cx_95*t + x - x0_95)**2 - (-cy_95*t + y - y0_95)**2 - (-cz_95*t + z - z0_95)**2)/b_95**2) + A_96**2*exp((-(-cx_96*t + x - x0_96)**2 - (-cy_96*t + y - y0_96)**2 - (-cz_96*t + z - z0_96)**2)/b_96**2) + A_97**2*exp((-(-cx_97*t + x - x0_97)**2 - (-cy_97*t + y - y0_97)**2 - (-cz_97*t + z - z0_97)**2)/b_97**2) + A_98**2*exp((-(-cx_98*t + x - x0_98)**2 - (-cy_98*t + y - y0_98)**2 - (-cz_98*t + z - z0_98)**2)/b_98**2) + A_99**2*exp((-(-cx_99*t + x - x0_99)**2 - (-cy_99*t + y - y0_99)**2 - (-cz_99*t + z - z0_99)**2)/b_99**2)
    Lambdifying

```python
help(dill.dump)
```

    Help on function dump in module dill.dill:

    dump(obj, file, protocol=None, byref=None, fmode=None, recurse=None)
        pickle an object to a file

```python

```


CS 403/603 Machine Learning Ridge Regression (L2 Regularization)

minimizes the following cost function:

\begin{equation}
J(x, \theta, y) = \sum_{i=1}^{m}(\theta^T\bar{x}_i - y_i)^2 + \lambda ||\theta||^2
\end{equation}

**Ridge Regression**

*   It uses L2 Regularization

*   It shrinks the parameters, therefore it is mostly used to prevent multicollinearity.

*  It reduces the model complexity by coefficient shrinkage.

```python
from google.colab import files
uploaded = files.upload()
```

<input type="file" id="files-0fa3d8c3-6713-4b28-9224-4108712c5392" name="files[]" multiple disabled
   style="border:none" />
<output id="result-0fa3d8c3-6713-4b28-9224-4108712c5392">
 Upload widget is only available when the cell has been executed in the
 current browser session. Please rerun this cell to enable.
 </output>

    Saving salary_data.csv to salary_data.csv

```python
# Importing libraries

import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt

# Ridge Regression

class RidgeRegression() :

    def __init__( self, learning_rate, iterations, l2_penality ) :

        self.learning_rate = learning_rate
        self.iterations = iterations
        self.l2_penality = l2_penality

    # Function for model training
    def fit( self, X, Y ) :

        # no_of_training_examples, no_of_features
        self.m, self.n = X.shape

        # weight initialization
        self.W = np.zeros( self.n )

        self.b = 0
        self.X = X
        self.Y = Y

        # gradient descent learning

        for i in range( self.iterations ) :
            self.update_weights()
        return self

    # Helper function to update weights in gradient descent

    def update_weights( self ) :
        Y_pred = self.predict( self.X )

        # calculate gradients
        dW = ( - ( 2 * ( self.X.T ).dot( self.Y - Y_pred ) ) +
               ( 2 * self.l2_penality * self.W ) ) / self.m
        db = - 2 * np.sum( self.Y - Y_pred ) / self.m

        # update weights
        self.W = self.W - self.learning_rate * dW
        self.b = self.b - self.learning_rate * db
        return self

    # Hypothetical function  h( x )
    def predict( self, X ) :
        return X.dot( self.W ) + self.b

```

```python
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt

# Ridge Regression

class RidgeRegression1() :

    def __init__( self, learning_rate, iterations, l2_penality ) :

        self.learning_rate = learning_rate
        self.iterations = iterations
        self.l2_penality = l2_penality

    # Function for model training
    def fit( self, X, Y ) :

        # no_of_training_examples, no_of_features
        self.m, self.n = X.shape

        # weight initialization
        self.W = np.zeros( self.n )

        self.b = 0
        self.X = X
        self.Y = Y

        # gradient descent learning

        for i in range( self.iterations ) :
            self.update_weights()
        return self

    # Helper function to update weights in gradient descent

    def update_weights( self ) :
        Y_pred = self.predict( self.X )

        # calculate gradients
        dW = - ( 2 * ( self.X.T ).dot( self.Y - Y_pred ) )/ self.m
        db = - 2 * np.sum( self.Y - Y_pred ) / self.m

        # update weights
        self.W = self.W - self.learning_rate * dW
        self.b = self.b - self.learning_rate * db
        return self

    # Hypothetical function  h( x )
    def predict( self, X ) :
        return X.dot( self.W ) + self.b
```

```python
def main1() :

    # Importing dataset
    df = pd.read_csv( "/content/salary_data.csv" )
    print(df)

    X = df.iloc[:, :-1].values
    Y = df.iloc[:, 1].values

    # Splitting dataset into train and test set
    X_train, X_test, Y_train, Y_test = train_test_split( X, Y,

                                          test_size = 1 / 3, random_state = 0 )

    # Model training
    model = RidgeRegression1( iterations = 1000,
                            learning_rate = 0.01, l2_penality = 1 )
    model.fit( X_train, Y_train )

    # Prediction on test set
    Y_pred = model.predict( X_test )
    print( "Predicted values ", np.round( Y_pred[:3], 2 ) )
    print( "Real values      ", Y_test[:3] )
    print( "Trained W        ", round( model.W[0], 2 ) )
    print( "Trained b        ", round( model.b, 2 ) )

    # Visualization on test set
    plt.scatter( X_test, Y_test, color = 'blue' )
    plt.plot( X_test, Y_pred, color = 'orange' )
    plt.title( 'Salary vs Experience' )
    plt.xlabel( 'Years of Experience' )
    plt.ylabel( 'Salary' )
    plt.show()
```

```python
main1()
```

```python
 df = pd.read_csv( "/content/salary_data.csv" )
 print(df)
```

        YearsExperience  Salary
    0               1.1   39343
    1               1.3   46205
    2               1.5   37731
    3               2.0   43525
    4               2.2   39891
    5               2.9   56642
    6               3.0   60150
    7               3.2   54445
    8               3.2   64445
    9               3.7   57189
    10              3.9   63218
    11              4.0   55794
    12              4.0   56957
    13              4.1   57081
    14              4.5   61111
    15              4.9   67938
    16              5.1   66029
    17              5.3   83088
    18              5.9   81363
    19              6.0   93940
    20              6.8   91738
    21              7.1   98273
    22              7.9  101302
    23              8.2  113812
    24              8.7  109431
    25              9.0  105582
    26              9.5  116969
    27              9.6  112635
    28             10.3  122391
    29             10.5  121872

```python
# Driver code
def main() :

    # Importing dataset
    df = pd.read_csv( "/content/salary_data.csv" )
    print(df)

    X = df.iloc[:, :-1].values
    Y = df.iloc[:, 1].values

    # Splitting dataset into train and test set
    X_train, X_test, Y_train, Y_test = train_test_split( X, Y,

                                          test_size = 1 / 3, random_state = 0 )

    # Model training
    model = RidgeRegression( iterations = 1000,
                            learning_rate = 0.01, l2_penality = 1 )
    model.fit( X_train, Y_train )

    # Prediction on test set
    Y_pred = model.predict( X_test )
    print( "Predicted values ", np.round( Y_pred[:3], 2 ) )
    print( "Real values      ", Y_test[:3] )
    print( "Trained W        ", round( model.W[0], 2 ) )
    print( "Trained b        ", round( model.b, 2 ) )

    # Visualization on test set
    plt.scatter( X_test, Y_test, color = 'blue' )
    plt.plot( X_test, Y_pred, color = 'orange' )
    plt.title( 'Salary vs Experience' )
    plt.xlabel( 'Years of Experience' )
    plt.ylabel( 'Salary' )
    plt.show()
```

```python
if __name__ == "__main__" :
    main()
```

```python
Predicted values  [ 40831.44 122898.14  65078.42]
Real values       [ 37731 122391  57081]
Trained W         9325.76
Trained b         26842.8

Predicted values  [ 40594.69 123305.18  65031.88]
Real values       [ 37731 122391  57081]
Trained W         9398.92
Trained b         26496.31

```


# Loudness measures and vector measures for 2-channel stereo
for level differences, Franz Zotter, 2016.

The simplest loudness measure for panning with the gains $g_{1,2}$ and an undetermined broadband signal of normalized loudness is quadratic

\begin{equation}
   V=g_1+g_2\qquad\qquad E=g_1^2+g_2^2,
\end{equation}

and given any unnormalized panning gains $\tilde g_{1,2}$, it is wise to normalize them to $\sqrt{E}$

\begin{equation}
   g_{1,2}=\frac{\tilde g_{1,2}}{\sqrt{g_1^2+g_2^2}}.
\end{equation}

We may choose a level difference of $-30\mathrm{dB}\dots+30\mathrm{dB}$ for $g_1,g_2$ for a $\pm30^\circ$ loudspeaker setup. We will see what the modeled localization is that we get from the vector models using the direction vectors $\boldsymbol\theta_{1}=[\sin30^\circ,\cos30^\circ]^\mathrm{T}$ and $\boldsymbol\theta_{2}=[-\sin30^\circ,\cos30^\circ]^\mathrm{T}$

\begin{equation}
   \boldsymbol r_\mathrm{V}=\frac{g_1\boldsymbol{\theta}_1+g_2\boldsymbol{\theta}_2}{g_1+g_2}, \qquad\qquad
   \boldsymbol r_\mathrm{E}=\frac{g_1^2\boldsymbol{\theta}_1+g_2^2\boldsymbol{\theta}_2}{g_1^2+g_2^2}.
\end{equation}

```python
import numpy as np
from bokeh.plotting import figure, output_file, show
from bokeh.io import output_notebook

def r_vector(g,theta):
    L=theta.size
    g1=g[0,:]
    g2=g[1,:]
    normalizer=g1+g2
    g1/=normalizer
    g2/=normalizer
    th1x=np.cos(theta[0])
    th1y=np.sin(theta[0])
    th2x=np.cos(theta[1])
    th2y=np.sin(theta[1])
    rx=g1*th1x+g2*th2x
    ry=g1*th1y+g2*th2y
    r=np.array([rx,ry])
    return r

L=np.linspace(-30,30,31)
g=np.array([10**(-L/40), 10**(L/40)])
g/=np.sum(g,0)
theta=np.array([-30,30])*np.pi/180.0;
output_notebook()
p1=figure(title="r vector direction",plot_width=300,plot_height=250,x_range=[-30,30],y_range=[-30,30])
p2=figure(title="r vector length",plot_width=300,plot_height=250,x_range=[-30,30],y_range=[0,100])

r=r_vector(g,theta)
mr=np.sqrt(r[1,:]**2+r[0,:]**2)
p2.line(L,2*np.arccos(mr)*180/np.pi,legend_label="2acos||rV||",line_width=2)
p1.line(L,np.arctan2(r[1,:],r[0,:])*180/np.pi,legend_label="rV",line_width=2)

r=r_vector(g**2,theta)
mr=np.sqrt(r[1,:]**2+r[0,:]**2)
p2.line(L,2*np.arccos(mr)*180/np.pi,legend_label="2acos||rE||",line_width=2,color="red")
p1.line(L,np.arctan2(r[1,:],r[0,:])*180/np.pi,legend_label="rE",line_width=2,color="red")

g[1,:]/=2
r=r_vector(g**1.5,theta)
mr=np.sqrt(r[1,:]**2+r[0,:]**2)
p2.line(L,2*np.arccos(mr)*180/np.pi,legend_label="2acos||r1.5||",line_width=2,color="green")
p1.line(L,np.arctan2(r[1,:],r[0,:])*180/np.pi,legend_label="r1.5+3dB",line_width=2,color="green")

p1.legend.location="top_left"
p2.legend.background_fill_alpha = 0.5
show(p1)
show(p2)

```

    Loading BokehJS ...

```python

```