Getting Started
Prerequisites
Tessera requires Python 3.10 or higher. The following packages are installed automatically as dependencies:
qiskit >= 2.3qiskit-ibm-runtime >= 0.46numpy >= 2.0networkx >= 3.0
Installation
Clone the repository and install in editable mode:
git clone https://github.com/derrickboyer3/tessera.git
cd tessera
pip install -e .To also install development dependencies for running tests and benchmarks:
pip install -e .[dev]Your First Circuit
Once installed, you can transpile a circuit in a few lines. The transpile() function is the main entry point. Pass it a Qiskit circuit and a backend name and it handles everything else.
from qiskit import QuantumCircuit
from tessera.api.transpile import transpile
qc = QuantumCircuit(3)
qc.h(0)
qc.cx(0, 1)
qc.cx(1, 2)
qc.measure_all()
transpiled = transpile(qc, backend="IBM")
print(transpiled)The output is a standard Qiskit QuantumCircuit with all gates decomposed to the IBM basis set (cx, rz, sx, x, u), qubits mapped to physical hardware positions, and routing SWAPs inserted where needed.
Choosing a Backend
Tessera supports three backends out of the box. Pass the backend name as a string:
# IBM basis: cx, rz, sx, x, u
transpiled = transpile(qc, backend="IBM")
# IonQ basis: rx, ry, rz, cx
transpiled = transpile(qc, backend="IONQ")
# Rigetti basis: rx, rz, cz
transpiled = transpile(qc, backend="RIGETTI")Each backend uses a default coupling map based on real hardware. See the Backends page for the full list of devices and available coupling map overrides.
Overriding the Coupling Map
Each backend has a default coupling map but you can pass any named coupling map key or a custom TesseraCouplingMapregardless of which backend you're using. The backend controls gate decomposition, the coupling map controls routing topology. They are fully independent.
# Use any named coupling map key with any backend
transpiled = transpile(qc, backend="IBM", coupling_map="IBM_BRISBANE")
transpiled = transpile(qc, backend="IBM", coupling_map="IONQ_ARIA")
# Pass a custom coupling map
from tessera.hardware.coupling_map import TesseraCouplingMap
cm = TesseraCouplingMap(3, [(0, 1), (1, 0), (1, 2), (2, 1)])
transpiled = transpile(qc, backend="IBM", coupling_map=cm)See the Coupling Maps page for the full list of available named keys.
Choosing a Layout Algorithm
Tessera ships three layout algorithms for mapping logical qubits onto physical positions. Pass one by name with the layout_algorithm parameter. The default is "dense", matching pre-v1.1 behavior.
# Dense (default): greedy placement by interaction frequency
transpiled = transpile(qc, backend="IBM", layout_algorithm="dense")
# SABRE: forward-backward trial routing
transpiled = transpile(qc, backend="IBM", layout_algorithm="sabre")
# Trivial: direct logical to physical mapping
transpiled = transpile(qc, backend="IBM", layout_algorithm="trivial")You can also pass a custom callable of the form (circuit, coupling_map) -> dict[int, int] if you want to drop in your own algorithm. See the Layout Algorithms page for the details of each built-in algorithm and the custom-callable contract.
Choosing a Routing Algorithm
The routing algorithm controls how SWAP gates get inserted when two-qubit gates land on non-adjacent qubits. Pass one by name with the pathfinder parameter. The default is "bfs", matching pre-v1.1 behavior.
# BFS (default): pairwise shortest path
transpiled = transpile(qc, backend="IBM", pathfinder="bfs")
# A*: pairwise shortest path with an admissible heuristic
transpiled = transpile(qc, backend="IBM", pathfinder="a_star")
# SABRE: whole-circuit swap selection with front-layer lookahead
transpiled = transpile(qc, backend="IBM", pathfinder="sabre")The parameter kept its original name for backwards compatibility. It still accepts a custom pairwise callable of the form (start: int, end: int) -> list[int], just like before. See the Routing Algorithms page for the details of each built-in algorithm and the custom-callable contract.
Running the Optimization Loop
Cancel Adjacent and Merge Rotations are wrapped in an optimization loop that can run them more than once. Use optimization_iterations to control how many times. The default is 1, matching pre-v1.1 behavior.
# Default: a single optimization pass
transpiled = transpile(qc, backend="IBM")
# Fixed number of iterations
transpiled = transpile(qc, backend="IBM", optimization_iterations=5)
# Iterate until gate count stabilizes (capped by max_iterations, default 1000)
transpiled = transpile(qc, backend="IBM", optimization_iterations=-1)Convergence mode (-1) keeps iterating until the gate count stops decreasing. It is capped at max_iterations (default 1000) to prevent runaway loops on circuits that do not converge cleanly.
Debug Mode
Pass debug_on=True to print per-pass gate counts as the circuit moves through the pipeline. Useful for understanding where gates are being added or removed.
transpiled = transpile(qc, backend="IBM", debug_on=True)[Tessera] Running pass: BasisTranslationPass | Gates: 6
[Tessera] Finished pass: BasisTranslationPass | Gates: 14
[Tessera] Running pass: LayoutPass | Gates: 14
[Tessera] Finished pass: LayoutPass | Gates: 14
[Tessera] Running pass: BasicSwapRouter | Gates: 14
[Tessera] Finished pass: BasicSwapRouter | Gates: 14
[Tessera] Running pass: BasisTranslationPass | Gates: 14
[Tessera] Finished pass: BasisTranslationPass | Gates: 14
[Tessera] Running pass: RemoveBarriersPass | Gates: 14
[Tessera] Finished pass: RemoveBarriersPass | Gates: 14
[Tessera] Running pass: OptimizationLoopPass | Gates: 14
[Tessera] Running pass: CancelAdjacentPass | Gates: 14
[Tessera] Finished pass: CancelAdjacentPass | Gates: 13
[Tessera] Running pass: MergeRotationsPass | Gates: 13
[Tessera] Finished pass: MergeRotationsPass | Gates: 12
[Tessera] Finished pass: OptimizationLoopPass | Gates: 12The optimization loop wraps its inner passes, so you'll see them logged between the loop's start and finish markers. Each iteration of the loop prints another set of inner-pass logs.