Execute Custom Quantum Program: Tutorial
This notebook guides you through executing a custom quantum program using qoolqit.
A QuantumProgram combines a Register and a Drive and serves as the main interface for compilation and execution.
Here we will define a register via the DataGraph class of qoolqit and a custom waveform sublassing the Waveform class.
1. Create the Register
Section titled “1. Create the Register”from qoolqit import DataGraph, Register
#Define a register from a DataGraphgraph=DataGraph.hexagonal(1, 1)reg=Register.from_graph(graph)
#Define a register from positionsreg=Register.from_coordinates(list(graph.coords.values()))Remember it is possible to use prefedined embedders from graphs or define custom ones. Refer to the documentation (external) for this.
2. Create a custom Drive
Section titled “2. Create a custom Drive”Let us define a profile following the function .
import math
from qoolqit.waveforms import Waveform
class SmoothPulse(Waveform): """f(t) = omega_max * sin( (π/2) * sin(π t) )^2, for 0 ≤ t ≤ duration"""
def __init__(self, duration: float , omega_max: float) -> None: super().__init__(duration, omega_max=omega_max)
def function(self, t: float) -> float: return self.omega_max * math.sin(0.5 * math.pi * math.sin(math.pi * t/self.duration)) ** 23. Combine Register and Drive into a Quantum Program
Section titled “3. Combine Register and Drive into a Quantum Program”⚠️ Remember
qoolqit always uses dimensionless units ( documentation (external)):
- Energies are normalized by the maximum drive amplitude $\Omega_\text{max}=1$.
- Time is measured in units of $1/\Omega_\text{max}$ (Rabi oscillations).
- Space is measured in units of $r_B=(C_6/\Omega_\text{max})^{1/6}$.
3.a Details about units of measure
Section titled “3.a Details about units of measure”Why normalization?
Section titled “Why normalization?”All quantum devices have a hardware limit for the maximum drive strength they can apply .
To make programs device-independent, we take this maximum value as our unit of energy and measure every amplitude as a function of it:
For example:
- If the physical maximum is $\Omega_\text{max}=4\pi \,\text{rad/µs}$, then $\bar{\Omega}_\text{max}=1$.
- A drive of $\Omega=2\pi \,\text{rad/µs}$ becomes $\bar{\Omega}=0.5$.
- A drive of $\Omega=\pi \,\text{rad/µs}$ becomes $\bar{\Omega}=0.25$.
This makes all quantum programs dimensionless and therefore comparable across devices.
What happens to other units?
Section titled “What happens to other units?”Normalizing the drive amplitude automatically sets the scale for time and space as well:
Energy units:
So also the detuning is measured in units of .Time units:
At zero detuning, the Rabi frequency is equal to the drive,
so the natural unit of time is
All times are expressed in multiples of the Rabi period at maximum drive .
So, in a time one gets a full Rabi period.Space units:
Interactions follow .
The characteristic length is defined by setting :
Distances are expressed relative to , i.e. the Rydberg blockade radius at maximum drive.
Summary
Section titled “Summary”- Energy → normalized by $\Omega_\text{max}$
- Time → measured in $1/\Omega_\text{max}$ (Rabi oscillations)
- Space → measured in $r_B=(C_6 / \Omega_\text{max})^{1/6}$ (interaction length)
3.b Definition of drive and program
Section titled “3.b Definition of drive and program”from qoolqit import Drive, QuantumProgram, Ramp
T1 = 2*math.pi # Durationamp_1 = SmoothPulse(T1, omega_max=1) # amplitude drivedet_1 = Ramp(T1, -1, 1) # detuning drivedrive_1=Drive(amplitude=amp_1, detuning=det_1) # definine the driveprogram = QuantumProgram(reg, drive_1) # define the quantum programprogram.register.draw() # draw the registerprogram.draw() # draw the drive4. Compile to a device
Section titled “4. Compile to a device”To run the QuantumProgram we have to compile it to switch back to physical units that are compatible with a given device. The now compiled sequence and the compiled register are measured in Pulser units. In the compilation it is also possible to select CompilerProfiles to force specific compilation constraints [ documentation (external)]
from qoolqit import AnalogDevice
device=AnalogDevice() #Call the deviceprogram.compile_to(device) #Compile to a deviceprogram.compiled_sequence.draw()program.compiled_sequence.register.draw()5. Execute the compiled quantum program
Section titled “5. Execute the compiled quantum program”from qoolqit.execution import LocalEmulator
# runs specifies the number of bitstrings sampleemulator = LocalEmulator(runs=1000)results = emulator.run(program)
counter = results[0].final_bitstrings6. Plot bitstring distribution
Section titled “6. Plot bitstring distribution”import matplotlib.pyplot as plt
def plot_distribution(counter, bins=15): counter = dict(counter.most_common(bins)) fig, ax = plt.subplots() ax.set_xlabel("Bitstrings") ax.set_ylabel("Counts") ax.set_xticks(range(len(counter))) ax.set_xticklabels(counter.keys(), rotation=90) ax.bar(range(len(counter.keys())), counter.values(), tick_label=counter.keys()) return fig
fig = plot_distribution(counter, bins=15)