---
title: propagation (latest version)
description: API reference for pauli_prop.propagation in the latest version of pauli-prop
source: https://quantum.cloud.ibm.com/docs/en/api/pauli-prop/propagation
---

# Propagation

`pauli_prop.propagation`

Functions for performing Pauli propagation.

### RotationGates

*class* `RotationGates(gates, qargs, thetas)`

[GitHub](https://github.com/Qiskit/pauli-prop/tree/main/pauli_prop/propagation.py#L141-L209)

Bases: [`NamedTuple`](https://docs.python.org/3/library/typing.html#typing.NamedTuple)

An intermediate minimal representation of a `QuantumCircuit`.

Supported Pauli rotations: rx/rxx, ry/ryy, rz/rzz, PauliEvolutionGate

Create new instance of RotationGates(gates, qargs, thetas)

**Parameters**

- **gates** ([*list*](https://docs.python.org/3/library/stdtypes.html#list)*\[*[*ndarray*](https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html#numpy.ndarray)*\[*[*tuple*](https://docs.python.org/3/library/stdtypes.html#tuple)*\[*[*int*](https://docs.python.org/3/library/functions.html#int)*, ...],* [*dtype*](https://numpy.org/doc/stable/reference/generated/numpy.dtype.html#numpy.dtype)*\[*[*bool*](https://numpy.org/doc/stable/reference/arrays.scalars.html#numpy.bool)*]]]*)
- **qargs** ([*list*](https://docs.python.org/3/library/stdtypes.html#list)*\[*[*list*](https://docs.python.org/3/library/stdtypes.html#list)*\[*[*int*](https://docs.python.org/3/library/functions.html#int)*]]*)
- **thetas** ([*list*](https://docs.python.org/3/library/stdtypes.html#list)*\[*[*float*](https://docs.python.org/3/library/functions.html#float)*]*)

#### append\_circuit\_instruction

`append_circuit_instruction(inst, qargs, num_qubits, *, clifford=None)`

[GitHub](https://github.com/Qiskit/pauli-prop/tree/main/pauli_prop/propagation.py#L154-L209)

Parses a circuit instruction and appends its data to the internal lists.

**Parameters**

- **inst** ([*CircuitInstruction*](/docs/api/qiskit/qiskit.circuit.CircuitInstruction)) – The circuit instruction to parse and append
- **qargs** ([*list*](https://docs.python.org/3/library/stdtypes.html#list)*\[*[*int*](https://docs.python.org/3/library/functions.html#int)*]*) – The list of qubit indices of the instruction in the context of its circuit
- **num\_qubits** ([*int*](https://docs.python.org/3/library/functions.html#int)) – The number of qubits of the circuit containing this instruction
- **clifford** ([*Clifford*](/docs/api/qiskit/qiskit.quantum_info.Clifford) *| None*) – An optional Clifford through which the provided instruction should be moved. The Clifford must act on all qubits in the circuit.

**Raises**

- [**ValueError**](https://docs.python.org/3/library/exceptions.html#ValueError) – Unsupported gate encountered in circuit
- [**ValueError**](https://docs.python.org/3/library/exceptions.html#ValueError) – If given, `clifford` must act on all qubits in circuit

**Return type**

None

#### count

`count(value, /)`

Return number of occurrences of value.

#### gates

Type: [`list`](https://docs.python.org/3/library/stdtypes.html#list)\[[`ndarray`](https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html#numpy.ndarray)\[[`tuple`](https://docs.python.org/3/library/stdtypes.html#tuple)\[[`int`](https://docs.python.org/3/library/functions.html#int), ...], [`dtype`](https://numpy.org/doc/stable/reference/generated/numpy.dtype.html#numpy.dtype)\[[`bool`](https://numpy.org/doc/stable/reference/arrays.scalars.html#numpy.bool)]]]

A ZX-calculus-like representation of the gates.

#### index

`index(value, start=0, stop=9223372036854775807, /)`

Return first index of value.

Raises ValueError if the value is not present.

#### qargs

Type: [`list`](https://docs.python.org/3/library/stdtypes.html#list)\[[`list`](https://docs.python.org/3/library/stdtypes.html#list)\[[`int`](https://docs.python.org/3/library/functions.html#int)]]

The qubit indices acted upon by each gate.

#### thetas

Type: [`list`](https://docs.python.org/3/library/stdtypes.html#list)\[[`float`](https://docs.python.org/3/library/functions.html#float)]

The rotation angles of all gates.

### circuit\_to\_rotation\_gates

`circuit_to_rotation_gates(circuit)`

[GitHub](https://github.com/Qiskit/pauli-prop/tree/main/pauli_prop/propagation.py#L339-L379)

Converts the provided circuit to an intermediate representation.

Supports Pauli rotation gates (‘rx/rxx’, ‘ry/ryy’, ‘rz/rzz’, ‘PauliEvolutionGate’) and Pauli-Lindblad error channels, specified as [PauliLindbladError](https://qiskit.github.io/qiskit-aer/stubs/qiskit_aer.noise.PauliLindbladError.html#qiskit_aer.noise.PauliLindbladError) instructions.

**Parameters**

**circuit** ([*QuantumCircuit*](/docs/api/qiskit/qiskit.circuit.QuantumCircuit)) –

The circuit to convert. May contain Pauli rotations (rx/rxx, ry/ryy, rz/rzz, PauliEvolutionGate) and optionally [PauliLindbladError](https://qiskit.github.io/qiskit-aer/stubs/qiskit_aer.noise.PauliLindbladError.html#qiskit_aer.noise.PauliLindbladError) instructions.

**Returns**

A [`RotationGates`](#pauli_prop.propagation.RotationGates "pauli_prop.propagation.RotationGates") instance if the circuit does not contain `PauliLindbladError` instructions; otherwise, a `NoisyRotationGates` instance is returned.

**Raises**

[**ValueError**](https://docs.python.org/3/library/exceptions.html#ValueError) – when an unsupported gate is encountered in `circuit`.

**Return type**

[*RotationGates*](#pauli_prop.propagation.RotationGates "pauli_prop.propagation.RotationGates") | *NoisyRotationGates*

### propagate\_through\_rotation\_gates

`propagate_through_rotation_gates(operator, rot_gates, max_terms, atol, frame)`

[GitHub](https://github.com/Qiskit/pauli-prop/tree/main/pauli_prop/propagation.py#L382-L494)

Propagate a sparse Pauli operator, $O$, through a circuit (represented in `rot_gates`), $U$.

For Schrödinger propagation: $U O U^{\dagger}$. For Heisenberg propagation: $U^{\dagger} O U$.

If `rot_gates` is a `NoisyRotationGates` instance, the operator will be propagated through each noise generator. The coefficient associated with each term in `operator`, $c_i$, will be damped according to each anti-commuting error generator’s rate, $r_i$: $c_i *= exp(-2.0 * r_i)$.

In general, the memory and time required for propagating through a circuit grows exponentially with the number of operations in the circuit due to the exponential growth in the number of terms of the operator in the Pauli basis. To regulate this exponential difficulty, one may truncate small Pauli terms (i.e. set them to zero), resulting in a bias proportional to the magnitudes of the truncated terms. After propagating through each operation in the circuit, terms are truncated with respect to two parameters:

- Only the `max_terms` largest Pauli components are kept; any smaller terms will be truncated. This option makes it possible to estimate in advance how much time and memory will suffice for the computation.
- Terms with magnitudes less than `atol` are truncated (set to zero).

> **Note**
>
> This function pre-allocates space in memory for the full-sized operator and operator buffer. It is the caller’s responsibility to ensure they have enough memory to hold operators containing `max_terms` terms. When `max_terms` is `None`, the memory and time requirements typically grow exponentially with the number of operations in the circuit.

**Parameters**

- **operator** ([*SparsePauliOp*](/docs/api/qiskit/qiskit.quantum_info.SparsePauliOp)) – The operator to propagate
- **rot\_gates** ([*RotationGates*](#pauli_prop.propagation.RotationGates "pauli_prop.propagation.RotationGates") *| NoisyRotationGates*) – A circuit represented in the form of [`RotationGates`](#pauli_prop.propagation.RotationGates "pauli_prop.propagation.RotationGates").
- **max\_terms** ([*int*](https://docs.python.org/3/library/functions.html#int)) – The maximum number of terms the operator may contain as it is propagated
- **atol** ([*float*](https://docs.python.org/3/library/functions.html#float)) – Terms with coeff magnitudes less than this will not be added to the operator as it is propagated. This parameter is not a guarantee on the accuracy of the returned operator.
- **frame** ([*str*](https://docs.python.org/3/library/stdtypes.html#str)) – `s` for Schrödinger evolution `h` for Heisenberg evolution

**Returns**

The evolved operator and one-norm of all truncated coefficients.

**Raises**

- [**ValueError**](https://docs.python.org/3/library/exceptions.html#ValueError) – `frame` is neither `h` nor `s`.
- [**ValueError**](https://docs.python.org/3/library/exceptions.html#ValueError) – `atol` is negative.
- [**ValueError**](https://docs.python.org/3/library/exceptions.html#ValueError) – `max_terms` is not positive.

**Return type**

[tuple](https://docs.python.org/3/library/stdtypes.html#tuple)\[[*SparsePauliOp*](/docs/api/qiskit/qiskit.quantum_info.SparsePauliOp), [float](https://docs.python.org/3/library/functions.html#float)]

### propagate\_through\_circuit

`propagate_through_circuit(operator, circuit, max_terms, atol, frame)`

[GitHub](https://github.com/Qiskit/pauli-prop/tree/main/pauli_prop/propagation.py#L497-L548)

Propagate a sparse Pauli operator, $O$, through a circuit, $U$.

Supports Pauli rotation gates (‘rx/rxx’, ‘ry/ryy’, ‘rz/rzz’, ‘PauliEvolutionGate’) and Pauli-Lindblad error channels, specified as [PauliLindbladError](https://qiskit.github.io/qiskit-aer/stubs/qiskit_aer.noise.PauliLindbladError.html#qiskit_aer.noise.PauliLindbladError) instructions.

For Schrödinger propagation: $U O U^{\dagger}$. For Heisenberg propagation: $U^{\dagger} O U$.

If `circuit` contains Pauli-Lindblad noise instructions, the operator will be propagated through each noise generator. The coefficient associated with each term in `operator`, $c_i$, will be damped according to each anti-commuting error generator’s rate, $r_i$: $c_i *= exp(-2.0 * r_i)$.

In general, the memory and time required for propagating through a circuit grows exponentially with the number of operations in the circuit due to the exponential growth in the number of terms of the operator in the Pauli basis. To regulate this exponential difficulty, one may truncate small Pauli terms (i.e. set them to zero), resulting in a bias proportional to the magnitudes of the truncated terms. After propagating through each operation in the circuit, terms are truncated with respect to two parameters:

- Only the `max_terms` largest Pauli components are kept; any smaller terms will be truncated. This option makes it possible to estimate in advance how much time and memory will suffice for the computation.
- Terms with magnitudes less than `atol` are truncated (set to zero).

> **Note**
>
> This function pre-allocates space in memory for the full-sized operator and operator buffer. It is the caller’s responsibility to ensure they have enough memory to hold operators containing `max_terms` terms. When `max_terms` is `None`, the memory and time requirements typically grow exponentially with the number of operations in the circuit.

**Parameters**

- **operator** ([*SparsePauliOp*](/docs/api/qiskit/qiskit.quantum_info.SparsePauliOp)) – The operator to propagate
- **circuit** ([*QuantumCircuit*](/docs/api/qiskit/qiskit.circuit.QuantumCircuit)) – The circuit through which the operator will be propagated
- **max\_terms** ([*int*](https://docs.python.org/3/library/functions.html#int)) – The maximum number of terms the operator may contain as it is propagated
- **atol** ([*float*](https://docs.python.org/3/library/functions.html#float)) – Terms with coeff magnitudes less than this will not be added to the operator as it is propagated. This parameter is not a guarantee on the accuracy of the returned operator.
- **frame** ([*str*](https://docs.python.org/3/library/stdtypes.html#str)) – `s` for Schrödinger evolution `h` for Heisenberg evolution

**Returns**

The evolved operator

**Raises**

- [**ValueError**](https://docs.python.org/3/library/exceptions.html#ValueError) – `frame` is neither `h` nor `s`.
- [**ValueError**](https://docs.python.org/3/library/exceptions.html#ValueError) – `atol` is negative.
- [**ValueError**](https://docs.python.org/3/library/exceptions.html#ValueError) – `max_terms` is not positive.

**Return type**

[tuple](https://docs.python.org/3/library/stdtypes.html#tuple)\[[*SparsePauliOp*](/docs/api/qiskit/qiskit.quantum_info.SparsePauliOp), [float](https://docs.python.org/3/library/functions.html#float)]

### propagate\_through\_operator

`propagate_through_operator(op1, op2, max_terms=None, coerce_op1_traceless=False, num_leading_terms=0, frame='s', atol=0.0, search_step=4)`

[GitHub](https://github.com/Qiskit/pauli-prop/tree/main/pauli_prop/propagation.py#L551-L717)

Propagate an operator, op1 or $O$, through another operator, op2 or $U$.

For Schrödinger evolution: $U O U^{\dagger}$.

For Heisenberg evolution: $U^{\dagger} O U$.

Evolution is performed in the Pauli basis by summing terms of the form $U_i O_j U_k$ (neglecting the dagger, see note below). The number of such terms is cubic in operator size (len(op1) \* len(op2)\*\*2) and will generally include many duplicate Paulis.

Setting max\_terms produces an approximate result, where only the max\_terms largest terms (in coefficient magnitude) are computed. This can be much faster but results in some error due to truncation of smaller terms.

The approximate computation involves two parts: searching for the terms to keep, then computing those terms. Increasing `search_step` greatly (cubically) speeds up the search, at an accuracy cost that is often small.

It is possible that some Paulis present in the kept terms would have also appeared in the truncated terms. Because such truncated terms are never computed, they cannot possibly be merged into the kept terms sharing the same Pauli. Thus, the `n``th-largest term in the approximate result is not guaranteed to equal the nth-largest term in the exact result. Likewise, convergence to the exact result with increasing ``max_terms` can be non-monotonic.

> **Note**
>
> $O$ is assumed to be Hermitian ($O_j$ = $O_j^{\dagger}$)

**Parameters**

- **op1** ([*SparsePauliOp*](/docs/api/qiskit/qiskit.quantum_info.SparsePauliOp)) – The operator to propagate

- **op2** ([*SparsePauliOp*](/docs/api/qiskit/qiskit.quantum_info.SparsePauliOp)) – The operator through which to propagate

- **max\_terms** ([*int*](https://docs.python.org/3/library/functions.html#int) *| None*) –

  When not `None`, produces an approximate result including only the `max_terms` largest terms in the direct product of the three operators in Pauli space.

  When `max_terms` is `None` and the number of qubits is \< 12, the propagation will be performed in the computational basis using matrix multiplication. For systems > 12 qubits, all Pauli terms are computed and summed; however, this is usually not a good way to compute exact evolution due to the many duplicate terms present.

- **coerce\_op1\_traceless** ([*bool*](https://docs.python.org/3/library/functions.html#bool)) – A flag denoting whether to remove identity terms from the output operator.

- **num\_leading\_terms** ([*int*](https://docs.python.org/3/library/functions.html#int)) – The number of terms in `op1` to conjugate by every term in `op2`. The set of included terms is expanded to include its union with the set of terms $U_i O_j U_i^{\dagger}$, for $j < num_leading_terms$. This can improve accuracy for the leading components of O in the output, at some computational runtime cost.

- **frame** ([*str*](https://docs.python.org/3/library/stdtypes.html#str)) – s for Schrödinger evolution h for Heisenberg evolution

- **atol** ([*float*](https://docs.python.org/3/library/functions.html#float)) – Terms in the evolved operator with magnitudes below `atol` will be truncated

- **search\_step** ([*int*](https://docs.python.org/3/library/functions.html#int)) – A parameter that can speed up the search of the very large 3D space to identify the `max_terms` largest terms in the product. Setting this step size >1 accelerates that search by a factor of `search_step**3`, at a potential cost in accuracy. This inaccuracy is expected to be small for `search_step**3 << max_terms`.

**Returns**

The transformed operator

**Raises**

- [**ValueError**](https://docs.python.org/3/library/exceptions.html#ValueError) – `frame` is neither `s` nor `h`.
- [**ValueError**](https://docs.python.org/3/library/exceptions.html#ValueError) – `search_step` is not positive.
- [**ValueError**](https://docs.python.org/3/library/exceptions.html#ValueError) – `max_terms` contains an invalid value.

**Return type**

[*SparsePauliOp*](/docs/api/qiskit/qiskit.quantum_info.SparsePauliOp)

### evolve\_through\_cliffords

`evolve_through_cliffords(circuit)`

[GitHub](https://github.com/Qiskit/pauli-prop/tree/main/pauli_prop/propagation.py#L73-L138)

Evolve (Schrödinger frame) all non-Clifford instructions through all Clifford gates in the circuit.

This shifts all recognized Clifford gates to the beginning of the circuit and updates the bases of Pauli-rotation gates (e.g. `RxGate`, `RzzGate`, `PauliEvolutionGate`) and [PauliLindbladError](https://qiskit.github.io/qiskit-aer/stubs/qiskit_aer.noise.PauliLindbladError.html#qiskit_aer.noise.PauliLindbladError) channels. Other operations are not supported. See [Pauli.evolve docs](/docs/api/qiskit/qiskit.quantum_info.Pauli#evolve) for more info about evolution of Paulis by Cliffords.

The effect is similar to going to the Clifford interaction picture in [arXiv:2306.04797](https://arxiv.org/abs/2306.04797) but without mapping all rotation angle magnitudes to be $\leq \pi/4$.

The function returns two objects representing the Clifford and non-Clifford parts of the circuit.

**Parameters**

**circuit** ([*QuantumCircuit*](/docs/api/qiskit/qiskit.circuit.QuantumCircuit)) – The `QuantumCircuit` to transform. Can contain only Pauli-rotation gates, `PauliLindbladError` (appended to the circuit as quantum channels), and recognized Clifford gates.

**Returns**

- **Clifford** - A single all-qubit [Clifford](/docs/api/qiskit/qiskit.quantum_info.Clifford) representing the first part of the circuit
- **QuantumCircuit** - A circuit containing the remaining, transformed part of the circuit

**Raises**

[**ValueError**](https://docs.python.org/3/library/exceptions.html#ValueError) – Input circuit contains unsupported gate

**Return type**

[tuple](https://docs.python.org/3/library/stdtypes.html#tuple)\[[*Clifford*](/docs/api/qiskit/qiskit.quantum_info.Clifford), [*QuantumCircuit*](/docs/api/qiskit/qiskit.circuit.QuantumCircuit)]
