Hello World with Bloqade (Neutral Atom Computing)
Program neutral atom quantum hardware with QuEra's Bloqade framework - define atom arrays, drive Rydberg transitions, and run analog Hamiltonian simulations.
Neutral atom quantum computing is one of the most actively developed approaches to building large-scale quantum hardware. Instead of superconducting circuits or trapped ions, neutral atom machines use individual atoms held in place by tightly focused laser beams called optical tweezers. The atoms are arranged in programmable 2D patterns, and quantum operations are performed by driving atomic transitions with precisely shaped laser pulses.
QuEra’s Aquila processor, available through AWS Braket, is the first publicly accessible neutral atom quantum computer. Bloqade is the primary Python framework for programming it.
For a deeper introduction to the physical principles, see Neutral Atom Qubit.
Physical Background: Neutral Atoms as Qubits
Before writing any code, it helps to understand what is physically happening inside a neutral atom quantum computer.
Trapping and Cooling
The atoms used in QuEra’s machine are rubidium-87 (Rb-87). They are first cooled to near absolute zero (on the order of 10 microkelvin) using standard laser cooling techniques. At these temperatures the atoms are nearly motionless, which is essential for precise control.
Each atom is then trapped at a specific location by an optical tweezer, a tightly focused laser beam that creates a potential well for the atom. By steering arrays of these tweezers, the machine can arrange atoms in arbitrary 2D patterns with micrometer-level precision.
Qubit Encoding
The qubit is encoded in two internal energy levels of the Rb-87 atom:
- |0>: a ground hyperfine state (the atom is in its lowest energy configuration)
- |1>: a highly excited Rydberg state (typically the 70S orbital), where the outermost electron is promoted to a very large orbit far from the nucleus
A laser tuned to the transition frequency between these two states drives the atom back and forth between |0> and |1>. The rate of this oscillation is the Rabi frequency, Omega.
The Rydberg Blockade
The key feature that makes neutral atoms useful for quantum computing is the Rydberg blockade. When an atom is in the Rydberg state, its electron cloud is enormous compared to the ground state. Two nearby Rydberg atoms experience a strong van der Waals interaction:
V = C6 / r^6
Here C6 is the van der Waals coefficient (which depends on the specific Rydberg state) and r is the distance between the two atoms. For the 70S state of Rb-87, C6 is approximately 10^12 MHz * um^6.
When this interaction energy V is much larger than the Rabi frequency Omega, it costs too much energy for both atoms to be in the Rydberg state simultaneously. The system is “blockaded”: if one atom is excited to |1>, its neighbor is prevented from being excited. This is a purely physical effect that creates entanglement between atoms without any explicit two-qubit gate.
Blockade Radius
The blockade radius r_b is defined as the distance at which the interaction energy equals the Rabi frequency:
r_b = (C6 / Omega)^(1/6)
For typical parameters (C6 ~ 10^12 MHz*um^6, Omega ~ 1 MHz), this gives r_b ~ 10 um.
| Atom Spacing vs. r_b | Regime | Physical Effect |
|---|---|---|
| spacing < r_b | Blockaded | Atoms interact strongly; at most one can be in |1> at a time. This is the basis for entanglement. |
| spacing ~ r_b | Intermediate | Partial blockade; both atoms can be excited but with reduced probability. |
| spacing >> r_b | Independent | Atoms do not interact; each can be addressed and excited independently. |
QuEra’s Aquila processor uses Rb-87 atoms with typical blockade radii around 7 to 10 micrometers, depending on the chosen Rabi frequency.
Aquila Hardware Specifications
QuEra’s Aquila is the neutral atom QPU available through Amazon Braket. Here are its key specs:
| Parameter | Value |
|---|---|
| Atom species | Rubidium-87 |
| Maximum atom count | 256 |
| Field of view | 75 x 75 micrometers |
| Maximum Rabi frequency (Omega) | 4pi MHz (~12.57 rad/us) |
| Detuning range (delta) | -125 to +125 MHz |
| Maximum pulse duration | 4 microseconds |
| Minimum atom spacing | 4 micrometers |
| Typical blockade radius | 7 to 10 micrometers |
Measurement on Aquila: The hardware detects the state of each atom by imaging. Ground-state atoms are illuminated with a resonant laser and fluoresce brightly. Rydberg atoms do not fluoresce. In practice, the detection process uses a “push-out” beam that selectively removes Rydberg-state atoms from the trap, and then the remaining ground-state atoms are imaged. The result is a bitstring where 0 = ground state (atom present in image) and 1 = Rydberg state (atom absent from image).
How Bloqade Differs from Gate-Based Frameworks
In Qiskit or Cirq, you write a sequence of discrete gates: H, CNOT, Rz, and so on. The hardware executes them one by one (or in parallel where allowed by the circuit structure). This is digital quantum computing.
Bloqade mostly operates in analog mode. You do not specify individual gates. Instead, you define a continuous time-varying laser pulse (a drive) and the atoms evolve under the Rydberg Hamiltonian for the duration of the pulse. The measurement outcomes reflect the final state of that continuous evolution.
This is a fundamentally different mental model:
- Gate-based: what gates do I apply and in what order?
- Analog: what pulse shape do I drive, and what does the resulting time evolution do to the atoms?
This makes Bloqade harder to get started with if you have a gate-based background, but it also means you can access dynamics that are very natural to the neutral atom hardware and difficult to implement efficiently on gate-based machines.
Comparison to Gate-Based Hardware
The following table compares neutral atom hardware to the other major qubit platforms:
| Feature | Neutral Atoms (QuEra Aquila) | Superconducting (IBM Eagle) | Trapped Ion (IonQ Aria) |
|---|---|---|---|
| Qubit count | 256 | 433 | 32 |
| Gate fidelity | Analog mode (no discrete gates) | ~99.5% CX gate | ~99.5% two-qubit gate |
| Connectivity | All-to-all within blockade radius | Fixed heavy-hex topology | All-to-all |
| Operation mode | Analog Hamiltonian simulation | Gate-based digital | Gate-based digital |
| Coherence time | ~1 to 4 microseconds | ~100 to 300 microseconds | ~seconds |
| Best application | Optimization, quantum simulation | General algorithms | High-accuracy circuits |
The short coherence time for neutral atoms is offset by the fact that the entire computation happens in a single analog pulse, so the algorithm completes within that coherence window.
Installation
pip install bloqade
To submit jobs to real QuEra hardware on AWS Braket:
pip install bloqade[braket]
Defining an Atom Register
The first step in any Bloqade program is placing atoms in 2D space. Positions are in micrometers. Bloqade provides helpers for common lattice geometries:
from bloqade.atom_arrangement import Square, Chain, Honeycomb
square = Square(3, lattice_spacing=5.0) # 3x3 grid, 5 um spacing
chain = Chain(5, lattice_spacing=6.0) # 5 atoms in a line
You can also specify arbitrary positions directly:
from bloqade import start
custom = start.add_position([
(0.0, 0.0),
(6.0, 0.0),
(3.0, 5.2), # equilateral triangle
])
The Rydberg Hamiltonian: What the Physics Means
When you run a pulse in Bloqade, the atoms evolve under:
H(t) = (Omega(t)/2) * sum_i X_i - delta(t) * sum_i n_i + sum_{i<j} V_ij n_i n_j
The three terms correspond to physical things you can directly control or observe:
Omega(t): the Rabi frequency, controlled by the laser intensity. This drives transitions between the ground state and the Rydberg excited state. Think of it as “how fast the atoms are oscillating between 0 and 1.”delta(t): the detuning, controlled by the laser frequency relative to the atomic resonance. Positive detuning energetically favours the excited state; negative detuning favours the ground state.V_ij = C6 / r^6: the Rydberg interaction between atoms i and j, which depends on their distance. This is not directly controlled but is set by atom placement. When two atoms are within the blockade radius (roughly 10 um for typical Rydberg states), they cannot both be excited simultaneously, which is the basis for entanglement.
Building a Pulse Sequence
A pulse is specified as a piecewise linear function of time. You list durations and values at the breakpoints, and Bloqade interpolates linearly between them.
from bloqade import start
from bloqade.atom_arrangement import Chain
program = (
Chain(3, lattice_spacing=6.0) # 3 atoms in a line
.rydberg.rabi.amplitude.uniform # uniform Rabi drive on all atoms
.piecewise_linear(
durations=[0.4, 1.0, 0.4], # microseconds
values=[0, 15.7, 15.7, 0] # MHz
)
)
This defines a pulse that:
- Ramps up from 0 to 15.7 MHz over 0.4 us (turn on)
- Holds at 15.7 MHz for 1.0 us (constant drive)
- Ramps back down to 0 over 0.4 us (turn off)
The peak value of 15.7 MHz is chosen so that Omega * t_flat = pi, which is a pi pulse and inverts the atomic population.
Pulse Parameter Constraints
Bloqade enforces several hardware constraints on pulse shapes. Understanding these saves time debugging:
- Rabi amplitude must start and end at zero. You cannot abruptly turn the laser on or off. Every amplitude waveform needs a ramp-up at the beginning and a ramp-down at the end.
- Detuning can start and end at any value. Unlike the amplitude, detuning has no ramp requirement.
- Minimum segment duration is 0.05 microseconds. Each piece in a
piecewise_linearcall must last at least 50 nanoseconds. - Maximum total duration is 4 microseconds on Aquila hardware.
Here is a correct pulse specification:
from bloqade.atom_arrangement import Chain
# Correct: amplitude starts and ends at 0, segments are >= 0.05 us
correct_program = (
Chain(1, lattice_spacing=6.0)
.rydberg.rabi.amplitude.uniform
.piecewise_linear(
durations=[0.3, 1.0, 0.3], # all segments >= 0.05 us
values=[0, 10.0, 10.0, 0] # starts at 0, ends at 0
)
)
And here is an incorrect specification that Bloqade rejects:
from bloqade.atom_arrangement import Chain
# INCORRECT: amplitude jumps directly to 10.0 (does not start at 0)
try:
bad_program = (
Chain(1, lattice_spacing=6.0)
.rydberg.rabi.amplitude.uniform
.piecewise_linear(
durations=[1.0, 0.3],
values=[10.0, 10.0, 0] # starts at 10.0, not 0!
)
)
bad_program.bloqade.python().run(10)
except Exception as e:
print(f"Validation error: {e}")
When you see a validation error about amplitude boundaries, check that your values list begins and ends with 0.
Running on the Local Emulator
from bloqade import start
from bloqade.atom_arrangement import Chain
program = (
Chain(3, lattice_spacing=6.0)
.rydberg.rabi.amplitude.uniform
.piecewise_linear(
durations=[0.4, 1.0, 0.4],
values=[0, 15.7, 15.7, 0]
)
)
# Run 100 shots on the local Python emulator
result = program.bloqade.python().run(100)
report = result.report()
print(report.bitstrings())
print(report.counts())
Each bitstring has one character per atom. 0 means the atom is in the ground state and 1 means it is in the Rydberg excited state.
Inspecting Results
The report object provides several ways to look at results:
# Requires: bloqade
report = result.report()
# List of bitstrings, one per shot
bitstrings = report.bitstrings()
print(f"Total shots: {len(bitstrings)}")
print(f"Example outcome: {bitstrings[0]}")
# Dictionary of outcome -> count
counts = report.counts()
print(counts)
# Probability of each atom being in the Rydberg state
rydberg_density = report.rydberg_state_density()
print(rydberg_density) # array of floats, one per atom
Atom Loss and State Detection
On real hardware, not every shot produces a clean result. Atoms can be lost from the trap during computation due to heating, background gas collisions, or other technical noise. When an atom is lost, it disappears from the fluorescence image, which looks identical to an atom in the Rydberg state (both are absent from the image).
Bloqade’s result format distinguishes between these cases when running on hardware. In the returned data, shots where atom loss is detected are flagged. You can filter these out to get cleaner statistics:
# After running on hardware (or emulator)
report = result.report()
# Get all bitstrings including those with potential atom loss
all_bitstrings = report.bitstrings()
# Get counts, which aggregate over all shots
counts = report.counts()
print(f"Total unique outcomes: {len(counts)}")
print(f"Most common bitstrings: {sorted(counts.items(), key=lambda x: -x[1])[:5]}")
When analyzing results from Aquila, always consider that some fraction of “1” readings may be atom loss rather than genuine Rydberg excitation. For high-precision experiments, run enough shots to build reliable statistics and look for anomalous loss rates on specific sites.
Rabi Oscillations: Single-Atom Dynamics
The simplest experiment on a neutral atom computer is observing Rabi oscillations on a single atom. When you drive an isolated atom at Rabi frequency Omega with zero detuning, the probability of finding it in the Rydberg state oscillates as:
P(|1>) = sin^2(Omega * t / 2)
This is the neutral atom equivalent of a Bloch sphere rotation. A pulse of duration t = pi / Omega produces a full population inversion (a pi pulse). A pulse of duration t = pi / (2 * Omega) produces a balanced superposition (a pi/2 pulse).
Here is a complete Bloqade program that sweeps the pulse duration and prints the expected Rydberg population at each step:
import math
from bloqade.atom_arrangement import Chain
omega = 4.0 # Rabi frequency in MHz (rad/us)
n_steps = 10
max_duration = 2.0 # microseconds
for step in range(1, n_steps + 1):
t_flat = max_duration * step / n_steps
ramp = 0.05 # minimum ramp duration in microseconds
# Build pulse: ramp up, flat, ramp down
program = (
Chain(1, lattice_spacing=15.0) # single atom, large spacing = no interaction
.rydberg.rabi.amplitude.uniform
.piecewise_linear(
durations=[ramp, t_flat, ramp],
values=[0, omega, omega, 0]
)
)
result = program.bloqade.python().run(1000)
report = result.report()
counts = report.counts()
# Count Rydberg excitations
total = sum(counts.values())
rydberg_count = sum(v for k, v in counts.items() if k[0] == 1)
measured_prob = rydberg_count / total
# Theoretical prediction (approximate, ignoring short ramps)
theory_prob = math.sin(omega * t_flat / 2) ** 2
print(
f"t_flat = {t_flat:.2f} us | "
f"Measured P(|1>) = {measured_prob:.3f} | "
f"Theory P(|1>) = {theory_prob:.3f}"
)
You should see the measured probability oscillating between 0 and 1, closely matching the theoretical sine-squared curve. The small discrepancy comes from the finite ramp durations, which contribute additional rotation.
Two-Atom Blockade Demonstration
The Rydberg blockade is what makes neutral atoms useful for quantum computing. This example demonstrates the blockade by comparing two scenarios: atoms inside the blockade radius and atoms far outside it.
Case 1: Atoms Within the Blockade Radius
Place two atoms 6 um apart. With typical parameters, the blockade radius is around 7 to 10 um, so these atoms are well within the blockade regime. When you apply a Rabi drive, the system cannot reach the |11> state (both atoms excited).
from bloqade import start
# Two atoms at 6 um spacing (within blockade radius)
blockaded_program = (
start.add_position([(0.0, 0.0), (6.0, 0.0)])
.rydberg.rabi.amplitude.uniform
.piecewise_linear(
durations=[0.3, 1.0, 0.3],
values=[0, 15.7, 15.7, 0] # pi pulse for a single atom
)
)
result = blockaded_program.bloqade.python().run(1000)
counts = result.report().counts()
print("=== Blockaded regime (6 um spacing) ===")
for bitstring, count in sorted(counts.items(), key=lambda x: -x[1]):
print(f" {bitstring}: {count}")
In the blockaded regime, you see outcomes like |01> and |10> (one atom excited) but rarely or never |11> (both atoms excited). The blockade interaction prevents double excitation.
Case 2: Atoms Outside the Blockade Radius
Now place the same two atoms 30 um apart. At this distance the interaction is negligible (V = C6 / 30^6, which is tiny), and the atoms behave independently.
from bloqade import start
# Two atoms at 30 um spacing (well outside blockade radius)
independent_program = (
start.add_position([(0.0, 0.0), (30.0, 0.0)])
.rydberg.rabi.amplitude.uniform
.piecewise_linear(
durations=[0.3, 1.0, 0.3],
values=[0, 15.7, 15.7, 0] # pi pulse for a single atom
)
)
result = independent_program.bloqade.python().run(1000)
counts = result.report().counts()
print("=== Independent regime (30 um spacing) ===")
for bitstring, count in sorted(counts.items(), key=lambda x: -x[1]):
print(f" {bitstring}: {count}")
In the independent regime, both atoms behave as if they are alone. Since the pulse is a pi pulse (full inversion), you see |11> as the dominant outcome. The |00>, |01>, and |10> outcomes also appear because the pi pulse fidelity is not perfect over the ramp segments, but |11> is clearly the most frequent result.
The contrast between these two cases is a direct measurement of the blockade effect.
The Adiabatic Protocol Explained
A standard technique in neutral atom computing is to slowly sweep the detuning from large negative (favoring ground state) to large positive (favoring Rydberg excitations) while driving with a constant Rabi frequency. This is the adiabatic protocol, and it is the primary tool for solving optimization problems on neutral atom hardware.
How the Sweep Works
The adiabatic protocol has three stages:
-
Initial state (delta << 0): At the start, the detuning is large and negative. This means the ground state |0> is heavily favored for every atom. The system starts in the trivial all-zeros state |000…0>.
-
Sweep phase (delta ramps from negative to positive): The detuning gradually increases. As delta crosses zero and becomes positive, the Rydberg state |1> becomes energetically favorable. Atoms “want” to be excited. However, the Rydberg interaction prevents neighboring atoms (within the blockade radius) from both being excited.
-
Final state (delta >> 0): At the end, the detuning is large and positive. The system settles into a configuration that maximizes the number of excited atoms while respecting the blockade constraint. This configuration is the ground state of the Rydberg Hamiltonian with large positive detuning.
The Adiabatic Condition
For the system to follow the instantaneous ground state throughout the sweep, the sweep must be slow enough. Formally, the adiabatic condition requires:
|d(delta)/dt| << gap^2 / hbar
Here “gap” is the minimum energy gap between the ground state and first excited state encountered during the sweep. If the sweep is too fast, the system can “jump” to excited states and the final measurement gives suboptimal results.
In practice, there is a tradeoff: slower sweeps are more adiabatic and produce better solutions, but they also give more time for decoherence (loss of quantum information). On Aquila, you have at most 4 microseconds, so the sweep rate is a key parameter to optimize.
Adiabatic Sweep Code
from bloqade import start
from bloqade.atom_arrangement import Square
program = (
Square(3, lattice_spacing=5.0)
.rydberg.rabi.amplitude.uniform
.piecewise_linear(
durations=[0.3, 1.5, 0.3],
values=[0, 15.7, 15.7, 0]
)
.detuning.uniform
.piecewise_linear(
durations=[0.3, 1.5, 0.3],
values=[-20, -20, 20, 20] # MHz, sweep negative to positive
)
)
result = program.bloqade.python().run(500)
print(result.report().counts())
This protocol is used to prepare ground states of the Rydberg Hamiltonian, which encodes the solution to Maximum Independent Set problems when the atom graph matches the problem graph.
Maximum Independent Set (MIS): The Native Optimization Problem
The Maximum Independent Set problem is arguably the most natural application of neutral atom hardware. It maps directly onto the physics of the Rydberg blockade without any encoding overhead.
What Is MIS?
Given a graph G = (V, E), an independent set is a subset S of vertices such that no two vertices in S are connected by an edge. The Maximum Independent Set is the largest such subset.
MIS is NP-hard on general graphs, meaning there is no known polynomial-time classical algorithm that solves it for all inputs. It appears in scheduling, resource allocation, network design, and many other domains.
Why Neutral Atoms Solve MIS Naturally
On neutral atom hardware, the atom positions define a unit-disk graph: two atoms are connected by an edge if and only if their distance is less than the blockade radius r_b. The Rydberg blockade physically enforces the independent set constraint: two connected atoms (within blockade range) cannot both be in the excited state |1>.
When you run the adiabatic protocol with large positive final detuning, the system tries to maximize the number of excited atoms (because each |1> lowers the energy by delta). The blockade prevents adjacent atoms from both being excited. The result is the maximum independent set of the unit-disk graph defined by the atom positions.
No encoding, no compilation, no QAOA ansatz design. The physics does the optimization directly.
Concrete Example: 4 Atoms in a Square
Place four atoms at the corners of a square with 6 um sides. Each atom is 6 um from its two neighbors along the edges and about 8.5 um from the atom diagonally opposite. With a blockade radius of ~10 um, all four pairs of neighbors are blockaded, but the diagonal pairs (at 8.5 um) are also within blockade range.
Wait: if all pairs are within blockade, the maximum independent set is only 1 atom. To get a more interesting example, choose the spacing so that edge-neighbors are blockaded but diagonal-opposite atoms are not.
Place atoms at (0,0), (6,0), (0,6), (6,6). The edge distance is 6 um (blockaded) and the diagonal distance is sqrt(72) ~ 8.49 um. If the blockade radius is around 7 to 8 um (controlled by Omega), the diagonal atoms might be partially blockaded. For a cleaner demonstration, use spacing of 7 um:
from bloqade import start
import math
# 4 atoms in a square, 7 um sides
# Edge distance: 7 um (within blockade)
# Diagonal distance: 7*sqrt(2) ~ 9.9 um (near blockade edge)
positions = [(0.0, 0.0), (7.0, 0.0), (0.0, 7.0), (7.0, 7.0)]
program = (
start.add_position(positions)
.rydberg.rabi.amplitude.uniform
.piecewise_linear(
durations=[0.3, 2.0, 0.3],
values=[0, 15.7, 15.7, 0]
)
.detuning.uniform
.piecewise_linear(
durations=[0.3, 2.0, 0.3],
values=[-25, -25, 25, 25] # strong adiabatic sweep
)
)
result = program.bloqade.python().run(1000)
counts = result.report().counts()
print("=== MIS on a 4-atom square ===")
for bitstring, count in sorted(counts.items(), key=lambda x: -x[1])[:10]:
labels = []
for i, b in enumerate(bitstring):
if b == 1:
labels.append(f"atom {i} at {positions[i]}")
excited = ", ".join(labels) if labels else "none"
print(f" {bitstring}: {count:4d} shots | excited: {excited}")
The most common outcomes correspond to the two diagonal MIS solutions: atoms at (0,0) and (7,7) both excited, or atoms at (7,0) and (0,7) both excited. These are the configurations with the maximum number of excited atoms that respect the blockade on edge-connected pairs. The exact distribution depends on the sweep parameters and the blockade strength at each distance.
Local Addressing
So far, every example applies the same laser pulse to all atoms uniformly. For more advanced programs, you may need to drive individual atoms differently. Bloqade supports local addressing, where each atom site can have a different drive amplitude or detuning.
Here is an example where atoms on the left side of a chain receive a pi pulse (full excitation) and atoms on the right side receive a pi/2 pulse (partial excitation). We accomplish this by applying a uniform pulse and then modifying the local amplitude with a site-dependent scale factor:
from bloqade.atom_arrangement import Chain
import math
n_atoms = 6
omega = 10.0 # MHz
t_pi = math.pi / omega # duration for a pi pulse
# Build a chain of 6 atoms with large spacing (independent atoms)
program = (
Chain(n_atoms, lattice_spacing=15.0)
.rydberg.rabi.amplitude.uniform
.piecewise_linear(
durations=[0.05, t_pi, 0.05],
values=[0, omega, omega, 0]
)
.detuning.uniform
.piecewise_linear(
durations=[0.05, t_pi, 0.05],
values=[0, 0, 0, 0] # zero detuning throughout
)
.amplitude.location(3, 0.5) # atom 3: scale amplitude by 0.5
.location(4, 0.5) # atom 4: scale by 0.5
.location(5, 0.5) # atom 5: scale by 0.5
)
result = program.bloqade.python().run(1000)
report = result.report()
# Show per-atom Rydberg population
densities = report.rydberg_state_density()
for i, d in enumerate(densities):
side = "left (pi pulse)" if i < 3 else "right (pi/2 pulse)"
print(f" Atom {i} [{side}]: P(|1>) = {d:.3f}")
Atoms 0, 1, 2 receive the full Omega and undergo a pi rotation (P ~ 1.0). Atoms 3, 4, 5 receive half the amplitude (0.5 * Omega) for the same duration, resulting in a pi/2 rotation (P ~ 0.5). Local addressing enables site-selective control, which is useful for state preparation, boundary conditions in simulations, and defect studies.
Submitting to QuEra Aquila on AWS Braket
Aquila is QuEra’s publicly accessible neutral atom processor, available through AWS Braket. Once your program is working on the local emulator, submitting to real hardware requires only changing the execution target:
from bloqade.atom_arrangement import Chain
from bloqade import start
program = (
Chain(3, lattice_spacing=6.0)
.rydberg.rabi.amplitude.uniform
.piecewise_linear(
durations=[0.3, 1.0, 0.3],
values=[0, 15.7, 15.7, 0]
)
)
# Submit to Aquila (requires AWS account and Braket access)
hardware_job = (
program
.braket.aquila()
.run_async(shots=100)
)
# Fetch results when the job completes
job_result = hardware_job.fetch()
print(job_result.report().counts())
You need an AWS account with Braket enabled and appropriate IAM permissions. Aquila jobs run in a queue and typically return results within a few minutes to an hour depending on current demand. AWS charges per shot.
What Problems Suit Neutral Atoms Best
Neutral atom hardware in analog mode is particularly well-matched to:
- Quantum simulation of condensed matter physics: the Rydberg Hamiltonian is a natural realisation of transverse-field Ising models, useful for studying quantum phase transitions, spin liquids, and correlated electron systems.
- Combinatorial optimisation via Maximum Independent Set: on unit-disk graphs (where edges connect any two atoms within the Rydberg blockade radius), the ground state of the Rydberg Hamiltonian directly encodes the maximum independent set. This is a hard NP problem on general graphs.
- Quantum annealing: the adiabatic sweep protocol is an analog equivalent of quantum annealing, useful for finding low-energy configurations of classical and quantum spin systems.
- Large system sizes: QuEra has demonstrated neutral atom arrays with 256 atoms, and the roadmap targets thousands of atoms with error correction. Gate-based processors at comparable scales are further out.
Problems that require deep gate circuits, precise multi-qubit gate sequences, or algorithms designed around the gate model (Grover, Shor, quantum Fourier transform) are less natural fits for current neutral atom hardware in analog mode.
Common Mistakes
If you are coming from a gate-based background, these are the most frequent pitfalls when working with Bloqade:
1. Lattice Spacing Below the Hardware Minimum
Aquila requires at least 4 um between atoms. Setting a smaller spacing causes a validation error when submitting to hardware. The emulator may still run, but the results will not match hardware behavior.
# BAD: 2 um spacing is below the 4 um minimum
# Chain(5, lattice_spacing=2.0) # will fail on hardware submission
# GOOD: use 6 um or more for blockaded atoms
Chain(5, lattice_spacing=6.0)
2. Forgetting to Ramp Omega Smoothly
The Rabi amplitude must start and end at zero. Step functions are not physically realizable (you cannot instantaneously turn a laser on). Bloqade rejects pulses where the amplitude does not begin and end at 0.
3. Confusing the Blockade and Independent Regimes
Two atoms at 6 um spacing interact strongly via the blockade. Two atoms at 30 um spacing are essentially independent. If your results look like uncorrelated single-atom behavior, check whether your atoms are too far apart to interact. If you see unexpected correlations, check if atoms you thought were independent are actually within blockade range.
4. Expecting Discrete Gate Operations
Bloqade in analog mode does not have H, CNOT, or other gate primitives. You cannot “apply a Hadamard to qubit 3.” Instead, you shape the laser pulse to achieve the desired dynamics. A pi/2 pulse at zero detuning is the closest analog to a Hadamard, but it is not identical (it rotates around a different Bloch sphere axis).
5. Misinterpreting Bitstring Conventions
In Bloqade, 0 = ground state and 1 = Rydberg excited state. This is straightforward, but be careful when comparing to gate-based frameworks where |0> and |1> may have different physical meanings. In particular, the “computational basis” in gate-based quantum computing is defined abstractly, while in Bloqade the states have direct physical significance.
6. Not Accounting for Atom Loss
On real hardware, some shots contain atom loss events where atoms escape the trap. A lost atom looks like a Rydberg excitation (both are absent from the fluorescence image). If you do not account for this, your measured Rydberg populations will be systematically too high. Always check for anomalous excitation rates, especially on edge sites where trapping is less stable.
Where to Go Next
- Full API reference: /reference/bloqade
- Background on the hardware: Neutral Atom Qubit
- QuEra’s Bloqade documentation and example notebooks: bloqade.quera.com
- AWS Braket Aquila getting-started guide: search for “Amazon Braket Aquila” in the AWS docs
Was this tutorial helpful?