Setup

First, import the necessary modules from QGL. This will also import numpy into the namespace as np.

The AWGDir environment variable is used to indicate where QGL will store it's output sequence files. First we load the QGL module. It defaults to a temporary directory as provided by Python's tempfile module.


In [1]:
from QGL import *


AWG_DIR environment variable not defined. Unless otherwise specified, using temporary directory for AWG sequence file outputs.

Next we instantiate the channel library. By default bbndb will use an sqlite database at the location specified by the BBN_DB environment variabe, but we override this behavior below in order to use a specific filename. Also shown (but commented out) is the syntax for creating a temporary in-memory database for testing purposes.


In [2]:
cl = ChannelLibrary(db_resource_name="example.sqlite")

# This would be a temporary, in memory database
# cl = ChannelLibrary(db_resource_name=":memory:")


Creating engine...

The channel library has a number of convenience functions defined to create instruments and qubits, as well as functions to define the relationships between them. Let us create a qubit first:


In [3]:
q1 = cl.new_qubit("q1")

In order to compile the QGL program into pulse sequences, we need to define a minimal hardware configuration. Basically, we need to specify AWG resources for output pulse compilation and digitizer resources for signal measurement.


In [4]:
# Most calls required label and address. Let's define 
# an AWG for control pulse generation 
aps2_1 = cl.new_APS2("BBNAPS1", address="192.168.5.101") 
# an AWG for measurement pulse generation
aps2_2 = cl.new_APS2("BBNAPS2", address="192.168.5.102")
# and digitizer for measurement collection
dig_1  = cl.new_X6("X6_1", address=0)

# Qubit q1 is controlled by AWG aps2_1
cl.set_control(q1, aps2_1)
# Qubit q1 is measured by AWG aps2_2 and digitizer dig_1
cl.set_measure(q1, aps2_2, dig_1.ch(1))

Commit the changes to the channel library.


In [5]:
cl.commit()

Basic sequence construction and plotting

You can construct simple gate sequences by creating Lists of Pulse objects. These can be constructed by calling various primitives defined for qubits, for example, 90 and 180 degree rotations about X and Y:


In [ ]:
seq1 = [[X(q1), Y(q1)]]
seq2 = [[X90(q1),Y90(q1),X(q1),Id(q1),Y(q1)]]

This sequence of pulses can be plotted for visual review. First, you must compile the QGL into pulses based on the hardware defined above. Since our Qubit object is a quadrature channel, you see two colors corresponding to the I and Q control signals.


In [ ]:
mf = compile_to_hardware(seq1, 'Test1')

In [ ]:
plot_pulse_files(mf)

Now, let's plot the second sequence.


In [ ]:
mf = compile_to_hardware(seq2, 'Test2')
plot_pulse_files(mf)

Constructing more sophisticated sequences

To get rotations of arbitrary angle (i.e., amplitude control of the pulse), you can use the "theta" primitive:


In [ ]:
seq = [[Xtheta(q1, 0.2), Xtheta(q1, 0.4), Xtheta(q1, 0.6), Xtheta(q1, 0.8), Xtheta(q1, 1.0)]]
mf = compile_to_hardware(seq, 'Test')
plot_pulse_files(mf)

To rotate about an arbitrary axis, use the "U" primitives:


In [ ]:
seq = [[U(q1, 0.0), U(q1, np.pi/8), U(q1, np.pi/4), U(q1, 3*np.pi/8), U(q1, np.pi/2)]]
mf = compile_to_hardware(seq, 'Test')
plot_pulse_files(mf)

Z rotations are performed in "software:" they act as frame changes on the following pulses.


In [ ]:
seq = [[X(q1), Z90(q1), X(q1), Z90(q1), X(q1), Z90(q1), X(q1), Z90(q1), X(q1)]]
mf = compile_to_hardware(seq, 'Test')
plot_pulse_files(mf)

Sequences can act on multiple qubits, i.e., channels. Let's create another "logical" qubit channel as well as a "physical" channel.


In [ ]:
q2 = cl.new_qubit("q2")
aps2_3 = cl.new_APS2("BBNAPS3", address="192.168.5.103")
cl.set_control(q2, aps2_3)

When you plot a sequence with multiple logical channels, each channel (both I and Q) is plotted seperately.


In [ ]:
seq = [[X(q1), X(q2), Y(q1), Y(q2)]]
mf = compile_to_hardware(seq, 'Test')
plot_pulse_files(mf)

One can express simultaneous operations with the * operator (meant to evoke a tensor product). If no operation is specified for a channel in a given time slot, an identity (no-op) operation is inserted.


In [ ]:
seq = [[X(q1)*X(q2), X(q1)*Y(q2), Y(q1), X(q2)]]
mf = compile_to_hardware(seq, 'Test')
plot_pulse_files(mf)

Constructing sequences with measurements

Measurement pulses can be created with the MEAS primitive. Given a qubit X, the compiler finds the associated logical measurement channel and creates a Pulse on that channel. Note that a Trigger pulse for the digitizer is created along with the qubit measurement pulse.

Remember, measurement channel was defined above with:
Qubit q1 is measured by AWG aps2_2 and digitizer dig_1
cl.set_measure(q1, aps2_2, dig_1.ch(1))


In [ ]:
seq = [[MEAS(q1)]]
mf = compile_to_hardware(seq, 'Test')
plot_pulse_files(mf)

Single-sideband modulation

In order to prevent leakage at the source frequency, typically single-sideband (SSB) modulation with IQ mixers is used to generate both control and measurement pulses.

For control pulses, we define the SSB frequency as q1.frequency. In the following example, you can set that parameter to a non-zero SSB to modulate the pulse envelope.


In [ ]:
q1.frequency = 50e6
seq= [[X(q1), Y(q1)]]
mf = compile_to_hardware(seq, 'Test')
plot_pulse_files(mf)

For measurement pulses, we define the SSB frequency as

q1.measure_chan.autodyne_freq

Note that autodyne_freq is different from frequency. In the first case, the SSB frequency is baked directly in the pulse waveform, and is independent of the time when the pulse is applied. In the second case (normally used for the control SSB, see above), the actual pulse shape depends on the time when the pulse occurs. This is in order to maintain the phase reference allowing for arbitrary rotations around any transversal axis.


In [ ]:
# set the modulation frequency
cl["q1"].measure_chan.autodyne_freq = 10e6

seq = [[X(q1), MEAS(q1)]]
mf = compile_to_hardware(seq, 'Test')
plot_pulse_files(mf)

Longer sequences using list comprehensions

Rabi amplitude

Note the increasing amplitude with each sequence.


In [ ]:
seq = [[Xtheta(q1, a), MEAS(q1)] for a in np.linspace(0,2,11)]

In [ ]:
mf = compile_to_hardware(seq, 'Test')
plot_pulse_files(mf)

T1

Note the delay between the X pulse and the measurement.


In [ ]:
seq = [[X(q1), Id(q1, d), MEAS(q1)] for d in np.linspace(0, 10e-7, 11)]

In [ ]:
mf = compile_to_hardware(seq, 'Test')
plot_pulse_files(mf)

Ramsey

Note the varying delay between the X pulses prior to measurement.


In [ ]:
seq = [[X90(q1), Id(q1, delay), X90(q1), MEAS(q1)] for delay in np.linspace(0, 5e-7, 11)]

In [ ]:
mf = compile_to_hardware(seq, 'Test')
plot_pulse_files(mf)