Online demo · Website · Examples · Benchmark
Ket (|⟩) is a quantum computing library for C++20, with first-class Python bindings. You build a circuit, simulate it on a built-in state-vector simulator, and sample measurement outcomes — from C++, from Python, the command line, a desktop GUI, or right in your browser.
▶ Try the debugger in your browser
No install — step a circuit gate by gate and watch every amplitude and Bloch vector.
Internally a circuit is a directed acyclic graph (DAG) of gates: each node is a gate and edges connect gates that share a qubit. This makes gate dependencies explicit and leaves room for future analysis and optimization passes.
- A broad gate set — Paulis, phase and
Tgates, parameterized rotations (Rx/Ry/Rz), the general single-qubitU, their controlled forms (including controlled rotations and controlled-phase),SWAP, and the three-qubit Toffoli (CCX) and Fredkin (CSWAP). - Exact state-vector simulation of the full
2ⁿamplitude vector, multithreaded across a persistentstd::thread pool (set_num_threads), with gate fusion. - Automatic backend selection — Clifford circuits are routed to an
O(n²)stabilizer simulator (scaling to thousands of qubits), everything else to the state vector. The choice is automatic but overridable. - Measurement and sampling into a classical register, for shot experiments.
- Composite gates — package a sub-circuit into a reusable, labeled block,
and
decompose()it back into primitives. - Qiskit-style ASCII diagrams via
circuit.print(). - OpenQASM 2.0 import and export, including user-defined
gateblocks. - Python bindings (built with [pybind11]) that mirror the C++ API.
- Tooling — a
ket-clicommand-line tool and aket-guicircuit viewer.
The complete list of gate methods lives in
include/ket/circuit.hpp.
Ket uses CMake and a C++20 compiler. By default build.sh builds everything —
the library, the example programs, and the ket-cli and ket-gui executables;
--help lists the rest (focused builds, the test runners, and the Python
bindings).
./build.sh # build everything
./build.sh --help # all options./build.sh --install installs ket under /usr/local: the ket-cli and
ket-gui executables, plus the library, headers, and a CMake package config so
other projects can find_package(ket) and link ket::ket. --uninstall
removes it.
The examples/ directory has runnable programs — Bell and GHZ
states, the Deutsch–Jozsa and Bernstein–Vazirani algorithms, a bit-flip code,
and Grover's search. Once built, run them from build/examples/:
./build/examples/bell
./build/examples/groverket-cli operates on OpenQASM files (or stdin, so it composes in pipelines):
./build/cli/ket-cli draw examples/bell.qasm # ASCII diagram
./build/cli/ket-cli run examples/bell.qasm # final state vector
./build/cli/ket-cli sample examples/bell.qasm --shots 1000 # measurement histogramket-gui is a step-through debugger: an editable QASM panel, a circuit view,
the live state vector, and per-qubit Bloch spheres (GLFW, Dear ImGui, ImPlot).
The fastest way to try it is the live demo
— it's the desktop GUI compiled to WebAssembly, no install required.
To run it natively:
./build/gui/ket-gui examples/grover.qasmTo use ket as a library, link the ket target, add include/ to your include
path, and include the umbrella header <ket/ket.hpp>.
#include <ket/ket.hpp>
#include <iostream>
int main() {
// A Bell state: H on q0, then CNOT(q0 -> q1).
ket::Circuit c{2};
c.h(0);
c.cx(0, 1);
std::cout << c.print(); // ASCII circuit diagram
std::cout << ket::run(c).print(); // final state vector
} ┌───┐
q_0: ┤ H ├──■──
└───┘┌─┴─┐
q_1: ─────┤ X ├
└───┘
|00⟩: 0.707107
|01⟩: 0
|10⟩: 0
|11⟩: 0.707107
Install from PyPI — prebuilt wheels, so no compiler is needed:
pip install ketpyThe distribution is named ketpy (ket is taken on PyPI), imported as such
(commonly import ketpy as ket):
import ketpy as ket
c = ket.Circuit(2)
c.h(0)
c.cx(0, 1)
c.measure_all()
print(c) # circuit diagram
print(ket.run(c)) # state vector
print(ket.sample(c, seed=0)) # one shot, e.g. [0, 0] or [1, 1]The Python API mirrors the C++ one — Circuit, run, measure, sample —
with a few Pythonic touches: Circuit and State render through
print()/str(), State supports len() and indexing (returning a Python
complex), and measure/sample take an optional seed.
For development, build the bindings from source instead — this needs a C++20 compiler and CMake (via [scikit-build-core]):
pip install --editable ".[test]"A register of n qubits is a vector of 2ⁿ complex amplitudes. Each gate is
applied as an in-place linear transformation over that vector — qubit i is
bit i of the basis-state index (little-endian). The simulator walks the DAG
in topological order (which matches insertion order) and applies each gate in
turn. Measurement samples a basis state by the Born rule (outcome i with
probability |amplitudeᵢ|²), with an optional seed for reproducibility.
Because the state vector stores all 2ⁿ amplitudes, simulation is exact but
bounded by memory — practical up to roughly 25 qubits. There is no noise
modeling.
Backends. sample and expval go through simulate(circuit, method),
which picks an engine: a Clifford circuit (only H/S/Sdg/X/Y/Z/
CX/CY/CZ/SWAP) is run on a stabilizer tableau in O(n²) time and memory
— thousands of qubits — while anything else uses the state vector. method
defaults to auto but can be forced to statevector or stabilizer
(is_clifford(circuit) / chosen_method(circuit) report the decision). The
stabilizer engine never forms a 2ⁿ vector, so run() — which returns the full
state vector — is always the dense path.
The state-vector backend applies each gate's 2ⁿ-amplitude update across a
persistent std::thread pool (the pairs are independent, so the split needs no
locks). set_num_threads(n) controls it — 0 selects the hardware concurrency;
it defaults to 1 (or KET_NUM_THREADS). Small states stay serial, since the
synchronization would cost more than the work. Consecutive gates on the same
qubits are fused into one combined gate, so a deep circuit makes fewer
sweeps over the state vector.
- DAG optimization passes (gate cancellation, commutation, fusion) and a scheduler that no longer relies on insertion order.
- A state panel in the GUI (amplitudes / Bloch-style plots via ImPlot3D).
- More backends behind
simulate()— e.g. a tensor-network (MPS) simulator. - Cache-blocking with qubit reordering, to fuse across non-adjacent qubits without thrashing the cache (the remaining gap to Aer on deep circuits).
Generated by the benchmark script.
MIT © 2026 Breno Cunha Queiroz
