Symbolic Analysis and Simulation¶
Symbolic Analysis of the Pseudo NAND gate and the Pseudo NAND SR-Latch¶
Pseudo NAND gate¶
In[1]:
from qnet.algebra.circuit_algebra import *
In[2]:
from qnet.circuit_components import pseudo_nand_cc as nand
# real parameters
kappa = symbols('kappa', positive = True)
Delta, chi, phi, theta = symbols('Delta, chi, phi, theta', real = True)
# complex parameters
A, B, beta = symbols('A, B, beta')
N = nand.PseudoNAND('N', kappa=kappa, Delta=Delta, chi=chi, phi=phi, theta=theta, beta=beta)
N
Out[2]:
Circuit Analysis of Pseudo NAND gate¶
In[3]:
N.creduce()
Out[3]:
In[4]:
# yields a single block
N.show()
# decompose into sub components
N.creduce().show()
SLH model¶
In[5]:
NSLH = N.coherent_input(A, B, 0, 0).toSLH()
NSLH
Out[5]:
Heisenberg equation of motion of the mode operator \(a\)¶
In[6]:
s = N.space
a = Destroy(s)
a
Out[6]:
In[7]:
NSLH.symbolic_heisenberg_eom(a).expand().simplify_scalar()
Out[7]:
Super operator algebra: The system’s liouvillian and a re-derivation of the eom for \(a\) via the super-operator adjoint of the liouvillian.¶
In[8]:
LLN = NSLH.symbolic_liouvillian().expand().simplify_scalar()
LLN
Out[8]:
In[9]:
(LLN.superadjoint() * a).expand().simplify_scalar()
Out[9]:
A full Pseudo-NAND SR-Latch¶
In[10]:
N1 = nand.PseudoNAND('N_1', kappa=kappa, Delta=Delta, chi=chi, phi=phi, theta=theta, beta=beta)
N2 = nand.PseudoNAND('N_2', kappa=kappa, Delta=Delta, chi=chi, phi=phi, theta=theta, beta=beta)
# NAND gates in mutual feedback configuration
NL = (N1 + N2).feedback(2, 4).feedback(5, 0).coherent_input(A, 0, 0, B, 0, 0)
NL
Out[10]:
The circuit algebra simplification rules have already eliminated one of the two feedback operations in favor or a series product.
In[11]:
NL.show()
NL.creduce().show()
NL.creduce().creduce().show()
SLH model¶
In[12]:
NLSLH = NL.toSLH().expand().simplify_scalar()
NLSLH
Out[12]:
Heisenberg equations of motion for the mode operators¶
In[13]:
NL.space
Out[13]:
In[14]:
s1, s2 = NL.space.operands
a1 = Destroy(s1)
a2 = Destroy(s2)
In[15]:
da1dt = NLSLH.symbolic_heisenberg_eom(a1).expand().simplify_scalar()
da1dt
Out[15]:
In[16]:
da2dt = NLSLH.symbolic_heisenberg_eom(a2).expand().simplify_scalar()
da2dt
Out[16]:
Show Exchange-Symmetry of the Pseudo NAND latch Liouvillian super operator¶
Simultaneously exchanging the degrees of freedom and the coherent input amplitudes leaves the liouvillian unchanged.
In[17]:
C = symbols('C')
LLNL = NLSLH.symbolic_liouvillian().expand().simplify_scalar()
LLNL
Out[17]:
In[18]:
C = symbols('C')
(LLNL.substitute({A:C}).substitute({B:A}).substitute({C:B}) - LLNL.substitute({s1:s2,s2:s1}).expand().simplify_scalar()).expand().simplify_scalar()
Out[18]:
Numerical Analysis via QuTiP¶
Input-Output Logic of the Pseudo-NAND Gate¶
In[19]:
NSLH.space
Out[19]:
In[20]:
NSLH.space.dimension = 75
Numerical parameters taken from
Mabuchi, H. (2011). Nonlinear interferometry approach to photonic sequential logic. Appl. Phys. Lett. 99, 153103 (2011)
In[21]:
# numerical values for simulation
alpha = 22.6274 # logical 'one' amplitude
numerical_vals = {
beta: -34.289-11.909j, # bias input for pseudo-nands
kappa: 25., # Kerr-Cavity mirror couplings
Delta: 50., # Kerr-Cavity Detuning
chi : -50./60., # Kerr-Non-Linear coupling coefficient
theta: 0.891, # pseudo-nand beamsplitter mixing angle
phi: 2.546, # pseudo-nand corrective phase
}
In[22]:
NSLHN = NSLH.substitute(numerical_vals)
NSLHN
Out[22]:
In[23]:
input_configs = [
(0,0),
(1, 0),
(0, 1),
(1, 1)
]
In[24]:
Lout = NSLHN.L[2,0]
Loutqt = Lout.to_qutip()
times = arange(0, 1., 0.01)
psi0 = qutip.basis(N.space.dimension, 0)
datasets = {}
for ic in input_configs:
H, Ls = NSLHN.substitute({A: ic[0]*alpha, B: ic[1]*alpha}).HL_to_qutip()
data = qutip.mcsolve(H, psi0, times, Ls, [Loutqt], ntraj = 1)
datasets[ic] = data.expect[0]
100.0% (1/1) Est. time remaining: 00:00:00:00
100.0% (1/1) Est. time remaining: 00:00:00:00
100.0% (1/1) Est. time remaining: 00:00:00:00
100.0% (1/1) Est. time remaining: 00:00:00:00
In[25]:
figure(figsize=(10, 8))
for ic in input_configs:
plot(times, real(datasets[ic])/alpha, '-', label = str(ic) + ", real")
plot(times, imag(datasets[ic])/alpha, '--', label = str(ic) + ", imag")
legend()
xlabel('Time $t$', size = 20)
ylabel(r'$\langle L_out \rangle$ in logic level units', size = 20)
title('Pseudo NAND logic, stochastically simulated time \n dependent output amplitudes for different inputs.', size = 20)
Out[25]:
<matplotlib.text.Text at 0x1100b7dd0>
Pseudo NAND latch memory effect¶
In[26]:
NLSLH.space
Out[26]:
In[27]:
s1, s2 = NLSLH.space.operands
s1.dimension = 75
s2.dimension = 75
NLSLH.space.dimension
Out[27]:
5625
In[28]:
NLSLHN = NLSLH.substitute(numerical_vals)
NLSLHN
Out[28]:
In[29]:
input_configs = {
"SET": (1, 0),
"RESET": (0, 1),
"HOLD": (1, 1)
}
models = {k: NLSLHN.substitute({A:v[0]*alpha, B:v[1]*alpha}).HL_to_qutip() for k, v in input_configs.items()}
In[30]:
a1, a2 = Destroy(s1), Destroy(s2)
observables = [a1.dag()*a1, a2.dag()*a2]
observables_qt = [o.to_qutip(full_space = NLSLH.space) for o in observables]
In[31]:
def model_sequence_single_trajectory(models, durations, initial_state, dt):
"""
Solve a sequence of constant QuTiP open system models (H_i, [L_1_i, L_2_i, ...])
via Quantum Monte-Carlo. Each model is valid for a duration deltaT_i and the initial state for
is given by the previous model's final state.
The function returns an array with the times and an array with the states at each time.
:param models: Sequence of models given as tuples: (H_j, [L1j,L2j,...])
:type models: Sequence of tuples
:param durations: Sequence of times
:type durations: Sequence of float
:param initial_state: Overall initial state
:type initial_state: qutip.Qobj
:param dt: Sampling interval
:type dt: float
:return: times, states
:rtype: tuple((numpy.ndarray, numpy.ndarray)
"""
totalT = 0
totalTimes = array([])
totalStates = array([])
current_state = initial_state
for j, (model, deltaT) in enumerate(zip(models, durations)):
print "Solving step {}/{} of model sequence".format(j + 1, len(models))
HQobj, LQObjs = model
times = arange(0, deltaT, dt)
data = qutip.mcsolve(HQobj, current_state, times, LQObjs, [], ntraj = 1, options = qutip.Odeoptions(gui = False))
# concatenate states
totalStates = np.hstack((totalStates,data.states.flatten()))
current_state = data.states.flatten()[-1]
# concatenate times
totalTimes = np.hstack((totalTimes, times + totalT))
totalT += times[-1]
return totalTimes, totalStates
In[32]:
durations = [.5, 1., .5, 1.]
model_sequence = [models[v] for v in ['SET', 'HOLD', 'RESET', 'HOLD']]
initial_state = qutip.tensor(qutip.basis(s1.dimension, 0), qutip.basis(s2.dimension, 0))
In[33]:
times, data = model_sequence_single_trajectory(model_sequence, durations, initial_state, 5e-3)
Solving step 1/4 of model sequence
100.0% (1/1) Est. time remaining: 00:00:00:00
Solving step 2/4 of model sequence
100.0% (1/1) Est. time remaining: 00:00:00:00
Solving step 3/4 of model sequence
100.0% (1/1) Est. time remaining: 00:00:00:00
Solving step 4/4 of model sequence
100.0% (1/1) Est. time remaining: 00:00:00:00
In[34]:
datan1 = qutip.expect(observables_qt[0], data)
datan2 = qutip.expect(observables_qt[1], data)
In[36]:
figsize(10,6)
plot(times, datan1)
plot(times, datan2)
for t in cumsum(durations):
axvline(t, color = "r")
xlabel("Time $t$", size = 20)
ylabel("Intra-cavity Photon Numbers", size = 20)
legend((r"$\langle n_1 \rangle $", r"$\langle n_2 \rangle $"), loc = 'lower right')
title("SET - HOLD - RESET - HOLD sequence for $\overline{SR}$-latch", size = 20)
Out[36]:
<matplotlib.text.Text at 0x1121d8990>