Two-Qubit Characterization Sequences

Examples of two-qubit sequences, including CR gates

In [ ]:
from QGL import *

See Auspex example notebooks on how to configure a channel library.

For the examples in this notebook, we will the channel library generated by running ex1_QGL_basics.ipynb in this same directory.

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

In [ ]:
q1 = cl["q1"]

# Repeat similar configuration for q2
q2 = cl.new_qubit("q2")
aps2_3 = cl.new_APS2("BBNAPS3", address="") 
aps2_4 = cl.new_APS2("BBNAPS4", address="")
dig_2  = cl.new_X6("X6_2", address=0)
cl.set_control(q2, aps2_3)
cl.set_measure(q2, aps2_4, dig_2.ch(1))

One can define simultaneous operations on qubits using the * operator (indicating a tensor product), see ex1_QGL_basics.

Below is an example of QGL basic sequence for two qubits.

  • The first argument (a tuple) selects the driven qubits
  • The optional argument measChans selects which qubits to measure (default = all driven)
  • The optional argument add_cals adds reference segments corresponding to the computational states at the end of the sequence, used to normalize the readout signals

In [ ]:
RabiPoints = 101;
plot_pulse_files(RabiAmp_NQubits((q1,q2),np.linspace(0,1,RabiPoints), measChans=(q1,q2), add_cals=True))

Two-qubit gates

To write a two-qubit gate in QGL, you must add to your channel library a logical channel representing the coupling between qubits. In QGL terminology, this is known as an Edge, and is a directed edge in the connectivity graph of your device. QGL uses directed edges because certain two-qubit interactions have a preferred ordering of the interaction. For instance, a cross resonance gate has a preferred sign of the qubit-qubit detuning. By storing directed edges, we can write two-qubit primitives that emit different pulses depending on whether the (control, target) pair is aligned or anti-aligned with the underlying interaction Hamiltonian.

The following examples declares that q1 and q2 are connected, and defines an edge connecting from q1 to q2:

In [ ]:
e = cl.new_edge(q1, q2)

We can now include CNOT gates in our sequences. In this example you can see the use of the two-qubit primitive CNOT. The exact sequence will depend on the (source, target) order you selected in creating the q1-q2 Edge and on the chosen cnot_implementation.

You can select a different default CNOT implementation by modifying the cnot_implementation key in your local QGL's config.py file.

In order to actually compile a CNOT, there needs to be a physical channel and generator associated with the edge, i.e.:

In [ ]:
# Most calls required label and address. Let's define 
# an AWG for control pulse generation 
aps2_5 = cl.new_APS2("BBNAPS5", address="") 
cl.set_control(e, aps2_5)

In [ ]:
seqs = [[Id(q1), CNOT(q1, q2)]] # use the default CNOT_simple implementation, where the CNOT is represented as an X pulse
mf = compile_to_hardware(seqs,'CNOT_simple')  

You can also explicitly call CNOT_CR to use the CR decomposition, independently of the global configuration.

In [ ]:
seqs = [[Id(q1), CNOT_CR(q1, q2)]] # use the CNOT_CR implementation, where the CNOT is decomposed 
# into a sequence of single-qubit gates and a ZX90, as is appropriate for a cross-resonance interaction.
mf = compile_to_hardware(seqs,'CNOT_CR')  

Inverting the order of the CNOT_CR input will also produce a CNOT using the same directed edge (q1->q2), but with added single-qubit gates to invert the CNOT control and target.

In [ ]:
seqs = [[Id(q1), CNOT_CR(q2, q1)]] # use the CNOT_CR implementation, where the CNOT is decomposed 
# into a sequence of single-qubit gates and a ZX90, as is appropriate for a cross-resonance interaction.
mf = compile_to_hardware(seqs,'CNOT_CR_inv')  

In [ ]: