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 *
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:")
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()
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)
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)
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)
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)
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)
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)
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)