Source code for second_quantization.pauli_strings
# Copied from https://github.com/HANTLUK/PauliDecomposition and modified
# to support sympy matrices.
# MIT License
#
# Copyright (c) 2023 Lukas Hantzko
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from collections import defaultdict
import numpy as np
from scipy import sparse
import sympy
[docs]
def PauliDecomposition(matrix, PauliStringInit=""):
"""
Computes the Pauli decomposition of a square matrix.
Iteratively splits tensor factors off and decomposes those smaller
matrices. This is done using submatrices of the original matrix.
The Pauli strings are generated in each step.
Args:
matrix: Matrix to be decomposed
(Preferably numpy array/scipysparse).
sparse: Whether matrix is in sparse format.
PauliStringInit: For recursive computation.
Returns:
decomposition/outString: String of 1XYZ with their factors.
"""
# Dimension check
if matrix.shape[0] != matrix.shape[1]:
raise ValueError("Matrix is not square.")
if (qBitDim := np.log(matrix.shape[0]) / np.log(2)) % 1 != 0:
raise ValueError("Matrix dimension is not a power of 2.")
I = sympy.I if isinstance(matrix, sympy.Matrix) else 1j # noqa: E741
decomposition = defaultdict(lambda: 0)
# Output for dimension 1
if qBitDim == 0:
if matrix[0, 0] != 0.0:
decomposition[PauliStringInit] = matrix[0, 0]
else:
# Calculates the tensor product coefficients via the sliced submatrices.
# If one of these components is zero that coefficient is ignored.
halfDim = int(2 ** (qBitDim - 1))
coeff1 = (matrix[0:halfDim, 0:halfDim] + matrix[halfDim:, halfDim:]) / 2
coeffX = (matrix[halfDim:, 0:halfDim] + matrix[0:halfDim, halfDim:]) / 2
coeffY = -I * (matrix[halfDim:, 0:halfDim] - matrix[0:halfDim, halfDim:]) / 2
coeffZ = (matrix[0:halfDim, 0:halfDim] - matrix[halfDim:, halfDim:]) / 2
coefficients = {"I": coeff1, "X": coeffX, "Y": coeffY, "Z": coeffZ}
# Recursion for the Submatrices
for c in coefficients:
matrix = coefficients[c]
if not (is_zero(matrix)):
subDec = PauliDecomposition(matrix, f"{PauliStringInit}{c}")
decomposition.update(subDec)
return decomposition
[docs]
def is_zero(matrix):
if isinstance(matrix, sympy.Matrix):
return matrix.is_zero
elif sparse.issparse(matrix):
return len(matrix.nonzero()[0]) == 0
elif isinstance(matrix, np.ndarray):
return not (matrix.any())
else:
raise ValueError("Unknown matrix type.")