# --- # 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] # # Lattice Gauge Theory Simulation # ## Introduction # In this notebook, we utilize Aquila to simulate the dynamics of a Lattice Gauge Theory (LGT) with a 1D Rydberg atom chain. # In the realm of gauge theories, it has been discovered that the Z2 ground state and the quantum scar of the Rydberg chain correspond to the 'string' state and the string-inversion mechanism of the studied LGT, respectively. # More intriguingly, by selectively addressing certain atoms, we can induce defects in the chain and simulate the propagation of particle-antiparticle pairs. # This notebook is inspired by the paper by F. M. Surace et al. (DOI: 10.1103/PhysRevX.10.021041). # %% [markdown] # ## Define the Program # %% import os import numpy as np import matplotlib.pyplot as plt from bloqade.analog import cast, load, save, piecewise_linear from bloqade.analog.ir.location import Chain if not os.path.isdir("data"): os.mkdir("data") # %% [markdown] # We introduce two new features of Bloqade here in order to accomplish this LGT simulation. # # Firstly, instead of building the waveforms off the program itself using methods like `.piecewise_linear` and `.piecewise_constant` as seen in previous tutorials, # we build the waveforms outside the program and then use the `.apply` method to put them into the program. # This is similar to the *Multi-qubit Blockaded Rabi Oscillations* tutorial except instead of building an entire program # without a geometry and then applying it on top of one, we introduce waveforms to an existing program structure. # # For complex programs such as this one this makes prototyping significantly easier # (you can view individual waveforms by calling `.show()` on them so long as there are no variables in the waveform) and keeps things modular. # # Secondly, we need to be able to "selectively address" certain atoms. # This is accomplished via Local Detuning where we are able to control how much of the global detuning # is applied to each atom through a multiplicative scaling factor. # # Bloqade has two methods of doing this (refer to the "Advanced Usage" page) but here we opt for the `.scale` method. # We create a list of values from 0 to 1 with length equal to the number of atoms in the system. # A value of 1 means the atom should experience the full detuning waveform while 0 means the atom should not experience it at all. # The n-th value in the list corresponds to the n-th atom in the system. # %% N_atom = 13 # Setup the detuning scaling per atom. # Note that the list of scaling values has a length equal to # the number of atoms detuning_ratio = [0] * N_atom detuning_ratio[1 : (N_atom - 1) : 2] = [1, 1, 1, 1, 1, 1] detuning_ratio[(N_atom - 1) // 2] = 1 # Notice that the detuning ratio will allow us to prepare a Z2 # ordered phase. However, the sequence of contiguous 1s in the # middle introduce a defect. detuning_ratio # %% run_time = cast("run_time") # Define our waveforms first, then plug them into the # program structure below rabi_amplitude_wf = piecewise_linear( durations=[0.1, 2.0, 0.05, run_time, 0.05], values=[0, 5 * np.pi, 5 * np.pi, 4 * np.pi, 4 * np.pi, 0], ) uniform_detuning_wf = piecewise_linear( durations=[2.1, 0.05, run_time + 0.05], values=[-6 * np.pi, 8 * np.pi, 0, 0] ) local_detuning_wf = piecewise_linear( [0.1, 2.0, 0.05, run_time + 0.05], values=[0, -8 * 2 * np.pi, -8 * 2 * np.pi, 0, 0] ) # Note that `scale` is called right after defining the # global detuning program = ( Chain(N_atom, lattice_spacing=5.5, vertical_chain=True) .rydberg.rabi.amplitude.uniform.apply(rabi_amplitude_wf) .detuning.uniform.apply(uniform_detuning_wf) .scale(detuning_ratio) .apply(local_detuning_wf) ) run_times = np.arange(0.0, 1.05, 0.05) batch = program.batch_assign(run_time=run_times) # %% [markdown] # ## Run on Emulator and Hardware # # We now run the program on both the emulator and quantum hardware. # #
#

Hardware Execution Cost

#

# # For this particular program, 21 tasks are generated with each task having 200 shots, amounting to # __USD \\$48.30__ on AWS Braket. # #

#
# %% emu_filename = os.path.join(os.path.abspath(""), "data", "lgt-emulation.json") if not os.path.isfile(emu_filename): emu_batch = batch.bloqade.python().run(1000, rtol=1e-10) save(emu_batch, emu_filename) # %% filename = os.path.join(os.path.abspath(""), "data", "lgt-job.json") if not os.path.isfile(filename): hardware_batch = batch.parallelize(15).braket.aquila().run_async(shots=200) save(hardware_batch, filename) # %% [markdown] # ## Plot the Results # %% emu_batch = load(emu_filename) hardware_batch = load(filename) # %% [markdown] # The following code plots the Rydberg onsite density from emulation as a function of evolution time after preparing the initial state with a defect in the middle. # We observe that this defect propagates ballistically to the boundary and bounces back and forth. # %% emu_report = emu_batch.report() emu_rydberg_densities = emu_report.rydberg_densities() plt.imshow(np.array(emu_rydberg_densities).T, vmin=0, vmax=1) plt.xticks( ticks=np.arange(0, 20, 2), labels=np.round(np.arange(0.0, 1.0, 0.1), 1), minor=False ) plt.xlabel("t[us]", fontsize=14) plt.ylabel("atom", fontsize=14) plt.title("simulation", fontsize=14) plt.colorbar(shrink=0.68) plt.show() # %% [markdown] # Additionally, we can plot the correlation between nearest-neighbor sites to illustrate the propagation of the defect across the chain. # %% def rydberg_correlation(task_bits: np.ndarray, i: int, j: int) -> np.ndarray: return np.mean(task_bits[:, i] * task_bits[:, j]) emu_bitstrings = emu_report.bitstrings() bits = np.array(emu_bitstrings) num_tasks = len(bits) corrs = np.zeros((num_tasks, N_atom - 1), dtype=np.float64) for task_index, task_bits in enumerate(bits): for i in range(N_atom - 1): corrs[task_index, i] = rydberg_correlation(task_bits, i, i + 1) plt.imshow(corrs.T, vmin=0, vmax=1) plt.xticks( ticks=np.arange(0, 20, 2), labels=np.round(np.arange(0.0, 1.0, 0.1), 1), minor=False ) plt.xlabel("t[us]") plt.ylabel("atom") plt.title("simulation") plt.colorbar(shrink=0.68) plt.show() # %% [markdown] # As observed below, the state with a local defect in the middle can be prepared with high fidelity on *Aquila*. # Following the preparation stage, akin to the emulation, the defect rapidly propagates across the system. # %% aquila_rydberg_densities = hardware_batch.report().rydberg_densities() plt.imshow(np.array(aquila_rydberg_densities).T, vmin=0, vmax=0.8) plt.xticks( ticks=np.arange(0, 20, 2), labels=np.round(np.arange(0.0, 1.0, 0.1), 1), minor=False ) plt.xlabel("t[us]", fontsize=14) plt.ylabel("atom", fontsize=14) plt.title("Aquila", fontsize=14) plt.colorbar(shrink=0.68) plt.show()