Ocean SDK Beginner Free 8/12 in series 20 min read

Hello World in D-Wave Ocean

Write your first quantum annealing program with D-Wave Ocean, formulate a simple QUBO problem and solve it with the simulated annealing sampler.

What you'll learn

  • d-wave
  • ocean
  • quantum annealing
  • qubo
  • optimization
  • python

Prerequisites

  • Basic Python (variables, functions, loops)
  • No quantum physics background needed

What is D-Wave Ocean?

D-Wave Ocean is a Python SDK for programming D-Wave quantum computers. If you have used Qiskit or Cirq, you might expect to build quantum circuits out of gates like Hadamard or CNOT. D-Wave works in a completely different way. Instead of gate-based circuits, D-Wave hardware uses quantum annealing, a physical process that finds the lowest-energy state of a system. Your job as a programmer is to encode your problem as an energy function, and the annealer does the rest.

The core idea: you define a mathematical objective over binary variables (0s and 1s), and the quantum annealer searches for the variable assignment that minimizes that objective. This makes D-Wave a natural fit for optimization problems like scheduling, routing, portfolio optimization, and graph partitioning.

In gate-based quantum computing, the “hello world” is often creating a Bell state or running a simple circuit. In quantum annealing, the “hello world” is formulating a small optimization problem and finding its minimum. That is exactly what we will do here.

Setup and Installation

Install the full Ocean SDK with pip:

pip install dwave-ocean-sdk

No API key or D-Wave account is required for this tutorial. We will use a simulated annealing sampler that runs entirely on your local CPU. When you are ready to run on real quantum hardware later, the code change is minimal.

Understanding QUBO: The Language of Quantum Annealing

Before writing any code, let’s understand the problem format that D-Wave expects.

QUBO stands for Quadratic Unconstrained Binary Optimization. That name packs in three important constraints on the problem:

  • Binary: every variable xix_i is either 0 or 1.
  • Quadratic: the objective function can include individual variables (xix_i) and pairs of variables (xixjx_i x_j), but nothing higher (no xixjxkx_i x_j x_k).
  • Unconstrained: there are no explicit constraints. If your problem has constraints, you must fold them into the objective as penalty terms.

The general QUBO objective looks like this:

minimizeiQiixi+i<jQijxixj\text{minimize} \quad \sum_i Q_{ii} x_i + \sum_{i<j} Q_{ij} x_i x_j

The diagonal terms QiiQ_{ii} are linear biases (how much each variable individually contributes to the energy). The off-diagonal terms QijQ_{ij} are quadratic couplings (how pairs of variables interact).

A Worked Example by Hand

Suppose we have two binary variables xx and yy, and we want to minimize:

f(x,y)=x+y2xyf(x, y) = x + y - 2xy

Let’s evaluate every possible input:

xxyyx+y2xyx + y - 2xy
000
011
101
110

The minimum value is 0, achieved by either (0,0)(0, 0) or (1,1)(1, 1). The annealer should return one (or both) of these solutions. With only four possibilities, this is trivial to check by hand, but the same approach scales to thousands of variables where brute-force enumeration is impossible.

The QUBO dictionary for this problem maps variable pairs to coefficients:

  • (0,0)1(0, 0) \to 1 for the linear bias on xx
  • (1,1)1(1, 1) \to 1 for the linear bias on yy
  • (0,1)2(0, 1) \to -2 for the coupling between xx and yy

What is a Sampler?

In Ocean, a sampler is any object that takes a problem (QUBO or Ising) and returns a set of candidate solutions called samples. The sampler is your interface to a solver, whether that solver is real D-Wave quantum hardware, a classical heuristic, or a hybrid approach.

For local development and testing, Ocean provides SimulatedAnnealingSampler, which mimics the annealing process using classical computation. It is not quantum, but it follows the same API, so you can develop and debug your problem formulation locally and then swap in a hardware sampler with a single line change.

When you call sampler.sample_qubo(qubo, num_reads=100), the sampler runs 100 independent attempts and returns all of them in a SampleSet, sorted by energy (lowest first).

Example 1: Minimizing a Simple QUBO

Now let’s put it all together. The following code defines the QUBO from our hand-worked example, runs it through the simulated annealing sampler, and prints the best result.

from dimod import SimulatedAnnealingSampler

# Define the QUBO as a dictionary: {(i, j): coefficient}
# Diagonal entries are linear biases, off-diagonal are quadratic couplings
qubo = {
    (0, 0):  1,   # linear bias on variable x
    (1, 1):  1,   # linear bias on variable y
    (0, 1): -2,   # quadratic coupling: -2xy
}

# Create a simulated annealing sampler (runs locally, no hardware needed)
sampler = SimulatedAnnealingSampler()

# Run 100 independent annealing attempts
sampleset = sampler.sample_qubo(qubo, num_reads=100)

# The .first property gives the lowest-energy sample
best = sampleset.first
print(f"Best solution: x={best.sample[0]}, y={best.sample[1]}")
print(f"Minimum energy: {best.energy}")

Expected Output

Best solution: x=0, y=0
Minimum energy: 0.0

You may also see x=1, y=1 since both are equally optimal. The energy value is the objective function evaluated at the returned solution. The sampler consistently finds one of the two global minima.

Reading the SampleSet

The sampleset object contains all 100 samples. You can iterate over them or inspect aggregate statistics:

# Show the top 5 lowest-energy solutions with their occurrence counts
print(sampleset.truncate(5))

Each row shows a variable assignment, its energy, and how many of the 100 reads produced that exact assignment. In a well-behaved problem, the lowest-energy solution appears most frequently.

Example 2: Max-Cut on a Triangle Graph

Let’s try a more realistic problem. Max-Cut asks: given a graph, divide the nodes into two groups to maximize the number of edges that cross between groups.

For a triangle (3 nodes, all connected), we build a Binary Quadratic Model (BQM) where each node gets a binary variable. If two connected nodes are in different groups (one is 0, the other is 1), that edge is “cut.” We want to maximize cuts, but since the annealer minimizes, we negate the objective.

from dimod import SimulatedAnnealingSampler, BinaryQuadraticModel
import networkx as nx

# Create a triangle graph: 3 nodes, 3 edges
G = nx.Graph()
G.add_edges_from([(0, 1), (1, 2), (0, 2)])

# Build the BQM for Max-Cut
# For each edge (u, v), the term 2*u*v - u - v rewards cutting the edge
bqm = BinaryQuadraticModel(vartype='BINARY')
for u, v in G.edges():
    bqm.add_variable(u, -1)
    bqm.add_variable(v, -1)
    bqm.add_interaction(u, v, 2)

sampler = SimulatedAnnealingSampler()
sampleset = sampler.sample(bqm, num_reads=100)

best = sampleset.first
print(f"Best partition: {best.sample}")
print(f"Energy (negative of cut size): {best.energy}")

Expected Output

Best partition: {0: 0, 1: 1, 2: 0}
Energy (negative of cut size): -2.0

The result places node 1 in one group and nodes 0 and 2 in the other. This cuts 2 of 3 edges (0-1 and 1-2), which is the maximum possible for a triangle. The energy of -2.0 confirms two edges were cut (the negative sign is because we converted maximization to minimization).

QUBO vs. Ising: Two Sides of the Same Coin

Ocean supports two equivalent problem representations. The QUBO format uses binary variables (0 or 1), which is intuitive for most developers. The Ising model uses spin variables (si{1,+1}s_i \in \{-1, +1\}) and splits the energy into:

E(s)=ihisi+i<jJijsisjE(\mathbf{s}) = \sum_i h_i s_i + \sum_{i<j} J_{ij} s_i s_j

Here, hih_i values are the local magnetic fields (linear biases) and JijJ_{ij} values are the coupling strengths between spins.

D-Wave hardware natively operates in the Ising model, since qubits naturally have two states analogous to spin-up and spin-down. Ocean automatically converts between QUBO and Ising behind the scenes, so you can use whichever is more natural for your problem. Optimization problems with binary decisions (include/exclude, yes/no) tend to be easier to express as QUBO. Physics simulations and problems with symmetric variables often map more cleanly to Ising.

You can convert between them explicitly:

from dimod import BinaryQuadraticModel

# Start with a QUBO
qubo = {(0, 0): 1, (1, 1): 1, (0, 1): -2}
bqm = BinaryQuadraticModel.from_qubo(qubo)

# Convert to Ising form
h, J, offset = bqm.to_ising()
print(f"Linear biases (h): {h}")
print(f"Couplings (J): {J}")
print(f"Energy offset: {offset}")

The offset is a constant energy shift that arises from the variable substitution xi=(si+1)/2x_i = (s_i + 1) / 2. It does not affect which solution is optimal, but you need it to compare energy values between the two representations.

When to Use D-Wave vs. Gate-Based Quantum Computing

Choosing between quantum annealing and gate-based approaches depends on your problem type:

Use D-Wave (quantum annealing) when:

  • Your problem is naturally an optimization or constraint satisfaction problem
  • You need to find “good enough” solutions quickly rather than exact answers
  • The problem maps well to binary variables with pairwise interactions
  • You are solving logistics, scheduling, portfolio optimization, or graph problems

Use gate-based QC (Qiskit, Cirq) when:

  • Your problem requires quantum simulation (chemistry, materials science)
  • You need algorithms like Shor’s (factoring) or Grover’s (search) that rely on quantum interference patterns
  • You want to explore quantum machine learning or quantum error correction
  • You need a universal model of computation that can implement arbitrary quantum algorithms

In practice, D-Wave hardware today offers thousands of qubits (5,000+ on current processors), far more than gate-based machines. However, those qubits are specialized for annealing and cannot run arbitrary quantum algorithms. Gate-based machines have fewer qubits (100-1,000 range) but are universal, meaning they can in principle run any quantum algorithm.

For optimization problems, D-Wave provides a more direct path: you define the objective function and let the hardware minimize it. With gate-based systems, solving optimization problems typically requires hybrid algorithms like QAOA or VQE, which add significant complexity.

Running on Real D-Wave Hardware

When you are ready to move beyond simulation, the code change is straightforward. Replace the simulated sampler with a hardware sampler:

from dwave.system import DWaveSampler, EmbeddingComposite

# EmbeddingComposite maps your logical problem onto the physical qubit topology
sampler = EmbeddingComposite(DWaveSampler())
sampleset = sampler.sample_qubo(qubo, num_reads=1000)
print(sampleset.first)

You will need a free D-Wave Leap account at cloud.dwavesys.com. The free tier provides one minute of quantum processing time per month. EmbeddingComposite handles the mapping from your problem’s logical variables onto the hardware’s physical qubit connectivity (the Pegasus graph topology), which is a non-trivial step that Ocean automates for you.

Next Steps

Now that you can formulate and solve basic QUBO problems, here is where to go next:

  • QUBO Formulation Techniques: Learn how to encode real-world constraints (equality, inequality, one-hot) as penalty terms in a QUBO. This is the most important skill for practical quantum annealing.
  • Simulated Annealing Deep Dive: Understand how the classical simulated annealing sampler works, how to tune its parameters, and when its results diverge from true quantum annealing.

Was this tutorial helpful?