A toolset for quantum circuit fixing written in Rust, with an emphasis on code quality and understandability.
It started as an enhanced port of a previous prototype (QSimplify), which was written purely in Python. The new version uses Rust to provide these benefits:
- Better performance and memory usage, because it's compiled to native code and uses zero-cost abstractions.
- More reliability due to Rust's stronger type system and ownership model.
- More robust error handling and null-safety due to Rust's
ResultandOptiontypes. - Occasional code simplification thanks to Rust's macro system.
- The same interface as before can be kept for Python users by providing PyO3 bindings.
Detailed benchmark results are published in the benchmarks branch.
- Features
- Installation
- Project Structure
- Project Architecture
- Useful Commands
- Usage
- Contributing
- License
-
Graph-based circuit representation: quantum circuits are modelled as a hybrid directed graph / 2D matrix (
Graph), where each qubit is a row and each "time step" is a column.- This structure allows easy analysis and manipulation.
- The directed graph is used to store semantic relationships between nodes (e.g., control or target qubits).
- The 2D matrix is used to speed up element access by position.
-
Rich gate support: 26 gates are available, including:
- Single-qubit gates (ID, H, X, Y, Z, P, RX, RY, RZ, S, SDG, SX, SY, T, TDG, U, Measure).
- Two-qubit gates (Swap, CH, CX, CY, CZ, CP).
- Three-qubit gates (CSwap / Fredkin, CCX / Toffoli, CCZ).
-
Fluent builder API:
GraphBuilderprovides a safe, chainable interface for constructing circuits without touching the graph internals directly.- Any circuit built by using the builder is guaranteed to be internally consistent.
-
Python bindings: They are exposed as a Python extension module via PyO3. So far there are bindings for:
- Qiskit, in crates/qiskit.
-
CLI: Command-line interface for loading and processing circuits.
-
Multi-format serialization: circuits can be parsed and serialized as CBOR, JSON, MessagePack or XML, and saved to or loaded from disk.
Install the qctidy command-line interface from a local checkout:
cargo install --path crates/cliOr with the Just recipe:
just install-cliThe package is not published to crates.io yet.
The project is composed of various Rust crates, which focus on making it modular and extensible. A facade exposes all the common use cases so that client implementations don't have to worry about the internal details.
├── AGENTS.md # Documentation for code agents
├── Cargo.lock # Global lockfile
├── Cargo.toml # Cargo workspace root, each crate also has its own Cargo.toml
├── prek.toml # Configuration for pre-commit hooks
├── README.md # Project description (you are reading this)
├── rustfmt.toml # Configuration for code formatting
├── crates # Workspace member crates
│ ├── analyzer # Circuit analysis and metrics (library)
│ ├── cli # Command-line interface (binary)
│ ├── codegen # Code generator (library)
│ ├── converter # Circuit parser and serializer for various formats (library)
│ ├── estimator # Circuit execution time and cost estimation (library)
│ ├── facade # Facade for client implementation (library)
│ ├── library # The core of QCTidy (library)
│ ├── ports # Ports for optional services (library)
│ ├── presenter # Circuit and graph visualization (library)
│ ├── qiskit # Python + Qiskit bindings via PyO3 (library)
│ ├── server # Server implementation as a REST API (binary)
│ └── tui # Terminal user interface (binary)
├── docs # Documentation for the project
│ ├── CHANGELOG.md # Release notes
│ ├── CHANGES.md # Changes from the original Python prototype
│ ├── CONVENTIONS.md # Repository guidelines and code conventions
│ └── SPEC.md # Requirements specification
└── images # Images used in the documentation
The architecture of the project is based on the hexagonal architecture, and is used to separate ports and adapters, which decouples the facade from the optional service crates.
The project includes a justfile with common commands, for ease of use. Run just or just --list to see all the available recipes.
Some commonly used commands:
| Just Recipe | Cargo Equivalent | Purpose |
|---|---|---|
just format |
cargo fmt |
Auto-format all source files. |
just lint |
cargo clippy --workspace |
Lint code in all the crates. |
just check |
cargo check --workspace |
Check that all the crates compile. |
just build |
cargo build --workspace |
Compile a debug build of all the crates. |
just build-release |
cargo build --release --workspace |
Compile an optimized release build. |
just test |
cargo test --workspace |
Run all unit and integration tests. |
just bench-sysinfo |
cargo run -p benchmarks --bin sysinfo |
Save the hardware/OS specs to JSON for benchmarks. |
just install-cli |
cargo install --path crates/cli |
Install the qctidy CLI binary. |
If for some reason you don't want to use Just, you can read the contents of the justfile for more common examples.
The qctidy CLI checks circuits for fixable patterns, without modifying them:
qctidy check -i circuit.jsonInputs are passed with -i/--input (repeatable) and parsed with the converter. The format is guessed from the file extension, or can be forced with --input-format (json, xml, msgpack or cbor). When no input is given, the circuit is read from standard input:
cat circuit.json | qctidy checkReports are human-readable and colorized by default. Use --output-format json to get a machine-readable report instead:
qctidy check -i circuit.json --output-format jsonThe exit code is 0 when no opportunities are found, 1 when any are found, and 2 when an input cannot be read or parsed. Use --quiet to print only the diagnostics, --verbose to show a snippet of the circuit around each detection, and --no-fail to always exit with code 0.
Rules can be chosen with --select and --ignore, which accept comma-separated rule codes (R001) or category codes (R). --select restricts the run to the selected rules; --ignore disables the given ones. When both are given, --ignore wins.
Other commands are available:
# List the available fix rules
qctidy rules
# Convert between formats
qctidy convert -i circuit.json -o circuit.xml
# Render as text: graph, grid, matrix or circuit
qctidy display -i circuit.json --format grid
# Render with graphviz: gv, png or svg
qctidy present -i circuit.json --format svgconvert guesses the output format from the -o extension (or use --output-format), and supports --prettify/--indentation. present writes to standard output unless -o is given, and accepts --dpi. The --color option (auto, always, never) is available on every command.
Run the backend and frontend together with Compose:
docker compose up --buildThe frontend is available at http://localhost:5173 and the backend at http://localhost:3000. Customize the published ports with environment variables:
FRONTEND_PORT=8081 SERVER_PORT=8080 docker compose up --builddocker build -f server.Dockerfile -t qctidy-server .
docker run --rm -p 3000:3000 qctidy-serverUse another host port if needed:
docker run --rm -p 8080:3000 qctidy-serverThe frontend container reaches the backend through a shared Docker network, using the backend container name as the host:
docker network create qctidy-net
docker build -f server.Dockerfile -t qctidy-server .
docker run --rm -d --name qctidy-server --network qctidy-net -p 3000:3000 qctidy-server
docker build -f frontend.Dockerfile -t qctidy-frontend .
docker run --rm -d --name qctidy-frontend --network qctidy-net -p 5173:80 \
-e API_URL=http://qctidy-server:3000 qctidy-frontendThe frontend is available at http://localhost:5173. To stop the containers and remove the network:
docker rm -f qctidy-server qctidy-frontend
docker network rm qctidy-netTODO.
TODO.
Each gate has a canonical name, and parsers also accept the aliases listed below (case-insensitive).
| Qubits | Gate | Description | Aliases |
|---|---|---|---|
| 1 | ID |
Identity | i, identity |
| 1 | H |
Hadamard | hadamard |
| 1 | X |
Pauli-X (NOT) | not |
| 1 | Y |
Pauli-Y | |
| 1 | Z |
Pauli-Z | |
| 1 | P |
Phase | phase |
| 1 | RX |
Rotation around X axis | |
| 1 | RY |
Rotation around Y axis | |
| 1 | RZ |
Rotation around Z axis | |
| 1 | S |
S (√Z) | sz, sqrtz |
| 1 | SDG |
S dagger (S†) | sd, szd, szdg, sqrtzd, sqrtzdg |
| 1 | SX |
√X | sqrtx |
| 1 | SY |
√Y | sqrty |
| 1 | T |
T | |
| 1 | TDG |
T dagger (T†) | td |
| 1 | U |
General unitary (θ, φ, λ) | u3 |
| 1 | Measure |
Measurement | m |
| 2 | Swap |
SWAP | |
| 2 | CH |
Controlled Hadamard | |
| 2 | CX |
Controlled X (CNOT) | cnot |
| 2 | CY |
Controlled Y | |
| 2 | CZ |
Controlled Z | |
| 2 | CP |
Controlled phase | cphase |
| 3 | CSwap |
Controlled SWAP (Fredkin) | fredkin |
| 3 | CCX |
Doubly-controlled X (Toffoli) | ccnot, toffoli |
| 3 | CCZ |
Doubly-controlled Z |
- Fork the repository and create a feature branch.
- Follow the coding conventions below.
- Submit a pull request.
- Wait for all the checks to pass and for the changes to be approved.
Check the CONVENTIONS.md file for a detailed list of repository and coding conventions.
Similar to many popular Rust projects, this is licensed under either of:
- Apache License, Version 2.0 (LICENSE-APACHE)
- MIT license (LICENSE-MIT)
at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this project by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
Copyright (c) 2026 The QCTidy Authors

