Quantum Alternating Operator Ansatz/Quantum Approximate Optimization Algorithm (QAOA)
Project description
QAOA
A flexible, modular Python library for the Quantum Approximate Optimization Algorithm / Quantum Alternating Operator Ansatz (QAOA), designed for research and experimentation. Swap problems, mixers, initial states, optimizers, and backends without rewriting your code.
Table of Contents
- Installation
- Requirements
- Quick Example
- Background
- Custom Ansatz
- Running Optimization
- Further Parameters
- Extracting Results
- Multi-Angle QAOA
- Fixing One Node to Reduce Circuit depth/width
- Building Circuits like Lego
- Minimizing Circuit Depth
- Repository Structure
- Agent
- Citation
- Acknowledgement
Installation
pip install qaoa
Or install in development mode from source:
git clone https://github.com/OpenQuantumComputing/QAOA.git
cd QAOA
pip install -e .
Requirements
- Python ≥ 3.9
- qiskit ≥ 2.3.0
- qiskit-aer ≥ 0.17.0
- qiskit-algorithms ≥ 0.4.0
- numpy, scipy, matplotlib, networkx
Optional (for the agent):
- langchain, langchain_community, langchain_chroma, langchain_openai, streamlit
Quick Example
import networkx as nx
from qaoa import QAOA, problems, mixers, initialstates
# Build a random graph
G = nx.random_regular_graph(3, 8, seed=42)
# Define QAOA components
qaoa = QAOA(
problem=problems.MaxCut(G),
mixer=mixers.X(),
initialstate=initialstates.Plus()
)
# Sample cost landscape at depth p=1
qaoa.sample_cost_landscape()
# Optimize to depth p=3
qaoa.optimize(depth=3)
# Extract results
print("Optimal expectation value:", qaoa.get_Exp(depth=3))
print("Optimal parameters (gamma):", qaoa.get_gamma(depth=3))
print("Optimal parameters (beta):", qaoa.get_beta(depth=3))
See examples/ for more complete worked examples.
Background
Given a cost function $$c: \lbrace 0, 1\rbrace^n \rightarrow \mathbb{R}$$ one defines a problem Hamiltonian $H_P$ through the action on computational basis states via
$$ H_P |x\rangle = c(x) |x\rangle,$$
which means that ground states minimize the cost function $c$. Given a parametrized ansatz $| \gamma, \beta \rangle$, a classical optimizer is used to minimize the energy
$$ \langle \gamma, \beta | H_P | \gamma, \beta \rangle.$$
QAOA of depth $p$ consists of the following ansatz:
$$ |\gamma, \beta \rangle = \prod_{l=1}^p \left( U_M(\beta_l) U_P(\gamma_l)\right) | s\rangle, $$
where
- $U_P$ is a family of phase-separating operators,
- $U_M$ is a family of mixing operators, and
- $|s\rangle$ is a "simple" initial state.
In plain vanilla QAOA these have the form $U_M(\beta_l)=e^{-i\beta_l X^{\otimes n}}$, $U_P(\gamma_l)=e^{-i\gamma_l H_P}$, and the uniform superposition $| s \rangle = |+\rangle^{\otimes n}$ as initial state.
Custom Ansatz
To create a custom QAOA ansatz, specify a problem, a mixer, and an initial state. These base classes each have an abstract method def create_circuit: that must be implemented. The problem base class additionally requires def cost:.
This library already contains several standard implementations.
- The following problem cases are already available:
- The following mixer cases are already available:
- X-mixer
- XY-mixer
- Grover-mixer
- Max k-CUT grover
- Max k-CUT LX
- X multi-angle mixer (one β per qubit)
- The following initial state cases are already available:
- Plus
- Statevector
- Dicke
- Dicke 1- and 2-states superposition
- Less than k
- Max k-CUT feasible
- Plus parameterized (|+⟩ with optimizable phase rotations)
It is very easy to extend this list by implementing the abstract methods of the base classes above. Feel free to fork the repo and open a pull request!
See examples/MaxCut/OverlapInitialState.ipynb for a study of how the overlap between the initial state and the X-mixer ground state affects QAOA performance at various circuit depths.
See examples/MaxCut/KCutExamples.ipynb for worked examples of Max k-cut using both one-hot and binary encodings.
For example, to set up QAOA for MaxCut using the X-mixer and $|+\rangle^{\otimes n}$ as the initial state:
qaoa = QAOA(
problem=problems.MaxCut(G),
mixer=mixers.X(),
initialstate=initialstates.Plus()
)
Running Optimization at Depth $p$
For depth $p=1$ the expectation value can be sampled on an $n\times m$ Cartesian grid over the domain $[0,\gamma_\text{max}]\times[0,\beta_\text{max}]$ with:
qaoa.sample_cost_landscape()
Sampling high-dimensional target functions quickly becomes intractable for depth $p>1$. The library therefore iteratively increases the depth. At each depth a local optimization algorithm (e.g. COBYLA) finds a local minimum, using the following initial guess:
-
At depth $p=1$: parameters $(\gamma, \beta)$ are taken from the minimum of the sampled cost landscape.
-
At depth $p>1$: two strategies are available, controlled by the
interpolateparameter:-
Interpolation (
interpolate=True, default): uses the INTERP heuristic to produce a smooth initial guess by interpolating the optimal angles from depth $p-1$. Works well for vanilla QAOA. -
Layer-by-layer grid scan (
interpolate=False): the best angles from depth $p-1$ are locked and a 2-D grid search is performed over the new layer's parameters. Because the grid includes $(γ=0, β=0)$ — which adds an identity layer reproducing the depth-$(p-1)$ result — the initial cost at depth $p$ is guaranteed to be ≤ cost at depth $p-1$, ensuring a monotonically increasing approximation ratio. Recommended for multi-angle and orbit ansätze.
-
# Interpolation (default)
qaoa = QAOA(..., interpolate=True)
qaoa.optimize(depth=p)
# Layer-by-layer grid scan
qaoa = QAOA(..., interpolate=False)
qaoa.optimize(depth=p)
This will call sample_cost_landscape automatically if it has not been run yet.
Further Parameters
qaoa = QAOA(
...,
backend=,
noisemodel=,
optimizer=,
precision=,
shots=,
cvar=
)
backend: the backend to use, defaults toAerSimulator()fromqiskit_aernoisemodel: noise model to apply, defaults toNoneoptimizer: optimizer from qiskit-algorithms with options, defaults to[COBYLA, {}]precision: sample until a certain precision of the expectation value is reached, based on $\text{error}=\frac{\text{variance}}{\sqrt{\text{shots}}}$, defaults toNoneshots: number of measurement shots, defaults to1024cvar: value for Conditional Value at Risk (CVaR), defaults to1(standard expectation value)
Extract Results
Once qaoa.optimize(depth=p) is run, extract the expectation value, variance, and parameters for each depth $1\leq i \leq p$:
qaoa.get_Exp(depth=i)
qaoa.get_Var(depth=i)
qaoa.get_gamma(depth=i)
qaoa.get_beta(depth=i)
Additionally, for every optimizer call at each depth, the angles, expectation value, variance, maximum cost, minimum cost, and number of shots are stored in:
qaoa.optimization_results[i]
Multi-Angle QAOA
Multi-angle QAOA allows components to use multiple parameters per layer, increasing expressibility:
- Multi-angle mixer (
XMultiAngle): each qubit gets its own independent β parameter. - Parameterized initial state (
PlusParameterized): the initial state |+⟩ with optimizable per-qubit phase rotations.
qaoa = QAOA(
problem=problems.MaxCut(G),
mixer=mixers.XMultiAngle(), # N_qubits beta parameters per layer
initialstate=initialstates.Plus()
)
The flat angle array format used by hist(), getParametersToBind(), and interp() is:
[init_0, ..., init_{n-1}, # initial state params (0 for Plus)
gamma_{0,0}, ..., beta_{0,n-1}, # layer 0 params
gamma_{1,0}, ..., beta_{1,n-1}, # layer 1 params
...]
For the standard single-parameter case this reduces to [gamma_0, beta_0, gamma_1, beta_1, ...].
Implement get_num_parameters() in a custom component to enable multi-angle support. See examples/MultiAngle for a complete example.
Fixing One Node to Reduce Circuit Size
The MaxCut (and Max k-cut) problem exhibits a flip symmetry: swapping all partition labels yields an equally valid solution. This symmetry allows one node to be fixed to a specific partition, removing it from the circuit entirely.
The node selected for fixing is always the highest-degree node. Fixing this node eliminates CZ gates equal to its degree — the maximum possible reduction for a single fixed node.
Enable this via the fix_one_node flag on any graph problem:
problem = problems.MaxCut(G, fix_one_node=True)
See examples/MaxCut/FixOneQubit.ipynb for a worked example showing the circuit-size reduction and that the approximation quality is preserved.
Minimizing Depth of Phase Separating Operator
Assuming all-to-all connectivity of qubits, one can minimize the circuit depth of the phase separating operator by solving the minimum edge colouring problem. This is implemented in GraphHandler and is invoked automatically. An example output is shown below:
Building Circuits like "Lego"
Components can be freely composed ("lego style") to build more complex circuits.
A typical workflow is:
- Define a feasible-state preparation circuit (e.g. Dicke).
- Build a mixer acting on that feasible space (e.g. Grover).
- Replicate the resulting block across independent registers using a tensor product.
For example, construct a Dicke state with Hamming weight $k=2$ on 4 qubits:
from qaoa import initialstates, mixers
dicke = initialstates.Dicke(2, 4) # k=2 excitations on N=4 qubits
Next, build a Grover mixer that operates on the feasible space prepared by the Dicke circuit:
grover = mixers.Grover(dicke)
grover.create_circuit()
grover.circuit.draw('mpl')
The Grover mixer implements
$$U_M(\beta) = U_S^\dagger , X^{\otimes n} , C^{n-1}P(\beta) , X^{\otimes n} , U_S,$$
where $U_S$ is the state-preparation circuit (here, Dicke). In the circuit diagram, $U_S$ and $U_S^\dagger$ appear as labelled blocks (Dicke / Dicke†).
Finally, use Tensor to replicate the block across independent registers:
tensor = initialstates.Tensor(grover, 3) # 3 copies → 12 qubits total
tensor.create_circuit()
tensor.circuit.draw('mpl')
The Grover mixer automatically inherits the qubit count from the Dicke circuit, and Tensor replicates the full block without manual qubit bookkeeping.
Lego-like circuit: three Grover blocks on 12 qubits
Annotating circuits
Every component (initial state or mixer) carries a label attribute used as the circuit name when create_circuit() is called. The label defaults to the class name but can be customised at construction time (for Dicke, Grover, and Tensor) or by setting the attribute before calling create_circuit():
dicke = initialstates.Dicke(2, 4, label="Dicke-2")
dicke.create_circuit()
print(dicke.circuit.name) # → "Dicke-2"
xy = mixers.XY()
xy.label = "XY-ring"
xy.setNumQubits(4)
xy.create_circuit()
print(xy.circuit.name) # → "XY-ring"
Repository Structure
QAOA/
├── qaoa/ # Core library
│ ├── qaoa.py # Main QAOA class
│ ├── problems/ # Problem Hamiltonians (MaxCut, QUBO, Portfolio, …)
│ ├── mixers/ # Mixing operators (X, XY, Grover, …)
│ ├── initialstates/ # Initial state circuits (Plus, Dicke, Tensor, …)
│ └── utils/ # Graph utilities, plot routines, and helpers
├── examples/ # Jupyter notebook examples
│ ├── MaxCut/
│ ├── ExactCover/
│ ├── PortfolioOptimization/
│ └── QUBO/
├── scripts/ # Batch / SLURM run scripts
├── agent/ # LLM-powered QAOA assistant
├── unittests/ # Unit tests
├── images/ # Figures used in documentation
└── setup.py
Note: Several notebooks in
examples/(e.g.ValidateMaxCut,ValidateCircuitQUBO,PortOptValidate) are validation notebooks whose checks are covered by the automated unit tests inunittests/. They are kept as worked examples; the authoritative validation lives in the test suite and is run viapytest unittests/.
Talk to an Agent
The agent/ folder contains a specialized QAOA assistant that can answer questions about the library or generate example code.
Run from the terminal:
cd agent
python planner.py
Run as a web interface:
cd agent
streamlit run interface.py
Example interaction:
Dependencies for the agent: langchain, langchain_community, langchain_chroma, langchain_openai, and optionally streamlit for the web interface. An OpenAI API key is also required.
Citation
If you use this library in your research, please cite:
@software{fuchs2024qaoa,
author = {Franz Georg Fuchs},
title = {{QAOA}: A Modular Python Library for the Quantum Approximate Optimization Algorithm},
year = {2024},
url = {https://github.com/OpenQuantumComputing/QAOA},
note = {Version 2.0.0}
}
Acknowledgement
This work was funded by the Research Council of Norway through project number 33202.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
File details
Details for the file qaoa-2.0.0.tar.gz.
File metadata
- Download URL: qaoa-2.0.0.tar.gz
- Upload date:
- Size: 96.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
976e26b98491bd10f82ea862fedc65fd8c1bac6a82f200d0e44d3b543fdc9513
|
|
| MD5 |
1bbc764e4915d74f4da4fc3d411cb8dc
|
|
| BLAKE2b-256 |
75b5fc8fe94c2799cc76a9a4b473b45589b688e9d7ab4080b73bed24f1f13010
|