May 17th, 2022
basically an extended problem set on superconducting loops and quantum flux parametron devices
import numpy as np
import logging, importlib
from pyWRspice import script, simulation, remote
import matplotlib.pyplot as plt
import matplotlib.lines as mlines
%matplotlib inline
logging.basicConfig(level=logging.WARNING)
/Users/cblackburn/.pyenv/versions/3.9.1/lib/python3.9/site-packages/pandas/compat/__init__.py:97: UserWarning: Could not import the lzma module. Your installed Python is incomplete. Attempting to use lzma compression will result in a RuntimeError. warnings.warn(msg) /Users/cblackburn/.pyenv/versions/3.9.1/lib/python3.9/site-packages/paramiko/transport.py:236: CryptographyDeprecationWarning: Blowfish has been deprecated "class": algorithms.Blowfish,
Superconductors exhibit long range phase coherence because it's energetically favorable for overlapping cooper pairs to be coherent. Therefore, the wavefunction of a superconductor can be approximated as an ensemble average wavefunction
$$ \psi(\vec r) = |\psi(\vec r)|e^{i\theta(\vec r)} $$where $\theta(\vec r)$ is a scalar function of the phase of the electron pairs and $\psi(\vec r)$ is typically normalized so that $\psi^*\psi = n_c(\vec r) $ where $n_c(\vec r)$ is the effective density of cooper pairs and can usually be taken to be spatially constant.
The momentum for a single cooper in a weak magnetic field is $$\vec p = m^*\vec v_s + e^*\vec A$$ where $m^* = 2m_e$ and $e^* = 2e$ are the effectie mass and charge of the pair. Extrapolating to all pairs $$ n_c\vec p = n_c (m^*\vec v_s + e^*\vec A) = \bra{\psi}-i\hbar\nabla\ket{\psi}$$ \ $$\bra{\psi}-i\hbar\nabla\ket{\psi} \\ = \bra{\psi}-i\hbar\sqrt{n^*}e^{i\theta(\vec r)} \cdot i\nabla\theta \\ = \bra{\psi}\hbar\nabla\theta\psi \\ = \hbar\nabla\theta\psi^*\psi \\ = n_c\hbar\nabla\theta $$ \ $$ n_c\vec p = n_c\hbar\nabla\theta \\ \vec p = \hbar\nabla\theta = m^*\vec v_s + e^*\vec A $$ and in terms of the superconducting current density and London's penetration depth $$ \vec p = \hbar\nabla\theta = e^*(\Lambda\vec J_s + \vec A) \\ \Lambda = \frac{m^*}{n_ce^{*2}} $$
For a supercondcutor with a hole of non-superconducting material, shown in the image below.
The momentum around the contour C must be $$ \oint\vec p \cdot d\vec l = \hbar\oint\nabla\theta\cdot d\vec l = e_c\oint(\Lambda \vec J_s + \vec A)\cdot d\vec l $$ The closed loop integral across the phase change must be a multiple of $2\pi$ and we'll assume that the contour is deep enough into the material so the current is 0. Then the equation simplies to $$ \hbar 2n\pi = e^*\oint\vec A\cdot d\vec l \\ \hbar 2n\pi = e^*\int_s (\nabla \times\vec A) dS \\ \hbar 2n\pi = e^*\int_s \vec B \cdot d\vec S \\ \hbar 2n\pi = e^* \Phi_s $$ Using Stoke's theorem in the definition of magnetic field, we get an equation for the flux through the non-superconducting loop which is quantized. $$ \Phi_s = \frac{hn}{2e} = n\Phi_0 \qquad n = 0, 1, 2, . . . $$ and $\Phi_0$ is the basic quantum of magnetic flux described by fundamental constants $$ \Phi_0 = \frac{h}{2e} = 2.07\times 10^{-15} \text{Wb} $$
TO DO: add some analysis on JJs?
When a Josephson junction is added to a superconducting loop, there is interference between the wavefunctions across the junction and the flux is generally no longer quantized.
Solve for the phase difference accross the junction by integrating the momentum across the path within the superconducting.
$$ \int^B_A \nabla\theta\cdot d\vec l = \oint\nabla\theta\cdot d\vec l - \int^A_B\nabla\theta\cdot d\vec l \\ = \frac{2e}{\hbar}\oint\vec A\cdot d\vec l - \frac{2e}{\hbar}\int^A_B\vec A\cdot d\vec l $$Assuming that the loop is deep enough for the current density to be 0. Using Stoke's law as before to convert the integral into a flux, the equation simplifies to $$ 2\pi n + \theta_A - \theta_B = \frac{2\pi\Phi}{\Phi_0} - \frac{2e}{\hbar}\int^A_B\vec A \cdot d\vec l $$
The gauge invariant phase accross a josephson junction is defined as
$$ \phi = \theta_2 - \theta_1 + \frac{2e}{\hbar}\int^2_1\vec A(x, t) \cdot d\vec l $$Therefore, rearanging the loop gradient, we can find the phase difference accross the loop to be described by $$ \phi = 2\pi n - \frac{2\pi\Phi}{\Phi_0} $$
Then using the Josephson current relation to find the current through the loop $$ I = I_c\sin\phi = I_c\sin\left(2\pi n - \frac{2\pi\Phi}{\Phi_0}\right) \\ I = -I_c\sin\left(\frac{2\pi\Phi}{\Phi_0}\right) $$ The flux through the loop is made up of an externally applied flux, $\Phi_{ext}$, plus the reaction flux produced by the current induced in the loop from the external flux, which can be expressed as the loop inductance times the induced current $$ \Phi = \Phi_{ext} + LI $$ Therefore, we can get the following implicit relation for the current in the loop $$ I = -I_c\sin\left(\frac{2\pi\Phi_{ext}}{\Phi_0} + \frac{\beta_L I}{I_c}\right) \\ \beta_L = \frac{2\pi LI_c}{\Phi_0} $$ and similarly the implicit relation for the flux in a loop, normalized by the flux quantum $$ \frac{\Phi}{\Phi_0} = \frac{\Phi_{ext}}{\Phi_0} - \frac{\beta_L}{2\pi}\sin\left(\frac{2\pi\Phi}{\Phi_0}\right) $$
plotting these:
I_c = 50e-6 # 50uA from MITLL fab
phi_0 = 2.07e-15
L_1 = 5e-12 # 1 fH range gives beta<1
beta_1 = 2*np.pi*L_1*I_c/phi_0
L_2 = 20e-12 # 1 fH range gives beta<1
beta_2 = 2*np.pi*L_2*I_c/phi_0
phi_ext_range = np.linspace(0, 3, 100)
phi_range = np.linspace(0, 3, 100)
phi_ext, phi = np.meshgrid(phi_ext_range, phi_range)
X = phi
Y_1 = phi_ext - (beta_1/(2*np.pi))*np.sin(2*np.pi*phi)
Y_2 = phi_ext - (beta_2/(2*np.pi))*np.sin(2*np.pi*phi)
plt.contour(phi_ext, phi, (X - Y_1), [0], colors='red')
plt.contour(phi_ext, phi, (X - Y_2), [0], colors='purple')
plt.plot(phi_ext_range, phi_range)
plt.xlabel(r"$\Phi_{ext}/\Phi_0$")
plt.ylabel(r"$\Phi/\Phi_0$")
blue = mlines.Line2D([], [], color='blue', marker="_", linestyle='None', label=r'$\Phi = \Phi_{ext}$')
red = mlines.Line2D([], [], color='red',marker="_", linestyle='None', label=r"$\beta_L$ = %0.3f" % beta_1)
purple = mlines.Line2D([], [], color='purple', marker="_", linestyle='None', label=r"$\beta_L$ = %0.3f" % beta_2)
plt.legend(handles=[blue, red, purple])
plt.show()
The flux through the loop oscillates as a function of the changing external flux. When $\beta_L < 1$ there is one possible flux for each external field, but when $\beta_L > 1$ the curves are re-entrant so there is a hysteretic loop between discontinuous transitions - the portions of the curve with negative slope are unstable so as the external flux is varied, the flux through the loop will follow the path with positive slope. This is what is exploited in a RF SQUID Magnetometer: an applied RF field drives the loop through a hysteretic lossy path (each period traps and releases a single flux quanta so the energy dissipation per cycle is $\Phi_0 I_c$), and the amount of RF flux needed to drive the path depends on a quasistatic external flux that is being detected.
This plot also demontrates that the flux in the loop is always equal to the external flux when $\Phi_{ext}/\Phi_0 = n/2$, regardless of $\beta_L$.
I_c = 50e-6 # 50uA from MITLL fab
phi_0 = 2.07e-15
L_1 = 5e-12 # 1 fH range gives beta<1
beta_1 = 2*np.pi*L_1*I_c/phi_0
L_2 = 20e-12 # 1 fH range gives beta<1
beta_2 = 2*np.pi*L_2*I_c/phi_0
L_3 = 50e-12 # 1 fH range gives beta<1
beta_3 = 2*np.pi*L_3*I_c/phi_0
phi_ext_range = np.linspace(-1, 3, 100)
curr_range = np.linspace(-1.2, 1.2, 100)
phi_ext, curr = np.meshgrid(phi_ext_range, curr_range)
X = curr
Y_1 = -np.sin(2*np.pi*phi_ext + beta_1*curr)
Y_2 = -np.sin(2*np.pi*phi_ext + beta_2*curr)
Y_3 = -np.sin(2*np.pi*phi_ext + beta_3*curr)
plt.contour(phi_ext, curr, (X - Y_1), [0], colors='red')
#plt.contour(phi_ext, curr, (X - Y_2), [0], colors='purple')
plt.contour(phi_ext, curr, (X - Y_3), [0], colors='blue')
plt.xlabel(r"$\Phi_{ext}/\Phi_0$")
plt.ylabel(r"$I/I_c$")
red = mlines.Line2D([], [], color='red',marker="_", linestyle='None', label=r"$\beta_L$ = %0.3f" % beta_1)
#purple = mlines.Line2D([], [], color='purple', marker="_", linestyle='None', label=r"$\beta_L$ = %0.3f" % beta_2)
blue = mlines.Line2D([], [], color='blue', marker="_", linestyle='None', label=r"$\beta_L$ = %0.3f" % beta_3)
plt.legend(handles=[blue, red, purple])
plt.show()
The plot above shows the current through the loop for varying external flux and different $\beta_L$. The current always remains less than the critical current in magnitude. And again, the current is re-entrant and nonre-entrant when $\beta_L$ is greater than or less than 1 respectively. The stable states for the current can be found by minimizing the energy of the circuit.
Alternatively, we can think about the superconducting loop and Josephson junction geometry as it's equivalent circuit
And defining a generalized flux angle, which can be described
putting this in WRspice to compare current
loop_JJ = '''* Transient response of current driven loop with Josephson Junction
.tran 50p 100n
B0 1 0 jj1 area=0.05
L1 1 0 1.3f
*R1 2 0 1
* Pulse current source
I1 0 1 pulse(0 15u 1n 1n 1n 20n)
.model jj1 jj(rtype=1, cct=1, icon=10m, vg=2.8m, delv=0.08m,
+ icrit=1m, vshunt=0.5mV cap=1.31p)
.save @I1[c]
.control
run
set filetype=binary
write {output_file} @I1[c] i(L1)
.endc
'''
engine = simulation.WRWrapper(command = "wrspice")
dat1 = engine.run(loop_JJ)
df = dat1.to_array()
ts = df[0]
i = df[1]
iL = df[2]
#iJJ = df[2]
# Plot the data
fig = plt.figure(figsize=(12,6))
plt.plot(ts*1e9, i, label="I")
plt.plot(ts*1e9, iL, label="iL")
plt.xlabel("Time [ns]")
#plt.ylabel("Voltage [V]")
plt.legend()
plt.show()
Simulating QFP buffer in wrSPICE through pyWRspice
QFP = ''' * QFP buffer
.subckt qfp_buf CLKIN CLKOUT DATAIN DATAOUT
B0 9 0 jj1 area=0.05
B1 11 0 jj1 area=0.05
K1 L3 L6 0.3
K2 L2 L5 0.3
K3 L8 L11 0.3
L0 DATAIN 1 1p
L5 9 1 1.3p
L6 1 11 1.3p
L7 1 12 1p
L8 12 14 8.4p
L10 14 0 500f
L1 CLKIN 6 2.5p
L2 6 7 1.3p
L3 7 8 1.3p
L4 8 CLKOUT 1.2p
R0 13 0 1p
L9 15 13 500f
L11 16 15 11p
L12 DATAOUT 16 500f
.model jj1 jj(rtype=1, cct=1, icon=10m, vg=2.8m, delv=0.08m,
+ icrit=1m, vshunt=0.5mV cap=1.31p)
.ends qfp_buf
* data input
I1 0 1 pulse(0 15u 200p 10f 10f 800p 1600p)
*activation input
I2 0 100 pulse(0 3m 200p 200p 200p 600p 1200p)
I3 0 200 pulse(0 3m 800p 200p 200p 600p 1200p)
I4 0 300 pulse(0 3m 1.4n 200p 200p 600p 1200p)
X0 100 101 1 2 qfp_buf
X1 200 201 2 3 qfp_buf
X2 300 301 3 4 qfp_buf
* close out clocks
R1 101 0 1
R2 201 0 1
R3 301 0 1
* read out buffer
X400 400 0 4 0 qfp_buf
.tran 10p 12n
.save @I1[c]
.save @I2[c]
.save @I3[c]
.save @I4[c]
.control
run
set filetype=binary
write {output_file} @I1[c] @I2[c] @I3[c] @I4[c] i(L0.X400)
.endc
'''
dat2 = engine.run(QFP)
df = dat2.to_array()
ts = df[0]
Iin = df[1]
Ia1 = df[2]
Ia2 = df[3]
Ia3 = df[4]
iL = df[5]
WRspice ERROR when running: /var/folders/gx/16lxjnvd27s0q12g7rz5drmh0000gn/T/tmpiibifmh7/tmp_script_1f9f3ece-8945-4167-a8bc-c24dc8e29a1e.cir Warning: R0.X400: resistance reset to 1e-06 by gmax limiting. Warning: R0.X2: resistance reset to 1e-06 by gmax limiting. Warning: R0.X1: resistance reset to 1e-06 by gmax limiting. Warning: R0.X0: resistance reset to 1e-06 by gmax limiting.
# Plot the data
fig, axs = plt.subplots(5, 1, figsize=(12,8))
axs[0].plot(ts*1e9, Iin, label="I input")
axs[0].set_ylabel('I in (A)')
axs[1].plot(ts*1e9, Ia1, label="I activation 1")
axs[1].set_ylabel('act1 (A)')
axs[2].plot(ts*1e9, Ia2, label="I activation 2")
axs[2].set_ylabel('act2 (A)')
axs[3].plot(ts*1e9, Ia3, label="I activation 3")
axs[3].set_ylabel('act3 (A)')
axs[4].plot(ts*1e9, iL, label="iL")
axs[4].set_ylabel('output (A)')
axs[4].set_xlabel('time')
plt.show()
pulse(v1 v2 [td tr tf pw per td1 td2 ...]):