pauli_propagation

Dependencies

Included in the core package — no additional dependencies required.

Executor

class qc_executor.pauli_propagation.PauliPropagationExecutor(shots=None, seed=None, log_file=None, log_level='WARNING', caching=None, cache_dir='cache', max_cache_size=4096, truncate_threshold=None, max_weight=None, symmetry_strategy=None, n_jobs=1)[source]

Bases: ExecutorBase

Executor for quantum circuits using Pauli propagation (Heisenberg picture).

This executor propagates observables backward through quantum circuits, enabling efficient computation of expectation values for sparse observables.

Parameters:
  • shots (int | None) – Number of measurement shots (not used for exact simulation)

  • seed (int | None) – Random seed for reproducibility (not used for exact simulation)

  • log_file (str | None) – Path to log file (not implemented)

  • caching (bool | None) – Whether to use caching (not implemented yet)

  • cache_dir (str) – Directory for caching (not implemented yet)

  • max_cache_size (int | None) – Maximum number of entries kept in each in-memory cache (None = unbounded)

  • truncate_threshold (float | None) – Coefficient threshold for automatic truncation (None = no truncation)

  • max_weight (int | None) – Maximum Pauli weight for truncation (None = no weight limit)

  • symmetry_strategy (SymmetryStrategy | None) – Strategy for Pauli symmetry merging (None = no merging)

  • n_jobs (int) – Number of worker processes for independent expectation-value evaluations (multiple circuits x observables, parameter-shift gradients). 1 (default) runs serially; -1 uses all CPU cores. Process startup on Windows (spawn) costs roughly 100 ms per worker plus import time, so parallelism pays off only for workloads with many or expensive independent evaluations.

  • log_level (str)

property shots: int | None

Return the number of shots.

property remote: bool

Return False (Pauli propagation is local execution).

get_truncation_stats()[source]

Get statistics from last truncation operation.

Return type:

TruncationStats | None

Returns:

TruncationStats from most recent execution, or None if no truncation

classmethod get_accepted_backend_types()[source]

Return all object types accepted as backend in factory auto-detection.

PauliPropagationExecutor does not accept backend objects during initialization.

Return type:

List[type]

classmethod get_accepted_backend_aliases()[source]

Return string aliases accepted by this executor in Executor.create.

Return type:

List[str]

Native abstraction

Circuit

class qc_executor.pauli_propagation.PauliPropagationCircuit(num_qubits=0, num_clbits=0, *, gates=None, _ir=None)[source]

Bases: QuantumCircuitBase

Backend-native circuit representation for Pauli propagation.

Instructions are recorded in the shared circuit IR and compiled into the engine’s flat gate list on demand. Building the gate list from the IR is what removed this backend’s dependency on Qiskit for circuit conversion.

Parameters:
  • num_qubits (int) – Number of qubits in the circuit.

  • num_clbits (int) – Number of classical bits (unused; measurement is not representable in the Heisenberg picture).

  • gates (Optional[Sequence[Gate | LayerBarrier]]) – Adopt this gate list directly instead of compiling one. Used by replace_gate() and by callers holding engine gates already.

  • _ir (CircuitIR | None) – Adopt this instruction store instead of starting empty.

classmethod supported_opcodes()[source]

Return the opcodes the propagation engine executes directly.

Return type:

FrozenSet[OpCode]

property gates: List[Gate | LayerBarrier]

Return a shallow copy of the engine gate instructions.

property parameter_symbols: Dict[str, Parameter]

Return the parameters keyed by name, e.g. {"theta[0]": theta[0]}.

property parameter_names: List[str]

Return the parameter names used by the circuit.

property parameters: List[Parameter]

Return the free parameters, sorted by (vector_name, index).

draw()[source]

Return a plain-text listing of the engine gate sequence.

Return type:

str

replace_gate(index, gate)[source]

Return a copy with the gate at index replaced.

Used by the parameter-shift rule, which needs one rotation’s angle displaced while everything else stays put. The result carries an explicit gate list, because a shifted gate need not correspond to any instruction in the original store.

Parameters:
  • index (int) – Position of the gate to replace.

  • gate (Gate | LayerBarrier) – The replacement.

Return type:

PauliPropagationCircuit

Returns:

A new circuit; this one is untouched.

Raises:

IndexError – If index is out of range.

assign_parameters(parameters)[source]

Return a copy with values substituted for parameters.

Only the adopted-gate-list case needs this override; a circuit backed by the instruction store binds through the base implementation.

Parameters:

parameters (Dict[Any, float]) – Values keyed by Parameter or by name.

Return type:

PauliPropagationCircuit

Returns:

A new bound circuit; this one is untouched.

copy()[source]

Return an independent copy of this circuit.

Return type:

PauliPropagationCircuit

measure(qubits=None, clbits=None)[source]

Measurement is not representable in the Heisenberg picture.

from_qasm(qasm)[source]

QASM import is not supported for this datatype.

Return type:

None

Parameters:

qasm (str)

to_qasm()[source]

QASM export is not supported for this datatype.

Return type:

str

circuit_metrics()[source]

Return gate counts and depth for the compiled gate list.

Return type:

dict

property num_parameters: int

Number of free parameters in the circuit.

property is_parameterized: bool

Whether the circuit has any free parameter.

Operator

class qc_executor.pauli_propagation.PauliPropagationOperator(paulis=None, coeffs=None, num_qubits=None, pauli_sum=None, symmetry_strategy=None, *, parametric_coeffs=None, parameter_symbols=None)[source]

Bases: QuantumOperatorBase

Backend-native operator representation for Pauli propagation.

Parameters:
  • paulis (List[str] | None)

  • coeffs (List[complex | 'sp.Expr'] | None)

  • num_qubits (int | None)

  • pauli_sum (PauliSum | None)

  • symmetry_strategy (SymmetryStrategy | None)

  • parametric_coeffs (Dict[int, sp.Expr] | None)

  • parameter_symbols (Dict[str, sp.Symbol] | None)

classmethod from_quantum_operator(operator, symmetry_strategy=None)[source]
Overloads:
  • cls, operator (QuantumOperatorBase) → PauliPropagationOperator

  • cls, operator (QuantumOperatorBase), symmetry_strategy (SymmetryStrategy) → PauliPropagationOperator

Parameters:
Return type:

PauliPropagationOperator

Create a PauliPropagationOperator from a generic operator.

property pauli_sum: PauliSum

Return a copy of the underlying PauliSum.

property symmetry

Return the symmetry strategy of the underlying PauliSum.

property has_active_symmetry: bool

Return True if a non-trivial symmetry strategy is active.

property num_qubits: int

Number of qubits the operator acts on.

property num_paulis: int

Number of Pauli terms.

property paulis: List[str]

The Pauli labels, qubit 0 leftmost.

property coeffs: List[complex]

The coefficients, symbolic entries kept as SymPy expressions.

property is_parametrized: bool

Whether any coefficient is symbolic.

property parameters: List[str]

The free parameters, sorted by (vector_name, index).

property parameter_symbols: Dict[str, Symbol]

Return a copy of the parameter-name to sympy-symbol mapping.

property parametric_coeffs: Dict[int, Expr]

Return a copy of the term to symbolic-coefficient mapping.

property num_parameters: int

Number of free parameters.

copy()[source]

Return a deep copy of this operator.

Return type:

PauliPropagationOperator

assign_parameters(parameters)[source]

Bind symbolic parameters to concrete values.

Parameters:

parameters (Dict[str, float]) – Dict mapping parameter names to float values

Return type:

PauliPropagationOperator

Returns:

New operator with parameters substituted

adjoint()[source]

Return the adjoint of the operator.

Return type:

PauliPropagationOperator

apply_layout(layout, num_qubits=None)[source]

Move each qubit onto a new position.

Parameters:
  • layout (Union[Sequence[int], Mapping[int, int]]) – layout[i] is the target position of source qubit i. A mapping is also accepted, in which case qubits it omits stay where they are.

  • num_qubits (int | None) – Accepted for interface parity; this backend keeps its own width.

Return type:

PauliPropagationOperator

Returns:

The relocated operator.

compose(other)[source]

Return the product with another operator.

This is pure: neither operand is modified.

Parameters:

other (QuantumOperatorBase) – The right-hand operator.

Return type:

PauliPropagationOperator

Returns:

The composed operator.

Raises:

TypeError – If other is not a quantum operator.

append(pauli, coeff=None)[source]

Return this operator with one more Pauli term.

Parameters:
  • pauli (str) – The Pauli label to add.

  • coeff – Its coefficient, defaulting to 1.

Return type:

PauliPropagationOperator

Returns:

The extended operator.

simplify()[source]

Combine duplicate terms and drop those with zero coefficient.

Parameters:

atol – Terms whose combined coefficient is smaller than this are dropped; 0.0 drops exact zeros only.

Return type:

PauliPropagationOperator

transpose()[source]

Return the transpose of the operator.

Return type:

PauliPropagationOperator

conjugate()[source]

Return the complex conjugate of the operator.

Return type:

PauliPropagationOperator

group_commuting()[source]

Split the operator into groups of mutually commuting terms.

Parameters:

qubit_wise – Require commutation on every qubit individually.

Return type:

List[PauliPropagationOperator]

property is_unitary: bool

Whether the operator is unitary.

Raises:

NotImplementedError – If any coefficient is symbolic.

property is_real: bool

Whether every numeric coefficient is real.

property is_imaginary: bool

Whether every numeric coefficient is purely imaginary.

property ir: PauliIR

The sparse Pauli representation, materialised from the term store.

This backend keeps its terms in a bit-packed PauliSum rather than in a PauliIR, so the base’s store is a placeholder and this builds the real thing on demand.

fingerprint()[source]

Return a stable digest of the operator’s content.

Derived from the term store, not the base’s placeholder: inheriting that gave every operator of the same width the same fingerprint.

Return type:

bytes

property is_hermitian: bool

Whether every numeric coefficient is real.

Other classes

class qc_executor.pauli_propagation.SymmetryStrategy[source]

Bases: ABC

Abstract base class for symmetry strategies.

Symmetry strategies define how Pauli terms are grouped into equivalence classes. Each strategy must implement canonical_representative() to map a Pauli term to its canonical form.

Canonical Representative:

A unique representative element from each equivalence class under the symmetry transformation. Terms with the same canonical representative are considered equivalent and can be merged (coefficients summed).

Example

For qubit permutation symmetry, the strings XXY, XYX, YXX all have the same multiset {X, X, Y} and should map to the same canonical form.

Implementation Notes:
  • Pauli terms are encoded as integers (2 bits per qubit)

  • Encoding: I=00, X=01, Y=10, Z=11 (little-endian)

  • canonical_representative() must be deterministic

  • canonical_representative() should be fast (called many times)

abstractmethod canonical_representative(term, nqubits)[source]

Compute canonical representative of a Pauli term.

Maps a Pauli term to its canonical form under this symmetry. All terms in the same equivalence class must map to the same canonical.

Parameters:
  • term (int) – Pauli term encoded as integer (2 bits per qubit, little-endian)

  • nqubits (int) – Number of qubits

Return type:

int

Returns:

Canonical representative (integer, same encoding as input)

abstract property name: str

Return human-readable name of this symmetry strategy.

Used for debugging, logging, and statistics tracking.

class qc_executor.pauli_propagation.NoSymmetry[source]

Bases: SymmetryStrategy

Identity symmetry strategy (no merging).

This strategy performs no merging - each Pauli term is its own canonical representative. Used as the default symmetry when no specific symmetry is requested.

Useful for:
  • Backward compatibility (default behavior)

  • Baseline comparisons (measuring impact of symmetry merging)

  • Debugging (disable merging without changing code structure)

Performance:
  • O(1) per canonical call (identity function)

  • Zero computational overhead

canonical_representative(term, nqubits)[source]

Return term unchanged (identity function).

Parameters:
  • term (int) – Pauli term encoded as integer

  • nqubits (int) – Number of qubits (unused)

Return type:

int

Returns:

The input term unchanged

property name: str

Return ‘no_symmetry’ as identifier.

class qc_executor.pauli_propagation.PermutationSymmetry[source]

Bases: SymmetryStrategy

Qubit permutation symmetry (S_n) using sorted multiset canonical form.

Groups Pauli terms that differ only by qubit permutations. The canonical representative is the term with Paulis sorted lexicographically (I < X < Y < Z).

Mathematical Background:

Two Pauli strings P and Q are in the same orbit under S_n (symmetric group of n elements) if they have the same multiset of local Pauli operators.

Example: XXY, XYX, YXX all have multiset {X, X, Y} → same orbit

Implementation:

Uses O(n) bit manipulation without lookup tables. Canonical form is computed by counting occurrences of each Pauli type and reconstructing in sorted order.

Algorithm Complexity:

Time: O(n) per canonical computation (one pass to count, one to reconstruct) Space: O(1) (fixed-size counter array [4])

Scales efficiently to 100+ qubits.

Example

>>> sym = PermutationSymmetry()
>>> # Encode XXY, XYX, YXX at 3 qubits
>>> term1 = 0x05  # XXY binary: 010001
>>> term2 = 0x09  # XYX binary: 100001
>>> term3 = 0x06  # YXX binary: 000110
>>> sym.canonical_representative(term1, 3)  # All map to same value
>>> sym.canonical_representative(term2, 3)
>>> sym.canonical_representative(term3, 3)
canonical_representative(term, nqubits)[source]

Compute canonical Pauli term under qubit permutations.

The canonical form is reconstructed from the multiset of local Pauli symbols in sorted order I < X < Y < Z.

Uses whole-word popcounts to count Pauli types and repeated-pattern arithmetic to reconstruct the sorted form, so the cost is O(1) word operations instead of a per-qubit Python loop.

Parameters:
  • term (int) – Pauli term encoded as integer (2 bits per qubit, little-endian).

  • nqubits (int) – Number of qubits.

Return type:

int

Returns:

Canonical representative as integer.

property name: str

Return ‘permutation’ as identifier.

class qc_executor.pauli_propagation.CompositeSymmetry(*strategies)[source]

Bases: SymmetryStrategy

Compose multiple symmetry strategies.

Applies multiple symmetry strategies sequentially to compute the final canonical representative. The result of strategy i becomes the input to strategy i+1.

Use Cases:
  • Combine multiple independent symmetries (e.g., permutation + point group)

  • Layer symmetries with different granularities

  • Experimental symmetry compositions for research

Implementation:

Strategies are applied in the order they are provided to __init__(). For commuting symmetries, order doesn’t matter. For non-commuting symmetries, different orders may give different canonical forms.

Performance:

Time: Sum of individual strategy times Space: O(1) beyond storage of strategy references

Example

>>> perm_sym = PermutationSymmetry()
>>> point_group_sym = PointGroupSymmetry()  # hypothetical
>>> composite = CompositeSymmetry(perm_sym, point_group_sym)
>>>
>>> # Applies permutation symmetry first, then point group
>>> canonical = composite.canonical_representative(term, nqubits)

Notes

  • Empty CompositeSymmetry (no strategies) acts as NoSymmetry

  • Single strategy behaves identically to using that strategy alone

  • Ordering matters only if symmetries don’t commute

Parameters:

strategies (SymmetryStrategy)

canonical_representative(term, nqubits)[source]

Apply all configured strategies sequentially.

Parameters:
  • term (int) – Pauli term encoded as integer.

  • nqubits (int) – Number of qubits.

Return type:

int

Returns:

Canonical representative after all strategies are applied.

property name: str

Return composite name listing all strategies.

Format: ‘composite(strategy1 + strategy2 + …)’