Rydberg Layout addon

The Rydberg layout addon creates atomic layouts and analog schedules for neutral atom quantum computers.

Device Specification

To work with quantum hardware and simulators, we need to define the specifications of our device. These specifications include information about atom placement, the maximum number of atoms, the interaction coefficient, and the allowed detuning range. In this example, we will define an AtomicDeviceSpecs.

import math
from parityos_addons.rydberg_layout.base.device import AtomicDeviceSpecs, CircularDevice

device_specs = AtomicDeviceSpecs(
    device_geometry=CircularDevice(50),
    min_atom_distance=4,
    max_atom_count=100,
    interaction_coefficient=5.0 * 1e6,
    detuning_range=(-60, 60),
)

where device_geometry specifies the geometry of the device (circular, radius = maximum atomic distance from center, in µm), min_atom_distance is the minimum allowed separation between atoms (in µm), max_atom_count is the maximum number of atoms, and interaction_coefficient corresponds to \(C_6/\hbar\) (in rad/µs x µm^6), with detuning_range representing the allowed range of detuning (in rad/µs).

Atoms

Let’s arrange atoms in the triangle gadget proposed in [Lanthaler23]. To do this, we need to define the Qubit instances, their coordinates, and their Detunings.

from parityos import Qubit
from parityos_addons.rydberg_layout.base.atom import Atom
from parityos_addons.rydberg_layout.base.atom_coordinate import AtomCoordinate
from parityos_addons.rydberg_layout.base.detuning import Detuning

qubit_count = 6
qubits = [Qubit(index) for index in range(qubit_count)]
rydberg_distance = 5.0
list_of_coordinates = [
    AtomCoordinate(0.0, 0.0, 0.0),
    AtomCoordinate(2 * rydberg_distance, 0.0, 0.0),
    AtomCoordinate(rydberg_distance, math.sqrt(3) * rydberg_distance, 0.0),
    AtomCoordinate(0.5 * rydberg_distance, math.sqrt(3) / 2 * rydberg_distance, 0.0),
    AtomCoordinate(rydberg_distance, 0.0, 0.0),
    AtomCoordinate(1.5 * rydberg_distance, math.sqrt(3) / 2 * rydberg_distance, 0.0),
]

alpha_detuning = 15 # rad/µs
list_of_detunings = [
    Detuning(J=alpha_detuning, alpha=alpha_detuning),
    Detuning(J=alpha_detuning, alpha=alpha_detuning),
    Detuning(J=alpha_detuning, alpha=alpha_detuning),
    Detuning(J=0.0, alpha=2 * alpha_detuning),
    Detuning(J=0.0, alpha=2 * alpha_detuning),
    Detuning(J=0.0, alpha=2 * alpha_detuning),
]


atoms = [
    Atom(coordinates, qubit, detunings)
    for coordinates, qubit, detunings in zip(
        list_of_coordinates, qubits, list_of_detunings
    )
]

Here, we have chosen J values for the detunings such that all the atoms will have the same Detuning.value, which will allow us to run simulations later.

RydbergAtoms

For creating a RydbergAtoms instance one needs to supply the list of Atomss and the given device specification. An exception is raised if the Atomss do not fit to the device, e.g. because minimal distances are violated.

from parityos_addons.rydberg_layout.base.rydberg_atoms import RydbergAtoms

rydberg_atoms = RydbergAtoms(atoms, device_specs)

Analog Computation with Rydberg Atoms

The Ising Hamiltonian for Rydberg Atoms [Henriet2020] can be written as follows:

\[H(t) = \frac{\hbar}{2} \Omega(t) \sum_j X_j - \hbar \delta(t) \sum_j n_j + \sum_{i \ne j} \frac{C_6}{r_{ij}^6} n_i n_j,\]

where \(n_j = (1 + Z_j) / 2\), \(X_j\) and \(Z_j\) are the Pauli matrices of the spin \(j\) and \(C_6\) is the van-der-Waals interaction coefficient. \(\Omega(t)\) denotes the time-dependent Rabi-frequency and \(\delta(t)\) denotes the time-dependent detuning. Analog schedules for neutral atom can be defined by providing the corresponding \(\Omega(t)\), \(\delta(t)\) functions. Here is an example how one can create a RydbergSchedule by providing those inputs:

import sympy
from parityos_addons.rydberg_layout.schedule.rydberg_schedule import RydbergSchedule


# Parameters in rad/µs and ns
Omega_max = 9
final_detuning = 1
initial_detuning = -0.5

t_rise = 1000
t_sweep = 3000
t_fall = 1000
t_total = t_rise + t_sweep + t_fall

t = sympy.Symbol("t")

# the times when the pulse is changing
t_1 = t_rise
t_2 = t_rise + t_sweep

rabi_coefficient = sympy.Piecewise(
    ((t / t_1) * Omega_max, (t >= 0) & (t < t_1)),
    (Omega_max, (t >= t_1) & (t < t_2)),
    ((1 - (t - t_2) / (t_total - t_2)) * Omega_max, (t >= t_2) & (t <= t_total)),
)

detuning_coefficient = sympy.Piecewise(
    (initial_detuning, (t >= 0) & (t < t_1)),
    (
        (final_detuning * (t - t_1) + initial_detuning * (t_2 - t)) / (t_2 - t_1),
        (t >= t_1) & (t < t_2),
    ),
    (final_detuning, (t >= t_2) & (t <= t_total)),
)

rydberg_schedule = RydbergSchedule(
    rydberg_atoms=rydberg_atoms,
    detuning_coefficient=detuning_coefficient,
    rabi_coefficient=rabi_coefficient,
    time_parameter=t,
    duration=t_total,
)

Visualization tools for atoms and schedules

We can use visualization tools to check if the atoms are in the expected positions:

from parityos_addons.rydberg_layout.visualization.plot_rydberg_atoms import (
    plot_rydberg_atoms,
)

plot_rydberg_atoms(rydberg_atoms, show=True)

Also, one can visualize the RydbergSchedules:

from parityos_addons.rydberg_layout.visualization.plot_rydberg_schedule import (
    plot_rydberg_schedule,
)

plot_rydberg_schedule(rydberg_schedule, show=True)

Pulser Exporter

For simulations, we can use pulser_exporter. For that, one needs to add [pulser] during the installation:

pip install parityos["rydberg_layout", "pulser"]

Here is how we can run the simulation for the example created above:

import pulser
from parityos_addons.interfaces.pulser_exporter import (
    rydberg_schedule_to_pulse, atoms_to_pulser_register,
)

register = atoms_to_pulser_register(atoms=atoms)
pulse = rydberg_schedule_to_pulse(schedule=rydberg_schedule)

sequence = pulser.Sequence(register, pulser.devices.MockDevice)
sequence.declare_channel("rydberg_global", "rydberg_global")
sequence.add(pulse, "rydberg_global")

backend = pulser.backends.QutipBackend(sequence)
result = backend.run()
counts = result.sample_final_state(1000)
solution = max(counts, key=counts.get)

print("counts: ", counts)
print("solution:", solution)

For this simulation, it is expected that in the solution only the atoms at the corners of the triangle gadget will be in the Rydberg state. This can be checked by visualizing the obtained atomic state:

from parityos_addons.rydberg_layout.utils.rydberg_atom_state import RydbergAtomState
from parityos_addons.rydberg_layout.visualization.plot_rydberg_state import plot_rydberg_state

atom_state = RydbergAtomState.from_bit_string(atoms=atoms, bit_string=solution)
plot_rydberg_state(atom_state, show=True)

References

[Lanthaler23]
  1. Lanthaler et al, Rydberg-blockade-based parity quantum optimization, Phys. Rev. Lett. 130, 220601 (2023).

[Henriet2020]
  1. Henriet et al, “Quantum computing with neutral atoms”, Quantum 4, 327 (2020).

[pulser]
  1. Silvério et al, “Pulser: An open-source package for the design of pulse sequences in programmable neutral-atom arrays”, Quantum 6, 629 (2022).