# Using Aquila on Braket

The AWS Braket interface allows you to send tasks from your laptop to quantum processors at QuEra via the AWS cloud infrastructure. This tutorial will give you a first taste of what this workflow looks like. It's simple: Define the arrangement of atoms you want to use and specify your Hamiltonian parameters of choice to govern the behavior of those atoms. Submit the program and analyze the results.

Below we go through the code that can be found here. Please note that quera_ahs_utils is installed via pip:

pip install quera_ahs_utils

or Conda via:

conda install -c conda-forge quera-ahs-utils

Ready to explore what's possible with Aquila? Let's dive straight in.

# Goal

To say hello to the world of neutral atoms, let's investigate one of the central phenomena in quantum many-body physics: the emergence of ordered phases of matter. In this tutorial, we'll show how Aquila can prepare the simplest of such ordered phases, namely an antiferromagnetic phase (aka Z2 phase) in both one- and two-dimensional arrays of atoms. To make this possible, we will make use of the Rydberg blockade - the mechanism at the heart of our quantum computing architecture.

# Register

Let's start off with the 1D case. To achieve our goal, we first need to define our Rydberg-atom quantum computer register. We arrange them in a line with a separation of 6.1 μm between each pair of atoms.


from braket.ahs.atom_arrangement import AtomArrangement
import numpy as np
from quera_ahs_utils.plotting import show_register

a = 6.1e-6  # meters
N_atoms = 11

register = AtomArrangement()
for i in range(N_atoms):

fig = show_register(register)


# Hamiltonian

The next component we need to specify is the Hamiltonian. It's the energy function that governs the behavior of our atoms, including their interactions. In the lab, the Hamiltonian is implemented by applying lasers to the atoms.


from braket.ahs.hamiltonian import Hamiltonian

H = Hamiltonian()

Specifically, the Hamiltonian governing our system of atoms takes the following form:
$H(t) = \sum\limits_{j} \frac{\Omega(t)}{2} \left( e^{i \phi(t) } | g_j \rangle \langle r_j | + e^{-i \phi(t) } | r_j \rangle \langle g_j | \right) - \sum\limits_{j} \Delta(t) \hat{n}_j + \sum\limits_{j < k} V_{jk} \hat{n}_j \hat{n}_k$

where $$\Omega$$, $$\phi$$ and $$\Delta$$ denote the Rabi frequency, laser phase, and the detuning of the driving laser field coupling the ground states $$\ket{g_j}$$ and excited Rydberg state $$\ket{r_j}$$ of the j-th atom. Our task is to specify these Hamiltonian parameters ($$\Omega$$, $$\phi$$ and $$\Delta$$). Here, they will be time-dependent in order to help us carry out a protocol called adiabatic state preparation. In case this sounds unfamiliar, feel free to read up on the physics background on our open source platform Bloqade.

In our current case, we only want to tell the system to activate the set of lasers controlling the Rabi frequency and the global detuning. In particular, we will choose a constant Rabi frequency of $$\Omega = 2.5 \times 10^6$$ and a linearly increasing detuning from $$\Delta = -9 \times 10^6$$ to $$\Delta = 7 \times 10^6$$, together with a total time duration of t=4 μs. The phase parameter will simply be set to zero. The rest of the code ensures that $$\Omega$$ and $$\Delta$$ are ramped on and off in a way compatible with the experimental setup.


from quera_ahs_utils.plotting import show_global_drive
from quera_ahs_utils.drive import get_drive

omega_min = 0
omega_max = 2.5e6 * 2 * np.pi
detuning_min = -9e6 * 2 * np.pi
detuning_max = 7e6 * 2 * np.pi

time_max = 4e-6
time_ramp = 0.15*time_max

time_points = [0, time_ramp, time_max - time_ramp, time_max]
omega_values = [omega_min, omega_max, omega_max, omega_min]
detuning_values = [detuning_min, detuning_min, detuning_max, detuning_max]
phase_values = [0, 0, 0, 0]

drive = get_drive(time_points, omega_values, detuning_values, phase_values)
H += drive

show_global_drive(drive)

However, that's not the full story. These drive terms alone don't tell us how the atoms interact. But interactions are at the core of many-body quantum physics! That's where the Rydberg blockade comes in. Mathematically, it is described by the term
$H_{interaction} = \sum\limits_{j < k}V_{jk} \hat{n}_j \hat{n}_k$

in the Rydberg Hamiltonian. The key idea is that within a certain distance - the so-called blockade radius - only one atom will be excited into an $$\ket{r}$$ state. Having more excitations will cost too much energy. This means that the states of two neighboring atoms will depend on each other and exhibit specific patterns which we will plot below. Creating these ordered states is non-trivial. Following the adiabatic protocol mentioned previously is crucial since it allows us to slowly introduce these complex interactions into the system.

Note: In the current version of the AHS module in Braket, this Rydberg interaction term is automatically calculated from the atom positions. Hence, we don't need to worry about specifying it explicitly. Our Hamiltonian is all set!

# Defining the program (1D case)

Now, we can combine the register and Hamiltonian into a program. In particular, this program falls within the class of Analog Hamiltonian Simulation (AHS). If you're curious about other types of quantum computing, take a look at gate-based circuits or tutorials on quantum annealing.

from braket.ahs.analog_hamiltonian_simulation import AnalogHamiltonianSimulation

ahs_program = AnalogHamiltonianSimulation(
register=register,
hamiltonian=H
)


# Simulation on classical hardware

Before submitting the task to run on actual quantum hardware, let's first check our program by running a local simulation on one of AWS's classical servers.

from braket.devices import LocalSimulator

classical_device = LocalSimulator("braket_ahs")

nshots = 1000

# The result can be downloaded directly into an object in the python session:

What phase of matter did we create? Let's have a look at the average density of atoms on each site:

from quera_ahs_utils.analysis import get_avg_density
from quera_ahs_utils.plotting import plot_avg_density

n_rydberg = get_avg_density(result)
plot_avg_density(n_rydberg, register)

Indeed, we already see from this classical simulation that a pattern emerges: The alternating occupation density in the chain of atoms indicates a so-called Z2 phase.

# Simulation on Aquila

And now for the truly exciting part - let's bring Aquila into the game.

Note: Running this program on the Aquila processor will incur a cost of 0.30 USD for submitting the program and 0.01 USD per shot. For scientific purposes, one would run this notebook with 1000 shots per task. To simply get a feel for the workflow of using Braket, however, we recommend reducing the number of shots. Furthermore, AWS offers 1 hour of free simulation time per month.

from braket.aws import AwsDevice, AwsSession
from boto3 import Session

boto_session = Session(region_name="us-east-1")
aws_session = AwsSession(boto_session)

aquila = AwsDevice("arn:aws:braket:us-east-1::device/qpu/quera/Aquila",aws_session)

# To make the program compatible with the quantum hardware, we still need to slice it into discrete time steps:
discretized_ahs_program = ahs_program.discretize(aquila)

f,ax = show_final_avg_density(result)
plt.show()

Success! We can clearly observe an alternating occupation density, i.e. the Z2 phase, just as in the previous classical simulation.

# 2D Case

Now that we are familiar with the basic workflow, we can follow the same steps to extend our investigation to the 2D case.

Let's start off with a simple square lattice with 3x3 atoms. Why? This system is again small enough to first simulate it on classical hardware and thus benchmark the quantum device.

b = 6.7e-6  # meters
N_x = N_y = 3

register_2D = AtomArrangement()
for i in range(N_x):
for j in range(N_y):

show_register(register_2D)


H_2D = Hamiltonian()

omega_min = 0
omega_max = 2.5e6 * 2 * np.pi
detuning_min = -8.75e6 * 2 * np.pi
detuning_max = 8.75e6 * 2 * np.pi

time_max = 3e-6
time_ramp = 0.25e-6

time_points = [0, time_ramp, time_max - time_ramp, time_max]
omega_values = [omega_min, omega_max, omega_max, omega_min]
detuning_values = [detuning_min, detuning_min, detuning_max, detuning_max]
phase_values = [0, 0, 0, 0]

drive_2D = get_drive(time_points, omega_values, detuning_values, phase_values)
H_2D += drive_2D

Now, we run the simulation on classical hardware:

ahs_program_2D = AnalogHamiltonianSimulation(
register=register_2D,
hamiltonian=H_2D
)

result_2D = classical_device.run(ahs_program_2D, shots=nshots).result()

plot_avg_density(get_avg_density(result_2D), register_2D)

We see a pattern emerging, similar to the Z2 phase in the 1D case. In 2D, this pattern is typically referred to as the checkerboard phase.
But will we observe the same behavior on Aquila?

ahs_program_finale = AnalogHamiltonianSimulation(
register=register_finale,
hamiltonian=H_2D
)

result_2D = aquila.run(ahs_program_2D, shots=nshots).result()

plot_avg_density(get_avg_density(result_2D), register_2D);


# Finale: Moving beyond classical limits

So what's the big picture? We've walked through two best practice examples of simulating the order in a quantum many-body system, first on classical hardware and then on an actual quantum device, namely Aquila.

But what's the advantage in using Aquila? Well, having gained confidence in Aquila, we can use this quantum device to move into a regime that is not so easy to simulate classically. To give you an impression, let's make one final plot showing the order of an 11x11 square lattice of neutral atoms.

b = 6.7e-6  # meters
N_x = N_y = 11

register_finale = AtomArrangement()
for i in range(N_x):
for j in range(N_y):


ahs_program_finale = AnalogHamiltonianSimulation(
register=register_finale,
hamiltonian=H_2D
)

result_2D = aquila.run(ahs_program_2D, shots=nshots).result()

plot_avg_density(get_avg_density(result_2D), register_2D);

Indeed, the checkerboard phase clearly emerges. We have successfully created a complex many-body quantum state in which 121 atoms interact in Aquila to create an ordered phase of matter!

# Connecting to Braket

Now that you've understood the basic workflow of using the Braket SDK you are ready to start submitting your own tasks to Aquila for real. Before heading over to the full Braket documentation page, here's an outline of the five most important steps you will be taken through there: