From d7fd9aeb07eb2a5ffd47966e86d11fa93c6bb630 Mon Sep 17 00:00:00 2001 From: dhruvbhq Date: Fri, 28 Oct 2022 17:32:03 +0200 Subject: [PATCH 01/30] added xp_pauli class --- qiskit_qec/operators/__init__.py | 1 + qiskit_qec/operators/xp_pauli.py | 137 +++++++++++++++++++++++++++++++ 2 files changed, 138 insertions(+) create mode 100644 qiskit_qec/operators/xp_pauli.py diff --git a/qiskit_qec/operators/__init__.py b/qiskit_qec/operators/__init__.py index f65f8144..768bbd11 100644 --- a/qiskit_qec/operators/__init__.py +++ b/qiskit_qec/operators/__init__.py @@ -34,3 +34,4 @@ from .pauli import Pauli from .pauli_list import PauliList from .base_xp_pauli import BaseXPPauli +from .xp_pauli import XPPauli diff --git a/qiskit_qec/operators/xp_pauli.py b/qiskit_qec/operators/xp_pauli.py new file mode 100644 index 00000000..eaddcd7e --- /dev/null +++ b/qiskit_qec/operators/xp_pauli.py @@ -0,0 +1,137 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017, 2020 +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. +# Part of the QEC framework +"""Module for Pauli""" +from typing import Any, Dict, List, Optional, Union + +import numpy as np +from qiskit.circuit import Instruction, QuantumCircuit +from qiskit.circuit.barrier import Barrier +from qiskit.circuit.delay import Delay +from qiskit.circuit.library.generalized_gates import PauliGate +from qiskit.circuit.library.standard_gates import IGate, XGate, YGate, ZGate +from qiskit.exceptions import QiskitError +from qiskit.quantum_info.operators.mixins import generate_apidocs +from qiskit.quantum_info.operators.scalar_op import ScalarOp +from qiskit_qec.operators.base_xp_pauli import BaseXPPauli +from qiskit_qec.utils import xp_pauli_rep + + +class XPPauli(BaseXPPauli): + """`XPPauli` inherits from `BaseXPPauli`""" + + def __init__( + self, + data: Any, + *, + x: Union[List, np.ndarray, None] = None, + z: Union[List, np.ndarray, None] = None, + phase_exp: Union[int, np.ndarray, None] = None, + precision: int = None, + input_xppauli_encoding: str = BaseXPPauli.EXTERNAL_XP_PAULI_ENCODING, + ): + """XPPauli Init + + Args: + data (str): Still in progress + x ([type], optional): [description]. Defaults to None. + z ([type], optional): [description]. Defaults to None. + phase_exponent ([type], optional): [description]. Defaults to None. + + Raises: + QiskitError: Something went wrong. + """ + if isinstance(data, np.ndarray): + matrix = np.atleast_2d(data) + if phase_exp is None: + phase_exp = 0 + elif isinstance(data, BaseXPPauli): + matrix = data.matrix[:, :] + phase_exp = data._phase_exp[:] + # TODO: elif isinstance(data, (tuple, list)), isinstance(data, str), + # isinstance(data, ScalarOp) may be implemented later, like Pauli class + else: + raise QiskitError("Invalid input data for XPPauli.") + + # Initialize BaseXPPauli + if matrix.shape[0] != 1: + raise QiskitError("Input is not a single XPPauli") + + super().__init__(matrix, phase_exp, precision) + self.vlist = self.matrix[0].tolist() + + # --------------------------------------------------------------------- + # Property Methods + # --------------------------------------------------------------------- + + @property + def name(self): + """Unique string identifier for operation type.""" + return "xppauli" + + # TODO: several @property methods exist in Pauli class, analogous methods + # may be added here later. + + @property + def phase_exp(self): + """Return the group phase exponent for the Pauli.""" + # Convert internal Pauli encoding to external Pauli encoding + # return pauli_rep.change_pauli_encoding( + # self._phase_exp, self.num_y, output_pauli_encoding=BasePauli.EXTERNAL_PAULI_ENCODING + # ) + pass + + @phase_exp.setter + def phase_exp(self, value): + # Convert external Pauli encoding to the internal Pauli Encoding + # self._phase_exp[:] = pauli_rep.change_pauli_encoding( + # value, + # self.num_y, + # input_pauli_encoding=BasePauli.EXTERNAL_PAULI_ENCODING, + # output_pauli_encoding=pauli_rep.INTERNAL_PAULI_ENCODING, + # same_type=False, + # ) + pass + + @property + def phase(self): + """Return the complex phase of the Pauli""" + # return pauli_rep.exp2cpx(self.phase_exp, input_encoding=BasePauli.EXTERNAL_PHASE_ENCODING) + pass + + @property + def x(self): + """The x vector for the XPPauli.""" + return self.matrix[:, : self.num_qubits][0] + + @x.setter + def x(self, val): + self.matrix[:, : self.num_qubits][0] = val + + @property + def z(self): + """The z vector for the XPPauli.""" + return self.matrix[:, self.num_qubits :][0] + + @z.setter + def z(self, val): + self.matrix[:, self.num_qubits :][0] = val + + def unique_vector_rep(self): + return super().unique_vector_rep() + + def rescale_precision(self, new_precision): + return super().rescale_precision(new_precision) + + +# Update docstrings for API docs +generate_apidocs(XPPauli) From 023960d94d00b6c7a9b7a157cff0c7c2f64cc05b Mon Sep 17 00:00:00 2001 From: dhruvbhq Date: Fri, 28 Oct 2022 17:33:56 +0200 Subject: [PATCH 02/30] added functions for random xp, precision rescaling, unique vector --- qiskit_qec/operators/base_xp_pauli.py | 68 +++++++++++++++++++++++++++ qiskit_qec/operators/random.py | 32 +++++++++++++ 2 files changed, 100 insertions(+) diff --git a/qiskit_qec/operators/base_xp_pauli.py b/qiskit_qec/operators/base_xp_pauli.py index 6c6431b3..d69af1bf 100644 --- a/qiskit_qec/operators/base_xp_pauli.py +++ b/qiskit_qec/operators/base_xp_pauli.py @@ -90,6 +90,9 @@ def __init__( See Also: Pauli, PauliList """ + assert (type(precision) == int) and (precision > 1), QiskitError( + "Precision of XP operators must be an integer greater than or equal to 2." + ) if matrix is None or matrix.size == 0: matrix = np.empty(shape=(0, 0), dtype=np.int64) @@ -104,6 +107,7 @@ def __init__( matrix = nmatrix self.matrix = matrix + self.precision = precision self._num_paulis = self.matrix.shape[0] if phase_exp is None: self._phase_exp = np.zeros(shape=(self.matrix.shape[0],), dtype=np.int64) @@ -397,6 +401,70 @@ def _append_circuit( """_summary_""" pass + # --------------------------------------------------------------------- + # BaseXPPauli methods for XP arithmetic + # --------------------------------------------------------------------- + + def unique_vector_rep(self): + return self._unique_vector_rep() + + def _unique_vector_rep(self): + """(TODO improve doc): This is the equivalent of XPRound from Mark's + code. It converts the XPPauli operator into unique vector form, ie + phase_exp in Z modulo 2*precision, x in Z_2, z in Z modulo + precision.""" + matrix = np.empty(shape=np.shape(self.matrix), dtype=np.int64) + + phase_exp = np.mod(self._phase_exp, 2 * self.precision) + matrix[:, : self.num_qubits][0] = np.mod(self.matrix[:, : self.num_qubits][0], 2) + matrix[:, self.num_qubits : ][0] = np.mod( + self.matrix[:, self.num_qubits : ][0], self.precision + ) + + return BaseXPPauli(matrix, phase_exp, self.precision) + + def rescale_precision(self, new_precision): + return self._rescale_precision(new_precision) + + def _rescale_precision(self, new_precision): + """(TODO improve doc): This is the equivalent of XPSetNsingle from + Mark's code. It rescales the generalized symplectic vector components + of XPPauli operator to the new precision. Returns None if the + rescaling is not possible, else returns the rescaled BaseXPPauli object.""" + + # TODO this code will probably only work for XPPauli, may need to be upgraded for XPPauliList + unique_xp_op = self.unique_vector_rep() + + if new_precision > unique_xp_op.precision: + if np.mod(new_precision, unique_xp_op.precision > 0): + return None + matrix = np.empty(shape=np.shape(unique_xp_op.matrix), dtype=np.int64) + scale_factor = new_precision // unique_xp_op.precision + phase_exp = scale_factor * unique_xp_op.phase_exp + matrix[:, unique_xp_op.num_qubits :][0] = ( + scale_factor * unique_xp_op.matrix[:, unique_xp_op.num_qubits :][0] + ) + + elif new_precision < unique_xp_op.precision: + scale_factor = unique_xp_op.precision // new_precision + if( + (unique_xp_op.precision % new_precision > 0) + or (np.sum(np.mod(unique_xp_op._phase_exp, scale_factor)) > 0) + or ( + np.sum( + np.mod(unique_xp_op.matrix[:, unique_xp_op.num_qubits :][0], scale_factor) + ) + > 0 + ) + ): + return None + matrix = np.empty(shape=np.shape(unique_xp_op.matrix), dtype=np.int64) + phase_exp = unique_xp_op._phase_exp // scale_factor + matrix[:, 0 : unique_xp_op.num_qubits][0] = unique_xp_op.matrix[:, 0 : unique_xp_op.num_qubits][0] + matrix[:, unique_xp_op.num_qubits :][0] = unique_xp_op.matrix[:, unique_xp_op.num_qubits :][0] // scale_factor + + return BaseXPPauli(matrix, phase_exp, new_precision) + # --------------------------------------------------------------------- # Evolution by Clifford gates diff --git a/qiskit_qec/operators/random.py b/qiskit_qec/operators/random.py index 14d8a704..b21e97fb 100644 --- a/qiskit_qec/operators/random.py +++ b/qiskit_qec/operators/random.py @@ -196,6 +196,38 @@ def random_clifford(num_qubits, seed=None): return Clifford(StabilizerTable(table, phase)) +def random_xppauli(num_qubits, precision=None, seed=None): + """Return a random XPPauli. + + Args: + num_qubits (int): the number of qubits. + precision (int): Precision of XP operators. Must be an integer + greater than or equal to 2. + seed (int or np.random.Generator): Optional. Set a fixed seed or + generator for RNG. + + Returns: + XPPauli: a random XPPauli + """ + if seed is None: + rng = np.random.default_rng() + elif isinstance(seed, np.random.Generator): + rng = seed + else: + rng = default_rng(seed) + z = rng.integers(precision, size=num_qubits, dtype=np.int64) + x = rng.integers(2, size=num_qubits, dtype=bool) + # TODO: Need to decide whether we will add an argument group_phase in + # analogy with random_pauli. If yes, its implementation goes here. + # Mark's code randomizes phase modulo 2*precision. + phase = rng.integers(2 * precision, dtype=np.int64) + xppauli = XPPauli((z, x, phase)) + return xppauli + + +# TODO: def random_xppauli_list(): + + def _sample_qmallows(n, rng=None): """Sample from the quantum Mallows distribution""" From 0d33c168aa3418728d5f278a9064f4217e06fd4e Mon Sep 17 00:00:00 2001 From: dhruvbhq Date: Fri, 28 Oct 2022 17:34:56 +0200 Subject: [PATCH 03/30] added test_xp_pauli and simple test --- tests/operators/test_xp_pauli.py | 77 ++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 tests/operators/test_xp_pauli.py diff --git a/tests/operators/test_xp_pauli.py b/tests/operators/test_xp_pauli.py new file mode 100644 index 00000000..81611f90 --- /dev/null +++ b/tests/operators/test_xp_pauli.py @@ -0,0 +1,77 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017, 2020. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +# pylint: disable=invalid-name + +"""Tests for Pauli operator class.""" + +import unittest +import itertools as it +from functools import lru_cache + +import numpy as np +from ddt import ddt, data, unpack + +from qiskit import QuantumCircuit +from qiskit.exceptions import QiskitError +from qiskit.test import QiskitTestCase + +from qiskit.quantum_info.operators import Operator + +from qiskit_qec.operators.random import random_clifford, random_pauli, random_xppauli +from qiskit_qec.operators.base_xp_pauli import BaseXPPauli +from qiskit_qec.operators.xp_pauli import XPPauli + +# TODO from qiskit_qec.utils.pauli_rep import split_pauli, cpxstr2exp + +# from qiskit.quantum_info.operators.symplectic.pauli import _split_pauli_label, _phase_from_label + + +@ddt +class TestXPPauliInit(QiskitTestCase): + """Tests for XPPauli initialization.""" + + def test_array_init(self): + """Test array initialization.""" + # Test case taken from Mark's paper on XPF + matrix = np.array([1, 1, 1, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0], dtype=np.int64) + phase_exp = 12 + precision = 8 + xppauli = XPPauli(data=matrix, phase_exp=phase_exp, precision=precision) + np.testing.assert_equal(xppauli.matrix, np.atleast_2d(matrix)) + np.testing.assert_equal(xppauli._phase_exp, phase_exp) + np.testing.assert_equal(xppauli.precision, precision) + + +@ddt +class TestXPPauli(QiskitTestCase): + """Tests for XPPauli operator class.""" + + def test_precision_rescale(self): + """Test precision rescaling method.""" + # Test case taken from Mark's paper on XPF + matrix = np.array([1, 1, 1, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0], dtype=np.int64) + phase_exp = 12 + precision = 8 + new_precision = 2 + xppauli = XPPauli(data=matrix, phase_exp=phase_exp, precision=precision) + rescaled_xppauli = xppauli.rescale_precision(new_precision=new_precision) + target_matrix = np.array([1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0], dtype=np.int64) + target_phase_exp = 3 + target_xppauli = XPPauli(data=target_matrix, phase_exp=target_phase_exp, precision=new_precision) + np.testing.assert_equal(target_xppauli.matrix, rescaled_xppauli.matrix) + np.testing.assert_equal(target_xppauli._phase_exp, rescaled_xppauli._phase_exp) + np.testing.assert_equal(target_xppauli.precision, rescaled_xppauli.precision) + + +if __name__ == "__main__": + unittest.main() From ec28c91fd65f1c25544f8796a3854a61d0504e2e Mon Sep 17 00:00:00 2001 From: dhruvbhq Date: Fri, 28 Oct 2022 18:02:28 +0200 Subject: [PATCH 04/30] added weight / XPDistance function --- qiskit_qec/operators/base_xp_pauli.py | 11 +++++++++++ qiskit_qec/operators/xp_pauli.py | 2 ++ tests/operators/test_xp_pauli.py | 10 ++++++++++ 3 files changed, 23 insertions(+) diff --git a/qiskit_qec/operators/base_xp_pauli.py b/qiskit_qec/operators/base_xp_pauli.py index d69af1bf..d618ab09 100644 --- a/qiskit_qec/operators/base_xp_pauli.py +++ b/qiskit_qec/operators/base_xp_pauli.py @@ -465,6 +465,17 @@ def _rescale_precision(self, new_precision): return BaseXPPauli(matrix, phase_exp, new_precision) + def weight(self): + return self._weight() + + def _weight(self): + """(TODO improve doc) This is the equivalent of XPDistance function + from Mark's code. It returns the count of qubits where either z or x + component is nonzero.""" + # TODO Since 'distance' has a specific meaning in QECCs, for now, the + # name 'weight' has been used for this function. + return np.sum(np.logical_and(self.x, self.z), axis=-1) + # --------------------------------------------------------------------- # Evolution by Clifford gates diff --git a/qiskit_qec/operators/xp_pauli.py b/qiskit_qec/operators/xp_pauli.py index eaddcd7e..a4615b8d 100644 --- a/qiskit_qec/operators/xp_pauli.py +++ b/qiskit_qec/operators/xp_pauli.py @@ -132,6 +132,8 @@ def unique_vector_rep(self): def rescale_precision(self, new_precision): return super().rescale_precision(new_precision) + def weight(self): + return super().weight() # Update docstrings for API docs generate_apidocs(XPPauli) diff --git a/tests/operators/test_xp_pauli.py b/tests/operators/test_xp_pauli.py index 81611f90..b67dee48 100644 --- a/tests/operators/test_xp_pauli.py +++ b/tests/operators/test_xp_pauli.py @@ -71,6 +71,16 @@ def test_precision_rescale(self): np.testing.assert_equal(target_xppauli.matrix, rescaled_xppauli.matrix) np.testing.assert_equal(target_xppauli._phase_exp, rescaled_xppauli._phase_exp) np.testing.assert_equal(target_xppauli.precision, rescaled_xppauli.precision) + + def test_weight(self): + """Test weight method.""" + matrix = np.array([1, 1, 1, 0, 0, 1, 0, 0, 3, 4, 0, 0, 0, 1], dtype=np.int64) + phase_exp = 12 + precision = 8 + xppauli = XPPauli(data=matrix, phase_exp=phase_exp, precision=precision) + value = xppauli.weight() + target = 2 + self.assertEqual(value, target) if __name__ == "__main__": From 84c1af52aadd6db94ad3d0340743004573e257b8 Mon Sep 17 00:00:00 2001 From: dhruvbhq Date: Fri, 28 Oct 2022 18:42:59 +0200 Subject: [PATCH 05/30] added XPisDiag function as is_diagonal --- qiskit_qec/operators/base_xp_pauli.py | 9 +++++++- qiskit_qec/operators/xp_pauli.py | 6 ++++++ tests/operators/test_xp_pauli.py | 31 +++++++++++++++++++++++++-- 3 files changed, 43 insertions(+), 3 deletions(-) diff --git a/qiskit_qec/operators/base_xp_pauli.py b/qiskit_qec/operators/base_xp_pauli.py index d618ab09..0f5fb60d 100644 --- a/qiskit_qec/operators/base_xp_pauli.py +++ b/qiskit_qec/operators/base_xp_pauli.py @@ -474,8 +474,15 @@ def _weight(self): component is nonzero.""" # TODO Since 'distance' has a specific meaning in QECCs, for now, the # name 'weight' has been used for this function. - return np.sum(np.logical_and(self.x, self.z), axis=-1) + return np.sum(np.logical_or(self.x, self.z), axis=-1) + def is_diagonal(self): + return self._is_diagonal() + + def _is_diagonal(self): + """(TODO improve doc) This is the equivalent of XPisDiag function from + Mark's code. Returns True if the XP operator is diagonal.""" + return np.where(np.sum(self.x, axis=-1)==0, True, False) # --------------------------------------------------------------------- # Evolution by Clifford gates diff --git a/qiskit_qec/operators/xp_pauli.py b/qiskit_qec/operators/xp_pauli.py index a4615b8d..a7f53367 100644 --- a/qiskit_qec/operators/xp_pauli.py +++ b/qiskit_qec/operators/xp_pauli.py @@ -126,6 +126,9 @@ def z(self): def z(self, val): self.matrix[:, self.num_qubits :][0] = val + # TODO: as of now, these methods just call the base class method. If no + # class-specific code is needed in these functions, it should be possible + # to remove this code. def unique_vector_rep(self): return super().unique_vector_rep() @@ -135,5 +138,8 @@ def rescale_precision(self, new_precision): def weight(self): return super().weight() + def is_diagonal(self): + return super().is_diagonal() + # Update docstrings for API docs generate_apidocs(XPPauli) diff --git a/tests/operators/test_xp_pauli.py b/tests/operators/test_xp_pauli.py index b67dee48..81c6f827 100644 --- a/tests/operators/test_xp_pauli.py +++ b/tests/operators/test_xp_pauli.py @@ -79,9 +79,36 @@ def test_weight(self): precision = 8 xppauli = XPPauli(data=matrix, phase_exp=phase_exp, precision=precision) value = xppauli.weight() - target = 2 + target = 5 + self.assertEqual(value, target) + + def test_diagonal(self): + """Test is_diagonal method.""" + # Test case taken from Mark's paper, Table 5. + matrix = np.array([0, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3], dtype=np.int64) + phase_exp = 0 + precision = 8 + xppauli = XPPauli(data=matrix, phase_exp=phase_exp, precision=precision) + value = xppauli.is_diagonal() + target = np.array([True]) + self.assertEqual(value, target) + + # Test case taken from Mark's paper, Table 5. + matrix = np.array([0, 0, 0, 0, 0, 0, 0, 0, 1, 3, 3, 3, 3, 3], dtype=np.int64) + phase_exp = 0 + precision = 8 + xppauli = XPPauli(data=matrix, phase_exp=phase_exp, precision=precision) + value = xppauli.is_diagonal() + target = np.array([True]) + self.assertEqual(value, target) + + matrix = np.array([0, 1, 0, 1, 0, 0, 0, 0, 1, 3, 3, 3, 3, 3], dtype=np.int64) + phase_exp = 12 + precision = 8 + xppauli = XPPauli(data=matrix, phase_exp=phase_exp, precision=precision) + value = xppauli.is_diagonal() + target = np.array([False]) self.assertEqual(value, target) - if __name__ == "__main__": unittest.main() From b6b488e2d28cd78f4baca5e1acba2cb4912e9851 Mon Sep 17 00:00:00 2001 From: dhruvbhq Date: Sat, 29 Oct 2022 16:34:08 +0200 Subject: [PATCH 06/30] coded XPD (antisymmetric operator) and refactored --- qiskit_qec/operators/base_xp_pauli.py | 39 ++++++++++++++++----------- qiskit_qec/operators/xp_pauli.py | 11 ++++---- tests/operators/test_xp_pauli.py | 15 +++++++++++ 3 files changed, 45 insertions(+), 20 deletions(-) diff --git a/qiskit_qec/operators/base_xp_pauli.py b/qiskit_qec/operators/base_xp_pauli.py index 0f5fb60d..7c83b41f 100644 --- a/qiskit_qec/operators/base_xp_pauli.py +++ b/qiskit_qec/operators/base_xp_pauli.py @@ -416,10 +416,8 @@ def _unique_vector_rep(self): matrix = np.empty(shape=np.shape(self.matrix), dtype=np.int64) phase_exp = np.mod(self._phase_exp, 2 * self.precision) - matrix[:, : self.num_qubits][0] = np.mod(self.matrix[:, : self.num_qubits][0], 2) - matrix[:, self.num_qubits : ][0] = np.mod( - self.matrix[:, self.num_qubits : ][0], self.precision - ) + matrix[:, : self.num_qubits][0] = np.mod(self.x, 2) + matrix[:, self.num_qubits : ][0] = np.mod(self.z, self.precision) return BaseXPPauli(matrix, phase_exp, self.precision) @@ -441,27 +439,20 @@ def _rescale_precision(self, new_precision): matrix = np.empty(shape=np.shape(unique_xp_op.matrix), dtype=np.int64) scale_factor = new_precision // unique_xp_op.precision phase_exp = scale_factor * unique_xp_op.phase_exp - matrix[:, unique_xp_op.num_qubits :][0] = ( - scale_factor * unique_xp_op.matrix[:, unique_xp_op.num_qubits :][0] - ) + matrix[:, unique_xp_op.num_qubits :][0] = (scale_factor * unique_xp_op.z) elif new_precision < unique_xp_op.precision: scale_factor = unique_xp_op.precision // new_precision if( (unique_xp_op.precision % new_precision > 0) or (np.sum(np.mod(unique_xp_op._phase_exp, scale_factor)) > 0) - or ( - np.sum( - np.mod(unique_xp_op.matrix[:, unique_xp_op.num_qubits :][0], scale_factor) - ) - > 0 - ) + or (np.sum(np.mod(unique_xp_op.z, scale_factor)) > 0) ): return None matrix = np.empty(shape=np.shape(unique_xp_op.matrix), dtype=np.int64) phase_exp = unique_xp_op._phase_exp // scale_factor - matrix[:, 0 : unique_xp_op.num_qubits][0] = unique_xp_op.matrix[:, 0 : unique_xp_op.num_qubits][0] - matrix[:, unique_xp_op.num_qubits :][0] = unique_xp_op.matrix[:, unique_xp_op.num_qubits :][0] // scale_factor + matrix[:, 0 : unique_xp_op.num_qubits][0] = unique_xp_op.x + matrix[:, unique_xp_op.num_qubits :][0] = unique_xp_op.z // scale_factor return BaseXPPauli(matrix, phase_exp, new_precision) @@ -484,6 +475,24 @@ def _is_diagonal(self): Mark's code. Returns True if the XP operator is diagonal.""" return np.where(np.sum(self.x, axis=-1)==0, True, False) + def antisymmetric_op(self): + return self._antisymmetric_op() + + def _antisymmetric_op(self): + """(TODO improve doc) This is the equivalent of XPD function from + Mark's code. It returns the antisymmetric operator corresponding to the + z component of XP operator, only if x component is 0, else it returns + None.""" + + if np.any(self.x): + return None + + phase_exp = np.sum(self.z, axis = -1) + x = np.zeros(np.shape(self.z)) + matrix = np.concatenate((x, -self.z), axis = -1) + + return BaseXPPauli(matrix=matrix, phase_exp=phase_exp, precision=self.precision) + # --------------------------------------------------------------------- # Evolution by Clifford gates # --------------------------------------------------------------------- diff --git a/qiskit_qec/operators/xp_pauli.py b/qiskit_qec/operators/xp_pauli.py index a7f53367..aaa2825d 100644 --- a/qiskit_qec/operators/xp_pauli.py +++ b/qiskit_qec/operators/xp_pauli.py @@ -57,6 +57,7 @@ def __init__( elif isinstance(data, BaseXPPauli): matrix = data.matrix[:, :] phase_exp = data._phase_exp[:] + precision = data.precision # TODO: elif isinstance(data, (tuple, list)), isinstance(data, str), # isinstance(data, ScalarOp) may be implemented later, like Pauli class else: @@ -126,14 +127,11 @@ def z(self): def z(self, val): self.matrix[:, self.num_qubits :][0] = val - # TODO: as of now, these methods just call the base class method. If no - # class-specific code is needed in these functions, it should be possible - # to remove this code. def unique_vector_rep(self): - return super().unique_vector_rep() + return XPPauli(super().unique_vector_rep()) def rescale_precision(self, new_precision): - return super().rescale_precision(new_precision) + return XPPauli(super().rescale_precision(new_precision)) def weight(self): return super().weight() @@ -141,5 +139,8 @@ def weight(self): def is_diagonal(self): return super().is_diagonal() + def antisymmetric_op(self): + return XPPauli(super().antisymmetric_op()) + # Update docstrings for API docs generate_apidocs(XPPauli) diff --git a/tests/operators/test_xp_pauli.py b/tests/operators/test_xp_pauli.py index 81c6f827..ba76e960 100644 --- a/tests/operators/test_xp_pauli.py +++ b/tests/operators/test_xp_pauli.py @@ -110,5 +110,20 @@ def test_diagonal(self): target = np.array([False]) self.assertEqual(value, target) + def test_antisymmetric_op(self): + """Test antisymmetric_op method.""" + + matrix = np.array([0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 3, 3, 3], dtype=np.int64) + phase_exp = 0 + precision = 8 + xppauli = XPPauli(data=matrix, phase_exp=phase_exp, precision=precision) + value = xppauli.antisymmetric_op() + + target_matrix = np.array([0, 0, 0, 0, 0, 0, 0, 0, -1, -2, -3, -3, -3, -3], dtype=np.int64) + target_phase_exp = 15 + target_precision = 8 + target = XPPauli(data=target_matrix, phase_exp=target_phase_exp, precision=target_precision) + self.assertEqual(value, target) + if __name__ == "__main__": unittest.main() From 94b3df2996e8b3f9218f96e8bdeb3fb3fff4c5f7 Mon Sep 17 00:00:00 2001 From: dhruvbhq Date: Sat, 29 Oct 2022 18:51:07 +0200 Subject: [PATCH 07/30] added XPPower --- qiskit_qec/operators/base_xp_pauli.py | 43 ++++++++++++++++++++++----- qiskit_qec/operators/xp_pauli.py | 3 ++ tests/operators/test_xp_pauli.py | 15 ++++++++++ 3 files changed, 53 insertions(+), 8 deletions(-) diff --git a/qiskit_qec/operators/base_xp_pauli.py b/qiskit_qec/operators/base_xp_pauli.py index 7c83b41f..8bcd52c8 100644 --- a/qiskit_qec/operators/base_xp_pauli.py +++ b/qiskit_qec/operators/base_xp_pauli.py @@ -125,46 +125,46 @@ def __init__( @property def x(self): """_summary_""" - pass + return self.matrix[:, : self.num_qubits] @x.setter def x(self, val: np.ndarray): """_summary_""" - pass + self.matrix[:, : self.num_qubits] = val # @final Add when python >= 3.8 @property def _x(self): # pylint: disable=invalid-name """_summary_""" - pass + return self.matrix[:, : self.num_qubits] # @final Add when python >= 3.8 @_x.setter def _x(self, val): # pylint: disable=invalid-name """_summary_""" - pass + self.matrix[:, : self.num_qubits] = val @property def z(self): """_summary_""" - pass + return self.matrix[:, self.num_qubits :] @z.setter def z(self, val): """_summary_""" - pass + self.matrix[:, self.num_qubits :] = val # @final Add when python >= 3.8 @property def _z(self): # pylint: disable=invalid-name """_summary_""" - pass + return self.matrix[:, self.num_qubits :] # @final Add when python >= 3.8 @_z.setter def _z(self, val): # pylint: disable=invalid-name """_summary_""" - pass + self.matrix[:, self.num_qubits :] = val @property def num_y(self): @@ -493,6 +493,33 @@ def _antisymmetric_op(self): return BaseXPPauli(matrix=matrix, phase_exp=phase_exp, precision=self.precision) + def power(self, n): + return self._power(n) + + def _power(self, n): + """(TODO improve doc) This is te equivalent of XPPower function from + Mark's code. It returns the XP operator of specified precision raised + to the power n.""" + # TODO n = np.atleast_1d(n) + a = np.mod(n, 2) + + x = np.multiply(self.x, a) + z = np.multiply(self.z, n) + phase_exp = np.multiply(self._phase_exp, n) + matrix = np.concatenate((x, z), axis=-1) + first = BaseXPPauli(matrix=matrix, phase_exp=phase_exp, precision=self.precision) + + x = np.zeros(np.shape(self.z)) + z = np.multiply((n-a), np.multiply(self.x, self.z)) + matrix = np.concatenate((x, z), axis=-1) + second_temp = BaseXPPauli(matrix=matrix, precision=self.precision) + second = second_temp.antisymmetric_op() + + product = BaseXPPauli(matrix=first.matrix+second.matrix, phase_exp=first._phase_exp+second._phase_exp, precision=self.precision) + + return product._unique_vector_rep() + + # --------------------------------------------------------------------- # Evolution by Clifford gates # --------------------------------------------------------------------- diff --git a/qiskit_qec/operators/xp_pauli.py b/qiskit_qec/operators/xp_pauli.py index aaa2825d..52e9dbe5 100644 --- a/qiskit_qec/operators/xp_pauli.py +++ b/qiskit_qec/operators/xp_pauli.py @@ -142,5 +142,8 @@ def is_diagonal(self): def antisymmetric_op(self): return XPPauli(super().antisymmetric_op()) + def power(self, n): + return XPPauli(super().power(n)) + # Update docstrings for API docs generate_apidocs(XPPauli) diff --git a/tests/operators/test_xp_pauli.py b/tests/operators/test_xp_pauli.py index ba76e960..99ad18a4 100644 --- a/tests/operators/test_xp_pauli.py +++ b/tests/operators/test_xp_pauli.py @@ -125,5 +125,20 @@ def test_antisymmetric_op(self): target = XPPauli(data=target_matrix, phase_exp=target_phase_exp, precision=target_precision) self.assertEqual(value, target) + def test_power(self): + """Test power method.""" + matrix = np.array([1, 1, 1, 0, 0, 1, 0, 0, 3, 4, 0, 0, 0, 1], dtype=np.int64) + phase_exp = 12 + precision = 8 + n = 5 + xppauli = XPPauli(data=matrix, phase_exp=phase_exp, precision=precision) + value = xppauli.power(n=n) + + target_matrix = np.array([1, 1, 1, 0, 0, 1, 0, 0, 3, 4, 0, 0, 0, 5], dtype=np.int64) + target_phase_exp = 15 + target_precision = 8 + target = XPPauli(data=target_matrix, phase_exp=target_phase_exp, precision=target_precision) + self.assertEqual(value, target) + if __name__ == "__main__": unittest.main() From 6d69c3c65405f1638e1d6e2408af1e74475bdebe Mon Sep 17 00:00:00 2001 From: dhruvbhq Date: Sat, 29 Oct 2022 22:05:56 +0200 Subject: [PATCH 08/30] added XP multiplication --- qiskit_qec/operators/base_xp_pauli.py | 128 ++++++++++++++++++++++++-- qiskit_qec/operators/xp_pauli.py | 49 +++++++++- tests/operators/test_xp_pauli.py | 32 +++++-- 3 files changed, 196 insertions(+), 13 deletions(-) diff --git a/qiskit_qec/operators/base_xp_pauli.py b/qiskit_qec/operators/base_xp_pauli.py index 8bcd52c8..609e270a 100644 --- a/qiskit_qec/operators/base_xp_pauli.py +++ b/qiskit_qec/operators/base_xp_pauli.py @@ -262,8 +262,61 @@ def compose( front: bool = False, inplace: bool = False, ) -> "BaseXPPauli": - """_summary_""" - pass + r"""Return the composition of XPPaulis lists + + To be consistent with other compose functions in Qiskit, composition is defined via + left multiplication. That is + + A.compose(B) = B.A = B.dot(A) = A.compose(B, front=False) + + where . is the Pauli group multiplication and so B is applied after A. Likewise + + A.compose(B, front=True) = A.B = A.dot(B) + + That is B is applied first or at the front. + + This compose is: + + [A_1,A_2,...,A_k].compose([B_1,B_2,...,B_k]) = [A_1.compose(B_1),...,A_k.compose(B_k)] + + or + + [A].compose([B_1,B_2,...,B_k])) = [A.compose(B_1),...,A.compose(B_k)] + + Note: + This method does compose coordinate wise (which is different from the PauliTable compose + which should be corrected at some point). + + Args: + other: BaseXPPauli + front (bool): (default: False) + qargs (list or None): Optional, qubits to apply compose on + on (default: None->All). + inplace (bool): If True update in-place (default: False). + + Returns: + BaseXPPauli : Compositon of self and other + + Raises: + QiskitError: if number of qubits of other does not match qargs. + """ + + # Validation + if qargs is None and other.num_qubits != self.num_qubits: + raise QiskitError(f"other {type(self).__name__} must be on the same number of qubits.") + + if qargs and other.num_qubits != len(qargs): + raise QiskitError( + f"Number of qubits of the other {type(self).__name__} does not match qargs." + ) + + if other._num_paulis not in [1, self._num_paulis]: + raise QiskitError( + "Incompatible BaseXPPaulis. Second list must " + "either have 1 or the same number of XPPaulis." + ) + + return self._compose(self, other, qargs=qargs, front=front, inplace=inplace) @staticmethod def _compose( @@ -273,8 +326,66 @@ def _compose( front: bool = False, inplace: bool = False, ) -> "BaseXPPauli": - """_summary_""" - pass + """Returns the composition of two BaseXPPauli objects. + + Args: + a : BaseXPPauli object + b : BaseXPPauli object + qargs (Optional[list], optional): _description_. Defaults to None. + front (bool, optional): _description_. Defaults to False. + inplace (bool, optional): _description_. Defaults to False. + + Returns: + BaseXPPauli: _description_ + """ + + assert a.precision == b.precision, QiskitError("Precision of the two BaseXPPaulis to be multiplied must be the same.") + + if qargs is not None: + qargs = list(qargs) + [item + a.num_qubits for item in qargs] + amat = a.matrix[:, qargs] + else: + amat = a.matrix + bmat = b.matrix + + # Calculate the sum of generalized symplectic matrix for the composition, excluding D + x = np.logical_xor(amat[:, : a.num_qubits], bmat[:, : b.num_qubits]) + z = amat[:, a.num_qubits :] + bmat[:, b.num_qubits :] + mat = np.concatenate((x, z), axis=-1) + + # Calculate the phase of the composition, excluding D + phase_exp = a._phase_exp + b._phase_exp + # Calculate antisymmetric operator, i.e. D + if front: + Dx = np.zeros(np.shape(a.x)) + Dz = 2 * np.multiply(b.x, a.z) + Dmat = np.concatenate((Dx, Dz), axis=-1) + D = BaseXPPauli(matrix=Dmat, precision=a.precision)._antisymmetric_op() + else: + Dx = np.zeros(np.shape(a.x)) + Dz = 2 * np.multiply(a.x, b.z) + Dmat = np.concatenate((Dx, Dz), axis=-1) + D = BaseXPPauli(matrix=Dmat, precision=a.precision)._antisymmetric_op() + + if qargs is None: + if not inplace: + result_x = np.logical_xor(x, D.x) + result_z = z + D.z + result_phase_exp = phase_exp + D._phase_exp + result_mat = np.concatenate((result_x, result_z), axis=-1) + return BaseXPPauli(matrix=result_mat, phase_exp=result_phase_exp, precision=a.precision)._unique_vector_rep() + # Inplace update + a.x = np.logical_xor(x, D.x) + a.z = z + D.z + a._phase_exp = phase_exp + D._phase_exp + return a._unique_vector_rep() + + # Qargs update + ret = a if inplace else a.copy() + ret.matrix[:, qargs] = mat + ret._phase_exp = phase_exp + D._phase_exp + ret = ret._unique_vector_rep() + return ret # --------------------------------------------------------------------- @@ -500,6 +611,10 @@ def _power(self, n): """(TODO improve doc) This is te equivalent of XPPower function from Mark's code. It returns the XP operator of specified precision raised to the power n.""" + # TODO at present, this function only handles positive powers. If it is + # supposed to calculate inverses as well, that functionality needs to + # be coded. + # TODO n = np.atleast_1d(n) a = np.mod(n, 2) @@ -512,14 +627,15 @@ def _power(self, n): x = np.zeros(np.shape(self.z)) z = np.multiply((n-a), np.multiply(self.x, self.z)) matrix = np.concatenate((x, z), axis=-1) - second_temp = BaseXPPauli(matrix=matrix, precision=self.precision) - second = second_temp.antisymmetric_op() + second = BaseXPPauli(matrix=matrix, precision=self.precision).antisymmetric_op() product = BaseXPPauli(matrix=first.matrix+second.matrix, phase_exp=first._phase_exp+second._phase_exp, precision=self.precision) return product._unique_vector_rep() + + # --------------------------------------------------------------------- # Evolution by Clifford gates # --------------------------------------------------------------------- diff --git a/qiskit_qec/operators/xp_pauli.py b/qiskit_qec/operators/xp_pauli.py index 52e9dbe5..891a1f44 100644 --- a/qiskit_qec/operators/xp_pauli.py +++ b/qiskit_qec/operators/xp_pauli.py @@ -68,7 +68,8 @@ def __init__( raise QiskitError("Input is not a single XPPauli") super().__init__(matrix, phase_exp, precision) - self.vlist = self.matrix[0].tolist() + # TODO check if this is needed + # self.vlist = self.matrix[0].tolist() # --------------------------------------------------------------------- # Property Methods @@ -127,6 +128,52 @@ def z(self): def z(self, val): self.matrix[:, self.num_qubits :][0] = val + # --------------------------------------------------------------------- + # BaseOperator methods + # --------------------------------------------------------------------- + + def compose( + self, + other: Union["XPPauli", BaseXPPauli], + qargs: Optional[List[int]] = None, + front: bool = False, + inplace: bool = False, + ) -> "XPPauli": + """Return the operator composition with another XPPauli. + + Args: + other (XPPauli): a XPPauli object. + qargs (list or None): Optional, qubits to apply dot product + on (default: None). + front (bool): If True compose using right operator multiplication, + instead of left multiplication [default: False]. + inplace (bool): If True update in-place (default: False). + + Returns: + XPPauli: The composed XPPauli. + + Raises: + QiskitError: if other cannot be converted to an operator, or has + incompatible dimensions for specified subsystems. + + .. note:: + Composition (``&``) by default is defined as `left` matrix multiplication for + matrix operators, while :meth:`dot` is defined as `right` matrix + multiplication. That is that ``A & B == A.compose(B)`` is equivalent to + ``B.dot(A)`` when ``A`` and ``B`` are of the same type. + + Setting the ``front=True`` kwarg changes this to `right` matrix + multiplication and is equivalent to the :meth:`dot` method + ``A.dot(B) == A.compose(B, front=True)``. + """ + if qargs is None: + qargs = getattr(other, "qargs", None) + if not isinstance(other, XPPauli): + other = XPPauli(other) + return XPPauli(super().compose(other, qargs=qargs, front=front, inplace=inplace)) + + # --------------------------------------------------------------------- + def unique_vector_rep(self): return XPPauli(super().unique_vector_rep()) diff --git a/tests/operators/test_xp_pauli.py b/tests/operators/test_xp_pauli.py index 99ad18a4..298631cf 100644 --- a/tests/operators/test_xp_pauli.py +++ b/tests/operators/test_xp_pauli.py @@ -80,7 +80,7 @@ def test_weight(self): xppauli = XPPauli(data=matrix, phase_exp=phase_exp, precision=precision) value = xppauli.weight() target = 5 - self.assertEqual(value, target) + self.assertEqual(target, value) def test_diagonal(self): """Test is_diagonal method.""" @@ -91,7 +91,7 @@ def test_diagonal(self): xppauli = XPPauli(data=matrix, phase_exp=phase_exp, precision=precision) value = xppauli.is_diagonal() target = np.array([True]) - self.assertEqual(value, target) + self.assertEqual(target, value) # Test case taken from Mark's paper, Table 5. matrix = np.array([0, 0, 0, 0, 0, 0, 0, 0, 1, 3, 3, 3, 3, 3], dtype=np.int64) @@ -100,7 +100,7 @@ def test_diagonal(self): xppauli = XPPauli(data=matrix, phase_exp=phase_exp, precision=precision) value = xppauli.is_diagonal() target = np.array([True]) - self.assertEqual(value, target) + self.assertEqual(target, value) matrix = np.array([0, 1, 0, 1, 0, 0, 0, 0, 1, 3, 3, 3, 3, 3], dtype=np.int64) phase_exp = 12 @@ -108,7 +108,7 @@ def test_diagonal(self): xppauli = XPPauli(data=matrix, phase_exp=phase_exp, precision=precision) value = xppauli.is_diagonal() target = np.array([False]) - self.assertEqual(value, target) + self.assertEqual(target, value) def test_antisymmetric_op(self): """Test antisymmetric_op method.""" @@ -123,7 +123,7 @@ def test_antisymmetric_op(self): target_phase_exp = 15 target_precision = 8 target = XPPauli(data=target_matrix, phase_exp=target_phase_exp, precision=target_precision) - self.assertEqual(value, target) + self.assertEqual(target, value) def test_power(self): """Test power method.""" @@ -138,7 +138,27 @@ def test_power(self): target_phase_exp = 15 target_precision = 8 target = XPPauli(data=target_matrix, phase_exp=target_phase_exp, precision=target_precision) - self.assertEqual(value, target) + self.assertEqual(target, value) + + def test_multiplication(self): + """Test multiplication method.""" + # Test case taken from Mark's code. + a_matrix = np.array([0,1,0,0,2,0], dtype=np.int64) + a_phase_exp = 6 + a_precision = 4 + a = XPPauli(data=a_matrix, phase_exp=a_phase_exp, precision=a_precision) + b_matrix = np.array([1,1,1,3,3,0], dtype=np.int64) + b_phase_exp = 2 + b_precision = 4 + b = XPPauli(data=b_matrix, phase_exp=b_phase_exp, precision=b_precision) + value = XPPauli.compose(a, b) + + target_matrix = np.array([1,0,1,3,3,0], dtype=np.int64) + target_phase_exp = 6 + target_precision = 4 + target = XPPauli(data=target_matrix, phase_exp=target_phase_exp, precision=target_precision) + + self.assertEqual(target, value) if __name__ == "__main__": unittest.main() From fe338eb24ad6ff9cf14964b21b6721ee1a7a24ad Mon Sep 17 00:00:00 2001 From: dhruvbhq Date: Sun, 30 Oct 2022 12:14:08 +0100 Subject: [PATCH 09/30] added XP degree function --- qiskit_qec/operators/base_xp_pauli.py | 30 ++++++++++++++++++++++++++- qiskit_qec/operators/xp_pauli.py | 3 +++ tests/operators/test_xp_pauli.py | 10 +++++++++ 3 files changed, 42 insertions(+), 1 deletion(-) diff --git a/qiskit_qec/operators/base_xp_pauli.py b/qiskit_qec/operators/base_xp_pauli.py index 609e270a..b8c129b6 100644 --- a/qiskit_qec/operators/base_xp_pauli.py +++ b/qiskit_qec/operators/base_xp_pauli.py @@ -633,7 +633,35 @@ def _power(self, n): return product._unique_vector_rep() - + + def degree(self): + return self._degree() + + def _degree(self): + """(TODO improve doc) This is the equivalent of XPDegree from Mark's + code. It returns the degree of XP operator.""" + + gcd = np.gcd(self.z, self.precision) + precision_by_gcd = np.floor_divide(self.precision, gcd) + lcm = np.atleast_1d(precision_by_gcd)[0] + for i in precision_by_gcd: + lcm = np.lcm(lcm, i) + + square = type(self)(BaseXPPauli.compose(self, self)) + gcd_square = np.gcd(square.z, square.precision) + precision_by_gcd_square = np.floor_divide(square.precision, gcd_square) + lcm_square = np.atleast_1d(precision_by_gcd_square)[0] + for i in precision_by_gcd_square: + lcm_square = np.lcm(lcm_square, i) + + lcm_square = 2 * lcm_square + + # TODO it can be explored (maybe with Mark) if the algorithm used when + # the XP operator is non-diagonal (which involves squaring) gives the + # correct output for diagonal XP operators as well (naively that seems + # to be true). If that is the case, then checking np.where and + # is_diagonal can be removed and the code can be optimized a bit. + return np.where(self.is_diagonal(), lcm, lcm_square) # --------------------------------------------------------------------- diff --git a/qiskit_qec/operators/xp_pauli.py b/qiskit_qec/operators/xp_pauli.py index 891a1f44..f1207996 100644 --- a/qiskit_qec/operators/xp_pauli.py +++ b/qiskit_qec/operators/xp_pauli.py @@ -192,5 +192,8 @@ def antisymmetric_op(self): def power(self, n): return XPPauli(super().power(n)) + def degree(self): + return super().degree() + # Update docstrings for API docs generate_apidocs(XPPauli) diff --git a/tests/operators/test_xp_pauli.py b/tests/operators/test_xp_pauli.py index 298631cf..ab698cc1 100644 --- a/tests/operators/test_xp_pauli.py +++ b/tests/operators/test_xp_pauli.py @@ -157,7 +157,17 @@ def test_multiplication(self): target_phase_exp = 6 target_precision = 4 target = XPPauli(data=target_matrix, phase_exp=target_phase_exp, precision=target_precision) + self.assertEqual(target, value) + + def test_degree(self): + """Test degree method.""" + matrix = np.array([0,0,0,2,1,0], dtype=np.int64) + phase_exp = 2 + precision = 4 + xppauli = XPPauli(data=matrix, phase_exp=phase_exp, precision=precision) + value = xppauli.degree() + target = 4 self.assertEqual(target, value) if __name__ == "__main__": From 5ff09b3ce27476a8901d6a4ad992ddc56ecab4fd Mon Sep 17 00:00:00 2001 From: dhruvbhq Date: Sun, 30 Oct 2022 13:04:26 +0100 Subject: [PATCH 10/30] added dummy XPPauliList --- qiskit_qec/operators/__init__.py | 1 + qiskit_qec/operators/base_xp_pauli.py | 1 + qiskit_qec/operators/pauli_list.py | 2 +- qiskit_qec/operators/xp_pauli_list.py | 314 ++++++++++++++++++++++++++ 4 files changed, 317 insertions(+), 1 deletion(-) create mode 100644 qiskit_qec/operators/xp_pauli_list.py diff --git a/qiskit_qec/operators/__init__.py b/qiskit_qec/operators/__init__.py index 768bbd11..75417c02 100644 --- a/qiskit_qec/operators/__init__.py +++ b/qiskit_qec/operators/__init__.py @@ -35,3 +35,4 @@ from .pauli_list import PauliList from .base_xp_pauli import BaseXPPauli from .xp_pauli import XPPauli +from .xp_pauli_list import XPPauliList diff --git a/qiskit_qec/operators/base_xp_pauli.py b/qiskit_qec/operators/base_xp_pauli.py index b8c129b6..519e407c 100644 --- a/qiskit_qec/operators/base_xp_pauli.py +++ b/qiskit_qec/operators/base_xp_pauli.py @@ -108,6 +108,7 @@ def __init__( self.matrix = matrix self.precision = precision + # TODO should _num_paulis be renamed to _num_xppaulis in this and derived classes? self._num_paulis = self.matrix.shape[0] if phase_exp is None: self._phase_exp = np.zeros(shape=(self.matrix.shape[0],), dtype=np.int64) diff --git a/qiskit_qec/operators/pauli_list.py b/qiskit_qec/operators/pauli_list.py index b90aeb61..f2b326a4 100644 --- a/qiskit_qec/operators/pauli_list.py +++ b/qiskit_qec/operators/pauli_list.py @@ -10,7 +10,7 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. # Part of the QEC framework -"""Module fo Pauli List""" +"""Module for Pauli List""" import numbers from collections import defaultdict from typing import Iterable, List, Tuple, Union diff --git a/qiskit_qec/operators/xp_pauli_list.py b/qiskit_qec/operators/xp_pauli_list.py new file mode 100644 index 00000000..6d3a83e8 --- /dev/null +++ b/qiskit_qec/operators/xp_pauli_list.py @@ -0,0 +1,314 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017, 2020 +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. +# Part of the QEC framework +"""Module for XPPauli List""" +import numbers +from typing import Iterable, List, Tuple, Union + +import numpy as np +from qiskit.exceptions import QiskitError +from qiskit.quantum_info.operators.mixins import GroupMixin, LinearMixin +from qiskit_qec.operators.base_xp_pauli import BaseXPPauli +from qiskit_qec.operators.xp_pauli import XPPauli +from qiskit_qec.utils import pauli_rep + + +class XPPauliList(BaseXPPauli, LinearMixin, GroupMixin): + """`XPPauliList` inherits from `BaseXPPauli`""" + + # Set the max number of qubits * paulis before string truncation + _truncate__ = 2000 + + def __init__( + self, + data: Union[ + BaseXPPauli, np.ndarray, Tuple[np.ndarray], Iterable, None + ] = None, + phase_exp: Union[int, np.ndarray, None] = None, + *, + input_pauli_encoding: str = BaseXPPauli.EXTERNAL_XP_PAULI_ENCODING, + input_qubit_order: str = "right-to-left", + tuple_order: str = "zx", + ) -> None: + """Inits a XPPauliList + + Args: + data (str): List of XPPauli Operators. + phase_exp (int, optional): i**phase_exp. Defaults to 0. + input_qubit_order (str, optional): Order to read pdata. Defaults to "right-to-left". + + Raises: + QiskitError: Something went wrong. + """ + if data is None: + matrix = np.empty(shape=(0, 0), dtype=np.bool_) + phase_exp = np.empty(shape=(0,), dtype=np.int8) + elif isinstance(data, BaseXPPauli): + matrix = data.matrix + phase_exp = data._phase_exp + # TODO elif isinstance(data, StabilizerTable), elif isinstance(data, PauliTable) + elif isinstance(data, np.ndarray): + if data.size == 0: + matrix = np.empty(shape=(0, 0), dtype=np.bool_) + phase_exp = np.empty(shape=(0,), dtype=np.int8) + elif isinstance(data[0], str): + matrix, phase_exp = self._from_paulis(data, input_qubit_order) + else: + if phase_exp is None: + phase_exp = 0 + matrix, phase_exp = pauli_rep.from_array( + data, phase_exp, input_pauli_encoding=input_pauli_encoding + ) + # TODO elif isinstance(data, tuple) + else: + # TODO Conversion as iterable of Paulis + pass + + super().__init__(matrix, phase_exp) + + # TODO + # self.paulis = [ + # Pauli(self.matrix[i], phase_exp=self._phase_exp[i]) for i in range(self.matrix.shape[0]) + # ] + + # --------------------------------------------------------------------- + # Init Methods + # --------------------------------------------------------------------- + + # --------------------------------------------------------------------- + # Property Methods + # --------------------------------------------------------------------- + + @property + def phase(self): + """Return the phase vector of the XPPauliList. + + Note: This is different from the quantum_info phase property which + instead returns the phase_exp + """ + # TODO + pass + + @phase.setter + def phase(self, phase): + """Set the phase vector of the XPPauliList + + Args: + phase (numpy.ndarray or complex numbers): Array of phases, + phases must be one of [1,-1, 1j, -1j] + """ + # TODO + pass + + @property + def shape(self): + """The full shape of the :meth:`array`""" + return self._num_paulis, self.num_qubits + + @property + def size(self): + """The number of XPPauli rows in the table.""" + return self._num_paulis + + @property + def num_paulis(self): + """Returns the number of XPPauli's in List""" + return self._num_paulis + + @property + def phase_exp(self): + """Return the phase exponent vector of the XPPauliList""" + # TODO + pass + + @phase_exp.setter + def phase_exp(self, phase_exp, input_phase_encoding=BaseXPPauli.EXTERNAL_PHASE_ENCODING): + """Set the phase exponent vector of the XPPauliList. Note that this method + converts the phase exponents directly and does not take into account the + number of Y paulis in the representation. + + Args: + phase_exp (_type_): _description_ + input_phase_encoding (_type_, optional): _description_. Defaults to + BaseXPPauli.EXTERNAL_PHASE_ENCODING. + """ + # TODO + pass + + @property + def settings(self): + """Return settings.""" + return {"data": self.to_labels()} + + # --------------------------------------------------------------------- + # Magic Methods and related methods + # --------------------------------------------------------------------- + + def __getitem__(self, index): + """Return a view of the XPPauliList.""" + # Returns a view of specified rows of the XPPauliList + # This supports all slicing operations the underlying array supports. + # TODO + pass + + def getaslist(self, slc: Union[numbers.Integral, slice]) -> List["XPPauli"]: + """_summary_ + + Returns: + _type_: _description_ + """ + # TODO + pass + + def __setitem__(self, index, value): + """Update XPPauliList.""" + # TODO + pass + + def __repr__(self): + """Display representation.""" + return self._truncated_str(True) + + def __str__(self): + """Print representation.""" + return self._truncated_str(False) + + def _truncated_str(self, show_class): + # TODO + pass + + def __array__(self, dtype=None): + """Convert to numpy array""" + # pylint: disable=unused-argument + shape = (len(self),) + 2 * (2**self.num_qubits,) + ret = np.zeros(shape, dtype=complex) + for i, mat in enumerate(self.matrix_iter()): + ret[i] = mat + return ret + + def __eq__(self, other): + """Entrywise comparison of XPPauli equality.""" + if not isinstance(other, XPPauliList): + other = XPPauliList(other) + if not isinstance(other, BaseXPPauli): + return False + return self._eq(other) + + def __len__(self): + """Return the number of XPPauli rows in the table.""" + return self._num_paulis + + # ---- + # + # ---- + + # --------------------------------------------------------------------- + # BaseOperator methods + # --------------------------------------------------------------------- + + def tensor(self, other): + """Return the tensor product with each XPPauli in the list. + + Args: + other (XPPauliList): another XPPauliList. + + Returns: + XPPauliList: the list of tensor product XPPaulis. + + Raises: + QiskitError: if other cannot be converted to a XPPauliList, does + not have either 1 or the same number of XPPaulis as + the current list. + """ + # TODO + pass + + def compose(self, other, qargs=None, front=False, inplace=False): + """Return the composition self∘other for each XPPauli in the list. + + Args: + other (XPPauliList): another XPPauliList. + qargs (None or list): qubits to apply dot product on (Default: None). + front (bool): If True use `dot` composition method [default: False]. + inplace (bool): If True update in-place (default: False). + + Returns: + XPPauliList: the list of composed XPPaulis. + + Raises: + QiskitError: if other cannot be converted to a XPPauliList, does + not have either 1 or the same number of XPPaulis as + the current list, or has the wrong number of qubits + for the specified qargs. + """ + if qargs is None: + qargs = getattr(other, "qargs", None) + if not isinstance(other, XPPauliList): + other = XPPauliList(other) + if len(other) not in [1, len(self)]: + raise QiskitError( + "Incompatible XPPauliLists. Other list must " + "have either 1 or the same number of XPPaulis." + ) + return XPPauliList(super().compose(other, qargs=qargs, front=front, inplace=inplace)) + + def conjugate(self): + """Return the conjugate of each XPPauli in the list.""" + # TODO + pass + + def transpose(self): + """Return the transpose of each XPPauli in the list.""" + # TODO + pass + + def adjoint(self): + """Return the adjoint of each XPPauli in the list.""" + # TODO + pass + + def inverse(self): + """Return the inverse of each XPPauli in the list.""" + # TODO + pass + + # --------------------------------------------------------------------- + # Utility methods + # --------------------------------------------------------------------- + + # --------------------------------------------------------------------- + # Custom Iterators + # --------------------------------------------------------------------- + + # --------------------------------------------------------------------- + # Class methods + # --------------------------------------------------------------------- + + @classmethod + def from_symplectic(cls, z, x, phase_exp=0): + """Construct a XPPauliList from a symplectic data. + + Args: + z (np.ndarray): 2D boolean Numpy array. + x (np.ndarray): 2D boolean Numpy array. + phase_exp (np.ndarray or None): Optional, 1D integer array from Z_4. + + Returns: + XPPauliList: the constructed XPPauliList. + + Note: Initialization this way will copy matrices and not reference them. + + TODO: Fix this method to be more general and not in old form only + (i.e. include matrix inputs ...) + """ + # TODO + pass From 13e5c27cfbec5dba44f0a5763b9c097cc923761b Mon Sep 17 00:00:00 2001 From: dhruvbhq Date: Sun, 30 Oct 2022 16:57:44 +0100 Subject: [PATCH 11/30] added test for XPPauliList init, and function in xp_pauli_rep --- qiskit_qec/operators/base_xp_pauli.py | 5 +- qiskit_qec/operators/xp_pauli.py | 1 + qiskit_qec/operators/xp_pauli_list.py | 18 ++++--- qiskit_qec/utils/xp_pauli_rep.py | 30 ++++++++++-- tests/operators/test_xp_pauli_list.py | 69 +++++++++++++++++++++++++++ 5 files changed, 110 insertions(+), 13 deletions(-) create mode 100644 tests/operators/test_xp_pauli_list.py diff --git a/qiskit_qec/operators/base_xp_pauli.py b/qiskit_qec/operators/base_xp_pauli.py index 519e407c..8c2399ac 100644 --- a/qiskit_qec/operators/base_xp_pauli.py +++ b/qiskit_qec/operators/base_xp_pauli.py @@ -53,7 +53,7 @@ def __init__( self, matrix: Union[np.ndarray, None] = None, phase_exp: Union[None, np.ndarray, np.integer] = None, - precision: int = None, + precision: Union[int, np.ndarray] = None, order: str = "xz", ) -> None: # TODO: Need to update this docstring once the representation to be @@ -90,7 +90,8 @@ def __init__( See Also: Pauli, PauliList """ - assert (type(precision) == int) and (precision > 1), QiskitError( + + assert ((type(precision) == int) or (type(precision) == np.ndarray)) and (np.all(precision > 1)), QiskitError( "Precision of XP operators must be an integer greater than or equal to 2." ) diff --git a/qiskit_qec/operators/xp_pauli.py b/qiskit_qec/operators/xp_pauli.py index f1207996..017c18c9 100644 --- a/qiskit_qec/operators/xp_pauli.py +++ b/qiskit_qec/operators/xp_pauli.py @@ -46,6 +46,7 @@ def __init__( x ([type], optional): [description]. Defaults to None. z ([type], optional): [description]. Defaults to None. phase_exponent ([type], optional): [description]. Defaults to None. + precision: Precision of XP operators. Must be an integer greater than or equal to two. Raises: QiskitError: Something went wrong. diff --git a/qiskit_qec/operators/xp_pauli_list.py b/qiskit_qec/operators/xp_pauli_list.py index 6d3a83e8..4e387839 100644 --- a/qiskit_qec/operators/xp_pauli_list.py +++ b/qiskit_qec/operators/xp_pauli_list.py @@ -19,7 +19,7 @@ from qiskit.quantum_info.operators.mixins import GroupMixin, LinearMixin from qiskit_qec.operators.base_xp_pauli import BaseXPPauli from qiskit_qec.operators.xp_pauli import XPPauli -from qiskit_qec.utils import pauli_rep +from qiskit_qec.utils import xp_pauli_rep class XPPauliList(BaseXPPauli, LinearMixin, GroupMixin): @@ -34,6 +34,7 @@ def __init__( BaseXPPauli, np.ndarray, Tuple[np.ndarray], Iterable, None ] = None, phase_exp: Union[int, np.ndarray, None] = None, + precision: Union[int, np.ndarray] = None, *, input_pauli_encoding: str = BaseXPPauli.EXTERNAL_XP_PAULI_ENCODING, input_qubit_order: str = "right-to-left", @@ -45,6 +46,7 @@ def __init__( data (str): List of XPPauli Operators. phase_exp (int, optional): i**phase_exp. Defaults to 0. input_qubit_order (str, optional): Order to read pdata. Defaults to "right-to-left". + precision: Precision of XP operators. Must be an integer/array of integers greater than or equal to 2. Raises: QiskitError: Something went wrong. @@ -55,25 +57,25 @@ def __init__( elif isinstance(data, BaseXPPauli): matrix = data.matrix phase_exp = data._phase_exp + precision = data.precision # TODO elif isinstance(data, StabilizerTable), elif isinstance(data, PauliTable) elif isinstance(data, np.ndarray): if data.size == 0: matrix = np.empty(shape=(0, 0), dtype=np.bool_) phase_exp = np.empty(shape=(0,), dtype=np.int8) - elif isinstance(data[0], str): - matrix, phase_exp = self._from_paulis(data, input_qubit_order) + # TODO elif isinstance(data[0], str): else: if phase_exp is None: phase_exp = 0 - matrix, phase_exp = pauli_rep.from_array( - data, phase_exp, input_pauli_encoding=input_pauli_encoding + matrix, phase_exp, precision = xp_pauli_rep.from_array( + data, phase_exp, precision, input_pauli_encoding=input_pauli_encoding ) # TODO elif isinstance(data, tuple) else: # TODO Conversion as iterable of Paulis pass - super().__init__(matrix, phase_exp) + super().__init__(matrix, phase_exp, precision) # TODO # self.paulis = [ @@ -207,6 +209,10 @@ def __len__(self): """Return the number of XPPauli rows in the table.""" return self._num_paulis + def _add(self, other, qargs=None): + """summary""" + pass + # ---- # # ---- diff --git a/qiskit_qec/utils/xp_pauli_rep.py b/qiskit_qec/utils/xp_pauli_rep.py index 46667864..d200232f 100644 --- a/qiskit_qec/utils/xp_pauli_rep.py +++ b/qiskit_qec/utils/xp_pauli_rep.py @@ -690,15 +690,35 @@ def str2str( # ---------------------------------------------------------------------- -# pylint: disable=unused-argument def from_array( matrix: Union[List, Tuple, np.ndarray], phase_exp: Union[int, List, Tuple, np.ndarray] = None, + precision: Union[int, List, Tuple, np.ndarray] = None, input_pauli_encoding: Optional[str] = None, -) -> Tuple[np.ndarray, np.ndarray]: - """_summary_""" - pass - +) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: + """Convert array and phase_exp to the matrix, phase_exp and precision in BaseXPPauli's internal + XPPauli encoding (xp_pauli_rep.INTERNAL_XP_PAULI_ENCODING) + Args: + matrix (_type_): _description_ + phase_exp (_type_): _description_ + input_pauli_encoding: input XPPauli encoding + Returns: + _type_: _description_ + """ + if input_pauli_encoding is None: + input_pauli_encoding = DEFAULT_EXTERNAL_XP_PAULI_ENCODING + if isinstance(matrix, np.ndarray) and matrix.dtype == np.int64: + matrix_data = matrix + else: + matrix_data = np.asarray(matrix, dtype=np.int64) + matrix_data = np.atleast_2d(matrix_data) + # TODO + # if not is_symplectic_matrix_form(matrix_data): + # raise QiskitError("Input matrix not a symplectic matrix or symplectic vector") + if phase_exp is None or (isinstance(phase_exp, numbers.Integral) and phase_exp == 0): + phase_exp = np.zeros(shape=(matrix_data.shape[0],)) + # TODO may need to implement change_pauli_encoding + return matrix_data, phase_exp, precision # pylint: disable=unused-argument def from_split_array( diff --git a/tests/operators/test_xp_pauli_list.py b/tests/operators/test_xp_pauli_list.py new file mode 100644 index 00000000..fe59ca7c --- /dev/null +++ b/tests/operators/test_xp_pauli_list.py @@ -0,0 +1,69 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017, 2020. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Tests for XPPauliList class.""" + +import unittest + + +import itertools as it +import numpy as np +from ddt import ddt + +from qiskit import QiskitError +from qiskit.quantum_info.operators import ( + Clifford, + Operator, + PauliTable, + StabilizerTable, +) +from qiskit.quantum_info.random import random_clifford, random_pauli_list +from qiskit.test import QiskitTestCase + +# Note: In Qiskit tests below is just test + +from qiskit_qec.operators.base_xp_pauli import BaseXPPauli +from qiskit_qec.operators.xp_pauli import XPPauli +from qiskit_qec.operators.xp_pauli_list import XPPauliList + +from tests import combine + + +class TestXPPauliListInit(QiskitTestCase): + """Tests for XPPauliList initialization.""" + + def test_array_init(self): + """Test array initialization.""" + # Matrix array initialization + matrix1 = np.array([1, 1, 1, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0], dtype=np.int64) + phase_exp1 = 12 + precision1 = 8 + matrix2 = np.array([1, 1, 1, 0, 0, 0, 0, 0, 0, 2, 3, 0, 0, 0], dtype=np.int64) + phase_exp2 = 2 + precision2 = 4 + matrix = np.array([matrix1, matrix2]) + phase_exp = np.array([phase_exp1, phase_exp2]) + precision = np.array([precision1, precision2]) + xppaulilist = XPPauliList(data=matrix, phase_exp=phase_exp, precision=precision) + np.testing.assert_equal(xppaulilist.matrix, matrix) + np.testing.assert_equal(xppaulilist._phase_exp, phase_exp) + np.testing.assert_equal(xppaulilist.precision, precision) + + +@ddt +class TestXPPauliListOperator(QiskitTestCase): + """Tests for XPPauliList base operator methods.""" + pass + + +if __name__ == "__main__": + unittest.main() From f54823eafac58dd4331d3c17a5e3c95c20e92814 Mon Sep 17 00:00:00 2001 From: dhruvbhq Date: Sun, 30 Oct 2022 20:12:06 +0100 Subject: [PATCH 12/30] added some tests for XPPauliList and upgraded some functions to work for XPPauliList --- qiskit_qec/operators/base_xp_pauli.py | 59 ++++++++++++---------- tests/operators/test_xp_pauli_list.py | 70 ++++++++++++++++++++++++++- 2 files changed, 102 insertions(+), 27 deletions(-) diff --git a/qiskit_qec/operators/base_xp_pauli.py b/qiskit_qec/operators/base_xp_pauli.py index 8c2399ac..8fa8c12d 100644 --- a/qiskit_qec/operators/base_xp_pauli.py +++ b/qiskit_qec/operators/base_xp_pauli.py @@ -529,8 +529,8 @@ def _unique_vector_rep(self): matrix = np.empty(shape=np.shape(self.matrix), dtype=np.int64) phase_exp = np.mod(self._phase_exp, 2 * self.precision) - matrix[:, : self.num_qubits][0] = np.mod(self.x, 2) - matrix[:, self.num_qubits : ][0] = np.mod(self.z, self.precision) + matrix[:, : self.num_qubits] = np.mod(self.x, 2) + matrix[:, self.num_qubits : ] = np.mod(self.z, np.expand_dims(self.precision, axis=-1)) return BaseXPPauli(matrix, phase_exp, self.precision) @@ -543,29 +543,36 @@ def _rescale_precision(self, new_precision): of XPPauli operator to the new precision. Returns None if the rescaling is not possible, else returns the rescaled BaseXPPauli object.""" - # TODO this code will probably only work for XPPauli, may need to be upgraded for XPPauliList + # TODO Currently, if any operator in an XPPauliList can not be + # rescaled, this function will return None. unique_xp_op = self.unique_vector_rep() - - if new_precision > unique_xp_op.precision: - if np.mod(new_precision, unique_xp_op.precision > 0): - return None - matrix = np.empty(shape=np.shape(unique_xp_op.matrix), dtype=np.int64) - scale_factor = new_precision // unique_xp_op.precision - phase_exp = scale_factor * unique_xp_op.phase_exp - matrix[:, unique_xp_op.num_qubits :][0] = (scale_factor * unique_xp_op.z) - - elif new_precision < unique_xp_op.precision: - scale_factor = unique_xp_op.precision // new_precision - if( - (unique_xp_op.precision % new_precision > 0) - or (np.sum(np.mod(unique_xp_op._phase_exp, scale_factor)) > 0) - or (np.sum(np.mod(unique_xp_op.z, scale_factor)) > 0) - ): - return None - matrix = np.empty(shape=np.shape(unique_xp_op.matrix), dtype=np.int64) - phase_exp = unique_xp_op._phase_exp // scale_factor - matrix[:, 0 : unique_xp_op.num_qubits][0] = unique_xp_op.x - matrix[:, unique_xp_op.num_qubits :][0] = unique_xp_op.z // scale_factor + old_precision = np.atleast_1d(unique_xp_op.precision) + new_precision = np.atleast_1d(new_precision) + cmp = new_precision > old_precision + matrix = np.empty(shape=np.shape(unique_xp_op.matrix), dtype=np.int64) + phase_exp = np.empty(shape=np.shape(unique_xp_op._phase_exp)) + + for i in range(len(cmp)): + + if cmp[i]: + if np.mod(new_precision[i], old_precision[i] > 0): + return None + scale_factor = new_precision[i] // old_precision[i] + phase_exp[i] = scale_factor * unique_xp_op._phase_exp[i] + matrix[:, unique_xp_op.num_qubits :][i] = (scale_factor * np.atleast_2d(unique_xp_op.z)[i]) + + else: + scale_factor = old_precision[i] // new_precision[i] + if( + (old_precision[i] % new_precision[i] > 0) + or (np.sum(np.mod(unique_xp_op._phase_exp[i], scale_factor)) > 0) + or (np.sum(np.mod(unique_xp_op.z[i], scale_factor)) > 0) + ): + return None + phase_exp[i] = unique_xp_op._phase_exp[i] // scale_factor + matrix[:, unique_xp_op.num_qubits :][i] = np.atleast_2d(unique_xp_op.z)[i] // scale_factor + + matrix[:, 0 : unique_xp_op.num_qubits] = unique_xp_op.x return BaseXPPauli(matrix, phase_exp, new_precision) @@ -600,9 +607,9 @@ def _antisymmetric_op(self): if np.any(self.x): return None - phase_exp = np.sum(self.z, axis = -1) + phase_exp = np.sum(self.z, axis=-1) x = np.zeros(np.shape(self.z)) - matrix = np.concatenate((x, -self.z), axis = -1) + matrix = np.concatenate((x, -self.z), axis=-1) return BaseXPPauli(matrix=matrix, phase_exp=phase_exp, precision=self.precision) diff --git a/tests/operators/test_xp_pauli_list.py b/tests/operators/test_xp_pauli_list.py index fe59ca7c..fafcbb24 100644 --- a/tests/operators/test_xp_pauli_list.py +++ b/tests/operators/test_xp_pauli_list.py @@ -62,7 +62,75 @@ def test_array_init(self): @ddt class TestXPPauliListOperator(QiskitTestCase): """Tests for XPPauliList base operator methods.""" - pass + + def test_precision_rescale(self): + """Test precision rescaling method.""" + matrix1 = np.array([1, 1, 1, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0], dtype=np.int64) + phase_exp1 = 12 + precision1 = 8 + new_precision1 = 2 + matrix2 = np.array([1, 1, 1, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 8], dtype=np.int64) + phase_exp2 = 6 + precision2 = 10 + new_precision2 = 5 + matrix = np.array([matrix1, matrix2]) + phase_exp = np.array([phase_exp1, phase_exp2]) + precision = np.array([precision1, precision2]) + new_precision = np.array([new_precision1, new_precision2]) + + xppaulilist = XPPauliList(data=matrix, phase_exp=phase_exp, precision=precision) + rescaled_xppaulilist = xppaulilist.rescale_precision(new_precision=new_precision) + target_matrix1 = np.array([1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0], dtype=np.int64) + target_phase_exp1 = 3 + target_matrix2 = np.array([1, 1, 1, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 4], dtype=np.int64) + target_phase_exp2 = 3 + target_matrix = np.array([target_matrix1, target_matrix2]) + target_phase_exp = np.array([target_phase_exp1, target_phase_exp2]) + target_xppaulilist = XPPauliList(data=target_matrix, phase_exp=target_phase_exp, precision=new_precision) + np.testing.assert_equal(target_xppaulilist.matrix, rescaled_xppaulilist.matrix) + np.testing.assert_equal(target_xppaulilist._phase_exp, rescaled_xppaulilist._phase_exp) + np.testing.assert_equal(target_xppaulilist.precision, rescaled_xppaulilist.precision) + + + def test_weight(self): + """Test weight method.""" + matrix1 = np.array([1, 1, 1, 0, 0, 1, 0, 0, 3, 4, 0, 0, 0, 1], dtype=np.int64) + phase_exp1 = 12 + precision1 = 8 + matrix2 = np.array([1, 1, 1, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 4], dtype=np.int64) + phase_exp2 = 3 + precision2 = 5 + matrix = np.array([matrix1, matrix2]) + phase_exp = np.array([phase_exp1, phase_exp2]) + precision = np.array([precision1, precision2]) + + xppaulilist = XPPauliList(data=matrix, phase_exp=phase_exp, precision=precision) + value = xppaulilist.weight() + target = np.array([5, 4]) + np.testing.assert_equal(target, value) + + def test_diagonal(self): + """Test is_diagonal method.""" + matrix1 = np.array([0, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3], dtype=np.int64) + phase_exp1 = 0 + precision1 = 8 + + matrix2 = np.array([0, 0, 0, 0, 0, 0, 0, 0, 1, 3, 3, 3, 3, 3], dtype=np.int64) + phase_exp2 = 0 + precision2 = 5 + + matrix3 = np.array([0, 1, 0, 1, 0, 0, 0, 0, 1, 3, 3, 3, 3, 3], dtype=np.int64) + phase_exp3 = 12 + precision3 = 8 + matrix = np.array([matrix1, matrix2, matrix3]) + phase_exp = np.array([phase_exp1, phase_exp2, phase_exp3]) + precision = np.array([precision1, precision2, precision3]) + + xppaulilist = XPPauliList(data=matrix, phase_exp=phase_exp, precision=precision) + + value = xppaulilist.is_diagonal() + target = np.array([True, True, False]) + np.testing.assert_equal(target, value) if __name__ == "__main__": From 7406ed744607cfa9c84317e86e76a75f9402861e Mon Sep 17 00:00:00 2001 From: dhruvbhq Date: Mon, 31 Oct 2022 11:46:16 +0100 Subject: [PATCH 13/30] cleaned lint and black --- qiskit_qec/operators/base_xp_pauli.py | 82 ++++++++++++++++----------- qiskit_qec/operators/random.py | 1 + qiskit_qec/operators/xp_pauli.py | 20 ++----- qiskit_qec/operators/xp_pauli_list.py | 48 ++++++++-------- qiskit_qec/utils/xp_pauli_rep.py | 2 + tests/operators/test_xp_pauli.py | 27 ++++----- tests/operators/test_xp_pauli_list.py | 26 +-------- 7 files changed, 94 insertions(+), 112 deletions(-) diff --git a/qiskit_qec/operators/base_xp_pauli.py b/qiskit_qec/operators/base_xp_pauli.py index 8fa8c12d..1a86ca6b 100644 --- a/qiskit_qec/operators/base_xp_pauli.py +++ b/qiskit_qec/operators/base_xp_pauli.py @@ -91,7 +91,7 @@ def __init__( Pauli, PauliList """ - assert ((type(precision) == int) or (type(precision) == np.ndarray)) and (np.all(precision > 1)), QiskitError( + assert isinstance(precision, (int, np.ndarray)) and (np.all(precision > 1)), QiskitError( "Precision of XP operators must be an integer greater than or equal to 2." ) @@ -341,7 +341,9 @@ def _compose( BaseXPPauli: _description_ """ - assert a.precision == b.precision, QiskitError("Precision of the two BaseXPPaulis to be multiplied must be the same.") + assert a.precision == b.precision, QiskitError( + "Precision of the two BaseXPPaulis to be multiplied must be the same." + ) if qargs is not None: qargs = list(qargs) + [item + a.num_qubits for item in qargs] @@ -359,33 +361,35 @@ def _compose( phase_exp = a._phase_exp + b._phase_exp # Calculate antisymmetric operator, i.e. D if front: - Dx = np.zeros(np.shape(a.x)) - Dz = 2 * np.multiply(b.x, a.z) - Dmat = np.concatenate((Dx, Dz), axis=-1) - D = BaseXPPauli(matrix=Dmat, precision=a.precision)._antisymmetric_op() + dx = np.zeros(np.shape(a.x)) + dz = 2 * np.multiply(b.x, a.z) + dmat = np.concatenate((dx, dz), axis=-1) + d = BaseXPPauli(matrix=dmat, precision=a.precision)._antisymmetric_op() else: - Dx = np.zeros(np.shape(a.x)) - Dz = 2 * np.multiply(a.x, b.z) - Dmat = np.concatenate((Dx, Dz), axis=-1) - D = BaseXPPauli(matrix=Dmat, precision=a.precision)._antisymmetric_op() + dx = np.zeros(np.shape(a.x)) + dz = 2 * np.multiply(a.x, b.z) + dmat = np.concatenate((dx, dz), axis=-1) + d = BaseXPPauli(matrix=dmat, precision=a.precision)._antisymmetric_op() if qargs is None: if not inplace: - result_x = np.logical_xor(x, D.x) - result_z = z + D.z - result_phase_exp = phase_exp + D._phase_exp + result_x = np.logical_xor(x, d.x) + result_z = z + d.z + result_phase_exp = phase_exp + d._phase_exp result_mat = np.concatenate((result_x, result_z), axis=-1) - return BaseXPPauli(matrix=result_mat, phase_exp=result_phase_exp, precision=a.precision)._unique_vector_rep() + return BaseXPPauli( + matrix=result_mat, phase_exp=result_phase_exp, precision=a.precision + )._unique_vector_rep() # Inplace update - a.x = np.logical_xor(x, D.x) - a.z = z + D.z - a._phase_exp = phase_exp + D._phase_exp + a.x = np.logical_xor(x, d.x) + a.z = z + d.z + a._phase_exp = phase_exp + d._phase_exp return a._unique_vector_rep() # Qargs update ret = a if inplace else a.copy() ret.matrix[:, qargs] = mat - ret._phase_exp = phase_exp + D._phase_exp + ret._phase_exp = phase_exp + d._phase_exp ret = ret._unique_vector_rep() return ret @@ -519,6 +523,7 @@ def _append_circuit( # --------------------------------------------------------------------- def unique_vector_rep(self): + """_summary_""" return self._unique_vector_rep() def _unique_vector_rep(self): @@ -530,11 +535,12 @@ def _unique_vector_rep(self): phase_exp = np.mod(self._phase_exp, 2 * self.precision) matrix[:, : self.num_qubits] = np.mod(self.x, 2) - matrix[:, self.num_qubits : ] = np.mod(self.z, np.expand_dims(self.precision, axis=-1)) + matrix[:, self.num_qubits :] = np.mod(self.z, np.expand_dims(self.precision, axis=-1)) return BaseXPPauli(matrix, phase_exp, self.precision) def rescale_precision(self, new_precision): + """_summary_""" return self._rescale_precision(new_precision) def _rescale_precision(self, new_precision): @@ -552,31 +558,36 @@ def _rescale_precision(self, new_precision): matrix = np.empty(shape=np.shape(unique_xp_op.matrix), dtype=np.int64) phase_exp = np.empty(shape=np.shape(unique_xp_op._phase_exp)) - for i in range(len(cmp)): + for i, val in enumerate(cmp): - if cmp[i]: + if val: if np.mod(new_precision[i], old_precision[i] > 0): return None scale_factor = new_precision[i] // old_precision[i] phase_exp[i] = scale_factor * unique_xp_op._phase_exp[i] - matrix[:, unique_xp_op.num_qubits :][i] = (scale_factor * np.atleast_2d(unique_xp_op.z)[i]) + matrix[:, unique_xp_op.num_qubits :][i] = ( + scale_factor * np.atleast_2d(unique_xp_op.z)[i] + ) else: scale_factor = old_precision[i] // new_precision[i] - if( - (old_precision[i] % new_precision[i] > 0) - or (np.sum(np.mod(unique_xp_op._phase_exp[i], scale_factor)) > 0) - or (np.sum(np.mod(unique_xp_op.z[i], scale_factor)) > 0) + if ( + (old_precision[i] % new_precision[i] > 0) + or (np.sum(np.mod(unique_xp_op._phase_exp[i], scale_factor)) > 0) + or (np.sum(np.mod(unique_xp_op.z[i], scale_factor)) > 0) ): return None phase_exp[i] = unique_xp_op._phase_exp[i] // scale_factor - matrix[:, unique_xp_op.num_qubits :][i] = np.atleast_2d(unique_xp_op.z)[i] // scale_factor + matrix[:, unique_xp_op.num_qubits :][i] = ( + np.atleast_2d(unique_xp_op.z)[i] // scale_factor + ) matrix[:, 0 : unique_xp_op.num_qubits] = unique_xp_op.x return BaseXPPauli(matrix, phase_exp, new_precision) def weight(self): + """_summary_""" return self._weight() def _weight(self): @@ -588,14 +599,16 @@ def _weight(self): return np.sum(np.logical_or(self.x, self.z), axis=-1) def is_diagonal(self): + """_summary_""" return self._is_diagonal() def _is_diagonal(self): """(TODO improve doc) This is the equivalent of XPisDiag function from Mark's code. Returns True if the XP operator is diagonal.""" - return np.where(np.sum(self.x, axis=-1)==0, True, False) + return np.where(np.sum(self.x, axis=-1) == 0, True, False) def antisymmetric_op(self): + """_summary_""" return self._antisymmetric_op() def _antisymmetric_op(self): @@ -614,6 +627,7 @@ def _antisymmetric_op(self): return BaseXPPauli(matrix=matrix, phase_exp=phase_exp, precision=self.precision) def power(self, n): + """_summary_""" return self._power(n) def _power(self, n): @@ -634,16 +648,20 @@ def _power(self, n): first = BaseXPPauli(matrix=matrix, phase_exp=phase_exp, precision=self.precision) x = np.zeros(np.shape(self.z)) - z = np.multiply((n-a), np.multiply(self.x, self.z)) + z = np.multiply((n - a), np.multiply(self.x, self.z)) matrix = np.concatenate((x, z), axis=-1) second = BaseXPPauli(matrix=matrix, precision=self.precision).antisymmetric_op() - product = BaseXPPauli(matrix=first.matrix+second.matrix, phase_exp=first._phase_exp+second._phase_exp, precision=self.precision) + product = BaseXPPauli( + matrix=first.matrix + second.matrix, + phase_exp=first._phase_exp + second._phase_exp, + precision=self.precision, + ) return product._unique_vector_rep() - def degree(self): + """_summary_""" return self._degree() def _degree(self): @@ -670,7 +688,7 @@ def _degree(self): # correct output for diagonal XP operators as well (naively that seems # to be true). If that is the case, then checking np.where and # is_diagonal can be removed and the code can be optimized a bit. - return np.where(self.is_diagonal(), lcm, lcm_square) + return np.where(self.is_diagonal(), lcm, lcm_square) # --------------------------------------------------------------------- diff --git a/qiskit_qec/operators/random.py b/qiskit_qec/operators/random.py index b21e97fb..d9c6197e 100644 --- a/qiskit_qec/operators/random.py +++ b/qiskit_qec/operators/random.py @@ -22,6 +22,7 @@ from qiskit_qec.operators.pauli import Pauli from qiskit_qec.operators.pauli_list import PauliList +from qiskit_qec.operators.xp_pauli import XPPauli def random_pauli(num_qubits, group_phase=False, seed=None): diff --git a/qiskit_qec/operators/xp_pauli.py b/qiskit_qec/operators/xp_pauli.py index 017c18c9..eb2ae46e 100644 --- a/qiskit_qec/operators/xp_pauli.py +++ b/qiskit_qec/operators/xp_pauli.py @@ -11,24 +11,20 @@ # that they have been altered from the originals. # Part of the QEC framework """Module for Pauli""" -from typing import Any, Dict, List, Optional, Union +from typing import Any, List, Optional, Union import numpy as np -from qiskit.circuit import Instruction, QuantumCircuit -from qiskit.circuit.barrier import Barrier -from qiskit.circuit.delay import Delay -from qiskit.circuit.library.generalized_gates import PauliGate -from qiskit.circuit.library.standard_gates import IGate, XGate, YGate, ZGate from qiskit.exceptions import QiskitError from qiskit.quantum_info.operators.mixins import generate_apidocs -from qiskit.quantum_info.operators.scalar_op import ScalarOp from qiskit_qec.operators.base_xp_pauli import BaseXPPauli -from qiskit_qec.utils import xp_pauli_rep + +# from qiskit_qec.utils import xp_pauli_rep class XPPauli(BaseXPPauli): """`XPPauli` inherits from `BaseXPPauli`""" + # pylint: disable=unused-argument def __init__( self, data: Any, @@ -181,20 +177,12 @@ def unique_vector_rep(self): def rescale_precision(self, new_precision): return XPPauli(super().rescale_precision(new_precision)) - def weight(self): - return super().weight() - - def is_diagonal(self): - return super().is_diagonal() - def antisymmetric_op(self): return XPPauli(super().antisymmetric_op()) def power(self, n): return XPPauli(super().power(n)) - def degree(self): - return super().degree() # Update docstrings for API docs generate_apidocs(XPPauli) diff --git a/qiskit_qec/operators/xp_pauli_list.py b/qiskit_qec/operators/xp_pauli_list.py index 4e387839..18090fe5 100644 --- a/qiskit_qec/operators/xp_pauli_list.py +++ b/qiskit_qec/operators/xp_pauli_list.py @@ -18,10 +18,11 @@ from qiskit.exceptions import QiskitError from qiskit.quantum_info.operators.mixins import GroupMixin, LinearMixin from qiskit_qec.operators.base_xp_pauli import BaseXPPauli -from qiskit_qec.operators.xp_pauli import XPPauli from qiskit_qec.utils import xp_pauli_rep +# pylint: disable=unused-argument +# pylint: disable=no-member class XPPauliList(BaseXPPauli, LinearMixin, GroupMixin): """`XPPauliList` inherits from `BaseXPPauli`""" @@ -30,9 +31,7 @@ class XPPauliList(BaseXPPauli, LinearMixin, GroupMixin): def __init__( self, - data: Union[ - BaseXPPauli, np.ndarray, Tuple[np.ndarray], Iterable, None - ] = None, + data: Union[BaseXPPauli, np.ndarray, Tuple[np.ndarray], Iterable, None] = None, phase_exp: Union[int, np.ndarray, None] = None, precision: Union[int, np.ndarray] = None, *, @@ -46,7 +45,8 @@ def __init__( data (str): List of XPPauli Operators. phase_exp (int, optional): i**phase_exp. Defaults to 0. input_qubit_order (str, optional): Order to read pdata. Defaults to "right-to-left". - precision: Precision of XP operators. Must be an integer/array of integers greater than or equal to 2. + precision: Precision of XP operators. Must be an integer/array of + integers greater than or equal to 2. Raises: QiskitError: Something went wrong. @@ -267,25 +267,25 @@ def compose(self, other, qargs=None, front=False, inplace=False): ) return XPPauliList(super().compose(other, qargs=qargs, front=front, inplace=inplace)) - def conjugate(self): - """Return the conjugate of each XPPauli in the list.""" - # TODO - pass - - def transpose(self): - """Return the transpose of each XPPauli in the list.""" - # TODO - pass - - def adjoint(self): - """Return the adjoint of each XPPauli in the list.""" - # TODO - pass - - def inverse(self): - """Return the inverse of each XPPauli in the list.""" - # TODO - pass + # def conjugate(self): + # """Return the conjugate of each XPPauli in the list.""" + # # TODO + # pass + + # def transpose(self): + # """Return the transpose of each XPPauli in the list.""" + # # TODO + # pass + + # def adjoint(self): + # """Return the adjoint of each XPPauli in the list.""" + # # TODO + # pass + + # def inverse(self): + # """Return the inverse of each XPPauli in the list.""" + # # TODO + # pass # --------------------------------------------------------------------- # Utility methods diff --git a/qiskit_qec/utils/xp_pauli_rep.py b/qiskit_qec/utils/xp_pauli_rep.py index d200232f..b4c7fba6 100644 --- a/qiskit_qec/utils/xp_pauli_rep.py +++ b/qiskit_qec/utils/xp_pauli_rep.py @@ -701,6 +701,7 @@ def from_array( Args: matrix (_type_): _description_ phase_exp (_type_): _description_ + precision (_type_): Precision of XP operator input_pauli_encoding: input XPPauli encoding Returns: _type_: _description_ @@ -720,6 +721,7 @@ def from_array( # TODO may need to implement change_pauli_encoding return matrix_data, phase_exp, precision + # pylint: disable=unused-argument def from_split_array( x: Union[List, Tuple, np.ndarray], diff --git a/tests/operators/test_xp_pauli.py b/tests/operators/test_xp_pauli.py index ab698cc1..7b5994f4 100644 --- a/tests/operators/test_xp_pauli.py +++ b/tests/operators/test_xp_pauli.py @@ -15,20 +15,10 @@ """Tests for Pauli operator class.""" import unittest -import itertools as it -from functools import lru_cache import numpy as np -from ddt import ddt, data, unpack - -from qiskit import QuantumCircuit -from qiskit.exceptions import QiskitError +from ddt import ddt from qiskit.test import QiskitTestCase - -from qiskit.quantum_info.operators import Operator - -from qiskit_qec.operators.random import random_clifford, random_pauli, random_xppauli -from qiskit_qec.operators.base_xp_pauli import BaseXPPauli from qiskit_qec.operators.xp_pauli import XPPauli # TODO from qiskit_qec.utils.pauli_rep import split_pauli, cpxstr2exp @@ -36,7 +26,7 @@ # from qiskit.quantum_info.operators.symplectic.pauli import _split_pauli_label, _phase_from_label -@ddt +@ddt class TestXPPauliInit(QiskitTestCase): """Tests for XPPauli initialization.""" @@ -67,7 +57,9 @@ def test_precision_rescale(self): rescaled_xppauli = xppauli.rescale_precision(new_precision=new_precision) target_matrix = np.array([1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0], dtype=np.int64) target_phase_exp = 3 - target_xppauli = XPPauli(data=target_matrix, phase_exp=target_phase_exp, precision=new_precision) + target_xppauli = XPPauli( + data=target_matrix, phase_exp=target_phase_exp, precision=new_precision + ) np.testing.assert_equal(target_xppauli.matrix, rescaled_xppauli.matrix) np.testing.assert_equal(target_xppauli._phase_exp, rescaled_xppauli._phase_exp) np.testing.assert_equal(target_xppauli.precision, rescaled_xppauli.precision) @@ -143,17 +135,17 @@ def test_power(self): def test_multiplication(self): """Test multiplication method.""" # Test case taken from Mark's code. - a_matrix = np.array([0,1,0,0,2,0], dtype=np.int64) + a_matrix = np.array([0, 1, 0, 0, 2, 0], dtype=np.int64) a_phase_exp = 6 a_precision = 4 a = XPPauli(data=a_matrix, phase_exp=a_phase_exp, precision=a_precision) - b_matrix = np.array([1,1,1,3,3,0], dtype=np.int64) + b_matrix = np.array([1, 1, 1, 3, 3, 0], dtype=np.int64) b_phase_exp = 2 b_precision = 4 b = XPPauli(data=b_matrix, phase_exp=b_phase_exp, precision=b_precision) value = XPPauli.compose(a, b) - target_matrix = np.array([1,0,1,3,3,0], dtype=np.int64) + target_matrix = np.array([1, 0, 1, 3, 3, 0], dtype=np.int64) target_phase_exp = 6 target_precision = 4 target = XPPauli(data=target_matrix, phase_exp=target_phase_exp, precision=target_precision) @@ -161,7 +153,7 @@ def test_multiplication(self): def test_degree(self): """Test degree method.""" - matrix = np.array([0,0,0,2,1,0], dtype=np.int64) + matrix = np.array([0, 0, 0, 2, 1, 0], dtype=np.int64) phase_exp = 2 precision = 4 xppauli = XPPauli(data=matrix, phase_exp=phase_exp, precision=precision) @@ -170,5 +162,6 @@ def test_degree(self): target = 4 self.assertEqual(target, value) + if __name__ == "__main__": unittest.main() diff --git a/tests/operators/test_xp_pauli_list.py b/tests/operators/test_xp_pauli_list.py index fafcbb24..7e3c4263 100644 --- a/tests/operators/test_xp_pauli_list.py +++ b/tests/operators/test_xp_pauli_list.py @@ -13,30 +13,11 @@ """Tests for XPPauliList class.""" import unittest - - -import itertools as it import numpy as np from ddt import ddt - -from qiskit import QiskitError -from qiskit.quantum_info.operators import ( - Clifford, - Operator, - PauliTable, - StabilizerTable, -) -from qiskit.quantum_info.random import random_clifford, random_pauli_list from qiskit.test import QiskitTestCase - -# Note: In Qiskit tests below is just test - -from qiskit_qec.operators.base_xp_pauli import BaseXPPauli -from qiskit_qec.operators.xp_pauli import XPPauli from qiskit_qec.operators.xp_pauli_list import XPPauliList -from tests import combine - class TestXPPauliListInit(QiskitTestCase): """Tests for XPPauliList initialization.""" @@ -86,12 +67,13 @@ def test_precision_rescale(self): target_phase_exp2 = 3 target_matrix = np.array([target_matrix1, target_matrix2]) target_phase_exp = np.array([target_phase_exp1, target_phase_exp2]) - target_xppaulilist = XPPauliList(data=target_matrix, phase_exp=target_phase_exp, precision=new_precision) + target_xppaulilist = XPPauliList( + data=target_matrix, phase_exp=target_phase_exp, precision=new_precision + ) np.testing.assert_equal(target_xppaulilist.matrix, rescaled_xppaulilist.matrix) np.testing.assert_equal(target_xppaulilist._phase_exp, rescaled_xppaulilist._phase_exp) np.testing.assert_equal(target_xppaulilist.precision, rescaled_xppaulilist.precision) - def test_weight(self): """Test weight method.""" matrix1 = np.array([1, 1, 1, 0, 0, 1, 0, 0, 3, 4, 0, 0, 0, 1], dtype=np.int64) @@ -103,7 +85,6 @@ def test_weight(self): matrix = np.array([matrix1, matrix2]) phase_exp = np.array([phase_exp1, phase_exp2]) precision = np.array([precision1, precision2]) - xppaulilist = XPPauliList(data=matrix, phase_exp=phase_exp, precision=precision) value = xppaulilist.weight() target = np.array([5, 4]) @@ -125,7 +106,6 @@ def test_diagonal(self): matrix = np.array([matrix1, matrix2, matrix3]) phase_exp = np.array([phase_exp1, phase_exp2, phase_exp3]) precision = np.array([precision1, precision2, precision3]) - xppaulilist = XPPauliList(data=matrix, phase_exp=phase_exp, precision=precision) value = xppaulilist.is_diagonal() From 0edab64b530cb3b432c2f837978022f0d000747a Mon Sep 17 00:00:00 2001 From: dhruvbhq Date: Mon, 7 Nov 2022 21:54:06 +0100 Subject: [PATCH 14/30] renamed _num_paulis to _num_xppaulis --- qiskit_qec/operators/base_xp_pauli.py | 5 ++--- qiskit_qec/operators/xp_pauli_list.py | 10 +++++----- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/qiskit_qec/operators/base_xp_pauli.py b/qiskit_qec/operators/base_xp_pauli.py index 1a86ca6b..91b307de 100644 --- a/qiskit_qec/operators/base_xp_pauli.py +++ b/qiskit_qec/operators/base_xp_pauli.py @@ -109,8 +109,7 @@ def __init__( self.matrix = matrix self.precision = precision - # TODO should _num_paulis be renamed to _num_xppaulis in this and derived classes? - self._num_paulis = self.matrix.shape[0] + self._num_xppaulis = self.matrix.shape[0] if phase_exp is None: self._phase_exp = np.zeros(shape=(self.matrix.shape[0],), dtype=np.int64) else: @@ -312,7 +311,7 @@ def compose( f"Number of qubits of the other {type(self).__name__} does not match qargs." ) - if other._num_paulis not in [1, self._num_paulis]: + if other._num_xppaulis not in [1, self._num_xppaulis]: raise QiskitError( "Incompatible BaseXPPaulis. Second list must " "either have 1 or the same number of XPPaulis." diff --git a/qiskit_qec/operators/xp_pauli_list.py b/qiskit_qec/operators/xp_pauli_list.py index 18090fe5..7d4d2128 100644 --- a/qiskit_qec/operators/xp_pauli_list.py +++ b/qiskit_qec/operators/xp_pauli_list.py @@ -114,17 +114,17 @@ def phase(self, phase): @property def shape(self): """The full shape of the :meth:`array`""" - return self._num_paulis, self.num_qubits + return self._num_xppaulis, self.num_qubits @property def size(self): """The number of XPPauli rows in the table.""" - return self._num_paulis + return self._num_xppaulis @property - def num_paulis(self): + def num_xppaulis(self): """Returns the number of XPPauli's in List""" - return self._num_paulis + return self._num_xppaulis @property def phase_exp(self): @@ -207,7 +207,7 @@ def __eq__(self, other): def __len__(self): """Return the number of XPPauli rows in the table.""" - return self._num_paulis + return self._num_xppaulis def _add(self, other, qargs=None): """summary""" From 7e96d56644ac3a39f3428c095aea92cb8336e4ee Mon Sep 17 00:00:00 2001 From: dhruvbhq Date: Mon, 7 Nov 2022 22:09:14 +0100 Subject: [PATCH 15/30] added missing truncate variable --- qiskit_qec/operators/xp_pauli.py | 3 +++ qiskit_qec/operators/xp_pauli_list.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/qiskit_qec/operators/xp_pauli.py b/qiskit_qec/operators/xp_pauli.py index eb2ae46e..381f89b8 100644 --- a/qiskit_qec/operators/xp_pauli.py +++ b/qiskit_qec/operators/xp_pauli.py @@ -24,6 +24,9 @@ class XPPauli(BaseXPPauli): """`XPPauli` inherits from `BaseXPPauli`""" + # Set the max XPPauli string size before truncation + _truncate__ = 50 + # pylint: disable=unused-argument def __init__( self, diff --git a/qiskit_qec/operators/xp_pauli_list.py b/qiskit_qec/operators/xp_pauli_list.py index 7d4d2128..dfd0a198 100644 --- a/qiskit_qec/operators/xp_pauli_list.py +++ b/qiskit_qec/operators/xp_pauli_list.py @@ -26,7 +26,7 @@ class XPPauliList(BaseXPPauli, LinearMixin, GroupMixin): """`XPPauliList` inherits from `BaseXPPauli`""" - # Set the max number of qubits * paulis before string truncation + # Set the max number of qubits * xppaulis before string truncation _truncate__ = 2000 def __init__( From 63730970a9b633da5f8b4bfa898b5f1ea6fa2e99 Mon Sep 17 00:00:00 2001 From: dhruvbhq Date: Mon, 7 Nov 2022 22:14:11 +0100 Subject: [PATCH 16/30] resolved weight todo --- qiskit_qec/operators/base_xp_pauli.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/qiskit_qec/operators/base_xp_pauli.py b/qiskit_qec/operators/base_xp_pauli.py index 91b307de..4f32fbfd 100644 --- a/qiskit_qec/operators/base_xp_pauli.py +++ b/qiskit_qec/operators/base_xp_pauli.py @@ -593,8 +593,6 @@ def _weight(self): """(TODO improve doc) This is the equivalent of XPDistance function from Mark's code. It returns the count of qubits where either z or x component is nonzero.""" - # TODO Since 'distance' has a specific meaning in QECCs, for now, the - # name 'weight' has been used for this function. return np.sum(np.logical_or(self.x, self.z), axis=-1) def is_diagonal(self): From 84caaae098719aab1596926428e64580c76bc35d Mon Sep 17 00:00:00 2001 From: dhruvbhq Date: Mon, 7 Nov 2022 22:47:40 +0100 Subject: [PATCH 17/30] revert precision from list to integer in BaseXPPauli --- qiskit_qec/operators/base_xp_pauli.py | 46 +++++++++++---------------- tests/operators/test_xp_pauli_list.py | 20 +++++------- 2 files changed, 27 insertions(+), 39 deletions(-) diff --git a/qiskit_qec/operators/base_xp_pauli.py b/qiskit_qec/operators/base_xp_pauli.py index 4f32fbfd..2964ea55 100644 --- a/qiskit_qec/operators/base_xp_pauli.py +++ b/qiskit_qec/operators/base_xp_pauli.py @@ -53,7 +53,7 @@ def __init__( self, matrix: Union[np.ndarray, None] = None, phase_exp: Union[None, np.ndarray, np.integer] = None, - precision: Union[int, np.ndarray] = None, + precision: int = None, order: str = "xz", ) -> None: # TODO: Need to update this docstring once the representation to be @@ -551,35 +551,27 @@ def _rescale_precision(self, new_precision): # TODO Currently, if any operator in an XPPauliList can not be # rescaled, this function will return None. unique_xp_op = self.unique_vector_rep() - old_precision = np.atleast_1d(unique_xp_op.precision) - new_precision = np.atleast_1d(new_precision) - cmp = new_precision > old_precision + old_precision = unique_xp_op.precision matrix = np.empty(shape=np.shape(unique_xp_op.matrix), dtype=np.int64) phase_exp = np.empty(shape=np.shape(unique_xp_op._phase_exp)) - for i, val in enumerate(cmp): - - if val: - if np.mod(new_precision[i], old_precision[i] > 0): - return None - scale_factor = new_precision[i] // old_precision[i] - phase_exp[i] = scale_factor * unique_xp_op._phase_exp[i] - matrix[:, unique_xp_op.num_qubits :][i] = ( - scale_factor * np.atleast_2d(unique_xp_op.z)[i] - ) - - else: - scale_factor = old_precision[i] // new_precision[i] - if ( - (old_precision[i] % new_precision[i] > 0) - or (np.sum(np.mod(unique_xp_op._phase_exp[i], scale_factor)) > 0) - or (np.sum(np.mod(unique_xp_op.z[i], scale_factor)) > 0) - ): - return None - phase_exp[i] = unique_xp_op._phase_exp[i] // scale_factor - matrix[:, unique_xp_op.num_qubits :][i] = ( - np.atleast_2d(unique_xp_op.z)[i] // scale_factor - ) + if new_precision > old_precision: + if np.mod(new_precision, old_precision > 0): + return None + scale_factor = new_precision // old_precision + phase_exp = scale_factor * unique_xp_op._phase_exp + matrix[:, unique_xp_op.num_qubits :] = scale_factor * np.atleast_2d(unique_xp_op.z) + + else: + scale_factor = old_precision // new_precision + if ( + (old_precision % new_precision > 0) + or (np.sum(np.mod(unique_xp_op._phase_exp, scale_factor)) > 0) + or (np.sum(np.mod(unique_xp_op.z, scale_factor)) > 0) + ): + return None + phase_exp = unique_xp_op._phase_exp // scale_factor + matrix[:, unique_xp_op.num_qubits :] = np.atleast_2d(unique_xp_op.z) // scale_factor matrix[:, 0 : unique_xp_op.num_qubits] = unique_xp_op.x diff --git a/tests/operators/test_xp_pauli_list.py b/tests/operators/test_xp_pauli_list.py index 7e3c4263..1584abad 100644 --- a/tests/operators/test_xp_pauli_list.py +++ b/tests/operators/test_xp_pauli_list.py @@ -48,23 +48,19 @@ def test_precision_rescale(self): """Test precision rescaling method.""" matrix1 = np.array([1, 1, 1, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0], dtype=np.int64) phase_exp1 = 12 - precision1 = 8 - new_precision1 = 2 - matrix2 = np.array([1, 1, 1, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 8], dtype=np.int64) - phase_exp2 = 6 - precision2 = 10 - new_precision2 = 5 + matrix2 = np.array([1, 1, 0, 1, 0, 0, 0, 0, 0, 4, 0, 0, 0, 6], dtype=np.int64) + phase_exp2 = 8 + precision = 8 + new_precision = 4 matrix = np.array([matrix1, matrix2]) phase_exp = np.array([phase_exp1, phase_exp2]) - precision = np.array([precision1, precision2]) - new_precision = np.array([new_precision1, new_precision2]) xppaulilist = XPPauliList(data=matrix, phase_exp=phase_exp, precision=precision) rescaled_xppaulilist = xppaulilist.rescale_precision(new_precision=new_precision) - target_matrix1 = np.array([1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0], dtype=np.int64) - target_phase_exp1 = 3 - target_matrix2 = np.array([1, 1, 1, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 4], dtype=np.int64) - target_phase_exp2 = 3 + target_matrix1 = np.array([1, 1, 1, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0], dtype=np.int64) + target_phase_exp1 = 6 + target_matrix2 = np.array([1, 1, 0, 1, 0, 0, 0, 0, 0, 2, 0, 0, 0, 3], dtype=np.int64) + target_phase_exp2 = 4 target_matrix = np.array([target_matrix1, target_matrix2]) target_phase_exp = np.array([target_phase_exp1, target_phase_exp2]) target_xppaulilist = XPPauliList( From 588585bbc9dcfe22fb7acffcfd0ebe912886cd02 Mon Sep 17 00:00:00 2001 From: dhruvbhq Date: Mon, 7 Nov 2022 23:58:18 +0100 Subject: [PATCH 18/30] added type hints --- qiskit_qec/operators/base_xp_pauli.py | 40 ++++++++++++++------------- qiskit_qec/operators/random.py | 7 ++++- qiskit_qec/operators/xp_pauli.py | 10 +++---- qiskit_qec/operators/xp_pauli_list.py | 12 ++++++-- 4 files changed, 41 insertions(+), 28 deletions(-) diff --git a/qiskit_qec/operators/base_xp_pauli.py b/qiskit_qec/operators/base_xp_pauli.py index 2964ea55..e6e198cd 100644 --- a/qiskit_qec/operators/base_xp_pauli.py +++ b/qiskit_qec/operators/base_xp_pauli.py @@ -521,11 +521,11 @@ def _append_circuit( # BaseXPPauli methods for XP arithmetic # --------------------------------------------------------------------- - def unique_vector_rep(self): + def unique_vector_rep(self) -> "BaseXPPauli": """_summary_""" return self._unique_vector_rep() - def _unique_vector_rep(self): + def _unique_vector_rep(self) -> "BaseXPPauli": """(TODO improve doc): This is the equivalent of XPRound from Mark's code. It converts the XPPauli operator into unique vector form, ie phase_exp in Z modulo 2*precision, x in Z_2, z in Z modulo @@ -538,11 +538,11 @@ def _unique_vector_rep(self): return BaseXPPauli(matrix, phase_exp, self.precision) - def rescale_precision(self, new_precision): + def rescale_precision(self, new_precision: int) -> "BaseXPPauli": """_summary_""" return self._rescale_precision(new_precision) - def _rescale_precision(self, new_precision): + def _rescale_precision(self, new_precision: int) -> "BaseXPPauli": """(TODO improve doc): This is the equivalent of XPSetNsingle from Mark's code. It rescales the generalized symplectic vector components of XPPauli operator to the new precision. Returns None if the @@ -577,36 +577,37 @@ def _rescale_precision(self, new_precision): return BaseXPPauli(matrix, phase_exp, new_precision) - def weight(self): + def weight(self) -> Union[int, np.ndarray]: """_summary_""" return self._weight() - def _weight(self): + def _weight(self) -> Union[int, np.ndarray]: """(TODO improve doc) This is the equivalent of XPDistance function from Mark's code. It returns the count of qubits where either z or x component is nonzero.""" return np.sum(np.logical_or(self.x, self.z), axis=-1) - def is_diagonal(self): + def is_diagonal(self) -> np.ndarray: """_summary_""" return self._is_diagonal() - def _is_diagonal(self): + def _is_diagonal(self) -> np.ndarray: """(TODO improve doc) This is the equivalent of XPisDiag function from Mark's code. Returns True if the XP operator is diagonal.""" return np.where(np.sum(self.x, axis=-1) == 0, True, False) - def antisymmetric_op(self): + def antisymmetric_op(self) -> "BaseXPPauli": """_summary_""" return self._antisymmetric_op() - def _antisymmetric_op(self): + def _antisymmetric_op(self) -> "BaseXPPauli": """(TODO improve doc) This is the equivalent of XPD function from Mark's code. It returns the antisymmetric operator corresponding to the z component of XP operator, only if x component is 0, else it returns None.""" if np.any(self.x): + # TODO should there be an assertion here? return None phase_exp = np.sum(self.z, axis=-1) @@ -615,11 +616,11 @@ def _antisymmetric_op(self): return BaseXPPauli(matrix=matrix, phase_exp=phase_exp, precision=self.precision) - def power(self, n): + def power(self, n: int) -> "BaseXPPauli": """_summary_""" return self._power(n) - def _power(self, n): + def _power(self, n: int) -> "BaseXPPauli": """(TODO improve doc) This is te equivalent of XPPower function from Mark's code. It returns the XP operator of specified precision raised to the power n.""" @@ -649,11 +650,11 @@ def _power(self, n): return product._unique_vector_rep() - def degree(self): + def degree(self) -> np.ndarray: """_summary_""" return self._degree() - def _degree(self): + def _degree(self) -> np.ndarray: """(TODO improve doc) This is the equivalent of XPDegree from Mark's code. It returns the degree of XP operator.""" @@ -672,11 +673,12 @@ def _degree(self): lcm_square = 2 * lcm_square - # TODO it can be explored (maybe with Mark) if the algorithm used when - # the XP operator is non-diagonal (which involves squaring) gives the - # correct output for diagonal XP operators as well (naively that seems - # to be true). If that is the case, then checking np.where and - # is_diagonal can be removed and the code can be optimized a bit. + # Do not modify the logic of this function. Naively, it looks like the + # algorithm that is used when the XP operator is non-diagonal (which + # involves squaring) gives the correct output for diagonal XP operators + # as well. However, that is not true. Counter example given by Mark + # Webster is the operator -I, where the faulty method would give the + # degree 2, while the actual degree is 1. return np.where(self.is_diagonal(), lcm, lcm_square) diff --git a/qiskit_qec/operators/random.py b/qiskit_qec/operators/random.py index d9c6197e..56a762da 100644 --- a/qiskit_qec/operators/random.py +++ b/qiskit_qec/operators/random.py @@ -13,6 +13,7 @@ Random symplectic operator functions """ +from typing import Union import numpy as np from numpy.random import default_rng @@ -197,7 +198,11 @@ def random_clifford(num_qubits, seed=None): return Clifford(StabilizerTable(table, phase)) -def random_xppauli(num_qubits, precision=None, seed=None): +def random_xppauli( + num_qubits: int, + precision: int = None, + seed: Union[int, np.random.Generator, None] = None, +) -> XPPauli: """Return a random XPPauli. Args: diff --git a/qiskit_qec/operators/xp_pauli.py b/qiskit_qec/operators/xp_pauli.py index 381f89b8..b97c115c 100644 --- a/qiskit_qec/operators/xp_pauli.py +++ b/qiskit_qec/operators/xp_pauli.py @@ -10,7 +10,7 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. # Part of the QEC framework -"""Module for Pauli""" +"""Module for XPPauli""" from typing import Any, List, Optional, Union import numpy as np @@ -174,16 +174,16 @@ def compose( # --------------------------------------------------------------------- - def unique_vector_rep(self): + def unique_vector_rep(self) -> "XPPauli": return XPPauli(super().unique_vector_rep()) - def rescale_precision(self, new_precision): + def rescale_precision(self, new_precision) -> "XPPauli": return XPPauli(super().rescale_precision(new_precision)) - def antisymmetric_op(self): + def antisymmetric_op(self) -> "XPPauli": return XPPauli(super().antisymmetric_op()) - def power(self, n): + def power(self, n) -> "XPPauli": return XPPauli(super().power(n)) diff --git a/qiskit_qec/operators/xp_pauli_list.py b/qiskit_qec/operators/xp_pauli_list.py index dfd0a198..204edaa8 100644 --- a/qiskit_qec/operators/xp_pauli_list.py +++ b/qiskit_qec/operators/xp_pauli_list.py @@ -12,7 +12,7 @@ # Part of the QEC framework """Module for XPPauli List""" import numbers -from typing import Iterable, List, Tuple, Union +from typing import Iterable, List, Tuple, Union, Optional import numpy as np from qiskit.exceptions import QiskitError @@ -122,7 +122,7 @@ def size(self): return self._num_xppaulis @property - def num_xppaulis(self): + def num_xppaulis(self) -> int: """Returns the number of XPPauli's in List""" return self._num_xppaulis @@ -238,7 +238,13 @@ def tensor(self, other): # TODO pass - def compose(self, other, qargs=None, front=False, inplace=False): + def compose( + self, + other: "BaseXPPauli", + qargs: Optional[list] = None, + front: bool = False, + inplace: bool = False, + ) -> "XPPauliList": """Return the composition self∘other for each XPPauli in the list. Args: From 857216b58d4e0ff3ddded6ef8e0250b4b59d3af0 Mon Sep 17 00:00:00 2001 From: dhruvbhq Date: Tue, 8 Nov 2022 00:09:46 +0100 Subject: [PATCH 19/30] removed array as allowed precision from assertion and updated tests --- qiskit_qec/operators/base_xp_pauli.py | 2 +- tests/operators/test_xp_pauli_list.py | 13 +++---------- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/qiskit_qec/operators/base_xp_pauli.py b/qiskit_qec/operators/base_xp_pauli.py index e6e198cd..7b38f6e3 100644 --- a/qiskit_qec/operators/base_xp_pauli.py +++ b/qiskit_qec/operators/base_xp_pauli.py @@ -91,7 +91,7 @@ def __init__( Pauli, PauliList """ - assert isinstance(precision, (int, np.ndarray)) and (np.all(precision > 1)), QiskitError( + assert isinstance(precision, int) and (np.all(precision > 1)), QiskitError( "Precision of XP operators must be an integer greater than or equal to 2." ) diff --git a/tests/operators/test_xp_pauli_list.py b/tests/operators/test_xp_pauli_list.py index 1584abad..9d0b2dd8 100644 --- a/tests/operators/test_xp_pauli_list.py +++ b/tests/operators/test_xp_pauli_list.py @@ -25,15 +25,13 @@ class TestXPPauliListInit(QiskitTestCase): def test_array_init(self): """Test array initialization.""" # Matrix array initialization + precision = 8 matrix1 = np.array([1, 1, 1, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0], dtype=np.int64) phase_exp1 = 12 - precision1 = 8 matrix2 = np.array([1, 1, 1, 0, 0, 0, 0, 0, 0, 2, 3, 0, 0, 0], dtype=np.int64) phase_exp2 = 2 - precision2 = 4 matrix = np.array([matrix1, matrix2]) phase_exp = np.array([phase_exp1, phase_exp2]) - precision = np.array([precision1, precision2]) xppaulilist = XPPauliList(data=matrix, phase_exp=phase_exp, precision=precision) np.testing.assert_equal(xppaulilist.matrix, matrix) np.testing.assert_equal(xppaulilist._phase_exp, phase_exp) @@ -72,15 +70,13 @@ def test_precision_rescale(self): def test_weight(self): """Test weight method.""" + precision = 8 matrix1 = np.array([1, 1, 1, 0, 0, 1, 0, 0, 3, 4, 0, 0, 0, 1], dtype=np.int64) phase_exp1 = 12 - precision1 = 8 matrix2 = np.array([1, 1, 1, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 4], dtype=np.int64) phase_exp2 = 3 - precision2 = 5 matrix = np.array([matrix1, matrix2]) phase_exp = np.array([phase_exp1, phase_exp2]) - precision = np.array([precision1, precision2]) xppaulilist = XPPauliList(data=matrix, phase_exp=phase_exp, precision=precision) value = xppaulilist.weight() target = np.array([5, 4]) @@ -88,20 +84,17 @@ def test_weight(self): def test_diagonal(self): """Test is_diagonal method.""" + precision = 8 matrix1 = np.array([0, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3], dtype=np.int64) phase_exp1 = 0 - precision1 = 8 matrix2 = np.array([0, 0, 0, 0, 0, 0, 0, 0, 1, 3, 3, 3, 3, 3], dtype=np.int64) phase_exp2 = 0 - precision2 = 5 matrix3 = np.array([0, 1, 0, 1, 0, 0, 0, 0, 1, 3, 3, 3, 3, 3], dtype=np.int64) phase_exp3 = 12 - precision3 = 8 matrix = np.array([matrix1, matrix2, matrix3]) phase_exp = np.array([phase_exp1, phase_exp2, phase_exp3]) - precision = np.array([precision1, precision2, precision3]) xppaulilist = XPPauliList(data=matrix, phase_exp=phase_exp, precision=precision) value = xppaulilist.is_diagonal() From b5d41ea7f2f34bf2a13c061d5ae5dc3beed09b9c Mon Sep 17 00:00:00 2001 From: dhruvbhq Date: Thu, 10 Nov 2022 16:44:00 +0100 Subject: [PATCH 20/30] acknowledged XPF paper on top of BaseXPPauli... files --- qiskit_qec/operators/base_xp_pauli.py | 46 +++++++++++++++++++++++++-- qiskit_qec/operators/pauli_list.py | 5 +++ qiskit_qec/operators/xp_pauli_list.py | 5 +++ 3 files changed, 53 insertions(+), 3 deletions(-) diff --git a/qiskit_qec/operators/base_xp_pauli.py b/qiskit_qec/operators/base_xp_pauli.py index 7b38f6e3..a3d102c0 100644 --- a/qiskit_qec/operators/base_xp_pauli.py +++ b/qiskit_qec/operators/base_xp_pauli.py @@ -11,6 +11,11 @@ # that they have been altered from the originals. # Part of the QEC framework +# +# This code is based on the paper: "The XP Stabiliser Formalism: a +# Generalisation of the Pauli Stabiliser Formalism with Arbitrary Phases", Mark +# A. Webster, Benjamin J. Brown, and Stephen D. Bartlett. Quantum 6, 815 +# (2022). """Module for base XP pauli""" import numbers @@ -287,6 +292,11 @@ def compose( Note: This method does compose coordinate wise (which is different from the PauliTable compose which should be corrected at some point). + This method is adapted from XPFpackage: + https://github.com/m-webster/XPFpackage, originally developed by + Mark Webster. The original code is licensed under the GNU General + Public License v3.0 and Mark Webster has given permission to use + the code under the Apache License v2.0. Args: other: BaseXPPauli @@ -336,6 +346,13 @@ def _compose( front (bool, optional): _description_. Defaults to False. inplace (bool, optional): _description_. Defaults to False. + Note: + This method is adapted from XPFpackage: + https://github.com/m-webster/XPFpackage, originally developed by + Mark Webster. The original code is licensed under the GNU General + Public License v3.0 and Mark Webster has given permission to use + the code under the Apache License v2.0. + Returns: BaseXPPauli: _description_ """ @@ -522,14 +539,29 @@ def _append_circuit( # --------------------------------------------------------------------- def unique_vector_rep(self) -> "BaseXPPauli": - """_summary_""" + """_summary_ + Note: + This method is adapted from XPFpackage: + https://github.com/m-webster/XPFpackage, originally developed by + Mark Webster. The original code is licensed under the GNU General + Public License v3.0 and Mark Webster has given permission to use + the code under the Apache License v2.0. + """ return self._unique_vector_rep() def _unique_vector_rep(self) -> "BaseXPPauli": """(TODO improve doc): This is the equivalent of XPRound from Mark's code. It converts the XPPauli operator into unique vector form, ie phase_exp in Z modulo 2*precision, x in Z_2, z in Z modulo - precision.""" + precision. + + Note: + This method is adapted from XPFpackage: + https://github.com/m-webster/XPFpackage, originally developed by + Mark Webster. The original code is licensed under the GNU General + Public License v3.0 and Mark Webster has given permission to use + the code under the Apache License v2.0. + """ matrix = np.empty(shape=np.shape(self.matrix), dtype=np.int64) phase_exp = np.mod(self._phase_exp, 2 * self.precision) @@ -546,7 +578,15 @@ def _rescale_precision(self, new_precision: int) -> "BaseXPPauli": """(TODO improve doc): This is the equivalent of XPSetNsingle from Mark's code. It rescales the generalized symplectic vector components of XPPauli operator to the new precision. Returns None if the - rescaling is not possible, else returns the rescaled BaseXPPauli object.""" + rescaling is not possible, else returns the rescaled BaseXPPauli object. + + Note: + This method is adapted from XPFpackage: + https://github.com/m-webster/XPFpackage, originally developed by + Mark Webster. The original code is licensed under the GNU General + Public License v3.0 and Mark Webster has given permission to use + the code under the Apache License v2.0. + """ # TODO Currently, if any operator in an XPPauliList can not be # rescaled, this function will return None. diff --git a/qiskit_qec/operators/pauli_list.py b/qiskit_qec/operators/pauli_list.py index f2b326a4..eea6c6db 100644 --- a/qiskit_qec/operators/pauli_list.py +++ b/qiskit_qec/operators/pauli_list.py @@ -10,6 +10,11 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. # Part of the QEC framework +# +# This code is based on the paper: "The XP Stabiliser Formalism: a +# Generalisation of the Pauli Stabiliser Formalism with Arbitrary Phases", Mark +# A. Webster, Benjamin J. Brown, and Stephen D. Bartlett. Quantum 6, 815 +# (2022). """Module for Pauli List""" import numbers from collections import defaultdict diff --git a/qiskit_qec/operators/xp_pauli_list.py b/qiskit_qec/operators/xp_pauli_list.py index 204edaa8..2a5c2332 100644 --- a/qiskit_qec/operators/xp_pauli_list.py +++ b/qiskit_qec/operators/xp_pauli_list.py @@ -10,6 +10,11 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. # Part of the QEC framework +# +# This code is based on the paper: "The XP Stabiliser Formalism: a +# Generalisation of the Pauli Stabiliser Formalism with Arbitrary Phases", Mark +# A. Webster, Benjamin J. Brown, and Stephen D. Bartlett. Quantum 6, 815 +# (2022). """Module for XPPauli List""" import numbers from typing import Iterable, List, Tuple, Union, Optional From ab4ccc2554e38075db97b41d53562ec58dd7142c Mon Sep 17 00:00:00 2001 From: dhruvbhq Date: Thu, 10 Nov 2022 17:16:45 +0100 Subject: [PATCH 21/30] added acknowledgement note in individual methods of BaseXPPauli... --- qiskit_qec/operators/base_xp_pauli.py | 137 ++++++++++++++++++++------ qiskit_qec/operators/random.py | 8 ++ qiskit_qec/operators/xp_pauli.py | 39 ++++++++ qiskit_qec/operators/xp_pauli_list.py | 7 ++ 4 files changed, 163 insertions(+), 28 deletions(-) diff --git a/qiskit_qec/operators/base_xp_pauli.py b/qiskit_qec/operators/base_xp_pauli.py index a3d102c0..c7dbe546 100644 --- a/qiskit_qec/operators/base_xp_pauli.py +++ b/qiskit_qec/operators/base_xp_pauli.py @@ -292,7 +292,7 @@ def compose( Note: This method does compose coordinate wise (which is different from the PauliTable compose which should be corrected at some point). - This method is adapted from XPFpackage: + This method is adapted from method XPMul from XPFpackage: https://github.com/m-webster/XPFpackage, originally developed by Mark Webster. The original code is licensed under the GNU General Public License v3.0 and Mark Webster has given permission to use @@ -347,7 +347,7 @@ def _compose( inplace (bool, optional): _description_. Defaults to False. Note: - This method is adapted from XPFpackage: + This method is adapted from method XPMul from XPFpackage: https://github.com/m-webster/XPFpackage, originally developed by Mark Webster. The original code is licensed under the GNU General Public License v3.0 and Mark Webster has given permission to use @@ -541,7 +541,7 @@ def _append_circuit( def unique_vector_rep(self) -> "BaseXPPauli": """_summary_ Note: - This method is adapted from XPFpackage: + This method is adapted from method XPRound from XPFpackage: https://github.com/m-webster/XPFpackage, originally developed by Mark Webster. The original code is licensed under the GNU General Public License v3.0 and Mark Webster has given permission to use @@ -550,13 +550,12 @@ def unique_vector_rep(self) -> "BaseXPPauli": return self._unique_vector_rep() def _unique_vector_rep(self) -> "BaseXPPauli": - """(TODO improve doc): This is the equivalent of XPRound from Mark's - code. It converts the XPPauli operator into unique vector form, ie + """(TODO improve doc): This method converts the XPPauli operator into unique vector form, ie phase_exp in Z modulo 2*precision, x in Z_2, z in Z modulo precision. Note: - This method is adapted from XPFpackage: + This method is adapted from method XPRound from XPFpackage: https://github.com/m-webster/XPFpackage, originally developed by Mark Webster. The original code is licensed under the GNU General Public License v3.0 and Mark Webster has given permission to use @@ -571,17 +570,24 @@ def _unique_vector_rep(self) -> "BaseXPPauli": return BaseXPPauli(matrix, phase_exp, self.precision) def rescale_precision(self, new_precision: int) -> "BaseXPPauli": - """_summary_""" + """_summary_ + + Note: + This method is adapted from method XPSetN from XPFpackage: + https://github.com/m-webster/XPFpackage, originally developed by + Mark Webster. The original code is licensed under the GNU General + Public License v3.0 and Mark Webster has given permission to use + the code under the Apache License v2.0. + """ return self._rescale_precision(new_precision) def _rescale_precision(self, new_precision: int) -> "BaseXPPauli": - """(TODO improve doc): This is the equivalent of XPSetNsingle from - Mark's code. It rescales the generalized symplectic vector components + """(TODO improve doc): It rescales the generalized symplectic vector components of XPPauli operator to the new precision. Returns None if the rescaling is not possible, else returns the rescaled BaseXPPauli object. Note: - This method is adapted from XPFpackage: + This method is adapted from method XPSetN from XPFpackage: https://github.com/m-webster/XPFpackage, originally developed by Mark Webster. The original code is licensed under the GNU General Public License v3.0 and Mark Webster has given permission to use @@ -618,33 +624,78 @@ def _rescale_precision(self, new_precision: int) -> "BaseXPPauli": return BaseXPPauli(matrix, phase_exp, new_precision) def weight(self) -> Union[int, np.ndarray]: - """_summary_""" + """_summary_ + + Note: + This method is adapted from method XPDistance from XPFpackage: + https://github.com/m-webster/XPFpackage, originally developed by + Mark Webster. The original code is licensed under the GNU General + Public License v3.0 and Mark Webster has given permission to use + the code under the Apache License v2.0. + """ return self._weight() def _weight(self) -> Union[int, np.ndarray]: - """(TODO improve doc) This is the equivalent of XPDistance function - from Mark's code. It returns the count of qubits where either z or x - component is nonzero.""" + """(TODO improve doc) This method returns the count of qubits where either z or x + component is nonzero. + + Note: + This method is adapted from method XPDistance from XPFpackage: + https://github.com/m-webster/XPFpackage, originally developed by + Mark Webster. The original code is licensed under the GNU General + Public License v3.0 and Mark Webster has given permission to use + the code under the Apache License v2.0. + """ return np.sum(np.logical_or(self.x, self.z), axis=-1) def is_diagonal(self) -> np.ndarray: - """_summary_""" + """_summary_ + + Note: + This method is adapted from method XPisDiag from XPFpackage: + https://github.com/m-webster/XPFpackage, originally developed by + Mark Webster. The original code is licensed under the GNU General + Public License v3.0 and Mark Webster has given permission to use + the code under the Apache License v2.0. + """ return self._is_diagonal() def _is_diagonal(self) -> np.ndarray: - """(TODO improve doc) This is the equivalent of XPisDiag function from - Mark's code. Returns True if the XP operator is diagonal.""" + """(TODO improve doc) Returns True if the XP operator is diagonal. + + Note: + This method is adapted from method XPisDiag from XPFpackage: + https://github.com/m-webster/XPFpackage, originally developed by + Mark Webster. The original code is licensed under the GNU General + Public License v3.0 and Mark Webster has given permission to use + the code under the Apache License v2.0. + """ return np.where(np.sum(self.x, axis=-1) == 0, True, False) def antisymmetric_op(self) -> "BaseXPPauli": - """_summary_""" + """_summary_ + + Note: + This method is adapted from method XPD from XPFpackage: + https://github.com/m-webster/XPFpackage, originally developed by + Mark Webster. The original code is licensed under the GNU General + Public License v3.0 and Mark Webster has given permission to use + the code under the Apache License v2.0. + """ return self._antisymmetric_op() def _antisymmetric_op(self) -> "BaseXPPauli": - """(TODO improve doc) This is the equivalent of XPD function from - Mark's code. It returns the antisymmetric operator corresponding to the + """(TODO improve doc) This method returns the antisymmetric operator corresponding to the z component of XP operator, only if x component is 0, else it returns - None.""" + None. + + Note: + This method is adapted from method XPD from XPFpackage: + https://github.com/m-webster/XPFpackage, originally developed by + Mark Webster. The original code is licensed under the GNU General + Public License v3.0 and Mark Webster has given permission to use + the code under the Apache License v2.0. + """ if np.any(self.x): # TODO should there be an assertion here? @@ -657,13 +708,28 @@ def _antisymmetric_op(self) -> "BaseXPPauli": return BaseXPPauli(matrix=matrix, phase_exp=phase_exp, precision=self.precision) def power(self, n: int) -> "BaseXPPauli": - """_summary_""" + """_summary_ + + Note: + This method is adapted from method XPPower from XPFpackage: + https://github.com/m-webster/XPFpackage, originally developed by + Mark Webster. The original code is licensed under the GNU General + Public License v3.0 and Mark Webster has given permission to use + the code under the Apache License v2.0. + """ return self._power(n) def _power(self, n: int) -> "BaseXPPauli": - """(TODO improve doc) This is te equivalent of XPPower function from - Mark's code. It returns the XP operator of specified precision raised - to the power n.""" + """(TODO improve doc) This method returns the XP operator of specified precision raised + to the power n. + + Note: + This method is adapted from method XPPower from XPFpackage: + https://github.com/m-webster/XPFpackage, originally developed by + Mark Webster. The original code is licensed under the GNU General + Public License v3.0 and Mark Webster has given permission to use + the code under the Apache License v2.0. + """ # TODO at present, this function only handles positive powers. If it is # supposed to calculate inverses as well, that functionality needs to # be coded. @@ -691,12 +757,27 @@ def _power(self, n: int) -> "BaseXPPauli": return product._unique_vector_rep() def degree(self) -> np.ndarray: - """_summary_""" + """_summary_ + + Note: + This method is adapted from method XPDegree from XPFpackage: + https://github.com/m-webster/XPFpackage, originally developed by + Mark Webster. The original code is licensed under the GNU General + Public License v3.0 and Mark Webster has given permission to use + the code under the Apache License v2.0. + """ return self._degree() def _degree(self) -> np.ndarray: - """(TODO improve doc) This is the equivalent of XPDegree from Mark's - code. It returns the degree of XP operator.""" + """(TODO improve doc) This method returns the degree of XP operator. + + Note: + This method is adapted from method XPDegree from XPFpackage: + https://github.com/m-webster/XPFpackage, originally developed by + Mark Webster. The original code is licensed under the GNU General + Public License v3.0 and Mark Webster has given permission to use + the code under the Apache License v2.0. + """ gcd = np.gcd(self.z, self.precision) precision_by_gcd = np.floor_divide(self.precision, gcd) diff --git a/qiskit_qec/operators/random.py b/qiskit_qec/operators/random.py index 56a762da..ec7dc5c6 100644 --- a/qiskit_qec/operators/random.py +++ b/qiskit_qec/operators/random.py @@ -205,6 +205,13 @@ def random_xppauli( ) -> XPPauli: """Return a random XPPauli. + Note: + This method is adapted from XPFpackage: + https://github.com/m-webster/XPFpackage, originally developed by + Mark Webster. The original code is licensed under the GNU General + Public License v3.0 and Mark Webster has given permission to use + the code under the Apache License v2.0. + Args: num_qubits (int): the number of qubits. precision (int): Precision of XP operators. Must be an integer @@ -225,6 +232,7 @@ def random_xppauli( x = rng.integers(2, size=num_qubits, dtype=bool) # TODO: Need to decide whether we will add an argument group_phase in # analogy with random_pauli. If yes, its implementation goes here. + # Mark's code randomizes phase modulo 2*precision. phase = rng.integers(2 * precision, dtype=np.int64) xppauli = XPPauli((z, x, phase)) diff --git a/qiskit_qec/operators/xp_pauli.py b/qiskit_qec/operators/xp_pauli.py index b97c115c..a6d33786 100644 --- a/qiskit_qec/operators/xp_pauli.py +++ b/qiskit_qec/operators/xp_pauli.py @@ -141,6 +141,13 @@ def compose( ) -> "XPPauli": """Return the operator composition with another XPPauli. + Note: + This method is adapted from method XPMul from XPFpackage: + https://github.com/m-webster/XPFpackage, originally developed by + Mark Webster. The original code is licensed under the GNU General + Public License v3.0 and Mark Webster has given permission to use + the code under the Apache License v2.0. + Args: other (XPPauli): a XPPauli object. qargs (list or None): Optional, qubits to apply dot product @@ -175,15 +182,47 @@ def compose( # --------------------------------------------------------------------- def unique_vector_rep(self) -> "XPPauli": + """ + Note: + This method is adapted from the method XPRound from XPFpackage: + https://github.com/m-webster/XPFpackage, originally developed by + Mark Webster. The original code is licensed under the GNU General + Public License v3.0 and Mark Webster has given permission to use + the code under the Apache License v2.0. + """ return XPPauli(super().unique_vector_rep()) def rescale_precision(self, new_precision) -> "XPPauli": + """ + Note: + This method is adapted from the method XPSetN from XPFpackage: + https://github.com/m-webster/XPFpackage, originally developed by + Mark Webster. The original code is licensed under the GNU General + Public License v3.0 and Mark Webster has given permission to use + the code under the Apache License v2.0. + """ return XPPauli(super().rescale_precision(new_precision)) def antisymmetric_op(self) -> "XPPauli": + """ + Note: + This method is adapted from method XPD from XPFpackage: + https://github.com/m-webster/XPFpackage, originally developed by + Mark Webster. The original code is licensed under the GNU General + Public License v3.0 and Mark Webster has given permission to use + the code under the Apache License v2.0. + """ return XPPauli(super().antisymmetric_op()) def power(self, n) -> "XPPauli": + """ + Note: + This method is adapted from method XPPower from XPFpackage: + https://github.com/m-webster/XPFpackage, originally developed by + Mark Webster. The original code is licensed under the GNU General + Public License v3.0 and Mark Webster has given permission to use + the code under the Apache License v2.0. + """ return XPPauli(super().power(n)) diff --git a/qiskit_qec/operators/xp_pauli_list.py b/qiskit_qec/operators/xp_pauli_list.py index 2a5c2332..39e565bf 100644 --- a/qiskit_qec/operators/xp_pauli_list.py +++ b/qiskit_qec/operators/xp_pauli_list.py @@ -252,6 +252,13 @@ def compose( ) -> "XPPauliList": """Return the composition self∘other for each XPPauli in the list. + Note: + This method is adapted from the method XPMul from XPFpackage: + https://github.com/m-webster/XPFpackage, originally developed by + Mark Webster. The original code is licensed under the GNU General + Public License v3.0 and Mark Webster has given permission to use + the code under the Apache License v2.0. + Args: other (XPPauliList): another XPPauliList. qargs (None or list): qubits to apply dot product on (Default: None). From b94bd1a3040ea25a73c2f106f4e2fe8bebb89561 Mon Sep 17 00:00:00 2001 From: dhruvbhq Date: Thu, 10 Nov 2022 17:46:02 +0100 Subject: [PATCH 22/30] added see also in BaseXPPauli... methods --- qiskit_qec/operators/base_xp_pauli.py | 50 +++++++++++++++++++++++---- qiskit_qec/operators/xp_pauli.py | 15 ++++++++ qiskit_qec/operators/xp_pauli_list.py | 3 ++ 3 files changed, 61 insertions(+), 7 deletions(-) diff --git a/qiskit_qec/operators/base_xp_pauli.py b/qiskit_qec/operators/base_xp_pauli.py index c7dbe546..8c9bc10a 100644 --- a/qiskit_qec/operators/base_xp_pauli.py +++ b/qiskit_qec/operators/base_xp_pauli.py @@ -310,6 +310,9 @@ def compose( Raises: QiskitError: if number of qubits of other does not match qargs. + + See also: + _compose """ # Validation @@ -339,13 +342,6 @@ def _compose( ) -> "BaseXPPauli": """Returns the composition of two BaseXPPauli objects. - Args: - a : BaseXPPauli object - b : BaseXPPauli object - qargs (Optional[list], optional): _description_. Defaults to None. - front (bool, optional): _description_. Defaults to False. - inplace (bool, optional): _description_. Defaults to False. - Note: This method is adapted from method XPMul from XPFpackage: https://github.com/m-webster/XPFpackage, originally developed by @@ -353,8 +349,18 @@ def _compose( Public License v3.0 and Mark Webster has given permission to use the code under the Apache License v2.0. + Args: + a : BaseXPPauli object + b : BaseXPPauli object + qargs (Optional[list], optional): _description_. Defaults to None. + front (bool, optional): _description_. Defaults to False. + inplace (bool, optional): _description_. Defaults to False. + Returns: BaseXPPauli: _description_ + + See also: + unique_vector_rep, _unique_vector_rep """ assert a.precision == b.precision, QiskitError( @@ -546,6 +552,9 @@ def unique_vector_rep(self) -> "BaseXPPauli": Mark Webster. The original code is licensed under the GNU General Public License v3.0 and Mark Webster has given permission to use the code under the Apache License v2.0. + + See also: + _unique_vector_rep """ return self._unique_vector_rep() @@ -578,6 +587,9 @@ def rescale_precision(self, new_precision: int) -> "BaseXPPauli": Mark Webster. The original code is licensed under the GNU General Public License v3.0 and Mark Webster has given permission to use the code under the Apache License v2.0. + + See also: + _rescale_precision """ return self._rescale_precision(new_precision) @@ -592,6 +604,9 @@ def _rescale_precision(self, new_precision: int) -> "BaseXPPauli": Mark Webster. The original code is licensed under the GNU General Public License v3.0 and Mark Webster has given permission to use the code under the Apache License v2.0. + + See also: + unique_vector_rep """ # TODO Currently, if any operator in an XPPauliList can not be @@ -632,6 +647,9 @@ def weight(self) -> Union[int, np.ndarray]: Mark Webster. The original code is licensed under the GNU General Public License v3.0 and Mark Webster has given permission to use the code under the Apache License v2.0. + + See also: + _weight """ return self._weight() @@ -657,6 +675,9 @@ def is_diagonal(self) -> np.ndarray: Mark Webster. The original code is licensed under the GNU General Public License v3.0 and Mark Webster has given permission to use the code under the Apache License v2.0. + + See also: + _is_diagonal """ return self._is_diagonal() @@ -681,6 +702,9 @@ def antisymmetric_op(self) -> "BaseXPPauli": Mark Webster. The original code is licensed under the GNU General Public License v3.0 and Mark Webster has given permission to use the code under the Apache License v2.0. + + See also: + _antisymmetric_op """ return self._antisymmetric_op() @@ -716,6 +740,9 @@ def power(self, n: int) -> "BaseXPPauli": Mark Webster. The original code is licensed under the GNU General Public License v3.0 and Mark Webster has given permission to use the code under the Apache License v2.0. + + See also: + _power """ return self._power(n) @@ -729,6 +756,9 @@ def _power(self, n: int) -> "BaseXPPauli": Mark Webster. The original code is licensed under the GNU General Public License v3.0 and Mark Webster has given permission to use the code under the Apache License v2.0. + + See also: + _unique_vector_rep """ # TODO at present, this function only handles positive powers. If it is # supposed to calculate inverses as well, that functionality needs to @@ -765,6 +795,9 @@ def degree(self) -> np.ndarray: Mark Webster. The original code is licensed under the GNU General Public License v3.0 and Mark Webster has given permission to use the code under the Apache License v2.0. + + See also: + _degree """ return self._degree() @@ -777,6 +810,9 @@ def _degree(self) -> np.ndarray: Mark Webster. The original code is licensed under the GNU General Public License v3.0 and Mark Webster has given permission to use the code under the Apache License v2.0. + + See also: + is_diagonal """ gcd = np.gcd(self.z, self.precision) diff --git a/qiskit_qec/operators/xp_pauli.py b/qiskit_qec/operators/xp_pauli.py index a6d33786..61b3db1c 100644 --- a/qiskit_qec/operators/xp_pauli.py +++ b/qiskit_qec/operators/xp_pauli.py @@ -172,6 +172,9 @@ def compose( Setting the ``front=True`` kwarg changes this to `right` matrix multiplication and is equivalent to the :meth:`dot` method ``A.dot(B) == A.compose(B, front=True)``. + + See also: + _compose """ if qargs is None: qargs = getattr(other, "qargs", None) @@ -189,6 +192,9 @@ def unique_vector_rep(self) -> "XPPauli": Mark Webster. The original code is licensed under the GNU General Public License v3.0 and Mark Webster has given permission to use the code under the Apache License v2.0. + + See also: + _unique_vector_rep """ return XPPauli(super().unique_vector_rep()) @@ -200,6 +206,9 @@ def rescale_precision(self, new_precision) -> "XPPauli": Mark Webster. The original code is licensed under the GNU General Public License v3.0 and Mark Webster has given permission to use the code under the Apache License v2.0. + + See also: + _rescale_precision """ return XPPauli(super().rescale_precision(new_precision)) @@ -211,6 +220,9 @@ def antisymmetric_op(self) -> "XPPauli": Mark Webster. The original code is licensed under the GNU General Public License v3.0 and Mark Webster has given permission to use the code under the Apache License v2.0. + + See also: + _antisymmetric_op """ return XPPauli(super().antisymmetric_op()) @@ -222,6 +234,9 @@ def power(self, n) -> "XPPauli": Mark Webster. The original code is licensed under the GNU General Public License v3.0 and Mark Webster has given permission to use the code under the Apache License v2.0. + + See also: + _power """ return XPPauli(super().power(n)) diff --git a/qiskit_qec/operators/xp_pauli_list.py b/qiskit_qec/operators/xp_pauli_list.py index 39e565bf..0f273b4a 100644 --- a/qiskit_qec/operators/xp_pauli_list.py +++ b/qiskit_qec/operators/xp_pauli_list.py @@ -273,6 +273,9 @@ def compose( not have either 1 or the same number of XPPaulis as the current list, or has the wrong number of qubits for the specified qargs. + + See also: + _compose """ if qargs is None: qargs = getattr(other, "qargs", None) From 966f53235394d327f562b21ee3309d29b7595229 Mon Sep 17 00:00:00 2001 From: dhruvbhq Date: Thu, 10 Nov 2022 18:57:47 +0100 Subject: [PATCH 23/30] changed assertion to if...raise and moved check to higher level method --- qiskit_qec/operators/base_xp_pauli.py | 10 +++++----- qiskit_qec/operators/xp_pauli.py | 6 +++++- qiskit_qec/operators/xp_pauli_list.py | 8 ++++++-- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/qiskit_qec/operators/base_xp_pauli.py b/qiskit_qec/operators/base_xp_pauli.py index 8c9bc10a..b7bd1155 100644 --- a/qiskit_qec/operators/base_xp_pauli.py +++ b/qiskit_qec/operators/base_xp_pauli.py @@ -309,7 +309,8 @@ def compose( BaseXPPauli : Compositon of self and other Raises: - QiskitError: if number of qubits of other does not match qargs. + QiskitError: if number of qubits of other does not match qargs, or + if precision of other does not match precision of self. See also: _compose @@ -330,6 +331,9 @@ def compose( "either have 1 or the same number of XPPaulis." ) + if self.precision != other.precision: + raise QiskitError("Precision of the two BaseXPPaulis to be multiplied must be the same.") + return self._compose(self, other, qargs=qargs, front=front, inplace=inplace) @staticmethod @@ -363,10 +367,6 @@ def _compose( unique_vector_rep, _unique_vector_rep """ - assert a.precision == b.precision, QiskitError( - "Precision of the two BaseXPPaulis to be multiplied must be the same." - ) - if qargs is not None: qargs = list(qargs) + [item + a.num_qubits for item in qargs] amat = a.matrix[:, qargs] diff --git a/qiskit_qec/operators/xp_pauli.py b/qiskit_qec/operators/xp_pauli.py index 61b3db1c..14ac7bc3 100644 --- a/qiskit_qec/operators/xp_pauli.py +++ b/qiskit_qec/operators/xp_pauli.py @@ -161,7 +161,8 @@ def compose( Raises: QiskitError: if other cannot be converted to an operator, or has - incompatible dimensions for specified subsystems. + incompatible dimensions for specified subsystems, or + if precision of other does not match precision of self. .. note:: Composition (``&``) by default is defined as `left` matrix multiplication for @@ -180,6 +181,9 @@ def compose( qargs = getattr(other, "qargs", None) if not isinstance(other, XPPauli): other = XPPauli(other) + if self.precision != other.precision: + raise QiskitError("Precision of the two XPPaulis to be multiplied must be the same.") + return XPPauli(super().compose(other, qargs=qargs, front=front, inplace=inplace)) # --------------------------------------------------------------------- diff --git a/qiskit_qec/operators/xp_pauli_list.py b/qiskit_qec/operators/xp_pauli_list.py index 0f273b4a..763f4a3f 100644 --- a/qiskit_qec/operators/xp_pauli_list.py +++ b/qiskit_qec/operators/xp_pauli_list.py @@ -271,8 +271,9 @@ def compose( Raises: QiskitError: if other cannot be converted to a XPPauliList, does not have either 1 or the same number of XPPaulis as - the current list, or has the wrong number of qubits - for the specified qargs. + the current list, has the wrong number of qubits + for the specified qargs, or if precision of other + does not match precision of self. See also: _compose @@ -286,6 +287,9 @@ def compose( "Incompatible XPPauliLists. Other list must " "have either 1 or the same number of XPPaulis." ) + if self.precision != other.precision: + raise QiskitError("Precision of the two XPPauliLists to be multiplied must be the same.") + return XPPauliList(super().compose(other, qargs=qargs, front=front, inplace=inplace)) # def conjugate(self): From 603aac0b43740e5e5ae447859a167838b642a20f Mon Sep 17 00:00:00 2001 From: dhruvbhq Date: Thu, 10 Nov 2022 19:05:34 +0100 Subject: [PATCH 24/30] changed assertion to if...raise --- qiskit_qec/operators/base_xp_pauli.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/qiskit_qec/operators/base_xp_pauli.py b/qiskit_qec/operators/base_xp_pauli.py index b7bd1155..c7870336 100644 --- a/qiskit_qec/operators/base_xp_pauli.py +++ b/qiskit_qec/operators/base_xp_pauli.py @@ -86,7 +86,8 @@ def __init__( precision: Precision of XP operators. Must be an integer greater than or equal to 2. order: Set to 'xz' or 'zx'. Defines which side the x and z parts of the input matrix - Raises: QiskitError: matrix and phase_exp sizes are not compatible + Raises: + QiskitError: matrix and phase_exp sizes are not compatible, or if precision is less than 2. Examples: >>> matrix = numpy.array([[1,1,0,0],[0,1,0,1]]) @@ -96,9 +97,8 @@ def __init__( Pauli, PauliList """ - assert isinstance(precision, int) and (np.all(precision > 1)), QiskitError( - "Precision of XP operators must be an integer greater than or equal to 2." - ) + if not (isinstance(precision, int) and (precision > 1)): + raise QiskitError("Precision of XP operators must be an integer greater than or equal to 2.") if matrix is None or matrix.size == 0: matrix = np.empty(shape=(0, 0), dtype=np.int64) From dbabc4c8c59fd8f30ac320d12e5af93369901d36 Mon Sep 17 00:00:00 2001 From: dhruvbhq Date: Thu, 10 Nov 2022 19:50:19 +0100 Subject: [PATCH 25/30] added docstrings for BaseXPPauli... methods --- qiskit_qec/operators/base_xp_pauli.py | 95 ++++++++++++++++++++++----- qiskit_qec/operators/xp_pauli.py | 41 ++++++++++-- 2 files changed, 113 insertions(+), 23 deletions(-) diff --git a/qiskit_qec/operators/base_xp_pauli.py b/qiskit_qec/operators/base_xp_pauli.py index c7870336..080b1f01 100644 --- a/qiskit_qec/operators/base_xp_pauli.py +++ b/qiskit_qec/operators/base_xp_pauli.py @@ -545,7 +545,10 @@ def _append_circuit( # --------------------------------------------------------------------- def unique_vector_rep(self) -> "BaseXPPauli": - """_summary_ + """Convert the BaseXPPauli operator into unique vector form, ie + phase_exp in Z modulo 2*precision, x in Z_2, z in Z modulo + precision. + Note: This method is adapted from method XPRound from XPFpackage: https://github.com/m-webster/XPFpackage, originally developed by @@ -553,13 +556,16 @@ def unique_vector_rep(self) -> "BaseXPPauli": Public License v3.0 and Mark Webster has given permission to use the code under the Apache License v2.0. + Returns: + BaseXPPauli: Unique vector representation of self + See also: _unique_vector_rep """ return self._unique_vector_rep() def _unique_vector_rep(self) -> "BaseXPPauli": - """(TODO improve doc): This method converts the XPPauli operator into unique vector form, ie + """Convert the BaseXPPauli operator into unique vector form, ie phase_exp in Z modulo 2*precision, x in Z_2, z in Z modulo precision. @@ -569,6 +575,9 @@ def _unique_vector_rep(self) -> "BaseXPPauli": Mark Webster. The original code is licensed under the GNU General Public License v3.0 and Mark Webster has given permission to use the code under the Apache License v2.0. + + Returns: + BaseXPPauli: Unique vector representation of self """ matrix = np.empty(shape=np.shape(self.matrix), dtype=np.int64) @@ -579,7 +588,9 @@ def _unique_vector_rep(self) -> "BaseXPPauli": return BaseXPPauli(matrix, phase_exp, self.precision) def rescale_precision(self, new_precision: int) -> "BaseXPPauli": - """_summary_ + """Rescale the generalized symplectic vector components + of BaseXPPauli operator to the new precision. Returns None if the + rescaling is not possible, else returns the rescaled BaseXPPauli object. Note: This method is adapted from method XPSetN from XPFpackage: @@ -588,14 +599,23 @@ def rescale_precision(self, new_precision: int) -> "BaseXPPauli": Public License v3.0 and Mark Webster has given permission to use the code under the Apache License v2.0. + Args: + new_precision: The target precision in which BaseXPPauli is to be expressed + + Returns: + BaseXPPauli: Resultant of rescaling the precision of BaseXPPauli + + Raises: + QiskitError: If it is not possible to express BaseXPPauli in new_precision + See also: _rescale_precision """ return self._rescale_precision(new_precision) def _rescale_precision(self, new_precision: int) -> "BaseXPPauli": - """(TODO improve doc): It rescales the generalized symplectic vector components - of XPPauli operator to the new precision. Returns None if the + """Rescale the generalized symplectic vector components + of BaseXPPauli operator to the new precision. Returns None if the rescaling is not possible, else returns the rescaled BaseXPPauli object. Note: @@ -605,6 +625,12 @@ def _rescale_precision(self, new_precision: int) -> "BaseXPPauli": Public License v3.0 and Mark Webster has given permission to use the code under the Apache License v2.0. + Args: + new_precision: The target precision in which BaseXPPauli is to be expressed + + Returns: + BaseXPPauli: Resultant of rescaling the precision of BaseXPPauli + See also: unique_vector_rep """ @@ -639,7 +665,7 @@ def _rescale_precision(self, new_precision: int) -> "BaseXPPauli": return BaseXPPauli(matrix, phase_exp, new_precision) def weight(self) -> Union[int, np.ndarray]: - """_summary_ + """Return the weight, i.e. count of qubits where either z or x component is nonzero. Note: This method is adapted from method XPDistance from XPFpackage: @@ -648,14 +674,16 @@ def weight(self) -> Union[int, np.ndarray]: Public License v3.0 and Mark Webster has given permission to use the code under the Apache License v2.0. + Returns: + Union[int, np.ndarray]: Weight of BaseXPPauli + See also: _weight """ return self._weight() def _weight(self) -> Union[int, np.ndarray]: - """(TODO improve doc) This method returns the count of qubits where either z or x - component is nonzero. + """Return the weight, i.e. count of qubits where either z or x component is nonzero. Note: This method is adapted from method XPDistance from XPFpackage: @@ -663,11 +691,14 @@ def _weight(self) -> Union[int, np.ndarray]: Mark Webster. The original code is licensed under the GNU General Public License v3.0 and Mark Webster has given permission to use the code under the Apache License v2.0. + + Returns: + Union[int, np.ndarray]: Weight of BaseXPPauli """ return np.sum(np.logical_or(self.x, self.z), axis=-1) def is_diagonal(self) -> np.ndarray: - """_summary_ + """Return True if the XP operator is diagonal. Note: This method is adapted from method XPisDiag from XPFpackage: @@ -676,13 +707,16 @@ def is_diagonal(self) -> np.ndarray: Public License v3.0 and Mark Webster has given permission to use the code under the Apache License v2.0. + Returns: + np.ndarray: True, if BaseXPPauli is diagonal + See also: _is_diagonal """ return self._is_diagonal() def _is_diagonal(self) -> np.ndarray: - """(TODO improve doc) Returns True if the XP operator is diagonal. + """Return True if the XP operator is diagonal. Note: This method is adapted from method XPisDiag from XPFpackage: @@ -690,11 +724,15 @@ def _is_diagonal(self) -> np.ndarray: Mark Webster. The original code is licensed under the GNU General Public License v3.0 and Mark Webster has given permission to use the code under the Apache License v2.0. + + Returns: + np.ndarray: True, if BaseXPPauli is diagonal """ return np.where(np.sum(self.x, axis=-1) == 0, True, False) def antisymmetric_op(self) -> "BaseXPPauli": - """_summary_ + """Return the antisymmetric operator corresponding to the + z component of XP operator, only if x component is 0. Note: This method is adapted from method XPD from XPFpackage: @@ -703,13 +741,16 @@ def antisymmetric_op(self) -> "BaseXPPauli": Public License v3.0 and Mark Webster has given permission to use the code under the Apache License v2.0. + Returns: + BaseXPPauli: Antisymmetric operator corresponding to BaseXPPauli, if x is 0 + See also: _antisymmetric_op """ return self._antisymmetric_op() def _antisymmetric_op(self) -> "BaseXPPauli": - """(TODO improve doc) This method returns the antisymmetric operator corresponding to the + """Return the antisymmetric operator corresponding to the z component of XP operator, only if x component is 0, else it returns None. @@ -719,6 +760,9 @@ def _antisymmetric_op(self) -> "BaseXPPauli": Mark Webster. The original code is licensed under the GNU General Public License v3.0 and Mark Webster has given permission to use the code under the Apache License v2.0. + + Returns: + BaseXPPauli: Antisymmetric operator corresponding to BaseXPPauli, if x is 0 """ if np.any(self.x): @@ -732,7 +776,7 @@ def _antisymmetric_op(self) -> "BaseXPPauli": return BaseXPPauli(matrix=matrix, phase_exp=phase_exp, precision=self.precision) def power(self, n: int) -> "BaseXPPauli": - """_summary_ + """Return the XP operator of specified precision raised to the power n. Note: This method is adapted from method XPPower from XPFpackage: @@ -741,14 +785,19 @@ def power(self, n: int) -> "BaseXPPauli": Public License v3.0 and Mark Webster has given permission to use the code under the Apache License v2.0. + Args: + n: The power to which BaseXPPauli is to be raised + + Returns: + BaseXPPauli: BaseXPPauli raised to the power n + See also: _power """ return self._power(n) def _power(self, n: int) -> "BaseXPPauli": - """(TODO improve doc) This method returns the XP operator of specified precision raised - to the power n. + """Return the XP operator of specified precision raised to the power n. Note: This method is adapted from method XPPower from XPFpackage: @@ -757,6 +806,12 @@ def _power(self, n: int) -> "BaseXPPauli": Public License v3.0 and Mark Webster has given permission to use the code under the Apache License v2.0. + Args: + n: The power to which BaseXPPauli is to be raised + + Returns: + BaseXPPauli: BaseXPPauli raised to the power n + See also: _unique_vector_rep """ @@ -787,7 +842,7 @@ def _power(self, n: int) -> "BaseXPPauli": return product._unique_vector_rep() def degree(self) -> np.ndarray: - """_summary_ + """Return the degree of XP operator. Note: This method is adapted from method XPDegree from XPFpackage: @@ -796,13 +851,16 @@ def degree(self) -> np.ndarray: Public License v3.0 and Mark Webster has given permission to use the code under the Apache License v2.0. + Returns: + np.ndarray: Degree of BaseXPPauli + See also: _degree """ return self._degree() def _degree(self) -> np.ndarray: - """(TODO improve doc) This method returns the degree of XP operator. + """Return the degree of XP operator. Note: This method is adapted from method XPDegree from XPFpackage: @@ -811,6 +869,9 @@ def _degree(self) -> np.ndarray: Public License v3.0 and Mark Webster has given permission to use the code under the Apache License v2.0. + Returns: + np.ndarray: Degree of BaseXPPauli + See also: is_diagonal """ diff --git a/qiskit_qec/operators/xp_pauli.py b/qiskit_qec/operators/xp_pauli.py index 14ac7bc3..472f4d70 100644 --- a/qiskit_qec/operators/xp_pauli.py +++ b/qiskit_qec/operators/xp_pauli.py @@ -189,35 +189,54 @@ def compose( # --------------------------------------------------------------------- def unique_vector_rep(self) -> "XPPauli": - """ + """Convert the XPPauli operator into unique vector form, ie + phase_exp in Z modulo 2*precision, x in Z_2, z in Z modulo + precision. + Note: - This method is adapted from the method XPRound from XPFpackage: + This method is adapted from method XPRound from XPFpackage: https://github.com/m-webster/XPFpackage, originally developed by Mark Webster. The original code is licensed under the GNU General Public License v3.0 and Mark Webster has given permission to use the code under the Apache License v2.0. + Returns: + XPPauli: Unique vector representation of self + See also: _unique_vector_rep """ return XPPauli(super().unique_vector_rep()) def rescale_precision(self, new_precision) -> "XPPauli": - """ + """Rescale the generalized symplectic vector components + of XPPauli operator to the new precision. Returns the rescaled XPPauli object. + Note: - This method is adapted from the method XPSetN from XPFpackage: + This method is adapted from method XPSetN from XPFpackage: https://github.com/m-webster/XPFpackage, originally developed by Mark Webster. The original code is licensed under the GNU General Public License v3.0 and Mark Webster has given permission to use the code under the Apache License v2.0. + Args: + new_precision: The target precision in which XPPauli is to be expressed + + Returns: + XPPauli: Resultant of rescaling the precision of XPPauli + + Raises: + QiskitError: If it is not possible to express XPPauli in new_precision + See also: _rescale_precision """ return XPPauli(super().rescale_precision(new_precision)) def antisymmetric_op(self) -> "XPPauli": - """ + """Return the antisymmetric operator corresponding to the + z component of XP operator, only if x component is 0. + Note: This method is adapted from method XPD from XPFpackage: https://github.com/m-webster/XPFpackage, originally developed by @@ -225,13 +244,17 @@ def antisymmetric_op(self) -> "XPPauli": Public License v3.0 and Mark Webster has given permission to use the code under the Apache License v2.0. + Returns: + XPPauli: Antisymmetric operator corresponding to XPPauli, if x is 0 + See also: _antisymmetric_op """ return XPPauli(super().antisymmetric_op()) def power(self, n) -> "XPPauli": - """ + """Return the XP operator of specified precision raised to the power n. + Note: This method is adapted from method XPPower from XPFpackage: https://github.com/m-webster/XPFpackage, originally developed by @@ -239,6 +262,12 @@ def power(self, n) -> "XPPauli": Public License v3.0 and Mark Webster has given permission to use the code under the Apache License v2.0. + Args: + n: The power to which XPPauli is to be raised + + Returns: + XPPauli: XPPauli raised to the power n + See also: _power """ From 0ecc284dcff228918edb6b64b2d5c987865d46b2 Mon Sep 17 00:00:00 2001 From: dhruvbhq Date: Sun, 13 Nov 2022 14:19:12 +0100 Subject: [PATCH 26/30] XPF: 1. added examples in docstring 2. updated 1 method to work for xppaulilist 3. added test --- qiskit_qec/operators/base_xp_pauli.py | 86 +++++++++++++++++++++++---- qiskit_qec/operators/xp_pauli.py | 41 +++++++++++++ qiskit_qec/operators/xp_pauli_list.py | 9 +++ tests/operators/test_xp_pauli.py | 14 +++-- tests/operators/test_xp_pauli_list.py | 20 +++++++ 5 files changed, 155 insertions(+), 15 deletions(-) diff --git a/qiskit_qec/operators/base_xp_pauli.py b/qiskit_qec/operators/base_xp_pauli.py index 080b1f01..ebe51228 100644 --- a/qiskit_qec/operators/base_xp_pauli.py +++ b/qiskit_qec/operators/base_xp_pauli.py @@ -94,7 +94,7 @@ def __init__( >>> base_xp_pauli = BaseXPPauli(matrix) See Also: - Pauli, PauliList + XPPauli, XPPauliList """ if not (isinstance(precision, int) and (precision > 1)): @@ -311,6 +311,15 @@ def compose( Raises: QiskitError: if number of qubits of other does not match qargs, or if precision of other does not match precision of self. + + Examples: + >>> a = BaseXPPauli(matrix=np.array([0, 1, 0, 0, 2, 0], dtype=np.int64), phase_exp=6, precision=4) + >>> b = BaseXPPauli(matrix=np.array([1, 1, 1, 3, 3, 0], dtype=np.int64), phase_exp=2, precision=4) + >>> value = BaseXPPauli.compose(a, b) + >>> value.matrix + array([[1, 0, 1, 3, 3, 0]], dtype=int64) + >>> value._phase_exp + array([6]) See also: _compose @@ -559,6 +568,14 @@ def unique_vector_rep(self) -> "BaseXPPauli": Returns: BaseXPPauli: Unique vector representation of self + Examples: + >>> a = BaseXPPauli(matrix=np.array([0, 3, 1, 6, 4, 3], dtype=np.int64), phase_exp=11, precision=4) + >>> a = a.unique_vector_rep() + >>> a.matrix + np.array([[0, 1, 1, 2, 0, 3]], dtype=int64) + >>> a._phase_exp + array([3], dtype=int32) + See also: _unique_vector_rep """ @@ -607,6 +624,14 @@ def rescale_precision(self, new_precision: int) -> "BaseXPPauli": Raises: QiskitError: If it is not possible to express BaseXPPauli in new_precision + + Examples: + >>> a = BaseXPPauli(matrix=np.array([1, 1, 1, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0], dtype=np.int64), phase_exp=12, precision=8) + >>> a = a.rescale_precision(new_precision=2) + >>> a.matrix + array([[1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0]], dtype=int64) + >>> a._phase_exp + array([3, dtype=int32]) See also: _rescale_precision @@ -676,6 +701,11 @@ def weight(self) -> Union[int, np.ndarray]: Returns: Union[int, np.ndarray]: Weight of BaseXPPauli + + Examples: + >>> a = BaseXPPauli(matrix=np.array([1, 1, 1, 0, 0, 1, 0, 0, 3, 4, 0, 0, 0, 1], dtype=np.int64), phase_exp = 12, precision = 8) + >>> a.weight() + array([5]) See also: _weight @@ -710,6 +740,15 @@ def is_diagonal(self) -> np.ndarray: Returns: np.ndarray: True, if BaseXPPauli is diagonal + Examples: + >>> a = BaseXPPauli(matrix=np.array([0, 0, 0, 0, 0, 0, 0, 0, 1, 3, 3, 3, 3, 3], dtype=np.int64), phase_exp=0, precision=8) + >>> a.is_diagonal() + array([True]) + + >>> b = BaseXPPauli(matrix=np.array([0, 1, 0, 1, 0, 0, 0, 0, 1, 3, 3, 3, 3, 3], dtype=np.int64), phase_exp=12, precision=8) + >>> b.is_diagonal() + array([False]) + See also: _is_diagonal """ @@ -744,6 +783,14 @@ def antisymmetric_op(self) -> "BaseXPPauli": Returns: BaseXPPauli: Antisymmetric operator corresponding to BaseXPPauli, if x is 0 + Examples: + >>> a = BaseXPPauli(matrix=np.array([0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 3, 3, 3], dtype=np.int64), phase_exp=0, precision=8) + >>> value = a.antisymmetric_op() + >>> value.matrix + array([0, 0, 0, 0, 0, 0, 0, 0, -1, -2, -3, -3, -3, -3], dtype=int64) + >>> value._phase_exp + array([15]) + See also: _antisymmetric_op """ @@ -791,6 +838,14 @@ def power(self, n: int) -> "BaseXPPauli": Returns: BaseXPPauli: BaseXPPauli raised to the power n + Examples: + >>> a = BaseXPPauli(matrix=np.array([1, 1, 1, 0, 0, 1, 0, 0, 3, 4, 0, 0, 0, 1], dtype=np.int64), phase_exp=12, precision=8) + >>> value = a.power(n=5) + >>> value.matrix + array([1, 1, 1, 0, 0, 1, 0, 0, 3, 4, 0, 0, 0, 5], dtype=int64) + >>> value._phase_exp + array([8]) + See also: _power """ @@ -854,6 +909,11 @@ def degree(self) -> np.ndarray: Returns: np.ndarray: Degree of BaseXPPauli + Examples: + >>> a = BaseXPPauli(matrix=np.array([0, 0, 0, 2, 1, 0], dtype=np.int64), phase_exp=2, precision=4) + >>> a.degree() + array([4], dtype=int64) + See also: _degree """ @@ -877,17 +937,21 @@ def _degree(self) -> np.ndarray: """ gcd = np.gcd(self.z, self.precision) - precision_by_gcd = np.floor_divide(self.precision, gcd) - lcm = np.atleast_1d(precision_by_gcd)[0] - for i in precision_by_gcd: - lcm = np.lcm(lcm, i) - - square = type(self)(BaseXPPauli.compose(self, self)) + precision_by_gcd = np.atleast_2d(np.floor_divide(self.precision, gcd)) + lcm = np.atleast_2d(precision_by_gcd)[:,0] + for i,val in enumerate(precision_by_gcd): + for j in val: + lcm[i] = np.lcm(lcm[i], j) + + square = BaseXPPauli.compose(self, self) + if type(square) != type(self): + square = type(self)(square) gcd_square = np.gcd(square.z, square.precision) - precision_by_gcd_square = np.floor_divide(square.precision, gcd_square) - lcm_square = np.atleast_1d(precision_by_gcd_square)[0] - for i in precision_by_gcd_square: - lcm_square = np.lcm(lcm_square, i) + precision_by_gcd_square = np.atleast_2d(np.floor_divide(square.precision, gcd_square)) + lcm_square = np.atleast_2d(precision_by_gcd_square)[:,0] + for i,val in enumerate(precision_by_gcd_square): + for j in val: + lcm_square[i] = np.lcm(lcm_square[i], j) lcm_square = 2 * lcm_square diff --git a/qiskit_qec/operators/xp_pauli.py b/qiskit_qec/operators/xp_pauli.py index 472f4d70..258b92e9 100644 --- a/qiskit_qec/operators/xp_pauli.py +++ b/qiskit_qec/operators/xp_pauli.py @@ -174,6 +174,15 @@ def compose( multiplication and is equivalent to the :meth:`dot` method ``A.dot(B) == A.compose(B, front=True)``. + Examples: + >>> a = XPPauli(data=np.array([0, 1, 0, 0, 2, 0], dtype=np.int64), phase_exp=6, precision=4) + >>> b = XPPauli(data=np.array([1, 1, 1, 3, 3, 0], dtype=np.int64), phase_exp=2, precision=4) + >>> value = XPPauli.compose(a, b) + >>> value.matrix + array([[1, 0, 1, 3, 3, 0]], dtype=int64) + >>> value._phase_exp + array([6]) + See also: _compose """ @@ -203,6 +212,14 @@ def unique_vector_rep(self) -> "XPPauli": Returns: XPPauli: Unique vector representation of self + Examples: + >>> a = XPPauli(matrix=np.array([0, 3, 1, 6, 4, 3], dtype=np.int64), phase_exp=11, precision=4) + >>> a = a.unique_vector_rep() + >>> a.matrix + np.array([[0, 1, 1, 2, 0, 3]], dtype=int64) + >>> a._phase_exp + array([3], dtype=int32) + See also: _unique_vector_rep """ @@ -228,6 +245,14 @@ def rescale_precision(self, new_precision) -> "XPPauli": Raises: QiskitError: If it is not possible to express XPPauli in new_precision + Examples: + >>> a = XPPauli(data=np.array([1, 1, 1, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0], dtype=np.int64), phase_exp=12, precision=8) + >>> a = a.rescale_precision(new_precision=2) + >>> a.matrix + array([[1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0]], dtype=int64) + >>> a._phase_exp + array([3, dtype=int32]) + See also: _rescale_precision """ @@ -247,6 +272,14 @@ def antisymmetric_op(self) -> "XPPauli": Returns: XPPauli: Antisymmetric operator corresponding to XPPauli, if x is 0 + Examples: + >>> a = XPPauli(data=np.array([0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 3, 3, 3], dtype=np.int64), phase_exp=0, precision=8) + >>> value = a.antisymmetric_op() + >>> value.matrix + array([0, 0, 0, 0, 0, 0, 0, 0, -1, -2, -3, -3, -3, -3], dtype=int64) + >>> value._phase_exp + array([15]) + See also: _antisymmetric_op """ @@ -268,6 +301,14 @@ def power(self, n) -> "XPPauli": Returns: XPPauli: XPPauli raised to the power n + Examples: + >>> a = XPPauli(data=np.array([1, 1, 1, 0, 0, 1, 0, 0, 3, 4, 0, 0, 0, 1], dtype=np.int64), phase_exp=12, precision=8) + >>> value = a.power(n=5) + >>> value.matrix + array([1, 1, 1, 0, 0, 1, 0, 0, 3, 4, 0, 0, 0, 5], dtype=int64) + >>> value._phase_exp + array([8]) + See also: _power """ diff --git a/qiskit_qec/operators/xp_pauli_list.py b/qiskit_qec/operators/xp_pauli_list.py index 763f4a3f..7635a744 100644 --- a/qiskit_qec/operators/xp_pauli_list.py +++ b/qiskit_qec/operators/xp_pauli_list.py @@ -275,6 +275,15 @@ def compose( for the specified qargs, or if precision of other does not match precision of self. + Examples: + >>> a = XPPauliList(data=np.array([[0, 1, 0, 0, 2, 0], [0, 1, 0, 0, 2, 0]], dtype=np.int64), phase_exp=np.array([6, 6]), precision=4) + >>> b = XPPauliList(data=np.array([[1, 1, 1, 3, 3, 0], [1, 1, 1, 3, 3, 0]], dtype=np.int64), phase_exp=np.array([2, 2]), precision=4) + >>> value = XPPauliList.compose(a, b) + >>> value.matrix + array([[1, 0, 1, 3, 3, 0], [1, 0, 1, 3, 3, 0]], dtype=int64) + >>> value._phase_exp + array([6, 6]) + See also: _compose """ diff --git a/tests/operators/test_xp_pauli.py b/tests/operators/test_xp_pauli.py index 7b5994f4..51f606ca 100644 --- a/tests/operators/test_xp_pauli.py +++ b/tests/operators/test_xp_pauli.py @@ -115,7 +115,9 @@ def test_antisymmetric_op(self): target_phase_exp = 15 target_precision = 8 target = XPPauli(data=target_matrix, phase_exp=target_phase_exp, precision=target_precision) - self.assertEqual(target, value) + np.testing.assert_equal(target.matrix, value.matrix) + np.testing.assert_equal(target._phase_exp, value._phase_exp) + np.testing.assert_equal(target.precision, value.precision) def test_power(self): """Test power method.""" @@ -127,10 +129,12 @@ def test_power(self): value = xppauli.power(n=n) target_matrix = np.array([1, 1, 1, 0, 0, 1, 0, 0, 3, 4, 0, 0, 0, 5], dtype=np.int64) - target_phase_exp = 15 + target_phase_exp = 8 target_precision = 8 target = XPPauli(data=target_matrix, phase_exp=target_phase_exp, precision=target_precision) - self.assertEqual(target, value) + np.testing.assert_equal(target.matrix, value.matrix) + np.testing.assert_equal(target._phase_exp, value._phase_exp) + np.testing.assert_equal(target.precision, value.precision) def test_multiplication(self): """Test multiplication method.""" @@ -149,7 +153,9 @@ def test_multiplication(self): target_phase_exp = 6 target_precision = 4 target = XPPauli(data=target_matrix, phase_exp=target_phase_exp, precision=target_precision) - self.assertEqual(target, value) + np.testing.assert_equal(target.matrix, value.matrix) + np.testing.assert_equal(target._phase_exp, value._phase_exp) + np.testing.assert_equal(target.precision, value.precision) def test_degree(self): """Test degree method.""" diff --git a/tests/operators/test_xp_pauli_list.py b/tests/operators/test_xp_pauli_list.py index 9d0b2dd8..fedc70f3 100644 --- a/tests/operators/test_xp_pauli_list.py +++ b/tests/operators/test_xp_pauli_list.py @@ -101,6 +101,26 @@ def test_diagonal(self): target = np.array([True, True, False]) np.testing.assert_equal(target, value) + def test_multiplication(self): + """Test multiplication method.""" + a_matrix = np.array([[0, 1, 0, 0, 2, 0], [0, 1, 0, 0, 2, 0]], dtype=np.int64) + a_phase_exp = np.array([6, 6]) + a_precision = 4 + a = XPPauliList(data=a_matrix, phase_exp=a_phase_exp, precision=a_precision) + b_matrix = np.array([[1, 1, 1, 3, 3, 0], [1, 1, 1, 3, 3, 0]], dtype=np.int64) + b_phase_exp = np.array([2, 2]) + b_precision = 4 + b = XPPauliList(data=b_matrix, phase_exp=b_phase_exp, precision=b_precision) + value = XPPauliList.compose(a, b) + + target_matrix = np.array([[1, 0, 1, 3, 3, 0], [1, 0, 1, 3, 3, 0]], dtype=np.int64) + target_phase_exp = np.array([6., 6.]) + target_precision = 4 + target = XPPauliList(data=target_matrix, phase_exp=target_phase_exp, precision=target_precision) + np.testing.assert_equal(target.matrix, value.matrix) + np.testing.assert_equal(target._phase_exp, value._phase_exp) + np.testing.assert_equal(target.precision, value.precision) + if __name__ == "__main__": unittest.main() From b06e4e67e6feb048840cba3f122ae76078419cc0 Mon Sep 17 00:00:00 2001 From: dhruvbhq Date: Sun, 13 Nov 2022 14:30:56 +0100 Subject: [PATCH 27/30] added example and minor change for random_xppauli --- qiskit_qec/operators/random.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/qiskit_qec/operators/random.py b/qiskit_qec/operators/random.py index ec7dc5c6..3975c8ea 100644 --- a/qiskit_qec/operators/random.py +++ b/qiskit_qec/operators/random.py @@ -219,6 +219,9 @@ def random_xppauli( seed (int or np.random.Generator): Optional. Set a fixed seed or generator for RNG. + Examples: + >>> value = random_xppauli(num_qubits=3, precision=8) + Returns: XPPauli: a random XPPauli """ @@ -235,7 +238,7 @@ def random_xppauli( # Mark's code randomizes phase modulo 2*precision. phase = rng.integers(2 * precision, dtype=np.int64) - xppauli = XPPauli((z, x, phase)) + xppauli = XPPauli(data=np.concatenate((z, x)), phase_exp=phase, precision=precision) return xppauli From 25ac923f8b74bd1b2df9ef6b3680f961e568b8d3 Mon Sep 17 00:00:00 2001 From: dhruvbhq Date: Sun, 13 Nov 2022 14:51:48 +0100 Subject: [PATCH 28/30] added check in rescale_precision --- qiskit_qec/operators/base_xp_pauli.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/qiskit_qec/operators/base_xp_pauli.py b/qiskit_qec/operators/base_xp_pauli.py index ebe51228..e7cd0252 100644 --- a/qiskit_qec/operators/base_xp_pauli.py +++ b/qiskit_qec/operators/base_xp_pauli.py @@ -605,9 +605,8 @@ def _unique_vector_rep(self) -> "BaseXPPauli": return BaseXPPauli(matrix, phase_exp, self.precision) def rescale_precision(self, new_precision: int) -> "BaseXPPauli": - """Rescale the generalized symplectic vector components - of BaseXPPauli operator to the new precision. Returns None if the - rescaling is not possible, else returns the rescaled BaseXPPauli object. + """Rescale the generalized symplectic vector components of BaseXPPauli + operator to the new precision. Note: This method is adapted from method XPSetN from XPFpackage: @@ -636,6 +635,12 @@ def rescale_precision(self, new_precision: int) -> "BaseXPPauli": See also: _rescale_precision """ + unique_xp_op = self.unique_vector_rep() + old_precision = self.precision + if new_precision < old_precision: + scale_factor = old_precision // new_precision + if(((new_precision > old_precision) and (new_precision % old_precision > 0)) or ((new_precision < old_precision) and ((old_precision % new_precision > 0) or (np.sum(np.mod(unique_xp_op._phase_exp, scale_factor)) > 0) or (np.sum(np.mod(unique_xp_op.z, scale_factor)) > 0)))): + raise QiskitError("XP Operator can not be expressed in new_precision.") return self._rescale_precision(new_precision) def _rescale_precision(self, new_precision: int) -> "BaseXPPauli": From fd8977f616ba453c7a4447d78c578c1118bfcaaa Mon Sep 17 00:00:00 2001 From: dhruvbhq Date: Sun, 13 Nov 2022 15:36:50 +0100 Subject: [PATCH 29/30] added remaining tests for XPPauliList for currently coded functions --- qiskit_qec/operators/base_xp_pauli.py | 1 - qiskit_qec/operators/xp_pauli.py | 2 +- qiskit_qec/operators/xp_pauli_list.py | 104 ++++++++++++++++++++++++++ tests/operators/test_xp_pauli_list.py | 45 +++++++++++ 4 files changed, 150 insertions(+), 2 deletions(-) diff --git a/qiskit_qec/operators/base_xp_pauli.py b/qiskit_qec/operators/base_xp_pauli.py index e7cd0252..36943e23 100644 --- a/qiskit_qec/operators/base_xp_pauli.py +++ b/qiskit_qec/operators/base_xp_pauli.py @@ -879,7 +879,6 @@ def _power(self, n: int) -> "BaseXPPauli": # supposed to calculate inverses as well, that functionality needs to # be coded. - # TODO n = np.atleast_1d(n) a = np.mod(n, 2) x = np.multiply(self.x, a) diff --git a/qiskit_qec/operators/xp_pauli.py b/qiskit_qec/operators/xp_pauli.py index 258b92e9..32f37387 100644 --- a/qiskit_qec/operators/xp_pauli.py +++ b/qiskit_qec/operators/xp_pauli.py @@ -285,7 +285,7 @@ def antisymmetric_op(self) -> "XPPauli": """ return XPPauli(super().antisymmetric_op()) - def power(self, n) -> "XPPauli": + def power(self, n: int) -> "XPPauli": """Return the XP operator of specified precision raised to the power n. Note: diff --git a/qiskit_qec/operators/xp_pauli_list.py b/qiskit_qec/operators/xp_pauli_list.py index 7635a744..150b6d51 100644 --- a/qiskit_qec/operators/xp_pauli_list.py +++ b/qiskit_qec/operators/xp_pauli_list.py @@ -301,6 +301,110 @@ def compose( return XPPauliList(super().compose(other, qargs=qargs, front=front, inplace=inplace)) + def rescale_precision(self, new_precision) -> "XPPauliList": + """Rescale the generalized symplectic vector components + of XPPauli operator to the new precision. Returns the rescaled XPPauli object. + + Note: + This method is adapted from method XPSetN from XPFpackage: + https://github.com/m-webster/XPFpackage, originally developed by + Mark Webster. The original code is licensed under the GNU General + Public License v3.0 and Mark Webster has given permission to use + the code under the Apache License v2.0. + + Args: + new_precision: The target precision in which XPPauli is to be expressed + + Returns: + XPPauliList: Resultant of rescaling the precision of XPPauliList + + Raises: + QiskitError: If it is not possible to express XPPauli in new_precision + + Examples: + >>> matrix1 = np.array([1, 1, 1, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0], dtype=np.int64) + >>> phase_exp1 = 12 + >>> matrix2 = np.array([1, 1, 0, 1, 0, 0, 0, 0, 0, 4, 0, 0, 0, 6], dtype=np.int64) + >>> phase_exp2 = 8 + >>> precision = 8 + >>> new_precision = 4 + >>> matrix = np.array([matrix1, matrix2]) + >>> phase_exp = np.array([phase_exp1, phase_exp2]) + >>> xppauli_list = XPPauliList(data=matrix, phase_exp=phase_exp, precision=precision) + >>> rescaled_xppauli_list = xppaulilist.rescale_precision(new_precision=new_precision) + >>> rescaled_xppauli_list.matrix + array([[1, 1, 1, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0], [1, 1, 0, 1, 0, 0, 0, 0, 0, 2, 0, 0, 0, 3]] dtype=np.int64) + >>> rescaled_xppauli_list._phase_exp + array([6, 4]) + + See also: + _rescale_precision + """ + return XPPauliList(super().rescale_precision(new_precision)) + + def antisymmetric_op(self) -> "XPPauliList": + """Return the antisymmetric operator corresponding to the + z component of XP operator, only if x component is 0. + + Note: + This method is adapted from method XPD from XPFpackage: + https://github.com/m-webster/XPFpackage, originally developed by + Mark Webster. The original code is licensed under the GNU General + Public License v3.0 and Mark Webster has given permission to use + the code under the Apache License v2.0. + + Returns: + XPPauliList: Antisymmetric operator corresponding to XPPauliList, if x is 0 + + Examples: + >>> matrix = np.array([[0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 3, 3, 3], [0, 0, 0, 0, 0, 0, 0, 3, 1, 2, 3, 7, 6, 3]], dtype=np.int64) + >>> phase_exp = np.array([0, 0]) + >>> precision = 8 + >>> xppauli_list = XPPauliList(data=matrix, phase_exp=phase_exp, precision=precision) + >>> value = xppauli_list.antisymmetric_op() + >>> value.matrix + array([[0, 0, 0, 0, 0, 0, 0, 0, -1, -2, -3, -3, -3, -3], [0, 0, 0, 0, 0, 0, 0, -3, -1, -2, -3, -7, -6, -3]], dtype=np.int64) + >>> value._phase_exp + array([15, 25]) + + See also: + _antisymmetric_op + """ + return XPPauliList(super().antisymmetric_op()) + + def power(self, n: int) -> "XPPauliList": + """Return the XP operator of specified precision raised to the power n. + + Note: + This method is adapted from method XPPower from XPFpackage: + https://github.com/m-webster/XPFpackage, originally developed by + Mark Webster. The original code is licensed under the GNU General + Public License v3.0 and Mark Webster has given permission to use + the code under the Apache License v2.0. + + Args: + n: The power to which XPPauliList is to be raised + + Returns: + XPPauliList: XPPauliList raised to the power n + + Examples: + >>> matrix = np.array([[1, 1, 1, 0, 0, 1, 0, 0, 3, 4, 0, 0, 0, 1], [1, 1, 1, 0, 0, 1, 0, 0, 3, 4, 0, 0, 0, 1]], dtype=np.int64) + >>> phase_exp = np.array([12, 12]) + >>> precision = 8 + >>> n = 5 + >>> xppauli_list = XPPauliList(data=matrix, phase_exp=phase_exp, precision=precision) + >>> value = xppauli_list.power(n=n) + >>> value.matrix + array([[1, 1, 1, 0, 0, 1, 0, 0, 3, 4, 0, 0, 0, 5], [1, 1, 1, 0, 0, 1, 0, 0, 3, 4, 0, 0, 0, 5]] ,dtype=np.int64) + >>> value._phase_exp + np.array([8, 8]) + + See also: + _power + """ + return XPPauliList(super().power(n)) + # def conjugate(self): # """Return the conjugate of each XPPauli in the list.""" # # TODO diff --git a/tests/operators/test_xp_pauli_list.py b/tests/operators/test_xp_pauli_list.py index fedc70f3..8721594a 100644 --- a/tests/operators/test_xp_pauli_list.py +++ b/tests/operators/test_xp_pauli_list.py @@ -101,6 +101,23 @@ def test_diagonal(self): target = np.array([True, True, False]) np.testing.assert_equal(target, value) + def test_antisymmetric_op(self): + """Test antisymmetric_op method.""" + + matrix = np.array([[0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 3, 3, 3], [0, 0, 0, 0, 0, 0, 0, 3, 1, 2, 3, 7, 6, 3]], dtype=np.int64) + phase_exp = np.array([0, 0]) + precision = 8 + xppauli_list = XPPauliList(data=matrix, phase_exp=phase_exp, precision=precision) + value = xppauli_list.antisymmetric_op() + + target_matrix = np.array([[0, 0, 0, 0, 0, 0, 0, 0, -1, -2, -3, -3, -3, -3], [0, 0, 0, 0, 0, 0, 0, -3, -1, -2, -3, -7, -6, -3]], dtype=np.int64) + target_phase_exp = np.array([15, 25]) + target_precision = 8 + target = XPPauliList(data=target_matrix, phase_exp=target_phase_exp, precision=target_precision) + np.testing.assert_equal(target.matrix, value.matrix) + np.testing.assert_equal(target._phase_exp, value._phase_exp) + np.testing.assert_equal(target.precision, value.precision) + def test_multiplication(self): """Test multiplication method.""" a_matrix = np.array([[0, 1, 0, 0, 2, 0], [0, 1, 0, 0, 2, 0]], dtype=np.int64) @@ -121,6 +138,34 @@ def test_multiplication(self): np.testing.assert_equal(target._phase_exp, value._phase_exp) np.testing.assert_equal(target.precision, value.precision) + def test_degree(self): + """Test degree method.""" + matrix = np.array([[0, 0, 0, 2, 1, 0], [0, 0, 0, 2, 1, 0]], dtype=np.int64) + phase_exp = np.array([2, 2]) + precision = 4 + xppauli_list = XPPauliList(data=matrix, phase_exp=phase_exp, precision=precision) + value = xppauli_list.degree() + + target = np.array([4, 4]) + np.testing.assert_equal(target, value) + + def test_power(self): + """Test power method.""" + matrix = np.array([[1, 1, 1, 0, 0, 1, 0, 0, 3, 4, 0, 0, 0, 1], [1, 1, 1, 0, 0, 1, 0, 0, 3, 4, 0, 0, 0, 1]], dtype=np.int64) + phase_exp = np.array([12, 12]) + precision = 8 + n = 5 + xppauli_list = XPPauliList(data=matrix, phase_exp=phase_exp, precision=precision) + value = xppauli_list.power(n=n) + + target_matrix = np.array([[1, 1, 1, 0, 0, 1, 0, 0, 3, 4, 0, 0, 0, 5], [1, 1, 1, 0, 0, 1, 0, 0, 3, 4, 0, 0, 0, 5]] ,dtype=np.int64) + target_phase_exp = np.array([8, 8]) + target_precision = 8 + target = XPPauliList(data=target_matrix, phase_exp=target_phase_exp, precision=target_precision) + np.testing.assert_equal(target.matrix, value.matrix) + np.testing.assert_equal(target._phase_exp, value._phase_exp) + np.testing.assert_equal(target.precision, value.precision) + if __name__ == "__main__": unittest.main() From 8827962c3b1fdf9809812ce03678615ef991a7e4 Mon Sep 17 00:00:00 2001 From: dhruvbhq Date: Sun, 13 Nov 2022 17:41:20 +0100 Subject: [PATCH 30/30] cleaned black and lint --- qiskit_qec/operators/base_xp_pauli.py | 73 ++++++++++++++++++--------- qiskit_qec/operators/xp_pauli.py | 19 ++++--- qiskit_qec/operators/xp_pauli_list.py | 33 ++++++++---- tests/operators/test_xp_pauli_list.py | 46 ++++++++++++++--- 4 files changed, 123 insertions(+), 48 deletions(-) diff --git a/qiskit_qec/operators/base_xp_pauli.py b/qiskit_qec/operators/base_xp_pauli.py index 36943e23..713a1bfd 100644 --- a/qiskit_qec/operators/base_xp_pauli.py +++ b/qiskit_qec/operators/base_xp_pauli.py @@ -86,7 +86,7 @@ def __init__( precision: Precision of XP operators. Must be an integer greater than or equal to 2. order: Set to 'xz' or 'zx'. Defines which side the x and z parts of the input matrix - Raises: + Raises: QiskitError: matrix and phase_exp sizes are not compatible, or if precision is less than 2. Examples: @@ -98,7 +98,9 @@ def __init__( """ if not (isinstance(precision, int) and (precision > 1)): - raise QiskitError("Precision of XP operators must be an integer greater than or equal to 2.") + raise QiskitError( + "Precision of XP operators must be an integer greater than or equal to 2." + ) if matrix is None or matrix.size == 0: matrix = np.empty(shape=(0, 0), dtype=np.int64) @@ -311,10 +313,12 @@ def compose( Raises: QiskitError: if number of qubits of other does not match qargs, or if precision of other does not match precision of self. - + Examples: - >>> a = BaseXPPauli(matrix=np.array([0, 1, 0, 0, 2, 0], dtype=np.int64), phase_exp=6, precision=4) - >>> b = BaseXPPauli(matrix=np.array([1, 1, 1, 3, 3, 0], dtype=np.int64), phase_exp=2, precision=4) + >>> a = BaseXPPauli(matrix=np.array([0, 1, 0, 0, 2, 0], dtype=np.int64), + ... phase_exp=6, precision=4) + >>> b = BaseXPPauli(matrix=np.array([1, 1, 1, 3, 3, 0], dtype=np.int64), + ... phase_exp=2, precision=4) >>> value = BaseXPPauli.compose(a, b) >>> value.matrix array([[1, 0, 1, 3, 3, 0]], dtype=int64) @@ -341,7 +345,9 @@ def compose( ) if self.precision != other.precision: - raise QiskitError("Precision of the two BaseXPPaulis to be multiplied must be the same.") + raise QiskitError( + "Precision of the two BaseXPPaulis to be multiplied must be the same." + ) return self._compose(self, other, qargs=qargs, front=front, inplace=inplace) @@ -569,7 +575,8 @@ def unique_vector_rep(self) -> "BaseXPPauli": BaseXPPauli: Unique vector representation of self Examples: - >>> a = BaseXPPauli(matrix=np.array([0, 3, 1, 6, 4, 3], dtype=np.int64), phase_exp=11, precision=4) + >>> a = BaseXPPauli(matrix=np.array([0, 3, 1, 6, 4, 3], dtype=np.int64), + ... phase_exp=11, precision=4) >>> a = a.unique_vector_rep() >>> a.matrix np.array([[0, 1, 1, 2, 0, 3]], dtype=int64) @@ -622,10 +629,12 @@ def rescale_precision(self, new_precision: int) -> "BaseXPPauli": BaseXPPauli: Resultant of rescaling the precision of BaseXPPauli Raises: - QiskitError: If it is not possible to express BaseXPPauli in new_precision - + QiskitError: If it is not possible to express BaseXPPauli in new_precision + Examples: - >>> a = BaseXPPauli(matrix=np.array([1, 1, 1, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0], dtype=np.int64), phase_exp=12, precision=8) + >>> a = BaseXPPauli( + ... matrix=np.array([1, 1, 1, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0], dtype=np.int64), + ... phase_exp=12, precision=8) >>> a = a.rescale_precision(new_precision=2) >>> a.matrix array([[1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0]], dtype=int64) @@ -639,8 +648,15 @@ def rescale_precision(self, new_precision: int) -> "BaseXPPauli": old_precision = self.precision if new_precision < old_precision: scale_factor = old_precision // new_precision - if(((new_precision > old_precision) and (new_precision % old_precision > 0)) or ((new_precision < old_precision) and ((old_precision % new_precision > 0) or (np.sum(np.mod(unique_xp_op._phase_exp, scale_factor)) > 0) or (np.sum(np.mod(unique_xp_op.z, scale_factor)) > 0)))): + if (new_precision > old_precision) and (new_precision % old_precision > 0): + raise QiskitError("XP Operator can not be expressed in new_precision.") + if (new_precision < old_precision) and ( + (old_precision % new_precision > 0) + or (np.sum(np.mod(unique_xp_op._phase_exp, scale_factor)) > 0) + or (np.sum(np.mod(unique_xp_op.z, scale_factor)) > 0) + ): raise QiskitError("XP Operator can not be expressed in new_precision.") + return self._rescale_precision(new_precision) def _rescale_precision(self, new_precision: int) -> "BaseXPPauli": @@ -706,9 +722,11 @@ def weight(self) -> Union[int, np.ndarray]: Returns: Union[int, np.ndarray]: Weight of BaseXPPauli - + Examples: - >>> a = BaseXPPauli(matrix=np.array([1, 1, 1, 0, 0, 1, 0, 0, 3, 4, 0, 0, 0, 1], dtype=np.int64), phase_exp = 12, precision = 8) + >>> a = BaseXPPauli( + ... matrix=np.array([1, 1, 1, 0, 0, 1, 0, 0, 3, 4, 0, 0, 0, 1], dtype=np.int64), + ... phase_exp = 12, precision = 8) >>> a.weight() array([5]) @@ -746,11 +764,15 @@ def is_diagonal(self) -> np.ndarray: np.ndarray: True, if BaseXPPauli is diagonal Examples: - >>> a = BaseXPPauli(matrix=np.array([0, 0, 0, 0, 0, 0, 0, 0, 1, 3, 3, 3, 3, 3], dtype=np.int64), phase_exp=0, precision=8) + >>> a = BaseXPPauli( + ... matrix=np.array([0, 0, 0, 0, 0, 0, 0, 0, 1, 3, 3, 3, 3, 3], dtype=np.int64), + ... phase_exp=0, precision=8) >>> a.is_diagonal() array([True]) - >>> b = BaseXPPauli(matrix=np.array([0, 1, 0, 1, 0, 0, 0, 0, 1, 3, 3, 3, 3, 3], dtype=np.int64), phase_exp=12, precision=8) + >>> b = BaseXPPauli( + ... matrix=np.array([0, 1, 0, 1, 0, 0, 0, 0, 1, 3, 3, 3, 3, 3], dtype=np.int64), + ... phase_exp=12, precision=8) >>> b.is_diagonal() array([False]) @@ -789,7 +811,9 @@ def antisymmetric_op(self) -> "BaseXPPauli": BaseXPPauli: Antisymmetric operator corresponding to BaseXPPauli, if x is 0 Examples: - >>> a = BaseXPPauli(matrix=np.array([0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 3, 3, 3], dtype=np.int64), phase_exp=0, precision=8) + >>> a = BaseXPPauli( + ... matrix=np.array([0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 3, 3, 3], dtype=np.int64), + ... phase_exp=0, precision=8) >>> value = a.antisymmetric_op() >>> value.matrix array([0, 0, 0, 0, 0, 0, 0, 0, -1, -2, -3, -3, -3, -3], dtype=int64) @@ -844,7 +868,9 @@ def power(self, n: int) -> "BaseXPPauli": BaseXPPauli: BaseXPPauli raised to the power n Examples: - >>> a = BaseXPPauli(matrix=np.array([1, 1, 1, 0, 0, 1, 0, 0, 3, 4, 0, 0, 0, 1], dtype=np.int64), phase_exp=12, precision=8) + >>> a = BaseXPPauli( + ... matrix=np.array([1, 1, 1, 0, 0, 1, 0, 0, 3, 4, 0, 0, 0, 1], dtype=np.int64), + ... phase_exp=12, precision=8) >>> value = a.power(n=5) >>> value.matrix array([1, 1, 1, 0, 0, 1, 0, 0, 3, 4, 0, 0, 0, 5], dtype=int64) @@ -914,7 +940,8 @@ def degree(self) -> np.ndarray: np.ndarray: Degree of BaseXPPauli Examples: - >>> a = BaseXPPauli(matrix=np.array([0, 0, 0, 2, 1, 0], dtype=np.int64), phase_exp=2, precision=4) + >>> a = BaseXPPauli(matrix=np.array([0, 0, 0, 2, 1, 0], dtype=np.int64), + ... phase_exp=2, precision=4) >>> a.degree() array([4], dtype=int64) @@ -942,18 +969,18 @@ def _degree(self) -> np.ndarray: gcd = np.gcd(self.z, self.precision) precision_by_gcd = np.atleast_2d(np.floor_divide(self.precision, gcd)) - lcm = np.atleast_2d(precision_by_gcd)[:,0] - for i,val in enumerate(precision_by_gcd): + lcm = np.atleast_2d(precision_by_gcd)[:, 0] + for i, val in enumerate(precision_by_gcd): for j in val: lcm[i] = np.lcm(lcm[i], j) square = BaseXPPauli.compose(self, self) - if type(square) != type(self): + if not isinstance(square, type(self)): square = type(self)(square) gcd_square = np.gcd(square.z, square.precision) precision_by_gcd_square = np.atleast_2d(np.floor_divide(square.precision, gcd_square)) - lcm_square = np.atleast_2d(precision_by_gcd_square)[:,0] - for i,val in enumerate(precision_by_gcd_square): + lcm_square = np.atleast_2d(precision_by_gcd_square)[:, 0] + for i, val in enumerate(precision_by_gcd_square): for j in val: lcm_square[i] = np.lcm(lcm_square[i], j) diff --git a/qiskit_qec/operators/xp_pauli.py b/qiskit_qec/operators/xp_pauli.py index 32f37387..a415e4f9 100644 --- a/qiskit_qec/operators/xp_pauli.py +++ b/qiskit_qec/operators/xp_pauli.py @@ -213,7 +213,8 @@ def unique_vector_rep(self) -> "XPPauli": XPPauli: Unique vector representation of self Examples: - >>> a = XPPauli(matrix=np.array([0, 3, 1, 6, 4, 3], dtype=np.int64), phase_exp=11, precision=4) + >>> a = XPPauli(matrix=np.array([0, 3, 1, 6, 4, 3], dtype=np.int64), + ... phase_exp=11, precision=4) >>> a = a.unique_vector_rep() >>> a.matrix np.array([[0, 1, 1, 2, 0, 3]], dtype=int64) @@ -225,7 +226,7 @@ def unique_vector_rep(self) -> "XPPauli": """ return XPPauli(super().unique_vector_rep()) - def rescale_precision(self, new_precision) -> "XPPauli": + def rescale_precision(self, new_precision: int) -> "XPPauli": """Rescale the generalized symplectic vector components of XPPauli operator to the new precision. Returns the rescaled XPPauli object. @@ -243,10 +244,12 @@ def rescale_precision(self, new_precision) -> "XPPauli": XPPauli: Resultant of rescaling the precision of XPPauli Raises: - QiskitError: If it is not possible to express XPPauli in new_precision + QiskitError: If it is not possible to express XPPauli in new_precision Examples: - >>> a = XPPauli(data=np.array([1, 1, 1, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0], dtype=np.int64), phase_exp=12, precision=8) + >>> a = XPPauli( + ... data=np.array([1, 1, 1, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0], dtype=np.int64), + ... phase_exp=12, precision=8) >>> a = a.rescale_precision(new_precision=2) >>> a.matrix array([[1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0]], dtype=int64) @@ -273,7 +276,9 @@ def antisymmetric_op(self) -> "XPPauli": XPPauli: Antisymmetric operator corresponding to XPPauli, if x is 0 Examples: - >>> a = XPPauli(data=np.array([0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 3, 3, 3], dtype=np.int64), phase_exp=0, precision=8) + >>> a = XPPauli( + ... data=np.array([0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 3, 3, 3], dtype=np.int64), + ... phase_exp=0, precision=8) >>> value = a.antisymmetric_op() >>> value.matrix array([0, 0, 0, 0, 0, 0, 0, 0, -1, -2, -3, -3, -3, -3], dtype=int64) @@ -302,7 +307,9 @@ def power(self, n: int) -> "XPPauli": XPPauli: XPPauli raised to the power n Examples: - >>> a = XPPauli(data=np.array([1, 1, 1, 0, 0, 1, 0, 0, 3, 4, 0, 0, 0, 1], dtype=np.int64), phase_exp=12, precision=8) + >>> a = XPPauli( + ... data=np.array([1, 1, 1, 0, 0, 1, 0, 0, 3, 4, 0, 0, 0, 1], dtype=np.int64), + ... phase_exp=12, precision=8) >>> value = a.power(n=5) >>> value.matrix array([1, 1, 1, 0, 0, 1, 0, 0, 3, 4, 0, 0, 0, 5], dtype=int64) diff --git a/qiskit_qec/operators/xp_pauli_list.py b/qiskit_qec/operators/xp_pauli_list.py index 150b6d51..d62b30fa 100644 --- a/qiskit_qec/operators/xp_pauli_list.py +++ b/qiskit_qec/operators/xp_pauli_list.py @@ -272,12 +272,16 @@ def compose( QiskitError: if other cannot be converted to a XPPauliList, does not have either 1 or the same number of XPPaulis as the current list, has the wrong number of qubits - for the specified qargs, or if precision of other + for the specified qargs, or if precision of other does not match precision of self. Examples: - >>> a = XPPauliList(data=np.array([[0, 1, 0, 0, 2, 0], [0, 1, 0, 0, 2, 0]], dtype=np.int64), phase_exp=np.array([6, 6]), precision=4) - >>> b = XPPauliList(data=np.array([[1, 1, 1, 3, 3, 0], [1, 1, 1, 3, 3, 0]], dtype=np.int64), phase_exp=np.array([2, 2]), precision=4) + >>> a = XPPauliList( + ... data=np.array([[0, 1, 0, 0, 2, 0], [0, 1, 0, 0, 2, 0]], dtype=np.int64), + ... phase_exp=np.array([6, 6]), precision=4) + >>> b = XPPauliList( + ... data=np.array([[1, 1, 1, 3, 3, 0], [1, 1, 1, 3, 3, 0]], dtype=np.int64), + ... phase_exp=np.array([2, 2]), precision=4) >>> value = XPPauliList.compose(a, b) >>> value.matrix array([[1, 0, 1, 3, 3, 0], [1, 0, 1, 3, 3, 0]], dtype=int64) @@ -297,11 +301,13 @@ def compose( "have either 1 or the same number of XPPaulis." ) if self.precision != other.precision: - raise QiskitError("Precision of the two XPPauliLists to be multiplied must be the same.") + raise QiskitError( + "Precision of the two XPPauliLists to be multiplied must be the same." + ) return XPPauliList(super().compose(other, qargs=qargs, front=front, inplace=inplace)) - def rescale_precision(self, new_precision) -> "XPPauliList": + def rescale_precision(self, new_precision: int) -> "XPPauliList": """Rescale the generalized symplectic vector components of XPPauli operator to the new precision. Returns the rescaled XPPauli object. @@ -319,7 +325,7 @@ def rescale_precision(self, new_precision) -> "XPPauliList": XPPauliList: Resultant of rescaling the precision of XPPauliList Raises: - QiskitError: If it is not possible to express XPPauli in new_precision + QiskitError: If it is not possible to express XPPauliList in new_precision Examples: >>> matrix1 = np.array([1, 1, 1, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0], dtype=np.int64) @@ -333,7 +339,8 @@ def rescale_precision(self, new_precision) -> "XPPauliList": >>> xppauli_list = XPPauliList(data=matrix, phase_exp=phase_exp, precision=precision) >>> rescaled_xppauli_list = xppaulilist.rescale_precision(new_precision=new_precision) >>> rescaled_xppauli_list.matrix - array([[1, 1, 1, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0], [1, 1, 0, 1, 0, 0, 0, 0, 0, 2, 0, 0, 0, 3]] dtype=np.int64) + array([[1, 1, 1, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0], + [1, 1, 0, 1, 0, 0, 0, 0, 0, 2, 0, 0, 0, 3]] dtype=np.int64) >>> rescaled_xppauli_list._phase_exp array([6, 4]) @@ -357,13 +364,15 @@ def antisymmetric_op(self) -> "XPPauliList": XPPauliList: Antisymmetric operator corresponding to XPPauliList, if x is 0 Examples: - >>> matrix = np.array([[0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 3, 3, 3], [0, 0, 0, 0, 0, 0, 0, 3, 1, 2, 3, 7, 6, 3]], dtype=np.int64) + >>> matrix = np.array([[0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 3, 3, 3], + ... [0, 0, 0, 0, 0, 0, 0, 3, 1, 2, 3, 7, 6, 3]], dtype=np.int64) >>> phase_exp = np.array([0, 0]) >>> precision = 8 >>> xppauli_list = XPPauliList(data=matrix, phase_exp=phase_exp, precision=precision) >>> value = xppauli_list.antisymmetric_op() >>> value.matrix - array([[0, 0, 0, 0, 0, 0, 0, 0, -1, -2, -3, -3, -3, -3], [0, 0, 0, 0, 0, 0, 0, -3, -1, -2, -3, -7, -6, -3]], dtype=np.int64) + array([[0, 0, 0, 0, 0, 0, 0, 0, -1, -2, -3, -3, -3, -3], + [0, 0, 0, 0, 0, 0, 0, -3, -1, -2, -3, -7, -6, -3]], dtype=np.int64) >>> value._phase_exp array([15, 25]) @@ -389,14 +398,16 @@ def power(self, n: int) -> "XPPauliList": XPPauliList: XPPauliList raised to the power n Examples: - >>> matrix = np.array([[1, 1, 1, 0, 0, 1, 0, 0, 3, 4, 0, 0, 0, 1], [1, 1, 1, 0, 0, 1, 0, 0, 3, 4, 0, 0, 0, 1]], dtype=np.int64) + >>> matrix = np.array([[1, 1, 1, 0, 0, 1, 0, 0, 3, 4, 0, 0, 0, 1], + ... [1, 1, 1, 0, 0, 1, 0, 0, 3, 4, 0, 0, 0, 1]], dtype=np.int64) >>> phase_exp = np.array([12, 12]) >>> precision = 8 >>> n = 5 >>> xppauli_list = XPPauliList(data=matrix, phase_exp=phase_exp, precision=precision) >>> value = xppauli_list.power(n=n) >>> value.matrix - array([[1, 1, 1, 0, 0, 1, 0, 0, 3, 4, 0, 0, 0, 5], [1, 1, 1, 0, 0, 1, 0, 0, 3, 4, 0, 0, 0, 5]] ,dtype=np.int64) + array([[1, 1, 1, 0, 0, 1, 0, 0, 3, 4, 0, 0, 0, 5], + [1, 1, 1, 0, 0, 1, 0, 0, 3, 4, 0, 0, 0, 5]] ,dtype=np.int64) >>> value._phase_exp np.array([8, 8]) diff --git a/tests/operators/test_xp_pauli_list.py b/tests/operators/test_xp_pauli_list.py index 8721594a..a752ae05 100644 --- a/tests/operators/test_xp_pauli_list.py +++ b/tests/operators/test_xp_pauli_list.py @@ -104,16 +104,30 @@ def test_diagonal(self): def test_antisymmetric_op(self): """Test antisymmetric_op method.""" - matrix = np.array([[0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 3, 3, 3], [0, 0, 0, 0, 0, 0, 0, 3, 1, 2, 3, 7, 6, 3]], dtype=np.int64) + matrix = np.array( + [ + [0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 3, 3, 3], + [0, 0, 0, 0, 0, 0, 0, 3, 1, 2, 3, 7, 6, 3], + ], + dtype=np.int64, + ) phase_exp = np.array([0, 0]) precision = 8 xppauli_list = XPPauliList(data=matrix, phase_exp=phase_exp, precision=precision) value = xppauli_list.antisymmetric_op() - target_matrix = np.array([[0, 0, 0, 0, 0, 0, 0, 0, -1, -2, -3, -3, -3, -3], [0, 0, 0, 0, 0, 0, 0, -3, -1, -2, -3, -7, -6, -3]], dtype=np.int64) + target_matrix = np.array( + [ + [0, 0, 0, 0, 0, 0, 0, 0, -1, -2, -3, -3, -3, -3], + [0, 0, 0, 0, 0, 0, 0, -3, -1, -2, -3, -7, -6, -3], + ], + dtype=np.int64, + ) target_phase_exp = np.array([15, 25]) target_precision = 8 - target = XPPauliList(data=target_matrix, phase_exp=target_phase_exp, precision=target_precision) + target = XPPauliList( + data=target_matrix, phase_exp=target_phase_exp, precision=target_precision + ) np.testing.assert_equal(target.matrix, value.matrix) np.testing.assert_equal(target._phase_exp, value._phase_exp) np.testing.assert_equal(target.precision, value.precision) @@ -131,9 +145,11 @@ def test_multiplication(self): value = XPPauliList.compose(a, b) target_matrix = np.array([[1, 0, 1, 3, 3, 0], [1, 0, 1, 3, 3, 0]], dtype=np.int64) - target_phase_exp = np.array([6., 6.]) + target_phase_exp = np.array([6, 6]) target_precision = 4 - target = XPPauliList(data=target_matrix, phase_exp=target_phase_exp, precision=target_precision) + target = XPPauliList( + data=target_matrix, phase_exp=target_phase_exp, precision=target_precision + ) np.testing.assert_equal(target.matrix, value.matrix) np.testing.assert_equal(target._phase_exp, value._phase_exp) np.testing.assert_equal(target.precision, value.precision) @@ -151,17 +167,31 @@ def test_degree(self): def test_power(self): """Test power method.""" - matrix = np.array([[1, 1, 1, 0, 0, 1, 0, 0, 3, 4, 0, 0, 0, 1], [1, 1, 1, 0, 0, 1, 0, 0, 3, 4, 0, 0, 0, 1]], dtype=np.int64) + matrix = np.array( + [ + [1, 1, 1, 0, 0, 1, 0, 0, 3, 4, 0, 0, 0, 1], + [1, 1, 1, 0, 0, 1, 0, 0, 3, 4, 0, 0, 0, 1], + ], + dtype=np.int64, + ) phase_exp = np.array([12, 12]) precision = 8 n = 5 xppauli_list = XPPauliList(data=matrix, phase_exp=phase_exp, precision=precision) value = xppauli_list.power(n=n) - target_matrix = np.array([[1, 1, 1, 0, 0, 1, 0, 0, 3, 4, 0, 0, 0, 5], [1, 1, 1, 0, 0, 1, 0, 0, 3, 4, 0, 0, 0, 5]] ,dtype=np.int64) + target_matrix = np.array( + [ + [1, 1, 1, 0, 0, 1, 0, 0, 3, 4, 0, 0, 0, 5], + [1, 1, 1, 0, 0, 1, 0, 0, 3, 4, 0, 0, 0, 5], + ], + dtype=np.int64, + ) target_phase_exp = np.array([8, 8]) target_precision = 8 - target = XPPauliList(data=target_matrix, phase_exp=target_phase_exp, precision=target_precision) + target = XPPauliList( + data=target_matrix, phase_exp=target_phase_exp, precision=target_precision + ) np.testing.assert_equal(target.matrix, value.matrix) np.testing.assert_equal(target._phase_exp, value._phase_exp) np.testing.assert_equal(target.precision, value.precision)