|
Maestro 0.2.11
Unified interface for quantum circuit simulation
|
Maestro ships high-performance Python bindings built with nanobind. The bindings expose the full simulation pipeline — circuit construction, backend selection, execution, and expectation-value estimation — in a Pythonic API.
Install pre-built wheels from PyPI (Linux, macOS, Windows):
Or build from source from the repository root:
Supported platforms (pre-built wheels):
| Platform | Architecture | Python |
|---|---|---|
| Linux | x86_64 | 3.10, 3.11, 3.12 |
| macOS | arm64 (Apple Silicon) | 3.10, 3.11, 3.12 |
| Windows | AMD64 | 3.10, 3.11, 3.12 |
That's it — you're ready to import maestro and run quantum simulations.
All execution and estimation functions accept a SimulatorConfig object that bundles every simulator knob into a single, reusable value. Create one config and pass it to every call — no need to repeat simulator_type, simulation_type, max_bond_dimension, etc.
| Parameter | Type | Default | Description |
|---|---|---|---|
| simulator_type | SimulatorType | QCSim | Simulation backend |
| simulation_type | SimulationType | Statevector | Simulation method |
| max_bond_dimension | int or None | None | MPS bond dimension limit |
| singular_value_threshold | float or None | None | MPS truncation threshold |
| use_double_precision | bool | False | GPU double precision flag |
| disable_optimized_swapping | bool | False | Disable MPS swap optimization |
| lookahead_depth | int | -1 | Swap optimization lookahead depth |
| mps_measure_no_collapse | bool | True | Use probability-based MPS sampling |
The fastest way to run a circuit is maestro.simple_execute. Pass an OpenQASM 2.0 string and get measurement counts back immediately.
What's in the result dict?
| Key | Type | Description |
|---|---|---|
| counts | dict[str, int] | Measurement outcome → count |
| simulator | int | Backend enum value (see SimulatorType) |
| method | int | Simulation method enum value (see SimulationType) |
| time_taken | float | Wall-clock time in seconds |
You can explicitly select the simulator type and simulation method via a SimulatorConfig.
MPS enables simulation of circuits with hundreds of qubits when entanglement is bounded.
Ultra-fast simulation for circuits that only use Clifford gates (H, S, CX, X, Y, Z).
The table below shows which simulation types are supported by each backend.
| SimulationType | QCSim | Qiskit Aer | GPU | QuEST |
|---|---|---|---|---|
| Statevector | ✅ | ✅ | ✅ | ✅ |
| MatrixProductState | ✅ | ✅ | ✅ | ❌ |
| Stabilizer | ✅ | ✅ | ❌ | ❌ |
| TensorNetwork | ✅ | ✅ | ✅ | ❌ |
| PauliPropagator | ✅ | ❌ | ✅ | ❌ |
| ExtendedStabilizer | ❌ | ✅ | ❌ | ❌ |
Distributed execution: QCSim and Qiskit Aer support p-block composite simulation via CompositeQCSim / CompositeQiskitAer. QuEST natively supports MPI-distributed statevector simulation.
Use maestro.simple_estimate to compute expectation values of Pauli observables without measurements in the circuit.
You can also use a specific backend via config:
For a more Pythonic workflow (similar to Qiskit), use the QuantumCircuit class to build circuits programmatically without writing QASM strings.
| Gate | Method | Parameters |
|---|---|---|
| Pauli-X | qc.x(qubit) | — |
| Pauli-Y | qc.y(qubit) | — |
| Pauli-Z | qc.z(qubit) | — |
| Hadamard | qc.h(qubit) | — |
| S | qc.s(qubit) | — |
| S† | qc.sdg(qubit) | — |
| T | qc.t(qubit) | — |
| T† | qc.tdg(qubit) | — |
| √X | qc.sx(qubit) | — |
| √X† | qc.sxdg(qubit) | — |
| K | qc.k(qubit) | — |
| Phase | qc.p(qubit, λ) | λ (radians) |
| Rx | qc.rx(qubit, θ) | θ (radians) |
| Ry | qc.ry(qubit, θ) | θ (radians) |
| Rz | qc.rz(qubit, θ) | θ (radians) |
| U | qc.u(qubit, θ, φ, λ) | 3 angles |
| CNOT | qc.cx(ctrl, tgt) | — |
| CY | qc.cy(ctrl, tgt) | — |
| CZ | qc.cz(ctrl, tgt) | — |
| CH | qc.ch(ctrl, tgt) | — |
| CSX | qc.csx(ctrl, tgt) | — |
| CSX† | qc.csxdg(ctrl, tgt) | — |
| SWAP | qc.swap(q1, q2) | — |
| CP | qc.cp(ctrl, tgt, λ) | λ |
| CRx | qc.crx(ctrl, tgt, θ) | θ |
| CRy | qc.cry(ctrl, tgt, θ) | θ |
| CRz | qc.crz(ctrl, tgt, θ) | θ |
| CU | qc.cu(ctrl, tgt, θ, φ, λ, γ) | 4 angles |
| Toffoli | qc.ccx(c1, c2, tgt) | — |
| Fredkin | qc.cswap(ctrl, q1, q2) | — |
Mirror fidelity measures how well a circuit "undoes itself". Maestro constructs the mirror circuit by appending the adjoint (inverse) of every gate in reverse order and returns P(|0…0⟩) — the probability of returning to the initial state. A value of 1.0 indicates a perfect simulation.
This is useful for benchmarking simulator accuracy, characterising noise from approximate methods (e.g. MPS with low bond dimension), and for circuit validation.
By default, mirror fidelity uses shot-based sampling (1024 shots). For exact results on small circuits, pass full_amplitude=True.
The inner product computes ⟨ψ₁|ψ₂⟩ between two circuits' output states, where |ψᵢ⟩ = Uᵢ|0⟩. Internally, Maestro builds the combined circuit U₂ followed by U₁† and evaluates ⟨0|U₁†U₂|0⟩ via the efficient ProjectOnZero operation — this is particularly fast with the MPS backend as it avoids constructing the full statevector.
The result is a complex number: magnitude gives the overlap (fidelity when squared), and phase captures relative phase information.
Maestro provides a comprehensive noise simulation suite with multiple approaches, each targeting a different trade-off between speed and accuracy:
| Function | Overhead | Accuracy | Best For |
|---|---|---|---|
| noisy_estimate | Zero | Per-qubit Pauli damping | Fast ansatz screening |
| noisy_estimate_montecarlo | N × noiseless | Gate-by-gate accurate | Training with realistic noise |
| noisy_execute | N × noiseless | Gate-by-gate + shot noise | Shot-based workflows |
| coherent_estimate | N × noiseless | Coherent rotation errors | Coherent error analysis |
| coherent_execute | N × noiseless | Coherent + shot noise | Shot-based coherent noise |
| full_noise_execute | N × noiseless | All layers combined | Realistic device simulation |
| full_noise_estimate | N × noiseless | All layers combined | Hardware-accurate estimation |
All noise functions accept a config parameter for backend selection.
The NoiseModel class defines per-qubit Pauli noise channels. Each qubit can have independent X, Y, and Z error probabilities.
noisy_estimate runs a single noiseless simulation and analytically damps each expectation value based on the noise model. This is exact for a single-layer Pauli channel and provides a fast first-order approximation.
noisy_estimate_montecarlo injects random Pauli errors after every gate, runs noiseless estimation on each noisy circuit, and averages the results. This captures how noise compounds through the circuit depth.
This is the recommended approach for QML training with realistic noise. The cost is noise_realizations * noiseless_time. Works with any backend:
noisy_execute is the shot-based counterpart: it injects gate-by-gate noise and returns measurement counts rather than expectation values.
| Parameter | Type | Default | Description |
|---|---|---|---|
| noise_realizations | int | 100 / 64 | Independent noise samples |
| seed | int or None | None | RNG seed for reproducibility |
| config | SimulatorConfig | SimulatorConfig() | Simulator configuration |
Coherent noise models systematic calibration errors — e.g. over/under-rotation of gates — rather than stochastic Pauli errors. Instead of randomly inserting X, Y, Z gates, coherent noise injects deterministic rotation gates (Rx, Ry, Rz) after every gate in the circuit.
The key difference from Pauli noise:
Coherent Estimate — the coherent analogue of noisy_estimate_montecarlo:
Coherent Execute — the shot-based counterpart:
| Method | Description |
|---|---|
| nm.set_coherent_depolarizing(q, p) | Rz angle from depolarizing probability |
| nm.set_coherent_dephasing(q, p) | Rz rotation from dephasing probability |
| nm.set_coherent_bit_flip(q, p) | Rx rotation from bit-flip probability |
| nm.set_coherent_rotation(q, rx, ry, rz) | Explicit per-axis angles (radians) |
| nm.set_all_coherent_depolarizing(n, p) | Uniform coherent noise on all qubits |
| nm.set_all_coherent_dephasing(n, p) | Uniform coherent dephasing on all qubits |
| nm.set_coherent_strength(n, p) | Alias for set_all_coherent_depolarizing |
| nm.has_coherent() | Check if any coherent noise is configured |
T1 amplitude damping models energy relaxation — the spontaneous decay from |1⟩ to |0⟩ over time. Maestro implements this using the quantum trajectory method: after each gate, with probability γ, the qubit is probabilistically reset to |0⟩.
| Method | Description |
|---|---|
| nm.set_t1(q, gamma) | Per-gate T1 decay probability |
| nm.set_all_t1(n, gamma) | Uniform T1 on all qubits |
| nm.set_t1_from_time(q, gate_time_s, t1_time_s) | T1 from physical time constants |
| nm.has_t1() | Check if T1 is configured |
ZZ crosstalk models parasitic coupling between neighbouring qubits. When a gate acts on qubit q1, the spectator qubit q2 accumulates an unwanted Rz rotation. This is one of the dominant error sources on superconducting devices.
| Method | Description |
|---|---|
| nm.set_crosstalk(q1, q2, strength) | Symmetric ZZ coupling |
| nm.has_crosstalk() | Check if crosstalk is configured |
Readout error models classical measurement errors — the probability of reporting the wrong bit value when measuring a qubit. On real hardware, readout errors are asymmetric: the false-positive rate P(1|0) is typically much lower than the false-negative rate P(0|1).
Maestro applies readout error as a post-measurement classical channel: after all quantum noise and measurement, each bit is independently flipped according to its readout error rates.
Readout error is applied automatically by noisy_execute and full_noise_execute. For path integral queries, use qc.noisy_prob() which applies an analytic first-order readout correction.
| Method | Description |
|---|---|
| nm.set_readout_error(q, p10, p01) | Asymmetric readout error |
| nm.set_readout_error_symmetric(q, p) | Symmetric readout error |
| nm.set_all_readout_error(n, p) | Uniform symmetric readout on all qubits |
| nm.has_readout_error() | Check if readout error is configured |
Two-qubit depolarizing models correlated errors on qubit pairs after two-qubit gates (CX, CZ, etc.). The channel applies one of 15 non-identity two-qubit Pauli operators with equal probability p/15:
Λ(ρ) = (1−p)ρ + (p/15) Σ_{P ∈ {I,X,Y,Z}⊗2 \ {II}} PρP†
This is separate from per-qubit depolarizing — it captures the joint error that occurs specifically during two-qubit interactions.
| Method | Description |
|---|---|
| nm.set_2q_depolarizing(q1, q2, p) | Correlated 2Q depolarizing channel |
| nm.has_any_2q_depolarizing() | Check if any 2Q depolarizing is set |
Real hardware has different error rates for single-qubit and two-qubit gates. Gate-type-specific noise lets you set separate depolarizing rates that are applied only after the corresponding gate type:
These are in addition to the base set_depolarizing() channel, which applies after all gates regardless of type.
Typical usage — matching IBM backend calibration data:
| Method | Description |
|---|---|
| nm.set_1q_gate_depolarizing(q, p) | Depolarizing after 1Q gates only |
| nm.set_2q_gate_depolarizing(q, p) | Depolarizing after 2Q gates only |
| nm.set_all_1q_gate_depolarizing(n, p) | Bulk 1Q gate noise on all qubits |
| nm.set_all_2q_gate_depolarizing(n, p) | Bulk 2Q gate noise on all qubits |
| nm.has_1q_gate_noise() | Check if 1Q gate noise is set |
| nm.has_2q_gate_noise() | Check if 2Q gate noise is set |
The qc.noisy_prob() method computes the probability of a target state with analytic readout error correction via the path integral simulator. This avoids Monte Carlo sampling entirely — it uses a first-order expansion that requires only n+1 path integral evaluations (the target state plus n single-bit-flipped variants):
P_noisy(b) ≈ Π(1−pᵢ)·P(b) + Σᵢ pᵢ·Π_{j≠i}(1−pⱼ)·P(b⊕eᵢ)
| Key | Type | Description |
|---|---|---|
| probability | float | Readout-corrected P(target) |
| target_state | str | The queried bitstring |
| has_readout_error | bool | Whether correction was applied |
| time_taken | float | Computation time in seconds |
The full_noise_execute and full_noise_estimate functions apply all configured noise layers in a single call, using Monte Carlo averaging. The noise is injected per gate in physical order:
This enables realistic device-level simulation with a single noise model.
Combined Execute (shot-based):
Combined Estimate (expectation values):
| Function | Noise Type | Overhead | Best For |
|---|---|---|---|
| noisy_estimate | Pauli | Zero | Fast ansatz screening |
| noisy_estimate_montecarlo | Pauli | N × noiseless | Training with realistic noise |
| noisy_execute | Pauli | N × noiseless | Shot-based Pauli noise |
| coherent_estimate | Coherent | N × noiseless | Coherent error analysis |
| coherent_execute | Coherent | N × noiseless | Shot-based coherent noise |
| full_noise_execute | All layers | N × noiseless | Realistic device simulation |
| full_noise_estimate | All layers | N × noiseless | Hardware-accurate estimation |
| qc.noisy_prob(target, nm) | Readout | O(n) PI evals | Path integral readout correction |
All noise functions are also available as bound methods on QuantumCircuit: qc.noisy_execute(nm), qc.coherent_execute(nm), qc.full_noise_execute(nm), qc.noisy_prob(target, nm), etc.
| Method | Category | Description |
|---|---|---|
| nm.set_depolarizing(q, p) | Pauli | Symmetric depolarizing (px=py=pz=p/3) |
| nm.set_dephasing(q, p) | Pauli | Pure phase-flip (Z only) |
| nm.set_bit_flip(q, p) | Pauli | Pure bit-flip (X only) |
| nm.set_qubit_noise(q, px, py, pz) | Pauli | Custom Pauli channel |
| nm.set_all_depolarizing(n, p) | Pauli | Uniform depolarizing on all qubits |
| nm.set_all_dephasing(n, p) | Pauli | Uniform dephasing on all qubits |
| nm.set_1q_gate_depolarizing(q, p) | Gate-specific | Depolarizing after 1Q gates only |
| nm.set_2q_gate_depolarizing(q, p) | Gate-specific | Depolarizing after 2Q gates only |
| nm.set_all_1q_gate_depolarizing(n, p) | Gate-specific | Bulk 1Q gate noise |
| nm.set_all_2q_gate_depolarizing(n, p) | Gate-specific | Bulk 2Q gate noise |
| nm.set_2q_depolarizing(q1, q2, p) | 2Q correlated | Correlated 2Q Pauli channel |
| nm.set_readout_error(q, p10, p01) | Readout | Asymmetric readout error |
| nm.set_readout_error_symmetric(q, p) | Readout | Symmetric readout error |
| nm.set_all_readout_error(n, p) | Readout | Uniform readout error |
| nm.set_coherent_depolarizing(q, p) | Coherent | Rz from depolarizing probability |
| nm.set_coherent_dephasing(q, p) | Coherent | Rz from dephasing probability |
| nm.set_coherent_bit_flip(q, p) | Coherent | Rx from bit-flip probability |
| nm.set_coherent_rotation(q, rx, ry, rz) | Coherent | Explicit rotation angles |
| nm.set_all_coherent_depolarizing(n, p) | Coherent | Uniform coherent noise |
| nm.set_coherent_strength(n, p) | Coherent | Alias for above |
| nm.set_t1(q, gamma) | T1 | Per-gate decay probability |
| nm.set_all_t1(n, gamma) | T1 | Uniform T1 decay |
| nm.set_t1_from_time(q, t_gate, t1) | T1 | T1 from physical constants |
| nm.set_crosstalk(q1, q2, strength) | Crosstalk | Symmetric ZZ coupling |
| nm.has_any() | Query | Any noise configured? |
For full control over the simulation lifecycle, use the Maestro class directly to create, configure, and destroy simulators.
Maestro supports the QuEST simulator as an alternative CPU backend. QuEST is loaded dynamically as a shared library (libmaestroquest), so it is available only when the library has been built and installed.
QuEST natively supports MPI-distributed statevector simulation, enabling execution across multiple nodes for larger qubit counts.
QuEST only supports Statevector simulation. Requesting any other simulation type (MPS, Stabilizer, etc.) will raise an error.
The programmatic QuantumCircuit API works with QuEST too:
Maestro supports GPU-accelerated simulation via a dynamically-loaded CUDA library (libmaestro_gpu_simulators). Like QuEST, the GPU backend is optional and loaded at runtime only when requested.
Unlike QuEST, the GPU backend supports multiple simulation types including Statevector, MPS, Tensor Network, and Pauli Propagation.
| SimulationType | GPU Support |
|---|---|
| Statevector | ✅ |
| MatrixProductState | ✅ |
| TensorNetwork | ✅ |
| PauliPropagator | ✅ |
| Stabilizer | ❌ |
| ExtendedStabilizer | ❌ |
For portable scripts that should work with or without GPU:
| Value | Description | Dynamic? |
|---|---|---|
| SimulatorType.QCSim | QCSim CPU simulator | No (built-in) |
| SimulatorType.CompositeQCSim | Composite (distributed) CPU simulator | No (built-in) |
| SimulatorType.QuestSim | QuEST simulator | Yes (libmaestroquest) |
| SimulatorType.Gpu | GPU-accelerated simulator (requires CUDA) | Yes (libmaestro_gpu_simulators) |
| Value | Description | Supported By |
|---|---|---|
| SimulationType.Statevector | Full statevector simulation | QCSim, Aer, GPU, QuEST |
| SimulationType.MatrixProductState | MPS / tensor-train simulation | QCSim, Aer, GPU |
| SimulationType.Stabilizer | Clifford stabiliser simulation | QCSim, Aer |
| SimulationType.TensorNetwork | Tensor network contraction | QCSim, Aer, GPU |
| SimulationType.PauliPropagator | Pauli frame propagation | QCSim, GPU |
| SimulationType.ExtendedStabilizer | Extended stabiliser decomposition | Aer only |