Skip to content

Adiabatic Drive

Adiabatic Drive Shaper

AdiabaticDriveShaper is a class for implementing a standard adiabatic drive shaping method. It creates an InterpolatedWaveform drive for solving a QUBOInstance using adiabatic quantum evolution, There are no parameters to be customized.

How it works:

  • Creates a normalized weights list for the later application of Detuning Map Modulator (DMM).
  • Computes the maximum amplitude value.
  • Computes the global detuning values (initial and final).
  • Creates a drive with the values of amplitude, detuning and duration (maximal duration on analog device in ns).

Fields

Field Type Description
drive Drive A Qoolqit-compatible drive object generated by generate().

Methods

generate(register: Register, instance: QUBOInstance) -> tuple[Drive, QUBOSolution] Generates a shaped adiabatic drive for the given QUBO problem and register layout.

Argument Type Description
register Register The spatial layout of qubits/atoms in the physical system.
instance QUBOInstance The QUBO matrix of the problem.

Returns

Type Description
tuple[Drive, QUBOSolution] Returns the generated Drive object and a placeholder QUBOSolution (with all fields set to None)
Notes

The method sets the drive parameters (Amplitude, Detuning and weighted detunings used for DMM).

Definition of Amplitude and Detuning:

For the Adiabatic Drive, the values of the amplitude \(\Omega\) (Rabi frequency) and global detuning \(\delta\) are defined in terms of the QUBOInstance, as follows:

  • Amplitude: has a sine-like shape, starting from \(0\) and ending in \(0\), with a maximum value being the maximum value among the off-diagonal terms of the QUBO matrix:
\[\Omega_{max} = mean(Q_{ij, i != j})\]

If \(\Omega_{max}\) reaches a value above the maximum amplitude allowed by the device, it uses the max_amp value taken from the device specs as \(\Omega_{max}\). It it is below the min_avg_amp value from the specs, we rescale to match such value.

  • Detuning: starts from a negative value \(\delta_0\) that is the minimum value among the diagonal terms of the QUBO matrix (since diagonal terms are either negative or \(0\)), reaches \(0\) and ends in a positive final value \(\delta_f\), following a linear behavior:
\[\delta_0 = min(Q_{diag})\]
\[\delta_f = -\delta_0\]

Application of DMM

Along with the drive created with the amplitude and detuning values, the detuning of individual qubits (defined as list of values between 0 and 1) is applied using a channel called Detuning Map Modulator or DMM. Proportionally to the weights of each qubit, a drive of zero amplitude and negative detuning is applied. Here, the values of the detuning are normalized in terms of the maximum absolute value of the diagonal terms and subtracted from 1 (and normalized by device specs). For each qubit \(i\) the value is:

\[DMM = 1 - (w_i/w_{max}) \]

This way, the qubits with lower weights receive higher detuning values, whilst the ones with higher weights receive lower values. To activate DMM, specify in SolverConfig the dmm to True.

Example

import torch

from qubosolver import QUBOInstance
from qubosolver.config import SolverConfig, DriveShapingConfig
from qubosolver.solver import QuboSolver
from qubosolver.qubo_types import DriveType


Q = torch.tensor([
    [-63.9423,   0.0000,  73.6471,   0.0000,   0.0000,   0.0000,   0.0000,   0.0000,   0.0000,  55.2853],
    [  0.0000, -44.1916,   0.0000,   0.0000,   0.0000,   0.0000,  58.9307,   0.0000,   0.0000,   0.0000],
    [ 73.6471,   0.0000, -89.8861,  51.0382,   0.0000,   0.0000,   0.0000,   0.0000,   0.0000,   0.0000],
    [  0.0000,   0.0000,  51.0382, -63.7618,   0.0000,   0.0000,  33.9093,   0.0000,   0.0000,   0.0000],
    [  0.0000,   0.0000,   0.0000,   0.0000, -94.4426,  18.7963,   0.0000,   0.0000,  14.3994,   0.0000],
    [  0.0000,   0.0000,   0.0000,   0.0000,  18.7963, -60.7545,   0.0000,   0.0000,   0.0000,  96.9903],
    [  0.0000,  58.9307,   0.0000,  33.9093,   0.0000,   0.0000, -71.3241,   0.0000,   0.0000,   0.0000],
    [  0.0000,   0.0000,   0.0000,   0.0000,   0.0000,   0.0000,   0.0000, -38.2094,  59.3175,   0.0000],
    [  0.0000,   0.0000,   0.0000,   0.0000,  14.3994,   0.0000,   0.0000,  59.3175, -94.5790,  18.0653],
    [ 55.2853,   0.0000,   0.0000,   0.0000,   0.0000,  96.9903,   0.0000,   0.0000,  18.0653, -97.3174]
])

instance = QUBOInstance(Q)

default_config = SolverConfig(
    use_quantum = True, drive_shaping=DriveShapingConfig(drive_shaping_method=DriveType.ADIABATIC, dmm=True)
)
solver = QuboSolver(instance, default_config)

solution = solver.solve()
print(solution)
QUBOSolution(bitstrings=tensor([[0, 1, 0, 1, 1, 0, 0, 0, 0, 0], [0, 0, 1, 0, 0, 0, 0, 0, 1, 0], [0, 0, 0, 1, 1, 0, 0, 0, 0, 0], [0, 0, 1, 0, 0, 1, 0, 0, 0, 0], [0, 1, 1, 0, 0, 0, 0, 0, 0, 0], [0, 0, 1, 0, 0, 0, 0, 1, 0, 0], [0, 0, 0, 1, 0, 1, 0, 0, 0, 0], [1, 1, 0, 0, 0, 0, 0, 0, 0, 0], [0, 1, 0, 1, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 1], [0, 1, 1, 1, 0, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 1, 0, 0, 0, 0], [0, 0, 1, 1, 0, 0, 0, 0, 0, 0], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]], dtype=torch.int32), costs=tensor([-202.3960, -184.4651, -158.2044, -150.6406, -134.0777, -128.0955, -124.5163, -108.1339, -107.9534, -97.3174, -95.7631, -63.9423, -60.7545, -51.5715, 242.3500]), counts=tensor([ 1, 1, 3, 1, 8, 2, 4, 3, 11, 1, 27, 2, 1, 12, 23]), probabilities=tensor([0.0100, 0.0100, 0.0300, 0.0100, 0.0800, 0.0200, 0.0400, 0.0300, 0.1100, 0.0100, 0.2700, 0.0200, 0.0100, 0.1200, 0.2300]), solution_status=)
This will return a QUBOSolution instance, which comprehends the solution bitstrings, the counts of each bitstring, their probabilities and costs.

Obs.: The ADIABATIC method is the default one, it's explicit in drive_shaping_method for ilustration purposes.