# ---
# 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]
# # Quantum Scar Dynamics
# ## Introduction
# In this example we show how to use Bloqade to run a quantum scar dynamics experiment.
# The protocol is as follows: We first prepare the atoms in a Z2 state using an
# adiabatic sweep. We then apply a Rabi pulse to the atoms, which will cause the atoms
# to oscillate but because of the Blockade effect, the atoms will not be able to
# transition to the Rydberg state. However, the atoms will still oscillate between the
# ground and some other excited many-body states.
# %% [markdown]
# ## Define the program
# This notebook will also show some advanced features of Bloqade, in particular, how to
# use the `slice` and `record` API to build a program that is compatible with the
# hardware constraints that the rabi drive must be 0 at the end of the protocol.
#
# The idea is that first we define the full waveform we would like to apply to the atoms
# then after defining the full waveform you simply call the `slice` method to slice the
# that waveform stopping at a variable time `run_time`. This works fine for detuning but
# for the Rabi drive, we need to make sure that the Rabi drive is 0 at the end of the
# the waveform. To do this, we use the `record` method to record the value of the Rabi
# drive at the end of the waveform. We then use the `linear` method to append a segment
# to the waveform that fixes the value of the Rabi drive to 0 at the end of the
# waveform. Now any value of `run_time` will be a valid waveform that is compatible
# with the hardware constraints.
# %%
import os
import numpy as np
import matplotlib.pyplot as plt
from bloqade.analog import var, load, save
from bloqade.analog.atom_arrangement import Chain
if not os.path.isdir("data"):
os.mkdir("data")
n_atoms = 11
lattice_spacing = 6.1
run_time = var("run_time")
quantum_scar_program = (
Chain(n_atoms, lattice_spacing=lattice_spacing)
# define detuning waveform
.rydberg.detuning.uniform.piecewise_linear(
[0.3, 1.6, 0.3], [-18.8, -18.8, 16.3, 16.3]
)
.piecewise_linear([0.2, 1.6], [16.3, 0.0, 0.0])
# slice the detuning waveform
.slice(start=0, stop=run_time)
# define rabi waveform
.amplitude.uniform.piecewise_linear([0.3, 1.6, 0.3], [0.0, 15.7, 15.7, 0.0])
.piecewise_linear([0.2, 1.4, 0.2], [0, 15.7, 15.7, 0])
# slice waveform, add padding for the linear segment
.slice(start=0, stop=run_time - 0.065)
# record the value of the waveform at the end of the slice to "rabi_value"
.record("rabi_value")
# append segment to waveform that fixes the value of the waveform to 0
# at the end of the waveform
.linear("rabi_value", 0, 0.065)
)
# get run times via the following:
prep_times = np.arange(0.2, 2.2, 0.2)
scar_times = np.arange(2.2, 4.01, 0.01)
run_times = np.unique(np.hstack((prep_times, scar_times)))
batch = quantum_scar_program.batch_assign(run_time=run_times)
# %% [markdown]
# ## Run on Emulator and Hardware
# We will run the experiment on the emulator and hardware, saving the results to disk
# so that we can plot them later. for more details on where these lines of code come
# from, see the first few tutorials.
#
#
#
Hardware Execution Cost
#
#
# For this particular program, 191 tasks are generated with each task having 100 shots, amounting to
# __USD \\$248.30__ on AWS Braket.
#
#
#
# %%
emu_filename = os.path.join(
os.path.abspath(""), "data", "quantum-scar-dynamics-emulation.json"
)
if not os.path.isfile(emu_filename):
emu_batch = batch.bloqade.python().run(1000)
save(emu_batch, emu_filename)
filename = os.path.join(os.path.abspath(""), "data", "quantum-scar-dynamics-job.json")
if not os.path.isfile(filename):
hardware_batch = (
batch.parallelize(24)
.braket.aquila()
.run_async(100, ignore_submission_error=True)
)
save(hardware_batch, filename)
# %% [markdown]
# ## Plotting the results
# The quantity we are interested in is the probability of the atoms being in the Z2
# state. We can get this by looking at the counts of the Z2 state in the report
# Below we define a function that will get the probability of the Z2 state for each
# time step in the experiment.
# %%
emu_batch = load(emu_filename)
hardware_batch = load(filename)
# hardware_batch.fetch()
# save(hardware_batch, filename)
# %%
def get_z2_probabilities(report):
z2_probabilities = []
for count in report.counts():
z2_probability = count.get("01010101010", 0) / sum(list(count.values()))
z2_probabilities.append(z2_probability)
return z2_probabilities
# %% [markdown]
# We can now plot the results from the emulator and hardware. We see that the emulator
# %%
emu_report = emu_batch.report()
hardware_report = hardware_batch.report()
emu_run_times = emu_report.list_param("run_time")
emu_z2_prob = get_z2_probabilities(emu_report)
hw_run_times = hardware_report.list_param("run_time")
hw_z2_prob = get_z2_probabilities(hardware_report)
plt.plot(emu_run_times, emu_z2_prob, label="Emulator", color="#878787")
plt.plot(hw_run_times, hw_z2_prob, label="QPU", color="#6437FF")
plt.legend()
plt.xlabel("Time ($\mu s$)")
plt.ylabel("Z2-state Probability")
plt.show()