pprop.propagator.pruning module¶
Pruning strategies for Heisenberg-picture Pauli propagation.
Overview¶
During Heisenberg evolution a PauliDict can
grow large as gates split each Pauli word into multiple branches. Many of
these branches are guaranteed to contribute zero to the final expectation
value \(\langle 0 | O | 0 \rangle\) and can be discarded early,
before they are evolved further and spawn even more branches.
This submodule provides a small framework for such pruning strategies:
Pruner— abstract base class defining the interface.DeadQubitPruner— removes words that carry anXorYoperator on a qubit that will never be touched by any remaining gate and therefore can never be driven back to the \(Z/I\) subspace required for a non-zero expectation value.XYWeightPruner— removes words whose XY-weight (number of qubits carryingXorY) exceeds the maximum reduction achievable by all remaining gates in the causal cone of that word. Since each gate reduces XY-weight by at most 1, a word with too high a weight simply cannot reach XY-weight 0 before the circuit ends.
All pruners share the same two-phase lifecycle:
Setup (
Pruner.setup()) — called once before the evolution loop with the full list of gates in reversed order. This is where each pruner precomputes any auxiliary data structures it needs (e.g. suffix sets of active qubits).Prune (
Pruner.prune()) — called once per gate step, just before the gate is applied, with the currentPauliDictand the index of the current step. Dead entries are removed in-place.
- class pprop.propagator.pruning.DeadQubitPruner[source]¶
Bases:
PrunerPrune Pauli words that have a frozen
XorYon an inactive qubit.Correctness argument¶
A Pauli word contributes to \(\langle 0 | O | 0 \rangle\) only if every qubit carries either \(Z\) or \(I\) after all remaining gates have been applied (see
to_expectation()). A gate can change the operator on a qubit only if that qubit appears in the gate’s wire list. Therefore, if a qubit currently carriesXorYand no remaining gate (including the gate about to be applied at the current step) touches that qubit, its operator is permanently frozen in the non-\(ZI\) state, the word can never reach zero-bracket and can be safely discarded.- prune(paulidict, step)[source]¶
Delete words with a frozen
XorYfrom paulidict.For each Pauli word, iterates over its non-identity support and checks whether any qubit carrying
XorYis absent from_active_qubits_from[step]. If so, the word is removed.- Parameters:
paulidict (PauliDict) – Observable terms at the current step. Modified in-place.
step (int) – Index of the gate about to be applied.
- Return type:
None
- setup(reversed_gates)[source]¶
Build the suffix-union table of active qubits.
Iterates over
reversed_gatesfrom right to left, accumulating the union of wire sets so that_active_qubits_from[i]contains every qubit touched by gatesi, i+1, …, n-1.- Parameters:
reversed_gates (list[Gate]) – Gates in Heisenberg traversal order (reversed circuit order).
- Return type:
None
- class pprop.propagator.pruning.Pruner[source]¶
Bases:
ABCAbstract base class for Heisenberg-evolution pruning strategies.
A
Prunerencapsulates a single dead-term elimination heuristic. Subclasses must implementsetup()andprune().The contract is:
setup()is called exactly once, before the evolution loop starts, withreversed_gates, the circuit gates in the order they are consumed during Heisenberg propagation (i.e. reversed with respect to the original circuit order). Implementations should use this call to precompute any per-step data they need.prune()is called at the beginning of each loop iteration, before the gate at positionstepis applied. It must remove all entries frompaulidictthat are provably dead, i.e. whose evolved descendants can never contribute to the expectation value, and leave all other entries untouched.
Warning
With prunings, the Pauli words carried in
paulidictduring evolution are not the exact Heisenberg-evolved observable. They are pruned to retain only those terms that can possibly survive theto_expectation()zero-bracket filter (i.e. words composed entirely of \(Z\) and \(I\)).- abstractmethod prune(paulidict, step)[source]¶
Remove provably dead entries from paulidict in-place.
Called at the start of each loop iteration, before the gate at index
stepinreversed_gatesis applied.
- abstractmethod setup(reversed_gates)[source]¶
Precompute auxiliary data for the full gate sequence.
Called once before the evolution loop.
- Parameters:
reversed_gates (list[Gate]) – Gates in the order they will be consumed during Heisenberg evolution (i.e. the original circuit gates reversed).
- Return type:
None
- class pprop.propagator.pruning.XYWeightPruner[source]¶
Bases:
PrunerPrune Pauli words whose XY-weight exceeds the maximum reduction achievable by all remaining gates combined.
Correctness argument¶
A Pauli word can only contribute to the expectation value if its XY-weight reaches zero by the end of evolution. Each gate can reduce the XY-weight by at most
gate.max_xy_reduction(verified to be 1 for all single- and two-qubit gates in this gate set). Therefore if the current XY-weight exceeds the total reduction budget remaining, the word can never reach the \(ZI\) subspace and is pruned.