Two Qubit Adiabatic Sweep
Download Script · Download Job
This notebook was executed using the upstream saved emulator and hardware results. No Aquila jobs were submitted during this website build.
Introduction
In this example, we show how to use Bloqade to program an adiabatic sweep on a pair of atoms, with the distance between atoms gradually increasing per task. This will allow us to explore the effect of the Rydberg interaction. We will run the program on both the emulator and the hardware to compare the results.
import os
import numpy as npimport matplotlib.pyplot as pltfrom bloqade.analog import var, cast, load, save, start
if not os.path.isdir("data"): os.mkdir("data")Defining the Program
Now, we define our program of interest. For an adiabatic protocol, we keep that Rabi frequency at a considerable value while slowly ramping the detuning from a large negative to a positive value. The idea is that when the detuning is large and negative the atoms remain in the ground state. As the detuning is ramped to positive values, the atoms are able to be excited to the Rydberg state, however if the atoms are too close together, the Rydberg interactions effectively acts like a negative detuning to neighboring atoms, preventing them from being excited. This is the blockade effect. For atoms that are sufficiently far apart, the Rydberg interaction is negligible and the atoms can be excited to the Rydberg state. As the atoms get closer together, the Rydberg interaction becomes more significant the probability of exciting both atoms becomes smaller. The typical length scale for the cross over from the non-interacting to the blockade regime is the blockade radius.
Note that you can perform arithmetic operations directly on variables in the program
but this requires the variable to be explicitly declared by passing a string to the
var function and THEN doing arithmetic on it.
detuning_value = var("detuning_value")durations = cast(["ramp_time", "run_time", "ramp_time"])prog = ( start.add_position([(0, 0), (0, "atom_distance")]) .rydberg.rabi.amplitude.uniform.piecewise_linear( durations=durations, values=[0, "rabi_value", "rabi_value", 0] ) .detuning.uniform.piecewise_linear( durations=durations, values=[ -detuning_value, -detuning_value, detuning_value, detuning_value, ], ))
distances = np.arange(4, 11, 1)batch = prog.assign( ramp_time=1.0, run_time=2.0, rabi_value=15.0, detuning_value=15.0).batch_assign(atom_distance=distances)Run on Emulator and Hardware
In previous examples, we have shown how to run a program on the emulator and hardware. First, we will run the program on the emulator and save the results to a file.
# get emulation batch, running 1000 shots per taskemu_filename = os.path.join( os.path.abspath(""), "data", "two-qubit-adiabatic-emulation.json")
if not os.path.isfile(emu_filename): emu_batch = batch.bloqade.python().run(1000) save(emu_batch, emu_filename)Then, we can run the program on the hardware after parallelizing the tasks. We can then save the results to a file.
filename = os.path.join(os.path.abspath(""), "data", "two-qubit-adiabatic-job.json")
if not os.path.isfile(filename): hardware_batch = batch.parallelize(24).braket.aquila().run_async(shots=100) save(hardware_batch, filename)Plot the Results
To show the blockade effect on the system, we will plot the
probability of having 0, 1, or 2 Rydberg atoms as a function of time.
We will do this for both the emulator and the hardware. We can use the
following function to get the probabilities from the shot counts of each
of the different configurations of the two Rydberg atoms: 00, 10, 01, and 11.
Note that 0 corresponds to the Rydberg state while 1 corresponds to the
ground state. As such, 00 corresponds to two Rydberg atoms, 10 and 01
corresponds to one Rydberg atom and one ground-state atom, and 11 corresponds
to two ground-state atoms.
def rydberg_state_probabilities(emu_counts): probabilities_dict = {"0": [], "1": [], "2": []}
# iterate over each of the task results for task_result in emu_counts: # get total number of shots total_shots = sum(task_result.values()) # get probability of each state probabilities_dict["0"].append(task_result.get("11", 0) / total_shots) probabilities_dict["1"].append( (task_result.get("10", 0) + task_result.get("01", 0)) / total_shots ) probabilities_dict["2"].append(task_result.get("00", 0) / total_shots)
return probabilities_dictBefore we can plot the results we need to load the data from the files.
# get emulation report and number of shots per each stateemu_batch = load(emu_filename)
# get hardware report and number of shots per each statehardware_batch = load(filename)# hardware_batch.fetch()# save(hardware_batch, filename)We can use the rydberg_state_probabilities
function to extract the probabilities from the counts. This function
takes a list of counts and returns a dictionary of probabilities for
each state. The counts are obtained from the report of the batch
object.
Now, we can plot the results!
emu_report = emu_batch.report()hardware_report = hardware_batch.report()
emu_rydberg_state_probabilities = rydberg_state_probabilities(emu_report.counts())hw_rydberg_state_probabilities = rydberg_state_probabilities(hardware_report.counts())
emu_distances = emu_report.list_param("atom_distance")hw_distances = hardware_report.list_param("atom_distance")
fig, ax = plt.subplots()emu_colors = ["#55DE79", "#EDFF1A", "#C2477F"] # Green, Yellow, Red
emu_lines = []hw_lines = []for rydberg_state, color in zip(["0", "1", "2"], emu_colors): (hw_line,) = ax.plot( emu_distances, hw_rydberg_state_probabilities[rydberg_state], label=rydberg_state + "-Rydberg QPU", color=color, ) (emu_line,) = ax.plot( hw_distances, emu_rydberg_state_probabilities[rydberg_state], color="#878787", label="Emulator", )
emu_lines.append(emu_line) hw_lines.append(hw_line)
ax.legend(handles=[*hw_lines, emu_lines[-1]])ax.set_xlabel("time ($\mu s$)")ax.set_ylabel("Probability")fig.show()