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_qubitsletters fromIXYZ.
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
rtolis 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 ismax(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 indecomposition.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 |