Overview

This is the first page of QCircuit project documentation. The python based code for superconducting qubits development.

Installation

These scripts can be downloaded from

https://github.com/ooovector/QCircuit

Examples

Here you can read the simplest examples of QCircuit usage

Flux qubit

The Hamiltonian

Transmon qubit

_images/Transmon_qubit.png

The transmon qubit can be described in charge basis by the Hamiltonian

\[H=4E_\text{C}(\hat{n}-n_g)^2+\frac{1}{2}E_\text{J}\sum_n(|n\rangle\langle n+1|+\text{h.c.}),\]

and in phase basis by

\[H=4E_\text{C}(\hat{n}-n_g)^2+\frac{1}{2}E_\text{J}\sum_n(|n\rangle\langle n+1|+\text{h.c.}),\]

Defining circuit topology

The circuit construction:

  Transmon = qubit.Circuit()
  Transmon.add_element(qubit.JosephsonJunction('JJ1'), ['GND', '1'])
  Transmon.add_element(qubit.JosephsonJunction('JJ2'), ['1', '2'])
  Transmon.add_element(qubit.Capacitance('Cq'), ['GND', '1'])

give

Functions

Here you can read an explanation of the functions implemented in Circuit library.

The main class object which should be created at the beginning is a Circuit class:

Node1 = QCircuit.Variable()

If return values aren’t specified then nothing is returned.

The element classes:

class circuit.Variable(name)

Represents a variable of the circuit wavefunction or an constant external bias flux or voltage.

create_grid(nodeNo, phase_periods, centre=0)

Creates a discrete grid for wavefunction variables.

Parameters
  • nodeNo (int) – number of discrete points on the grid.

  • phase_periods (float) – number of \(2\pi\) intervals in the grid.

  • centre (float) – additional flux offset.

set_parameter(phase_value, charge_value)

Sets an external flux and/or charge bias.

Parameters
  • phase_value (float.) – external flux bias in \(\Phi_{0}/2\pi\).

  • charge_value (float.) – external charge bias in cooper pairs

The Hamiltonian parameter definitions

Here you can read an explanation of the Hamiltonian parameter definitions in the Circuit library.

\[\begin{split}E_{J}&=\frac{\hbar I_C}{2e} \text{ is a Josephson junction energy,}\\ E_{C}&=\frac{e^2}{2C} \text{ is a charge energy,}\\ E_{L}&=(\frac{\hbar}{2e})^2\frac{1}{2L} \text{ is an inductor energy,}\\ \Phi_x &= 2\pi\frac{\Phi}{\Phi_0} = \frac{2e \Phi}{\hbar} \text{ is an external applied flux, in units of radians.}\end{split}\]

To simplify these definitions, \(2e\) and \(\hbar\) were equated to unity, giving:

\[\begin{split}&I_C=E_{J} \text{ Josephson junction critical current = Josephson junction energy}\\ &С=\frac{1}{8 E_{C}} \text{ is a capacity,}\\ &L=\frac{1}{2 E_{L}} \text{ is an inductance,}\\ &\Phi_x = \Phi \text{ is an external applied flux, in units of radians}\end{split}\]

Which means if you want to specify the Josephson junction energy you should set critical current in GHz.

Transmon qubit

This is the first page of QCircuit usage explanation.

[1]:
import sys
sys.path.insert(0,r"E:\Github\scqubits")

Initialization

[2]:
%matplotlib inline
%config InlineBackend.figure_format = 'svg'
import matplotlib.pyplot as plt
import scqubits as qubit
import numpy as np
import tqdm

Example: Transmon qubit

To illustrate basic functionality built into QCircuit, we consider the implementation of the transmon qubit as an example.

Defining circuit topology

First step is to define a circuit topology.

[3]:
Transmon = qubit.Circuit()
Transmon.add_element(qubit.JosephsonJunction('JJ1'), ['GND', '1'])
Transmon.add_element(qubit.JosephsonJunction('JJ2'), ['1', '2'])
Transmon.add_element(qubit.Capacitance('Cq'), ['GND', '1'])

Which has the following circuit schematic

[4]:
import SchemDraw as schem
import SchemDraw.elements as e
[5]:
d = schem.Drawing()
d.add(e.CAP, d='up', l=d.unit, label='$C_q$')
d.add(e.LINE,d='right', l=d.unit)
E1=d.add(e.DOT,label='1')
E2=d.add(e.JJ, d='down', l=d.unit, label='$I_{c_1}$')
E6=d.add(e.GND,d='right')
E5=d.add(e.LINE,xy=E1.end,d='right', l=d.unit)
E3=d.add(e.JJ, d='down', l=d.unit, botlabel='$I_{c_2}$')
E4=d.add(e.DOT,botlabel='2',d='down')
d.loopI([E5,E3,E4,E2], d='cw',theta1=330, theta2=-30, label='$\Phi$',pad=0.3)
d.add(e.LINE,xy=E4.end,d='left',ls='--')
d.add(e.LINE,d='left')
d.draw()
_images/ipynb_transmon_qubit_9_0.svg

Parametrizing Hamiltonian

  1. Specify the node variables, which can be used either to calculate wavefunctions or to set the external flux, voltage values

[6]:
phi1 = qubit.Variable('φ1')
f = qubit.Variable('f')
ng = qubit.Variable('ng')

here phi1 is a variable used for Hamiltonian parametrization, f - external flux trough the transmon’s SQUID and ng - charge offset

The method used for calculating qubit parameters based on numerical solution of the Schrödinger equation, which is a second-order differential equation. To solve it the grid for the “computational variables” should be specified and by specifing the grid it’s shown that this variable will be used for computation

[7]:
phi1.create_grid(32,1)
Transmon.add_variable(phi1)
Transmon.add_variable(f)
Transmon.add_variable(ng)

Mapping nodal phases to variables

The most important step is to…

which works in a following way:
**A*x=b**, b is a node vector=[‘GND’, ‘1’, ‘2’]
x is a variable vector= [‘ϕ1’,’f’,’ng’] and A describes the relationship between them
f - external flux, and should be specified as an additional node and a variable. In our case it equals to the second node
ng - charge offset. (‘GND’ node)
[9]:
Transmon.map_nodes_linear(['GND', '1' , '2'],
                          ['φ1', 'f','ng' ],
                          np.asarray([[0,0,1],
                                      [1,0,0],
                                      [0,1,1]]))

check that everything was specified correctly

[12]:
import sympy
Cq, E_J1, E_J2 = \
    sympy.symbols('Cq, E_J1, E_J2')
Transmon.find_element('Cq').set_capacitance(Cq)
Transmon.find_element('JJ1').set_critical_current(E_J1)
Transmon.find_element('JJ2').set_critical_current(E_J2)
[13]:
sympy.nsimplify(Transmon.symbolic_lagrangian())
[13]:
$\displaystyle - E_{J1} \left(1 - \cos{\left(ng - φ1 \right)}\right) - E_{J2} \left(1 - \cos{\left(f + ng - φ1 \right)}\right) + \partial_tng \left(\frac{Cq \partial_tng}{2} - \frac{Cq \partial_tφ1}{2}\right) + \partial_tφ1 \left(- \frac{Cq \partial_tng}{2} + \frac{Cq \partial_tφ1}{2}\right)$
[14]:
sympy.nsimplify(Transmon.symbolic_hamiltonian())
[14]:
$\displaystyle E_{J1} \left(1 - \cos{\left(ng - φ1 \right)}\right) + E_{J2} \left(1 - \cos{\left(f + ng - φ1 \right)}\right) + \frac{\partial_tng \left(Cq \partial_tng - i \partial_{φ1}\right)}{2} - \frac{i \partial_{φ1} \left(\partial_tng - \frac{i \partial_{φ1}}{Cq}\right)}{2}$

Eigenvalues using circuit tools

number of states should be less than grid discretization:
create_grid(num,period)
num-1>num_states
[19]:
import tqdm
[16]:
voltage_steps = 16
flux_steps=100
num_states=30
energies = np.zeros((voltage_steps,num_states), dtype=np.float)
Ej1 = 15
Ej2 = 5
Ec = 0.200
Transmon.find_element('JJ1').set_critical_current(Ej1)
Transmon.find_element('JJ2').set_critical_current(Ej2)
Transmon.find_element('Cq').set_capacitance(1/(8*Ec))
f_value = 0
ng_value = 0

for ng_id, ng_value in tqdm.tqdm(enumerate(np.linspace(-2, 2, voltage_steps)/8*Ec)):
    f.set_parameter(f_value, 0)
    ng.set_parameter(0, ng_value)
    Transmon.calculate_potentials()
    [eigenenergies, eigenfunctions] = Transmon.diagonalize_phase(num_states=num_states)
    energies[ng_id, :] = eigenenergies
0it [00:00, ?it/s]C:\Users\Ivan\Anaconda3\lib\site-packages\ipykernel_launcher.py:21: ComplexWarning: Casting complex values to real discards the imaginary part
16it [00:00, 65.34it/s]
[18]:
plt.plot(energies[:,:4])
[18]:
[<matplotlib.lines.Line2D at 0x16b7c0f5ba8>,
 <matplotlib.lines.Line2D at 0x16b7c156908>,
 <matplotlib.lines.Line2D at 0x16b7c156a58>,
 <matplotlib.lines.Line2D at 0x16b7c156ba8>]
_images/ipynb_transmon_qubit_27_1.svg
[17]:
plt.plot((energies[:,1]-energies[:,0]))
plt.plot((energies[:,2]-energies[:,1]))
[17]:
[<matplotlib.lines.Line2D at 0x16b7bbc2208>]
_images/ipynb_transmon_qubit_28_1.svg
[22]:
flux_steps=100
num_states=30
energies = np.zeros((flux_steps,num_states), dtype=np.float)
Ej1 = 15
Ej2 = 5
Ec = 0.200
Transmon.find_element('JJ1').set_critical_current(Ej1)
Transmon.find_element('JJ2').set_critical_current(Ej2)
Transmon.find_element('Cq').set_capacitance(1/(8*Ec))
f_value = 0
ng_value = 0

for f_id, f_value in tqdm.tqdm(enumerate(np.linspace(-np.pi, np.pi, flux_steps))):
    f.set_parameter(f_value, 0)
    ng.set_parameter(0, ng_value)
    Transmon.calculate_potentials()
    [eigenenergies, eigenfunctions] = Transmon.diagonalize_phase(num_states=num_states)
    energies[f_id, :] = eigenenergies
0it [00:00, ?it/s]C:\Users\Ivan\Anaconda3\lib\site-packages\ipykernel_launcher.py:18: ComplexWarning: Casting complex values to real discards the imaginary part
100it [00:00, 170.25it/s]
[23]:
plt.plot(energies[:,:4])
[23]:
[<matplotlib.lines.Line2D at 0x16b7c1a27f0>,
 <matplotlib.lines.Line2D at 0x16b7c1db400>,
 <matplotlib.lines.Line2D at 0x16b7c1db550>,
 <matplotlib.lines.Line2D at 0x16b7c1db6a0>]
_images/ipynb_transmon_qubit_30_1.svg
[24]:
plt.plot((energies[:,1]-energies[:,0]))
plt.plot((energies[:,2]-energies[:,1]))
[24]:
[<matplotlib.lines.Line2D at 0x16b7c20eb38>]
_images/ipynb_transmon_qubit_31_1.svg
[ ]: