Skip to content

Results are limited to the current section: Qoolqit

Product news

Programming with QoolQit

This page explains how QoolQit can be used to build quantum programs by employing the dimensionless Hamiltonian framework described in Programming a neutral-atom QPU.

Programming with QoolQit is based on five core steps:

  1. Defining a Register: where qubits live (their geometry).
  2. Defining a Drive: how you control the system over time (waveforms).
  3. Building a QuantumProgram: the pairing of Register + Drive.
  4. Compiling the QuantumProgram: mapping the abstract program onto a specific device model.
  5. Executing the compiled program: running the compiled sequence and collecting results.

A register defines the positions of qubits in your program. In neutral-atom / Rydberg analog models, geometry directly determines how strongly qubits interact.

from qoolqit import Register
# Create a register from coordinates (dimensionless units)
register = Register.from_coordinates([(0, 0), (1, 0), (0.5, 0.866)])
# Or use built-in graph patterns
from qoolqit.graphs import DataGraph
graph = DataGraph.square(m=2, n=2)
register = Register.from_graph(graph) # 2x2 square lattice
register.draw()
2026-10-23T13:53:13.923118 image/svg+xml Matplotlib v3.11.2, https://matplotlib.org/

For problem embedding, QoolQit also provides layout embedders that map graph structure onto qubit positions:

from qoolqit import Register
from qoolqit.embedding import SpringLayoutEmbedder
from qoolqit.graphs import DataGraph
graph = DataGraph.random_er(n=5, p=0.3, seed=3)
embedded_graph = SpringLayoutEmbedder().embed(graph)
register = Register.from_graph(embedded_graph)
register.draw()
2026-10-23T13:53:14.038184 image/svg+xml Matplotlib v3.11.2, https://matplotlib.org/

A drive is the time-dependent control applied to the system, built from one or more waveforms, for example amplitude Ω~\tilde{\Omega} and detuning δ~\tilde{\delta} as functions of time t~\tilde{t}.

Waveform durations are dimensionless: t~\tilde{t} measures duration relative to an interaction timescale. In an interacting many-body system, this gives t~\tilde t a natural physical interpretation in terms of the buildup and propagation of correlations.

Regime Condition Physical meaning
Short time t~≪1\tilde{t} \ll 1 Too short for interactions to strongly reshape the state
Long time t~∼n\tilde{t} \sim n Correlations may have propagated across a distance of order nn lattice spacings
from qoolqit import Drive
from qoolqit.waveforms import ConstantWaveform, InterpolatedWaveform, RampWaveform
# Interpolated waveform: smooth curve through specified values
omega_wf = InterpolatedWaveform(duration=10, values=[0, 1, 0])
# Constant waveform
delta_wf = ConstantWaveform(duration=10, value=-2)
# Ramp waveform
delta_wf = RampWaveform(duration=10, initial_value=-2, final_value=2)
# Combine into a Drive
drive = Drive(amplitude=omega_wf, detuning=delta_wf)
drive.draw()
2026-10-23T13:53:14.154141 image/svg+xml Matplotlib v3.11.2, https://matplotlib.org/

A QuantumProgram pairs a register (layout and interactions) with a drive (time-dependent controls). The initial state is always ∣0⟩⊗N|0\rangle^{\otimes N}, so every program describes how the drive and interactions evolve this fixed initial state.

from qoolqit import QuantumProgram
program = QuantumProgram(register=register, drive=drive)

Compilation translates your abstract program into something a given device (or emulator) can actually run, applying device-specific constraints such as maximum duration, amplitude and detuning bounds, discretization rules, and other hardware limits.

from qoolqit import AnalogDevice
device = AnalogDevice()
program.compile_to(device, profile="max_energy")

Once compiled, run the sequence and retrieve results. The three stages map cleanly onto three concepts:

  • Program: what you want to run (abstract physics).
  • Sequence: what you will run (device-compatible instructions).
  • Result: what you observed (samples, probabilities, observables, etc.).
from qoolqit.execution import LocalEmulator
emulator = LocalEmulator()
job = emulator.run(program)
results = job.results()

The blockade regime occurs when interactions prevent nearby atoms from being simultaneously excited. In the dimensionless framework this is straightforward to set up:

  • Place two qubits at unit distance ⇒J~=1\Rightarrow \tilde{J} = 1.
  • Blockade condition: Ω~≪1\tilde{\Omega} \ll 1 (drive is weak compared to interactions).

We will compare two programs:

Case Ω~\tilde{\Omega} Regime Expected behavior
Blockade 0.30.3 Ω~≪J~\tilde{\Omega} \ll \tilde{J} Double excitation ∣11⟩\vert 11\rangle suppressed
Non-blockade 2.02.0 Ω~≫J~\tilde{\Omega} \gg \tilde{J} Qubits behave more independently; ∣11⟩\vert 11\rangle accessible
import numpy as np
from qoolqit import AnalogDevice, Drive, QuantumProgram, Register
from qoolqit.execution import BitStrings, EmulationConfig, LocalEmulator
from qoolqit.waveforms import ConstantWaveform
# Two qubits at unit distance => maximum interaction J = 1
register = Register.from_coordinates([(0, 0), (1, 0)])
duration = 10
# Blockade regime: Omega << J => double excitation is suppressed
drive_blockade = Drive(
amplitude=ConstantWaveform(duration, 0.3),
)
# Non-blockade regime: Omega >> J => drive dominates, both atoms can be excited
drive_no_blockade = Drive(
amplitude=ConstantWaveform(duration, 2.0),
)
# Build and compile programs
program_blockade = QuantumProgram(register, drive_blockade)
program_no_blockade = QuantumProgram(register, drive_no_blockade)
device = AnalogDevice()
program_blockade.compile_to(device, profile="max_energy")
program_no_blockade.compile_to(device, profile="max_energy")
# Configure emulation: sample bitstrings at 81 evaluation times
eval_times = np.linspace(0.0, 1.0, 81)
bitstrings = BitStrings(evaluation_times=list(eval_times), num_shots=1000)
configuration = EmulationConfig(observables=[bitstrings])
emulator = LocalEmulator(emulation_config=configuration)
job_blockade = emulator.run(program_blockade)
result_blockade = job_blockade.results()
job_no_blockade = emulator.run(program_no_blockade)
result_no_blockade = job_no_blockade.results()
Terminal window
import matplotlib.pyplot as plt
for result, label, color in [
(result_blockade, "Blockade", "navy"),
(result_no_blockade, "No Blockade", "crimson"),
]:
times = result.get_result_times(bitstrings)
occupation = [
result.get_result(bitstrings.tag, time=t)["11"] / bitstrings.num_shots for t in times
]
plt.plot(times, occupation, label=label, color=color)
plt.xlabel(r"$t$", fontsize=20)
plt.ylabel(r"$P_{rr}$", fontsize=20)
plt.xticks(fontsize=16)
plt.yticks(fontsize=16)
plt.legend(fontsize=14)
2026-10-23T13:53:14.563720 image/svg+xml Matplotlib v3.11.2, https://matplotlib.org/
  • Blockade case (Ω~=0.3\tilde{\Omega} = 0.3): the strong interaction heavily penalizes the ∣11⟩|11\rangle state. You should observe a suppressed probability of both qubits being excited simultaneously.
  • Non-blockade case (Ω~=2.0\tilde{\Omega} = 2.0): the drive dominates and interactions are comparatively weak, so the two qubits behave more independently and ∣11⟩|11\rangle becomes much more accessible.