monoprop

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 monoprop

If you use uv as package manager:

uv add monoprop

These prebuilt wheels include an MPI backend for the ABIs of the MPICH family and Open MPI. To run across ranks, install mpi4py with the mpi extra and pass a communicator to a propagator, as described in Parallelism. 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 m0m1m2m4m_0 m_1 m_2 m_4 through a single Majorana rotation e+iθMγe^{+i\theta M_\gamma} generated by Mγ=i m4m5M_\gamma = i\, m_4 m_5. Because the generator anticommutes with the observable, the gate splits it into a cosine branch (the original monomial, scaled by cos⁡θ\cos\theta) and a sine branch (the new monomial MγMνM_\gamma M_\nu, scaled by isin⁡θi\sin\theta, where Mν=m0m1m2m4M_\nu = m_0 m_1 m_2 m_4 is the observable); see Propagation Algorithm:

import numpy as np
from monoprop import MajoranaPropagator, ExpGate, Circuit, MajoranaOperator

observable = MajoranaOperator({(0, 1, 2, 4): 1.0}, num_modes=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 θ · M_γ), M_γ = i·m_4 m_5
circuit = Circuit(gates=[gate], parameters=[0.5], system_size=8)
mbs = MajoranaPropagator.from_circuit(circuit, observable, cutoff=16)
result = mbs.evolved_operator()

assert sorted(result.terms) == [(0, 1, 2, 4), (0, 1, 2, 5)]  # original (cosine) term plus the new (sine) term
assert np.isclose(result.terms[(0, 1, 2, 4)].real, np.cos(2 * 0.5))  # cosine branch

For qubit problems there is a dedicated simulator, PauliPropagator, which takes Pauli operators and gates directly. The following example back-propagates the two-qubit observable Z⊗ZZ \otimes Z under a single qubit rotation e+iθX0e^{+i\theta X_0}, which likewise splits it into a cosine and a sine branch. Its result is a PauliOperator whose .terms map each Pauli to its coefficient (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 θ · X_0)
circuit = Circuit(gates=[gate], parameters=[0.5], system_size=2)
pp = PauliPropagator.from_circuit(circuit, observable, cutoff=16)
result = pp.evolved_operator()  # a PauliOperator; .terms maps Pauli -> coefficient

# Back-propagating Z⊗Z through the X_0 rotation splits it into a cosine and a sine branch.
coeffs = {pauli.string: coeff.real for pauli, coeff in result.terms.items()}
assert np.isclose(coeffs["ZZ"], np.cos(2 * 0.5))   # cosine branch (unrotated term)
assert np.isclose(coeffs["YZ"], -np.sin(2 * 0.5))  # sine branch (rotated term)

On this page