\(C^3\) - An integrated tool-set for control, calibration and characterization¶
The \(C^3\) software package provides tools to simulate and interact with experiments to perform common control and characterization tasks. Modules can be used individually or combined to achieve a certain goal. The main focus are three optimizations:
\(C_1\) Open-loop optimal control: Given a model, find the pulse shapes which maximize fidelity with a target operation.
\(C_2\) Closed-loop calibration: Given pulses, calibrate their parameters to maximize a figure of merit measured by the actual experiment, thus improving beyond the limits of a deficient model.
\(C_3\) Model learning: Given control pulses and their experimental measurement outcome, optimize model parameters to best reproduce the results.
When combined in sequence, these three procedures represent a recipe for system characterization.
Note: This documentation is work-in-progress.
Introduction to \(C^3\) Toolset¶
In this section, we go over the foundational components and concepts in \(C^3\) with the
primary objective of understanding how the different sub-modules inside the c3-toolset
are structured, the purpose they serve and how to tie them together into a complete Automated
Quantum Device Bring-up workflow. For more detailed examples of how to use the c3-toolset
to perform a specific Quantum Control task, please check out the Setup of a two-qubit chip with C^3 or the
Simulated calibration sections or refer to the API Documentation for descriptions of
Classes and Functions.
The Building Blocks¶
There are three basic building blocks that form the foundation of all the modelling and calibration
tasks one can perform using c3-toolset
, and depending on the use-case, some or all of these
blocks might be useful. These are the following:
Quantum Device Model¶
A theoretical Physics-based model of the Quantum Processing Unit. This is encapsulated by the
Model
class which consists of objects from the chip
and tasks
library.
chip
contains Hamiltonian models of different kinds of qubit realisations, along with
their couplings while tasks
let you perform common operations such as qubit initialisation or
readout. A typical Model
object would contain objects encapsulating qubits along with their
interactions as drive lines and tasks, if any.
Classical Control Electronics¶
A digital twin of the electronic control stack associated with the Quantum Processing Unit. The
Generator
class contains the required encapsulation in the form of devices
which
help model the behaviour of the classical control electronics taking account of their imperfections and
physical realisations. The devices e.g, an LO or an AWG or a Mixer are wired together in the
Generator
object to form a complete representation of accessory electronics.
Instructions¶
Once there is a software model for the QPU and the control electronics, one would need to define
Instructions or operations to be perform on this device. For gate-based quantum computing , this is
in the form of gates and their underlying pulse operations. Pulse shapes are described through a
Envelope
along with a Carrier
, which are then wrapped up in the form of Instruction
objects. The sequence in which these gates are applied are not defined at this stage.
Warning
Components inside the c3/generator/
and c3/signal/
sub-modules will be restructured
in an upcoming release to be more consistent with how the Model
class encapsulates smaller
blocks present in the c3/libraries
sub-module.
Parameter Map¶
The ParameterMap
helps to obtain an optimizable vector of parameters from the various theoretical
models previously defined. This allows for a simple interface to the optimization algorithms which are tasked
with optimizing different sets of variables used to define some entity, e.g, optimizing pulse parameters by
calibrating on hardware or providing an optimal gate-set through model-based quantum control.
Experiments¶
With the building blocks in place, we can bring them all together through an Experiment
object that
encapsulates the device model, the control signals, the instructions and the parameter map. Note that depending on
the use only some of the blocks are essential when building the experiment.
Optimizers¶
At its core, c3-toolset
is an optimization framework and all of the three steps - Open-Loop, Calibration and
Model Learning can be defined as a optimization task. The optimizers
contain classes that provide
helpful encapsulation for these steps. These objects take as arguments the previously defined Experiment
and
ParameterMap
objects along with an algorithm
e.g, CMA-eS
or L-BFGS
which performs
the iterative optimization steps.
Libraries¶
The c3/libraries
sub-module includes various helpful library of components that are used somewhat like lego
pieces when building the bigger blocks, e.g, hamiltonians
for the chip
present in the Model
or envelopes
defining a control pulse
. More details about these components are available in the
Libraries package section.
Parameter handling¶
The tool within \(C^3\) to manipulate the parameters of both the
model and controls is the ParameterMap
. It provides methods to
present the same data for human interaction, i.e. structured information
with physical units and for numerical optimization algorithms that
prefer a linear vector of scale 1. Here, we’ll show some example usage.
We’ll use the ParameterMap
of the model also used in the simulated
calibration example.
from single_qubit_blackbox_exp import create_experiment
exp = create_experiment()
pmap = exp.pmap
The pmap contains a list of all parameters and their values:
pmap.get_full_params()
{'Q1-freq': 5.000 GHz 2pi,
'Q1-anhar': -210.000 MHz 2pi,
'Q1-temp': 0.000 K,
'init_ground-init_temp': -3.469 aK,
'resp-rise_time': 300.000 ps,
'v_to_hz-V_to_Hz': 1.000 GHz/V,
'id[0]-d1-no_drive-amp': 1.000 V,
'id[0]-d1-no_drive-delta': 0.000 V,
'id[0]-d1-no_drive-freq_offset': 0.000 Hz 2pi,
'id[0]-d1-no_drive-xy_angle': 0.000 rad,
'id[0]-d1-no_drive-sigma': 5.000 ns,
'id[0]-d1-no_drive-t_final': 7.000 ns,
'id[0]-d1-carrier-freq': 5.050 GHz 2pi,
'id[0]-d1-carrier-framechange': 5.933 rad,
'rx90p[0]-d1-gauss-amp': 450.000 mV,
'rx90p[0]-d1-gauss-delta': -1.000 ,
'rx90p[0]-d1-gauss-freq_offset': -50.500 MHz 2pi,
'rx90p[0]-d1-gauss-xy_angle': -444.089 arad,
'rx90p[0]-d1-gauss-sigma': 1.750 ns,
'rx90p[0]-d1-gauss-t_final': 7.000 ns,
'rx90p[0]-d1-carrier-freq': 5.050 GHz 2pi,
'rx90p[0]-d1-carrier-framechange': 0.000 rad,
'ry90p[0]-d1-gauss-amp': 450.000 mV,
'ry90p[0]-d1-gauss-delta': -1.000 ,
'ry90p[0]-d1-gauss-freq_offset': -50.500 MHz 2pi,
'ry90p[0]-d1-gauss-xy_angle': 1.571 rad,
'ry90p[0]-d1-gauss-sigma': 1.750 ns,
'ry90p[0]-d1-gauss-t_final': 7.000 ns,
'ry90p[0]-d1-carrier-freq': 5.050 GHz 2pi,
'ry90p[0]-d1-carrier-framechange': 0.000 rad,
'rx90m[0]-d1-gauss-amp': 450.000 mV,
'rx90m[0]-d1-gauss-delta': -1.000 ,
'rx90m[0]-d1-gauss-freq_offset': -50.500 MHz 2pi,
'rx90m[0]-d1-gauss-xy_angle': 3.142 rad,
'rx90m[0]-d1-gauss-sigma': 1.750 ns,
'rx90m[0]-d1-gauss-t_final': 7.000 ns,
'rx90m[0]-d1-carrier-freq': 5.050 GHz 2pi,
'rx90m[0]-d1-carrier-framechange': 0.000 rad,
'ry90m[0]-d1-gauss-amp': 450.000 mV,
'ry90m[0]-d1-gauss-delta': -1.000 ,
'ry90m[0]-d1-gauss-freq_offset': -50.500 MHz 2pi,
'ry90m[0]-d1-gauss-xy_angle': 4.712 rad,
'ry90m[0]-d1-gauss-sigma': 1.750 ns,
'ry90m[0]-d1-gauss-t_final': 7.000 ns,
'ry90m[0]-d1-carrier-freq': 5.050 GHz 2pi,
'ry90m[0]-d1-carrier-framechange': 0.000 rad}
To access a specific parameter, e.g. the frequency of qubit 1, we use
the identifying tuple ('Q1','freq')
.
pmap.get_parameter(('Q1','freq'))
5.000 GHz 2pi
The opt_map¶
To deal with multiple parameters we use the opt_map
, a nested list
of identifyers.
opt_map = [
[
("Q1", "freq")
],
[
("Q1", "anhar")
],
]
Here, we get a list of the parameter values:
pmap.get_parameters(opt_map)
[5.000 GHz 2pi, -210.000 MHz 2pi]
Let’s look at the amplitude values of two gaussian control pulses, rotations about the \(X\) and \(Y\) axes repsectively.
opt_map = [
[
('rx90p[0]','d1','gauss','amp')
],
[
('ry90p[0]','d1','gauss','amp')
],
]
pmap.get_parameters(opt_map)
[450.000 mV, 450.000 mV]
We can set the parameters to new values.
pmap.set_parameters([0.5, 0.6], opt_map)
pmap.get_parameters(opt_map)
[500.000 mV, 600.000 mV]
The opt_map also allows us to specify that two parameters should have identical values. Here, let’s demand our \(X\) and \(Y\) rotations use the same amplitude.
opt_map_ident = [
[
('rx90p[0]','d1','gauss','amp'),
('ry90p[0]','d1','gauss','amp')
],
]
The grouping here means that these parameters share their numerical value.
pmap.set_parameters([0.432], opt_map_ident)
pmap.get_parameters(opt_map_ident)
[432.000 mV]
pmap.get_parameters(opt_map)
[432.000 mV, 432.000 mV]
During an optimization, the varied parameters do not change, so we fix the opt_map
pmap.set_opt_map(opt_map)
pmap.get_parameters()
[432.000 mV, 432.000 mV]
Optimizer scaling¶
To be independent of the choice of numerical optimizer, they should use the methods
pmap.get_parameters_scaled()
array([-0.68, -0.68])
To provide values bound to \([-1, 1]\). Let’s set the parameters to their allowed minimum an maximum value with
pmap.set_parameters_scaled([1.0,-1.0])
pmap.get_parameters()
[600.000 mV, 400.000 mV]
As a safeguard, when setting values outside of the unit range, their physical values get looped back in the specified limits.
pmap.set_parameters_scaled([2.0, 3.0])
pmap.get_parameters()
[500.000 mV, 400.000 mV]
Storing and reading¶
For optimization purposes, we can store and load parameter values in HJSON format.
pmap.store_values("current_vals.c3log")
!cat current_vals.c3log
{
opt_map:
[
[
rx90p[0]-d1-gauss-amp
]
[
ry90p[0]-d1-gauss-amp
]
]
units:
[
V
V
]
optim_status:
{
params:
[
0.5
0.4000000059604645
]
}
}
pmap.load_values("current_vals.c3log")
Setup of a two-qubit chip with \(C^3\)¶
In this example we will set-up a two qubit quantum processor and define a simple gate.
Imports¶
# System imports
import copy
import numpy as np
import time
import itertools
import matplotlib.pyplot as plt
import tensorflow as tf
import tensorflow_probability as tfp
# Main C3 objects
from c3.c3objs import Quantity as Qty
from c3.parametermap import ParameterMap as PMap
from c3.experiment import Experiment as Exp
from c3.model import Model as Mdl
from c3.generator.generator import Generator as Gnr
# Building blocks
import c3.generator.devices as devices
import c3.signal.gates as gates
import c3.libraries.chip as chip
import c3.signal.pulse as pulse
import c3.libraries.tasks as tasks
# Libs and helpers
import c3.libraries.algorithms as algorithms
import c3.libraries.hamiltonians as hamiltonians
import c3.libraries.fidelities as fidelities
import c3.libraries.envelopes as envelopes
import c3.utils.qt_utils as qt_utils
import c3.utils.tf_utils as tf_utils
Model components¶
We first create a qubit. Each parameter is a Quantity (Qty()
) object
with bounds and a unit. In \(C^3\), the default multi-level qubit is
a Transmon modelled as a Duffing oscillator with frequency
\(\omega\) and anharmonicity \(\delta\) :
The “name” will be used to identify this qubit (or other component) later and should thus be chosen carefully.
qubit_lvls = 3
freq_q1 = 5e9
anhar_q1 = -210e6
t1_q1 = 27e-6
t2star_q1 = 39e-6
qubit_temp = 50e-3
q1 = chip.Qubit(
name="Q1",
desc="Qubit 1",
freq=Qty(
value=freq_q1,
min_val=4.995e9 ,
max_val=5.005e9 ,
unit='Hz 2pi'
),
anhar=Qty(
value=anhar_q1,
min_val=-380e6 ,
max_val=-120e6 ,
unit='Hz 2pi'
),
hilbert_dim=qubit_lvls,
t1=Qty(
value=t1_q1,
min_val=1e-6,
max_val=90e-6,
unit='s'
),
t2star=Qty(
value=t2star_q1,
min_val=10e-6,
max_val=90e-3,
unit='s'
),
temp=Qty(
value=qubit_temp,
min_val=0.0,
max_val=0.12,
unit='K'
)
)
And the same for a second qubit.
freq_q2 = 5.6e9
anhar_q2 = -240e6
t1_q2 = 23e-6
t2star_q2 = 31e-6
q2 = chip.Qubit(
name="Q2",
desc="Qubit 2",
freq=Qty(
value=freq_q2,
min_val=5.595e9 ,
max_val=5.605e9 ,
unit='Hz 2pi'
),
anhar=Qty(
value=anhar_q2,
min_val=-380e6 ,
max_val=-120e6 ,
unit='Hz 2pi'
),
hilbert_dim=qubit_lvls,
t1=Qty(
value=t1_q2,
min_val=1e-6,
max_val=90e-6,
unit='s'
),
t2star=Qty(
value=t2star_q2,
min_val=10e-6,
max_val=90e-6,
unit='s'
),
temp=Qty(
value=qubit_temp,
min_val=0.0,
max_val=0.12,
unit='K'
)
)
A static coupling between the two is realized in the following way. We
supply the type of coupling by selecting int_XX
\((b_1+b_1^\dagger)(b_2+b_2^\dagger)\) from the hamiltonian library.
The “connected” property contains the list of qubit names to be coupled,
in this case “Q1” and “Q2”.
coupling_strength = 20e6
q1q2 = chip.Coupling(
name="Q1-Q2",
desc="coupling",
comment="Coupling qubit 1 to qubit 2",
connected=["Q1", "Q2"],
strength=Qty(
value=coupling_strength,
min_val=-1 * 1e3 ,
max_val=200e6 ,
unit='Hz 2pi'
),
hamiltonian_func=hamiltonians.int_XX
)
In the same spirit, we specify control Hamiltonians to drive the system. Again “connected” connected tells us which qubit this drive acts on and “name” will later be used to assign the correct control signal to this drive line.
drive = chip.Drive(
name="d1",
desc="Drive 1",
comment="Drive line 1 on qubit 1",
connected=["Q1"],
hamiltonian_func=hamiltonians.x_drive
)
drive2 = chip.Drive(
name="d2",
desc="Drive 2",
comment="Drive line 2 on qubit 2",
connected=["Q2"],
hamiltonian_func=hamiltonians.x_drive
)
SPAM errors¶
In experimental practice, the qubit state can be mis-classified during read-out. We simulate this by constructing a confusion matrix, containing the probabilities for one qubit state being mistaken for another.
m00_q1 = 0.97 # Prop to read qubit 1 state 0 as 0
m01_q1 = 0.04 # Prop to read qubit 1 state 0 as 1
m00_q2 = 0.96 # Prop to read qubit 2 state 0 as 0
m01_q2 = 0.05 # Prop to read qubit 2 state 0 as 1
one_zeros = np.array([0] * qubit_lvls)
zero_ones = np.array([1] * qubit_lvls)
one_zeros[0] = 1
zero_ones[0] = 0
val1 = one_zeros * m00_q1 + zero_ones * m01_q1
val2 = one_zeros * m00_q2 + zero_ones * m01_q2
min_val = one_zeros * 0.8 + zero_ones * 0.0
max_val = one_zeros * 1.0 + zero_ones * 0.2
confusion_row1 = Qty(value=val1, min_val=min_val, max_val=max_val, unit="")
confusion_row2 = Qty(value=val2, min_val=min_val, max_val=max_val, unit="")
conf_matrix = tasks.ConfusionMatrix(Q1=confusion_row1, Q2=confusion_row2)
The following task creates an initial thermal state with given temperature.
init_temp = 50e-3
init_ground = tasks.InitialiseGround(
init_temp=Qty(
value=init_temp,
min_val=-0.001,
max_val=0.22,
unit='K'
)
)
We collect the parts specified above in the Model.
model = Mdl(
[q1, q2], # Individual, self-contained components
[drive, drive2, q1q2], # Interactions between components
[conf_matrix, init_ground] # SPAM processing
)
Further, we can decide between coherent or open-system dynamics using set_lindbladian() and whether to eliminate the static coupling by going to the dressed frame with set_dressed().
model.set_lindbladian(False)
model.set_dressed(True)
Control signals¶
With the system model taken care of, we now specify the control electronics and signal chain. Complex shaped controls are often realized by creating an envelope signal with an arbitrary waveform generator (AWG) with limited bandwith and mixing it with a fast, stable local oscillator (LO).
sim_res = 100e9 # Resolution for numerical simulation
awg_res = 2e9 # Realistic, limited resolution of an AWG
lo = devices.LO(name='lo', resolution=sim_res)
awg = devices.AWG(name='awg', resolution=awg_res)
mixer = devices.Mixer(name='mixer')
Waveform generators exhibit a rise time, the time it takes until the target voltage is set. This has a smoothing effect on the resulting pulse shape.
resp = devices.Response(
name='resp',
rise_time=Qty(
value=0.3e-9,
min_val=0.05e-9,
max_val=0.6e-9,
unit='s'
),
resolution=sim_res
)
In simulation, we translate between AWG resolution and simulation (or “analog”) resolution by including an up-sampling device.
dig_to_an = devices.DigitalToAnalog(
name="dac",
resolution=sim_res
)
Control electronics apply voltages to lines, whereas in a Hamiltonian we usually write the control fields in energy or frequency units. In practice, this conversion can be highly non-trivial if it involves multiple stages of attenuation and for example the conversion of a line voltage in an antenna to a dipole field coupling to the qubit. The following device represents a simple, linear conversion factor.
v2hz = 1e9
v_to_hz = devices.VoltsToHertz(
name='v_to_hz',
V_to_Hz=Qty(
value=v2hz,
min_val=0.9e9,
max_val=1.1e9,
unit='Hz/V'
)
)
The generator combines the parts of the signal generation and assigns a signal chain to each control line.
generator = Gnr(
devices={
"LO": devices.LO(name='lo', resolution=sim_res, outputs=1),
"AWG": devices.AWG(name='awg', resolution=awg_res, outputs=1),
"DigitalToAnalog": devices.DigitalToAnalog(
name="dac",
resolution=sim_res,
inputs=1,
outputs=1
),
"Response": devices.Response(
name='resp',
rise_time=Qty(
value=0.3e-9,
min_val=0.05e-9,
max_val=0.6e-9,
unit='s'
),
resolution=sim_res,
inputs=1,
outputs=1
),
"Mixer": devices.Mixer(name='mixer', inputs=2, outputs=1),
"VoltsToHertz": devices.VoltsToHertz(
name='v_to_hz',
V_to_Hz=Qty(
value=1e9,
min_val=0.9e9,
max_val=1.1e9,
unit='Hz/V'
),
inputs=1,
outputs=1
)
},
chains= {
"d1": {
"LO": [],
"AWG": [],
"DigitalToAnalog": ["AWG"],
"Response": ["DigitalToAnalog"],
"Mixer": ["LO", "Response"],
"VoltsToHertz": ["Mixer"]
},
"d2": {
"LO": [],
"AWG": [],
"DigitalToAnalog": ["AWG"],
"Response": ["DigitalToAnalog"],
"Mixer": ["LO", "Response"],
"VoltsToHertz": ["Mixer"]
}
}
)
Gates-set and Parameter map¶
It remains to write down what kind of operations we want to perform on the device. For a gate based quantum computing chip, we define a gate-set.
We choose a gate time of 7ns and a Gaussian envelope shape with a list of parameters.
t_final = 7e-9 # Time for single qubit gates
sideband = 50e6
gauss_params_single = {
'amp': Qty(
value=0.5,
min_val=0.4,
max_val=0.6,
unit="V"
),
't_final': Qty(
value=t_final,
min_val=0.5 * t_final,
max_val=1.5 * t_final,
unit="s"
),
'sigma': Qty(
value=t_final / 4,
min_val=t_final / 8,
max_val=t_final / 2,
unit="s"
),
'xy_angle': Qty(
value=0.0,
min_val=-0.5 * np.pi,
max_val=2.5 * np.pi,
unit='rad'
),
'freq_offset': Qty(
value=-sideband - 3e6 ,
min_val=-56 * 1e6 ,
max_val=-52 * 1e6 ,
unit='Hz 2pi'
),
'delta': Qty(
value=-1,
min_val=-5,
max_val=3,
unit=""
)
}
Here we take gaussian_nonorm()
from the libraries as the function to
define the shape.
gauss_env_single = pulse.Envelope(
name="gauss",
desc="Gaussian comp for single-qubit gates",
params=gauss_params_single,
shape=envelopes.gaussian_nonorm
)
We also define a gate that represents no driving.
nodrive_env = pulse.Envelope(
name="no_drive",
params={
't_final': Qty(
value=t_final,
min_val=0.5 * t_final,
max_val=1.5 * t_final,
unit="s"
)
},
shape=envelopes.no_drive
)
We specify the drive tones with an offset from the qubit frequencies. As is done in experiment, we will later adjust the resonance by modulating the envelope function.
lo_freq_q1 = 5e9 + sideband
carrier_parameters = {
'freq': Qty(
value=lo_freq_q1,
min_val=4.5e9 ,
max_val=6e9 ,
unit='Hz 2pi'
),
'framechange': Qty(
value=0.0,
min_val= -np.pi,
max_val= 3 * np.pi,
unit='rad'
)
}
carr = pulse.Carrier(
name="carrier",
desc="Frequency of the local oscillator",
params=carrier_parameters
)
For the second qubit drive tone, we copy the first one and replace the frequency. The deepcopy is to ensure that we don’t just create a pointer to the first drive.
lo_freq_q2 = 5.6e9 + sideband
carr_2 = copy.deepcopy(carr)
carr_2.params['freq'].set_value(lo_freq_q2)
Instructions¶
We define the gates we want to perform with a “name” that will identify them later and “channels” relating to the control Hamiltonians and drive lines we specified earlier. As a start we write down 90 degree rotations in the positive \(x\)-direction and identity gates for both qubits. Then we add a carrier and envelope to each.
rx90p_q1 = gates.Instruction(
name="rx90p", targets=[0], t_start=0.0, t_end=t_final, channels=["d1", "d2"]
)
rx90p_q2 = gates.Instruction(
name="rx90p", targets=[1], t_start=0.0, t_end=t_final, channels=["d1", "d2"]
)
rx90p_q1.add_component(gauss_env_single, "d1")
rx90p_q1.add_component(carr, "d1")
rx90p_q2.add_component(copy.deepcopy(gauss_env_single), "d2")
rx90p_q2.add_component(carr_2, "d2")
When later compiling gates into sequences, we have to take care of the relative rotating frames of the qubits and local oscillators. We do this by adding a phase after each gate that realigns the frames.
rx90p_q1.add_component(nodrive_env, "d2")
rx90p_q1.add_component(copy.deepcopy(carr_2), "d2")
rx90p_q1.comps["d2"]["carrier"].params["framechange"].set_value(
(-sideband * t_final) * 2 * np.pi % (2 * np.pi)
)
rx90p_q2.add_component(nodrive_env, "d1")
rx90p_q2.add_component(copy.deepcopy(carr), "d1")
rx90p_q2.comps["d1"]["carrier"].params["framechange"].set_value(
(-sideband * t_final) * 2 * np.pi % (2 * np.pi)
)
The remainder of the gates-set can be derived from the RX90p gate by shifting its phase by multiples of \(\pi/2\).
ry90p_q1 = copy.deepcopy(rx90p_q1)
ry90p_q1.name = "ry90p"
rx90m_q1 = copy.deepcopy(rx90p_q1)
rx90m_q1.name = "rx90m"
ry90m_q1 = copy.deepcopy(rx90p_q1)
ry90m_q1.name = "ry90m"
ry90p_q1.comps['d1']['gauss'].params['xy_angle'].set_value(0.5 * np.pi)
rx90m_q1.comps['d1']['gauss'].params['xy_angle'].set_value(np.pi)
ry90m_q1.comps['d1']['gauss'].params['xy_angle'].set_value(1.5 * np.pi)
single_q_gates = [rx90p_q1, ry90p_q1, rx90m_q1, ry90m_q1]
ry90p_q2 = copy.deepcopy(rx90p_q2)
ry90p_q2.name = "ry90p"
rx90m_q2 = copy.deepcopy(rx90p_q2)
rx90m_q2.name = "rx90m"
ry90m_q2 = copy.deepcopy(rx90p_q2)
ry90m_q2.name = "ry90m"
ry90p_q2.comps['d2']['gauss'].params['xy_angle'].set_value(0.5 * np.pi)
rx90m_q2.comps['d2']['gauss'].params['xy_angle'].set_value(np.pi)
ry90m_q2.comps['d2']['gauss'].params['xy_angle'].set_value(1.5 * np.pi)
single_q_gates.extend([rx90p_q2, ry90p_q2, rx90m_q2, ry90m_q2])
With every component defined, we collect them in the parameter map, our object that holds information and methods to manipulate and examine model and control parameters.
parameter_map = PMap(instructions=all_1q_gates_comb, model=model, generator=generator)
The experiment¶
Finally everything is collected in the experiment that provides the functions to interact with the system.
exp = Exp(pmap=parameter_map)
Simulation¶
With our experiment all set-up, we can perform simulations. We first decide which basic gates to simulate, in this case only the 90 degree rotation on one qubit and the identity.
exp.set_opt_gates(['RX90p:Id', 'Id:Id'])
The actual numerical simulation is done by calling exp.compute_propagators()
.
This is the most resource intensive part as it involves solving the
equations of motion for the system.
unitaries = exp.compute_propagators()
Dynamics¶
To investigate dynamics, we define the ground state as an initial state.
psi_init = [[0] * 9]
psi_init[0][0] = 1
init_state = tf.transpose(tf.constant(psi_init, tf.complex128))
init_state
<tf.Tensor: shape=(9, 1), dtype=complex128, numpy=
array([[1.+0.j],
[0.+0.j],
[0.+0.j],
[0.+0.j],
[0.+0.j],
[0.+0.j],
[0.+0.j],
[0.+0.j],
[0.+0.j]])>
Since we stored the process matrices, we can now relatively inexpensively evaluate sequences. We start with just one gate
barely_a_seq = ['rx90p[0]']
and plot system dynamics.
def plot_dynamics(exp, psi_init, seq, goal=-1):
"""
Plotting code for time-resolved populations.
Parameters
----------
psi_init: tf.Tensor
Initial state or density matrix.
seq: list
List of operations to apply to the initial state.
goal: tf.float64
Value of the goal function, if used.
debug: boolean
If true, return a matplotlib figure instead of saving.
"""
model = exp.pmap.model
dUs = exp.partial_propagators
psi_t = psi_init.numpy()
pop_t = exp.populations(psi_t, model.lindbladian)
for gate in seq:
for du in dUs[gate]:
psi_t = np.matmul(du.numpy(), psi_t)
pops = exp.populations(psi_t, model.lindbladian)
pop_t = np.append(pop_t, pops, axis=1)
fig, axs = plt.subplots(1, 1)
ts = exp.ts
dt = ts[1] - ts[0]
ts = np.linspace(0.0, dt*pop_t.shape[1], pop_t.shape[1])
axs.plot(ts / 1e-9, pop_t.T)
axs.grid(linestyle="--")
axs.tick_params(
direction="in", left=True, right=True, top=True, bottom=True
)
axs.set_xlabel('Time [ns]')
axs.set_ylabel('Population')
plt.legend(model.state_labels)
pass
plot_dynamics(exp, init_state, barely_a_seq)

We can see an ill-defined un-optimized gate. The labels indicate qubit states in the product basis. Next we increase the number of repetitions of the same gate.
barely_a_seq * 10
['rx90p[0]',
'rx90p[0]',
'rx90p[0]',
'rx90p[0]',
'rx90p[0]',
'rx90p[0]',
'rx90p[0]',
'rx90p[0]',
'rx90p[0]',
'rx90p[0]']
plot_dynamics(exp, init_state, barely_a_seq * 5)

plot_dynamics(exp, init_state, barely_a_seq * 10)

Note that at this point, we only multiply already computed matrices. We don’t need to solve the equations of motion again for new sequences.
Open-loop optimal control¶
In order to improve the gate from the previous example Setup of a two-qubit chip with C^3, we create the optimizer object for open-loop optimal control. Examining the previous dynamics .. image:: dyn_singleX.png
in addition to over-rotation, we notice some leakage into the \(|2,0>\) state and enable a DRAG option. Details on DRAG can be found here. The main principle is adding a phase-shifted component proportional to the derivative of the original signal. With automatic differentiation, our AWG can perform this operation automatically for arbitrary shapes.
generator.devices['AWG'].enable_drag_2()
At the moment there are two implementations of DRAG, variant 2 is independent of the AWG resolution.
To define which parameters we optimize, we write the gateset_opt_map, a nested list of tuples that identifies each parameter.
opt_gates = ["rx90p[0]"]
gateset_opt_map=[
[
("rx90p[0]", "d1", "gauss", "amp"),
],
[
("rx90p[0]", "d1", "gauss", "freq_offset"),
],
[
("rx90p[0]", "d1", "gauss", "xy_angle"),
],
[
("RX90p:Id", "d1", "gauss", "delta"),
]
]
parameter_map.set_opt_map(gateset_opt_map)
We can look at the parameter values this opt_map specified with
parameter_map.print_parameters()
rx90p[0]-d1-gauss-amp : 500.000 mV
rx90p[0]-d1-gauss-freq_offset : -53.000 MHz 2pi
rx90p[0]-d1-gauss-xy_angle : -444.089 arad
rx90p[0]-d1-gauss-delta : -1.000
from c3.optimizers.optimalcontrol import OptimalControl
import c3.libraries.algorithms as algorithms
The OptimalControl object will handle the optimization for us. As a fidelity function we choose average fidelity as well as LBFG-S (a wrapper of the scipy implementation) from our library. See those libraries for how these functions are defined and how to supply your own, if necessary.
import os
import tempfile
# Create a temporary directory to store logfiles, modify as needed
log_dir = os.path.join(tempfile.TemporaryDirectory().name, "c3logs")
opt = OptimalControl(
dir_path=log_dir,
fid_func=fidelities.average_infid_set,
fid_subspace=["Q1", "Q2"],
pmap=parameter_map,
algorithm=algorithms.lbfgs,
options={"maxfun" : 10},
run_name="better_X90"
)
Finally we supply our defined experiment.
exp.set_opt_gates(opt_gates)
opt.set_exp(exp)
Everything is in place to start the optimization.
opt.optimize_controls()
After a few steps we have improved the gate significantly, as we can check with
opt.current_best_goal
0.00063
And by looking at the same sequences as before.
plot_dynamics(exp, init_state, barely_a_seq)

plot_dynamics(exp, init_state, barely_a_seq * 5)

Compared to before the optimization.

Entangling gate on two coupled qubits¶
Imports¶
!pip install -q -U pip
!pip install -q matplotlib
# System imports
import copy
import numpy as np
import time
import itertools
import matplotlib.pyplot as plt
import tensorflow as tf
import tensorflow_probability as tfp
from typing import List
from pprint import pprint
# Main C3 objects
from c3.c3objs import Quantity as Qty
from c3.parametermap import ParameterMap as PMap
from c3.experiment import Experiment as Exp
from c3.model import Model as Mdl
from c3.generator.generator import Generator as Gnr
# Building blocks
import c3.generator.devices as devices
import c3.signal.gates as gates
import c3.libraries.chip as chip
import c3.signal.pulse as pulse
import c3.libraries.tasks as tasks
# Libs and helpers
import c3.libraries.algorithms as algorithms
import c3.libraries.hamiltonians as hamiltonians
import c3.libraries.fidelities as fidelities
import c3.libraries.envelopes as envelopes
import c3.utils.qt_utils as qt_utils
import c3.utils.tf_utils as tf_utils
# Qiskit related modules
from c3.qiskit import C3Provider
from c3.qiskit.c3_gates import RX90pGate
from qiskit import QuantumCircuit, Aer, execute
from qiskit.tools.visualization import plot_histogram
Model components¶
The model consists of two qubits with 3 levels each and slightly different parameters:
qubit_lvls = 3
freq_q1 = 5e9
anhar_q1 = -210e6
t1_q1 = 27e-6
t2star_q1 = 39e-6
qubit_temp = 50e-3
q1 = chip.Qubit(
name="Q1",
desc="Qubit 1",
freq=Qty(value=freq_q1, min_val=4.995e9, max_val=5.005e9, unit='Hz 2pi'),
anhar=Qty(value=anhar_q1, min_val=-380e6, max_val=-120e6, unit='Hz 2pi'),
hilbert_dim=qubit_lvls,
t1=Qty(value=t1_q1, min_val=1e-6, max_val=90e-6, unit='s'),
t2star=Qty(value=t2star_q1, min_val=10e-6, max_val=90e-3, unit='s'),
temp=Qty(value=qubit_temp, min_val=0.0, max_val=0.12, unit='K')
)
freq_q2 = 5.6e9
anhar_q2 = -240e6
t1_q2 = 23e-6
t2star_q2 = 31e-6
q2 = chip.Qubit(
name="Q2",
desc="Qubit 2",
freq=Qty(value=freq_q2, min_val=5.595e9, max_val=5.605e9, unit='Hz 2pi'),
anhar=Qty(value=anhar_q2, min_val=-380e6, max_val=-120e6, unit='Hz 2pi'),
hilbert_dim=qubit_lvls,
t1=Qty(value=t1_q2, min_val=1e-6, max_val=90e-6,unit='s'),
t2star=Qty(value=t2star_q2, min_val=10e-6, max_val=90e-6, unit='s'),
temp=Qty(value=qubit_temp, min_val=0.0, max_val=0.12, unit='K')
)
There is a static coupling in x-direction between them: \((b_1+b_1^\dagger)(b_2+b_2^\dagger)\)
coupling_strength = 50e6
q1q2 = chip.Coupling(
name="Q1-Q2",
desc="coupling",
comment="Coupling qubit 1 to qubit 2",
connected=["Q1", "Q2"],
strength=Qty(
value=coupling_strength,
min_val=-1 * 1e3 ,
max_val=200e6 ,
unit='Hz 2pi'
),
hamiltonian_func=hamiltonians.int_XX
)
and each qubit has a drive line
drive1 = chip.Drive(
name="d1",
desc="Drive 1",
comment="Drive line 1 on qubit 1",
connected=["Q1"],
hamiltonian_func=hamiltonians.x_drive
)
drive2 = chip.Drive(
name="d2",
desc="Drive 2",
comment="Drive line 2 on qubit 2",
connected=["Q2"],
hamiltonian_func=hamiltonians.x_drive
)
All parts are collected in the model. The initial state will be thermal at a non-vanishing temperature.
init_temp = 50e-3
init_ground = tasks.InitialiseGround(
init_temp=Qty(value=init_temp, min_val=-0.001, max_val=0.22, unit='K')
)
model = Mdl(
[q1, q2], # Individual, self-contained components
[drive1, drive2, q1q2], # Interactions between components
# [init_ground] # SPAM processing
)
model.set_lindbladian(False)
model.set_dressed(True)
Control signals¶
The devices for the control line are set up
sim_res = 100e9 # Resolution for numerical simulation
awg_res = 2e9 # Realistic, limited resolution of an AWG
v2hz = 1e9
lo = devices.LO(name='lo', resolution=sim_res)
awg = devices.AWG(name='awg', resolution=awg_res)
mixer = devices.Mixer(name='mixer')
resp = devices.Response(
name='resp',
rise_time=Qty(value=0.3e-9, min_val=0.05e-9, max_val=0.6e-9, unit='s'),
resolution=sim_res
)
dig_to_an = devices.DigitalToAnalog(name="dac", resolution=sim_res)
v_to_hz = devices.VoltsToHertz(
name='v_to_hz',
V_to_Hz=Qty(value=v2hz, min_val=0.9e9, max_val=1.1e9, unit='Hz/V')
)
The generator combines the parts of the signal generation and assignes a signal chain to each control line.
generator = Gnr(
devices={
"LO": lo,
"AWG": awg,
"DigitalToAnalog": dig_to_an,
"Response": resp,
"Mixer": mixer,
"VoltsToHertz": v_to_hz
},
chains={
"d1": {
"LO": [],
"AWG": [],
"DigitalToAnalog": ["AWG"],
"Response": ["DigitalToAnalog"],
"Mixer": ["LO", "Response"],
"VoltsToHertz": ["Mixer"],
},
"d2": {
"LO": [],
"AWG": [],
"DigitalToAnalog": ["AWG"],
"Response": ["DigitalToAnalog"],
"Mixer": ["LO", "Response"],
"VoltsToHertz": ["Mixer"],
}
}
)
Gates-set and Parameter map¶
Following a general cross resonance scheme, both qubits will be resonantly driven at the frequency of qubit 2 with a Gaussian envelope. We drive qubit 1 (the control) at the frequency of qubit 2 (the target) with a higher amplitude to compensate for the reduced Rabi frequency.
t_final_2Q = 45e-9
sideband = 50e6
gauss_params_2Q_1 = {
'amp': Qty(value=0.8, min_val=0.2, max_val=3, unit="V"),
't_final': Qty(value=t_final_2Q, min_val=0.5 * t_final_2Q, max_val=1.5 * t_final_2Q, unit="s"),
'sigma': Qty(value=t_final_2Q / 4, min_val=t_final_2Q / 8, max_val=t_final_2Q / 2, unit="s"),
'xy_angle': Qty(value=0.0, min_val=-0.5 * np.pi, max_val=2.5 * np.pi, unit='rad'),
'freq_offset': Qty(value=-sideband - 3e6, min_val=-56 * 1e6, max_val=-52 * 1e6, unit='Hz 2pi'),
'delta': Qty(value=-1, min_val=-5, max_val=3, unit="")
}
gauss_params_2Q_2 = {
'amp': Qty(value=0.03, min_val=0.02, max_val=0.6, unit="V"),
't_final': Qty(value=t_final_2Q, min_val=0.5 * t_final_2Q, max_val=1.5 * t_final_2Q, unit="s"),
'sigma': Qty(value=t_final_2Q / 4, min_val=t_final_2Q / 8, max_val=t_final_2Q / 2, unit="s"),
'xy_angle': Qty(value=0.0, min_val=-0.5 * np.pi, max_val=2.5 * np.pi, unit='rad'),
'freq_offset': Qty(value=-sideband - 3e6, min_val=-56 * 1e6, max_val=-52 * 1e6, unit='Hz 2pi'),
'delta': Qty(value=-1, min_val=-5, max_val=3, unit="")
}
gauss_env_2Q_1 = pulse.Envelope(
name="gauss1",
desc="Gaussian envelope on drive 1",
params=gauss_params_2Q_1,
shape=envelopes.gaussian_nonorm
)
gauss_env_2Q_2 = pulse.Envelope(
name="gauss2",
desc="Gaussian envelope on drive 2",
params=gauss_params_2Q_2,
shape=envelopes.gaussian_nonorm
)
We choose a single qubit gate time of 7ns and a gaussian envelope shape with a list of parameters.
t_final_1Q = 7e-9 # Time for single qubit gates
sideband = 50e6
gauss_params_single = {
'amp': Qty(
value=0.5,
min_val=0.2,
max_val=0.6,
unit="V"
),
't_final': Qty(
value=t_final_1Q,
min_val=0.5 * t_final_1Q,
max_val=1.5 * t_final_1Q,
unit="s"
),
'sigma': Qty(
value=t_final_1Q / 4,
min_val=t_final_1Q / 8,
max_val=t_final_1Q / 2,
unit="s"
),
'xy_angle': Qty(
value=0.0,
min_val=-0.5 * np.pi,
max_val=2.5 * np.pi,
unit='rad'
),
'freq_offset': Qty(
value=-sideband - 3e6 ,
min_val=-56 * 1e6 ,
max_val=-52 * 1e6 ,
unit='Hz 2pi'
),
'delta': Qty(
value=-1,
min_val=-5,
max_val=3,
unit=""
)
}
gauss_env_1Q = pulse.Envelope(
name="gauss",
desc="Gaussian comp for single-qubit gates",
params=gauss_params_single,
shape=envelopes.gaussian_nonorm
)
We also define a gate that represents no driving (used for the single qubit gates).
nodrive_env = pulse.Envelope(
name="no_drive",
params={
't_final': Qty(
value=t_final_1Q,
min_val=0.5 * t_final_1Q,
max_val=1.5 * t_final_1Q,
unit="s"
)
},
shape=envelopes.no_drive
)
The carrier signal of each drive for the 2 Qubit gates is set to the resonance frequency of the target qubit.
lo_freq_q1 = freq_q1 + sideband
lo_freq_q2 = freq_q2 + sideband
carr_2Q_1 = pulse.Carrier(
name="carrier",
desc="Carrier on drive 1",
params={
'freq': Qty(value=lo_freq_q2, min_val=0.9 * lo_freq_q2, max_val=1.1 * lo_freq_q2, unit='Hz 2pi'),
'framechange': Qty(value=0.0, min_val=-np.pi, max_val=3 * np.pi, unit='rad')
}
)
carr_2Q_2 = pulse.Carrier(
name="carrier",
desc="Carrier on drive 2",
params={
'freq': Qty(value=lo_freq_q2, min_val=0.9 * lo_freq_q2, max_val=1.1 * lo_freq_q2, unit='Hz 2pi'),
'framechange': Qty(value=0.0, min_val=-np.pi, max_val=3 * np.pi, unit='rad')
}
)
We specify the drive tones for the 1Q gates with an offset from the qubit frequencies. As is done in experiment, we will later adjust the resonance by modulating the envelope function.
carr_1Q_1 = pulse.Carrier(
name="carrier",
desc="Frequency of the local oscillator",
params={
'freq': Qty(
value=lo_freq_q1,
min_val=0.9 * lo_freq_q1 ,
max_val=1.1 * lo_freq_q1 ,
unit='Hz 2pi'
),
'framechange': Qty(
value=0.0,
min_val= -np.pi,
max_val= 3 * np.pi,
unit='rad'
)
}
)
carr_1Q_2 = pulse.Carrier(
name="carrier",
desc="Frequency of the local oscillator",
params={
'freq': Qty(
value=lo_freq_q2,
min_val=0.9 * lo_freq_q2 ,
max_val=1.1 * lo_freq_q2 ,
unit='Hz 2pi'
),
'framechange': Qty(
value=0.0,
min_val= -np.pi,
max_val= 3 * np.pi,
unit='rad'
)
}
)
Instructions¶
The instruction to be optimised is a CNOT gates controlled by qubit 1.
# CNOT comtrolled by qubit 1
cnot12 = gates.Instruction(
name="cx", targets=[0, 1], t_start=0.0, t_end=t_final_2Q, channels=["d1", "d2"],
ideal=np.array([
[1,0,0,0],
[0,1,0,0],
[0,0,0,1],
[0,0,1,0]
])
)
cnot12.add_component(gauss_env_2Q_1, "d1")
cnot12.add_component(carr_2Q_1, "d1")
cnot12.add_component(gauss_env_2Q_2, "d2")
cnot12.add_component(carr_2Q_2, "d2")
cnot12.comps["d1"]["carrier"].params["framechange"].set_value(
(-sideband * t_final_2Q) * 2 * np.pi % (2 * np.pi)
)
We also add some typical single qubit gates to the instruction set.
rx90p_q1 = gates.Instruction(
name="rx90p", targets=[0], t_start=0.0, t_end=t_final_1Q, channels=["d1", "d2"]
)
rx90p_q2 = gates.Instruction(
name="rx90p", targets=[1], t_start=0.0, t_end=t_final_1Q, channels=["d1", "d2"]
)
rx90p_q1.add_component(gauss_env_1Q, "d1")
rx90p_q1.add_component(carr_1Q_1, "d1")
rx90p_q2.add_component(gauss_env_1Q, "d2")
rx90p_q2.add_component(carr_1Q_2, "d2")
When later compiling gates into sequences, we have to take care of the relative rotating frames of the qubits and local oscillators. We do this by adding a phase after each gate that realigns the frames.
rx90p_q1.add_component(nodrive_env, "d2")
rx90p_q1.add_component(copy.deepcopy(carr_1Q_2), "d2")
rx90p_q1.comps["d2"]["carrier"].params["framechange"].set_value(
(-sideband * t_final_1Q) * 2 * np.pi % (2 * np.pi)
)
rx90p_q2.add_component(nodrive_env, "d1")
rx90p_q2.add_component(copy.deepcopy(carr_1Q_1), "d1")
rx90p_q2.comps["d1"]["carrier"].params["framechange"].set_value(
(-sideband * t_final_1Q) * 2 * np.pi % (2 * np.pi)
)
The experiment¶
All components are collected in the parameter map and the experiment is set up.
parameter_map = PMap(instructions=[cnot12, rx90p_q1, rx90p_q2], model=model, generator=generator)
exp = Exp(pmap=parameter_map)
Calculate and print the propagator before the optimisation.
unitaries = exp.compute_propagators()
# print(unitaries[cnot12.get_key()])
# print(unitaries[rx90p_q1.get_key()])
2022-01-01 20:55:15.191809: I tensorflow/compiler/mlir/mlir_graph_optimization_pass.cc:185] None of the MLIR Optimization Passes are enabled (registered 2)
2022-01-01 20:55:15.193814: W tensorflow/core/platform/profile_utils/cpu_utils.cc:128] Failed to get CPU frequency: 0 Hz
Dynamics¶
The system is initialised in the state \(|0,1\rangle\) so that a transition to \(|1,1\rangle\) should be visible.
psi_init = [[0] * 9]
psi_init[0][0] = 1
init_state = tf.transpose(tf.constant(psi_init, tf.complex128))
print(init_state)
tf.Tensor(
[[1.+0.j]
[0.+0.j]
[0.+0.j]
[0.+0.j]
[0.+0.j]
[0.+0.j]
[0.+0.j]
[0.+0.j]
[0.+0.j]], shape=(9, 1), dtype=complex128)
def plot_dynamics(exp, psi_init, seq):
"""
Plotting code for time-resolved populations.
Parameters
----------
psi_init: tf.Tensor
Initial state or density matrix.
seq: list
List of operations to apply to the initial state.
"""
model = exp.pmap.model
dUs = exp.partial_propagators
psi_t = psi_init.numpy()
pop_t = exp.populations(psi_t, model.lindbladian)
for gate in seq:
for du in dUs[gate]:
psi_t = np.matmul(du.numpy(), psi_t)
pops = exp.populations(psi_t, model.lindbladian)
pop_t = np.append(pop_t, pops, axis=1)
fig, axs = plt.subplots(1, 1)
ts = exp.ts
dt = ts[1] - ts[0]
ts = np.linspace(0.0, dt*pop_t.shape[1], pop_t.shape[1])
axs.plot(ts / 1e-9, pop_t.T)
axs.grid(linestyle="--")
axs.tick_params(
direction="in", left=True, right=True, top=True, bottom=True
)
axs.set_xlabel('Time [ns]')
axs.set_ylabel('Population')
plt.legend(model.state_labels)
pass
def getQubitsPopulation(population: np.array, dims: List[int]) -> np.array:
"""
Splits the population of all levels of a system into the populations of levels per subsystem.
Parameters
----------
population: np.array
The time dependent population of each energy level. First dimension: level index, second dimension: time.
dims: List[int]
The number of levels for each subsystem.
Returns
-------
np.array
The time-dependent population of energy levels for each subsystem. First dimension: subsystem index, second
dimension: level index, third dimension: time.
"""
numQubits = len(dims)
# create a list of all levels
qubit_levels = []
for dim in dims:
qubit_levels.append(list(range(dim)))
combined_levels = list(itertools.product(*qubit_levels))
# calculate populations
qubitsPopulations = np.zeros((numQubits, dims[0], population.shape[1]))
for idx, levels in enumerate(combined_levels):
for i in range(numQubits):
qubitsPopulations[i, levels[i]] += population[idx]
return qubitsPopulations
def plotSplittedPopulation(
exp: Exp,
psi_init: tf.Tensor,
sequence: List[str]
) -> None:
"""
Plots time dependent populations for multiple qubits in separate plots.
Parameters
----------
exp: Experiment
The experiment containing the model and propagators
psi_init: np.array
Initial state vector
sequence: List[str]
List of gate names that will be applied to the state
-------
"""
# calculate the time dependent level population
model = exp.pmap.model
dUs = exp.partial_propagators
psi_t = psi_init.numpy()
pop_t = exp.populations(psi_t, model.lindbladian)
for gate in sequence:
for du in dUs[gate]:
psi_t = np.matmul(du, psi_t)
pops = exp.populations(psi_t, model.lindbladian)
pop_t = np.append(pop_t, pops, axis=1)
dims = [s.hilbert_dim for s in model.subsystems.values()]
splitted = getQubitsPopulation(pop_t, dims)
# timestamps
dt = exp.ts[1] - exp.ts[0]
ts = np.linspace(0.0, dt * pop_t.shape[1], pop_t.shape[1])
# create both subplots
titles = list(exp.pmap.model.subsystems.keys())
fig, axs = plt.subplots(1, len(splitted), sharey="all")
for idx, ax in enumerate(axs):
ax.plot(ts / 1e-9, splitted[idx].T)
ax.tick_params(direction="in", left=True, right=True, top=False, bottom=True)
ax.set_xlabel("Time [ns]")
ax.set_ylabel("Population")
ax.set_title(titles[idx])
ax.legend([str(x) for x in np.arange(dims[idx])])
ax.grid()
plt.tight_layout()
plt.show()
sequence = [cnot12.get_key()]
plot_dynamics(exp, init_state, sequence)
plotSplittedPopulation(exp, init_state, sequence)


Visualisation with qiskit circuit¶
qc = QuantumCircuit(2, 2)
qc.append(RX90pGate(), [0])
qc.cx(0, 1)
qc.draw()
┌────────────┐ q_0: ┤ Rx90p(π/2) ├──■── └────────────┘┌─┴─┐ q_1: ──────────────┤ X ├ └───┘ c: 2/═══════════════════
c3_provider = C3Provider()
c3_backend = c3_provider.get_backend("c3_qasm_physics_simulator")
c3_backend.set_c3_experiment(exp)
c3_job_unopt = c3_backend.run(qc)
result_unopt = c3_job_unopt.result()
res_pops_unopt = result_unopt.data()["state_pops"]
print("Result from unoptimized gates:")
pprint(res_pops_unopt)
No measurements in circuit "circuit-0", classical register will remain all zeros.
Result from unoptimized gates:
{'(0, 0)': 0.08793249061599799,
'(0, 1)': 0.20140214999016118,
'(0, 2)': 1.7916216253388795e-05,
'(1, 0)': 0.3507070201094552,
'(1, 1)': 0.34492529411056444,
'(1, 2)': 5.054006496570894e-06,
'(2, 0)': 0.009940433069486916,
'(2, 1)': 0.005069321214083548,
'(2, 2)': 3.20667334658816e-07}
plot_histogram(res_pops_unopt, title='Simulation of Qiskit circuit with Unoptimized Gates')

Open-loop optimal control¶
Now, open-loop optimisation with DRAG enabled is set up.
generator.devices['AWG'].enable_drag_2()
opt_gates = [cnot12.get_key()]
exp.set_opt_gates(opt_gates)
gateset_opt_map=[
[(cnot12.get_key(), "d1", "gauss1", "amp")],
[(cnot12.get_key(), "d1", "gauss1", "freq_offset")],
[(cnot12.get_key(), "d1", "gauss1", "xy_angle")],
[(cnot12.get_key(), "d1", "gauss1", "delta")],
[(cnot12.get_key(), "d1", "carrier", "framechange")],
[(cnot12.get_key(), "d2", "gauss2", "amp")],
[(cnot12.get_key(), "d2", "gauss2", "freq_offset")],
[(cnot12.get_key(), "d2", "gauss2", "xy_angle")],
[(cnot12.get_key(), "d2", "gauss2", "delta")],
[(cnot12.get_key(), "d2", "carrier", "framechange")],
]
parameter_map.set_opt_map(gateset_opt_map)
parameter_map.print_parameters()
cx[0, 1]-d1-gauss1-amp : 800.000 mV
cx[0, 1]-d1-gauss1-freq_offset : -53.000 MHz 2pi
cx[0, 1]-d1-gauss1-xy_angle : -444.089 arad
cx[0, 1]-d1-gauss1-delta : -1.000
cx[0, 1]-d1-carrier-framechange : 4.712 rad
cx[0, 1]-d2-gauss2-amp : 30.000 mV
cx[0, 1]-d2-gauss2-freq_offset : -53.000 MHz 2pi
cx[0, 1]-d2-gauss2-xy_angle : -444.089 arad
cx[0, 1]-d2-gauss2-delta : -1.000
cx[0, 1]-d2-carrier-framechange : 0.000 rad
As a fidelity function we choose unitary fidelity as well as LBFG-S (a wrapper of the scipy implementation) from our library.
import os
import tempfile
from c3.optimizers.optimalcontrol import OptimalControl
log_dir = os.path.join(tempfile.TemporaryDirectory().name, "c3logs")
opt = OptimalControl(
dir_path=log_dir,
fid_func=fidelities.unitary_infid_set,
fid_subspace=["Q1", "Q2"],
pmap=parameter_map,
algorithm=algorithms.lbfgs,
options={
"maxfun": 25
},
run_name="cnot12"
)
Start the optimisation
exp.set_opt_gates(opt_gates)
opt.set_exp(exp)
opt.optimize_controls()
C3:STATUS:Saving as: /var/folders/04/np4lgk2d7sq6w0dpn758sgp80000gn/T/tmpryftkry4/c3logs/cnot12/2022_01_01_T_20_55_19/open_loop.c3log
The final parameters and the fidelity are
parameter_map.print_parameters()
print(opt.current_best_goal)
cx[0, 1]-d1-gauss1-amp : 2.359 V
cx[0, 1]-d1-gauss1-freq_offset : -53.252 MHz 2pi
cx[0, 1]-d1-gauss1-xy_angle : 587.818 mrad
cx[0, 1]-d1-gauss1-delta : -743.473 m
cx[0, 1]-d1-carrier-framechange : -815.216 mrad
cx[0, 1]-d2-gauss2-amp : 56.719 mV
cx[0, 1]-d2-gauss2-freq_offset : -53.176 MHz 2pi
cx[0, 1]-d2-gauss2-xy_angle : -135.515 mrad
cx[0, 1]-d2-gauss2-delta : -519.864 m
cx[0, 1]-d2-carrier-framechange : 598.919 mrad
0.0055213431696764514
Results of the optimisation¶
Plotting the dynamics with the same initial state:
plot_dynamics(exp, init_state, sequence)
plotSplittedPopulation(exp, init_state, sequence)


Now we plot the dynamics for the control in the excited state.
psi_init = [[0] * 9]
psi_init[0][4] = 1
init_state = tf.transpose(tf.constant(psi_init, tf.complex128))
print(init_state)
plot_dynamics(exp, init_state, sequence)
plotSplittedPopulation(exp, init_state, sequence)
tf.Tensor(
[[0.+0.j]
[0.+0.j]
[0.+0.j]
[0.+0.j]
[1.+0.j]
[0.+0.j]
[0.+0.j]
[0.+0.j]
[0.+0.j]], shape=(9, 1), dtype=complex128)


As intended, the dynamics of the target is dependent on the control qubit performing a flip if the control is excited and an identity otherwise.
Optimizing the single qubit gate on Qubit 1¶
opt_gates = [rx90p_q1.get_key()]
gateset_opt_map=[
[
(rx90p_q1.get_key(), "d1", "gauss", "amp"),
],
[
(rx90p_q1.get_key(), "d1", "gauss", "freq_offset"),
],
[
(rx90p_q1.get_key(), "d1", "gauss", "xy_angle"),
],
[
(rx90p_q1.get_key(), "d1", "gauss", "delta"),
],
[
(rx90p_q1.get_key(), "d1", "carrier", "framechange"),
]
]
parameter_map.set_opt_map(gateset_opt_map)
parameter_map.print_parameters()
rx90p[0]-d1-gauss-amp : 500.000 mV
rx90p[0]-d1-gauss-freq_offset : -53.000 MHz 2pi
rx90p[0]-d1-gauss-xy_angle : -444.089 arad
rx90p[0]-d1-gauss-delta : -1.000
rx90p[0]-d1-carrier-framechange : 0.000 rad
opt_1Q = OptimalControl(
dir_path=log_dir,
fid_func=fidelities.unitary_infid_set,
fid_subspace=["Q1", "Q2"],
pmap=parameter_map,
algorithm=algorithms.lbfgs,
options={
"maxfun": 25
},
run_name="rx90p_q1"
)
exp.set_opt_gates(opt_gates)
opt_1Q.set_exp(exp)
opt_1Q.optimize_controls()
C3:STATUS:Saving as: /var/folders/04/np4lgk2d7sq6w0dpn758sgp80000gn/T/tmpryftkry4/c3logs/rx90p_q1/2022_01_01_T_20_58_09/open_loop.c3log
parameter_map.print_parameters()
print(opt_1Q.current_best_goal)
rx90p[0]-d1-gauss-amp : 390.140 mV
rx90p[0]-d1-gauss-freq_offset : -52.986 MHz 2pi
rx90p[0]-d1-gauss-xy_angle : -195.771 mrad
rx90p[0]-d1-gauss-delta : -964.376 m
rx90p[0]-d1-carrier-framechange : -282.109 mrad
0.00575481536619904
Before running the qiskit simulation, we must call set_opt_gates()
to ensure propagators are calculated for all the required gates
exp.set_opt_gates([rx90p_q1.get_key(), cnot12.get_key()])
c3_job_opt = c3_backend.run(qc)
result_opt = c3_job_opt.result()
res_pops_opt = result_opt.data()["state_pops"]
print("Result from gates:")
pprint(res_pops_opt)
No measurements in circuit "circuit-0", classical register will remain all zeros.
Result from gates:
{'(0, 0)': 0.522074672738806,
'(0, 1)': 0.0009262330305873641,
'(0, 2)': 5.58398828418534e-07,
'(1, 0)': 0.002148790053836785,
'(1, 1)': 0.4695772823691831,
'(1, 2)': 3.241481428134574e-05,
'(2, 0)': 0.0030096031488172107,
'(2, 1)': 0.0022302669196215767,
'(2, 2)': 1.7852604795947343e-07}
plot_histogram(res_pops_opt, title='Simulation of Qiskit circuit with Optimized Gates')

Simulated calibration¶
Calibration of control pulses is the process of fine-tuning parameters in a feedback-loop with the experiment. We will simulate this process here by constructing a black-box simulation and interacting with it exactly like an experiment.
We have manange imports and creation of the black-box the same way as in
the previous example in a helper single_qubit_blackbox_exp.py
.
from single_qubit_blackbox_exp import create_experiment
blackbox = create_experiment()
This blackbox is constructed the same way as in the OptimalControl example. The difference will be in how we interact with it. First, we decide on what experiment we want to perform and need to specify it as a python function. A general, minimal example would be
def exp_communication(params):
# Send parameters to experiment controller
# and receive a measurement result.
return measurement_result
Again, params
is a linear vector of bare numbers. The measurement
result can be a single number or a set of results. It can also include
additional information about statistics, like averaging, standard
deviation, etc.
ORBIT - Single-length randomized benchmarking¶
The following defines an ORBIT
procedure. In short, we define sequences of gates that result in an
identity gate if our individual gates are perfect. Any deviation from
identity gives us a measure of the imperfections in our gates. Our
helper qt_utils
provides these sequences.
from c3.utils import qt_utils
qt_utils.single_length_RB(
RB_number=1, RB_length=5, target=0
)
[['ry90m[0]',
'rx90p[0]',
'rx90m[0]',
'rx90p[0]',
'ry90p[0]',
'ry90p[0]',
'ry90p[0]',
'rx90p[0]',
'ry90m[0]',
'rx90p[0]']]
The desired number of 5 gates is selected from a specific set (the Clifford group) and has to be decomposed into the available gate-set. Here, this means 4 gates per Clifford, hence a sequence of 20 gates.
Communication with the experiment¶
Some of the following code is specific to the fact that this a
simulated calibration. The interface of \(C^2\) to the experiment
is simple: parameters in \(\rightarrow\) results out. Thus, we have
to wrap the blackbox by defining the target states and the opt_map
.
import numpy as np
import tensorflow as tf
def ORBIT_wrapper(p):
def ORBIT(params, exp, opt_map, qubit_labels, logdir):
### ORBIT meta-parameters ###
RB_length = 60 # How long each sequence is
RB_number = 40 # How many sequences
shots = 1000 # How many averages per readout
################################
### Simulation specific part ###
################################
do_noise = False # Whether to add artificial noise to the results
qubit_label = list(qubit_labels.keys())[0]
state_labels = qubit_labels[qubit_label]
state_label = [tuple(l) for l in state_labels]
# Creating the RB sequences #
seqs = qt_utils.single_length_RB(
RB_number=RB_number, RB_length=RB_length, target=0
)
# Transmitting the parameters to the experiment #
exp.pmap.set_parameters(params, opt_map)
exp.set_opt_gates_seq(seqs)
# Simulating the gates #
U_dict = exp.compute_propagators()
# Running the RB sequences and read-out the results #
pops = exp.evaluate(seqs)
pop1s, _ = exp.process(pops, labels=state_label)
results = []
results_std = []
shots_nums = []
# Collecting results and statistics, add noise #
if do_noise:
for p1 in pop1s:
draws = tf.keras.backend.random_binomial(
[shots],
p=p1[0],
dtype=tf.float64,
)
results.append([np.mean(draws)])
results_std.append([np.std(draws)/np.sqrt(shots)])
shots_nums.append([shots])
else:
for p1 in pop1s:
results.append(p1.numpy())
results_std.append([0])
shots_nums.append([shots])
#######################################
### End of Simulation specific part ###
#######################################
goal = np.mean(results)
return goal, results, results_std, seqs, shots_nums
return ORBIT(
p, blackbox, gateset_opt_map, state_labels, "/tmp/c3logs/blackbox"
)
Optimization¶
We first import algorithms and the correct optimizer object.
import copy
from c3.experiment import Experiment as Exp
from c3.c3objs import Quantity as Qty
from c3.parametermap import ParameterMap as PMap
from c3.libraries import algorithms, envelopes
from c3.signal import gates, pulse
from c3.optimizers.calibration import Calibration
Representation of the experiment within \(C^3\)¶
At this point we have to make sure that the gates (“RX90p”, etc.) and drive line (“d1”) are compatible to the experiment controller operating the blackbox. We mirror the blackbox by creating an experiment in the \(C^3\) context:
t_final = 7e-9 # Time for single qubit gates
sideband = 50e6
lo_freq = 5e9 + sideband
# ### MAKE GATESET
gauss_params_single = {
'amp': Qty(
value=0.45,
min_val=0.4,
max_val=0.6,
unit="V"
),
't_final': Qty(
value=t_final,
min_val=0.5 * t_final,
max_val=1.5 * t_final,
unit="s"
),
'sigma': Qty(
value=t_final / 4,
min_val=t_final / 8,
max_val=t_final / 2,
unit="s"
),
'xy_angle': Qty(
value=0.0,
min_val=-0.5 * np.pi,
max_val=2.5 * np.pi,
unit='rad'
),
'freq_offset': Qty(
value=-sideband - 0.5e6,
min_val=-53 * 1e6,
max_val=-47 * 1e6,
unit='Hz 2pi'
),
'delta': Qty(
value=-1,
min_val=-5,
max_val=3,
unit=""
)
}
gauss_env_single = pulse.Envelope(
name="gauss",
desc="Gaussian comp for single-qubit gates",
params=gauss_params_single,
shape=envelopes.gaussian_nonorm
)
nodrive_env = pulse.Envelope(
name="no_drive",
params={
't_final': Qty(
value=t_final,
min_val=0.5 * t_final,
max_val=1.5 * t_final,
unit="s"
)
},
shape=envelopes.no_drive
)
carrier_parameters = {
'freq': Qty(
value=lo_freq,
min_val=4.5e9,
max_val=6e9,
unit='Hz 2pi'
),
'framechange': Qty(
value=0.0,
min_val= -np.pi,
max_val= 3 * np.pi,
unit='rad'
)
}
carr = pulse.Carrier(
name="carrier",
desc="Frequency of the local oscillator",
params=carrier_parameters
)
rx90p = gates.Instruction(
name="rx90p",
t_start=0.0,
t_end=t_final,
channels=["d1"]
)
QId = gates.Instruction(
name="id",
t_start=0.0,
t_end=t_final,
channels=["d1"]
)
rx90p.add_component(gauss_env_single, "d1")
rx90p.add_component(carr, "d1")
QId.add_component(nodrive_env, "d1")
QId.add_component(copy.deepcopy(carr), "d1")
QId.comps['d1']['carrier'].params['framechange'].set_value(
(-sideband * t_final * 2 * np.pi) % (2*np.pi)
)
ry90p = copy.deepcopy(rx90p)
ry90p.name = "ry90p"
rx90m = copy.deepcopy(rx90p)
rx90m.name = "rx90m"
ry90m = copy.deepcopy(rx90p)
ry90m.name = "ry90m"
ry90p.comps['d1']['gauss'].params['xy_angle'].set_value(0.5 * np.pi)
rx90m.comps['d1']['gauss'].params['xy_angle'].set_value(np.pi)
ry90m.comps['d1']['gauss'].params['xy_angle'].set_value(1.5 * np.pi)
parameter_map = PMap(instructions=[QId, rx90p, ry90p, rx90m, ry90m])
# ### MAKE EXPERIMENT
exp = Exp(pmap=parameter_map)
Next, we define the parameters we whish to calibrate. See how these gate
instructions are defined in the experiment setup example or in
single_qubit_blackbox_exp.py
. Our gate-set is made up of 4 gates,
rotations of 90 degrees around the \(x\) and \(y\)-axis in
positive and negative direction. While it is possible to optimize each
parameters of each gate individually, in this example all four gates
share parameters. They only differ in the phase \(\phi_{xy}\) that
is set in the definitions.
gateset_opt_map = [
[
("rx90p[0]", "d1", "gauss", "amp"),
("ry90p[0]", "d1", "gauss", "amp"),
("rx90m[0]", "d1", "gauss", "amp"),
("ry90m[0]", "d1", "gauss", "amp")
],
[
("rx90p[0]", "d1", "gauss", "delta"),
("ry90p[0]", "d1", "gauss", "delta"),
("rx90m[0]", "d1", "gauss", "delta"),
("ry90m[0]", "d1", "gauss", "delta")
],
[
("rx90p[0]", "d1", "gauss", "freq_offset"),
("ry90p[0]", "d1", "gauss", "freq_offset"),
("rx90m[0]", "d1", "gauss", "freq_offset"),
("ry90m[0]", "d1", "gauss", "freq_offset")
],
[
("id[0]", "d1", "carrier", "framechange")
]
]
parameter_map.set_opt_map(gateset_opt_map)
As defined above, we have 16 parameters where 4 share their numerical value. This leaves 4 values to optimize.
parameter_map.print_parameters()
rx90p[0]-d1-gauss-amp : 450.000 mV
ry90p[0]-d1-gauss-amp
rx90m[0]-d1-gauss-amp
ry90m[0]-d1-gauss-amp
rx90p[0]-d1-gauss-delta : -1.000
ry90p[0]-d1-gauss-delta
rx90m[0]-d1-gauss-delta
ry90m[0]-d1-gauss-delta
rx90p[0]-d1-gauss-freq_offset : -50.500 MHz 2pi
ry90p[0]-d1-gauss-freq_offset
rx90m[0]-d1-gauss-freq_offset
ry90m[0]-d1-gauss-freq_offset
id[0]-d1-carrier-framechange : 4.084 rad
It is important to note that in this example, we are transmitting only these four parameters to the experiment. We don’t know how the blackbox will implement the pulse shapes and care has to be taken that the parameters are understood on the other end. Optionally, we could specifiy a virtual AWG within \(C^3\) and transmit pixilated pulse shapes directly to the physiscal AWG.
Algorithms¶
As an optimization algoritm, we choose CMA-Es and set up some options specific to this algorithm.
alg_options = {
"popsize" : 10,
"maxfevals" : 300,
"init_point" : "True",
"tolfun" : 0.01,
"spread" : 0.25
}
We define the subspace as both excited states \(\{|1>,|2>\}\), assuming read-out can distinguish between 0, 1 and 2.
state_labels = {
"excited" : [(1,), (2,)]
}
In the real world, this setup needs to be handled in the experiment controller side. We construct the optimizer object with the options we setup:
import os
import tempfile
# Create a temporary directory to store logfiles, modify as needed
log_dir = os.path.join(tempfile.TemporaryDirectory().name, "c3logs")
opt = Calibration(
dir_path=log_dir,
run_name="ORBIT_cal",
eval_func=ORBIT_wrapper,
pmap=parameter_map,
exp_right=exp,
algorithm=algorithms.cmaes,
options=alg_options
)
opt.set_exp(exp)
And run the calibration:
x = parameter_map.get_parameters_scaled()
opt.optimize_controls()
C3:STATUS:Saving as: /tmp/tmpicnnbliz/c3logs/ORBIT_cal/2021_01_28_T_15_17_30/calibration.log
(5_w,10)-aCMA-ES (mu_w=3.2,w_1=45%) in dimension 4 (seed=912463, Thu Jan 28 15:17:30 2021)
C3:STATUS:Adding initial point to CMA sample.
Iterat #Fevals function value axis ratio sigma min&max std t[m:s]
1 10 1.446744168975211e-01 1.0e+00 2.11e-01 2e-01 2e-01 1:18.9
2 20 2.074359374665050e-01 1.4e+00 1.96e-01 1e-01 2e-01 2:28.5
3 30 1.042216610303495e-01 1.5e+00 1.76e-01 1e-01 2e-01 3:36.4
4 40 1.720244494886762e-01 1.9e+00 1.88e-01 1e-01 2e-01 4:46.5
5 50 9.761264536669531e-02 2.2e+00 2.05e-01 1e-01 2e-01 6:15.4
6 60 1.956493007802809e-01 2.8e+00 1.75e-01 8e-02 2e-01 7:17.9
7 70 6.625917264980545e-02 3.0e+00 2.20e-01 9e-02 3e-01 8:22.8
8 80 7.697621753428294e-02 4.1e+00 2.19e-01 8e-02 3e-01 9:25.8
9 90 8.826758030850271e-02 4.7e+00 1.85e-01 6e-02 3e-01 10:28.7
10 100 9.099567192014653e-02 5.3e+00 1.59e-01 4e-02 2e-01 11:32.7
11 110 6.673347151005890e-02 6.9e+00 1.49e-01 3e-02 2e-01 12:27.9
12 120 6.822093884865452e-02 7.6e+00 1.68e-01 4e-02 2e-01 13:26.6
13 130 6.307315835232992e-02 8.1e+00 1.42e-01 3e-02 2e-01 14:22.8
14 140 6.301017013241370e-02 7.8e+00 1.42e-01 2e-02 2e-01 15:18.7
15 150 6.795728963072037e-02 9.3e+00 1.32e-01 2e-02 2e-01 16:15.8
16 160 7.675314380135559e-02 9.2e+00 1.03e-01 2e-02 1e-01 17:12.9
17 170 6.806172046778505e-02 9.1e+00 8.05e-02 1e-02 1e-01 18:11.5
18 180 5.698438523961635e-02 1.0e+01 7.42e-02 9e-03 9e-02 19:06.1
19 190 5.536707419037251e-02 1.1e+01 6.89e-02 8e-03 9e-02 20:00.6
20 200 4.924177790655197e-02 1.2e+01 7.31e-02 8e-03 9e-02 20:58.2
21 210 5.836136870997249e-02 1.2e+01 8.20e-02 8e-03 1e-01 21:55.1
22 220 5.463139088536284e-02 1.3e+01 8.29e-02 9e-03 1e-01 22:51.0
23 230 4.562693294212217e-02 1.4e+01 8.66e-02 9e-03 1e-01 23:48.3
24 240 5.188441161313757e-02 1.6e+01 7.74e-02 7e-03 1e-01 24:46.1
25 250 5.199237655967553e-02 1.7e+01 7.41e-02 6e-03 9e-02 25:47.1
26 260 5.684400595430246e-02 1.6e+01 6.41e-02 5e-03 9e-02 26:43.7
27 270 4.441763519087279e-02 1.8e+01 5.12e-02 4e-03 7e-02 27:36.2
28 280 4.994977609185950e-02 1.8e+01 5.51e-02 5e-03 8e-02 28:33.9
29 290 6.108777009078262e-02 1.8e+01 5.14e-02 4e-03 7e-02 29:30.4
30 300 5.658962789881571e-02 1.8e+01 4.65e-02 4e-03 6e-02 30:28.0
31 310 5.765354335022381e-02 1.8e+01 4.77e-02 4e-03 6e-02 31:26.9
termination on maxfevals=300
final/bestever f-value = 5.765354e-02 4.441764e-02
incumbent solution: [-0.4739081748676816, -0.09828275146514219, -1.0504851431889897, 0.9108808620989909]
std deviation: [0.013780217516583012, 0.0038070906112681576, 0.02460767003734409, 0.05816700836608336]
Analysis¶
The following code uses matplotlib to create an ORBIT plot from the logfile.
import json
from matplotlib.ticker import MaxNLocator
from matplotlib import rcParams
from matplotlib import cycler
import matplotlib as mpl
import matplotlib.pyplot as plt
rcParams['xtick.direction'] = 'in'
rcParams['axes.grid'] = True
rcParams['grid.linestyle'] = '--'
rcParams['markers.fillstyle'] = 'none'
rcParams['axes.prop_cycle'] = cycler(
'linestyle', ["-", "--"]
)
rcParams['text.usetex'] = True
rcParams['font.size'] = 16
rcParams['font.family'] = 'serif'
logfilename = opt.logdir + "calibration.log"
with open(logfilename, "r") as filename:
log = filename.readlines()
options = json.loads(log[7])
goal_function = []
batch = 0
batch_size = options["popsize"]
eval = 0
for line in log[9:]:
if line[0] == "{":
if not eval % batch_size:
batch = eval // batch_size
goal_function.append([])
eval += 1
point = json.loads(line)
if 'goal' in point.keys():
goal_function[batch].append(point['goal'])
# Clean unfinished batch
if len(goal_function[-1])<batch_size:
goal_function.pop(-1)
fig, ax = plt.subplots(1)
means = []
bests = []
for ii in range(len(goal_function)):
means.append(np.mean(np.array(goal_function[ii])))
bests.append(np.min(np.array(goal_function[ii])))
for pt in goal_function[ii]:
ax.plot(ii+1, pt, color='tab:blue', marker="D", markersize=2.5, linewidth=0)
ax.xaxis.set_major_locator(MaxNLocator(integer=True))
ax.set_ylabel('ORBIT')
ax.set_xlabel('Iterations')
ax.plot(
range(1, len(goal_function)+1), bests, color="tab:red", marker="D",
markersize=5.5, linewidth=0, fillstyle='full'
)

Model Learning¶
In this notebook, we will use a dataset from a simulated experiment,
more specifically, the Simulated_calibration.ipynb
example notebook
and perform Model Learning on a simple 1 qubit model.
Imports¶
import pickle
from pprint import pprint
import copy
import numpy as np
import os
import ast
import pandas as pd
from c3.model import Model as Mdl
from c3.c3objs import Quantity as Qty
from c3.parametermap import ParameterMap as PMap
from c3.experiment import Experiment as Exp
from c3.generator.generator import Generator as Gnr
import c3.signal.gates as gates
import c3.libraries.chip as chip
import c3.generator.devices as devices
import c3.libraries.hamiltonians as hamiltonians
import c3.signal.pulse as pulse
import c3.libraries.envelopes as envelopes
import c3.libraries.tasks as tasks
from c3.optimizers.modellearning import ModelLearning
The Dataset¶
We first take a look below at the dataset and its properties. To explore
more details about how the dataset is generated, please refer to the
Simulated_calibration.ipynb
example notebook.
DATAFILE_PATH = "data/small_dataset.pkl"
with open(DATAFILE_PATH, "rb+") as file:
data = pickle.load(file)
data.keys()
dict_keys(['seqs_grouped_by_param_set', 'opt_map'])
Since this dataset was obtained from an ORBIT
(arXiv:1403.0035) calibration
experiment, we have the opt_map
which will tell us about the gateset
parameters being optimized.
data["opt_map"]
[['rx90p[0]-d1-gauss-amp',
'ry90p[0]-d1-gauss-amp',
'rx90m[0]-d1-gauss-amp',
'ry90m[0]-d1-gauss-amp'],
['rx90p[0]-d1-gauss-delta',
'ry90p[0]-d1-gauss-delta',
'rx90m[0]-d1-gauss-delta',
'ry90m[0]-d1-gauss-delta'],
['rx90p[0]-d1-gauss-freq_offset',
'ry90p[0]-d1-gauss-freq_offset',
'rx90m[0]-d1-gauss-freq_offset',
'ry90m[0]-d1-gauss-freq_offset'],
['id[0]-d1-carrier-framechange']]
This opt_map
implies the calibration experiment focussed on
optimizing the amplitude, delta and frequency offset of the gaussian
pulse, along with the framechange angle
Now onto the actual measurement data from the experiment runs
seqs_data = data["seqs_grouped_by_param_set"]
How many experiment runs do we have?
len(seqs_data)
41
What does the data from each experiment look like?
We take a look at the first data point
example_data_point = seqs_data[0]
example_data_point.keys()
dict_keys(['params', 'seqs', 'results', 'results_std', 'shots'])
These keys
are useful in understanding the structure of the dataset.
We look at them one by one.
example_data_point["params"]
[450.000 mV, -1.000 , -50.500 MHz 2pi, 4.084 rad]
These are the parameters for our parameterised gateset, for the first experiment run. They correspond to the optimization parameters we previously discussed.
The seqs
key stores the sequence of gates that make up this ORBIT
calibration experiment. Each ORBIT sequence consists of a set of gates,
followed by a measurement operation. This is then repeated for some
n
number of shots (eg, 1000
in this case) and we only store the
averaged result along with the standard deviation of these readout
shots. Each experiment in turn consists of a number of these ORBIT
sequences. The terms sequence, set and experiment are used
somewhat loosely here, so we show below what these look like.
A single ORBIT sequence
example_data_point["seqs"][0]
['ry90p[0]',
'rx90p[0]',
'rx90p[0]',
'rx90m[0]',
'ry90p[0]',
'ry90p[0]',
'rx90p[0]',
'ry90p[0]',
'rx90p[0]',
'rx90p[0]',
'ry90p[0]',
'rx90m[0]',
'rx90p[0]',
'rx90p[0]',
'ry90p[0]',
'ry90p[0]',
'rx90p[0]',
'ry90p[0]',
'ry90m[0]',
'rx90p[0]',
'rx90p[0]',
'ry90m[0]',
'rx90p[0]',
'rx90p[0]',
'rx90p[0]',
'rx90p[0]']
Total number of ORBIT sequences in an experiment
len(example_data_point["seqs"])
20
Total number of Measurement results
len(example_data_point["results"])
20
The measurement results and the standard deviation look like this
example_results = [
(example_data_point["results"][i], example_data_point["results_std"][i])
for i in range(len(example_data_point["results"]))
]
pprint(example_results)
[([0.745], [0.013783141876945182]),
([0.213], [0.012947239087929134]),
([0.137], [0.0108734079294396]),
([0.224], [0.013184233007649706]),
([0.434], [0.015673034167001616]),
([0.105], [0.009694070352540258]),
([0.214], [0.012969348480166613]),
([0.112], [0.009972762907038352]),
([0.318], [0.014726710426975877]),
([0.122], [0.010349685985574633]),
([0.348], [0.015063067416698366]),
([0.122], [0.010349685985574633]),
([0.558], [0.01570464899321217]),
([0.186], [0.01230463327369004]),
([0.096], [0.009315793041926168]),
([0.368], [0.015250442616527561]),
([0.146], [0.011166198995181842]),
([0.121], [0.010313049985334118]),
([0.748], [0.013729384545565035]),
([0.692], [0.01459917805905524])]
The Model for Model Learning¶
An initial model needs to be provided, which we refine by fitting to our
calibration data. We do this below. If you want to learn more about what
the various components of the model mean, please refer back to the
two_qubits.ipynb
notebook or the documentation.
Define Constants¶
lindblad = False
dressed = True
qubit_lvls = 3
freq = 5.001e9
anhar = -210.001e6
init_temp = 0
qubit_temp = 0
t_final = 7e-9 # Time for single qubit gates
sim_res = 100e9
awg_res = 2e9
sideband = 50e6
lo_freq = 5e9 + sideband
Model¶
q1 = chip.Qubit(
name="Q1",
desc="Qubit 1",
freq=Qty(
value=freq,
min_val=4.995e9,
max_val=5.005e9,
unit="Hz 2pi",
),
anhar=Qty(
value=anhar,
min_val=-250e6,
max_val=-150e6,
unit="Hz 2pi",
),
hilbert_dim=qubit_lvls,
temp=Qty(value=qubit_temp, min_val=0.0, max_val=0.12, unit="K"),
)
drive = chip.Drive(
name="d1",
desc="Drive 1",
comment="Drive line 1 on qubit 1",
connected=["Q1"],
hamiltonian_func=hamiltonians.x_drive,
)
phys_components = [q1]
line_components = [drive]
init_ground = tasks.InitialiseGround(
init_temp=Qty(value=init_temp, min_val=-0.001, max_val=0.22, unit="K")
)
task_list = [init_ground]
model = Mdl(phys_components, line_components, task_list)
model.set_lindbladian(lindblad)
model.set_dressed(dressed)
Generator¶
generator = Gnr(
devices={
"LO": devices.LO(name="lo", resolution=sim_res, outputs=1),
"AWG": devices.AWG(name="awg", resolution=awg_res, outputs=1),
"DigitalToAnalog": devices.DigitalToAnalog(
name="dac", resolution=sim_res, inputs=1, outputs=1
),
"Response": devices.Response(
name="resp",
rise_time=Qty(value=0.3e-9, min_val=0.05e-9, max_val=0.6e-9, unit="s"),
resolution=sim_res,
inputs=1,
outputs=1,
),
"Mixer": devices.Mixer(name="mixer", inputs=2, outputs=1),
"VoltsToHertz": devices.VoltsToHertz(
name="v_to_hz",
V_to_Hz=Qty(value=1e9, min_val=0.9e9, max_val=1.1e9, unit="Hz/V"),
inputs=1,
outputs=1,
),
},
chains={
"d1": {
"LO": [],
"AWG": [],
"DigitalToAnalog": ["AWG"],
"Response": ["DigitalToAnalog"],
"Mixer": ["LO", "Response"],
"VoltsToHertz": ["Mixer"]
}
},
)
generator.devices["AWG"].enable_drag_2()
Gateset¶
gauss_params_single = {
"amp": Qty(value=0.45, min_val=0.4, max_val=0.6, unit="V"),
"t_final": Qty(
value=t_final, min_val=0.5 * t_final, max_val=1.5 * t_final, unit="s"
),
"sigma": Qty(value=t_final / 4, min_val=t_final / 8, max_val=t_final / 2, unit="s"),
"xy_angle": Qty(value=0.0, min_val=-0.5 * np.pi, max_val=2.5 * np.pi, unit="rad"),
"freq_offset": Qty(
value=-sideband - 0.5e6,
min_val=-60 * 1e6,
max_val=-40 * 1e6,
unit="Hz 2pi",
),
"delta": Qty(value=-1, min_val=-5, max_val=3, unit=""),
}
gauss_env_single = pulse.Envelope(
name="gauss",
desc="Gaussian comp for single-qubit gates",
params=gauss_params_single,
shape=envelopes.gaussian_nonorm,
)
nodrive_env = pulse.Envelope(
name="no_drive",
params={
"t_final": Qty(
value=t_final, min_val=0.5 * t_final, max_val=1.5 * t_final, unit="s"
)
},
shape=envelopes.no_drive,
)
carrier_parameters = {
"freq": Qty(
value=lo_freq,
min_val=4.5e9,
max_val=6e9,
unit="Hz 2pi",
),
"framechange": Qty(value=0.0, min_val=-np.pi, max_val=3 * np.pi, unit="rad"),
}
carr = pulse.Carrier(
name="carrier",
desc="Frequency of the local oscillator",
params=carrier_parameters,
)
rx90p = gates.Instruction(
name="rx90p", t_start=0.0, t_end=t_final, channels=["d1"], targets=[0]
)
QId = gates.Instruction(
name="id", t_start=0.0, t_end=t_final, channels=["d1"], targets=[0]
)
rx90p.add_component(gauss_env_single, "d1")
rx90p.add_component(carr, "d1")
QId.add_component(nodrive_env, "d1")
QId.add_component(copy.deepcopy(carr), "d1")
QId.comps["d1"]["carrier"].params["framechange"].set_value(
(-sideband * t_final) % (2 * np.pi)
)
ry90p = copy.deepcopy(rx90p)
ry90p.name = "ry90p"
rx90m = copy.deepcopy(rx90p)
rx90m.name = "rx90m"
ry90m = copy.deepcopy(rx90p)
ry90m.name = "ry90m"
ry90p.comps["d1"]["gauss"].params["xy_angle"].set_value(0.5 * np.pi)
rx90m.comps["d1"]["gauss"].params["xy_angle"].set_value(np.pi)
ry90m.comps["d1"]["gauss"].params["xy_angle"].set_value(1.5 * np.pi)
Experiment¶
parameter_map = PMap(
instructions=[QId, rx90p, ry90p, rx90m, ry90m], model=model, generator=generator
)
exp = Exp(pmap=parameter_map)
exp_opt_map = [[('Q1', 'anhar')], [('Q1', 'freq')]]
exp.pmap.set_opt_map(exp_opt_map)
Optimizer¶
datafiles = {"orbit": DATAFILE_PATH} # path to the dataset
run_name = "simple_model_learning" # name of the optimization run
dir_path = "ml_logs" # path to save the learning logs
algorithm = "cma_pre_lbfgs" # algorithm for learning
# this first does a grad-free CMA-ES and then a gradient based LBFGS
options = {
"cmaes": {
"popsize": 12,
"init_point": "True",
"stop_at_convergence": 10,
"ftarget": 4,
"spread": 0.05,
"stop_at_sigma": 0.01,
},
"lbfgs": {"maxfun": 50, "disp": 0},
} # options for the algorithms
sampling = "high_std" # how data points are chosen from the total dataset
batch_sizes = {"orbit": 2} # how many data points are chosen for learning
state_labels = {
"orbit": [
[
1,
],
[
2,
],
]
} # the excited states of the qubit model, in this case it is 3-level
opt = ModelLearning(
datafiles=datafiles,
run_name=run_name,
dir_path=dir_path,
algorithm=algorithm,
options=options,
sampling=sampling,
batch_sizes=batch_sizes,
state_labels=state_labels,
pmap=exp.pmap,
)
opt.set_exp(exp)
Model Learning¶
We are now ready to learn from the data and improve our model
opt.run()
C3:STATUS:Saving as: /home/users/anurag/c3/examples/ml_logs/simple_model_learning/2021_06_30_T_08_59_07/model_learn.log
(6_w,12)-aCMA-ES (mu_w=3.7,w_1=40%) in dimension 2 (seed=125441, Wed Jun 30 08:59:07 2021)
C3:STATUS:Adding initial point to CMA sample.
Iterat #Fevals function value axis ratio sigma min&max std t[m:s]
1 12 3.767977884544180e+00 1.0e+00 4.89e-02 4e-02 5e-02 0:31.1
termination on ftarget=4
final/bestever f-value = 3.767978e+00 3.767978e+00
incumbent solution: [-0.22224933524057258, 0.17615005514516885]
std deviation: [0.0428319357676611, 0.04699011947850928]
C3:STATUS:Saving as: /home/users/anurag/c3/examples/ml_logs/simple_model_learning/2021_06_30_T_08_59_07/confirm.log
Result of Model Learning¶
opt.current_best_goal
-0.031570491979011794
print(opt.pmap.str_parameters(opt.pmap.opt_map))
Q1-anhar : -210.057 MHz 2pi
Q1-freq : 5.000 GHz 2pi
Visualisation & Analysis of Results¶
The Model Learning logs provide a useful way to visualise the learning process and also understand what’s going wrong (or right). We now process these logs to read some data points and also plot some visualisations of the Model Learning process
Open, Clean-up and Convert Logfiles¶
LOGDIR = opt.logdir
logfile = os.path.join(LOGDIR, "model_learn.log")
with open(logfile, "r") as f:
log = f.readlines()
params_names = [
item for sublist in (ast.literal_eval(log[3].strip("\n"))) for item in sublist
]
print(params_names)
['Q1-anhar', 'Q1-freq']
data_list_dict = list()
for line in log[9:]:
if line[0] == "{":
temp_dict = ast.literal_eval(line.strip("\n"))
for index, param_name in enumerate(params_names):
temp_dict[param_name] = temp_dict["params"][index]
temp_dict.pop("params")
data_list_dict.append(temp_dict)
data_df = pd.DataFrame(data_list_dict)
Summary of Logs¶
data_df.describe()
goal | Q1-anhar | Q1-freq | |
---|---|---|---|
count | 24.000000 | 2.400000e+01 | 2.400000e+01 |
mean | 6.846330 | -2.084322e+08 | 5.000695e+09 |
std | 7.975091 | 9.620771e+06 | 4.833397e+05 |
min | -0.031570 | -2.141120e+08 | 4.999516e+09 |
25% | 1.771696 | -2.113225e+08 | 5.000466e+09 |
50% | 5.289741 | -2.100573e+08 | 5.000790e+09 |
75% | 9.288638 | -2.092798e+08 | 5.001038e+09 |
max | 37.919470 | -1.639775e+08 | 5.001476e+09 |
Best Point
best_point_file = os.path.join(LOGDIR, 'best_point_model_learn.log')
with open(best_point_file, "r") as f:
best_point = f.read()
best_point_log_dict = ast.literal_eval(best_point)
best_point_dict = dict(zip(params_names, best_point_log_dict["optim_status"]["params"]))
best_point_dict["goal"] = best_point_log_dict["optim_status"]["goal"]
print(best_point_dict)
{'Q1-anhar': -210057285.60876995, 'Q1-freq': 5000081146.481342, 'goal': -0.031570491979011794}
Plotting¶
We use matplotlib
to produce the plots below. Please make sure you
have the same installed in your python environment.
!pip install -q matplotlib
[33mWARNING: You are using pip version 21.1.2; however, version 21.1.3 is available.
You should consider upgrading via the '/home/users/anurag/.conda/envs/c3-qopt/bin/python -m pip install --upgrade pip' command.[0m
from matplotlib.ticker import MaxNLocator
from matplotlib import rcParams
from matplotlib import cycler
import matplotlib as mpl
import matplotlib.pyplot as plt
rcParams["axes.grid"] = True
rcParams["grid.linestyle"] = "--"
# enable usetex by setting it to True if LaTeX is installed
rcParams["text.usetex"] = False
rcParams["font.size"] = 16
rcParams["font.family"] = "serif"
In the plots below, the blue line shows the progress of the parameter optimization while the black and the red lines indicate the converged and true value respectively
Qubit Anharmonicity¶
plot_item = "Q1-anhar"
true_value = -210e6
fig = plt.figure(figsize=(12, 8))
ax = fig.add_subplot(111)
ax.set_xlabel("Iteration")
ax.set_ylabel(plot_item)
ax.axhline(y=true_value, color="red", linestyle="--")
ax.axhline(y=best_point_dict[plot_item], color="black", linestyle="-.")
ax.plot(data_df[plot_item])
[<matplotlib.lines.Line2D at 0x7fc3c5ab5f70>]

Qubit Frequency¶
plot_item = "Q1-freq"
true_value = 5e9
fig = plt.figure(figsize=(12, 8))
ax = fig.add_subplot(111)
ax.set_xlabel("Iteration")
ax.set_ylabel(plot_item)
ax.axhline(y=true_value, color="red", linestyle="--")
ax.axhline(y=best_point_dict[plot_item], color="black", linestyle="-.")
ax.plot(data_df[plot_item])
[<matplotlib.lines.Line2D at 0x7fc3c59aa340>]

Goal Function¶
plot_item = "goal"
fig = plt.figure(figsize=(12, 8))
ax = fig.add_subplot(111)
ax.set_xlabel("Iteration")
ax.axhline(y=best_point_dict[plot_item], color="black", linestyle="-.")
ax.set_ylabel(plot_item)
ax.plot(data_df[plot_item])
[<matplotlib.lines.Line2D at 0x7fc3c591d910>]

Sensitivity Analysis¶
Another interesting study to understand if our dataset is indeed helpful in improving certain model parameters is to perform a Sensitivity Analysis. The purpose of this exercise is to scan the Model Parameters of interest (eg, qubit frequency or anharmonicity) across a range of values and notice a prominent dip in the Model Learning Goal Function around the best-fit values
run_name = "Sensitivity"
dir_path = "sensi_logs"
algorithm = "sweep"
options = {"points": 20, "init_point": [-210e6, 5e9]}
sweep_bounds = [
[-215e6, -205e6],
[4.9985e9, 5.0015e9],
]
sense_opt = Sensitivity(
datafiles=datafiles,
run_name=run_name,
dir_path=dir_path,
algorithm=algorithm,
options=options,
sampling=sampling,
batch_sizes=batch_sizes,
state_labels=state_labels,
pmap=exp.pmap,
sweep_bounds=sweep_bounds,
sweep_map=exp_opt_map,
)
sense_opt.set_exp(exp)
sense_opt.run()
C3:STATUS:Sweeping [['Q1-anhar']]: [-215000000.0, -205000000.0]
C3:STATUS:Saving as: /home/users/anurag/c3/examples/sensi_logs/Sensitivity/2021_07_05_T_20_56_46/sensitivity.log
C3:STATUS:Sweeping [['Q1-freq']]: [4998500000.0, 5001500000.0]
C3:STATUS:Saving as: /home/users/anurag/c3/examples/sensi_logs/Sensitivity/2021_07_05_T_20_57_38/sensitivity.log
LOGDIR = sense_opt.logdir_list[0]
logfile = os.path.join(LOGDIR, "sensitivity.log")
with open(logfile, "r") as f:
log = f.readlines()
data_list_dict = list()
for line in log[9:]:
if line[0] == "{":
temp_dict = ast.literal_eval(line.strip("\n"))
param = temp_dict["params"][0]
data_list_dict.append({"param": param, "goal": temp_dict["goal"]})
data_df = pd.DataFrame(data_list_dict)
fig = plt.figure(figsize=(12, 8))
ax = fig.add_subplot(111)
ax.set_xlabel("Q1-Anharmonicity [Hz]")
ax.set_ylabel("Goal Function")
ax.axvline(x=best_point_dict["Q1-anhar"], color="black", linestyle="-.")
ax.scatter(data_df["param"], data_df["goal"])
<matplotlib.collections.PathCollection at 0x7f917a341d30>

LOGDIR = sense_opt.logdir_list[1]
logfile = os.path.join(LOGDIR, "sensitivity.log")
with open(logfile, "r") as f:
log = f.readlines()
data_list_dict = list()
for line in log[9:]:
if line[0] == "{":
temp_dict = ast.literal_eval(line.strip("\n"))
param = temp_dict["params"][0]
data_list_dict.append({"param": param, "goal": temp_dict["goal"]})
data_df = pd.DataFrame(data_list_dict)
fig = plt.figure(figsize=(12, 8))
ax = fig.add_subplot(111)
ax.set_xlabel("Q1-Frequency [Hz]")
ax.set_ylabel("Goal Function")
ax.axvline(x=best_point_dict["Q1-freq"], color="black", linestyle="-.")
ax.scatter(data_df["param"], data_df["goal"])
<matplotlib.collections.PathCollection at 0x7f917a203370>

Logs and current optimization status¶
During optimizations (optimal control, calibration, model learning), a
current best point is stored in the log folder to monitor progress.
Called on a log file it will print a
rich table of the current
status. With the -w
or -- watch
options the table will keep
updating.
c3/utils/log_reader.py -h
usage: log_reader.py [-h] [-w WATCH] log_file
positional arguments:
log_file
optional arguments:
-h, --help show this help message and exit
-w WATCH, --watch WATCH
Update the table every WATCH seconds.
Using the example log from the test folder:
c3/utils/log_reader.py test/sample_optim_log.c3log
Optimization reached 0.00462 at Tue Aug 17 15:28:09 2021
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━┓
┃ Parameter ┃ Value ┃ Gradient ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━┩
│ rx90p[0]-d1-gauss-amp │ 497.311 mV │ 18.720 mV │
│ rx90p[0]-d1-gauss-freq_offset │ -52.998 MHz 2pi │ -414.237 µHz 2pi │
│ rx90p[0]-d1-gauss-xy_angle │ -47.409 mrad │ 2.904 mrad │
│ rx90p[0]-d1-gauss-delta │ -1.077 │ 6.648 m │
└───────────────────────────────┴─────────────────┴──────────────────┘
C3 Simulator as a backend for Qiskit Experiments¶
This notebook demonstrates the use of the C3 Simulator with a high-level quantum programming framework Qiskit. You must additionally install qiskit and matplotlib to run this example.
!pip install -q qiskit matplotlib
from pprint import pprint
import numpy as np
from c3.qiskit import C3Provider
from c3.qiskit.c3_gates import RX90pGate
from qiskit import transpile, execute, QuantumCircuit, Aer
from qiskit.tools.visualization import plot_histogram
Define a basic Quantum circuit¶
qc = QuantumCircuit(3, 3)
qc.append(RX90pGate(), [0])
qc.append(RX90pGate(), [1])
<qiskit.circuit.instructionset.InstructionSet at 0x17b189980>
qc.draw()
┌────────────┐ q_0: ┤ Rx90p(π/2) ├ ├────────────┤ q_1: ┤ Rx90p(π/2) ├ └────────────┘ q_2: ────────────── c: 3/══════════════
Get the C3 Provider and Backend¶
c3_provider = C3Provider()
c3_backend = c3_provider.get_backend("c3_qasm_physics_simulator")
config = c3_backend.configuration()
print("Name: {0}".format(config.backend_name))
print("Version: {0}".format(config.backend_version))
print("Max Qubits: {0}".format(config.n_qubits))
print("OpenPulse Support: {0}".format(config.open_pulse))
print("Basis Gates: {0}".format(config.basis_gates))
Name: c3_qasm_physics_simulator
Version: 0.1
Max Qubits: 10
OpenPulse Support: False
Basis Gates: ['cx', 'rx']
Run a physical device simulation using C3¶
c3_backend.set_device_config("qiskit.cfg")
c3_job = c3_backend.run(qc)
result = c3_job.result()
No measurements in circuit "circuit-0", classical register will remain all zeros.
2022-01-01 03:30:00.931206: I tensorflow/compiler/mlir/mlir_graph_optimization_pass.cc:185] None of the MLIR Optimization Passes are enabled (registered 2)
2022-01-01 03:30:00.933640: W tensorflow/core/platform/profile_utils/cpu_utils.cc:128] Failed to get CPU frequency: 0 Hz
res_counts = result.get_counts()
pprint(res_counts)
{'000': 0.2501927838288728,
'001': 1.6223933410984962e-29,
'010': 0.27496041223138323,
'011': 1.1175740719685343e-31,
'100': 0.25116573990906244,
'101': 2.4732633272223437e-33,
'110': 0.22368106403066923,
'111': 6.525386280486658e-35}
plot_histogram(res_counts, title='C3 Physical Device simulation')

As we can see above, the c3 simulator correctly calculates the populations while accounting for non-optimal pulses and device limitations.
Run Simulation and verify results on Qiskit simulator¶
Qiskit uses little-endian bit ordering while most Quantum Computing literature uses big-endian. This is reflected in the reversed ordering of qubit labels here.
Ref: Basis Vector Ordering in Qiskit
qiskit_simulator = Aer.get_backend('qasm_simulator')
qc.measure([0, 1, 2], [0, 1, 2])
qiskit_result = execute(qc, qiskit_simulator, shots=1000).result()
counts = qiskit_result.get_counts(qc)
plot_histogram(counts, title='Qiskit simulation')
/opt/homebrew/Caskroom/miniforge/base/envs/c3-dev/lib/python3.8/site-packages/numpy/linalg/linalg.py:2159: RuntimeWarning: divide by zero encountered in det
r = _umath_linalg.det(a, signature=signature)
/opt/homebrew/Caskroom/miniforge/base/envs/c3-dev/lib/python3.8/site-packages/numpy/linalg/linalg.py:2159: RuntimeWarning: invalid value encountered in det
r = _umath_linalg.det(a, signature=signature)

API Documentation¶
C3objs¶
Basic custom objects.
- class c3.c3objs.C3obj(name, desc='', comment='', params=None)[source]¶
Bases:
object
Represents an abstract object with parameters. To be inherited from.
- Parameters
name (str) – short name that will be used as identifier
desc (str) – longer description of the component
comment (str) – additional information about the component
params (dict) – Parameters in this dict can be accessed and optimized
- class c3.c3objs.Quantity(value, unit='undefined', min_val=None, max_val=None, symbol='\\alpha')[source]¶
Bases:
object
Represents any physical quantity used in the model or the pulse specification. For arithmetic operations just the numeric value is used. The value itself is stored in an optimizer friendly way as a float between -1 and 1. The conversion is given by
scale (value + 1) / 2 + offset
- Parameters
value (np.array(np.float64) or np.float64) – value of the quantity
min_val (np.array(np.float64) or np.float64) – minimum this quantity is allowed to take
max_val (np.array(np.float64) or np.float64) – maximum this quantity is allowed to take
unit (str) – physical unit
symbol (str) – latex representation
- get_other_value(val) Tensor [source]¶
Return an arbitrary value of the same scale as this quantity as tensorflow.
- Parameters
val (tf.float64) –
dtype (tf.dtypes) –
- get_value() Tensor [source]¶
Return the value of this quantity as tensorflow.
- Parameters
val (tf.float64) –
dtype (tf.dtypes) –
Experiment module¶
Experiment class that models and simulates the whole experiment.
It combines the information about the model of the quantum device, the control stack and the operations that can be done on the device.
Given this information an experiment run is simulated, returning either processes, states or populations.
- class c3.experiment.Experiment(pmap: Optional[ParameterMap] = None, prop_method=None, sim_res=100000000000.0)[source]¶
Bases:
object
It models all of the behaviour of the physical experiment, serving as a host for the individual parts making up the experiment.
- Parameters
pmap (ParameterMap) –
including model: Model
The underlying physical device.
- generator: Generator
The infrastructure for generating and sending control signals to the device.
- gateset: GateSet
A gate level description of the operations implemented by control pulses.
- compute_final_state(solver='rk4', step_function='schrodinger')[source]¶
Solve the Lindblad master equation by integrating the differential equation of the density matrix
- Returns
Final state after time evolution.
- Return type
List
- compute_propagators()[source]¶
Compute the unitary representation of operations. If no operations are specified in self.opt_gates the complete gateset is computed.
- Returns
A dictionary of gate names and their unitary representation.
- Return type
dict
- compute_states(solver='rk4', step_function='schrodinger')[source]¶
Use a state solver to compute the trajectory of the system.
- Returns
List of states of the system from simulation.
- Return type
List[tf.tensor]
- evaluate_legacy(sequences, psi_init: Optional[Tensor] = None)[source]¶
Compute the population values for a given sequence of operations.
- Parameters
sequences (str list) – A list of control pulses/gates to perform on the device.
psi_init (tf.Tensor) – A tensor containing the initial statevector
- Returns
A list of populations
- Return type
list
- evaluate_qasm(sequences, psi_init: Optional[Tensor] = None)[source]¶
Compute the population values for a given sequence (in QASM format) of operations.
- Parameters
sequences (dict list) – A list of control pulses/gates to perform on the device in QASM format.
psi_init (tf.Tensor) – A tensor containing the initial statevector
- Returns
A list of populations
- Return type
list
- get_perfect_gates(gate_keys: Optional[list] = None) Dict[str, ndarray] [source]¶
Return a perfect gateset for the gate_keys.
- Parameters
gate_keys (list) – (Optional) List of gates to evaluate.
- Returns
A dictionary of gate names and np.array representation of the corresponding unitary
- Return type
Dict[str, np.array]
- Raises
Exception – Raise general exception for undefined gate
- load_quick_setup(filepath: str) None [source]¶
Load a quick setup file.
- Parameters
filepath (str) – Location of the configuration file
- lookup_gate(name, qubits, params=None) constant [source]¶
Returns a fixed operation or a parametric virtual Z gate. To be extended to general parametric gates.
- populations(state, lindbladian)[source]¶
Compute populations from a state or density vector.
- Parameters
state (tf.Tensor) – State or densitiy vector.
lindbladian (boolean) – Specify if conversion to density matrix is needed.
- Returns
Vector of populations.
- Return type
tf.Tensor
- process(populations, labels=None)[source]¶
Apply a readout procedure to a population vector. Very specialized at the moment.
- Parameters
populations (list) – List of populations from evaluating.
labels (list) – List of state labels specifying a subspace.
- Returns
A list of processed populations.
- Return type
list
- quick_setup(cfg, base_dir: Optional[str] = None) None [source]¶
Load a quick setup cfg and create all necessary components.
- Parameters
cfg (Dict) – Configuration options
- read_config(filepath: str) None [source]¶
Load a file and parse it to create a Model object.
- Parameters
filepath (str) – Location of the configuration file
- set_enable_store_unitaries(flag, logdir, exist_ok=False)[source]¶
Saving of unitary propagators.
- Parameters
flag (boolean) – Enable or disable saving.
logdir (str) – File path location for the resulting unitaries.
- set_opt_gates(gates)[source]¶
Specify a selection of gates to be computed.
- Parameters
gates (Identifiers of the gates of interest. Can contain duplicates.) –
- set_opt_gates_seq(seqs)[source]¶
Specify a selection of gates to be computed.
- Parameters
seqs (Identifiers of the sequences of interest. Can contain duplicates.) –
- set_prop_method(prop_method=None) None [source]¶
Configure the selected propagation method by either linking the function handle or looking it up in the library.
Model module¶
The model class, containing information on the system and its modelling.
- class c3.model.Model(subsystems=None, couplings=None, tasks=None, max_excitations=0)[source]¶
Bases:
object
What the theorist thinks about from the system.
Class to store information about our system/problem/device. Different models can represent the same system.
- Parameters
subsystems (list) – List of individual, non-interacting physical components like qubits or resonators
couplings (list) – List of interaction operators between subsystems, like couplings or drives.
tasks (list) – Badly named list of processing steps like line distortions and read out modeling
max_excitations (int) – Allow only up to max_excitations in the system
- Hs_of_t(signal, interpolate_res=2)[source]¶
Generate a list of Hamiltonians for each time step of interpolated signal for Runge-Kutta Methods.
- Parameters
signal (_type_) – Input signal
interpolate_res (int, optional) – Interpolation resolution according to RK method. Defaults to 2.
L_dag_L (tf.tensor, optional) – List of {L^dagger L} where L represents the collapse operators. Defaults to None. This is only used for stochastic case.
- Returns
List of Hamiltonians (or effective Hamiltonians for stochastic case) for each time step.
- Return type
dict
- fromdict(cfg: dict) None [source]¶
Load a file and parse it to create a Model object.
- Parameters
cfg (dict) – configuration file
- get_Frame_Rotation(t_final: float64, freqs: dict, framechanges: dict)[source]¶
Compute the frame rotation needed to align Lab frame and rotating Eigenframes of the qubits.
- Parameters
t_final (tf.float64) – Gate length
freqs (list) – Frequencies of the local oscillators.
framechanges (list) – List of framechanges. A phase shift applied to the control signal to compensate relative phases of drive oscillator and qubit.
- Returns
A (diagonal) propagator that adjust phases
- Return type
tf.Tensor
- get_Hamiltonian(signal=None)[source]¶
Get a hamiltonian with an optional signal. This will return an hamiltonian over time. Can be used e.g. for tuning the frequency of a transmon, where the control hamiltonian is not easily accessible. If max.excitation is non-zero the resulting Hamiltonian is cut accordingly
- get_dephasing_channel(t_final, amps)[source]¶
Compute the matrix of the dephasing channel to be applied on the operation.
- Parameters
t_final (tf.float64) – Duration of the operation.
amps (dict of tf.float64) – Dictionary of average amplitude on each drive line.
- Returns
Matrix representation of the dephasing channel.
- Return type
tf.tensor
- get_init_state() Tensor [source]¶
Get an initial state. If a task to compute a thermal state is set, return that.
- read_config(filepath: str) None [source]¶
Load a file and parse it to create a Model object.
- Parameters
filepath (str) – Location of the configuration file
- reorder_frame(e: constant, v: constant, ordered: bool) Tuple[constant, constant, constant] [source]¶
Reorders the new basis states according to their overlap with bare qubit states.
- set_FR(use_FR)[source]¶
Setter for the frame rotation option for adjusting the individual rotating frames of qubits when using gate sequences
- set_dressed(dressed)[source]¶
Go to a dressed frame where static couplings have been eliminated.
- Parameters
dressed (boolean) –
- set_lindbladian(lindbladian: bool) None [source]¶
Set whether to include open system dynamics.
- Parameters
lindbladian (boolean) –
- set_max_excitations(max_excitations) None [source]¶
Set the maximum number of excitations in the system used for propagation.
- update_dressed(ordered=True)[source]¶
Compute the Hamiltonians in the dressed basis by diagonalizing the drift and applying the resulting transformation to the control Hamiltonians.
- class c3.model.Model_basis_change(subsystems=None, couplings=None, tasks=None, max_excitations=0, U_transform=None)[source]¶
Bases:
Model
Model with an additional unitary basis change.
- Parameters
U_transform (tf.constant(dtype=tf.complex128)) – Unitary matrix describing the basis change of the system
Parameter map¶
ParameterMap class
- class c3.parametermap.ParameterMap(instructions: List[Instruction] = [], generator=None, model=None)[source]¶
Bases:
object
Collects information about control and model parameters and provides different representations depending on use.
- check_limits(opt_map)[source]¶
Check if all elements of equal ids have the same limits. This has to be checked against if setting values optimizer friendly.
- Parameters
opt_map –
- get_full_params() Dict[str, Quantity] [source]¶
Returns the full parameter vector, including model and control parameters.
- get_key_from_scaled_index(idx, opt_map=None) str [source]¶
Get the key of the value at position ìdx of the scaled_parameters output :param idx: :param opt_map:
- get_parameter(par_id: Tuple[str, ...]) Quantity [source]¶
Return one the current parameters.
- Parameters
par_id (tuple) – Hierarchical identifier for parameter.
- Return type
- get_parameter_dict(opt_map=None) Dict[str, Quantity] [source]¶
Return the current parameters in a dictionary including keys. :param opt_map:
- Return type
Dictionary with Quantities
- get_parameters(opt_map=None) List[Quantity] [source]¶
Return the current parameters.
- Parameters
opt_map (list) – Hierarchical identifier for parameters.
- Return type
list of Quantity
- get_parameters_scaled(opt_map=None) Tensor [source]¶
Return the current parameters. This fuction should only be called by an optimizer. Are you an optimizer?
- Parameters
opt_map (tuple) – Hierarchical identifier for parameters.
- Return type
list of Quantity
- load_values(init_point, extend_bounds=False)[source]¶
Load a previous parameter point to start the optimization from.
- Parameters
init_point (str) – File location of the initial point
extend_bounds (bool) – Whether or not to allow the loaded parameters’ bounds to be extended if they exceed those specified.
- read_config(filepath: str) None [source]¶
Load a file and parse it to create a ParameterMap object.
- Parameters
filepath (str) – Location of the configuration file
- set_parameters(values: Union[List, ndarray], opt_map=None, extend_bounds=False) None [source]¶
Set the values in the original instruction class.
- Parameters
values (list) – List of parameter values. Can be nested, if a parameter is matrix valued.
opt_map (list) – Corresponding identifiers for the parameter values.
extend_bounds (bool) – If true bounds of quantity objects will be extended.
- store_values(path: str, optim_status=None) None [source]¶
Write current parameter values to file. Stores the numeric values, as well as the names in form of the opt_map and physical units. If an optim_status is given that will be used.
- Parameters
path (str) – Location of the resulting logfile.
optim_status (dict) – Dictionary containing current parameters and goal function value.
- str_parameters(opt_map: Optional[Union[List[List[Tuple[str]]], List[List[str]]]] = None, human=False) str [source]¶
Return a multi-line human-readable string of the optmization parameter names and current values.
- Parameters
opt_map (list) – Optionally use only the specified parameters.
- Returns
Parameters and their values
- Return type
str
Main module¶
Base script to run the C3 code from a main config file.
- c3.main.run_cfg(cfg, opt_config_filename, debug=False)[source]¶
Execute an optimization problem described in the cfg file.
- Parameters
cfg (Dict[str, Union[str, int, float]]) – Configuration file containing optimization options and information needed to completely setup the system and optimization problem.
debug (bool, optional) – Skip running the actual optimization, by default False
Module contents¶
Subpackages¶
Generator package¶
Submodules¶
Devices module¶
- class c3.generator.devices.AWG(**props)[source]¶
Bases:
Device
AWG device, transforms digital input to analog signal.
- Parameters
logdir (str) – Filepath to store generated waveforms.
- create_IQ(instr: Instruction, chan: str, inputs: List[Dict[str, Any]]) dict [source]¶
Construct the in-phase (I) and quadrature (Q) components of the signal. These are universal to either experiment or simulation. In the xperiment these will be routed to AWG and mixer electronics, while in the simulation they provide the shapes of the instruction fields to be added to the Hamiltonian.
- Parameters
channel (str) – Identifier for the selected drive line.
components (dict) – Separate signals to be combined onto this drive line.
t_start (float) – Beginning of the signal.
t_end (float) – End of the signal.
- Returns
Waveforms as I and Q components.
- Return type
dict
- class c3.generator.devices.CouplingTuning(two_inputs=False, **props)[source]¶
Bases:
Device
Coupling dependent on frequency of coupled elements.
- Parameters
- process(instr: Instruction, chan: str, signal_in: List[Dict[str, Any]]) Dict[str, Any] [source]¶
Compute the qubit frequency resulting from an applied flux.
- Parameters
signal (tf.float64) –
- Returns
Qubit frequency.
- Return type
tf.float64
- class c3.generator.devices.Crosstalk(**props)[source]¶
Bases:
Device
Device to phenomenologically include crosstalk in the model by explicitly mixing drive lines.
- crosstalk_matrix: tf.constant
Matrix description of how to mix drive channels.
Examples
xtalk = Crosstalk( name="crosstalk", channels=["TC1", "TC2"], crosstalk_matrix=Quantity( value=[[1, 0], [0, 1]], min_val=[[0, 0], [0, 0]], max_val=[[1, 1], [1, 1]], unit="", ), )
- process(instr: Instruction, chan: str, signals: List[Dict[str, Any]]) Dict[str, Any] [source]¶
Mix channels in the input signal according to a crosstalk matrix.
- Parameters
signal (Dict[str, Any]) –
Dictionary of several signals identified by their channel as dict keys, e.g.
signal = { "TC1": {"values": [0, 0.5, 1, 1, ...]}, "TC2": {"values": [1, 1, 1, 1, ...], }
- Returns
signal
- Return type
Dict[str, Any]
- class c3.generator.devices.DC_Noise(**props)[source]¶
Bases:
Additive_Noise
Add a random constant offset to the signals
- class c3.generator.devices.Device(**props)[source]¶
Bases:
C3obj
A Device that is part of the stack generating the instruction signals.
- Parameters
resolution (float) – Number of samples per second this device operates at.
- calc_slice_num(t_start: float = 0.0, t_end: float = 0.0) None [source]¶
Effective number of time slices given start, end and resolution.
- Parameters
t_start (float) – Starting time for this device.
t_end (float) – End time for this device.
- create_ts(t_start: float = 0, t_end: float = 0, centered: bool = True) constant [source]¶
Compute time samples.
- Parameters
t_start (float) – Starting time for this device.
t_end (float) – End time for this device.
centered (boolean) – Sample in the middle of an interval, otherwise at the beginning.
- process(instr: Instruction, chan: str, signals: List[Dict[str, Any]]) Dict[str, Any] [source]¶
To be implemented by inheriting class.
- Parameters
instr (Instruction) – Information about the instruction or gate to be excecuted.
chan (str) – Identifier of the drive line
signals (List[Dict[str, Any]]) – List of potentially multiple input signals to this device.
- Returns
Output signal of this device.
- Return type
Dict[str, Any]
- Raises
NotImplementedError –
- class c3.generator.devices.DigitalToAnalog(**props)[source]¶
Bases:
Device
Take the values at the awg resolution to the simulation resolution.
- process(instr: Instruction, chan: str, awg_signal: List[Dict[str, Any]]) Dict[str, Any] [source]¶
Resample the awg values to higher resolution.
- Parameters
instr (Instruction) – The logical instruction or qubit operation for which the signal is generated.
chan (str) – Specifies which channel is being processed if needed.
awg_signal (dict) – Dictionary of several signals identified by their channel as dict keys.
- Returns
Inphase and Quadrature compontent of the upsampled signal.
- Return type
dict
- class c3.generator.devices.ExponentialIIR(**props)[source]¶
Bases:
StepFuncFilter
Implement IIR filter with step response of the form s(t) = (1 + A * exp(-t / t_iir) )
- class c3.generator.devices.Filter(**props)[source]¶
Bases:
Device
Apply a filter function to the signal.
- process(instr: Instruction, chan: str, Hz_signal: List[Dict[str, Any]]) Dict[str, Any] [source]¶
Apply a filter function to the signal.
- class c3.generator.devices.FluxTuning(**props)[source]¶
Bases:
Device
Flux tunable qubit frequency.
- Parameters
- process(instr: Instruction, chan: str, signal_in: List[Dict[str, Any]]) Dict[str, Any] [source]¶
Compute the qubit frequency resulting from an applied flux.
- Parameters
signal (tf.float64) –
- Returns
Qubit frequency.
- Return type
tf.float64
- class c3.generator.devices.FluxTuningLinear(**props)[source]¶
Bases:
Device
Flux tunable qubit frequency linear adjustment.
- Parameters
- class c3.generator.devices.HighpassExponential(**props)[source]¶
Bases:
StepFuncFilter
Implement Highpass filter based on exponential with step response of the form s(t) = exp(-t / t_hp)
- class c3.generator.devices.HighpassFilter(**props)[source]¶
Bases:
Device
Introduce a highpass filter
- Parameters
cutoff (Quantity) – cutoff frequency of highpass filter
keep_mean (bool) – should the mean of the signal be restored
- class c3.generator.devices.LO(**props)[source]¶
Bases:
Device
Local oscillator device, generates a constant oscillating signal.
- process(instr: Instruction, chan: str, signal: List[Dict[str, Any]]) dict [source]¶
To be implemented by inheriting class.
- Parameters
instr (Instruction) – Information about the instruction or gate to be excecuted.
chan (str) – Identifier of the drive line
signals (List[Dict[str, Any]]) – List of potentially multiple input signals to this device.
- Returns
Output signal of this device.
- Return type
Dict[str, Any]
- Raises
NotImplementedError –
- class c3.generator.devices.LONoise(**props)[source]¶
Bases:
Device
Noise applied to the local oscillator
- class c3.generator.devices.Mixer(**props)[source]¶
Bases:
Device
Mixer device, combines inputs from the local oscillator and the AWG.
- process(instr: Instruction, chan: str, inputs: List[Dict[str, Any]]) Dict[str, Any] [source]¶
Combine signal from AWG and LO.
- Parameters
lo_signal (dict) – Local oscillator signal.
awg_signal (dict) – Waveform generator signal.
- Returns
Mixed signal.
- Return type
dict
- class c3.generator.devices.Pink_Noise(**props)[source]¶
Bases:
Additive_Noise
Device creating pink noise, i.e. 1/f noise.
- class c3.generator.devices.Readout(**props)[source]¶
Bases:
Device
Mimic the readout process by multiplying a state phase with a factor and offset.
- class c3.generator.devices.Response(**props)[source]¶
Bases:
Device
Make the AWG signal physical by convolution with a Gaussian to limit bandwith.
- Parameters
rise_time (Quantity) – Time constant for the gaussian convolution.
- class c3.generator.devices.ResponseFFT(**props)[source]¶
Bases:
Device
Make the AWG signal physical by convolution with a Gaussian to limit bandwith.
- Parameters
rise_time (Quantity) – Time constant for the gaussian convolution.
- class c3.generator.devices.SkinEffectResponse(**props)[source]¶
Bases:
StepFuncFilter
Implement Highpass filter based on exponential with step response of the form s(t) = exp(-t / t_hp)
- class c3.generator.devices.StepFuncFilter(**props)[source]¶
Bases:
Device
Base class for filters that are based on the step response function Step function has to be defined explicetly
- process(instr, chan, signal_in: List[Dict[str, Any]]) Dict[str, Any] [source]¶
To be implemented by inheriting class.
- Parameters
instr (Instruction) – Information about the instruction or gate to be excecuted.
chan (str) – Identifier of the drive line
signals (List[Dict[str, Any]]) – List of potentially multiple input signals to this device.
- Returns
Output signal of this device.
- Return type
Dict[str, Any]
- Raises
NotImplementedError –
- class c3.generator.devices.VoltsToHertz(**props)[source]¶
Bases:
Device
Convert the voltage signal to an amplitude to plug into the model Hamiltonian.
- Parameters
V_to_Hz (Quantity) – Conversion factor.
offset (tf.float64) – Drive frequency offset.
- process(instr: Instruction, chan: str, mixed_signal: List[Dict[str, Any]]) Dict[str, Any] [source]¶
Transform signal from value of V to Hz.
- Parameters
mixed_signal (tf.Tensor) – Waveform as line voltages after IQ mixing
- Returns
Waveform as control amplitudes
- Return type
tf.Tensor
Generator module¶
Signal generation stack.
Contrary to most quanutm simulators, C^3 includes a detailed simulation of the control stack. Each component in the stack and its functions are simulated individually and combined here.
Example: A local oscillator and arbitrary waveform generator signal are put through via a mixer device to produce an effective modulated signal.
- class c3.generator.generator.Generator(devices: Optional[dict] = None, chains: Optional[dict] = None, resolution: float = 0.0, callback: Optional[Callable] = None)[source]¶
Bases:
object
Generator, creates signal from digital to what arrives to the chip.
- Parameters
devices (list) – Physical or abstract devices in the signal processing chain.
resolution (np.float64) – Resolution at which continuous functions are sampled.
callback (Callable) – Function that is called after each device in the signal line.
- generate_signals(instr: Instruction) dict [source]¶
Perform the signal chain for a specified instruction, including local oscillator, AWG generation and IQ mixing.
- Parameters
instr (Instruction) – Operation to be performed, e.g. logical gate.
- Returns
Signal to be applied to the physical device.
- Return type
dict
Module contents¶
Libraries package¶
Libraries contain a collection of functions that all share a signature to be used interchangably. One entry of a library is selected in the corresponding config file.
Algorithms module¶
Collection of (optimization) algorithms. All entries share a common signature with optional arguments.
- c3.libraries.algorithms.adaptive_scan(x_init, fun=None, fun_grad=None, grad_lookup=None, options={})[source]¶
One dimensional scan of the function values around the initial point, using adaptive sampling
- Parameters
x_init (float) – Initial point
fun (callable) – Goal function
fun_grad (callable) – Function that computes the gradient of the goal function
grad_lookup (callable) – Lookup a previously computed gradient
options (dict) –
Options include
- accuracy_goal: float
Targeted accuracy for the sampling algorithm
- probe_listlist
Points to definitely include in the sampling
- init_pointboolean
Include the initial point in the sampling
- c3.libraries.algorithms.cma_pre_lbfgs(x_init, fun=None, fun_grad=None, grad_lookup=None, options={})[source]¶
Performs a CMA-Es optimization and feeds the result into LBFG-S for further refinement.
- c3.libraries.algorithms.cmaes(x_init, fun=None, fun_grad=None, grad_lookup=None, options={})[source]¶
Wrapper for the pycma implementation of CMA-Es. See also:
http://cma.gforge.inria.fr/apidocs-pycma/
- Parameters
x_init (float) – Initial point.
fun (callable) – Goal function.
fun_grad (callable) – Function that computes the gradient of the goal function.
grad_lookup (callable) – Lookup a previously computed gradient.
options (dict) –
Options of pycma and the following custom options.
- noisefloat
Artificial noise added to a function evaluation.
- init_pointboolean
Force the use of the initial point in the first generation.
- spreadfloat
Adjust the parameter spread of the first generation cloud.
- stop_at_convergenceint
Custom stopping condition. Stop if the cloud shrunk for this number of generations.
- stop_at_sigmafloat
Custom stopping condition. Stop if the cloud shrunk to this standard deviation.
- Returns
Parameters of the best point.
- Return type
np.ndarray
- c3.libraries.algorithms.gcmaes(x_init, fun=None, fun_grad=None, grad_lookup=None, options={})[source]¶
EXPERIMENTAL CMA-Es where every point in the cloud is optimized with LBFG-S and the resulting cloud and results are used for the CMA update.
- c3.libraries.algorithms.grid2D(x_init, fun=None, fun_grad=None, grad_lookup=None, options={})[source]¶
Two dimensional scan of the function values around the initial point.
- Parameters
x_init (float) – Initial point
fun (callable) – Goal function
fun_grad (callable) – Function that computes the gradient of the goal function
grad_lookup (callable) – Lookup a previously computed gradient
options (dict) –
Options include points : int
The number of samples
- boundslist
Range of the scan for both dimensions
- c3.libraries.algorithms.lbfgs(x_init, fun=None, fun_grad=None, grad_lookup=None, options={})[source]¶
Wrapper for the scipy.optimize.minimize implementation of LBFG-S. See also:
https://docs.scipy.org/doc/scipy/reference/optimize.minimize-lbfgsb.html
- Parameters
x_init (float) – Initial point
fun (callable) – Goal function
fun_grad (callable) – Function that computes the gradient of the goal function
grad_lookup (callable) – Lookup a previously computed gradient
options (dict) – Options of scipy.optimize.minimize
- Returns
Scipy result object.
- Return type
Result
- c3.libraries.algorithms.lbfgs_grad_free(x_init, fun=None, fun_grad=None, grad_lookup=None, options={})[source]¶
Wrapper for the scipy.optimize.minimize implementation of LBFG-S. We let the algorithm determine the gradient by its own.
See also:
https://docs.scipy.org/doc/scipy/reference/optimize.minimize-lbfgsb.html
- Parameters
x_init (float) – Initial point
fun (callable) – Goal function
fun_grad (callable) – Function that computes the gradient of the goal function
grad_lookup (callable) – Lookup a previously computed gradient
options (dict) – Options of scipy.optimize.minimize
- Returns
Scipy result object.
- Return type
Result
- c3.libraries.algorithms.single_eval(x_init, fun=None, fun_grad=None, grad_lookup=None, options={})[source]¶
Return the function value at given point.
- Parameters
x_init (float) – Initial point
fun (callable) – Goal function
fun_grad (callable) – Function that computes the gradient of the goal function
grad_lookup (callable) – Lookup a previously computed gradient
options (dict) – Algorithm specific options
- c3.libraries.algorithms.sweep(x_init, fun=None, fun_grad=None, grad_lookup=None, options={})[source]¶
One dimensional scan of the function values around the initial point.
- Parameters
x_init (float) – Initial point
fun (callable) – Goal function
fun_grad (callable) – Function that computes the gradient of the goal function
grad_lookup (callable) – Lookup a previously computed gradient
options (dict) –
Options include points : int
The number of samples
- boundslist
Range of the scan
- c3.libraries.algorithms.tf_adadelta(x_init: ndarray, fun: Optional[Callable] = None, fun_grad: Optional[Callable] = None, grad_lookup: Optional[Callable] = None, options: dict = {}) OptimizeResult [source]¶
Optimize using TensorFlow Adadelta https://www.tensorflow.org/api_docs/python/tf/keras/optimizers/Adadelta
- Parameters
x_init (np.ndarray) – starting value of parameter(s)
fun (Callable, optional) – function to minimize, by default None
fun_grad (Callable, optional) – gradient of function to minimize, by default None
grad_lookup (Callable, optional) – lookup stored gradients, by default None
options (dict, optional) – optional parameters for optimizer, by default {}
- Returns
SciPy OptimizeResult type object with final parameters
- Return type
OptimizeResult
- c3.libraries.algorithms.tf_adam(x_init: ndarray, fun: Optional[Callable] = None, fun_grad: Optional[Callable] = None, grad_lookup: Optional[Callable] = None, options: dict = {}) OptimizeResult [source]¶
Optimize using TensorFlow ADAM https://www.tensorflow.org/api_docs/python/tf/keras/optimizers/Adam
- Parameters
x_init (np.ndarray) – starting value of parameter(s)
fun (Callable, optional) – function to minimize, by default None
fun_grad (Callable, optional) – gradient of function to minimize, by default None
grad_lookup (Callable, optional) – lookup stored gradients, by default None
options (dict, optional) – optional parameters for optimizer, by default {}
- Returns
SciPy OptimizeResult type object with final parameters
- Return type
OptimizeResult
- c3.libraries.algorithms.tf_rmsprop(x_init: ndarray, fun: Optional[Callable] = None, fun_grad: Optional[Callable] = None, grad_lookup: Optional[Callable] = None, options: dict = {}) OptimizeResult [source]¶
Optimize using TensorFlow RMSProp https://www.tensorflow.org/api_docs/python/tf/keras/optimizers/RMSprop
- Parameters
x_init (np.ndarray) – starting value of parameter(s)
fun (Callable, optional) – function to minimize, by default None
fun_grad (Callable, optional) – gradient of function to minimize, by default None
grad_lookup (Callable, optional) – lookup stored gradients, by default None
options (dict, optional) – optional parameters for optimizer, by default {}
- Returns
SciPy OptimizeResult type object with final parameters
- Return type
OptimizeResult
- c3.libraries.algorithms.tf_sgd(x_init: ndarray, fun: Optional[Callable] = None, fun_grad: Optional[Callable] = None, grad_lookup: Optional[Callable] = None, options: dict = {}) OptimizeResult [source]¶
Optimize using TensorFlow Stochastic Gradient Descent with Momentum https://www.tensorflow.org/api_docs/python/tf/keras/optimizers/SGD
- Parameters
x_init (np.ndarray) – starting value of parameter(s)
fun (Callable, optional) – function to minimize, by default None
fun_grad (Callable, optional) – gradient of function to minimize, by default None
grad_lookup (Callable, optional) – lookup stored gradients, by default None
options (dict, optional) – optional parameters for optimizer, by default {}
- Returns
SciPy OptimizeResult type object with final parameters
- Return type
OptimizeResult
Chip module¶
Component class and subclasses for the components making up the quantum device.
- class c3.libraries.chip.CShuntFluxQubit(name: str, desc: Optional[str] = None, comment: Optional[str] = None, hilbert_dim: Optional[int] = None, calc_dim: Optional[int] = None, EC: Optional[Quantity] = None, EJ: Optional[Quantity] = None, EL: Optional[Quantity] = None, phi: Optional[Quantity] = None, phi_0: Optional[Quantity] = None, gamma: Optional[Quantity] = None, d: Optional[Quantity] = None, t1: Optional[Quantity] = None, t2star: Optional[Quantity] = None, temp: Optional[Quantity] = None, anhar: Optional[Quantity] = None, params={}, resolution=None)[source]¶
Bases:
Qubit
- get_Hamiltonian(signal: Optional[Union[dict, bool]] = None, transform: Optional[Tensor] = None) Tensor [source]¶
Calculate the hamiltonian :returns: Hamiltonian :rtype: tf.Tensor
- get_freq(phi_sig=0)¶
- class c3.libraries.chip.CShuntFluxQubitCos(name: str, desc: Optional[str] = None, comment: Optional[str] = None, hilbert_dim: Optional[int] = None, calc_dim: Optional[int] = None, EC: Optional[Quantity] = None, EJ: Optional[Quantity] = None, EL: Optional[Quantity] = None, phi: Optional[Quantity] = None, phi_0: Optional[Quantity] = None, gamma: Optional[Quantity] = None, d: Optional[Quantity] = None, t1: Optional[Quantity] = None, t2star: Optional[Quantity] = None, temp: Optional[Quantity] = None, anhar: Optional[Quantity] = None, params=None)[source]¶
Bases:
Qubit
- get_Hamiltonian(signal: Optional[Union[dict, bool]] = None, transform: Optional[Tensor] = None)[source]¶
Compute the Hamiltonian. Multiplies the number operator with the frequency and anharmonicity with the Duffing part and returns their sum.
- Returns
Hamiltonian
- Return type
tf.Tensor
- class c3.libraries.chip.Coupling(name, desc=None, comment=None, strength: Optional[Quantity] = None, connected: Optional[List[str]] = None, params=None, hamiltonian_func=None)[source]¶
Bases:
LineComponent
Represents a coupling behaviour between elements.
- Parameters
strength (Quantity) – coupling strength
connected (list) – all physical components coupled via this specific coupling
- class c3.libraries.chip.Coupling_Drive(**props)[source]¶
Bases:
Drive
Represents a drive line that couples multiple qubits.
- Parameters
connected (list) – all physical components receiving driving signals via this line
- class c3.libraries.chip.Drive(**props)[source]¶
Bases:
LineComponent
Represents a drive line.
- Parameters
connected (list) – all physical components receiving driving signals via this line
- class c3.libraries.chip.Fluxonium(name: str, desc: Optional[str] = None, comment: Optional[str] = None, hilbert_dim: Optional[int] = None, calc_dim: Optional[int] = None, EC: Optional[Quantity] = None, EJ: Optional[Quantity] = None, EL: Optional[Quantity] = None, phi: Optional[Quantity] = None, phi_0: Optional[Quantity] = None, gamma: Optional[Quantity] = None, t1: Optional[Quantity] = None, t2star: Optional[Quantity] = None, temp: Optional[Quantity] = None, params=None)[source]¶
Bases:
CShuntFluxQubit
- class c3.libraries.chip.LineComponent(**props)[source]¶
Bases:
C3obj
Represents the components connecting chip elements and drives.
- Parameters
connected (list) – specifies the component that are connected with this line
- class c3.libraries.chip.PhysicalComponent(**props)[source]¶
Bases:
C3obj
Represents the components making up a chip.
- Parameters
hilbert_dim (int) – Dimension of the Hilbert space of this component
- get_Hamiltonian(signal: Optional[Union[dict, bool]] = None, transform: Optional[Tensor] = None) Dict[str, Tensor] [source]¶
Compute the Hamiltonian. :param signal: dictionary with signals to be used a time dependend Hamiltonian. By default “values” key will be used.
If true value control hamiltonian will be returned, used for later combination of signal and hamiltonians.
- Parameters
transform – transform the hamiltonian, e.g. for expressing the hamiltonian in the expressed basis. Use this function if transform will be necessary and signal is given, in order to apply the transform only on single hamiltonians instead of all timeslices.
- get_transformed_hamiltonians(transform: Optional[Tensor] = None)[source]¶
get transformed hamiltonians with given applied transformation. The Hamiltonians are assumed to be stored in Hs. :param transform: transform to be applied to the hamiltonians. Default: None for returning the hamiltonians without transformation applied.
- class c3.libraries.chip.Qubit(name, hilbert_dim, desc=None, comment=None, freq: Optional[Quantity] = None, anhar: Optional[Quantity] = None, t1: Optional[Quantity] = None, t2star: Optional[Quantity] = None, temp: Optional[Quantity] = None, params=None)[source]¶
Bases:
PhysicalComponent
Represents the element in a chip functioning as qubit.
- Parameters
freq (Quantity) – frequency of the qubit
anhar (Quantity) – anharmonicity of the qubit. defined as w01 - w12
t1 (Quantity) – t1, the time decay of the qubit due to dissipation
t2star (Quantity) – t2star, the time decay of the qubit due to pure dephasing
temp (Quantity) – temperature of the qubit, used to determine the Boltzmann distribution of energy level populations
- get_Hamiltonian(signal: Optional[Union[dict, bool]] = None, transform: Optional[Tensor] = None)[source]¶
Compute the Hamiltonian. Multiplies the number operator with the frequency and anharmonicity with the Duffing part and returns their sum.
- Returns
Hamiltonian
- Return type
tf.Tensor
- get_Lindbladian(dims)[source]¶
Compute the Lindbladian, based on relaxation, dephasing constants and finite temperature.
- Returns
Hamiltonian
- Return type
tf.Tensor
- class c3.libraries.chip.Resonator(**props)[source]¶
Bases:
PhysicalComponent
Represents the element in a chip functioning as resonator.
- Parameters
freq (Quantity) – frequency of the resonator
- get_Hamiltonian(signal: Optional[Union[dict, bool]] = None, transform: Optional[Tensor] = None)[source]¶
Compute the Hamiltonian.
- class c3.libraries.chip.SNAIL(name: str, desc: str = ' ', comment: str = ' ', hilbert_dim: int = 4, freq: Optional[Quantity] = None, anhar: Optional[Quantity] = None, beta: Optional[Quantity] = None, t1: Optional[Quantity] = None, t2star: Optional[Quantity] = None, temp: Optional[Quantity] = None, params: Optional[dict] = None)[source]¶
Bases:
Qubit
Represents the element in a chip functioning as a three wave mixing element also knwon as a SNAIL. Reference: https://arxiv.org/pdf/1702.00869.pdf :param freq: frequency of the qubit :type freq: Quantity :param anhar: anharmonicity of the qubit. defined as w01 - w12 :type anhar: Quantity :param beta: third order non_linearity of the qubit. :type beta: Quantity :param t1: t1, the time decay of the qubit due to dissipation :type t1: Quantity :param t2star: t2star, the time decay of the qubit due to pure dephasing :type t2star: Quantity :param temp: temperature of the qubit, used to determine the Boltzmann distribution
of energy level populations
- Parameters
beta. (Class is mostly an exact copy of the Qubit class. The only difference is the added third order non linearity with a prefactor) –
linearity (The only modification is the get hamiltonian and init hamiltonian definition. Also imported the necessary third order non) –
library. (from the hamiltonian) –
- class c3.libraries.chip.Transmon(name: str, desc: Optional[str] = None, comment: Optional[str] = None, hilbert_dim: Optional[int] = None, freq: Optional[Quantity] = None, anhar: Optional[Quantity] = None, phi: Optional[Quantity] = None, phi_0: Optional[Quantity] = None, gamma: Optional[Quantity] = None, d: Optional[Quantity] = None, t1: Optional[Quantity] = None, t2star: Optional[Quantity] = None, temp: Optional[Quantity] = None, params=None)[source]¶
Bases:
PhysicalComponent
Represents the element in a chip functioning as tunanble transmon qubit.
- Parameters
- get_Hamiltonian(signal: Optional[Union[dict, bool]] = None, transform: Optional[Tensor] = None)[source]¶
Compute the Hamiltonian. :param signal: dictionary with signals to be used a time dependend Hamiltonian. By default “values” key will be used.
If true value control hamiltonian will be returned, used for later combination of signal and hamiltonians.
- Parameters
transform – transform the hamiltonian, e.g. for expressing the hamiltonian in the expressed basis. Use this function if transform will be necessary and signal is given, in order to apply the transform only on single hamiltonians instead of all timeslices.
- class c3.libraries.chip.TransmonExpanded(name: str, desc: Optional[str] = None, comment: Optional[str] = None, hilbert_dim: Optional[int] = None, freq: Optional[Quantity] = None, anhar: Optional[Quantity] = None, phi: Optional[Quantity] = None, phi_0: Optional[Quantity] = None, gamma: Optional[Quantity] = None, d: Optional[Quantity] = None, t1: Optional[Quantity] = None, t2star: Optional[Quantity] = None, temp: Optional[Quantity] = None, params=None)[source]¶
Bases:
Transmon
- get_Hamiltonian(signal: Optional[Union[dict, bool]] = None, transform: Optional[Tensor] = None)[source]¶
Compute the Hamiltonian. :param signal: dictionary with signals to be used a time dependend Hamiltonian. By default “values” key will be used.
If true value control hamiltonian will be returned, used for later combination of signal and hamiltonians.
- Parameters
transform – transform the hamiltonian, e.g. for expressing the hamiltonian in the expressed basis. Use this function if transform will be necessary and signal is given, in order to apply the transform only on single hamiltonians instead of all timeslices.
Constants module¶
All physical constants used in other code.
Envelopes module¶
Library of envelope functions.
All functions assume the input of a time vector and return complex amplitudes.
- c3.libraries.envelopes.cosine(t, params)[source]¶
Cosine-shaped envelope. Maximum value is 1, area is given by length.
- Parameters
params (dict) –
- t_finalfloat
Total length of the Gaussian.
- sigma: float
Width of the Gaussian.
- c3.libraries.envelopes.cosine_flattop(t, params)[source]¶
Cosine-shaped envelope. Maximum value is 1, area is given by length.
- Parameters
params (dict) –
- t_finalfloat
Total length of the Gaussian.
- sigma: float
Width of the Gaussian.
- c3.libraries.envelopes.delta_pulse(t, params)[source]¶
Pulse shape which gives an output only at a given time bin
- c3.libraries.envelopes.flattop(t, params)[source]¶
Flattop gaussian with width of length risefall, modelled by error functions.
- Parameters
params (dict) –
- t_upfloat
Center of the ramp up.
- t_downfloat
Center of the ramp down.
- risefallfloat
Length of the ramps.
- c3.libraries.envelopes.flattop_cut(t, params)[source]¶
Flattop gaussian with width of length risefall, modelled by error functions.
- Parameters
params (dict) –
- t_upfloat
Center of the ramp up.
- t_downfloat
Center of the ramp down.
- risefallfloat
Length of the ramps.
- c3.libraries.envelopes.flattop_cut_center(t, params)[source]¶
Flattop gaussian with width of length risefall, modelled by error functions.
- Parameters
params (dict) –
- t_upfloat
Center of the ramp up.
- t_downfloat
Center of the ramp down.
- risefallfloat
Length of the ramps.
- c3.libraries.envelopes.flattop_risefall(t, params)[source]¶
Flattop gaussian with width of length risefall, modelled by error functions.
- Parameters
params (dict) –
- t_finalfloat
Total length of pulse.
- risefallfloat
Length of the ramps. Position of ramps is so that the pulse starts with the start of the ramp-up and ends at the end of the ramp-down
- c3.libraries.envelopes.flattop_risefall_1ns(t, params)[source]¶
Flattop gaussian with fixed width of 1ns.
- c3.libraries.envelopes.fourier_cos(t, params)[source]¶
Fourier basis of the pulse constant pulse (cos).
- Parameters
params (dict) –
- ampslist
Weights of the fourier components
- freqslist
Frequencies of the fourier components
- c3.libraries.envelopes.fourier_sin(t, params)[source]¶
Fourier basis of the pulse constant pulse (sin).
- Parameters
params (dict) –
- ampslist
Weights of the fourier components
- freqslist
Frequencies of the fourier components
- c3.libraries.envelopes.gaussian_der(t, params)[source]¶
Derivative of the normalized gaussian (ifself not normalized).
- c3.libraries.envelopes.gaussian_der_nonorm(t, params)[source]¶
Derivative of the normalized gaussian (ifself not normalized).
- c3.libraries.envelopes.gaussian_nonorm(t, params)[source]¶
Non-normalized gaussian. Maximum value is 1, area is given by length.
- Parameters
params (dict) –
- t_finalfloat
Total length of the Gaussian.
- sigma: float
Width of the Gaussian.
- c3.libraries.envelopes.gaussian_sigma(t, params)[source]¶
Normalized gaussian. Total area is 1, maximum is determined accordingly.
- Parameters
params (dict) –
- t_finalfloat
Total length of the Gaussian.
- sigma: float
Width of the Gaussian.
- c3.libraries.envelopes.pwc_shape(t, params)[source]¶
Piecewise constant pulse while defining only a given number of samples, while interpolating linearly between those. :param t: :param params: t_bin_start/t_bin_end can be used to specify specific range. e.g. timepoints taken from awg.
- c3.libraries.envelopes.pwc_symmetric(t, params)[source]¶
symmetic PWC pulse This works only for inphase component
Estimators module¶
Collection of estimator functions, to compare two sets of (noisy) data.
- c3.libraries.estimators.estimator_reg_deco(func)[source]¶
Decorator for making registry of functions
- c3.libraries.estimators.mean_dist(exp_values, sim_values, exp_stds, shots)[source]¶
Return the root mean squared of the differences.
- c3.libraries.estimators.mean_exp_stds_dist(exp_values, sim_values, exp_stds, shots)[source]¶
Return the mean of the distance in number of exp_stds away.
- c3.libraries.estimators.mean_sim_stds_dist(exp_values, sim_values, exp_stds, shots)[source]¶
Return the mean of the distance in number of exp_stds away.
- c3.libraries.estimators.median_dist(exp_values, sim_values, exp_stds, shots)[source]¶
Return the median of the differences.
- c3.libraries.estimators.neg_loglkh_binom(exp_values, sim_values, exp_stds, shots)[source]¶
Average likelihood of the experimental values with binomial distribution.
Return the likelihood of the experimental values given the simulated values, and given a binomial distribution function.
- c3.libraries.estimators.neg_loglkh_binom_norm(exp_values, sim_values, exp_stds, shots)[source]¶
Average likelihood of the exp values with normalised binomial distribution.
Return the likelihood of the experimental values given the simulated values, and given a binomial distribution function that is normalised to give probability 1 at the top of the distribution.
- c3.libraries.estimators.neg_loglkh_gauss(exp_values, sim_values, exp_stds, shots)[source]¶
Likelihood of the experimental values.
The distribution is assumed to be binomial (approximated by a gaussian).
- c3.libraries.estimators.neg_loglkh_gauss_norm(exp_values, sim_values, exp_stds, shots)[source]¶
Likelihood of the experimental values.
The distribution is assumed to be binomial (approximated by a gaussian) that is normalised to give probability 1 at the top of the distribution.
- c3.libraries.estimators.neg_loglkh_gauss_norm_sum(exp_values, sim_values, exp_stds, shots)[source]¶
Likelihood of the experimental values.
The distribution is assumed to be binomial (approximated by a gaussian) that is normalised to give probability 1 at the top of the distribution.
- c3.libraries.estimators.neg_loglkh_multinom(exp_values, sim_values, exp_stds, shots)[source]¶
Average likelihood of the experimental values with multinomial distribution.
Return the likelihood of the experimental values given the simulated values, and given a multinomial distribution function.
- c3.libraries.estimators.neg_loglkh_multinom_norm(exp_values, sim_values, exp_stds, shots)[source]¶
Average likelihood of the experimental values with multinomial distribution.
Return the likelihood of the experimental values given the simulated values, and given a multinomial distribution function that is normalised to give probability 1 at the top of the distribution.
- c3.libraries.estimators.rms_dist(exp_values, sim_values, exp_stds, shots)[source]¶
Return the root mean squared of the differences.
- c3.libraries.estimators.rms_exp_stds_dist(exp_values, sim_values, exp_stds, shots)[source]¶
Return the root mean squared of the differences measured in exp_stds.
Fidelities module¶
Library of fidelity functions.
- c3.libraries.fidelities.RB(propagators, min_length: int = 5, max_length: int = 500, num_lengths: int = 20, num_seqs: int = 30, logspace=False, lindbladian=False, padding='')[source]¶
- c3.libraries.fidelities.average_infid(ideal: ndarray, actual: Tensor, index: List[int] = [0], dims=[2]) constant [source]¶
Average fidelity uses the Pauli basis to compare. Thus, perfect gates are always 2x2 (per qubit) and the actual unitary needs to be projected down.
- Parameters
ideal (np.ndarray) – Contains ideal unitary representations of the gate
actual (tf.Tensor) – Contains actual unitary representations of the gate
index (List[int]) – Index of the qubit(s) in the Hilbert space to be evaluated
dims (list) – List of dimensions of qubits
- c3.libraries.fidelities.average_infid_seq(propagators: dict, instructions: dict, index, dims, n_eval=-1)[source]¶
Average sequence fidelity over all gates in propagators.
- Parameters
propagators (dict) – Contains unitary representations of the gates, identified by a key.
index (int) – Index of the qubit(s) in the Hilbert space to be evaluated
dims (list) – List of dimensions of qubits
proj (boolean) – Project to computational subspace
- Returns
Mean average fidelity
- Return type
tf.float64
- c3.libraries.fidelities.average_infid_set(propagators: dict, instructions: dict, index: List[int], dims, n_eval=-1)[source]¶
Mean average fidelity over all gates in propagators.
- Parameters
propagators (dict) – Contains unitary representations of the gates, identified by a key.
index (int) – Index of the qubit(s) in the Hilbert space to be evaluated
dims (list) – List of dimensions of qubits
proj (boolean) – Project to computational subspace
- Returns
Mean average fidelity
- Return type
tf.float64
- c3.libraries.fidelities.epc_analytical(propagators: dict, index, dims, proj: bool, cliffords=False)[source]¶
- c3.libraries.fidelities.leakage_RB(propagators, min_length: int = 5, max_length: int = 500, num_lengths: int = 20, num_seqs: int = 30, logspace=False, lindbladian=False)[source]¶
- c3.libraries.fidelities.lindbladian_RB_left(propagators: dict, gate: str, index, dims, proj: bool = False)[source]¶
- c3.libraries.fidelities.lindbladian_RB_right(propagators: dict, gate: str, index, dims, proj: bool)[source]¶
- c3.libraries.fidelities.lindbladian_average_infid(ideal: ndarray, actual: constant, index=[0], dims=[2]) constant [source]¶
Average fidelity uses the Pauli basis to compare. Thus, perfect gates are always 2x2 (per qubit) and the actual unitary needs to be projected down.
- Parameters
ideal (np.ndarray) – Contains ideal unitary representations of the gate
actual (tf.Tensor) – Contains actual unitary representations of the gate
index (int) – Index of the qubit(s) in the Hilbert space to be evaluated
dims (list) – List of dimensions of qubits
- c3.libraries.fidelities.lindbladian_average_infid_set(propagators: dict, instructions: Dict[str, Instruction], index, dims, n_eval)[source]¶
Mean average fidelity over all gates in propagators.
- Parameters
propagators (dict) – Contains unitary representations of the gates, identified by a key.
index (int) – Index of the qubit(s) in the Hilbert space to be evaluated
dims (list) – List of dimensions of qubits
proj (boolean) – Project to computational subspace
- Returns
Mean average fidelity
- Return type
tf.float64
- c3.libraries.fidelities.lindbladian_epc_analytical(propagators: dict, index, dims, proj: bool, cliffords=False)[source]¶
- c3.libraries.fidelities.lindbladian_unitary_infid(ideal: ndarray, actual: constant, index=[0], dims=[2]) constant [source]¶
Variant of the unitary fidelity for the Lindbladian propagator.
- Parameters
ideal (np.ndarray) – Contains ideal unitary representations of the gate
actual (tf.Tensor) – Contains actual unitary representations of the gate
index (List[int]) – Index of the qubit(s) in the Hilbert space to be evaluated
dims (list) – List of dimensions of qubits
- Returns
Overlap fidelity for the Lindblad propagator.
- Return type
tf.float
- c3.libraries.fidelities.lindbladian_unitary_infid_set(propagators: dict, instructions: Dict[str, Instruction], index, dims, n_eval)[source]¶
Variant of the mean unitary fidelity for the Lindbladian propagator.
- Parameters
propagators (dict) – Contains actual unitary representations of the gates, resulting from physical simulation
instructions (dict) – Contains the perfect unitary representations of the gates, identified by a key.
index (List[int]) – Index of the qubit(s) in the Hilbert space to be evaluated
dims (list) – List of dimensions of qubits
n_eval (int) – Number of evaluation
- Returns
Mean overlap fidelity for the Lindblad propagator for all gates in propagators.
- Return type
tf.float
- c3.libraries.fidelities.orbit_infid(propagators, RB_number: int = 30, RB_length: int = 20, lindbladian=False, shots: Optional[int] = None, seqs=None, noise=None)[source]¶
- c3.libraries.fidelities.state_transfer_from_states(states: Tensor, index, dims, params, n_eval=-1)[source]¶
- c3.libraries.fidelities.state_transfer_infid(ideal: ndarray, actual: constant, index, dims, psi_0)[source]¶
Single gate state transfer infidelity. The dimensions of psi_0 and ideal need to be compatible and index and dims need to project actual to these same dimensions.
- Parameters
ideal (np.ndarray) – Contains ideal unitary representations of the gate
actual (tf.Tensor) – Contains actual unitary representations of the gate
index (int) – Index of the qubit(s) in the Hilbert space to be evaluated
dims (list) – List of dimensions of qubits
psi_0 (tf.Tensor) – Initial state
- Returns
State infidelity for the selected gate
- Return type
tf.float
- c3.libraries.fidelities.state_transfer_infid_set(propagators: dict, instructions: dict, index, dims, psi_0, n_eval=-1, proj=True)[source]¶
Mean state transfer infidelity.
- Parameters
propagators (dict) – Contains unitary representations of the gates, identified by a key.
index (int) – Index of the qubit(s) in the Hilbert space to be evaluated
dims (list) – List of dimensions of qubits
psi_0 (tf.Tensor) – Initial state of the device
proj (boolean) – Project to computational subspace
- Returns
State infidelity, averaged over the gates in propagators
- Return type
tf.float
- c3.libraries.fidelities.unitary_infid(ideal: ndarray, actual: Tensor, index: Optional[List[int]] = None, dims=None) Tensor [source]¶
Unitary overlap between ideal and actually performed gate.
- Parameters
ideal (np.ndarray) – Ideal or goal unitary representation of the gate.
actual (np.ndarray) – Actual, physical unitary representation of the gate.
index (List[int]) – Index of the qubit(s) in the Hilbert space to be evaluated
gate (str) – One of the keys of propagators, selects the gate to be evaluated
dims (list) – List of dimensions of qubits
- Returns
Unitary fidelity.
- Return type
tf.float
- c3.libraries.fidelities.unitary_infid_set(propagators: dict, instructions: dict, index, dims, n_eval=-1)[source]¶
Mean unitary overlap between ideal and actually performed gate for the gates in propagators.
- Parameters
propagators (dict) – Contains actual unitary representations of the gates, resulting from physical simulation
instructions (dict) – Contains the perfect unitary representations of the gates, identified by a key.
index (List[int]) – Index of the qubit(s) in the Hilbert space to be evaluated
dims (list) – List of dimensions of qubits
n_eval (int) – Number of evaluation
- Returns
Unitary fidelity.
- Return type
tf.float
Hamiltonians module¶
Library of Hamiltonian functions.
- c3.libraries.hamiltonians.duffing(a)[source]¶
Anharmonic part of the duffing oscillator.
- Parameters
a (Tensor) – Annihilator.
- Returns
Number operator.
- Return type
Tensor
- c3.libraries.hamiltonians.hamiltonian_reg_deco(func)[source]¶
Decorator for making registry of functions
- c3.libraries.hamiltonians.int_XX(anhs)[source]¶
Dipole type coupling.
- Parameters
anhs (Tensor list) – Annihilators.
- Returns
coupling
- Return type
Tensor
- c3.libraries.hamiltonians.int_YY(anhs)[source]¶
Dipole type coupling.
- Parameters
anhs (Tensor list) – Annihilators.
- Returns
coupling
- Return type
Tensor
- c3.libraries.hamiltonians.resonator(a)[source]¶
Harmonic oscillator hamiltonian given the annihilation operator.
- Parameters
a (Tensor) – Annihilator.
- Returns
Number operator.
- Return type
Tensor
- c3.libraries.hamiltonians.third_order(a)[source]¶
- Parameters
a (Tensor) – Annihilator.
- Returns
Tensor – Number operator.
return literally the Hamiltonian a_dag a a + a_dag a_dag a for the use in any Hamiltonian that uses more than
just a resonator or Duffing part. A more general type of quantum element on a physical chip can have this type of interaction.
One example is a three wave mixing element used in signal amplification called a Superconducting non-linear asymmetric inductive eLement
(SNAIL in short). The code is a simple modification of the Duffing function and written in the same style.
- c3.libraries.hamiltonians.x_drive(a)[source]¶
Semiclassical drive.
- Parameters
a (Tensor) – Annihilator.
- Returns
Number operator.
- Return type
Tensor
Sampling module¶
Functions to select samples from a dataset by various criteria.
- c3.libraries.sampling.all(learn_from, batch_size)[source]¶
Return all points.
- Parameters
learn_from (list) – List of data points
batch_size (int) – Number of points to select
- Returns
All indeces.
- Return type
list
- c3.libraries.sampling.even(learn_from, batch_size)[source]¶
Select evenly distanced samples across the set.
- Parameters
learn_from (list) – List of data points
batch_size (int) – Number of points to select
- Returns
Selected indices.
- Return type
list
- c3.libraries.sampling.even_fid(learn_from, batch_size)[source]¶
Select evenly among reached fidelities.
- Parameters
learn_from (list) – List of data points.
batch_size (int) – Number of points to select.
- Returns
Selected indices.
- Return type
list
- c3.libraries.sampling.from_end(learn_from, batch_size)[source]¶
Select from the end.
- Parameters
learn_from (list) – List of data points
batch_size (int) – Number of points to select
- Returns
Selected indices.
- Return type
list
- c3.libraries.sampling.from_start(learn_from, batch_size)[source]¶
Select from the beginning.
- Parameters
learn_from (list) – List of data points
batch_size (int) – Number of points to select
- Returns
Selected indices.
- Return type
list
- c3.libraries.sampling.high_std(learn_from, batch_size)[source]¶
Select points that have a high ratio of standard deviation to mean. Sampling from ORBIT data, points with a high std have the most coherent error, thus might be suitable for model learning. This has yet to be confirmed beyond anecdotal observation.
- Parameters
learn_from (list) – List of data points.
batch_size (int) – Number of points to select.
- Returns
Selected indices.
- Return type
list
Tasks module¶
- class c3.libraries.tasks.ConfusionMatrix(name: str = 'conf_matrix', desc: str = ' ', comment: str = ' ', params=None, **confusion_rows)[source]¶
Bases:
Task
Allows for misclassificaiton of readout measurement.
- class c3.libraries.tasks.InitialiseGround(name: str = 'init_ground', desc: str = ' ', comment: str = ' ', init_temp: Optional[Quantity] = None, params=None)[source]¶
Bases:
Task
Initialise the ground state with a given thermal distribution.
- initialise(drift_ham, lindbladian=False, init_temp=None)[source]¶
Prepare the initial state of the system. At the moment finite temperature requires open system dynamics.
- Parameters
drift_ham (tf.Tensor) – Drift Hamiltonian.
lindbladian (boolean) – Whether to include open system dynamics. Required for Temperature > 0.
init_temp (Quantity) – Temperature of the device.
- Returns
State or density vector
- Return type
tf.Tensor
- class c3.libraries.tasks.MeasurementRescale(name: str = 'meas_rescale', desc: str = ' ', comment: str = ' ', meas_offset: Optional[Quantity] = None, meas_scale: Optional[Quantity] = None, params=None)[source]¶
Bases:
Task
Rescale the result of the measurements. This is usually done to account for preparation errors.
- Parameters
Module contents¶
Optimizers¶
C1 - Optimal control¶
Object that deals with the open loop optimal control.
- class c3.optimizers.optimalcontrol.OptimalControl(fid_func, fid_subspace, pmap, dir_path=None, callback_fids=None, algorithm=None, initial_point: str = '', store_unitaries=False, options={}, run_name=None, interactive=True, include_model=False, logger=None, fid_func_kwargs={}, ode_solver=None, ode_step_function='schrodinger', only_final_state=False)[source]¶
Bases:
Optimizer
Object that deals with the open loop optimal control.
- Parameters
dir_path (str) – Filepath to save results
fid_func (callable) – infidelity function to be minimized
fid_subspace (list) – Indeces identifying the subspace to be compared
pmap (ParameterMap) – Identifiers for the parameter vector
callback_fids (list of callable) – Additional fidelity function to be evaluated and stored for reference
algorithm (callable) – From the algorithm library Save plots of control signals
store_unitaries (boolean) – Store propagators as text and pickle
options (dict) – Options to be passed to the algorithm
run_name (str) – User specified name for the run, will be used as root folder
fid_func_kwargs (dict) – Additional kwargs to be passed to the main fidelity function.
- goal_run(current_params: Tensor) tf.float64 [source]¶
Evaluate the goal function for current parameters.
- Parameters
current_params (tf.Tensor) – Vector representing the current parameter values.
- Returns
Value of the goal function
- Return type
tf.float64
- goal_run_ode(current_params: Tensor) tf.float64 [source]¶
Evaluate the goal function using ode solver for current parameters.
- Parameters
current_params (tf.Tensor) – Vector representing the current parameter values.
- Returns
Value of the goal function
- Return type
tf.float64
- goal_run_ode_only_final(current_params: Tensor) tf.float64 [source]¶
Evaluate the goal function using ode solver for current parameters.
- Parameters
current_params (tf.Tensor) – Vector representing the current parameter values.
- Returns
Value of the goal function
- Return type
tf.float64
C2 - Calibration¶
Object that deals with the closed loop optimal control.
- class c3.optimizers.calibration.Calibration(eval_func, pmap, algorithm, dir_path=None, exp_type=None, exp_right=None, options={}, run_name=None, logger: Optional[List] = None)[source]¶
Bases:
Optimizer
Object that deals with the closed loop optimal control.
- Parameters
dir_path (str) – Filepath to save results
eval_func (callable) – infidelity function to be minimized
pmap (ParameterMap) – Identifiers for the parameter vector
algorithm (callable) – From the algorithm library
options (dict) – Options to be passed to the algorithm
run_name (str) – User specified name for the run, will be used as root folder
- goal_run(current_params)[source]¶
Evaluate the goal function for current parameters.
- Parameters
current_params (tf.Tensor) – Vector representing the current parameter values.
- Returns
Value of the goal function
- Return type
tf.float64
- log_pickle(params, seqs, results, results_std, shots)[source]¶
Save a pickled version of the performed experiment, suitable for model learning.
- Parameters
params (tf.Tensor) – Vector of parameter values
seqs (list) – Strings identifying the performed instructions
results (list) – Values of the goal function
results_std (list) – Standard deviation of the results, in the case of noisy data
shots (list) – Number of repetitions used in averaging noisy data
- log_setup() None [source]¶
Create the folders to store data.
- Parameters
dir_path (str) – Filepath
run_name (str) – User specified name for the run
C3 - Characterization¶
Object that deals with the model learning.
- class c3.optimizers.modellearning.ModelLearning(sampling, batch_sizes, pmap, datafiles, dir_path=None, estimator=None, seqs_per_point=None, state_labels=None, callback_foms=[], algorithm=None, run_name=None, options={}, logger: Optional[List] = None)[source]¶
Bases:
Optimizer
Object that deals with the model learning.
- Parameters
dir_path (str) – Filepath to save results
sampling (str) – Sampling method from the sampling library
batch_sizes (list) – Number of points to select from each dataset
seqs_per_point (int) – Number of sequences that use the same parameter set
pmap (ParameterMap) – Identifiers for the parameter vector
state_labels (list) – Identifiers for the qubit subspaces
callback_foms (list) – Figures of merit to additionally compute and store
algorithm (callable) – From the algorithm library
run_name (str) – User specified name for the run, will be used as root folder
options (dict) – Options to be passed to the algorithm
- confirm() None [source]¶
Compute the validation set, i.e. the value of the goal function on all points of the dataset that were not used for learning.
- goal_run(current_params: constant) tf.float64 [source]¶
Evaluate the figure of merit for the current model parameters.
- Parameters
current_params (tf.Tensor) – Current model parameters
- Returns
Figure of merit
- Return type
tf.float64
- goal_run_with_grad(current_params)[source]¶
Same as goal_run but with gradient. Very resource intensive. Unoptimized at the moment.
- log_setup() None [source]¶
Create the folders to store data.
- Parameters
dir_path (str) – Filepath
run_name (str) – User specified name for the run
Optimizer module¶
Optimizer object, where the optimal control is done.
- class c3.optimizers.optimizer.BestPointLogger[source]¶
Bases:
BaseLogger
- class c3.optimizers.optimizer.Optimizer(pmap: ParameterMap, initial_point: str = '', algorithm: Optional[Callable] = None, store_unitaries: bool = False, logger: Optional[List] = None)[source]¶
Bases:
object
General optimizer class from which specific classes are inherited.
- Parameters
algorithm (callable) – From the algorithm library
store_unitaries (boolean) – Store propagators as text and pickle
logger (List) – Logging classes
- fct_to_min(input_parameters: Union[ndarray, constant]) Union[ndarray, constant] [source]¶
Wrapper for the goal function.
- Parameters
input_parameters ([np.array, tf.constant]) – Vector of parameters in the optimizer friendly way.
- Returns
Value of the goal function. Float if input is np.array else tf.constant
- Return type
[np.ndarray, tf.constant]
- fct_to_min_autograd(x)[source]¶
Wrapper for the goal function, including evaluation and storage of the gradient.
- Parameters
- xnp.array
Vector of parameters in the optimizer friendly way.
- float
Value of the goal function.
- goal_run(current_params: Union[ndarray, constant]) Union[ndarray, constant] [source]¶
Placeholder for the goal function. To be implemented by inherited classes.
- load_best(init_point, extend_bounds=False) None [source]¶
Load a previous parameter point to start the optimization from. Legacy wrapper. Method moved to Parametermap.
- Parameters
init_point (str) – File location of the initial point
extend_bounds (bool) – Whether or not to allow the loaded optimal parameters’ bounds to be extended if they exceed those specified.
- log_parameters(params) None [source]¶
Log the current status. Write parameters to log. Update the current best parameters. Call plotting functions as set up.
- lookup_gradient(x)[source]¶
Return the stored gradient for a given parameter set.
- Parameters
x (np.array) – Parameter set.
- Returns
Value of the gradient.
- Return type
np.array
- replace_logdir(new_logdir)[source]¶
Specify a new filepath to store the log.
- Parameters
new_logdir –
- set_created_by(config) None [source]¶
Store the config file location used to created this optimizer.
- set_exp(exp: Experiment) None [source]¶
Sensitivity analysis¶
Module for Sensitivity Analysis. This allows the sweeping of the goal function in a given range of parameters to ascertain whether the dataset being used is sensitive to changes in the parameters of interest
- class c3.optimizers.sensitivity.Sensitivity(sampling: str, batch_sizes: Dict[str, int], pmap: ParameterMap, datafiles: Dict[str, str], state_labels: Dict[str, List[Any]], sweep_map: List[List[Tuple[str]]], sweep_bounds: List[List[int]], algorithm: str, estimator: Optional[str] = None, estimator_list: Optional[List[str]] = None, dir_path: Optional[str] = None, run_name: Optional[str] = None, options={})[source]¶
Bases:
ModelLearning
Class for Sensitivity Analysis, subclassed from Model Learning
- Parameters
sampling (str) – Name of the sampling method from library
batch_sizes (Dict[str, int]) – Number of points to select from the dataset
pmap (ParameterMap) – Model parameter map
datafiles (Dict[str, str]) – The datafiles for each of the learning datasets
state_labels (Dict[str, List[Any]]) – The labels for the excited states of the system
sweep_map (List[List[List[str]]]) – Map of variables to be swept in exp_opt_map format
sweep_bounds (List[List[int]]) – List of upper and lower bounds for each sweeping variable
algorithm (str) – Name of the sweeping algorithm from the library
estimator (str, optional) – Name of estimator method from library, by default None
estimator_list (List[str], optional) – List of different estimators to be used, by default None
dir_path (str, optional) – Path to save sensitivity logs, by default None
run_name (str, optional) – Name of the experiment run, by default None
options (dict, optional) – Options for the sweeping algorithm, by default {}
- Raises
NotImplementedError – When trying to set the estimator or estimator_list
Module contents¶
Signal package¶
Submodules¶
Gates module¶
- class c3.signal.gates.Instruction(name: str = ' ', targets: Optional[list] = None, params: Optional[dict] = None, ideal: Optional[ndarray] = None, channels: List[str] = [], t_start: float = 0.0, t_end: float = 0.0)[source]¶
Bases:
object
Collection of components making up the control signal for a line.
- Parameters
ideal (np.ndarray) – Ideal gate that the instruction should emulate.
channels (list) – List of channel names (strings).
t_start (np.float64) – Start of the signal.
t_end (np.float64) – End of the signal.
- comps¶
Nested dictionary with lines and components as keys
- Type
dict
- Example¶
- comps = {
- ‘channel_1’{
‘envelope1’: envelope1, ‘envelope2’: envelope2, ‘carrier’: carrier }
}
- add_component(comp: C3obj, chan: str, options=None, name=None) None [source]¶
Add one component, e.g. an envelope, local oscillator, to a channel.
- Parameters
comp (C3obj) – Component to be added.
chan (str) – Identifier for the target channel
options (dict) –
- Options for this component, available keys are
- delay: Quantity
Delay execution of this component by a certain time
- trigger_comp: Tuple[str]
Tuple of (chan, name) of component acting as trigger. Delay time will be counted beginning with end of trigger
- t_final_cut: Quantity
Length of component, signal will be cut after this time. Also used for the trigger. If not given this invokation from components t_final will be attempted.
- drag: bool
Use drag correction for this component.
t_end (float) – End of this component. None will use the full instruction. If t_end is None and t_start is given a length will be inherited from the instruction.
- property name: str¶
Pulse module¶
- class c3.signal.pulse.Carrier(name: str, desc: str = ' ', comment: str = ' ', params: dict = {})[source]¶
Bases:
C3obj
Represents the carrier of a pulse.
- class c3.signal.pulse.Envelope(name: str, desc: str = ' ', comment: str = ' ', params: Dict[str, Quantity] = {}, shape: Optional[Union[Callable, str]] = None, use_t_before=False)[source]¶
Bases:
C3obj
Represents the envelopes shaping a pulse.
- Parameters
shape (Callable) – function evaluating the shape in time
- class c3.signal.pulse.EnvelopeDrag(name: str, desc: str = ' ', comment: str = ' ', params: dict = {}, shape: Optional[function] = None, use_t_before=False)[source]¶
Bases:
Envelope
- class c3.signal.pulse.EnvelopeNetZero(name: str, desc: str = ' ', comment: str = ' ', params: dict = {}, shape: Optional[function] = None, use_t_before=False)[source]¶
Bases:
Envelope
Represents the envelopes shaping a pulse.
- Parameters
shape (function) – function evaluating the shape in time
params (dict) – Parameters of the envelope Note: t_final
Module contents¶
Utilities package¶
Qutip utilities module¶
Useful functions to get basis vectors and matrices of the right size.
- c3.utils.qt_utils.T1_sequence(length, target)[source]¶
Generate a gate sequence to measure relaxation time in a two-qubit chip.
- Parameters
length (int) – Number of Identity gates.
target (int) – Which qubit is measured.
- Returns
Relaxation sequence.
- Return type
list
- c3.utils.qt_utils.basis(lvls: int, pop_lvl: int) array [source]¶
Construct a basis state vector.
- Parameters
lvls (int) – Dimension of the state.
pop_lvl (int) – The populated entry.
- Returns
A normalized state vector with one populated entry.
- Return type
np.array
- c3.utils.qt_utils.expand_dims(op, dim)[source]¶
pad operator with zeros to be of dimension dim Attention! Not related to the TensorFlow function
- c3.utils.qt_utils.get_basis_matrices(dim)[source]¶
Basis matrices with single ones of the matrices with given dimensions.
- c3.utils.qt_utils.hilbert_space_kron(op, indx, dims)[source]¶
Extend an operator op to the full product hilbert space given by dimensions in dims.
- Parameters
op (np.array) – Operator to be extended.
indx (int) – Position of which subspace to extend.
dims (list) – New dimensions of the subspace.
- Returns
Extended operator.
- Return type
np.array
- c3.utils.qt_utils.insert_mat_kron(dims, target_ids, matrix) ndarray [source]¶
Insert matrix at given indices. All other spaces are filled with zeros.
- Parameters
dims (dimensions of each qubit subspace) –
target_ids (qubits to apply matrix to) –
matrix (matrix to be applied) –
- Return type
composed matrix
- c3.utils.qt_utils.inverseC(sequence)[source]¶
Find the clifford to end a sequence s.t. it returns identity.
- c3.utils.qt_utils.kron_ids(dims, indices, matrices)[source]¶
Kronecker product of matrices at specified indices with identities everywhere else.
- c3.utils.qt_utils.pad_matrix(matrix, dim, padding)[source]¶
Fills matrix dimensions with zeros or identity.
- c3.utils.qt_utils.pauli_basis(dims=[2])[source]¶
Qutip implementation of the Pauli basis.
- Parameters
dims (list) – List of dimensions of each subspace.
- Returns
A square matrix containing the Pauli basis of the product space
- Return type
np.array
- c3.utils.qt_utils.perfect_cliffords(lvls: List[int], proj: str = 'fulluni', num_gates: int = 1)[source]¶
Legacy function to compute the clifford gates.
- c3.utils.qt_utils.perfect_parametric_gate(paulis_str, ang, dims)[source]¶
Construct an ideal parametric gate.
- Parameters
paulis_str (str) –
- Names for the Pauli matrices that identify the rotation axis. Example:
”X” for a single-qubit rotation about the X axis
”Z:X” for an entangling rotation about Z on the first and X on the second qubit
ang (float) – Angle of the rotation
dims (list) – Dimensions of the subspaces.
- Returns
Ideal gate.
- Return type
np.array
- c3.utils.qt_utils.perfect_single_q_parametric_gate(pauli_str, target, ang, dims)[source]¶
Construct an ideal parametric gate.
- Parameters
paulis_str (str) –
- Name for the Pauli matrices that identify the rotation axis. Example:
”X” for a single-qubit rotation about the X axis
ang (float) – Angle of the rotation
dims (list) – Dimensions of the subspaces.
- Returns
Ideal gate.
- Return type
np.array
- c3.utils.qt_utils.projector(dims, indices, outdims=None)[source]¶
Computes the projector to cut down a matrix to the computational space. The subspaces indicated in indeces will be projected to the lowest two states, the rest is projected onto the lowest state. If outdims is defined projection will be performed to those states.
- c3.utils.qt_utils.ramsey_echo_sequence(length, target)[source]¶
Generate a gate sequence to measure dephasing time in a two-qubit chip including a flip in the middle. This echo reduce effects detrimental to the dephasing measurement.
- Parameters
length (int) – Number of Identity gates. Should be even.
target (str) – Which qubit is measured. Options: “left” or “right”
- Returns
Dephasing sequence.
- Return type
list
- c3.utils.qt_utils.ramsey_sequence(length, target)[source]¶
Generate a gate sequence to measure dephasing time in a two-qubit chip.
- Parameters
length (int) – Number of Identity gates.
target (str) – Which qubit is measured. Options: “left” or “right”
- Returns
Dephasing sequence.
- Return type
list
- c3.utils.qt_utils.rotation(phase: float, xyz: array) array [source]¶
General Rotation using Euler’s formula.
- Parameters
phase (np.float) – Rotation angle.
xyz (np.array) – Normal vector of the rotation axis.
- Returns
Unitary matrix
- Return type
np.array
- c3.utils.qt_utils.single_length_RB(RB_number: int, RB_length: int, target: int = 0) List[List[str]] [source]¶
Given a length and number of repetitions it compiles Randomized Benchmarking sequences.
- Parameters
RB_number (int) – The number of sequences to construct.
RB_length (int) – The number of Cliffords in each individual sequence.
target (int) – Index of the target qubit
- Returns
List of RB sequences.
- Return type
list
- c3.utils.qt_utils.two_qubit_gate_tomography(gate)[source]¶
Sequences to generate tomography for evaluating a two qubit gate.
- c3.utils.qt_utils.xy_basis(lvls: int, vect: str)[source]¶
Construct basis states on the X, Y and Z axis.
- Parameters
lvls (int) – Dimensions of the Hilbert space.
vect (str) –
Identifier of the state. Options:
’zp’, ‘zm’, ‘xp’, ‘xm’, ‘yp’, ‘ym’
- Returns
A state on one of the axis of the Bloch sphere.
- Return type
np.array
Tensorflow utilities module¶
Various utility functions to speed up tensorflow coding.
- c3.utils.tf_utils.get_tf_log_level()[source]¶
Display the current tensorflow log level of the system.
- c3.utils.tf_utils.set_tf_log_level(lvl)[source]¶
Set tensorflows system log level.
- REMARK: it seems like the ‘TF_CPP_MIN_LOG_LEVEL’ variable expects a string.
the input of this function seems to work with both string and/or integer, as casting string to string does nothing. feels hacked? but I guess it’s just python…
- c3.utils.tf_utils.tf_average_fidelity(A, B, lvls=None)[source]¶
A very useful but badly named fidelity measure.
- c3.utils.tf_utils.tf_choi_to_chi(U, dims=None)[source]¶
Convert the choi representation of a process to chi representation.
- c3.utils.tf_utils.tf_convolve(sig: Tensor, resp: Tensor)[source]¶
Compute the convolution with a time response.
- Parameters
sig (tf.Tensor) – Signal which will be convoluted, shape: [N]
resp (tf.Tensor) – Response function to be convoluted with signal, shape: [M]
- Returns
convoluted signal of shape [N]
- Return type
tf.Tensor
- c3.utils.tf_utils.tf_convolve_legacy(sig: Tensor, resp: Tensor)[source]¶
Compute the convolution with a time response. LEGACY version. Ensures compatibility with the previous response implementation.
- Parameters
sig (tf.Tensor) – Signal which will be convoluted, shape: [N]
resp (tf.Tensor) – Response function to be convoluted with signal, shape: [M]
- Returns
convoluted signal of shape [N]
- Return type
tf.Tensor
- c3.utils.tf_utils.tf_diff(l)[source]¶
Running difference of the input list l. Equivalent to np.diff, except it returns the same shape by adding a 0 in the last entry.
- c3.utils.tf_utils.tf_dmket_fid(rho, psi)[source]¶
Fidelity between a state vector and a density matrix.
- c3.utils.tf_utils.tf_kron(A, B)[source]¶
Kronecker product of 2 matrices. Can be applied with batch dimmensions.
- c3.utils.tf_utils.tf_list_avail_devices()[source]¶
List available devices.
Function for displaying all available devices for tf_setuptensorflow operations on the local machine.
- TODO: Refine output of this function. But without further knowledge
about what information is needed, best practise is to output all information available.
- c3.utils.tf_utils.tf_log_level_info()[source]¶
Display the information about different log levels in tensorflow.
- c3.utils.tf_utils.tf_matmul_left(dUs: Tensor)[source]¶
- Parameters
dUs – tf.Tensor Tensorlist of shape (N, n,m) with number N matrices of size nxm
Multiplies a list of matrices from the left.
- c3.utils.tf_utils.tf_matmul_n(tensor_list: Tensor, folding_stack: List[Callable]) Tensor [source]¶
Multipy a list of tensors using a precompiled stack of function to apply to each layer of a binary tree.
- Parameters
tensor_list (List[tf.Tensor]) – Matrices to be multiplied.
folding_stack (List[Callable]) – List of functions to multiply each layer.
- Returns
Reduced product of matrices.
- Return type
tf.Tensor
- c3.utils.tf_utils.tf_matmul_right(dUs)[source]¶
- Parameters
dUs – tf.Tensor Tensorlist of shape (N, n,m) with number N matrices of size nxm
Multiplies a list of matrices from the right.
- c3.utils.tf_utils.tf_measure_operator(M, rho)[source]¶
Expectation value of a quantum operator by tracing with a density matrix.
- Parameters
M (tf.tensor) – A quantum operator.
rho (tf.tensor) – A density matrix.
- Returns
Expectation value.
- Return type
tf.tensor
- c3.utils.tf_utils.tf_project_to_comp(A, dims, index=None, to_super=False)[source]¶
Project an operator onto the computational subspace.
- c3.utils.tf_utils.tf_superoper_average_fidelity(A, B, lvls=None)[source]¶
A very useful but badly named fidelity measure.
- c3.utils.tf_utils.tf_unitary_overlap(A: Tensor, B: Tensor, lvls: Optional[Tensor] = None) Tensor [source]¶
Unitary overlap between two matrices.
- Parameters
A (tf.Tensor) – Unitary A
B (tf.Tensor) – Unitary B
lvls (tf.Tensor, optional) – Levels, by default None
- Returns
Overlap between the two unitaries
- Return type
tf.Tensor
- Raises
TypeError – For errors during cast
ValueError – For errors during matrix multiplicaton
Log Reader utilities module¶
- c3.utils.log_reader.show_table(log: Dict[str, Any], console: Console) None [source]¶
Generate a rich table from an optimization status and display it on the console.
- Parameters
log (Dict) – Dictionary read from a json log file containing a c3-toolset optimization status.
console (Console) – Rich console for output.
Miscellaneous utilities module¶
Miscellaneous, general utilities.
- c3.utils.utils.deprecated(message: str)[source]¶
Decorator for deprecating functions
- Parameters
message (str) – Message to display along with DeprecationWarning
Examples
Add a
@deprecated("message")
decorator to the function:@deprecated("Using standard width. Better use gaussian_sigma.") def gaussian(t, params): ...
- c3.utils.utils.eng_num(val: float) Tuple[float, str] [source]¶
Convert a number to engineering notation by returning number and prefix.
- c3.utils.utils.flatten(lis: ~typing.List, ltypes=(<class 'list'>, <class 'tuple'>)) List [source]¶
Flatten lists of arbitrary lengths https://rightfootin.blogspot.com/2006/09/more-on-python-flatten.html
- Parameters
lis (List) – The iterable to flatten
ltypes (tuple, optional) – Possibly the datatype of the iterable, by default (list, tuple)
- Returns
Flattened list
- Return type
List
- c3.utils.utils.log_setup(data_path: Optional[str] = None, run_name: str = 'run') str [source]¶
Make sure the file path to save data exists. Create an appropriately named folder with date and time. Also creates a symlink “recent” to the folder.
- Parameters
data_path (str) – File path of where to store any data.
run_name (str) – User specified name for the run.
- Returns
The file path to store new data.
- Return type
str
Module contents¶
Qiskit modules for C3¶
C3 Backend module¶
- class c3.qiskit.c3_backend.C3QasmPerfectSimulator(configuration=None, provider=None, **fields)[source]¶
Bases:
C3QasmSimulator
A C3-based perfect gates simulator for Qiskit
- Parameters
C3QasmSimulator (c3.qiskit.c3_backend.C3QasmSimulator) – Inherits the C3QasmSimulator and implements a perfect gate simulator
- class c3.qiskit.c3_backend.C3QasmPhysicsSimulator(configuration=None, provider=None, **fields)[source]¶
Bases:
C3QasmSimulator
A C3-based perfect gates simulator for Qiskit
- Parameters
C3QasmSimulator (c3.qiskit.c3_backend.C3QasmSimulator) – Inherits the C3QasmSimulator and implements a physics based simulator
- MAX_QUBITS_MEMORY = 10¶
- run_experiment(experiment: QasmQobjExperiment) Dict[str, Any] [source]¶
Run an experiment (circuit) and return a single experiment result
- Parameters
experiment (QasmQobjExperiment) – experiment from qobj experiments list
- Returns
A result dictionary which looks something like:
{ "name": name of this experiment (obtained from qobj.experiment header) "seed": random seed used for simulation "shots": number of shots used in the simulation "data": { "counts": {'0x9: 5, ...}, "memory": ['0x9', '0xF', '0x1D', ..., '0x9'] }, "status": status string for the simulation "success": boolean "time_taken": simulation time of this single experiment }
- Return type
Dict[str, Any]
- Raises
C3QiskitError – If an error occured
- class c3.qiskit.c3_backend.C3QasmSimulator(configuration, provider=None, **fields)[source]¶
Bases:
BackendV1
,ABC
An Abtract Base Class for C3 Qasm Simulators for Qiskit. This class CAN NOT be instantiated directly. Classes derived from this must compulsorily implement
def __init__(self, configuration=None, provider=None, **fields): def _default_options(cls) -> None: def run_experiment(self, experiment: QasmQobjExperiment) -> Dict[str, Any]:
- Parameters
Backend (qiskit.providers.BackendV1) – The C3QasmSimulator is derived from BackendV1
ABC (abc.ABC) – Helper class for defining Abstract classes using ABCMeta
- disable_flip_labels() None [source]¶
Disable flipping of labels State Labels are flipped before returning results to match Qiskit style qubit indexing convention This function allows disabling of the flip
- generate_shot_readout()[source]¶
Generate shot style readout from population
- Returns
List of shots for each output state
- Return type
List[int]
- get_labels(format: str = 'qiskit') List[str] [source]¶
Return state labels for the system
- Parameters
format (str, optional) – How to format the state labels, by default “qiskit”
- Returns
A list of state labels in hex if qiskit format and decimal if c3 format
labels = ['0x1', ...] labels = ['(0, 0)', '(0, 1)', '(0, 2)', ...]
- Return type
List[str]
- Raises
C3QiskitError – When an supported format is passed
- locate_measurements(instructions_list: List[Dict]) List[int] [source]¶
Locate the indices of measurement operations in circuit
- Parameters
instructions_list (List[Dict]) – Instructions List in Qasm style
- Returns
The indices where measurement operations occur
- Return type
List[int]
- run(qobj: Qobj, **backend_options) C3Job [source]¶
Parse and run a Qobj
- Parameters
qobj (Qobj) – The Qobj payload for the experiment
backend_options (dict) – backend options
- Returns
An instance of the C3Job (derived from JobV1) with the result
- Return type
- Raises
QiskitError – Support for Pulse Jobs is not implemented
Notes
backend_options: Is a dict of options for the backend. It may contain:
"initial_statevector": vector_like
The
initial_statevector
option specifies a custom initial statevector for the simulator to be used instead of the all zero state. This size of this vector must be correct for the number of qubits in all experiments in the qobj. Example:backend_options = { "initial_statevector": np.array([1, 0, 0, 1j]) / np.sqrt(2), }
"params": list
List of parameter values. Can be nested, if a parameter is matrix valued.
"opt_map": list
Corresponding identifiers for the parameter values.
"shots": int
Total number of measurement shots.
"memory": bool
Whether individual measurement readout is stored.
- sanitize_instructions(instructions: Instruction) Tuple[List[Any], List[Any]] [source]¶
Convert from qiskit instruction object and Sanitize instructions by removing unsupported operations
- Parameters
instructions (Instruction) – qasm as Qiskit Instruction object
- Returns
Sanitized instruction list
Qasm style list of instruction represented as dicts
- Return type
Tuple[List[Any], List[Any]]
- Raises
UserWarning – Warns user about unsupported operations in circuit
- set_c3_experiment(exp: Experiment) None [source]¶
Set user-provided c3 experiment object for backend
- Parameters
exp (Experiment) – C3 experiment object
C3 Provider module¶
- class c3.qiskit.c3_provider.C3Provider[source]¶
Bases:
ProviderV1
Provider for C3 Qiskit backends
- Parameters
ProviderV1 (ProviderV1) – Derived from ProviderV1 from qiskit.providers.provider
- backends(name=None, filters=None, **kwargs)[source]¶
Return a list of backends matching the name
- Parameters
name (str, optional) – name of the backend, by default None
filters (callable, optional) – Filtering conditions, as callable, by default None
- Returns
A list of backend instances matching the condition
- Return type
list[BackendV1]
C3 Job module¶
- class c3.qiskit.c3_job.C3Job(backend, job_id, result)[source]¶
Bases:
JobV1
C3Job class
- Parameters
Job (JobV1) – Inherits JobV1 from qiskit.providers
- result() Result [source]¶
Return the result of the job
- Returns
Result of the job simulation
- Return type
qiskit.Result
C3 Exceptions module¶
Exception for errors raised by Basic Aer.
C3 Gates module¶
Library for interoperability of c3 gates with qiskit
- class c3.qiskit.c3_gates.CR90Gate(label: Optional[str] = None)[source]¶
Bases:
UnitaryGate
Cross Resonance 90 degree gate
Warning
This is not equivalent to the RZX(pi/2) gate in qiskit
- class c3.qiskit.c3_gates.CRGate(label: Optional[str] = None)[source]¶
Bases:
UnitaryGate
Cross Resonance Gate
Warning
This is not equivalent to the RZX(pi/2) gate in qiskit
- class c3.qiskit.c3_gates.RX90mGate(label: Optional[str] = None)[source]¶
Bases:
RXGate
90 degree rotation around X axis in the negative direction
- class c3.qiskit.c3_gates.RX90pGate(label: Optional[str] = None)[source]¶
Bases:
RXGate
90 degree rotation around X axis in the positive direction
- class c3.qiskit.c3_gates.RXpGate(label: Optional[str] = None)[source]¶
Bases:
RXGate
180 degree rotation around X axis
- class c3.qiskit.c3_gates.RY90mGate(label: Optional[str] = None)[source]¶
Bases:
RYGate
90 degree rotation around Y axis in the negative direction
- class c3.qiskit.c3_gates.RY90pGate(label: Optional[str] = None)[source]¶
Bases:
RYGate
90 degree rotation around Y axis in the positive direction
- class c3.qiskit.c3_gates.RYpGate(label: Optional[str] = None)[source]¶
Bases:
RYGate
180 degree rotation around Y axis
- class c3.qiskit.c3_gates.RZ90mGate(label: Optional[str] = None)[source]¶
Bases:
RZGate
90 degree rotation around Z axis in the negative direction
- class c3.qiskit.c3_gates.RZ90pGate(label: Optional[str] = None)[source]¶
Bases:
RZGate
90 degree rotation around Z axis in the positive direction
- class c3.qiskit.c3_gates.RZpGate(label: Optional[str] = None)[source]¶
Bases:
RZGate
180 degree rotation around Z axis
- class c3.qiskit.c3_gates.SetParamsGate(params: List)[source]¶
Bases:
Gate
Gate for setting parameter values through qiskit interface. This gate is only processed when it is the last gate in the circuit, otherwise it throws a KeyError. The qubit target for the gate can be any valid qubit in the circuit, this argument is currently ignored and not processed by the backend
These parameters should be supplied as a list with the first item a list of Quantity objects converted to a Dict of Python primitives and the second item an opt_map with the proper list nesting. For example:
amp = Qty(value=0.8, min_val=0.2, max_val=1, unit="V") opt_map = [[["rx90p[0]", "d1", "gaussian", "amp"]]] param_gate = SetParamsGate(params=[[amp.asdict()], opt_map])
C3 Backend Utilities module¶
Convenience Module for creating different c3_backend
- c3.qiskit.c3_backend_utils.flip_labels(counts: Dict[str, int]) Dict[str, int] [source]¶
Flip C3 qubit labels to match Qiskit qubit indexing
- Parameters
counts (Dict[str, int]) – OpenQasm 2.0 result counts with original C3 style qubit indices
- Returns
OpenQasm 2.0 result counts with Qiskit style labels
- Return type
Dict[str, int]
Note
Basis vector ordering in Qiskit
Qiskit uses a slightly different ordering of the qubits compared to what is seen in Physics textbooks. In qiskit, the qubits are represented from the most significant bit (MSB) on the left to the least significant bit (LSB) on the right (big-endian). This is similar to bitstring representation on classical computers, and enables easy conversion from bitstrings to integers after measurements are performed.
More details: https://qiskit.org/documentation/tutorials/circuits/3_summary_of_quantum_operations.html#Basis-vector-ordering-in-Qiskit
- c3.qiskit.c3_backend_utils.get_init_ground_state(n_qubits: int, n_levels: int) Tensor [source]¶
Return a perfect ground state
- Parameters
n_qubits (int) – Number of qubits in the system
n_levels (int) – Number of levels for each qubit
- Returns
Tensor array of ground state shape(m^n, 1), dtype=complex128 m = no of qubit levels n = no of qubits
- Return type
tf.Tensor
- c3.qiskit.c3_backend_utils.make_gate_str(instruction: dict, gate_name: str) str [source]¶
Make C3 style gate name string
- Parameters
instruction (Dict[str, Any]) –
A dict in OpenQasm instruction format
{"name": "rx", "qubits": [0], "params": [1.57]}
gate_name (str) – C3 style gate names
- Returns
C3 style gate name + qubit string
{"name": "rx", "qubits": [0], "params": [1.57]} -> rx90p[0]
- Return type
str