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:
ExecutorBaseExecutor 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
Native abstraction¶
Circuit¶
- class qc_executor.pauli_propagation.PauliPropagationCircuit(num_qubits=0, num_clbits=0, *, gates=None, _ir=None)[source]¶
Bases:
QuantumCircuitBaseBackend-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 byreplace_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).
- replace_gate(index, gate)[source]¶
Return a copy with the gate at
indexreplaced.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:
- Returns:
A new circuit; this one is untouched.
- Raises:
IndexError – If
indexis 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 byParameteror by name.- Return type:
- Returns:
A new bound circuit; this one is untouched.
- 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)
- 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:
QuantumOperatorBaseBackend-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:
operator (QuantumOperatorBase)
symmetry_strategy (SymmetryStrategy | None)
- Return type:
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.
- assign_parameters(parameters)[source]¶
Bind symbolic parameters to concrete values.
- Parameters:
parameters (
Dict[str,float]) – Dict mapping parameter names to float values- Return type:
- Returns:
New operator with parameters substituted
- 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 qubiti. 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:
- 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:
- Returns:
The composed operator.
- Raises:
TypeError – If
otheris 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:
- 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.0drops exact zeros only.- Return type:
- 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
PauliSumrather than in aPauliIR, 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:
ABCAbstract 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:
SymmetryStrategyIdentity 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 integernqubits (
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:
SymmetryStrategyQubit 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:
SymmetryStrategyCompose 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 + …)’