<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 Two-Qubit Characterization Sequences
Examples of two-qubit sequences, including CR gates
Step1:  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.
Step2:  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
Step3:  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
Step4:  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.
Step5:  You can also explicitly call CNOT_CR to 
use the CR decomposition, independently of the global configuration.
Step6:  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.
<ASSISTANT_TASK:>
Python Code:
from QGL import *
Explanation: Two-Qubit Characterization Sequences
Examples of two-qubit sequences, including CR gates
End of explanation
cl = ChannelLibrary("example")
q1 = cl["q1"]
# Repeat similar configuration for q2
q2 = cl.new_qubit("q2")
aps2_3 = cl.new_APS2("BBNAPS3", address="192.168.5.103") 
aps2_4 = cl.new_APS2("BBNAPS4", address="192.168.5.104")
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))
Explanation: 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.
End of explanation
RabiPoints = 101;
plot_pulse_files(RabiAmp_NQubits((q1,q2),np.linspace(0,1,RabiPoints), measChans=(q1,q2), add_cals=True))
Explanation: 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
End of explanation
e = cl.new_edge(q1, q2) 
Explanation: 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:
End of explanation
# Most calls required label and address. Let's define 
# an AWG for control pulse generation 
aps2_5 = cl.new_APS2("BBNAPS5", address="192.168.5.106") 
cl.set_control(e, aps2_5)
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')  
plot_pulse_files(mf)
Explanation: 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.:
End of explanation
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')  
plot_pulse_files(mf)
Explanation: You can also explicitly call CNOT_CR to 
use the CR decomposition, independently of the global configuration.
End of explanation
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')  
plot_pulse_files(mf)
Explanation: 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.
End of explanation
<END_TASK>