Instruction-based qubit characterization
The first step is locating the spin transition frequencies to drive the qubit with a resonance experiment. For Defect Centers, such as the NV center qubit in Fig. 1, this is usually done by driving a microwave (MW) tone and a spin initialization/readout laser at the same time. The frequency of the MW is swept until a change in the emission spectrum is observed, denoting the spin transitions of interest.
To start, we define a measurement macro in QUA. With the measure() command, we analyze the incoming signal from a photon counting module (e.g. SNSPD, APD, PMT) into the ADCs as a time-tagging measurement and save both the photon arrival times in an array called times, and the number of photons as counts. These are real-time variables that live within the Pulse Processing Unit (PPU) and can be accessed throughout the execution of the program.
# Time Tagging QUA Macro
# provides counts and times of arrival for tags within measurement window
def get_photons():
times = declare(int, size=100)
counts = declare(int)
measure("measure", "SPCM", None, time_tagging.analog(times, measurement_time, counts))
return times, counts
times, counts = get_photons()
Then, an optically detected magnetic resonance would be implemented like the following way:
# Optically detected magnetic resonance (ODMR) QUA code
def ODMR(f_start, f_end, f_steps):
f = declare(int)
with for_(f, f_start, f<f_end, f+f_steps):
update_frequency("Qubit", f)
align()
play("cw", "Qubit")
play("laser_ON","Laser")
times, counts = get_photons()
The for loop is implemented within the PPU and sweeps over the real-time variable with the provided parameters. play() commands generate a real-time predefined waveform (e.g., “cw”) envelope with the carrier frequency f that is parametrically changed with the for loop to look for the spin resonance. The signal is outputted from predetermined channels according to the rules defined for the “Qubit” element. Finally, the results are measured with the macro get_photons() defined earlier. Such an experiment is easily implemented with six lines of code that can be followed and understood easily. As the OPX platform is an instruction based system, there is not a difference between sweeping over 10 values or 10,000 – the system memory and the compilation overhead will be the same, saving a significant amount of time between each experiment and enabling ultra-long experiments easily.
Now we continue with some examples from standard characterization experiments. Whether you use your NV center qubits (or any other) for quantum communication, computation, sensing, or other quantum photonics application, it is likely that you will need to measure coherence times and perform basic characterization, for example \(T_1\) and Ramsey experiments (see Figure 2).
Fig. 2. Sketch depicting the result of \(T_1\) and Ramsey (\(T_2^*\)) experiments, with the pulse protocols in incept.
We can compose a \(T_1\) experiment very easily by initializing the spin of our NV center qubit (Fig. 1) optically with a laser, putting it into the opposite spin state with a 𝝅-pulse and waiting to see how long it takes for it to decay back into the initialized state as a function of the geometrically increasing wait time 𝝉. The averaging loop can be placed over the entire sequence to improve the \(1/f\) noise as the system can update sweeping parameters in real-time and record its result.
# T1 protocol QUA code
def T1(N, t_start, t_end, t_mult):
tau = declare(int) # time to vary
avg_idx = declare(int) # averaging index
play("laser_ON", "Laser")
with for_(avg_idx, 0, avg_idx<N, avg_idx+1):
with for_(tau, t_start, tau<t_end, tau*t_mult):
play("pi", "Qubit")
wait(tau, "Qubit")
align()
play("laser_ON", "Laser")
times, counts = get_photons()
Note how easy it would be to turn this code into a contrast measurement, with and without a 𝝅-pulse, and saving the respective results into a new QUA variable to calculate real-time contrast with a couple more lines of code.
Here we see an instance of the align() command, which instructs the pulse processor to wait for all channels to be done with their respective subroutines before moving to the next set of instructions. The temporal alignment of sequences works for both time-deterministic and non-deterministic sequences (e.g., heralding and other quantum photonics schemes).
A Rabi experiment, used to calibrate control pulses, can be very easily coded similar to the \(T_1\) example, by stretching the pulse duration in real-time within the play() command, so instead of varying 𝝉 we would vary the pulse duration t:
# additional QUA code for Rabi protocol
…
with for_(t, t_start, t<t_end, t+t_steps):
play("cw", "Qubit", duration=t)
…
A Ramsey experiment is defined as follows: 𝝅/2-pulse, wait a variable time 𝝉, 𝝅/2-pulse again, then measure. In our experiment we can simply loop over this block by changing 𝝉 depending on the experimental needs and recording the measured counts. After averaging, we can uncover the qubits \(T_2^*\) from the decay spectrum and further calibrate the qubit’s frequency from the oscillations (this sequence is often used to build frequency-tracking ultra-fast mid-circuit calibration routines, i.e. embedded calibrations).
# Ramsey protocol QUA code to measure qubit T2*
def Ramsey(N, tau_start, tau_end, tau_steps):
tau = declare(int) # time to vary
avg_idx = declare(int) # averaging index
play("laser_ON", "Laser")
with for_(avg_idx, 0, avg_idx<N, avg_idx+1):
with for_(tau, tau_start, tau<tau_end, tau+tau_steps):
play("pi_half", "Qubit")
wait(tau)
play("pi_half", "Qubit")
align()
play("laser_ON", "Laser")
times, counts = get_photons()
All other characterization and calibration protocols, from echo measurements to randomized benchmarking with depth >10,000, are easy to code in QUA and run with minimal overhead – orders of magnitude faster than standard AWGs – and with the highest performance on the OPX products.