In this ipython notebook we will consider our method of constructing nonlinear interactions. We examine the resulting terms and confirm they have the expected behavior.


In [5]:
import Potapov_Code.Roots as Roots
import Potapov_Code.Potapov as Potapov
import Potapov_Code.Time_Delay_Network as Time_Delay_Network
import Potapov_Code.Time_Sims as Time_Sims
import Potapov_Code.functions as functions
import Potapov_Code.tests as tests

import numpy as np
import numpy.linalg as la
from scipy.integrate import ode

import matplotlib.pyplot as plt
%matplotlib inline

In [6]:
def contour_plot(Mat,func = abs):
    '''
    Make a simple plot to view a matrix

    Args:
        Mat (compelx-valued matrix): a matrix to view
        func (optional[function]): a function to apply to each component

    Generates a plot of the matrix.
    '''
    fig = plt.figure()
    ax = fig.add_subplot(111)
    cax = ax.matshow(func(Mat), interpolation='nearest')
    fig.colorbar(cax)
    plt.show()

In [400]:
Ex = Time_Delay_Network.Example1(r = 0.7, max_linewidth=35.)
Ex.run_Potapov()
E = Ex.E
roots = Ex.roots
M1 = Ex.M1
delays = Ex.delays
modes = functions.spatial_modes(roots,M1,E)

In [401]:
vecs = Ex.vecs

In [402]:
roots.sort(key = lambda z: z.imag)

In [403]:
roots


Out[403]:
[(-1.1889164797957745-20.943951023931955j),
 (-1.1889164797957745+0j),
 (-1.1889164797957745+20.943951023931955j)]

In [463]:
root_z = lambda z: 1j*z  ## a fake root we will vary

In [471]:
roots_to_use = lambda z: [root_z(z).imag,roots[0].imag,roots[-1].imag]

In [472]:
modes_to_use = modes

In [473]:
delay_indices = 0

In [474]:
plus_or_minus_arr =  [1,1,1]

In [488]:
x = np.linspace(-10000, 10000,4000)

In [489]:
reload(Potapov_Code.functions)


Out[489]:
<module 'Potapov_Code.functions' from 'Potapov_Code/functions.pyc'>

In [490]:
f = lambda z: functions.make_nonlinear_interaction(roots_to_use(z),modes_to_use,Ex.delays,0,
                                                   0,0.01,plus_or_minus_arr)

In [491]:
plt.plot(x, [abs(f(z))**2 for z in x])


Out[491]:
[<matplotlib.lines.Line2D at 0x12929b850>]

In [492]:
Ex = Time_Delay_Network.Example3(r1 = 0.7, r3 = 0.7, max_linewidth=35.)
Ex.run_Potapov()
E = Ex.E
roots = Ex.roots
M1 = Ex.M1
delays = Ex.delays
modes = functions.spatial_modes(roots,M1,E)

In [493]:
vecs = Ex.vecs

In [494]:
roots.sort(key = lambda z: z.imag)

In [495]:
roots


Out[495]:
[(-1.1867857239576904-53.731290378625815j),
 (-1.1714495298385854-40.60438955513678j),
 (-1.2178438327806835-32.542503045139362j),
 (-1.1500606861696614-20.178105255320496j),
 (-1.236142384757039-10.901754569501422j),
 (-1.1431816829945924+1.2586240840022705e-167j),
 (-1.2361423847570399+10.901754569501421j),
 (-1.1500606861696616+20.178105255320492j),
 (-1.2178438327806838+32.542503045139362j),
 (-1.1714495298385852+40.60438955513678j),
 (-1.1867857239576902+53.731290378625815j)]

In [496]:
roots[-1]+roots[-2]


Out[496]:
(-2.358235253796275+94.33567993376259j)

In [497]:
root_z = lambda z: 1j*z  ## a fake root we will vary

In [563]:
roots_to_use = lambda z: [root_z(z).imag,roots[0].imag,roots[1].imag]

In [564]:
modes_to_use = [modes[0],modes[0], modes[0]]

In [565]:
delay_indices = 0

In [566]:
plus_or_minus_arr =  [1,1,1]

In [582]:
x = np.linspace(-800,1000,4000)

In [583]:
reload(Potapov_Code.functions)


Out[583]:
<module 'Potapov_Code.functions' from 'Potapov_Code/functions.pyc'>

In [584]:
f = lambda z: functions.make_nonlinear_interaction(roots_to_use(z),modes_to_use,Ex.delays,0,
                                                   0,0.01,plus_or_minus_arr)

In [585]:
### by default, indices_of_refraction=[1,1,1]

We will vary the fake root we introduced to obtain the phase-mismatch diagram. That is, the phase mismatch $\delta k$ is going to be some linear function of $z$.


In [586]:
plt.plot(x, [abs(f(z))**2 for z in x])


Out[586]:
[<matplotlib.lines.Line2D at 0x12a91b210>]

What happens when we change the indices of refraction for the different modes? The phase-mismatch will shift depending on where the new $\delta k = 0$ occurs. The width of the peak may also change if the indices of refraction are large.


In [605]:
indices_of_refraction=[3.,5.,10.]

In [606]:
f = lambda z: functions.make_nonlinear_interaction(roots_to_use(z),
                modes_to_use,Ex.delays,0,0,0.1,plus_or_minus_arr,indices_of_refraction=indices_of_refraction)

In [607]:
plt.plot(x, [abs(f(z))**2 for z in x])


Out[607]:
[<matplotlib.lines.Line2D at 0x12b4a1910>]

Generating a Hamiltonian from a model

In this section we will use example 3 to generate a Hamiltonian with nonlinaer coefficients resulting when inserting a nonlinearity in a circuit. We will assume that the nonlinearity is inserting at the delay line of index 0 corresponding to $\tau_1$.


In [611]:
import sympy as sp
import itertools
from qnet.algebra.circuit_algebra import *

In [612]:
Ex = Time_Delay_Network.Example3(r1 = 0.9, r3 = 0.9, max_linewidth=35.,max_freq=25.)
Ex.run_Potapov()
E = Ex.E
roots = Ex.roots
M1 = Ex.M1
delays = Ex.delays
modes = functions.spatial_modes(roots,M1,E)

In [613]:
roots


Out[613]:
[(-0.36514939263783197-10.901513851604911j),
 (-0.33769377381866061+2.0232070709165708e-21j),
 (-0.36514939263783225+10.901513851604911j),
 (-0.33972600185569901+20.178467995423109j),
 (-0.3397260018556984-20.17846799542311j)]

In [614]:
## nonlinearity information

delay_index = 0 
start_nonlin = 0.
duration_nonlin = .1

In [806]:
NONLIN_WEIGHT = 10.

In [615]:
m = len(roots)

In [616]:
indices = range(m)

In [617]:
chi_order = 3  ## i.e. chi-3 nonlinearity

In [618]:
plus_minus_combinations = list(itertools.combinations(range(chi_order + 1), 2))  ## pick which fields are annihilated

In [619]:
list_of_pm_arr = []
for tup in plus_minus_combinations:
    ls = [1]*(chi_order+1)
    for i in tup:
        ls[i]=-1
    list_of_pm_arr.append(ls)

In [620]:
a = [sp.symbols('a_'+str(i)) for i in range(m)]
a_H = [sp.symbols('a^H_'+str(i)) for i in range(m)]

In [ ]:


In [621]:
A,B,C,D = Potapov.get_Potapov_ABCD(Ex.roots,Ex.vecs,Ex.T,z=0.)

In [622]:
#Omega = (A-A.H)/(2j) #### closed dynamics only. i.e. not damping

In [623]:
Omega = -1j*A  ## full dynamics

In [624]:
H_lin_sp = 0
## with sympy only
for i in range(m):
    for j in range(m):
        H_lin_sp += a_H[i]*a[j]*Omega[i,j]

In [625]:
def make_nonlin_term_sp(combination,pm_arr):
    '''
    Make symbolic term
    With sympy only
    '''
    r = 1
    for index,sign in zip(combination,pm_arr):
        if sign == 1:
            r*= a_H[index]
        else:
            r *= a[index]
    return r

Let's impose a large 'index of refraction'. In the future we will replaces this by better conditions for phase-mismatch, including realistic values. For now, this will narrow the gain versus $\Delta k$ function so that few interaction terms remain.


In [626]:
def weight(combination,pm_arr):
    roots_to_use = np.array([roots[i].imag for i in combination])
    modes_to_use = [modes[i] for i in combination]
    return functions.make_nonlinear_interaction(roots_to_use, modes_to_use, delays, delay_indices,
                                start_nonlin,duration_nonlin,pm_arr,
                                indices_of_refraction = [1000.]*len(combination),
                                eps=1e-12,)

In [627]:
## TODO: add a priori check to restrict exponential growth
weights = {}

In [628]:
count = 0

for pm_arr in list_of_pm_arr:
    field_combinations = itertools.combinations_with_replacement(range(m), chi_order+1)
    for combination in field_combinations:
        count += 1
        weights[tuple(combination),tuple(pm_arr)] = weight(combination,pm_arr)        
print count


delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
delta_k is less than epsilon, approximating linearly.
420

In [629]:
plt.hist([abs(x) for x in [weights[key] for key in weights] ],bins=100);


As we see above, most of the interactions are negligible. Let's drop them out.


In [630]:
significant_weight_keys = [key for key in weights if abs(weights[key]) > 1e-4]

In [631]:
significant_weights = dict((key,weights[key]) for key in significant_weight_keys)

In [632]:
significant_weights =  {k:v for k,v in weights.iteritems() if abs(v) > 1e-4} ## more elegant

In [633]:
len(significant_weights)


Out[633]:
76

In [782]:
H_nonlin_sp = 0  ## with sympy only

for combination,pm_arr in significant_weights:
    H_nonlin_sp += make_nonlin_term_sp(combination,pm_arr)*significant_weights[combination,pm_arr]

In [807]:
H_sp = H_lin_sp + H_nonlin_sp*NONLIN_WEIGHT

In [808]:
def make_sp_conj(A):
    '''
    Returns the symbolic conjugate of A.
    Args:
        A (symbolic expression in symbols a[i] and a_H[i])
    Returns:
        The complex conjugate of A
    '''
    A_H = sp.conjugate(H_sp)
    for i in range(len(a)):
        A_H = A_H.subs(sp.conjugate(a[i]),a_H[i])
        A_H = A_H.subs(sp.conjugate(a_H[i]),a[i])
    return A_H

In [809]:
def make_eq_motion(H_sp):
    '''
    Input is a tuple or list, output is a matrix vector
    '''
    A_H = make_sp_conj(H_sp)
    diff_ls = [1j*sp.diff(H_sp,var) for var in a_H] + [-1j*sp.diff(A_H,var) for var in a]
    fs = [sp.lambdify( tuple(a+a_H),expression) for expression in diff_ls ]
    return lambda arr: (np.asmatrix([ f(* arr ) for f in fs])).T

In [810]:
A_H = sp.conjugate(H_sp)
    for i in range(len(a)):
        A_H = A_H.subs(sp.conjugate(a[i]),a_H[i])
        A_H = A_H.subs(sp.conjugate(a_H[i]),a[i])

In [811]:
eq_mot = make_eq_motion(H_sp)

In [812]:
def double_up(M1,M2=None):
    if M2 == None:
        M2 = np.zeros_like(M1)
    top = np.hstack([M1,M2])
    bottom = np.hstack([np.conj(M2),np.conj(M1)])
    return np.vstack([top,bottom])

In [813]:
A_d,C_d,D_d = map(double_up,(A,C,D))

In [814]:
B_d = -double_up(C.H)

In [815]:
def make_f(eq_mot,B,a_in):
    '''
    Nonlinear equations of motion
    '''
    return lambda t,a: np.asarray(eq_mot(a)+B*a_in(t)).T[0]

In [816]:
def make_f_lin(A,B,a_in):
    '''
    Linear equations of motion
    '''
    return lambda t,a: np.asarray(A*np.asmatrix(a).T+B*a_in(t)).T[0]

In [817]:
a_in = lambda t: np.asmatrix([1.]*4).T

In [818]:
f = make_f(eq_mot,B_d,a_in)

In [819]:
f_lin = make_f_lin(A_d,B_d,a_in)

In [820]:
eq_res = eq_mot([1.]*10)
print eq_res


[[-0.33972600-19.11352881j]
 [-0.87314689+23.3997759j ]
 [-0.42266661+11.76116304j]
 [-1.60513022 +2.79281965j]
 [-1.09514712 -7.76191194j]
 [-0.33972600+19.11352881j]
 [-0.87314689-23.3997759j ]
 [-0.42266661-11.76116304j]
 [-1.60513022 -2.79281965j]
 [-1.09514712 +7.76191194j]]

In [821]:
mat_res = A_d*np.asmatrix([1.]*10).T
print mat_res


[[-0.33972600-20.178468j  ]
 [-0.87314689+20.38977829j]
 [-0.42266661+10.69622386j]
 [-1.60513022 +0.1043035j ]
 [-1.09514712-10.4504281j ]
 [-0.33972600+20.178468j  ]
 [-0.87314689-20.38977829j]
 [-0.42266661-10.69622386j]
 [-1.60513022 -0.1043035j ]
 [-1.09514712+10.4504281j ]]

In [822]:
## compute L2 error between 
np.sqrt(sum(np.asarray(abs(eq_res - mat_res))**2))


Out[822]:
array([ 7.18115794])

In [845]:
r = ode(f).set_integrator('zvode', method='bdf')
r_lin = ode(f_lin).set_integrator('zvode', method='bdf')

In [846]:
y0 = np.asmatrix([0.]*10).T
t0=0.

In [847]:
r.set_initial_value(y0, t0)
r_lin.set_initial_value(y0, t0)


Out[847]:
<scipy.integrate._ode.ode at 0x12d9a89d0>

In [848]:
t1 = 100
dt = 0.01

In [849]:
Y = []

while r.successful() and r.t < t1:
    r.integrate(r.t+dt)
    u = a_in(r.t)
    Y.append(C_d*r.y+D_d*u)

In [850]:
Y_lin = []

while r_lin.successful() and r_lin.t < t1:
    r_lin.integrate(r_lin.t+dt)
    u = a_in(r_lin.t)
    Y_lin.append(C_d*r_lin.y+D_d*u)

In [851]:
for i in range(4):
    plt.plot([(y).real[i][0,0] for y in Y ])
    
for i in range(4):
    plt.plot([(y).real[i][0,0] for y in Y_lin ])


When Making the nonlinear terms above zero, we find agreement with the linear equtions of motion.

Using the symbolic packages is kind of slow. For classical simulations maybe we can avoid that. We just need to extract the equations of motion, which should end up being sparse in the interaction terms.

TODO: implement without sympy, e.g. with Julia

#


In [ ]:


In [ ]:


In [ ]:


In [ ]:


In [ ]:


In [ ]:

Testing different cases with make_nonlinear_interaction

making sure different exceptions get caught


In [206]:
roots_to_use = np.array([roots[i] for i in combination])
modes_to_use = [modes[i] for i in combination]

In [207]:
def call_make_non_lin():
    return functions.make_nonlinear_interaction(roots_to_use, modes_to_use, delays, delay_indices,
                            start_nonlin,duration_nonlin,pm_arr,
                            indices_of_refraction,
                            eps=1e-12,func=lambda z : z.imag)

In [208]:
call_make_non_lin()


Out[208]:
(0.0021779902481650101+0.0036737045407247908j)

In [209]:
indices_of_refraction = 1000.
call_make_non_lin()


Out[209]:
(0.0051419161593478663+0j)

In [210]:
start_nonlin = -1  ## this shouldn't happen
call_make_non_lin()


---------------------------------------------------------------------------
Exception                                 Traceback (most recent call last)
<ipython-input-210-699e1b01d691> in <module>()
      1 start_nonlin = -1  ## this shouldn't happen
----> 2 call_make_non_lin()

<ipython-input-207-65a81927434a> in call_make_non_lin()
      3                             start_nonlin,duration_nonlin,pm_arr,
      4                             indices_of_refraction,
----> 5                             eps=1e-12,func=lambda z : z.imag)

/Users/gil/Google Drive/Copy/Potapov/Potapov_4/Potapov_Code/functions.py in make_nonlinear_interaction(roots, modes, delays, delay_indices, start_nonlin, duration_nonlin, plus_or_minus_arr, indices_of_refraction, eps, func)
    281     for delay_index,start_loc in zip(delay_indices,start_nonlin):
    282         if start_loc < 0:
--> 283             raise Exception('each element of start_nonlin must be greater than 0.')
    284         if duration_nonlin + start_loc > delays[delay_index]:
    285             raise Exception('duration_nonlin + start_loc must be less than the '

Exception: each element of start_nonlin must be greater than 0.

In [211]:
start_nonlin = [1]*len(roots_to_use)
call_make_non_lin()


---------------------------------------------------------------------------
Exception                                 Traceback (most recent call last)
<ipython-input-211-4b091efebb23> in <module>()
      1 start_nonlin = [1]*len(roots_to_use)
----> 2 call_make_non_lin()

<ipython-input-207-65a81927434a> in call_make_non_lin()
      3                             start_nonlin,duration_nonlin,pm_arr,
      4                             indices_of_refraction,
----> 5                             eps=1e-12,func=lambda z : z.imag)

/Users/gil/Google Drive/Copy/Potapov/Potapov_4/Potapov_Code/functions.py in make_nonlinear_interaction(roots, modes, delays, delay_indices, start_nonlin, duration_nonlin, plus_or_minus_arr, indices_of_refraction, eps, func)
    285             raise Exception('duration_nonlin + start_loc must be less than the '
    286                            +'delay of index delay_index for start_loc in '
--> 287                            +'start_nonlin and delay_index in delay_indices.')
    288 
    289     if indices_of_refraction == None:

Exception: duration_nonlin + start_loc must be less than the delay of index delay_index for start_loc in start_nonlin and delay_index in delay_indices.

In [212]:
start_nonlin = 0.00001
duration_nonlin = .099
call_make_non_lin()


Out[212]:
(0.0050904969977543877+0j)

In [213]:
start_nonlin = [0.00001]*len(roots_to_use)
duration_nonlin = .099
call_make_non_lin()


Out[213]:
(0.0050904969977543877+0j)

Unused methods below


In [131]:
## consolidated weights do not take into account which modes are createad or annihilated.

consolidated_weightes = {}
for key in significant_weights:
    if not key[0] in consolidated_weightes:
        consolidated_weightes[key[0]] = significant_weights[key]
    else:
        consolidated_weightes[key[0]] += significant_weights[key]


Out[131]:
'\nconsolidated_weightes = {}\nfor key in significant_weights:\n    if not key[0] in consolidated_weightes:\n        consolidated_weightes[key[0]] = significant_weights[key]\n    else:\n        consolidated_weightes[key[0]] += significant_weights[key]\n'

In [ ]:
## QNET annihilation and creation operators

a_ = [Destroy(local_space('fock', namespace = str(i))) for i in range(m)]

In [ ]:
## Make linear Hamiltonian with QNET

H_lin = sum([a_[i].dag()*a_[i]*Omega[i,i] for i in range(m)])  ## with QNET
def make_nonlin_term(combination,pm_arr):
    '''
    Make symbolic term
    With QNET
    '''
    r = 1
    for index,sign in zip(combination,pm_arr):
        if sign == 1:
            r*= a_[index].dag()
        else:
            r *= a_[index]
    return r

In [ ]:
## Make nonlinear Hamiltonian in QNET 

H_nonlin = 0  ## with QNET

for combination,pm_arr in significant_weights:
    H_nonlin += make_nonlin_term(combination,pm_arr)*significant_weights[combination,pm_arr]
    
H_qnet = H_lin+H_nonlin