Skip to content

Pauli decomposition

Write an explicit square matrix as a sum of Pauli strings, A = sum_P c_P P with c_P = 2**-n Tr(P A), and remove small terms with a bound on the removed operator. Import the functions from nwqlib.subroutines.pauli_decomposition. Structured Pauli inputs do not use this dense conversion.

import numpy as np
from nwqlib.subroutines.pauli_decomposition import decompose_matrix_to_pauli

decomposition = decompose_matrix_to_pauli(np.eye(3), pad_to_power_of_two=True)
for term in decomposition.terms:
    print(term.label, term.coefficient.real)
print(decomposition.input_dimension, decomposition.operator_dimension)
II 0.75
IZ 0.25
ZI 0.25
ZZ -0.25
3 4

The 3 by 3 identity, zero-padded to 4 by 4, is diag(1, 1, 1, 0) = (3 II + IZ + ZI - ZZ) / 4, with qubit 0 the last character of each label.

Decompose and prune

Pauli decomposition helpers for matrix-valued algorithm inputs.

An n-qubit matrix is A = sum_P c_P P over Pauli strings P with c_P = 2**-n Tr(P A), because Pauli strings are Hermitian, unitary and orthogonal in the trace inner product. Labels follow Qiskit, whose last character acts on qubit 0. Every Pauli string has operator norm one, so the coefficient 1-norm bounds ||A||_2, and it is the LCU subnormalization of the Pauli block encoding.

PauliTerm

PauliTerm(*, label: str, coefficient: complex)

One term c P of a Pauli decomposition, with P a Qiskit Pauli label.

Build it with keyword arguments, for example PauliTerm(label="ZI", coefficient=0.5). Both arguments are required.

Parameters:

  • label (str) –

    Pauli string label using Qiskit's ordering convention.

  • coefficient (complex) –

    Complex coefficient multiplying the Pauli string.

PauliDecomposition

PauliDecomposition(*, terms: tuple[PauliTerm, ...], input_dimension: int, operator_dimension: int, num_qubits: int, atol: float)

A matrix written as a sum of Pauli strings, with its dimensions and the coefficient cutoff used.

decompose_matrix_to_pauli returns it. It can also be built with keyword arguments, all required, to pass a Pauli sum to build_block_encoding. The operator is sum_j c_j P_j over terms.

Parameters:

  • terms (tuple[PauliTerm, ...]) –

    Nonzero Pauli terms defining the represented operator after any pruning.

  • input_dimension (int) –

    Original matrix dimension.

  • operator_dimension (int) –

    Dimension actually decomposed; it exceeds input_dimension exactly when the input was zero-padded to a power-of-two dimension.

  • num_qubits (int) –

    Number of qubits represented by the Pauli labels.

  • atol (float) –

    Resolved absolute coefficient cutoff after combining atol and rtol. It is not a bound on the difference from the original matrix.

Raises:

  • ValueError –

    If a term's label is not a string of num_qubits letters from IXYZ.

to_sparse_pauli_op

to_sparse_pauli_op() -> SparsePauliOp

Return the decomposition as a Qiskit SparsePauliOp.

prune_pauli_terms_relative

prune_pauli_terms_relative(terms: Sequence[PauliTerm], *, rtol: float = PAULI_COEFFICIENT_RTOL) -> tuple[tuple[PauliTerm, ...], float]

Remove Pauli terms that are small relative to the largest, and return a bound on the removed operator.

A term is removed when |c| <= rtol * max_j |c_j| and kept otherwise.

Parameters:

  • terms (Sequence[PauliTerm]) –

    Exact Pauli-decomposition terms to inspect, such as decomposition.terms.

  • rtol (float, default: PAULI_COEFFICIENT_RTOL ) –

    Default 1e-12. Relative cutoff applied to the largest coefficient magnitude.

Returns:

  • kept ( tuple[PauliTerm, ...] ) –

    The kept terms.

  • removed_mass ( float ) –

    sum(abs(c)) over the removed coefficients. Since every Pauli string has operator norm one, it bounds the operator norm of the removed part.

Raises:

  • ValueError –

    If rtol is negative or non-finite.

Examples:

The 1e-14 XI term of 0.5 ZZ + 1e-14 XI is below 1e-12 times the largest coefficient:

>>> from qiskit.quantum_info import SparsePauliOp
>>> from nwqlib.subroutines.pauli_decomposition import (
...     decompose_matrix_to_pauli, prune_pauli_terms_relative)
>>> A = SparsePauliOp(["ZZ", "XI"], [0.5, 1e-14]).to_matrix()
>>> kept, removed = prune_pauli_terms_relative(
...     decompose_matrix_to_pauli(A).terms)
>>> print([term.label for term in kept], removed)
['ZZ'] 1e-14

decompose_matrix_to_pauli

decompose_matrix_to_pauli(matrix: Any, *, atol: float = 0.0, rtol: float | None = None, pad_to_power_of_two: bool = False) -> PauliDecomposition

Write a square matrix as a sum of Pauli strings, A = sum_P c_P P with c_P = 2**-n Tr(P A).

This helper prepares matrix inputs for LCHS and later circuit-building paths. Matrix dimensions must be powers of two unless explicit zero padding is requested. NWQLib's I/X/Y/Z block transform computes the coefficients with overflow-safe componentwise averages at each level and omits only computed exact-zero coefficients before the cutoff below. It avoids the tiny-magnitude loss of Qiskit's zero-tolerance decomposition. For dimension D and width q, the arithmetic is O(q D**2) with O(D**2) array workspace. No matrix norm or reference decomposition is added to choose the pruning scale. Rounding already present in a densely assembled matrix can appear as tiny coefficients. Explicit rtol or prune_pauli_terms_relative can remove those terms, with the omitted coefficient mass reported by the latter.

Parameters:

  • matrix (array_like) –

    Square complex matrix to decompose.

  • atol (float, default: 0.0 ) –

    Default 0.0. Explicit absolute coefficient cutoff, in the units of the matrix. Zero keeps every computed nonzero coefficient.

  • rtol (float | None, default: None ) –

    Default None, which uses zero. Relative cutoff against the largest computed coefficient. The combined cutoff is max(atol, rtol * max_P |c_P|), and a term is kept when |c_P| exceeds it.

  • pad_to_power_of_two (bool, default: False ) –

    Default False. Whether to zero-pad a non-power-of-two matrix to the next power-of-two dimension before decomposition.

Returns:

  • decomposition ( PauliDecomposition ) –

    The kept terms in decomposition.terms, the dimensions, and the resolved cutoff in decomposition.atol.

Raises:

  • ValueError –

    If the matrix is not square, the tolerance is negative, or a non-power-of-two dimension is provided without padding, or a coefficient magnitude exceeds the finite binary64 range.

Examples:

[[1, 0.5], [0.5, -1]] is 0.5 X + Z:

>>> from nwqlib.subroutines.pauli_decomposition import (
...     decompose_matrix_to_pauli)
>>> decomposition = decompose_matrix_to_pauli([[1.0, 0.5], [0.5, -1.0]])
>>> print([(t.label, t.coefficient) for t in decomposition.terms])
[('X', (0.5+0j)), ('Z', (1+0j))]

Source map

Scientific step Source Code
c_P = 2**-n Tr(P A) for Hermitian, trace-orthogonal Pauli strings Pauli-basis orthogonality, stated in the module docstring nwqlib.subroutines.pauli_decomposition.decompose_matrix_to_pauli
I/X/Y/Z block transform with componentwise averages NWQLib, described in its docstring nwqlib.operators._pauli.pauli_coefficients
Removed operator norm at most the pruned coefficient 1-norm Triangle inequality with unit-norm Pauli strings nwqlib.subroutines.pauli_decomposition.prune_pauli_terms_relative