Getting Started
Install monoprop and run a minimal operator-propagation example.
This page covers the installation and a minimal introduction to monoprop.
For more detailed usage instructions, see the Python API.
Installation
Install monoprop from PyPI using pip:
pip install monopropIf you use uv as package manager:
uv add monopropThese prebuilt wheels run on a single process — they are published without MPI. For a multi-rank / MPI-enabled build, or to build the C++ library and executables, build from source as described in Building from source.
Introduction
monoprop is a classical quantum-circuit simulator based on operator propagation: rather than storing the full quantum state, it expands an operator in the Majorana (or Pauli) basis and propagates it through a circuit, dropping terms that contribute little. Expectation values and gradients are then read off the propagated operator.
The Concepts section explains how the method works — the basis and notation, the propagation algorithm, and the Heisenberg and Schrödinger simulation modes.
Minimal Example
The following example back-propagates the Majorana observable through a single Majorana rotation generated by . Because the generator anticommutes with the observable, the gate splits it into a cosine branch (the original monomial, scaled by ) and a sine branch (the new monomial , scaled by ); see Propagation Algorithm:
import numpy as np
from monoprop import MajoranaPropagator, ExpGate, Circuit, MajoranaOperator
observable = MajoranaOperator({(0, 1, 2, 4): 1.0}, 8)
# A gate generator carries the Hermitian operator, the same convention as an observable:
# M_γ = i·m_4 m_5 is a length-2 monomial, so its coefficient is imaginary. monoprop
# divides out the Hermitian phase i for you when the circuit is ingested.
gate = ExpGate(MajoranaOperator({(4, 5): 1j}, num_modes=8)) # exp(-i θ/2 · M_γ), M_γ = i·m_4 m_5
circuit = Circuit(gates=[gate], parameters=[0.5])
mbs = MajoranaPropagator.from_circuit(circuit, observable, cutoff=16)
result = mbs.evolved_operator()
assert sorted(result) == [(0, 1, 2, 4), (0, 1, 2, 5)] # original (cosine) term plus the new (sine) term
assert np.isclose(result[(0, 1, 2, 4)].real, np.cos(2 * 0.5)) # cosine branchFor qubit problems there is a dedicated simulator, PauliPropagator, which takes
Pauli operators and gates directly. The following example back-propagates the
two-qubit observable under a single qubit rotation
, which likewise splits it into a cosine and a sine branch.
Its results are keyed by Majorana indices (see Notation):
import numpy as np
from monoprop import PauliPropagator, ExpGate, Circuit, PauliOperator, Pauli
observable = PauliOperator({"ZZ": 1.0}, num_qubits=2)
gate = ExpGate(PauliOperator({Pauli("X", 0): 1.0}, num_qubits=2)) # exp(-i θ/2 · X_0)
circuit = Circuit(gates=[gate], parameters=[0.5])
mbs = PauliPropagator.from_circuit(circuit, observable, cutoff=16)
result = mbs.evolved_operator() # keys are Majorana indices
assert sorted(result) == [(0, 1, 2, 3), (1, 2, 3)]
assert np.isclose(result[(0, 1, 2, 3)].real, -np.cos(2 * 0.5)) # cosine branch