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.

QAOA for MaxCut: Building the Circuit from Scratch

Tags: algorithm optimization variational

This tutorial walks through the Quantum Approximate Optimization Algorithm (QAOA) pipeline step by step, using Qamomile’s low-level circuit primitives. Rather than using the high-level QAOAConverter, we will:

  1. Define a MaxCut problem for a small graph.

  2. Formulate it directly as an Ising model on spin variables.

  3. Write the QAOA circuit step by step using @qkernel.

  4. Optimize variational parameters with a classical optimizer.

  5. Decode and visualize the results.

At the end, we show that qamomile.circuit.algorithm.qaoa_state provides the same circuit in a single function call.

What is MaxCut?

Given an undirected graph G=(V,E)G = (V, E), the MaxCut problem asks us to partition the vertices into two sets so that the number of edges crossing between the two sets is maximized.

MaxCut is naturally a spin problem. Assign each vertex ii a spin si∈{+1,−1}s_i \in \{+1, -1\} indicating which side of the cut it belongs to. An edge (i,j)(i, j) is cut exactly when si≠sjs_i \ne s_j, so the number of cut edges is

MaxCut(s)=∑(i,j)∈E1−sisj2.\text{MaxCut}(\boldsymbol{s}) = \sum_{(i,j) \in E} \frac{1 - s_i s_j}{2}.

Spin-based problems such as MaxCut, spin-glass ground states, and Ising model benchmarks are most cleanly written in the spin domain. We therefore skip the QUBO / binary encoding detour and work directly with spin variables throughout this tutorial.

Create the Graph

We use a small 5-node graph with 6 edges. This is large enough to be non-trivial, yet small enough to brute-force for comparison.

<Figure size 500x400 with 1 Axes>

Ising Formulation

Maximizing ∑(i,j)∈E(1−sisj)/2\sum_{(i,j) \in E} (1 - s_i s_j) / 2 is equivalent (up to a constant) to minimizing the antiferromagnetic Ising Hamiltonian

HC(s)=∑(i,j)∈Esisj.H_C(\boldsymbol{s}) = \sum_{(i,j) \in E} s_i s_j.

Compared to the general Ising form H=∑ihisi+∑i<jJijsisjH = \sum_i h_i s_i + \sum_{i < j} J_{ij} s_i s_j, unweighted MaxCut has:

  • no linear terms: hi=0h_i = 0 for every vertex, and

  • uniform couplings: Jij=1J_{ij} = 1 for every edge (i,j)∈E(i, j) \in E.

BinaryModel.from_ising takes the Ising coefficients directly — there is no need to go through a QUBO and convert variable types.

Variable type:          SPIN
Linear terms (h_i):     {}
Quadratic terms (J_ij): {(0, 1): 1.0, (0, 2): 1.0, (1, 2): 1.0, (1, 3): 1.0, (2, 3): 1.0, (3, 4): 1.0}
Constant:               0.0

Note: BinaryModel also provides from_qubo() and from_hubo() for problems that are naturally expressed in the binary domain (e.g., assignment problems, constrained problems with penalty terms). See QAOA for Graph Partitioning for a QUBO / JijModeling-based workflow.

Exact Solution (Brute Force)

Before running QAOA, let’s find the optimal partition by trying all 2n=322^n = 32 spin configurations. This gives us a ground truth to compare against.

Optimal MaxCut value: 5
Number of optimal partitions: 2
  (1, -1, -1, 1, -1)
  (-1, 1, 1, -1, 1)

QAOA Circuit: The Idea

The QAOA ansatz prepares a parameterized quantum state:

∣γ,β⟩=∏l=1pe−iβlHM e−iγlHC  ∣+⟩⊗n|\boldsymbol{\gamma}, \boldsymbol{\beta}\rangle = \prod_{l=1}^{p} e^{-i \beta_l H_M} \, e^{-i \gamma_l H_C} \; |{+}\rangle^{\otimes n}

where:

  • ∣+⟩⊗n|{+}\rangle^{\otimes n}: uniform superposition (Hadamard on every qubit)

  • e−iγHCe^{-i \gamma H_C}: cost unitary — for the Ising cost HCH_C, this decomposes into RZZ\text{RZZ} gates for quadratic terms and RZ\text{RZ} gates for linear terms (see Steps 2–3 for the gate convention details)

  • e−iβHMe^{-i \beta H_M}: mixer unitary — with HM=∑iXiH_M = \sum_i X_i, this becomes RX(2β)\text{RX}(2\beta) on every qubit

  • pp: number of layers (depth of the ansatz)

The spin ↔\leftrightarrow computational-basis correspondence is the standard quantum convention Z∣0⟩=∣0⟩Z|0\rangle = |0\rangle, Z∣1⟩=−∣1⟩Z|1\rangle = -|1\rangle, so measurement outcome 0 maps to spin +1 and outcome 1 maps to spin -1.

We will now build each component as a @qkernel.

Step 1: Uniform Superposition

Apply a Hadamard gate to every qubit to start from the equal superposition state ∣+⟩⊗n|{+}\rangle^{\otimes n}.

Step 2: Cost Layer

Apply the cost unitary e−iγHCe^{-i \gamma H_C}.

Qamomile’s rotation gates include a 1/21/2 factor: RZ(θ)=e−iθZ/2\text{RZ}(\theta) = e^{-i \theta Z / 2} and RZZ(θ)=e−iθZ⊗Z/2\text{RZZ}(\theta) = e^{-i \theta Z \otimes Z / 2}. To match e−iγHCe^{-i \gamma H_C} exactly, we therefore pass 2Jijγ2 J_{ij} \gamma to rzz and 2hiγ2 h_i \gamma to rz.

We keep the linear argument even though it is empty for unweighted MaxCut — this makes the kernel immediately reusable for weighted MaxCut and generic spin-glass Hamiltonians, which do include linear hih_i terms.

Step 3: Mixer Layer

Apply the mixer unitary e−iβHMe^{-i \beta H_M} where HM=∑iXiH_M = \sum_i X_i. Since RX(θ)=e−iθX/2\text{RX}(\theta) = e^{-i \theta X / 2}, we need θ=2β\theta = 2\beta to implement e−iβXie^{-i \beta X_i} on each qubit.

Step 4: Full QAOA Ansatz

Compose the three pieces: superposition, then pp rounds of cost + mixer, and finally measurement.

Transpile and Optimize

We transpile the kernel, binding the problem structure (Ising coefficients, number of qubits, number of layers) while keeping gammas and betas as runtime parameters that the optimizer will tune.

We use scipy.optimize.minimize with the COBYLA method. At each iteration, the optimizer samples the circuit and evaluates the mean energy.

To make the tutorial reproducible we (i) pass seed_simulator=SEED to AerSimulator so the per-shot pseudo-random sampling is deterministic, (ii) seed the NumPy generator with the same value so initial variational parameters are stable, and (iii) set max_parallel_threads=1 so the simulator does not interleave random draws across threads. The single-thread setting trades a little performance for fully reproducible runs; in production code you can drop it (or only enable it in tests / docs builds).

Optimized cost: -0.8203
Optimal params: [np.float64(0.8871), np.float64(-0.1645), np.float64(2.0864), np.float64(0.7465), np.float64(-1.292), np.float64(1.4783)]
<Figure size 800x400 with 1 Axes>

Decode and Analyze Results

We sample the circuit with the optimized parameters and interpret the measurement outcomes. decode_from_sampleresult returns samples already in the spin domain (+1 / -1), so we can count cut edges directly — no binary conversion needed.

Best QAOA cut: 5  (optimal: 5)
Best partition (spins): [1, -1, -1, 1, -1]
<Figure size 800x400 with 1 Axes>
<Figure size 500x400 with 1 Axes>

Using the Built-in qaoa_state

Everything we implemented above — superposition, cost layer, mixer layer, and the layered loop — is already provided by qamomile.circuit.algorithm.qaoa_state. It accepts exactly the same Ising coefficients (quad, linear) and variational parameters (gammas, betas).

Let’s build the same circuit using the built-in function to confirm that it implements the same structure. Each executor below is instantiated with the same seed_simulator=SEED. Under a fixed seed, identical circuits yield identical samples and therefore identical mean energies. With finite shots the per-circuit estimate still carries shot-noise — that does not vanish under seeding — so if the two printed mean energies do differ, it indicates that the manual and built-in routes did not emit bit-identical circuits (e.g., a gate ordering or compilation difference), not residual sampling noise.

We transpile and sample with the same optimized parameters.

Manual   mean energy: -0.8203
Built-in mean energy: -0.8203

Summary

In this tutorial we:

  1. Defined a MaxCut problem and wrote it directly as an Ising Hamiltonian on spin variables — no QUBO / binary-variable detour.

  2. Built the spin-domain BinaryModel with BinaryModel.from_ising.

  3. Built every component of the QAOA circuit as a @qkernel — superposition, cost layer, mixer layer, and the full ansatz.

  4. Ran a classical optimization loop and decoded the spin-domain results.

  5. Verified that qamomile.circuit.algorithm.qaoa_state provides the same circuit with a single function call.

The same spin-first recipe applies to any Ising-like problem — spin-glass ground-state search, weighted MaxCut, Sherrington–Kirkpatrick model, and so on: plug the hih_i and JijJ_{ij} coefficients into BinaryModel.from_ising and reuse the circuit components above.

Next steps:

  • For problems that are naturally expressed with binary variables or that require constraints (penalty terms), see QAOA for Graph Partitioning, which uses the higher-level QAOAConverter together with JijModeling.