Skip to content

Results are limited to the current section: Application solving tools

Product news

The quantum pipeline

A QUBO problem on NN variables consists in a symmetric matrix QQ of size N×NN\times N.

Solving a QUBO problem means to find the bitstring z=(z1,...,zN)∈{0,1}Nz=(z_1,...,z_N)\in \{0, 1\}^N that minimizes the quantity

f(z)=zTQz=∑iQiizi+∑i≠jQijzizj,zi∈{0,1}. f(z) = z^TQz= \sum_i Q_{ii} z_i + \sum_{i \neq j} Q_{ij} z_i z_j, \quad z_i \in \{0,1\}.

Problem formulation in Rydberg Hamiltonian

Section titled “Problem formulation in Rydberg Hamiltonian”

To use a Rydberg Analog model, we need to map the QUBO problem onto the Rydberg Hamiltonian.

This is achieved by identifying the binary variables with atomic occupations zi=niz_i = n_i

where ni∈{0,1}n_i \in \{0,1\} denotes whether atom ii is in the Rydberg state.

The effective Hamiltonian in the classical (diagonal Ω~=0\tilde{\Omega}=0) limit of the driven Rydberg system can be written as

H=−∑iδ~ini+∑i<jJ~ijninj, H = - \sum_i \tilde{\delta}_i n_i + \sum_{i<j} \tilde{J}_{ij} n_i n_j,

where δ~\tilde{\delta} is the local detuning and Jij=1/rij6J_{ij}=1/r_{ij}^6 is the interaction energy between atoms ii and jj. By direct comparison, we obtain the mapping:

Qii  ⟷  −2δ~i(1) Q_{ii} \;\longleftrightarrow\; - 2\tilde{\delta}_i \tag{1} Qij  ⟷  J~ij(2) Q_{ij} \;\longleftrightarrow\; \tilde{J}_{ij} \tag{2}

Mapping (1)(1) is the final detuning part of the drive shaping. Mapping (2)(2) is called the embedding.

In this way, tuning the interaction strengths between atoms (e.g., via their spatial separation) we can match the QUBO coefficients, while adjusting QiiQ_{ii} maps to change the local detunings. Under this correspondence, the ground state of the Rydberg Hamiltonian minimizes HH and therefore encodes the optimal solution of the original QUBO problem.

See QoolQit (external)'s documentation for more details on the Rydberg Hamiltonian.

Both drive (external) and register (external) are then compiled into a QuantumProgram (external), to run on a quantum backend (emulator or QPU).

Qubo Solver can solve a QUBO instance using Pasqal's Rydberg analog devices, either on local/remote emulators or on a real QPU. Solving with a quantum approach is a pipeline of three steps, each exposed as a standalone function you can call directly:

  1. Embedding — map the QUBO instance's variables to atoms on a device, producing a Register.
  2. Drive shaping — build the time-dependent drive Hamiltonian applied to the register.
  3. Compiling and running — compile the register and drive into a program, and run it on a backend: a local emulator, a remote emulator, or a real QPU.
from qubosolver import (
Instance,
Solution,
LocalEmulator,
embedding,
drive_shaping,
solving,
matrix,
analysis,
)
import qoolqit
# Private utility to set seed.
from qubosolver.utils._random import manual_seed
manual_seed(147)
instance = Instance(
matrix.tensor(
[
[-0.2, 0.0, 1.0],
[0.0, -1.0, 1.5],
[1.0, 1.5, -0.1],
]
)
)
device = qoolqit.AnalogDeviceWithDMM()
backend = LocalEmulator()
# 1. Embedding: map the instance onto a register of atoms.
register = embedding.blade.embed_for_device(instance, device)
# 2. Drive shaping: build the drive Hamiltonian for that register.
drive = drive_shaping.proportional_diagonal.build_drive(instance, register, device=device, dmm=True)
# 3. Compile and run on the chosen backend.
program = solving.analog_quantum_sampling.compile(register, drive, device)
job = backend.run(program)
# 4. Turn the raw results into a Solution and inspect it.
solution = Solution.from_results(job.results(), instance)
print(analysis.to_dataframe([solution]))
labels bitstrings costs counts probs
0 0 110 -1.2 777 0.777
1 0 010 -1.0 219 0.219
2 0 100 -0.2 3 0.003
3 0 000 0.0 1 0.001

You can plot the register and the drive.

Register of atoms embedding the QUBO instance
Register of atoms embedding the QUBO instance
Drive Hamiltonian applied to the register
Drive Hamiltonian applied to the register

from pathlib import Path
from matplotlib import pyplot as plt
output_dir = Path.cwd()
output_dir.mkdir(parents=True, exist_ok=True)
register.draw()
fig = plt.gcf()
fig.savefig(output_dir / "quantum_intro_register.svg")
plt.close(fig)
drive.draw()
fig = plt.gcf()
fig.savefig(output_dir / "quantum_intro_drive.svg")
plt.close(fig)

Each call returns a plain object you can inspect or pass to a different step: swap embedding.blade.embed for embedding.greedy.embed, try another drive shaping method, or run on a different backend — the rest of the pipeline is unaffected.

To run remotely (on a remote emulator or a real QPU), pass a RemoteEmulator or QPU backend instead of LocalEmulator. Remote runs are asynchronous: backend.run(program) returns as soon as the job is queued, so you can save its identifiers and retrieve the results later. See the qubosolver-in-full tutorial for the full remote and save/retrieve example, as well as the equivalent classical functional call.

For the common case, SolverConfig and Solver wrap the four steps above into a single call, using sensible defaults for anything you don't specify:

from qubosolver import (
Instance,
Solver,
SolverConfig,
matrix,
analysis,
)
# Private utility to set seed.
from qubosolver.utils._random import manual_seed
manual_seed(147)
instance = Instance(
matrix.tensor(
[
[-0.2, 0.0, 1.0],
[0.0, -1.0, 1.5],
[1.0, 1.5, -0.1],
]
)
)
config = SolverConfig()
solver = Solver(instance, config)
solution = solver.solve()
print(analysis.to_dataframe([solution]))
labels bitstrings costs counts probs
0 0 110 -1.2 983 0.983
1 0 001 -0.1 17 0.017
  • Learn how variables are mapped onto atoms in Embedding.
  • Learn how the drive Hamiltonian is built in Drive shaping.
  • Choose between local emulators, remote emulators and a QPU in Backends.