Remove deprecated classes and methods in quantum_info (#8070)

* remove deprecated

* fix test

* fix test

* fix cyclic dependency

* create releasenote

* leave import path

* fix lint

* deprecate argument

* Update qiskit/quantum_info/operators/symplectic/pauli.py

* Remove already-expired deprecation

* Revert unnecessary over-specific import

* Flesh out detail in release note

* Issue deprecation warning for old Pauli path

* Reinstate buggy deprecated behaviour

* Reinstate None default for Pauli data kwarg

Co-authored-by: ikkoham <ikkoham@users.noreply.github.com>
Co-authored-by: Matthew Treinish <mtreinish@kortar.org>
Co-authored-by: Jake Lishman <jake.lishman@ibm.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
This commit is contained in:
Daiki Murata 2022-06-23 20:16:16 +09:00 committed by GitHub
parent 3241978340
commit 8455c9f459
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 106 additions and 1173 deletions

View File

@ -15,7 +15,7 @@
import itertools
import logging
from copy import deepcopy
from typing import List, Optional, Union, cast, Dict
from typing import Dict, List, Optional, Union, cast
import numpy as np
@ -317,7 +317,7 @@ class Z2Symmetries:
and stacked_symmetries[row, col + symm_shape[1] // 2] == 1
):
sq_paulis.append(
Pauli(np.zeros(symm_shape[1] // 2), np.zeros(symm_shape[1] // 2))
Pauli((np.zeros(symm_shape[1] // 2), np.zeros(symm_shape[1] // 2)))
)
sq_paulis[row].z[col] = True
sq_paulis[row].x[col] = False
@ -347,7 +347,7 @@ class Z2Symmetries:
and stacked_symmetries[row, col + symm_shape[1] // 2] == 0
):
sq_paulis.append(
Pauli(np.zeros(symm_shape[1] // 2), np.zeros(symm_shape[1] // 2))
Pauli((np.zeros(symm_shape[1] // 2), np.zeros(symm_shape[1] // 2)))
)
sq_paulis[row].z[col] = True
sq_paulis[row].x[col] = True

View File

@ -32,7 +32,6 @@ Operators
PauliTable
StabilizerTable
pauli_basis
pauli_group
States
======
@ -124,7 +123,7 @@ Synthesis
"""
from .operators import Operator, ScalarOp, Pauli, Clifford, SparsePauliOp
from .operators import PauliList, PauliTable, StabilizerTable, pauli_basis, pauli_group
from .operators import PauliList, PauliTable, StabilizerTable, pauli_basis
from .operators.channel import Choi, SuperOp, Kraus, Stinespring, Chi, PTM
from .operators.measures import process_fidelity, average_gate_fidelity, gate_error, diamond_norm
from .operators.dihedral import CNOTDihedral

View File

@ -12,11 +12,22 @@
"""Quantum Operators."""
from .channel import PTM, Chi, Choi, Kraus, Stinespring, SuperOp
from .dihedral import CNOTDihedral
from .measures import (
average_gate_fidelity,
diamond_norm,
gate_error,
process_fidelity,
)
from .operator import Operator
from .scalar_op import ScalarOp
from .channel import Choi, SuperOp, Kraus, Stinespring, Chi, PTM
from .measures import process_fidelity, average_gate_fidelity, gate_error, diamond_norm
from .symplectic import Clifford, Pauli, PauliList, SparsePauliOp, PauliTable, StabilizerTable
from .symplectic import pauli_basis
from .pauli import pauli_group
from .dihedral import CNOTDihedral
from .symplectic import (
Clifford,
Pauli,
PauliList,
PauliTable,
SparsePauliOp,
StabilizerTable,
pauli_basis,
)

View File

@ -19,9 +19,8 @@ import numpy as np
from qiskit.exceptions import QiskitError
from qiskit.quantum_info.operators.base_operator import BaseOperator
from qiskit.quantum_info.operators.operator import Operator
from qiskit.quantum_info.operators.pauli import Pauli
from qiskit.quantum_info.operators.symplectic.pauli import Pauli
from qiskit.quantum_info.operators.scalar_op import ScalarOp
from qiskit.quantum_info.synthesis.cnotdihedral_decompose import decompose_cnotdihedral
from qiskit.quantum_info.operators.mixins import generate_apidocs, AdjointMixin
from qiskit.circuit import QuantumCircuit, Instruction
from .dihedral_circuits import _append_circuit
@ -314,6 +313,8 @@ class CNOTDihedral(BaseOperator, AdjointMixin):
*Scalable randomised benchmarking of non-Clifford gates*,
npj Quantum Inf 2, 16012 (2016).
"""
from qiskit.quantum_info.synthesis.cnotdihedral_decompose import decompose_cnotdihedral
return decompose_cnotdihedral(self)
def to_instruction(self):

View File

@ -10,74 +10,26 @@
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.
# pylint: disable=unused-import
"""
DEPRECATED Tools for working with Pauli Operators.
"""
from warnings import warn
import numpy as np
from qiskit.exceptions import QiskitError
from qiskit.quantum_info.operators.symplectic.pauli import Pauli
import warnings
def pauli_group(number_of_qubits, case="weight"):
"""DEPRECATED: Return the Pauli group with 4^n elements.
def __getattr__(name):
if name == "Pauli":
from qiskit.quantum_info import Pauli
This function is deprecated. Use :func:`~qiskit.quantum_info.pauli_basis`
for equivalent functionality.
warnings.warn(
f"Importing from '{__name__}' is deprecated since Qiskit Terra 0.21 and the module"
" will be removed in a future release. Import directly from 'qiskit.quantum_info'.",
category=DeprecationWarning,
stacklevel=2,
)
The phases have been removed.
case 'weight' is ordered by Pauli weights and
case 'tensor' is ordered by I,X,Y,Z counting lowest qubit fastest.
Args:
number_of_qubits (int): number of qubits
case (str): determines ordering of group elements ('weight' or 'tensor')
Returns:
list: list of Pauli objects
Raises:
QiskitError: case is not 'weight' or 'tensor'
QiskitError: number_of_qubits is larger than 4
"""
warn(
"`insert_paulis` is deprecated and will be removed no earlier than "
"3 months after the release date. For equivalent functionality to"
"`qiskit.quantum_info.pauli_group` instead. "
"`pauli_group(n)` is equivalent to `pauli_basis(n, weight=True)`, "
'`pauli_group(n, case="tensor") is equivalent to `pauli_basis(n)`',
DeprecationWarning,
stacklevel=2,
)
if number_of_qubits < 5:
temp_set = []
if case == "weight":
tmp = pauli_group(number_of_qubits, case="tensor")
# sort on the weight of the Pauli operator
return sorted(tmp, key=lambda x: -np.count_nonzero(np.array(x.to_label(), "c") == b"I"))
elif case == "tensor":
# the Pauli set is in tensor order II IX IY IZ XI ...
for k in range(4**number_of_qubits):
z = np.zeros(number_of_qubits, dtype=bool)
x = np.zeros(number_of_qubits, dtype=bool)
# looping over all the qubits
for j in range(number_of_qubits):
# making the Pauli for each j fill it in from the
# end first
element = (k // (4**j)) % 4
if element == 1:
x[j] = True
elif element == 2:
z[j] = True
x[j] = True
elif element == 3:
z[j] = True
temp_set.append(Pauli(z, x))
return temp_set
else:
raise QiskitError(f"Only support 'weight' or 'tensor' cases but you have {case}.")
raise QiskitError("Only support number of qubits is less than 5")
return Pauli
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")

View File

@ -21,7 +21,6 @@ from qiskit.circuit.library.standard_gates import IGate, XGate, YGate, ZGate, HG
from qiskit.quantum_info.operators.base_operator import BaseOperator
from qiskit.quantum_info.operators.operator import Operator
from qiskit.quantum_info.operators.scalar_op import ScalarOp
from qiskit.quantum_info.synthesis.clifford_decompose import decompose_clifford
from qiskit.quantum_info.operators.mixins import generate_apidocs, AdjointMixin
from qiskit.quantum_info.operators.symplectic.base_pauli import _count_y
from .stabilizer_table import StabilizerTable
@ -384,6 +383,8 @@ class Clifford(BaseOperator, AdjointMixin):
Phys. Rev. A 70, 052328 (2004).
`arXiv:quant-ph/0406196 <https://arxiv.org/abs/quant-ph/0406196>`_
"""
from qiskit.quantum_info.synthesis.clifford_decompose import decompose_clifford
return decompose_clifford(self)
def to_instruction(self):

View File

@ -13,9 +13,9 @@
N-qubit Pauli Operator Class
"""
# pylint: disable=invalid-name
# pylint: disable=bad-docstring-quotes # for deprecate_function decorator
import re
import warnings
from typing import Dict
import numpy as np
@ -29,7 +29,6 @@ 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.quantum_info.operators.symplectic.base_pauli import BasePauli, _count_y
from qiskit.utils.deprecation import deprecate_function
class Pauli(BasePauli):
@ -167,9 +166,6 @@ class Pauli(BasePauli):
characters from ['+', '-', ''], ['1', ''], and ['i', 'j', ''] in this order,
e.g. '', '-1j' while a Pauli string is 1 or more characters of 'I', 'X', 'Y' or 'Z',
e.g. 'Z', 'XIYY'.
x (np.ndarray): DEPRECATED, symplectic x vector.
z (np.ndarray): DEPRECATED, symplectic z vector.
label (str): DEPRECATED, string label.
Raises:
QiskitError: if input array is invalid shape.
@ -188,13 +184,27 @@ class Pauli(BasePauli):
base_z, base_x, base_phase = self._from_scalar_op(data)
elif isinstance(data, (QuantumCircuit, Instruction)):
base_z, base_x, base_phase = self._from_circuit(data)
elif x is not None: # DEPRECATED
elif x is not None:
if z is None:
# Using old Pauli initialization with positional args instead of kwargs
z = data
base_z, base_x, base_phase = self._from_array_deprecated(z, x)
elif label is not None: # DEPRECATED
base_z, base_x, base_phase = self._from_label_deprecated(label)
warnings.warn(
"Passing 'z' and 'x' arrays separately to 'Pauli' is deprecated as of"
" Qiskit Terra 0.17 and will be removed in version 0.23 or later."
" Use a tuple instead, such as 'Pauli((z, x[, phase]))'.",
DeprecationWarning,
stacklevel=2,
)
base_z, base_x, base_phase = self._from_array(z, x)
elif label is not None:
warnings.warn(
"The 'label' keyword argument of 'Pauli' is deprecated as of"
" Qiskit Terra 0.17 and will be removed in version 0.23 or later."
" Pass the label positionally instead, such as 'Pauli(\"XYZ\")'.",
DeprecationWarning,
stacklevel=2,
)
base_z, base_x, base_phase = self._from_label(label)
else:
raise QiskitError("Invalid input data for Pauli.")
@ -687,353 +697,6 @@ class Pauli(BasePauli):
ret = ret.compose(next_instr, qargs=qargs)
return ret._z, ret._x, ret._phase
# ---------------------------------------------------------------------
# DEPRECATED methods from old Pauli class
# ---------------------------------------------------------------------
@classmethod
@deprecate_function(
"Initializing Pauli from `Pauli(label=l)` kwarg is deprecated as of "
"version 0.17.0 and will be removed no earlier than 3 months after "
"the release date. Use `Pauli(l)` instead."
)
def _from_label_deprecated(cls, label):
# Deprecated wrapper of `_from_label` so that a deprecation warning
# can be displaced during initialization with deprecated kwarg
return cls._from_label(label)
@classmethod
@deprecate_function(
"Initializing Pauli from `Pauli(z=z, x=x)` kwargs is deprecated as of "
"version 0.17.0 and will be removed no earlier than 3 months after "
"the release date. Use tuple initialization `Pauli((z, x))` instead."
)
def _from_array_deprecated(cls, z, x):
# Deprecated wrapper of `_from_array` so that a deprecation warning
# can be displaced during initialization with deprecated kwarg
return cls._from_array(z, x)
@staticmethod
def _make_np_bool(arr):
if not isinstance(arr, (list, np.ndarray, tuple)):
arr = [arr]
arr = np.asarray(arr).astype(bool)
return arr
@staticmethod
@deprecate_function(
"`from_label` is deprecated and will be removed no earlier than "
"3 months after the release date. Use Pauli(label) instead."
)
def from_label(label):
"""DEPRECATED: Construct a Pauli from a string label.
This function is deprecated use ``Pauli(label)`` instead.
Args:
label (str): Pauli string label.
Returns:
Pauli: the constructed Pauli.
Raises:
QiskitError: If the input list is empty or contains invalid
Pauli strings.
"""
if isinstance(label, tuple):
# Legacy usage from aqua
label = "".join(label)
return Pauli(label)
@staticmethod
@deprecate_function(
"sgn_prod is deprecated and will be removed no earlier than "
"3 months after the release date. Use `dot` instead."
)
def sgn_prod(p1, p2):
r"""
DEPRECATED: Multiply two Paulis and track the phase.
This function is deprecated. The Pauli class now handles full
Pauli group multiplication using :meth:`compose` or :meth:`dot`.
$P_3 = P_1 \otimes P_2$: X*Y
Args:
p1 (Pauli): pauli 1
p2 (Pauli): pauli 2
Returns:
Pauli: the multiplied pauli (without phase)
complex: the sign of the multiplication, 1, -1, 1j or -1j
"""
pauli = p1.dot(p2)
return pauli[:], (-1j) ** pauli.phase
@deprecate_function(
"`to_spmatrix` is deprecated and will be removed no earlier than "
"3 months after the release date. Use `to_matrix(sparse=True)` instead."
)
def to_spmatrix(self):
r"""
DEPRECATED Convert Pauli to a sparse matrix representation (CSR format).
This function is deprecated. Use :meth:`to_matrix` with kwarg
``sparse=True`` instead.
Returns:
scipy.sparse.csr_matrix: a sparse matrix with CSR format that
represents the pauli.
"""
return self.to_matrix(sparse=True)
@deprecate_function(
"`kron` is deprecated and will be removed no earlier than "
"3 months after the release date of Qiskit Terra 0.17.0. "
"Use `expand` instead, but note this does not change "
"the operator in-place."
)
def kron(self, other):
r"""DEPRECATED: Kronecker product of two paulis.
This function is deprecated. Use :meth:`expand` instead.
Order is $P_2 (other) \otimes P_1 (self)$
Args:
other (Pauli): P2
Returns:
Pauli: self
"""
pauli = self.expand(other)
self._z = pauli._z
self._x = pauli._x
self._phase = pauli._phase
self._op_shape = self._op_shape.expand(other._op_shape)
return self
@deprecate_function(
"`update_z` is deprecated and will be removed no earlier than "
"3 months after the release date. Use `Pauli.z = val` or "
"`Pauli.z[indices] = val` instead."
)
def update_z(self, z, indices=None):
"""
DEPRECATED: Update partial or entire z.
This function is deprecated. Use the setter for :attr:`Z` instead.
Args:
z (numpy.ndarray or list): to-be-updated z
indices (numpy.ndarray or list or optional): to-be-updated qubit indices
Returns:
Pauli: self
Raises:
QiskitError: when updating whole z, the number of qubits must be the same.
"""
phase = self.phase
z = self._make_np_bool(z)
if indices is None:
if len(self.z) != len(z):
raise QiskitError(
"During updating whole z, you can not change the number of qubits."
)
self.z = z
else:
if not isinstance(indices, list) and not isinstance(indices, np.ndarray):
indices = [indices]
for p, idx in enumerate(indices):
self.z[idx] = z[p]
self.phase = phase
return self
@deprecate_function(
"`update_z` is deprecated and will be removed no earlier than "
"3 months after the release date. Use `Pauli.x = val` or "
"`Pauli.x[indices] = val` instead."
)
def update_x(self, x, indices=None):
"""
DEPRECATED: Update partial or entire x.
This function is deprecated. Use the setter for :attr:`X` instead.
Args:
x (numpy.ndarray or list): to-be-updated x
indices (numpy.ndarray or list or optional): to-be-updated qubit indices
Returns:
Pauli: self
Raises:
QiskitError: when updating whole x, the number of qubits must be the same.
"""
phase = self.phase
x = self._make_np_bool(x)
if indices is None:
if len(self.x) != len(x):
raise QiskitError(
"During updating whole x, you can not change the number of qubits."
)
self.x = x
else:
if not isinstance(indices, list) and not isinstance(indices, np.ndarray):
indices = [indices]
for p, idx in enumerate(indices):
self.x[idx] = x[p]
self.phase = phase
return self
@deprecate_function(
"`insert_paulis` is deprecated and will be removed no earlier than "
"3 months after the release date. For similar functionality use "
"`Pauli.insert` instead."
)
def insert_paulis(self, indices=None, paulis=None, pauli_labels=None):
"""
DEPRECATED: Insert or append pauli to the targeted indices.
This function is deprecated. Similar functionality can be obtained
using the :meth:`insert` method.
If indices is None, it means append at the end.
Args:
indices (list[int]): the qubit indices to be inserted
paulis (Pauli): the to-be-inserted or appended pauli
pauli_labels (list[str]): the to-be-inserted or appended pauli label
Note:
the indices refers to the location of original paulis,
e.g. if indices = [0, 2], pauli_labels = ['Z', 'I'] and original pauli = 'ZYXI'
the pauli will be updated to ZY'I'XI'Z'
'Z' and 'I' are inserted before the qubit at 0 and 2.
Returns:
Pauli: self
Raises:
QiskitError: provide both `paulis` and `pauli_labels` at the same time
"""
if pauli_labels is not None:
if paulis is not None:
raise QiskitError("Please only provide either `paulis` or `pauli_labels`")
if isinstance(pauli_labels, str):
pauli_labels = list(pauli_labels)
# since pauli label is in reversed order.
label = "".join(pauli_labels[::-1])
paulis = self.from_label(label)
# Insert and update self
if indices is None: # append
z = np.concatenate((self.z, paulis.z))
x = np.concatenate((self.x, paulis.x))
else:
if not isinstance(indices, list):
indices = [indices]
z = np.insert(self.z, indices, paulis.z)
x = np.insert(self.x, indices, paulis.x)
pauli = Pauli((z, x, self.phase + paulis.phase))
self._z = pauli._z
self._x = pauli._x
self._phase = pauli._phase
self._op_shape = pauli._op_shape
return self
@deprecate_function(
"`append_paulis` is deprecated and will be removed no earlier than "
"3 months after the release date. Use `Pauli.expand` instead."
)
def append_paulis(self, paulis=None, pauli_labels=None):
"""
DEPRECATED: Append pauli at the end.
Args:
paulis (Pauli): the to-be-inserted or appended pauli
pauli_labels (list[str]): the to-be-inserted or appended pauli label
Returns:
Pauli: self
"""
return self.insert_paulis(None, paulis=paulis, pauli_labels=pauli_labels)
@deprecate_function(
"`append_paulis` is deprecated and will be removed no earlier than "
"3 months after the release date. For equivalent functionality "
"use `Pauli.delete` instead."
)
def delete_qubits(self, indices):
"""
DEPRECATED: Delete pauli at the indices.
This function is deprecated. Equivalent functionality can be obtained
using the :meth:`delete` method.
Args:
indices(list[int]): the indices of to-be-deleted paulis
Returns:
Pauli: self
"""
pauli = self.delete(indices)
self._z = pauli._z
self._x = pauli._x
self._phase = pauli._phase
self._op_shape = pauli._op_shape
return self
@classmethod
@deprecate_function(
"`pauli_single` is deprecated and will be removed no earlier than "
"3 months after the release date."
)
def pauli_single(cls, num_qubits, index, pauli_label):
"""
DEPRECATED: Generate single qubit pauli at index with pauli_label with length num_qubits.
Args:
num_qubits (int): the length of pauli
index (int): the qubit index to insert the single qubit
pauli_label (str): pauli
Returns:
Pauli: single qubit pauli
"""
tmp = Pauli(pauli_label)
ret = Pauli((np.zeros(num_qubits, dtype=bool), np.zeros(num_qubits, dtype=bool)))
ret.x[index] = tmp.x[0]
ret.z[index] = tmp.z[0]
ret.phase = tmp.phase
return ret
@classmethod
@deprecate_function(
"`random` is deprecated and will be removed no earlier than "
"3 months after the release date. "
"Use `qiskit.quantum_info.random_pauli` instead"
)
def random(cls, num_qubits, seed=None):
"""DEPRECATED: Return a random Pauli on number of qubits.
This function is deprecated use
:func:`~qiskit.quantum_info.random_pauli` instead.
Args:
num_qubits (int): the number of qubits
seed (int): Optional. To set a random seed.
Returns:
Pauli: the random pauli
"""
# pylint: disable=cyclic-import
from qiskit.quantum_info.operators.symplectic.random import (
random_pauli,
)
return random_pauli(num_qubits, group_phase=False, seed=seed)
# ---------------------------------------------------------------------
# Label parsing helper functions

View File

@ -0,0 +1,47 @@
---
upgrade:
- |
The deprecated function ``qiskit.quantum_info.pauli_group()`` has been removed.
It was originally deprecated in Qiskit Terra 0.17.
- |
Several deprecated methods on :class:`.Pauli` have been removed, which were
originally deprecated in Qiskit Terra 0.17. These were:
``sgn_prod``
Use :meth:`.Pauli.compose` or :meth:`.Pauli.dot` instead.
``to_spmatrix``
Use :meth:`.Pauli.to_matrix` with argument ``sparse=True`` instead.
``kron``
Use :meth:`.Pauli.expand`, but beware that this returns a new object, rather
than mutating the existing one.
``update_z`` and ``update_x``
Set the ``z`` and ``x`` attributes of the object directly.
``insert_paulis``
Use :meth:`.Pauli.insert`.
``append_paulis``
Use :meth:`.Pauli.expand`.
``delete_qubits``
Use :meth:`.Pauli.delete`.
``pauli_single``
Construct the label manually and pass directly to the initializer, such as::
Pauli("I" * index + pauli_label + "I" * (num_qubits - index - len(pauli_label)))
``random``
Use :func:`.quantum_info.random_pauli` instead.
deprecations:
- |
The arguments ``x``, ``z`` and ``label`` to the initializer of
:class:`.Pauli` were documented as deprecated in Qiskit Terra 0.17, but a bug
prevented the expected warning from being shown at runtime. The warning will
now correctly show, and the arguments will be removed in Qiskit Terra 0.23 or
later. A pair of ``x`` and ``z`` should be passed positionally as a single
tuple (``Pauli((z, x))``). A string ``label`` should be passed positionally
in the first argument (``Pauli("XYZ")``).

View File

@ -1,741 +0,0 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2017.
#
# 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.
"""Quick program to test the qi tools modules."""
import unittest
from copy import deepcopy
import numpy as np
from qiskit.quantum_info.operators import Pauli
from qiskit.quantum_info import pauli_group
from qiskit.test import QiskitTestCase
class TestPauliAPI(QiskitTestCase):
"""Tests for Pauli class API."""
def check(self, result):
"""checks for result to be a Pauli 'IY'"""
self.assertIsInstance(result, Pauli)
self.assertEqual(result.num_qubits, 2)
self.assertEqual(result.to_label(), "IY")
def test_ndarray_bool(self):
"""Test creation from bool."""
x = np.asarray([1, 0]).astype(bool)
z = np.asarray([1, 0]).astype(bool)
pauli = Pauli(x=x, z=z)
self.check(pauli)
def test_ndarray_int(self):
"""Test creation from int."""
x = np.asarray([2, 0]).astype(int)
z = np.asarray([2, 0]).astype(int)
pauli = Pauli(x=x, z=z)
self.check(pauli)
def test_list(self):
"""Test creation from lists."""
pauli = Pauli(x=[1, 0], z=[1, 0])
self.check(pauli)
def test_tuple(self):
"""Test creation from tuples."""
pauli = Pauli(x=(1, 0), z=(1, 0))
self.check(pauli)
def test_mix(self):
"""Test creation from tuples and list."""
pauli = Pauli(x=(1, 0), z=[1, 0])
self.check(pauli)
class TestPauli(QiskitTestCase):
"""Tests for Pauli class."""
def setUp(self):
"""Setup."""
super().setUp()
z = np.asarray([1, 0, 1, 0]).astype(bool)
x = np.asarray([1, 1, 0, 0]).astype(bool)
self.ref_p = Pauli(z, x)
self.ref_label = "IZXY"
self.ref_matrix = np.array(
[
[
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 - 1.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
],
[
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 1.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
],
[
0.0 + 0.0j,
0.0 - 1.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
],
[
0.0 + 1.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
],
[
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 1.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
],
[
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 - 1.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
],
[
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 1.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
],
[
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 - 1.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
],
[
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 - 1.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
],
[
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 1.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
],
[
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 - 1.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
],
[
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 1.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
],
[
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 1.0j,
],
[
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 - 1.0j,
0.0 + 0.0j,
],
[
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 1.0j,
0.0 + 0.0j,
0.0 + 0.0j,
],
[
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 - 1.0j,
0.0 + 0.0j,
0.0 + 0.0j,
0.0 + 0.0j,
],
]
)
def test_create_from_label(self):
"""Test creation from pauli label."""
label = "IZXY"
pauli = Pauli(label=label)
self.assertEqual(pauli, self.ref_p)
self.assertEqual(pauli.to_label(), self.ref_label)
self.assertEqual(len(pauli), 4)
def test_create_from_z_x(self):
"""Test creation for boolean vector."""
self.assertEqual(self.ref_p.to_label(), "IZXY")
self.assertEqual(len(self.ref_p), 4)
def test_random_pauli(self):
"""Test random pauli creation."""
length = 4
q = Pauli.random(length, seed=42)
self.log.info(q)
self.assertEqual(q.num_qubits, length)
self.assertEqual(len(q.z), length)
self.assertEqual(len(q.x), length)
self.assertEqual(len(q.to_label()), length)
self.assertEqual(len(q.to_matrix()), 2**length)
def test_mul(self):
"""Test multiplication."""
p1 = self.ref_p
p2 = Pauli.from_label("ZXXI")
p3 = p1.dot(p2)
self.assertEqual(len(p3), 4)
self.assertEqual(p3[:].to_label(), "ZYIY")
def test_imul(self):
"""Test in-place multiplication."""
p1 = self.ref_p
p2 = Pauli.from_label("ZXXI")
p3 = deepcopy(p2)
p2 *= p1
self.assertTrue(p2 != p3)
self.assertEqual(p2[:].to_label(), "ZYIY")
def test_equality_equal(self):
"""Test equality operator: equal Paulis."""
p1 = self.ref_p
p2 = deepcopy(p1)
self.assertTrue(p1 == p2)
self.assertEqual(p1.to_label(), self.ref_label)
self.assertEqual(p2.to_label(), self.ref_label)
def test_equality_different(self):
"""Test equality operator: different Paulis."""
p1 = self.ref_p
p2 = deepcopy(p1)
p2.update_z(True, 1)
self.assertFalse(p1 == p2)
self.assertEqual(p1.to_label(), self.ref_label)
self.assertEqual(p2.to_label(), "IZYY")
def test_inequality_equal(self):
"""Test inequality operator: equal Paulis."""
p1 = self.ref_p
p2 = deepcopy(p1)
self.assertFalse(p1 != p2)
def test_inequality_different(self):
"""Test inequality operator: different Paulis."""
p1 = self.ref_p
p2 = deepcopy(p1)
p2.update_x(False, 1)
self.assertTrue(p1 != p2)
self.assertEqual(p2.to_label(), "IZIY")
def test_update_z(self):
"""Test update_z method."""
updated_z = np.asarray([0, 0, 0, 0]).astype(bool)
self.ref_p.update_z(updated_z)
np.testing.assert_equal(self.ref_p.z, np.asarray([False, False, False, False]))
self.assertEqual(self.ref_p.to_label(), "IIXX")
def test_update_z_2(self):
"""Test update_z method, update partial z."""
updated_z = np.asarray([0, 1]).astype(bool)
self.ref_p.update_z(updated_z, [0, 1])
np.testing.assert_equal(self.ref_p.z, np.asarray([False, True, True, False]))
self.assertEqual(self.ref_p.to_label(), "IZYX")
def test_update_x(self):
"""Test update_x method."""
updated_x = np.asarray([0, 1, 0, 1]).astype(bool)
self.ref_p.update_x(updated_x)
np.testing.assert_equal(self.ref_p.x, np.asarray([False, True, False, True]))
self.assertEqual(self.ref_p.to_label(), "XZXZ")
def test_update_x_2(self):
"""Test update_x method, update partial x."""
updated_x = np.asarray([0, 1]).astype(bool)
self.ref_p.update_x(updated_x, [1, 2])
np.testing.assert_equal(self.ref_p.x, np.asarray([True, False, True, False]))
self.assertEqual(self.ref_p.to_label(), "IYIY")
def test_to_matrix(self):
"""Test pauli to matrix."""
np.testing.assert_allclose(self.ref_p.to_matrix(), self.ref_matrix)
def test_delete_qubit(self):
"""Test deleting single qubit."""
p1 = self.ref_p
p2 = deepcopy(p1)
p2.delete_qubits(0)
self.assertTrue(p1 != p2)
self.assertEqual(len(p2), 3)
self.assertEqual(p2.to_label(), "IZX")
def test_delete_qubits(self):
"""Test deleting multiple qubits."""
p1 = self.ref_p
p2 = deepcopy(p1)
p2.delete_qubits([0, 2])
self.assertTrue(p1 != p2)
self.assertEqual(len(p2), 2)
self.assertEqual(p2.to_label(), "IX")
def test_append_pauli_labels(self):
"""Test appending paulis via labels."""
p1 = self.ref_p
p2 = deepcopy(p1)
p2.append_paulis(pauli_labels=["Z", "Y", "I"])
self.assertTrue(p1 != p2)
self.assertEqual(len(p2), 7)
self.assertEqual(p2.to_label(), "IYZ" + self.ref_label)
def test_append_paulis(self):
"""Test appending paulis via pauli object."""
p1 = self.ref_p
p2 = deepcopy(p1)
p2.append_paulis(paulis=p1)
self.assertTrue(p1 != p2)
self.assertEqual(len(p2), 8)
self.assertEqual(p2.to_label(), self.ref_label + self.ref_label)
def test_insert_pauli_labels_1(self):
"""Test inserting paulis via labels."""
p2 = deepcopy(self.ref_p)
p2.insert_paulis(indices=[1, 2], pauli_labels=["Y", "I"])
self.assertTrue(self.ref_p != p2)
self.assertEqual(len(p2), 6)
self.assertEqual(p2.to_label(), "IZIXYY")
def test_insert_pauli_labels_2(self):
"""Test inserting paulis via labels."""
p2 = deepcopy(self.ref_p)
p2.insert_paulis(indices=[3, 2], pauli_labels=["Y", "I"])
self.assertTrue(self.ref_p != p2)
self.assertEqual(len(p2), 6)
self.assertEqual(p2.to_label(), "IYZIXY")
def test_insert_paulis(self):
"""Test inserting paulis via pauli object."""
p1 = deepcopy(self.ref_p)
new_p = Pauli.from_label("XY")
p1.insert_paulis(indices=[0], paulis=new_p)
self.assertTrue(p1 != self.ref_p)
self.assertEqual(len(p1), 6)
self.assertEqual(p1.to_label(), self.ref_label + "XY")
def test_kron(self):
"""Test kron production."""
p1 = deepcopy(self.ref_p)
p2 = deepcopy(self.ref_p)
p2.kron(p1)
self.assertTrue(p1 != p2)
self.assertEqual(len(p2), 8)
self.assertEqual(p2.to_label(), self.ref_label + self.ref_label)
def test_pauli_single(self):
"""Test pauli single."""
num_qubits = 5
pz = Pauli.pauli_single(num_qubits, 2, "Z")
self.assertTrue(pz.to_label(), "IIIZI")
py = Pauli.pauli_single(num_qubits, 4, "Y")
self.assertTrue(py.to_label(), "IYIII")
px = Pauli.pauli_single(num_qubits, 3, "X")
self.assertTrue(px.to_label(), "IIXII")
def test_pauli_group(self):
"""Test pauli group."""
self.log.info("Group in tensor order:")
expected = [
"III",
"XII",
"YII",
"ZII",
"IXI",
"XXI",
"YXI",
"ZXI",
"IYI",
"XYI",
"YYI",
"ZYI",
"IZI",
"XZI",
"YZI",
"ZZI",
"IIX",
"XIX",
"YIX",
"ZIX",
"IXX",
"XXX",
"YXX",
"ZXX",
"IYX",
"XYX",
"YYX",
"ZYX",
"IZX",
"XZX",
"YZX",
"ZZX",
"IIY",
"XIY",
"YIY",
"ZIY",
"IXY",
"XXY",
"YXY",
"ZXY",
"IYY",
"XYY",
"YYY",
"ZYY",
"IZY",
"XZY",
"YZY",
"ZZY",
"IIZ",
"XIZ",
"YIZ",
"ZIZ",
"IXZ",
"XXZ",
"YXZ",
"ZXZ",
"IYZ",
"XYZ",
"YYZ",
"ZYZ",
"IZZ",
"XZZ",
"YZZ",
"ZZZ",
]
grp = pauli_group(3, case="tensor")
for j in grp:
self.log.info("==== j (tensor order) ====")
self.log.info(j.to_label())
self.assertEqual(expected.pop(0)[::-1], j.to_label())
self.log.info("Group in weight order:")
expected = [
"III",
"XII",
"YII",
"ZII",
"IXI",
"IYI",
"IZI",
"IIX",
"IIY",
"IIZ",
"XXI",
"YXI",
"ZXI",
"XYI",
"YYI",
"ZYI",
"XZI",
"YZI",
"ZZI",
"XIX",
"YIX",
"ZIX",
"IXX",
"IYX",
"IZX",
"XIY",
"YIY",
"ZIY",
"IXY",
"IYY",
"IZY",
"XIZ",
"YIZ",
"ZIZ",
"IXZ",
"IYZ",
"IZZ",
"XXX",
"YXX",
"ZXX",
"XYX",
"YYX",
"ZYX",
"XZX",
"YZX",
"ZZX",
"XXY",
"YXY",
"ZXY",
"XYY",
"YYY",
"ZYY",
"XZY",
"YZY",
"ZZY",
"XXZ",
"YXZ",
"ZXZ",
"XYZ",
"YYZ",
"ZYZ",
"XZZ",
"YZZ",
"ZZZ",
]
grp = pauli_group(3, case="weight")
for j in grp:
self.log.info("==== j (weight order) ====")
self.log.info(j.to_label())
self.assertEqual(expected.pop(0)[::-1], j.to_label())
def test_sgn_prod(self):
"""Test sgn prod."""
p1 = Pauli(np.array([False]), np.array([True]))
p2 = Pauli(np.array([True]), np.array([True]))
self.log.info("sign product:")
p3, sgn = Pauli.sgn_prod(p1, p2)
self.log.info("p1: %s", p1.to_label())
self.log.info("p2: %s", p2.to_label())
self.log.info("p3: %s", p3.to_label())
self.log.info("sgn_prod(p1, p2): %s", str(sgn))
self.assertEqual(p1.to_label(), "X")
self.assertEqual(p2.to_label(), "Y")
self.assertEqual(p3.to_label(), "Z")
self.assertEqual(sgn, 1j)
self.log.info("sign product reverse:")
p3, sgn = Pauli.sgn_prod(p2, p1) # pylint: disable=arguments-out-of-order
self.log.info("p2: %s", p2.to_label())
self.log.info("p1: %s", p1.to_label())
self.log.info("p3: %s", p3.to_label())
self.log.info("sgn_prod(p2, p1): %s", str(sgn))
self.assertEqual(p1.to_label(), "X")
self.assertEqual(p2.to_label(), "Y")
self.assertEqual(p3.to_label(), "Z")
self.assertEqual(sgn, -1j)
if __name__ == "__main__":
unittest.main()