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 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 m0m1m2m4m_0 m_1 m_2 m_4 through a single Majorana rotation eiθMγ/2e^{-i\theta\, M_\gamma/2} generated by Mγ=im4m5M_\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); 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 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 ZZZ \otimes Z under a single qubit rotation eiθX0/2e^{-i\theta X_0/2}, 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

On this page