Skip to main content

pennylane-maestro

PyPI version Tests License: GPL v3

A PennyLane plugin for the Maestro quantum simulator by Qoro Quantum.

Drop-in replacement for default.qubit — one line change, same code, faster results.

# Before
dev = qp.device("default.qubit", wires=20)

# After 
dev = qp.device("maestro.qubit", wires=20)

Why Maestro?

  • Iterate Faster — Up to 20× faster statevector simulations for VQE/QAOA loops.
  • Scale Up — Simulate 1000+ qubits using Maestro's optimized MPS backend.
  • Sample from MPS — The only PennyLane MPS backend that supports shot-based sampling.
  • Persistent-State Trotter Evolution — Linear-time simulation of repeated Trotter steps with intermediate snapshots (qp.Snapshot or incremental_evolve).
  • GPU Ready — Fast GPU simulation on MPS workloads via cuQuantum.

Installation

pip install pennylane-maestro

That's it. This installs pennylane (≥0.38) and qoro-maestro (≥0.3.8) automatically.

Quick Start

import pennylane as qp
import numpy as np

dev = qp.device("maestro.qubit", wires=2)

@qp.qnode(dev)
def circuit(theta):
    qp.RX(theta, wires=0)
    qp.CNOT(wires=[0, 1])
    return qp.expval(qp.PauliZ(1))

print(circuit(np.pi / 4))

Performance

Benchmarked on PennyLane 0.44.1. Run examples/benchmark_lightning_vs_maestro.py to reproduce.

Statevector

Qubits default.qubit lightning.qubit maestro.qubit vs dq vs lq
20 977 ms 115 ms 45 ms 22× 2.6×
22 4.31 s 543 ms 184 ms 23× 3.0×
24 10.5 s 2.36 s 820 ms 13× 2.9×
26 DNF 10.1 s 3.56 s ∞ 2.8×

MPS — Scaling to 1000+ Qubits

Qubits default.qubit default.tensor (quimb) maestro.qubit (MPS) vs quimb
100 OOM 90 ms 11 ms 8×
500 OOM 689 ms 90 ms 7.7×
1000 OOM 1.96 s 207 ms 9.5×

MPS Shot Sampling

🔥 Only Maestro supports this. Neither default.tensor nor Qiskit Aer MPS offer shot-based sampling through PennyLane.

Qubits maestro.qubit (MPS) Unique Samples
100 259 ms 10,000
500 1.22 s 10,000
1000 2.43 s 10,000

Backends

All backends are selected via keyword arguments — no code changes needed:

# CPU Statevector (default)
dev = qp.device("maestro.qubit", wires=20)

# MPS for 100+ qubits
dev = qp.device("maestro.qubit", wires=100,
                simulation_type="MatrixProductState",
                max_bond_dimension=256)

# Stabilizer for Clifford circuits
dev = qp.device("maestro.qubit", wires=1000,
                simulation_type="Stabilizer")

# Finite shots with reproducible seed
dev = qp.device("maestro.qubit", wires=10, shots=10_000, seed=42)

# GPU (requires separate license — see below)
dev = qp.device("maestro.qubit", wires=28, simulator_type="Gpu")
All available options
simulator_type Description
"QCSim" Qoro's optimized CPU simulator (default)
"Gpu" CUDA-accelerated GPU simulator
"CompositeQCSim" Multi-node distributed CPU
"QiskitAer" Integrated Qiskit Aer backend
"CompositeQiskitAer" Distributed Qiskit Aer backend
"DistributedGpu" State distributed across local GPUs
"DistributedMpiGpu" State distributed across MPI ranks/GPUs
"QuestSim" QuEST simulator
simulation_type Description
"Statevector" Full statevector (default)
"MatrixProductState" MPS / tensor-train for large qubit counts
"Stabilizer" Clifford-only stabilizer
"TensorNetwork" General tensor network
"PauliPropagator" Pauli propagation
"ExtendedStabilizer" Extended stabilizer
"DensityMatrix" Density matrix for mixed states
"MatrixProductOperator" MPO simulation
"PathIntegral" Path integral simulation

Device options

Further keyword arguments map onto Maestro's SimulatorConfig:

# Double precision on GPU / Qiskit Aer
dev = qp.device("maestro.qubit", wires=28, simulator_type="Gpu",
                precision="double")

# Pauli propagation with truncation
dev = qp.device("maestro.qubit", wires=200,
                simulation_type="PauliPropagator",
                pp_max_pauli_weight=8, pp_gates_between_trims=10)

# Force gate fusion on; turn off pre-execution reductions
dev = qp.device("maestro.qubit", wires=20, gate_fusion=True,
                enable_causal_cone_reduction=False, auto_reduce=False)
Option Default Description
precision None "single" or "double" for GPU and Qiskit Aer backends. None keeps each backend's default; QCSim always uses double.
max_bond_dimension None MPS bond-dimension cap.
singular_value_threshold None MPS SVD truncation cutoff.
disable_optimized_swapping False Disable MPS swap optimisation.
pp_coefficient_threshold None PauliPropagator: drop Pauli strings with smaller coefficients.
pp_max_pauli_weight None PauliPropagator: drop Pauli strings acting on more qubits than this.
pp_gates_between_trims None PauliPropagator: gates between truncation passes.
pp_gates_between_deduplications None PauliPropagator: gates between merges of duplicate strings.
enable_causal_cone_reduction True Simulate only the causal cone of the measured observables.
auto_reduce True Detect invariant subspaces and simulate the reduced register.
enable_observable_clustering True Split multi-observable estimates into groups with disjoint lightcones.
max_cluster_qubits None Cap each observable cluster at this many lightcone qubits.
gate_fusion None Fuse compatible gates. None uses each backend's default.
seed None Seed for reproducible sampling and noise.
adjoint_backend "auto" How adjoint derivatives are computed (see Gradients): "engine", "inner_product", or "auto" (engine by default).

use_double_precision=True/False is still accepted as shorthand for precision="double"/"single". The legacy names pp_pauli_weight_threshold (→ pp_max_pauli_weight) and pp_steps_between_trims (→ pp_gates_between_trims) still work but emit a DeprecationWarning.

Gradients

On the CPU statevector (simulator_type="QCSim", simulation_type="Statevector", the defaults), analytic expectation values of Pauli strings, tensor products and Hamiltonians/Sums, and qml.probs, are differentiated with the adjoint method. diff_method="best" (the QNode default) resolves to "adjoint":

dev = qp.device("maestro.qubit", wires=8)

@qp.qnode(dev)                       # diff_method="best" -> "adjoint"
def circuit(weights, x):
    qp.AngleEmbedding(x, wires=range(8))
    qp.StronglyEntanglingLayers(weights, wires=range(8))
    return qp.expval(qp.Z(0))
  • With a qoro-maestro build that includes AdjointProgram, the adjoint sweep runs in the engine, restricted to each observable's causal cone. A broadcast batch of inputs (e.g. x of shape (32, 8)) is evaluated and differentiated in one engine call. This includes trainable broadcast parameters, as in data re-uploading (qml.Rot(w * x + b, ...)): diff_method="best" then uses a device VJP, which returns one gradient entry per batch element. With device_vjp=False set explicitly, with jax.jit, or with torch when only some trainable parameters are broadcast, such a batch is split into one circuit per element.
  • qml.probs needs the engine path. A Jacobian costs one backward sweep per outcome, so it is used for up to 10 wires with the default device_vjp=False and up to 24 wires with device_vjp=True (one sweep in total); wider probabilities use parameter-shift.
  • The inner-product fallback (adjoint_backend="inner_product") is also available, evaluating one maestro.inner_product per gate parameter and Pauli term (expectation values only).
  • Finite shots, mid-circuit measurements, other simulation types (MPS, stabilizer, ...) and other measurements (var, state, samples, probs beyond the wire limits above) use parameter-shift, as before. diff_method="adjoint" raises for these.
  • With PennyLane's default device_vjp=False, the Jacobian is computed together with the forward pass. This is the fastest choice for training, but plain forward calls with trainable (requires_grad=True) parameters also pay for the Jacobian. For inference-heavy use, pass device_vjp=True to the QNode: forward calls then only simulate, and the gradient is a separate VJP pass (on a 20-qubit, 32-sample VQC: forward 0.46 s instead of 1.73 s, loss+grad 2.55 s instead of 1.82 s).

Examples

Hamiltonian VQE

Hamiltonians are evaluated natively via Maestro's batched estimate() — all Pauli terms in a single C++ call:

import pennylane as qp
from pennylane import numpy as np  # trainable arrays for qp.grad

n_qubits = 20
H = qp.Hamiltonian(
    [0.5] * (n_qubits - 1) + [0.3] * n_qubits,
    [qp.PauliZ(i) @ qp.PauliZ(i+1) for i in range(n_qubits - 1)] +
    [qp.PauliX(i) for i in range(n_qubits)]
)

dev = qp.device("maestro.qubit", wires=n_qubits)

@qp.qnode(dev)  # adjoint gradients on the statevector
def vqe_circuit(params):
    for i in range(n_qubits):
        qp.RY(params[i], wires=i)
    for i in range(n_qubits - 1):
        qp.CNOT(wires=[i, i + 1])
    return qp.expval(H)

params = np.random.random(n_qubits)
energy = vqe_circuit(params)
gradient = qp.grad(vqe_circuit)(params)

Incremental Trotter Evolution & Snapshots

Time evolution algorithms often measure expectation values across intermediate steps. Standard simulation re-runs from time $t=0$ for each step (quadratic $O(N^2)$ cost). Maestro retains persistent simulator state across steps for linear $O(N)$ execution.

Via PennyLane Snapshots (Automatic Acceleration)

When a circuit repeats a Trotter step with intermediate qp.Snapshot calls, maestro.qubit automatically detects the pattern and uses Maestro's persistent-state execution:

import pennylane as qp

dev = qp.device("maestro.qubit", wires=2)

@qp.qnode(dev)
def trotter_circuit():
    qp.PauliX(0)
    for step in range(1, 4):
        qp.IsingXX(0.2, wires=[0, 1])
        qp.Snapshot(f"step_{step}", measurement=qp.expval(qp.PauliZ(0)))
    return qp.expval(qp.PauliZ(0))

results = qp.snapshots(trotter_circuit)()
# results contains "step_1", "step_2", "step_3", and "execution_results"

Via Direct Incremental Evolution API

For large workloads (e.g. 100+ steps on MPS), bypass tape generation entirely with incremental_evolve:

import pennylane as qp
from pennylane_maestro import incremental_evolve

dev = qp.device("maestro.qubit", wires=2, simulation_type="MatrixProductState")

res = dev.incremental_evolve(
    init=lambda: qp.PauliX(0),
    trotter_step=lambda: qp.IsingXX(0.2, wires=[0, 1]),
    measure_at_steps=[10, 20, 30],
    observables=[qp.PauliZ(0), qp.PauliZ(1)],
)
# res["expectation_values"] has shape (len(measure_at_steps), len(observables))

100-Qubit Ising Quench with MPS Shot Sampling

See examples/ising_phase_transition.py for a full demo that simulates a 100-qubit transverse-field Ising model and uses Maestro's exclusive MPS shot sampling to extract magnetization distributions and spatial correlations. Runs in ~30 seconds.

Supported Operations

  • Automatic Gate Decomposition — PennyLane decomposes any unsupported gate (e.g. IsingXX) into Maestro's native gate set automatically. Rot and U3 are native.
  • Mid-Circuit Measurements (MCM) — Supports mid-circuit measurements (qp.measure), resets, and conditional operations (qp.cond).
  • Snapshots — Native support for qp.Snapshot with batched Pauli expectation estimation.
  • Parameter Broadcasting — Broadcast analytic Pauli expectation values and probabilities run as one batched engine call (with an AdjointProgram-enabled qoro-maestro); other broadcast circuits are split per batch element (see Gradients for trainable broadcast parameters).
  • Delay — Native support for qp.Delay timing operations.

🚀 GPU Acceleration

For large-scale workloads, Maestro supports CUDA-accelerated simulation (statevector, MPS, tensor network) via NVIDIA cuQuantum.

dev = qp.device("maestro.qubit", wires=28, simulator_type="Gpu")

GPU Benchmark — Fermi-Hubbard MPS (48 qubits, χ=256)

Simulator Relative Runtime
Maestro GPU 1×
Maestro CPU 5×
Qibo GPU 7.5×
Qiskit CPU 14×
PennyLane GPU 64×

Larger instances failed to run on some platforms, limiting comparison.

→ Request GPU access & free trial at maestro.qoroquantum.net


Citation

If you use pennylane-maestro in your research, please cite:

@misc{pennylane_maestro,
  author       = {{Qoro Quantum}},
  title        = {PennyLane-Maestro: High-Performance C++ Backend for PennyLane},
  year         = {2026},
  publisher    = {GitHub},
  howpublished = {\url{https://github.com/QoroQuantum/pennylane-maestro}}
}

Authors & License

Maintained by Qoro Quantum (team@qoroquantum.de).

Licensed under GPL-3.0. See the LICENSE file for details.

Metadata

Release files for pennylane-maestro 0.6.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pennylane-maestro 0.6.0
File Size Uploaded Provenance Actions
pennylane_maestro-0.6.0.tar.gz 64.9 kB With provenance Details for pennylane_maestro-0.6.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for pennylane-maestro 0.6.0
File Interpreter ABI Platform Actions
pennylane_maestro-0.6.0-py3-none-any.whl Python 3 none any Details for pennylane_maestro-0.6.0-py3-none-any.whl

Total release size: 109.7 kB

Release files / pennylane_maestro-0.6.0.tar.gz

Download URL pennylane_maestro-0.6.0.tar.gz
Size 64.9 kB
Tags Source
SHA-256 checksum
How to use checksums
fd5514d2957e980d47e3984882de855711844dc10d2992d6eaa097680fe08b25
BLAKE2b-256 checksum
How to use checksums
848e9d9ab96b7715b0094bb6147ed58ecc46531292588ad56279af385de694e2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 26, 2026.

Transparency log

Release files / pennylane_maestro-0.6.0-py3-none-any.whl

Download URL pennylane_maestro-0.6.0-py3-none-any.whl
Size 44.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
b061a447256e02d45df2750675a5e19b2636da4994c933bab08d786770b39a77
BLAKE2b-256 checksum
How to use checksums
9c8566004a448df3c6f076d4c5e550e534c8473a48273ae6addf1b059ed50352
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 26, 2026.

Transparency log

Security for pennylane-maestro 0.6.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for pennylane_maestro-0.6.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 26, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page