Skip to content

Adiabatic Pulse

Adiabatic Pulse Shaper

AdiabaticPulseShaper is a class for implementing a standard adiabatic pulse shaping method. It creates an InterpolatedWaveform pulse 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 DMM.
  • Computes the maximum amplitude value.
  • Computes the global detuning values (initial and final).
  • Creates a pulse with the values of amplitude, detuning and duration (4000 ns).

Fields

Field Type Description
pulse Pulse A Pulser-compatible pulse object generated by generate().

Methods

generate(register: Register, instance: QUBOInstance) -> tuple[Pulse, QUBOSolution] Generates a shaped adiabatic pulse 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[Pulse, QUBOSolution] Returns the generated Pulse object and a placeholder QUBOSolution (with all fields set to None)
Notes

The method sets:

  • pulse.norm_weights: A normalized weight per node, used for DMM.

  • pulse.duration: Total evolution time (fixed at T = 4000 ns by default).

Definition of Amplitude and Detuning:

For the Adiabatic Pulse, 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} = max(Q_{off})\]

If \(\Omega_{max}\) reaches a value above the maximum amplitude allowed by the device, it uses this max_amp as \(\Omega_{max}\).

  • 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 pulse 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 pulse 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. 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.

Example

import torch

from qubosolver import QUBOInstance
from qubosolver.config import SolverConfig, PulseShapingConfig
from qoolqit._solvers.types import BackendType, DeviceType
from qubosolver.solver import QuboSolver
from qubosolver.qubo_types import PulseType


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, pulse_shaping=PulseShapingConfig(pulse_shaping_method=PulseType.ADIABATIC)
)
solver = QuboSolver(instance, default_config)

solution = solver.solve()
print(solution)
QUBOSolution(bitstrings=tensor([[0., 1., 1., 0., 0., 0., 0., 0., 0., 1.], [0., 1., 0., 1., 1., 0., 0., 0., 0., 0.], [1., 1., 0., 1., 0., 0., 0., 0., 0., 0.], [0., 0., 1., 1., 0., 1., 0., 0., 0., 0.], [0., 1., 1., 1., 0., 0., 0., 0., 0., 0.], [0., 0., 1., 1., 0., 0., 0., 1., 0., 0.], [1., 1., 1., 1., 1., 1., 1., 1., 1., 1.]]), costs=tensor([-231.3951, -202.3960, -171.8957, -112.3260, -95.7631, -89.7809, 242.3500]), counts=tensor([ 2, 2, 1, 2, 304, 1, 188], dtype=torch.int32), probabilities=tensor([0.0040, 0.0040, 0.0020, 0.0040, 0.6080, 0.0020, 0.3760]), 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 pulse_shaping_method for ilustration purposes.