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)) ** 2
3. 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 drive
4. 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 compile quantum program
Section titled “5. Execute the compile quantum program”import numpy as np
from qoolqit import BackendName, ResultType
# Number of samplesruns = 1000evaluation_times = np.linspace(0, 1, num=10).tolist()# Simulate with EMUMPS backend and output bitstring countsemumps_bitstrings = program.run( backend_name=BackendName.EMUMPS, result_type=ResultType.BITSTRINGS, evaluation_times=evaluation_times, runs=runs)
print("Length of the output list:")print(len(emumps_bitstrings))
print("Final state bitstrings:")print(emumps_bitstrings[-1])
Length of the output list: 10 Final state bitstrings: Counter({'000000': 133, '000001': 68, '100000': 67, '000010': 67, '000100': 64, '010100': 63, '001010': 60, '010000': 58, '001000': 56, '000101': 54, '010001': 50, '101000': 48, '100010': 40, '010101': 35, '101010': 33, '001001': 13, '000110': 12, '110000': 11, '000011': 10, '100100': 9, '001011': 7, '011000': 7, '010010': 4, '110100': 4, '101101': 3, '110001': 2, '101100': 2, '001100': 2, '100110': 2, '011010': 2, '100001': 2, '111100': 1, '111000': 1, '001101': 1, '111001': 1, '110011': 1, '010011': 1, '011100': 1, '110010': 1, '000111': 1, '011001': 1, '100101': 1, '100011': 1})
6. Plot bitstring distribution
Section titled “6. Plot bitstring distribution”import matplotlib.pyplot as plt
def plot_distribution(counter): counter = dict(sorted(counter.items(), key=lambda item: item[1], reverse=True)) fig, ax = plt.subplots(1, 1, figsize=(12, 6)) ax.set_xlabel("Bitstrings") ax.set_ylabel("Counts") ax.set_xticks(range(len(counter))) ax.set_xticklabels(counter.keys(), rotation=90) ax.bar(counter.keys(), counter.values(), width=0.5) return fig
fig = plot_distribution(emumps_bitstrings[-1])
Using categorical units to plot a list of strings that are all parsable as floats or dates. If these strings should be plotted as numbers, cast to the appropriate data type before plotting. Using categorical units to plot a list of strings that are all parsable as floats or dates. If these strings should be plotted as numbers, cast to the appropriate data type before plotting.