Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Quantum Annealing via D-Wave (Python)

Maintained by the JuliaQUBO organization
SECQUOIA  ·  PSR Energy

Open In Colab

Setup

Google Colab

Click the badge above to open this notebook in Colab. The setup cell installs the local simulated-annealing dependencies.

Local installation

Run the following from the repository root before opening this notebook locally (Python 3.10–3.12):

uv sync --locked --group docs --group qubo
uv run --locked --group docs --group qubo jupyter lab

make verify-dwave-python-local executes the complete local path into .nbverify/ without QPU access. The optional hardware cells require a separately managed Ocean installation and an explicit opt-in; see the quantum-annealing section below.

Learning objectives

By the end of this notebook you will be able to:

  1. Prepare a QUBO/BQM for submission through D-Wave Ocean tools.

  2. Distinguish local simulated annealing from quantum annealing on D-Wave hardware.

  3. Inspect QPU topology, embeddings, and chain-related sampling metadata when a solver is available.

  4. Explain how annealing time, chain strength, and annealing schedules can affect sampling behavior.

  5. Compare a prescribed quantum annealing schedule with an optional QAOA circuit on the same QUBO.

Prerequisites

Mathematical background: QUBO models, graph representations, and the penalty-model ideas from Notebook 2.
Prior notebooks: Notebook 2 (QUBO); Notebook 3 is useful for comparing solver strategies.
Accounts required: A D-Wave Leap account and DWAVE_API_TOKEN for QPU sections; local cells run without an account or token.
Python version: Python 3.10–3.12; local dependencies are locked in the docs and qubo groups.

This notebook introduces D-Wave’s quantum annealing workflow. We formulate the earlier QUBO example with dimod, solve it locally with neal simulated annealing, and optionally submit it to a D-Wave quantum processing unit. We also use NetworkX to represent and visualize graphs.

Problem statement

We define a QUBO as the following optimization problem:

min⁡x∈{0,1}n∑(ij)∈E(G)Qijxixj+∑i∈V(G)Qiixi+cQ=min⁡x∈{0,1}nx⊤Qx+cQ\min_{x \in \{0,1 \}^n} \sum_{(ij) \in E(G)} Q_{ij}x_i x_j + \sum_{i \in V(G)}Q_{ii}x_i + c_Q = \min_{x \in \{0,1 \}^n} x^\top Q x + c_Q

where we optimize over binary variables x∈{0,1}nx \in \{ 0,1 \}^n, on a constrained graph G(V,E)G(V,E) defined by an adjacency matrix QQ. We also include an arbitrary offset cQc_Q.

Example

Suppose we want to solve the following problem via QUBO $$ \min_{\mathbf{x}} 2𝑥_0+4𝑥_1+4𝑥_2+4𝑥_3+4𝑥_4+4𝑥_5+5𝑥_6+4𝑥_7+5𝑥_8+6𝑥_9+5𝑥_{10} \ s.t. \begin{bmatrix} 1 & 0 & 0 & 1 & 1 & 1 & 0 & 1 & 1 & 1 & 1\ 0 & 1 & 0 & 1 & 0 & 1 & 1 & 0 & 1 & 1 & 1\ 0 & 0 & 1 & 0 & 1 & 0 & 1 & 1 & 1 & 1 & 1 \end{bmatrix}\mathbf{x}=

[111]\begin{bmatrix} 1\\ 1\\ 1 \end{bmatrix}

\mathbf{x} \in {0,1 }^{11} $$

Notebook Cell

First, we rewrite this problem as an unconstrained one by adding quadratic penalties for the linear constraints. Let’s define the problem parameters.

In order to define the QQ matrix, we write the problem

min⁡xc⊤xs.t.Ax=bx∈{0,1}11\min_{\mathbf{x}} \mathbf{c}^\top \mathbf{x}\\ s.t. \mathbf{A}\mathbf{x}=\mathbf{b} \\ \mathbf{x} \in \{0,1 \}^{11}

as follows:

min⁡xc⊤x+ρ(Ax−b)⊤(Ax−b)x∈{0,1}11\min_{\mathbf{x}} \mathbf{c}^\top \mathbf{x} + \rho(\mathbf{A}\mathbf{x}-\mathbf{b})^\top (\mathbf{A}\mathbf{x}-\mathbf{b}) \\ \mathbf{x} \in \{0,1 \}^{11}

Exploting the fact that x2=xx^2=x for x∈{0,1}x \in \{0,1\}, we can make the linear terms appear in the diagonal of the QQ matrix.

ρ(Ax−b)⊤(Ax−b)=ρ(x⊤(A⊤A)x−2b⊤Ax+b⊤b)\rho(\mathbf{A}\mathbf{x}-\mathbf{b})^\top (\mathbf{A}\mathbf{x}-\mathbf{b}) = \rho( \mathbf{x}^\top (\mathbf{A}^\top \mathbf{A}) \mathbf{x} - 2\mathbf{b}^\top \mathbf{A} \mathbf{x} + \mathbf{b}^\top \mathbf{b} )

Penalty parameter rationale

The penalty term must be large enough that any infeasible assignment is worse than the objective improvement it might gain by violating the constraint. For a binary constraint A x = b, the residual A x - b is integer-valued; the smallest nonzero violation has squared penalty at least 1. A conservative sufficient bound is rho = sum(abs(c)) + epsilon, because changing binary variables can improve the linear objective by at most sum(abs(c_i)).

Worked example: if sum(abs(c_i)) = 6 and rho = 5.9, an infeasible assignment with violation 1 can gain 6 objective units while paying only 5.9 penalty units, so it can look 0.1 units better than a feasible assignment. Choosing rho = 6 + epsilon closes that gap for this bounded binary model.

This is a sufficient bound for this model family, not a universal rule. Constraints with non-binary variables, non-integer residuals, or a larger objective range need a problem-specific penalty analysis. See Glover, Kochenberger, and Du (2019), “A Tutorial on Formulating and Using QUBO Models.”

[[ -46    0    0   48   48   48    0   48   48   48   48]
 [   0  -44    0   48    0   48   48    0   48   48   48]
 [   0    0  -44    0   48    0   48   48   48   48   48]
 [  48   48    0  -92   48   96   48   48   96   96   96]
 [  48    0   48   48  -92   48   48   96   96   96   96]
 [  48   48    0   96   48  -92   48   48   96   96   96]
 [   0   48   48   48   48   48  -91   48   96   96   96]
 [  48    0   48   48   96   48   48  -92   96   96   96]
 [  48   48   48   96   96   96   96   96 -139  144  144]
 [  48   48   48   96   96   96   96   96  144 -138  144]
 [  48   48   48   96   96   96   96   96  144  144 -139]]
144

We can visualize the graph that defines this instance using the Q matrix as the adjacency matrix of a graph.

<Figure size 640x480 with 1 Axes>

Let’s define a QUBO model and then solve it via simulated annealing.

Let’s now solve this problem using Simulated Annealing

minimum energy: 5.0
<Figure size 640x480 with 1 Axes>
<Figure size 640x480 with 1 Axes>
minimum energy: 5.0

Now let’s solve this using Quantum Annealing!

The default execution stays local: it skips account discovery, connectivity checks, and QPU submission, then uses seeded SimulatedAnnealingSampler samples. The saved QPU topology, embedding, and sampling outputs below are historical results from an earlier hardware run. They are retained for the published lesson; a fresh local run produces skip messages and classical sampling plots instead.

To deliberately run the hardware demonstration, use a separately managed Ocean environment and set QUBONOTEBOOKS_DWAVE_ENABLE_QPU=1 before running the notebook. The locked local environment excludes the Ocean cloud client; see local setup. QPU jobs consume access and credits, and an opted-in connection or submission failure raises an error.

Create a D-Wave Leap account at https://cloud.dwavesys.com/leap/ and supply DWAVE_API_TOKEN through your process environment, or store it in Colab Secrets under that name. Do not paste a token into a notebook cell. Existing Ocean local configuration remains supported when you opt in without this environment variable. The guarded dwave ping diagnostic uses the same configuration.

Using endpoint: https://cloud.dwavesys.com/sapi/
Using region: na-west-1
Using solver: Advantage2_system1
Working graph version: 01138bbada
Submitted problem ID: 2456d588-e031-485f-ae03-25e3e639fb12

Wall clock time:
 * Solver definition fetch: 326.372 ms
 * Problem submit and results fetch: 521.775 ms
 * Total: 848.148 ms

QPU timing:
 * post_processing_overhead_time = 1.0 us
 * qpu_access_overhead_time = 734.24 us
 * qpu_access_time = 34690.76 us
 * qpu_anneal_time_per_sample = 20.0 us
 * qpu_delay_time_per_sample = 60.57 us
 * qpu_programming_time = 34586.8 us
 * qpu_readout_time_per_sample = 23.39 us
 * qpu_sampling_time = 103.96 us
 * total_post_processing_time = 1.0 us
Solver id:     Advantage_system4;graph_id=01d07086e1
Topology:      pegasus shape= [16]
Number of qubits= 5627
Number of couplers= 40279
<Figure size 700x500 with 1 Axes>
{'timing': {'qpu_sampling_time': 88880.0, 'qpu_anneal_time_per_sample': 20.0, 'qpu_readout_time_per_sample': 48.3, 'qpu_access_time': 104642.76, 'qpu_access_overhead_time': 724.24, 'qpu_programming_time': 15762.76, 'qpu_delay_time_per_sample': 20.58, 'total_post_processing_time': 1.0, 'post_processing_overhead_time': 1.0}, 'problem_id': '2a51da6a-6066-4e2a-b24c-1eb9d5d7ef64', 'embedding_context': {'embedding': {3: (3835,), 0: (3715, 1924), 1: (3820,), 4: (1939, 3700), 2: (1984,), 5: (1909, 3865), 6: (3760, 1969), 7: (1985, 3850), 8: (3805, 1954), 9: (2000, 1999), 10: (3790, 1894)}, 'chain_break_method': 'majority_vote', 'embedding_parameters': {}, 'chain_strength': 159.83033942960208, 'timing': {'embedding': 0.0007941749645397067, 'unembedding': 0.0005198059370741248}}}
<Figure size 700x500 with 1 Axes>
minimum energy: 5.0
<Figure size 640x480 with 1 Axes>
<Figure size 640x480 with 1 Axes>
minimum energy: 5.0

Now we can play with the other parameters such as Annealing time, chain strength, and annealing schedule to improve the performance of D-Wave’s Quantum Annealing.

Practice checkpoints

Use these checkpoints during the workshop to test the main ideas before moving on.

Notebook Cell
rho = 0.25; best = ((0, 0), 0.25); feasible = false
rho = 4.0; best = ((1, 0), 1.0); feasible = true
Notebook Cell
best_energy_by_reads = {100: -1.0, 500: -1.05, 1000: -1.1}
Notebook Cell
QPU embedding metadata is unavailable; use the local fallback result above.

Simulating the quantum anneal with CUDA-Q

The local neal runs above use classical simulated annealing. Here we simulate the quantum evolution itself, on a CPU, for the same 11-variable integer program. We will measure the probability of an optimal feasible assignment, inspect the energy spectrum, and sample solutions. Enumerating all 211=20482^{11}=2048 assignments provides our correctness reference; this is a teaching example, not a speed comparison with neal or a QPU.

CUDA-Q is optional. From the repository root, make verify-cudaq-python installs the optional group and runs both this annealing section and the QAOA comparison that follows. Without it, the notebook prints Skipping CUDA-Q quantum methods: install the optional cudaq group. and continues. The availability message, numerical outputs, and figure below record one CUDA-Q-enabled CPU run. The missing-package test and D-Wave local CI lane verify the skip path. See local setup for platform and installation details.

From the integer program to the problem Hamiltonian

The model already built above has energy

E(x)=cTx+ρ∥Ax−b∥22=xTQx+cQ.E(x)=c^\mathsf{T}x+\rho\lVert Ax-b\rVert_2^2=x^\mathsf{T}Qx+c_Q.

dimod uses spins si=2xi−1s_i=2x_i-1 when converting a BQM to Ising coefficients. On a computational basis state, Pauli ZiZ_i has eigenvalue 1−2xi=−si1-2x_i=-s_i. Therefore the Hamiltonian for minimization is

HQ=CI−∑ihiZi+∑i<jJijZiZj.H_Q=C I-\sum_i h_i Z_i+\sum_{i \lt j}J_{ij}Z_iZ_j.

The minus sign applies to the linear spin coefficients; the pair terms keep their sign. Its computational-basis energies must equal the original penalized objective, including the offset CC.

Choose Eref=ρE_{\rm ref}=\rho in the model’s arbitrary cost units and use the dimensionless schedule

H~(s)=(1−s)(−∑iXi)⏟H~m+sHQ/Eref⏟H~p,s=t/T.\widetilde H(s)=(1-s)\underbrace{\left(-\sum_i X_i\right)}_{\widetilde H_m}+s\underbrace{H_Q/E_{\rm ref}}_{\widetilde H_p},\qquad s=t/T.

Here tt and TT are dimensionless: physical time would be ℏt/Eref\hbar t/E_{\rm ref}. They are not D-Wave annealing times in microseconds. The ground state of H~m\widetilde H_m is ∣+⟩⊗n|+\rangle^{\otimes n} with energy −n-n, so Hadamard gates prepare the correct initial state. Positive rescaling of HQH_Q preserves its minimizers.

CUDA-Q statevector indices use qubit 0 as the least significant bit; sampled strings list qubit 0 first. We account for both conventions explicitly and check every basis-state energy.

CUDA-Q enabled: qpp-cpu, ideal noiseless quantum simulation.
Exact optimum: 5; feasible minimizers: 2
Optimal assignments (x0, ..., x10):
[[0 0 0 0 0 0 0 0 1 0 0]
 [0 0 0 0 0 0 0 0 0 0 1]]

Follow the annealing schedule with a quantum circuit

For a midpoint sks_k and step Δt=T/K\Delta t=T/K, use the symmetric product formula

Uk≈e−i(Δt/2)(1−sk)H~m  e−iΔt skH~p  e−i(Δt/2)(1−sk)H~m.U_k\approx e^{-i(\Delta t/2)(1-s_k)\widetilde H_m}\;e^{-i\Delta t\,s_k\widetilde H_p}\;e^{-i(\Delta t/2)(1-s_k)\widetilde H_m}.

Since RP(θ)=e−iθP/2R_P(\theta)=e^{-i\theta P/2}, each mixer half-step uses Rx[−Δt(1−sk)]R_x[-\Delta t(1-s_k)]. Problem terms use Rz(2Δt skhiZ)R_z(2\Delta t\,s_k h_i^Z) and a CNOT–Rz(2Δt skJij)R_z(2\Delta t\,s_k J_{ij})–CNOT pair. All problem terms commute. The constant offset contributes only a global phase and can be omitted during evolution, while remaining in energy comparisons.

The step size below is at most 0.1 in dimensionless time. During development, this circuit was compared with a direct dense SciPy Schrödinger-equation solution at T=10T=10: the ground-space probability differed by about 2.4×10−42.4\times10^{-4}, falling to 6.0×10−56.0\times10^{-5} when the step size was halved. This is a discretized, finite-time anneal, not an assumption of perfect adiabatic evolution.

 T (dimensionless)  steps  P(optimal)  P(feasible)  mean penalized cost
          0.000000      1    0.000977     0.004395          1319.500000
          2.000000     20    0.038713     0.176971           215.081707
         10.000000    100    0.120527     0.525642            35.076591
         50.000000    500    0.240729     0.990139             7.684564
        200.000000   2000    0.401332     0.999282             6.588367

Spectrum, degeneracy, and optimization success

This integer program has two optimal assignments: selecting variable x8x_8 alone or x10x_{10} alone gives feasible cost 5. Success therefore means overlap with the entire optimal ground space,

Popt(T)=∑x: E(x)=Emin⁡∣⟨x∣ψ(T)⟩∣2,P_{\rm opt}(T)=\sum_{x:\,E(x)=E_{\min}}|\langle x|\psi(T)\rangle|^2,

not overlap with one arbitrarily selected eigenvector.

The spectrum below shows the four lowest energies relative to the instantaneous ground energy. Its full gap E1−E0E_1-E_0 reaches zero at s=1s=1 because those two optima are degenerate. The endpoint gap above the optimal subspace is instead E2−E0E_2-E_0. We also report and mark the minimum sampled value of E2−E0E_2-E_0 along the schedule; it is slightly smaller than its endpoint value. This spectral diagnostic does not by itself provide an adiabatic runtime bound. A zero splitting between two successful answers must not be interpreted as failure to reach an optimum, or used blindly in a nondegenerate adiabatic-time estimate. Interior spectral points are sampled numerically; they are not a proof that all interior avoided crossings have been located.

Longer anneals increase success for the settings shown, but finite-time quantum interference need not make every time sweep monotone. This idealized, noiseless, fully connected evolution omits temperature, control errors, and minor embedding. Hardware embedding changes the Hamiltonian and its gaps; the spectrum and schedule help explain why chain strength and annealing time matter in the QPU section above.

Minimum full gap E1-E0: 0.000000 at s=1.00
Endpoint gap above the optimal subspace: 0.020833 (normalized energy)
Minimum sampled E2-E0: 0.020693 at s=0.99 (normalized energy)
<Figure size 1000x370 with 2 Axes>

Decode the quantum samples into integer-program solutions

We now sample the explicitly selected longest-anneal state, retained together with its exact probabilities, and map each bit back to the corresponding decision variable. The check uses the original constraints Ax=bAx=b and objective cTxc^\mathsf{T}x, as well as the QUBO ground energy. The exact statevector probabilities describe the simulator; the counts are a finite-shot estimate. Neither is a claim of quantum advantage.

Sampled anneal time T: 200 (dimensionless)
Best sampled assignment (x0 first): 00000000001
Original objective: 5; Ax = [1 1 1]
Optimal reads: 1660/4096; exact state probability: 0.401332
CUDA-Q quantum annealing checks passed.

The circuit implements the product-formula construction described in NVIDIA’s Hamiltonian simulation documentation, with signs derived here for minimization and the original lecture model. CUDA-Q’s execution and bit-ordering conventions explain the distinction between statevector indices and sampled strings.

Optional comparison: QAOA with CUDA-Q

The annealing demonstration followed a prescribed schedule. QAOA (the Quantum Approximate Optimization Algorithm) instead alternates cost and mixer layers, adjusts their angles with a classical optimizer, and measures candidate assignments. We reuse the same 11-variable QUBO, Pauli coefficients, and exact baseline from the annealing section so the change of method is easy to follow. Both are ideal, noiseless CPU simulations; neither establishes a speedup over classical solvers.

This optional comparison uses the CUDA-Q setup above. Without the optional package, both quantum sections skip after one notice. The published results show an enabled CPU run; make verify-cudaq-python reproduces and checks both methods. Students can continue directly to benchmarking, then revisit circuit methods in the Julia QAOA notebook.

The alternating circuit follows Farhi, Goldstone, and Gutmann (2014), A Quantum Approximate Optimization Algorithm.

Reuse the annealing problem Hamiltonian

The earlier mapping already checked all 211=20482^{11}=2048 Hamiltonian energies against this QUBO and the original penalty formula. Reuse its dimensionless problem_hamiltonian, which represents HQ/ErefH_Q/E_{\rm ref} with Eref=ρE_{\rm ref}=\rho. For QAOA, remove only the identity offset oIoI to obtain the cost operator HCH_C:

HC=HQ/Eref−oI,fQUBO(x)=Eref [Eraw Ising(x)+o].H_C=H_Q/E_{\rm ref}-oI, \qquad f_{\rm QUBO}(x)=E_{\rm ref}\,[E_{\rm raw\ Ising}(x)+o].

The identity contributes only a global phase to the circuit. Restore its offset and the energy scale when reporting QUBO costs, including constraint penalties. The raw Ising expectation alone is not the original objective.

Reusing the 11-variable QUBO and exact optimum 5.

Alternating cost and mixer layers

Start from ∣+⟩⊗n|+\rangle^{\otimes n} using Hadamard gates. Each of the pp layers applies e−iγℓHCe^{-i\gamma_\ell H_C} followed by e−iβℓ∑iXie^{-i\beta_\ell\sum_i X_i}. Our parameter order is [gamma[0], ..., gamma[p-1], beta[0], ..., beta[p-1]], matching the indices used below. The identity offset contributes only a global phase, so no gate is needed for it.

Since RZ(θ)=e−iθZ/2R_Z(\theta)=e^{-i\theta Z/2}, each field needs rz(2*gamma*field). A CNOT–RZ–CNOT sequence implements each pair term. The mixer uses rx(2*beta). We write these gates directly with @cudaq.kernel; no QAOA solver package is needed. Increasing pp adds parameters and gates, and need not improve the result of a bounded local optimization.

The classical outer loop

SciPy’s COBYLA optimizer adjusts the 2p2p angles to minimize cudaq.observe(...).expectation(). On this simulator observe uses the exact state expectation by default; this optimization has no measurement shots. We use three seeded starting points and a fixed evaluation budget because the objective has local minima. A budget stop is reported, and the lowest observed energy is retained.

The expectation is an average over all assignments, not the energy of a returned solution. We compare it with the uniform initial state’s QUBO expectation and verify it independently from the final state probabilities. Only the subsequent sample call uses finite shots. See CUDA-Q’s execution primitives for these semantics.

 start  evaluations  converged                                                                            message
     1          150      False Return from COBYLA because the objective function has been evaluated MAXFUN times.
     2          150      False Return from COBYLA because the objective function has been evaluated MAXFUN times.
     3          150      False Return from COBYLA because the objective function has been evaluated MAXFUN times.
p=2; parameters (gamma then beta): [ 0.051586  0.141741 -0.509189 -0.269327]
Raw Ising expectation (dimensionless, no offset): -25.723833
Mean QUBO cost: 84.756034; uniform baseline: 1319.500000
Exact state P(optimal): 0.042601; P(feasible): 0.166546

Sample, decode, and compare with enumeration

cudaq.sample measures the final state in the computational basis (implicitly, because the ansatz has no measurement gates). Its bitstrings list qubit 0 first: the string 100...0 means x0=1x_0=1. That differs from the integer ordering of the matrix/statevector above; we construct the index explicitly to keep the exact probabilities paired with the right sample.

We report the lowest-QUBO-cost observed sample separately from the most frequent sample; frequency is not an optimization objective. count / shots estimates a sample’s probability, while probabilities[index] is its exact simulator probability. P(optimal) sums the probabilities of all degenerate optima. Feasibility means Ax=bAx=b; subtracting ρ∥Ax−b∥2\rho\|Ax-b\|^2 from the QUBO cost recovers the original unpenalized objective.

For this small seeded demonstration we assert that the sampled best reaches the exact optimum and is feasible, without pinning a histogram or selecting one of the degenerate optimal bitstrings. QAOA and finite-shot sampling do not guarantee that outcome on other instances or budgets.

Final measurement shots: 8192
              bits (x0 first)  raw Ising  QUBO cost  original cost  feasible  count  sample frequency  exact probability
lowest cost       00000000001 -27.385417   5.000000       5.000000      True    191          0.023315           0.021300
most frequent     00000000000 -24.489583 144.000000       0.000000     False    736          0.089844           0.082723
CUDA-Q QAOA checks passed.

Summary

In this notebook we:

  • Reused a QUBO/BQM model and sampled it with local simulated annealing and, when credentials are available, D-Wave quantum annealing.

  • Checked D-Wave configuration and guarded QPU access so local runs can fall back gracefully.

  • Inspected solver topology, embeddings, returned sample metadata, and energy distributions.

  • Simulated the same QUBO with an optional CUDA-Q quantum anneal, checked its Ising convention, and measured optimal-solution probability and spectral gaps.

  • Compared the annealing schedule with optional QAOA, reusing the Hamiltonian and exact baseline while a classical optimizer chooses circuit angles.

  • Identified annealing controls such as reads, chain strength, annealing time, and schedules for follow-up experiments.

Learning objectives met: You practiced preparing BQMs for Ocean samplers, distinguishing local and QPU execution, inspecting embeddings, and connecting annealing parameters to solver behavior.

Next steps: Proceed to Notebook 5: Benchmarking to compare solver performance with time-to-solution and performance-ratio metrics.

Further reading:

Acknowledgments

This notebook was developed by: