Skip to content
Pasqal Documentation

Understanding unit conversions

Consider a system of two qubits in state 00|00\rangle, placed in two sites at a distance rr at t=0t=0. For simplicity, we set the program drive as a constant amplitude waveform of value Ω\Omega for t0t\geq0. Following the Rydberg model page (external), the two-qubit system evolves with the Hamiltonian

H01=Ω2(σ^0x+σ^1x)+1r6n^0n^1 H_{01}=\frac{\Omega}{2}(\hat{\sigma}^x_0+\hat{\sigma}^x_1)+\frac{1}{r^6}\hat{n}_0\hat{n}_1

The interaction term imposes an energy of 1/r61/r^6 on state 11|11\rangle, and thus this state is only accessible if Ω>1/r6\Omega>1/r^6. As such, the radius

rb=Ω16 r_b=\Omega^{-\frac{1}{6}}

is called the Rydberg blockade radius, such that if two qubits are at a distance r<rbr < r_b, the state 11|11\rangle is blocked, while if they are at a distance r>rbr > r_b, the state 11|11\rangle is accessible.

For simplicity, we we now set Ω=1\Omega = 1, and thus rb=1r_b = 1. If you solve the system analytically you will find that in the blockade regime the initial population of 00|00\rangle transitions to 1201+10\frac{1}{\sqrt{2}}|01\rangle+|10\rangle at t=π/2t=\pi/\sqrt{2}, while in the non-blockade regime the transition 0011|00\rangle \rightarrow |11\rangle is complete at t=πt=\pi.

import numpy as np
from qoolqit import Constant, Drive
omega = 1.0
# Defining the Drive for the close / far system
duration_close = np.pi * np.sqrt(2.0)
duration_far = np.pi
drive_close = Drive(amplitude=Constant(duration_close, omega))
drive_far = Drive(amplitude=Constant(duration_far, omega))
print(f"duration_close={duration_close:.6f}, duration_far={duration_far:.6f}")
from qoolqit import QuantumProgram, Register
r_close = 0.7
r_far = 1.5
reg_close = Register.from_coordinates([(0.0, 0.0), (r_close, 0.0)])
reg_far = Register.from_coordinates([(0.0, 0.0), (r_far, 0.0)])
program_close = QuantumProgram(reg_close, drive_close)
program_far = QuantumProgram(reg_far, drive_far)
print("Registers and programs created.")
n_points = 100
evaluation_times = np.linspace(0.0, 1.0, n_points).tolist()
print("Evaluation times set.")

First, let's run the programs on a mock device and see what we get. We instantiate the device, compile both programs, and call the run() method. By default, this uses ResultType.STATEVECTOR, which is what we want here.

from qoolqit import MockDevice
device = MockDevice()
program_close.compile_to(device)
program_far.compile_to(device)
states_close = program_close.run(evaluation_times=evaluation_times)
states_far = program_far.run(evaluation_times=evaluation_times)
print("Simulations complete on MockDevice.")
import matplotlib.pyplot as plt
def get_figure(states_close: np.ndarray, states_far: np.ndarray) -> plt.Figure:
# Compute populations
pop_close = abs(states_close.T) ** 2
pop_far = abs(states_far.T) ** 2
fig, ax = plt.subplots(1, 2, figsize=(8, 4), dpi=200)
t_vals = np.linspace(0, duration_close, n_points).tolist()
ax[0].grid(True, linestyle="--", linewidth=0.7)
ax[0].plot(t_vals, pop_close[0], label="00")
ax[0].plot(t_vals, pop_close[1], label="01")
ax[0].plot(t_vals, pop_close[2], label="10", linestyle="dashed")
ax[0].plot(t_vals, pop_close[3], label="11")
ax[0].set_ylabel("Population")
ax[0].set_xlabel("Time t")
ax[0].set_title("Blockade regime")
ax[0].legend()
t_vals = np.linspace(0, duration_far, n_points).tolist()
ax[1].grid(True, linestyle="--", linewidth=0.7)
ax[1].plot(t_vals, pop_far[0], label="00")
ax[1].plot(t_vals, pop_far[1], label="01")
ax[1].plot(t_vals, pop_far[2], label="10", linestyle="dashed")
ax[1].plot(t_vals, pop_far[3], label="11")
ax[1].set_xlabel("Time t")
ax[1].set_title("Non-blockade regime")
ax[1].legend()
return fig
fig = get_figure(states_close, states_far)
plt.show()
program_close.draw(compiled = True)
fig = program_close.draw(compiled = True, return_fig = True)
print("Converter:", device.converter)
device.set_energy_unit(10.0)
print("Updated converter:", device.converter)
program_close.compile_to(device)
program_far.compile_to(device)
states_close = program_close.run(evaluation_times=evaluation_times)
states_far = program_far.run(evaluation_times=evaluation_times)
fig = get_figure(states_close, states_far)
plt.show()

Now, let's replicate this experiment for a more realistic device. Note that the workflow we will show now is for demonstration purposes, and is not the recommended one for the average QoolQit user.

We can use the AnalogDevice, which unlike the MockDevice, has limitations on certain values that can be set. This is immediately clear if we try to compile the program as it is to the default settings of the AnalogDevice:

from qoolqit import AnalogDevice
device = AnalogDevice()
try:
program_close.compile_to(device)
except Exception as error:
print("Compilation (close) failed:", error)
program_far.compile_to(device)
print("Far program compiled.")
print("Device specs:", device.specs)
print("Converter (before):", device.converter)
device.set_distance_unit(7.5)
print("Converter (after):", device.converter)
print("Device specs (updated):", device.specs)
program_close.compile_to(device)
program_far.compile_to(device)
states_close = program_close.run(evaluation_times=evaluation_times)
states_far = program_far.run(evaluation_times=evaluation_times)
fig = get_figure(states_close, states_far)
plt.show()
program_close.draw(compiled = True)
fig = program_close.draw(compiled = True, return_fig = True)

At the start of the previous section it was noted that the workflow of manually changing the unit converter is not necessarily the recommended one, and should be reserved for more advanced users. At the same time, at the end of the section we saw that in this case the compilation was not ideal, because the program was slightly affected by waveform modulation errors.

To address both of these issues, we can use compiler profiles. These are directives for the compiler to follow while trying to compile the sequence, which can be designed with various specific purposes in mind. Going in detail on compiler profiles in QoolQit is not the purpose of this tutorial, but here we can exemplify the usage of one:

from qoolqit import AnalogDevice, CompilerProfile
device = AnalogDevice()
program_close.compile_to(device, profile=CompilerProfile.MAX_DURATION)
program_far.compile_to(device, profile=CompilerProfile.MAX_DURATION)
states_close = program_close.run(evaluation_times=evaluation_times)
states_far = program_far.run(evaluation_times=evaluation_times)
fig = get_figure(states_close, states_far)
plt.show()
program_close.draw(compiled = True)
fig = program_close.draw(compiled = True, return_fig = True)