tensorcircuit.pauliprop¶

Pauli Propagation Engine¶

This module implements a Pauli Propagation Engine (PPE) that tracks the evolution of Pauli observables through a quantum circuit using a global k-local representation.

Key Features:
  • Tracks all k-local Pauli strings globally, avoiding subset boundary issues.

  • Uses precomputed Neighbor Maps for O(1) fiber lookups.

  • JAX JIT and AD compatible.

  • Naturally handles truncation of terms exceeding locality k.

class tensorcircuit.pauliprop.PauliPropagationEngine(N: int, k: int)[source]¶

Bases: object

A Pauli Propagation Engine that tracks observables in the global k-local space.

The state is represented as a flat vector of size $|P_k| = sum_{i=0}^k binom{N}{i} 3^i$.

Note

The engine internally uses single precision (complex64) for state coefficients. Since Pauli propagation is an approximate algorithm, the systematic errors introduced by truncation are typically much larger than the numerical errors from single-precision floating point.

__init__(N: int, k: int) None[source]¶

Initialize the Pauli Propagation Engine.

Parameters:
  • N (int) – Total number of qubits.

  • k (int) – Maximum locality of the Pauli strings to track.

apply_gate(state: Any, gate_name: str, wires: Any, params: Any | None = None) Any[source]¶

Propagate the observable through a quantum gate in the Heisenberg picture. Applies $O to U^dagger O U$.

Parameters:
  • state (Any) – Current observable state vector.

  • gate_name (str) – Name of the gate (e.g., ‘rx’, ‘cnot’).

  • wires (Any) – List of qubit indices the gate acts on.

  • params (Any) – Optional gate parameters.

Returns:

Updated observable state vector.

Return type:

Any

compute_expectation_scan(ham_structures: Any, ham_weights: Any, layer_fn: Callable[[...], None], params_batch: Any, extra_inputs: Sequence[Any] | None = None) Any[source]¶

Compute expectation value with JAX scan optimization for layers.

Parameters:
  • ham_structures (Any) – Hamiltonian structure array.

  • ham_weights (Any) – Hamiltonian weights.

  • layer_fn (Callable) – A function f(circuit, params, *extra_inputs) that defines a circuit layer.

  • params_batch (Any) – Batched parameters for the layers.

  • extra_inputs (Optional[Sequence[Any]]) – Optional static inputs for the layer function.

Returns:

Final expectation value.

Return type:

Any

expectation(state: Any) Any[source]¶

Compute the expectation value $langle 0| O(t) |0 rangle$. Sum coefficients of purely Z observables in the final state.

Parameters:

state (Any) – Propagated observable state vector.

Returns:

Real-valued expectation value.

Return type:

Any

get_initial_state(structures: Any, weights: Any) Any[source]¶

Initialize the state vector in the k-local Hilbert space from Hamiltonian terms.

Parameters:
  • structures (Any) – Hamiltonian structure array of shape [n_terms, n_qubits]. Each entry is 0:I, 1:X, 2:Y, 3:Z.

  • weights (Any) – Coefficients for each Hamiltonian term.

Returns:

Initial state vector of size dim+1 (including sink).

Return type:

Any

# Initialize Z0 + Z1 for a 2-qubit system
engine = PauliPropagationEngine(N=2, k=2)
structures = [[3, 0], [0, 3]]
weights = [1.0, 1.0]
state = engine.get_initial_state(structures, weights)
get_ptm_1q(u: Any) Any[source]¶
get_ptm_2q(u: Any) Any[source]¶
string_to_code(s: Tuple[Tuple[int, ...], Tuple[int, ...]]) int[source]¶

Convert a Pauli string representation ((qidx, …), (opcode, …)) to its index in the basis.

Parameters:

s (Tuple[Tuple[int, ...], Tuple[int, ...]]) – Pauli string as ((qidx, …), (opcode, …)).

Returns:

Index in the basis.

Return type:

int

class tensorcircuit.pauliprop.SparsePauliPropagationEngine(N: int, k: int, buffer_size: int = 2000)[source]¶

Bases: object

A Truly Sparse Pauli Propagation Engine that tracks Pauli strings using bitpacked integers. No combinatorial basis is precomputed, making it suitable for hundreds of qubits.

Note

The engine internally uses single precision (complex64) for coefficients, but utilizes int64 bit-packing for encoding Pauli strings. It is highly recommended to call tc.set_dtype("complex128") when using this engine. While the coefficients remain single-precision, this setting ensures the backend (especially JAX) enables native 64-bit support, which is essential for the int64 encoding and bitwise operations used in the large-scale simulation.

Note

The numerical precision error of complex64 is significantly smaller than the systematic approximation error from buffer truncation, making double-precision coefficients practically unnecessary for this engine.

__init__(N: int, k: int, buffer_size: int = 2000) None[source]¶
apply_gate(state: Any, gate_name: str, wires: Any, params: Any | None = None) Any[source]¶

Propagate the observable through a quantum gate in the Heisenberg picture. Applies $O to U^dagger O U$.

Parameters:
  • state (Any) – Current sparse observable state (codes, weights).

  • gate_name (str) – Name of the gate.

  • wires (Any) – List of qubit indices the gate acts on.

  • params (Any) – Optional gate parameters.

Returns:

Updated sparse observable state (codes, weights).

Return type:

Any

compute_expectation_scan(structures: Any, weights: Any, layer: Callable[[Any, Any], None], params: Any) Any[source]¶

Compute expectation value with JAX scan optimization for layers.

Parameters:
  • structures (Any) – Initial Hamiltonian structures.

  • weights (Any) – Initial Hamiltonian weights.

  • layer (Callable) – A function f(circuit, params) that defines a circuit layer.

  • params (Any) – Batched parameters for the layers.

Returns:

Final expectation value.

Return type:

Any

expectation(state: Any) Any[source]¶

Compute the expectation value $langle 0| O(t) |0 rangle$. Sum coefficients of purely Z observables in the final state.

Parameters:

state (Any) – Propagated sparse observable state (codes, weights).

Returns:

Real-valued expectation value.

Return type:

Any

get_initial_state(structures: Any, weights: Any) Any[source]¶

Initialize the sparse state (codes, coefficients).

Parameters:
  • structures (Any) – Hamiltonian structures [n_terms, n_qubits].

  • weights (Any) – Hamiltonian coefficients.

Returns:

A tuple of (codes, weights) representing the sparse state.

Return type:

Any

# Initialize Z0 + Z1 for a 100-qubit system
engine = SparsePauliPropagationEngine(N=100, k=2, buffer_size=1000)
structures = np.zeros((2, 100), dtype=int)
structures[0, 0] = 3 # Z
structures[1, 1] = 3 # Z
weights = [1.0, 1.0]
state = engine.get_initial_state(structures, weights)
string_to_code(s: Tuple[Tuple[int, ...], Tuple[int, ...]]) Any[source]¶

Convert a Pauli string representation ((qidx, …), (opcode, …)) to its bit-packed int64 representation.

Parameters:

s (Tuple[Tuple[int, ...], Tuple[int, ...]]) – Pauli string as ((qidx, …), (opcode, …)).

Returns:

Bit-packed int64 tensor.

Return type:

Any

tensorcircuit.pauliprop.pauli_propagation(c: Circuit, observable: Any, weights: Any | None = None, k: int = 3) Any[source]¶

High-level API for Heisenberg-picture Pauli propagation.

Parameters:
  • c (Circuit) – The quantum circuit to propagate through.

  • observable (Any) – The initial observable. Can be: 1. A list of (coeff, pauli_string) pairs, e.g., [(1.0, "ZZ"), (-0.5, "XI")]. 2. A structure array if weights is also provided.

  • weights (Optional[Any]) – Optional weights if observable is a structure array.

  • k (int) – Maximum locality to track. Defaults to 3.

Returns:

Real-valued expectation value $langle 0 | U^dagger O U | 0 rangle$.

Return type:

Any

import tensorcircuit as tc
from tensorcircuit.pauliprop import pauli_propagation

c = tc.Circuit(2)
c.h(0)
c.cnot(0, 1)

# Compute expectation of Z0 + Z1
obs = [(1.0, "ZI"), (1.0, "IZ")]
energy = pauli_propagation(c, obs, k=2)