Propagation (pauli_prop.propagation)

Functions for performing Pauli propagation.

class RotationGates(gates, qargs, thetas)[source]

Bases: NamedTuple

An intermediate minimal representation of a QuantumCircuit.

During propagate_through_circuit() the QuantumCircuit gets converted into a sequence of rotation gates, extracting the parameters and acted-upon qubit indices. These data structures can be passed to the Rust-accelerated internal function in a straight forward manner.

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

Parameters:
append_circuit_instruction(inst, qargs, num_qubits, *, clifford=None)[source]

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

Parameters:
  • inst (CircuitInstruction) – The circuit instruction to parse and append

  • qargs (list[int]) – The list of qubit indices of the instruction in the context of its circuit

  • num_qubits (int) – The number of qubits of the circuit containing this instruction

  • clifford (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 – Unsupported gate encountered in circuit

  • ValueError – If given, clifford must act on all qubits in circuit

Return type:

None

count(value, /)

Return number of occurrences of value.

gates: list[ndarray[tuple[int, ...], dtype[bool]]]

A ZX-calculus-like representation of the gates.

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

Return first index of value.

Raises ValueError if the value is not present.

qargs: list[list[int]]

The qubit indices acted upon by each gate.

thetas: list[float]

The rotation angles of all gates.

circuit_to_rotation_gates(circuit)[source]

Converts the provided circuit to an intermediate representation.

Parameters:

circuit (QuantumCircuit) – The circuit to convert. It may contain gates that are Pauli rotations or PauliLindbladError instances.

Returns:

The extracted rotation gate data.

Raises:

ValueError – when an unsupported gate is encountered in circuit.

Return type:

RotationGates

propagate_through_rotation_gates(operator, rot_gates, max_terms, atol, frame)[source]

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

For Schrödinger propagation: \(U O U†\).

For Heisenberg propagation: \(U† O U\).

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) – The operator to propagate

  • rot_gates (RotationGates) – The circuit represented in the form of RotationGates (see also circuit_to_rotation_gates()).

  • max_terms (int) – The maximum number of terms the operator may contain as it is propagated

  • atol (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) – s for Schrödinger evolution h for Heisenberg evolution

Returns:

The evolved operator

Raises:
Return type:

tuple[SparsePauliOp, float]

propagate_through_circuit(operator, circuit, max_terms, atol, frame)[source]

Propagate a sparse Pauli operator, \(O\), through a circuit, \(U\).

For Schrödinger propagation: \(U O U†\).

For Heisenberg propagation: \(U† O U\).

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) – The operator to propagate

  • circuit (QuantumCircuit) – The circuit through which the operator will be propagated

  • max_terms (int) – The maximum number of terms the operator may contain as it is propagated

  • atol (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) – s for Schrödinger evolution h for Heisenberg evolution

Returns:

The evolved operator

Raises:
Return type:

tuple[SparsePauliOp, float]

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

Propagate an operator, op1 or \(O\), through another operator, op2 or \(U\).

For Schrödinger evolution: \(U O U†\).

For Heisenberg evolution: \(U† 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' = :math:`O_j†\))

Parameters:
  • op1 (SparsePauliOp) – The operator to propagate

  • op2 (SparsePauliOp) – The operator through which to propagate

  • max_terms (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) – A flag denoting whether to remove identity terms from the output operator.

  • num_leading_terms (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†\), 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) – s for Schrödinger evolution h for Heisenberg evolution

  • atol (float) – Terms in the evolved operator with magnitudes below atol will be truncated

  • search_step (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:
Return type:

SparsePauliOp

evolve_through_cliffords(circuit)[source]

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 channels. Other operations are not supported. See Pauli.evolve docs for more info about evolution of Paulis by Cliffords.

The effect is similar to going to the Clifford interaction picture in arXiv: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) – 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 representing the first part of the circuit

  • QuantumCircuit - A circuit containing the remaining, transformed part of the circuit

Raises:

ValueError – Input circuit contains unsupported gate

Return type:

tuple[Clifford, QuantumCircuit]