From 2035bad497d9ea9012e44c658b1ae9b1a1ba240c Mon Sep 17 00:00:00 2001 From: Noureldin Date: Fri, 19 May 2023 18:14:41 +0100 Subject: [PATCH 1/9] Create a unitary to pauli string transformer --- cirq-core/cirq/transformers/__init__.py | 1 + .../analytical_decompositions/__init__.py | 4 + .../pauli_string_decomposition.py | 105 ++++++++++++++++++ .../pauli_string_decomposition_test.py | 29 +++++ 4 files changed, 139 insertions(+) create mode 100644 cirq-core/cirq/transformers/analytical_decompositions/pauli_string_decomposition.py create mode 100644 cirq-core/cirq/transformers/analytical_decompositions/pauli_string_decomposition_test.py diff --git a/cirq-core/cirq/transformers/__init__.py b/cirq-core/cirq/transformers/__init__.py index 25f8ba35080..81884c4d766 100644 --- a/cirq-core/cirq/transformers/__init__.py +++ b/cirq-core/cirq/transformers/__init__.py @@ -36,6 +36,7 @@ two_qubit_matrix_to_diagonal_and_cz_operations, two_qubit_matrix_to_ion_operations, two_qubit_matrix_to_sqrt_iswap_operations, + unitary_to_pauli_string, ) from cirq.transformers.heuristic_decompositions import ( diff --git a/cirq-core/cirq/transformers/analytical_decompositions/__init__.py b/cirq-core/cirq/transformers/analytical_decompositions/__init__.py index b4c5efa63e1..d4dbc7f6a2d 100644 --- a/cirq-core/cirq/transformers/analytical_decompositions/__init__.py +++ b/cirq-core/cirq/transformers/analytical_decompositions/__init__.py @@ -67,3 +67,7 @@ from cirq.transformers.analytical_decompositions.single_to_two_qubit_isometry import ( two_qubit_matrix_to_cz_isometry, ) + +from cirq.transformers.analytical_decompositions.pauli_string_decomposition import ( + unitary_to_pauli_string, +) diff --git a/cirq-core/cirq/transformers/analytical_decompositions/pauli_string_decomposition.py b/cirq-core/cirq/transformers/analytical_decompositions/pauli_string_decomposition.py new file mode 100644 index 00000000000..52bf700dd29 --- /dev/null +++ b/cirq-core/cirq/transformers/analytical_decompositions/pauli_string_decomposition.py @@ -0,0 +1,105 @@ +# Copyright 2023 The Cirq Developers +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import Optional, Tuple, cast + +import numpy as np +import numpy.typing as npt + +from cirq.ops import DensePauliString +from cirq import linalg +from cirq import protocols + +def _argmax(V: npt.NDArray) -> Tuple[int, float]: + V = (V*V.conj()).real + idx_max = np.argmax(V) + V[idx_max] = 0 + return cast(int, idx_max), np.max(V) + +def _validate_decomposition(decomposition: DensePauliString, U: npt.NDArray, eps: float) -> bool: + got = protocols.unitary(decomposition) + return np.abs(got - U).max() < eps + + +def _fast_walsh_hadamard_transform(V: npt.NDArray) -> None: + """Fast Walsh–Hadamard Transform of an array.""" + m = len(V) + n = m.bit_length() - 1 + for h in [2**i for i in range(n)]: + for i in range(0, m, h * 2): + for j in range(i, i + h): + x = V[j] + y = V[j + h] + V[j] = x + y + V[j + h] = x - y + +def _conjugate_with_hadamard(U: npt.NDArray) -> npt.NDArray: + """Applies H†UH in O(n4^n) instead of O(8^n).""" + + U = np.copy(U.T) + for i in range(U.shape[1]): + _fast_walsh_hadamard_transform(U[:, i]) + U = U.T + for i in range(U.shape[1]): + _fast_walsh_hadamard_transform(U[:, i]) + return U + +def unitary_to_pauli_string(U: npt.NDArray, eps: float = 1e-15) -> Optional[DensePauliString]: + """Attempts to find a pauli string (with possible phase) equivalent to U up to eps. + + Args: + U: A square array whose dimension is a power of 2. + eps: numbers smaller than `eps` are considered zero. + + Returns: + A DensePauliString of None. + + Raises: + ValueError: if U is not square with a power of 2 dimension. + """ + + if len(U.shape) != 2 or U.shape[0] != U.shape[1]: + raise ValueError(f'Input has a none square shape {U}') + n = U.shape[0].bit_length() - 1 + if U.shape[0] != 2**n: + raise ValueError(f'Input dimension {U.shape[0]} isn\'t a power of 2') + + x_msk, second_largest = _argmax(U[:, 0]) + if second_largest > eps: + return None + U_z = _conjugate_with_hadamard(U) + z_msk, second_largest = _argmax(U_z[:, 0]) + if second_largest > eps: + return None + def select(i): + has_x = (x_msk >> i) & 1 + has_z = (z_msk >> i) & 1 + gate_table = [ + 'IX', + 'ZY', + ] + return gate_table[has_z][has_x] + decomposition = DensePauliString(''.join(select(i) for i in reversed(range(n)))) + + guess = protocols.unitary(decomposition) + if np.abs(guess[x_msk, 0]) < eps: + return None + phase = U[x_msk, 0] / guess[x_msk, 0] + + decomposition = DensePauliString(''.join(select(i) for i in reversed(range(n))), coefficient=phase) + + if not _validate_decomposition(decomposition, U, eps): + return None + + return decomposition \ No newline at end of file diff --git a/cirq-core/cirq/transformers/analytical_decompositions/pauli_string_decomposition_test.py b/cirq-core/cirq/transformers/analytical_decompositions/pauli_string_decomposition_test.py new file mode 100644 index 00000000000..02526479cc5 --- /dev/null +++ b/cirq-core/cirq/transformers/analytical_decompositions/pauli_string_decomposition_test.py @@ -0,0 +1,29 @@ +import pytest +import itertools +import cmath + +import numpy as np + +from cirq.ops import DensePauliString, T +from cirq import protocols +from cirq.transformers.analytical_decompositions import unitary_to_pauli_string + + +@pytest.mark.parametrize('phase', [cmath.exp(i*2*cmath.pi/5 * 1j) for i in range(5)]) +@pytest.mark.parametrize('pauli_string', [''.join(p) for p in itertools.product(['', 'I', 'X', 'Y', 'Z'], repeat=4)]) +def test_unitary_to_pauli_string(pauli_string: str, phase: complex): + want = DensePauliString(pauli_string, coefficient=phase) + got = unitary_to_pauli_string(protocols.unitary(want)) + assert want == got + + +def test_unitary_to_pauli_string_non_pauli_input(): + got = unitary_to_pauli_string(protocols.unitary(T)) + assert got is None + + + got = unitary_to_pauli_string(np.array([[1, 0], [1, 0]])) + assert got is None + + got = unitary_to_pauli_string(np.array([[1, 1], [0, 2]])) + assert got is None From 235fbc61d7223a96a6ce3824f60502f9962a19e2 Mon Sep 17 00:00:00 2001 From: Noureldin Date: Fri, 19 May 2023 18:21:28 +0100 Subject: [PATCH 2/9] update docstring --- .../pauli_string_decomposition.py | 32 +++++++++++-------- .../pauli_string_decomposition_test.py | 23 ++++++++++--- 2 files changed, 38 insertions(+), 17 deletions(-) diff --git a/cirq-core/cirq/transformers/analytical_decompositions/pauli_string_decomposition.py b/cirq-core/cirq/transformers/analytical_decompositions/pauli_string_decomposition.py index 52bf700dd29..ce4219af35c 100644 --- a/cirq-core/cirq/transformers/analytical_decompositions/pauli_string_decomposition.py +++ b/cirq-core/cirq/transformers/analytical_decompositions/pauli_string_decomposition.py @@ -18,15 +18,16 @@ import numpy.typing as npt from cirq.ops import DensePauliString -from cirq import linalg from cirq import protocols + def _argmax(V: npt.NDArray) -> Tuple[int, float]: - V = (V*V.conj()).real + V = (V * V.conj()).real idx_max = np.argmax(V) V[idx_max] = 0 return cast(int, idx_max), np.max(V) + def _validate_decomposition(decomposition: DensePauliString, U: npt.NDArray, eps: float) -> bool: got = protocols.unitary(decomposition) return np.abs(got - U).max() < eps @@ -44,6 +45,7 @@ def _fast_walsh_hadamard_transform(V: npt.NDArray) -> None: V[j] = x + y V[j + h] = x - y + def _conjugate_with_hadamard(U: npt.NDArray) -> npt.NDArray: """Applies H†UH in O(n4^n) instead of O(8^n).""" @@ -55,16 +57,19 @@ def _conjugate_with_hadamard(U: npt.NDArray) -> npt.NDArray: _fast_walsh_hadamard_transform(U[:, i]) return U + def unitary_to_pauli_string(U: npt.NDArray, eps: float = 1e-15) -> Optional[DensePauliString]: - """Attempts to find a pauli string (with possible phase) equivalent to U up to eps. + """Attempts to find a pauli string (with possible phase) equivalent to U up to eps. + + Based on this answer https://shorturl.at/aA079. Args: U: A square array whose dimension is a power of 2. eps: numbers smaller than `eps` are considered zero. - + Returns: A DensePauliString of None. - + Raises: ValueError: if U is not square with a power of 2 dimension. """ @@ -82,24 +87,25 @@ def unitary_to_pauli_string(U: npt.NDArray, eps: float = 1e-15) -> Optional[Dens z_msk, second_largest = _argmax(U_z[:, 0]) if second_largest > eps: return None + def select(i): has_x = (x_msk >> i) & 1 has_z = (z_msk >> i) & 1 - gate_table = [ - 'IX', - 'ZY', - ] + gate_table = ['IX', 'ZY'] return gate_table[has_z][has_x] + decomposition = DensePauliString(''.join(select(i) for i in reversed(range(n)))) guess = protocols.unitary(decomposition) if np.abs(guess[x_msk, 0]) < eps: - return None + return None phase = U[x_msk, 0] / guess[x_msk, 0] - decomposition = DensePauliString(''.join(select(i) for i in reversed(range(n))), coefficient=phase) + decomposition = DensePauliString( + ''.join(select(i) for i in reversed(range(n))), coefficient=phase + ) if not _validate_decomposition(decomposition, U, eps): return None - - return decomposition \ No newline at end of file + + return decomposition diff --git a/cirq-core/cirq/transformers/analytical_decompositions/pauli_string_decomposition_test.py b/cirq-core/cirq/transformers/analytical_decompositions/pauli_string_decomposition_test.py index 02526479cc5..d193c87ad86 100644 --- a/cirq-core/cirq/transformers/analytical_decompositions/pauli_string_decomposition_test.py +++ b/cirq-core/cirq/transformers/analytical_decompositions/pauli_string_decomposition_test.py @@ -1,6 +1,20 @@ -import pytest +# Copyright 2023 The Cirq Developers +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + import itertools import cmath +import pytest import numpy as np @@ -9,8 +23,10 @@ from cirq.transformers.analytical_decompositions import unitary_to_pauli_string -@pytest.mark.parametrize('phase', [cmath.exp(i*2*cmath.pi/5 * 1j) for i in range(5)]) -@pytest.mark.parametrize('pauli_string', [''.join(p) for p in itertools.product(['', 'I', 'X', 'Y', 'Z'], repeat=4)]) +@pytest.mark.parametrize('phase', [cmath.exp(i * 2 * cmath.pi / 5 * 1j) for i in range(5)]) +@pytest.mark.parametrize( + 'pauli_string', [''.join(p) for p in itertools.product(['', 'I', 'X', 'Y', 'Z'], repeat=4)] +) def test_unitary_to_pauli_string(pauli_string: str, phase: complex): want = DensePauliString(pauli_string, coefficient=phase) got = unitary_to_pauli_string(protocols.unitary(want)) @@ -21,7 +37,6 @@ def test_unitary_to_pauli_string_non_pauli_input(): got = unitary_to_pauli_string(protocols.unitary(T)) assert got is None - got = unitary_to_pauli_string(np.array([[1, 0], [1, 0]])) assert got is None From 2989021f648035bbfcd48cee89403880c14d0f26 Mon Sep 17 00:00:00 2001 From: Noureldin Date: Fri, 19 May 2023 18:26:00 +0100 Subject: [PATCH 3/9] add numerical stability to phase --- .../analytical_decompositions/pauli_string_decomposition.py | 1 + 1 file changed, 1 insertion(+) diff --git a/cirq-core/cirq/transformers/analytical_decompositions/pauli_string_decomposition.py b/cirq-core/cirq/transformers/analytical_decompositions/pauli_string_decomposition.py index ce4219af35c..9c1e456f000 100644 --- a/cirq-core/cirq/transformers/analytical_decompositions/pauli_string_decomposition.py +++ b/cirq-core/cirq/transformers/analytical_decompositions/pauli_string_decomposition.py @@ -100,6 +100,7 @@ def select(i): if np.abs(guess[x_msk, 0]) < eps: return None phase = U[x_msk, 0] / guess[x_msk, 0] + phase /= np.abs(phase) # Make sure |phase| = 1 to avoid rounding issues. decomposition = DensePauliString( ''.join(select(i) for i in reversed(range(n))), coefficient=phase From ea32cd4287cb6c148cdb203ae86618774f479bbd Mon Sep 17 00:00:00 2001 From: Noureldin Date: Fri, 19 May 2023 19:47:53 +0100 Subject: [PATCH 4/9] fix test --- .../pauli_string_decomposition_test.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cirq-core/cirq/transformers/analytical_decompositions/pauli_string_decomposition_test.py b/cirq-core/cirq/transformers/analytical_decompositions/pauli_string_decomposition_test.py index d193c87ad86..6de55c2cf96 100644 --- a/cirq-core/cirq/transformers/analytical_decompositions/pauli_string_decomposition_test.py +++ b/cirq-core/cirq/transformers/analytical_decompositions/pauli_string_decomposition_test.py @@ -30,7 +30,8 @@ def test_unitary_to_pauli_string(pauli_string: str, phase: complex): want = DensePauliString(pauli_string, coefficient=phase) got = unitary_to_pauli_string(protocols.unitary(want)) - assert want == got + assert np.all(want.pauli_mask == got.pauli_mask) + assert np.isclose(want.coefficient, got.coefficient) def test_unitary_to_pauli_string_non_pauli_input(): From 02e9d829cbea45d160cedc41c4114520baa80703 Mon Sep 17 00:00:00 2001 From: Noureldin Date: Fri, 19 May 2023 20:04:11 +0100 Subject: [PATCH 5/9] fix type hint --- .../pauli_string_decomposition_test.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cirq-core/cirq/transformers/analytical_decompositions/pauli_string_decomposition_test.py b/cirq-core/cirq/transformers/analytical_decompositions/pauli_string_decomposition_test.py index 6de55c2cf96..9b286b1ba11 100644 --- a/cirq-core/cirq/transformers/analytical_decompositions/pauli_string_decomposition_test.py +++ b/cirq-core/cirq/transformers/analytical_decompositions/pauli_string_decomposition_test.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +from typing import cast import itertools import cmath import pytest @@ -30,8 +31,9 @@ def test_unitary_to_pauli_string(pauli_string: str, phase: complex): want = DensePauliString(pauli_string, coefficient=phase) got = unitary_to_pauli_string(protocols.unitary(want)) + assert got is not None assert np.all(want.pauli_mask == got.pauli_mask) - assert np.isclose(want.coefficient, got.coefficient) + assert np.isclose(cast(np.complex128, want.coefficient), cast(np.complex128, got.coefficient)) def test_unitary_to_pauli_string_non_pauli_input(): From 69e80fdeb86608d347dfa560806a24bff4a52eac Mon Sep 17 00:00:00 2001 From: Noureldin Date: Tue, 23 May 2023 17:39:55 +0100 Subject: [PATCH 6/9] fix coverage --- .../pauli_string_decomposition_test.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/cirq-core/cirq/transformers/analytical_decompositions/pauli_string_decomposition_test.py b/cirq-core/cirq/transformers/analytical_decompositions/pauli_string_decomposition_test.py index 9b286b1ba11..99ce2ca3d67 100644 --- a/cirq-core/cirq/transformers/analytical_decompositions/pauli_string_decomposition_test.py +++ b/cirq-core/cirq/transformers/analytical_decompositions/pauli_string_decomposition_test.py @@ -45,3 +45,14 @@ def test_unitary_to_pauli_string_non_pauli_input(): got = unitary_to_pauli_string(np.array([[1, 1], [0, 2]])) assert got is None + + got = unitary_to_pauli_string(np.array([[0, 0.5], [1, -1]]), eps=1.1) + assert got is None + + +def test_invalid_input(): + with pytest.raises(ValueError): + _ = unitary_to_pauli_string(np.zeros((2, 3))) + + with pytest.raises(ValueError): + _ = unitary_to_pauli_string(np.zeros((3, 3))) From e61cd1a7c6493d059fbbf2d1240e7f29c82452db Mon Sep 17 00:00:00 2001 From: Noureldin Date: Mon, 12 Jun 2023 17:24:27 +0100 Subject: [PATCH 7/9] improved comments --- .../pauli_string_decomposition.py | 20 ++++++++++++++++++- .../pauli_string_decomposition_test.py | 4 ++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/cirq-core/cirq/transformers/analytical_decompositions/pauli_string_decomposition.py b/cirq-core/cirq/transformers/analytical_decompositions/pauli_string_decomposition.py index 9c1e456f000..cdbee76bbb0 100644 --- a/cirq-core/cirq/transformers/analytical_decompositions/pauli_string_decomposition.py +++ b/cirq-core/cirq/transformers/analytical_decompositions/pauli_string_decomposition.py @@ -22,6 +22,7 @@ def _argmax(V: npt.NDArray) -> Tuple[int, float]: + """Returns a tuple (index of max number, max number).""" V = (V * V.conj()).real idx_max = np.argmax(V) V[idx_max] = 0 @@ -29,6 +30,7 @@ def _argmax(V: npt.NDArray) -> Tuple[int, float]: def _validate_decomposition(decomposition: DensePauliString, U: npt.NDArray, eps: float) -> bool: + """Returns whether the max absolute value of the elementwise difference is less than eps.""" got = protocols.unitary(decomposition) return np.abs(got - U).max() < eps @@ -47,7 +49,7 @@ def _fast_walsh_hadamard_transform(V: npt.NDArray) -> None: def _conjugate_with_hadamard(U: npt.NDArray) -> npt.NDArray: - """Applies H†UH in O(n4^n) instead of O(8^n).""" + """Applies HcUH in O(n4^n) instead of O(8^n).""" U = np.copy(U.T) for i in range(U.shape[1]): @@ -62,6 +64,16 @@ def unitary_to_pauli_string(U: npt.NDArray, eps: float = 1e-15) -> Optional[Dens """Attempts to find a pauli string (with possible phase) equivalent to U up to eps. Based on this answer https://shorturl.at/aA079. + Let x_mask be the index of the maximum number of the first column of U + and z_mask be the index of the maximum number of the first column of H†UH + each of these indicies is n-bits long where U is 2^n x 2^n. + + These two indicies/masks encode in binary the indicies of the qubits that + have I, X, Y, Z acting on them as follows: + x_mask[i] == 1 and z_mask[i] == 0: X acts on the ith qubit + x_mask[i] == 0 and z_mask[i] == 1: Z acts on the ith qubit + x_mask[i] == 1 and z_mask[i] == 1: Y acts on the ith qubit + x_mask[i] == 0 and z_mask[i] == 0: I acts on the ith qubit Args: U: A square array whose dimension is a power of 2. @@ -89,8 +101,14 @@ def unitary_to_pauli_string(U: npt.NDArray, eps: float = 1e-15) -> Optional[Dens return None def select(i): + """Returns the gate that acts on the ith qubit.""" has_x = (x_msk >> i) & 1 has_z = (z_msk >> i) & 1 + # The mapping is: + # - has_x and not has_z => X + # - not has_x and has_z => Z + # - has_x and has_z => Y + # - not has_x and not has_z => I gate_table = ['IX', 'ZY'] return gate_table[has_z][has_x] diff --git a/cirq-core/cirq/transformers/analytical_decompositions/pauli_string_decomposition_test.py b/cirq-core/cirq/transformers/analytical_decompositions/pauli_string_decomposition_test.py index 99ce2ca3d67..d924255b3a0 100644 --- a/cirq-core/cirq/transformers/analytical_decompositions/pauli_string_decomposition_test.py +++ b/cirq-core/cirq/transformers/analytical_decompositions/pauli_string_decomposition_test.py @@ -51,8 +51,8 @@ def test_unitary_to_pauli_string_non_pauli_input(): def test_invalid_input(): - with pytest.raises(ValueError): + with pytest.raises(ValueError, match='Input has a none square shape.*'): _ = unitary_to_pauli_string(np.zeros((2, 3))) - with pytest.raises(ValueError): + with pytest.raises(ValueError, match='Input dimension [0-9]* isn\'t a power of 2'): _ = unitary_to_pauli_string(np.zeros((3, 3))) From 147b1021d42dacc9c9200339f3fb10c04d8360c4 Mon Sep 17 00:00:00 2001 From: Noureldin Date: Wed, 5 Jul 2023 19:44:52 +0100 Subject: [PATCH 8/9] fixed nits --- .../analytical_decompositions/pauli_string_decomposition.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cirq-core/cirq/transformers/analytical_decompositions/pauli_string_decomposition.py b/cirq-core/cirq/transformers/analytical_decompositions/pauli_string_decomposition.py index cdbee76bbb0..93b14101288 100644 --- a/cirq-core/cirq/transformers/analytical_decompositions/pauli_string_decomposition.py +++ b/cirq-core/cirq/transformers/analytical_decompositions/pauli_string_decomposition.py @@ -68,7 +68,7 @@ def unitary_to_pauli_string(U: npt.NDArray, eps: float = 1e-15) -> Optional[Dens and z_mask be the index of the maximum number of the first column of H†UH each of these indicies is n-bits long where U is 2^n x 2^n. - These two indicies/masks encode in binary the indicies of the qubits that + These two indices/masks encode in binary the indices of the qubits that have I, X, Y, Z acting on them as follows: x_mask[i] == 1 and z_mask[i] == 0: X acts on the ith qubit x_mask[i] == 0 and z_mask[i] == 1: Z acts on the ith qubit @@ -87,7 +87,7 @@ def unitary_to_pauli_string(U: npt.NDArray, eps: float = 1e-15) -> Optional[Dens """ if len(U.shape) != 2 or U.shape[0] != U.shape[1]: - raise ValueError(f'Input has a none square shape {U}') + raise ValueError(f'Input has a non-square shape {U}') n = U.shape[0].bit_length() - 1 if U.shape[0] != 2**n: raise ValueError(f'Input dimension {U.shape[0]} isn\'t a power of 2') From 445b6a6c5fe1a57ac56bd5bde9dff30299cb5b80 Mon Sep 17 00:00:00 2001 From: Noureldin Date: Wed, 5 Jul 2023 19:54:57 +0100 Subject: [PATCH 9/9] fixed text --- .../pauli_string_decomposition_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cirq-core/cirq/transformers/analytical_decompositions/pauli_string_decomposition_test.py b/cirq-core/cirq/transformers/analytical_decompositions/pauli_string_decomposition_test.py index d924255b3a0..d1af1d63c2c 100644 --- a/cirq-core/cirq/transformers/analytical_decompositions/pauli_string_decomposition_test.py +++ b/cirq-core/cirq/transformers/analytical_decompositions/pauli_string_decomposition_test.py @@ -51,7 +51,7 @@ def test_unitary_to_pauli_string_non_pauli_input(): def test_invalid_input(): - with pytest.raises(ValueError, match='Input has a none square shape.*'): + with pytest.raises(ValueError, match='Input has a non-square shape.*'): _ = unitary_to_pauli_string(np.zeros((2, 3))) with pytest.raises(ValueError, match='Input dimension [0-9]* isn\'t a power of 2'):