Quantum Program
In this page, you will learn how to build a quantum program, from its building blocks:
- Create a
Registerfrom a set of coordinates, - Define
Waveformsselecting amplitude and detuning, - Build a
Drivefrom waveform components, - Instantiate a
QuantumProgramfrom aRegisterand aDrive, - 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.
Registers
Section titled “Registers”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()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.0The interaction coefficients 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}Waveforms
Section titled “Waveforms”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.
Base waveforms
Section titled “Base waveforms”A full list of the available waveforms can be found in the API reference.
from qoolqit import Constant, Ramp, Delay
# An empty waveformwf1 = Delay(1.0)
# A waveform with a constant valuewf2 = Constant(1.0, 2.0)
# A waveform that ramps linearly between two valueswf3 = 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 .
wf1(t = 0.0)wf2(t = 0.5)wf3(t = 1.0)wf1(t = 0.0) = 0.0wf2(t = 0.5) = 2.0wf3(t = 1.0) = 1.0Each 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 , and then evaluated it over nine points from to . As you can see, all points after evaluated to . By default, any waveform evaluated at a time that falls outside the specified duration gives .
Waveforms can be quickly drawn with the draw() method.
wf3.draw()Interpolated waveform
Section titled “Interpolated waveform”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()Composite waveforms
Section titled “Composite waveforms”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 >> wf3Composite 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 durationwf_comp.duration
# List of durations of the individual waveformswf_comp.durations
# List of times where each individual waveform starts / endswf_comp.timesTotal duration : 3.0List 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 ) and a list of values (of size ) 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()Custom waveforms
Section titled “Custom waveforms”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.
Drives
Section titled “Drives”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, Rampfrom qoolqit import Drive
# Defining two waveformsamplitude = 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 drivedrive = Drive( amplitude = amplitude, detuning = detuning)
# Expanding the drive through compositiondrive = drive >> driveAmplitude:| 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()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.
Defining a quantum program
Section titled “Defining a quantum program”A QuantumProgram combines a Register and a Drive and serves as the main interface for compilation and execution.
from qoolqit import PiecewiseLinearfrom qoolqit import Register, Drive, QuantumProgram
# Defining the Drivewf0 = 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 Registercoords = [(0.0, 0.0), (0.0, 1.0), (1.0, 0.0), (1.0, 1.0)]register = Register.from_coordinates(coords)
# Creating the Programprogram = QuantumProgram(register, drive)Quantum Program:| Register(n_qubits = 4)| Drive(duration = 4.000)| Compiled: FalseAt 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_compiledFalseNext, 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
