Core Concepts

This section explains the theoretical foundations of pyrauli. Understanding these concepts will help you use the library more effectively and decide when it is the right tool for your problem.

The Heisenberg Picture vs. The Schrödinger Picture

Most quantum circuit simulators operate in the Schrödinger picture. In this view, the quantum state (a state vector \(|\psi\rangle\) of size \(2^N\)) evolves in time, while the operators (like observables \(O\)) are typically fixed.

  • Schrödinger Picture: State evolves, operator is fixed. - State evolution: \(|\psi(t)\rangle = U(t) |\psi(0)\rangle\) - Expectation value: \(\langle \psi(t) | O | \psi(t) \rangle\)

pyrauli operates in the Heisenberg picture. Here, the roles are reversed: the state is considered fixed at its initial value (\(|\psi(0)\rangle\)), and the operators evolve instead.

  • Heisenberg Picture: State is fixed, operator evolves. - Operator evolution: \(O(t) = U^\dagger(t) O U(t)\) - Expectation value: \(\langle \psi(0) | O(t) | \psi(0) \rangle\)

The two pictures are mathematically equivalent, but they can have vastly different computational costs. pyrauli is optimized for the common case where the initial state is \(|0\rangle^{\otimes N}\), making the Heisenberg calculation particularly efficient.

Pauli Back-Propagation

pyrauli implements the Heisenberg evolution using a technique called Pauli back-propagation.

An observable \(O\) is represented as a linear combination of Pauli strings (e.g., \(c_1 IXYZ + c_2 ZIZI + \dots\)). A circuit \(U\) is a sequence of gates \(U = G_k \dots G_2 G_1\).

The evolved observable is \(O' = U^\dagger O U = G_1^\dagger G_2^\dagger \dots G_k^\dagger O G_k \dots G_2 G_1\).

pyrauli calculates this by starting with the initial observable \(O\) and successively applying the conjugation for each gate in reverse order (hence “back-propagation”):

  1. Start with \(O_k = O\).

  2. Compute \(O_{k-1} = G_k^\dagger O_k G_k\).

  3. Compute \(O_{k-2} = G_{k-1}^\dagger O_{k-1} G_{k-1}\).

  4. …and so on, until…

  5. The final observable is \(O_0 = G_1^\dagger O_1 G_1\).

The key insight is that if \(O_i\) is a Pauli string, then for many common quantum gates \(G_{i+1}\), the result \(G_{i+1}^\dagger O_i G_{i+1}\) is also a simple combination of one or two Pauli strings. pyrauli’s C++ backend, ProPauli, is highly optimized to perform these Pauli algebra transformations very quickly.

Without any merge or truncation, the number terms in the observable can grow exponentially with the number of Rz gates, which is why complexity management via truncators is an important feature.

The pyrauli + ProPauli Architecture

The library is composed of two distinct parts that work together:

  • pyrauli (Python Frontend): This is the library you interact with. It provides the user-facing API (Circuit, Observable), handles integration with the Python ecosystem (e.g., Qiskit), and orchestrates the simulation logic.

  • ProPauli (C++ Backend): This is the high-performance engine. It implements the core data structures for representing Pauli strings efficiently, contains the optimized C++ routines for applying gate conjugations, and manages the computationally intensive work.

This separation of concerns allows pyrauli to offer both a high-level, easy-to-use Python interface and the raw performance of compiled C++ code.