# ---
# jupyter:
# jupytext:
# formats: ipynb,py:percent
# hide_notebook_metadata: false
# text_representation:
# extension: .py
# format_name: percent
# format_version: '1.3'
# jupytext_version: 1.14.5
# kernelspec:
# display_name: .venv
# language: python
# name: python3
# ---
# %% [markdown]
# Download Script
# Download Job
#
#
#
Job Files for Complete Examples
#
# To be able to run the complete examples without having to submit your program to hardware and wait, you'll
# need to download the associated job files. These files contain the results of running the program on
# the quantum hardware.
#
# You can download the job files by clicking the "Download Job" button above. You'll then need to place
# the job file in the `data` directory that was created for you when you ran the `import` part of the script
# (alternatively you can make the directory yourself, it should live at the same level as wherever you put this script).
#
#
#
# %% [markdown]
# # Two Qubit Adiabatic Sweep
# ## 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 np
import matplotlib.pyplot as plt
from bloqade.analog import var, cast, load, save, start
if not os.path.isdir("data"):
os.mkdir("data")
# %% [markdown]
# ## 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)
# %% [markdown]
# ## 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 task
emu_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)
# %% [markdown]
# Then, we can run the program on the hardware after parallelizing the tasks.
# We can then save the results to a file.
#
#
#
Hardware Execution Cost
#
#
# For this particular program, 7 tasks are generated with each task having 100 shots, amounting to
# __USD \\$9.10__ on AWS Braket.
#
#
#
# %%
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)
# %% [markdown]
# ## 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_dict
# %% [markdown]
# Before we can plot the results we need to load the data from the files.
# %%
# get emulation report and number of shots per each state
emu_batch = load(emu_filename)
# get hardware report and number of shots per each state
hardware_batch = load(filename)
# hardware_batch.fetch()
# save(hardware_batch, filename)
# %% [markdown]
# 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()