Skip to content

Results are limited to the current section : Qoolqit

Quantum Program

In this page, you will learn how to build a quantum program, from its building blocks:

  • Create a Register from a set of coordinates,
  • Define Waveforms selecting amplitude and detuning,
  • Build a Drive from waveform components,
  • Instantiate a QuantumProgram from a Register and a Drive,
  • Check whether a program has already been compiled.

The QuantumProgram defines the protocol used to solve your problem within the adimensional framework of the Rydberg Analog model.

In practice, this involves specifying both the interaction and the driving Hamiltonian. In QoolQit, these are set up by creating a Register, which determines the positions of the qubits, and a Drive object, which describes how laser fields control the qubits over time.

To run the program on real quantum hardware, the abstract QuantumProgram must first be compiled into a form compatible with a specific QPU. This compilation process will be covered later in the Compilation page.

A Register defines the qubit resources to be used by a quantum program.

from qoolqit import Register
qubits = {
0: (-0.5, -0.5),
1: (-0.5, 0.5),
2: (0.5, -0.5),
3: (0.5, 0.5),
}
register = Register(qubits)
Register(n_qubits = 4)

It can be instantiated from a list of coordinates.

coords = [(-0.5, -0.5), (-0.5, 0.5), (0.5, -0.5), (0.5, 0.5)]
register = Register.from_coordinates(coords)
register.draw()
2026-04-09T13:49:24.991843 image/svg+xml Matplotlib v3.10.8, https://matplotlib.org/

The distances between all qubits can be directly accessed.

register.distances()
{(0, 1): 1.0, (1, 2): 1.4142135623730951, (0, 3): 1.4142135623730951, (2, 3): 1.0, (0, 2): 1.0, (1, 3): 1.0}

The minimum distance can be directly accessed.

register.min_distance()
1.0

The interaction coefficients 1/rij61/r_{ij}^6 can be directly accessed.

register.interactions()
{(0, 1): 1.0, (1, 2): 0.12499999999999994, (0, 3): 0.12499999999999994, (2, 3): 1.0, (0, 2): 1.0, (1, 3): 1.0}

An essential part of writing programs in the Rydberg analog model is to write the time-dependent functions representing the amplitude and detuning terms in the drive Hamiltonian. For that, QoolQit implements a set of waveforms that can be used directly and/or composed together.

A full list of the available waveforms can be found in the API reference.

from qoolqit import Constant, Ramp, Delay
# An empty waveform
wf1 = Delay(1.0)
# A waveform with a constant value
wf2 = Constant(1.0, 2.0)
# A waveform that ramps linearly between two values
wf3 = Ramp(1.0, -1.0, 1.0)
0.00 ≤ t ≤ 1.00: Delay(t)
0.00 ≤ t ≤ 1.00: Constant(t, 2.00)
0.00 ≤ t ≤ 1.00: Ramp(t, -1.00, 1.00)

As shown above, printing a waveform shows the duration interval over which it applies followed by the description of the waveform.

The first argument is always the duration of the waveform, and the remaining arguments depend on the information required by each waveform. The resulting object is a callable that can be evaluated at any time tt.

wf1(t = 0.0)
wf2(t = 0.5)
wf3(t = 1.0)
wf1(t = 0.0) = 0.0
wf2(t = 0.5) = 2.0
wf3(t = 1.0) = 1.0

Each waveform also supports evaluation at multiple time steps by calling it on an array of times.

import numpy as np
t_array = np.linspace(0.0, 2.0, 9)
wf3(t_array)
t = [0. 0.25 0.5 0.75 1. 1.25 1.5 1.75 2. ]
wf(t) = [-1. -0.5 0. 0.5 1. 0. 0. 0. 0. ]

In the waveform above, we defined it with a duration of 1.01.0, and then evaluated it over nine points from t=0.0t = 0.0 to t=2.0t=2.0. As you can see, all points after t=1.0t = 1.0 evaluated to 0.00.0. By default, any waveform evaluated at a time tt that falls outside the specified duration gives 0.00.0.

Waveforms can be quickly drawn with the draw() method.

wf3.draw()
2026-04-09T13:49:25.074694 image/svg+xml Matplotlib v3.10.8, https://matplotlib.org/

Special waveform to easily fit a set given values with a smooth function. For the full set of available options please refer to the API reference.

from qoolqit import Interpolated
values = np.sin(np.linspace(0,2*np.pi, 10))
wf_interpolated = Interpolated(100, values)
wf_interpolated.draw()
2026-04-09T13:49:25.149604 image/svg+xml Matplotlib v3.10.8, https://matplotlib.org/

The most straightforward way to arbitrarily compose waveforms is to use the >> operator. This will create a CompositeWaveform representing the waveforms in the order provided.

wf_comp = wf1 >> wf2 >> wf3
Composite waveform:
| 0.00 ≤ t < 1.00: Delay(t)
| 1.00 ≤ t < 2.00: Constant(t, 2.00)
| 2.00 ≤ t ≤ 3.00: Ramp(t, -1.00, 1.00)

The code above is equivalent to calling CompositeWaveform(wf1, wf2, wf3). As shown, printing the composite waveform will automatically show the individual waveforms in the composition and the times at which they are active. These are automatically calculated from the individual waveforms. A CompositeWaveform is by itself a subclass of Waveform, and thus the previous logic on calling it at arbitrary time values also applies.

A few convenient properties are directly available in a composite waveform:

# Total duration
wf_comp.duration
# List of durations of the individual waveforms
wf_comp.durations
# List of times where each individual waveform starts / ends
wf_comp.times
Total duration : 3.0
List of durations : [1.0, 1.0, 1.0]
List of times : [0.0, 1.0, 2.0, 3.0]

A custom waveform can directly be a CompositeWaveform. That is the case with the PiecewiseLinear waveform, which takes a list of durations (of size NN) and a list of values (of size N+1N+1) and creates a linear interpolation between all values using individual waveforms of type Ramp.

from qoolqit import PiecewiseLinear
durations = [1.0, 1.0, 2.0]
values = [0.0, 1.0, 0.5, 0.5]
wf_pwl = PiecewiseLinear(durations, values)
wf_pwl.draw()
2026-04-09T13:49:25.247256 image/svg+xml Matplotlib v3.10.8, https://matplotlib.org/

Built-in waveforms cover the most common shapes, but any differentiable (or piecewise-smooth) profile can be realized by subclassing Waveform. For a full walkthrough — including concrete examples and how to use custom waveforms inside a Drive — see Defining custom waveforms.

The Drive is a collection of amplitude and detuning waveforms, plus an optional phase, fully specifying the drive Hamiltonian described in the QoolQit model page. Here is an example on how to create a drive:

from qoolqit import Constant, Ramp
from qoolqit import Drive
# Defining two waveforms
amplitude = Constant(duration=5.0, value=1.0) >> Ramp(1.0, 0.0, 0.5)
detuning = Ramp(2.0, -1.0, 1.0) >> Constant(1.0, 1.0)
# Defining the drive
drive = Drive(
amplitude = amplitude,
detuning = detuning
)
# Expanding the drive through composition
drive = drive >> drive
Amplitude:
| 0.00 ≤ t < 5.00: Constant(t, 1.00)
| 5.00 ≤ t < 6.00: Ramp(t, 0.00, 0.50)
| 6.00 ≤ t < 11.00: Constant(t, 1.00)
| 11.00 ≤ t ≤ 12.00: Ramp(t, 0.00, 0.50)
Detuning:
| 0.00 ≤ t < 2.00: Ramp(t, -1.00, 1.00)
| 2.00 ≤ t < 3.00: Constant(t, 1.00)
| 3.00 ≤ t < 6.00: Delay(t)
| 6.00 ≤ t < 8.00: Ramp(t, -1.00, 1.00)
| 8.00 ≤ t < 9.00: Constant(t, 1.00)
| 9.00 ≤ t ≤ 12.00: Delay(t)

While defining an amplitude is required, if not provided, the detuning and the phase value will be assumed to be zero. Finally, after creation, drives can be conveniently plotted and inspected as:

drive.draw()
2026-04-09T13:49:25.392679 image/svg+xml Matplotlib v3.10.8, https://matplotlib.org/

To understand the role of time and the duration of a drive in the Rydberg Analog model, please have a look at the Time regimes page. Alternatively, duration can also be overwritten at compilation time, as relative to the maximum duration allowed by a specific hardware device. Such feature, is useful, for example, when working on adiabatic protocols. For more details, please have a look at the Device and compilation page of the documentation, specifically at the Special compilation flags section.

Finally, at the compilation stage, the duration set by the user might be higher than what the selected QPU device allows. Compilation will thus trigger an informative error about the hardware limitations and how to comply with those.

A QuantumProgram combines a Register and a Drive and serves as the main interface for compilation and execution.

from qoolqit import PiecewiseLinear
from qoolqit import Register, Drive, QuantumProgram
# Defining the Drive
wf0 = PiecewiseLinear([1.0, 2.0, 1.0], [0.0, 0.5, 0.5, 0.0])
wf1 = PiecewiseLinear([1.0, 2.0, 1.0], [-1.0, -1.0, 1.0, 1.0])
drive = Drive(amplitude = wf0, detuning = wf1)
# Defining the Register
coords = [(0.0, 0.0), (0.0, 1.0), (1.0, 0.0), (1.0, 1.0)]
register = Register.from_coordinates(coords)
# Creating the Program
program = QuantumProgram(register, drive)
Quantum Program:
| Register(n_qubits = 4)
| Drive(duration = 4.000)
| Compiled: False

At this point, the program has not been compiled to any device. As shown above, this is conveniently displayed when printing the program. It can also be checked through the is_compiled property.

program.is_compiled
False

Next, we have to choose a device and compile the program for it. In QoolQit, compilation refers to converting the dimensionless time, energy, and distance values used in the Rydberg analog model into concrete values. More detailed information on this conversion is provided in the Rydberg analog model page and in Compilation