from QGL import *

"""
Explanation: 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.
End of explanation
"""


cl = ChannelLibrary("example")

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

"""
Explanation: 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.
End of explanation
"""


q1 = cl.new_qubit("q1")

"""
Explanation: 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:
End of explanation
"""


# 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))

"""
Explanation: 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.
End of explanation
"""


cl.commit()

"""
Explanation: Commit the changes to the channel library.
End of explanation
"""


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


"""
Explanation: 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:
End of explanation
"""


mf = compile_to_hardware(seq1, 'Test1')

plot_pulse_files(mf)

"""
Explanation: 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.
End of explanation
"""


mf = compile_to_hardware(seq2, 'Test2')
plot_pulse_files(mf)

"""
Explanation: Now, let's plot the second sequence.
End of explanation
"""


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)

"""
Explanation: Constructing more sophisticated sequences
To get rotations of arbitrary angle (i.e., amplitude control of the pulse), you can use the "theta" primitive:
End of explanation
"""


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)

"""
Explanation: To rotate about an arbitrary axis, use the "U" primitives:
End of explanation
"""


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)

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


q2 = cl.new_qubit("q2")
aps2_3 = cl.new_APS2("BBNAPS3", address="192.168.5.103")
cl.set_control(q2, aps2_3)

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


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

"""
Explanation: When you plot a sequence with multiple logical channels, each channel (both I and Q) is plotted seperately.
End of explanation
"""


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

"""
Explanation: 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.
End of explanation
"""


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

"""
Explanation: 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))
End of explanation
"""


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

"""
Explanation: 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.
End of explanation
"""


# 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)

"""
Explanation: 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.
End of explanation
"""


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

mf = compile_to_hardware(seq, 'Test')
plot_pulse_files(mf)

"""
Explanation: Longer sequences using list comprehensions
Rabi amplitude
Note the increasing amplitude with each sequence.
End of explanation
"""


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

mf = compile_to_hardware(seq, 'Test')
plot_pulse_files(mf)

"""
Explanation: T1
Note the delay between the X pulse and the measurement.
End of explanation
"""


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

mf = compile_to_hardware(seq, 'Test')
plot_pulse_files(mf)

"""
Explanation: Ramsey
Note the varying delay between the X pulses prior to measurement.
End of explanation
"""


