Source code for qiskit_qec.operators.pauli

# 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 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.utils.deprecation import deprecate_function

from qiskit_qec.operators.base_pauli import BasePauli
from qiskit_qec.utils import pauli_rep


[docs] class Pauli(BasePauli): r"""N-qubit Pauli operator""" # Set the max Pauli string size before truncation _truncate__ = 50 # Pauli (x,z) lookup table to string pltb_str = {(0, 0): "I", (1, 0): "X", (0, 1): "Z", (1, 1): "Y"} # Pauli (x,z) lookup table to int pltb_int = {(0, 0): 0, (1, 0): 1, (0, 1): 2, (1, 1): 3} def __init__( self, data: Union[str, tuple, List, np.ndarray, BasePauli, None] = None, *, x: Union[List, np.ndarray, None] = None, z: Union[List, np.ndarray, None] = None, phase_exp: Union[int, str, np.ndarray, None] = None, input_pauli_encoding: Optional[str] = BasePauli.EXTERNAL_PAULI_ENCODING, input_qubit_order: Optional[str] = "right-to-left", order: Optional[str] = "xz", tuple_order: Optional[str] = "zx", num_qubits: Optional[int] = None, ): """Initialize the Pauli Initialiazation of the N-qubit Pauli operator Args: data (Union[str, tuple, List, np.ndarray, BasePauli, None]): Input data. x (Union[List, np.ndarray, None], optional): X part. Defaults to None. z (Union[List, np.ndarray, None], optional): Z part. Defaults to None. phase_exp (Union[str, np.ndarray, None], optional): Phase expression of Pauli. Defaults to None. input_pauli_encoding (str, optional): What encoding is used for the input data. Defaults to BasePauli.EXTERNAL_PAULI_ENCODING. input_qubit_order (str, optional): Qubit read order. Defaults to "right-to-left". order (str, optional): Order in which data lists X and Z. Defaults to 'xz'. tuple_order (str, optional): Order in data for X and Z parts of tuples. Defaults to 'zx'. num_qubits (int, optional): Number of qubits to use in Pauli. Defaults to None. Raises: QiskitError: Something went wrong. Examples: >>> Pauli('XYXXIZ') Pauli('XYXXIZ') >>> Pauli('X1Y3Z12') Pauli('ZIIIIIIIIYIXI') >>> Pauli('X', num_qubits=12) Pauli('IIIIIIIIIIIX') >>> Pauli(np.array([[0,1,1,1]]), phase_exp="(-i,1)", num_qubits=10) Pauli('-iIIIIIIIIYZ') >>> Pauli(np.array([[0,1,1,1]]),phase_exp="(-i,1)", num_qubits=10, order="zx") Pauli('-iIIIIIIIIYX') >>> Pauli(None, x=[0,1],z=[1,1],phase_exp = '-i') Pauli('-iYZ') >>> Pauli(np.array([[0,1,1,1]]), phase_exp="(-i,1)(-1,0)", num_qubits=10, order="zx", input_pauli_encoding='-isXZ') Pauli('-iIIIIIIIIYX') >>> Pauli(([0,1],[1,1],'-i'), tuple_order='xz') Pauli('-iYZ') >>> Pauli(([0,1],[1,1],'-i')) Pauli('-iYX') """ # str if isinstance(data, str): matrix, phase_exp = pauli_rep.str2symplectic( data, qubit_order=input_qubit_order, num_qubits=num_qubits ) # numpy or list elif isinstance(data, (np.ndarray, list)): matrix = np.atleast_2d(data) phase_enc, _ = pauli_rep.split_pauli_enc(input_pauli_encoding) if isinstance(phase_exp, str): phase_exp = pauli_rep.str2exp( pauli_rep.stand_phase_str(phase_exp), encoding=phase_enc ) elif phase_exp is None: phase_exp = pauli_rep.exp2exp(0, pauli_rep.INTERNAL_PHASE_ENCODING, phase_enc) matrix, phase_exp = pauli_rep.from_array( matrix, phase_exp, input_pauli_encoding=input_pauli_encoding ) # tuple (x,z,phase), (z,x,phase), (x,z), (z,x) elif isinstance(data, tuple): p1 = np.atleast_2d(data[0]) p2 = np.atleast_2d(data[1]) matrix = np.hstack((p1, p2)) phase_enc, _ = pauli_rep.split_pauli_enc(input_pauli_encoding) if len(data) == 3: phase_exp = data[2] order = tuple_order if isinstance(phase_exp, str): phase_exp = pauli_rep.str2exp( pauli_rep.stand_phase_str(phase_exp), encoding=phase_enc ) elif phase_exp is None: phase_exp = pauli_rep.exp2exp(0, pauli_rep.INTERNAL_PHASE_ENCODING, phase_enc) matrix, phase_exp = pauli_rep.from_array( matrix, phase_exp, input_pauli_encoding=input_pauli_encoding ) order = tuple_order # BasePauli elif isinstance(data, BasePauli): matrix = data.matrix[:, :] phase_exp = data._phase_exp[:] # ScalarOp elif isinstance(data, ScalarOp): matrix, phase_exp = pauli_rep.scalar_op2symplectic( data, output_encoding=pauli_rep.INTERNAL_PHASE_ENCODING ) # Quantum Circuit or Instruction elif isinstance(data, (QuantumCircuit, Instruction)): matrix, phase_exp = self.instrs2symplectic(data) # x= ... , z= ..., [phase_exp = ...] elif ( data is None and isinstance(x, (List, np.ndarray)) and isinstance(z, (List, np.ndarray)) ): x = np.atleast_2d(x) z = np.atleast_2d(z) matrix = np.hstack((x, z)) phase_enc, _ = pauli_rep.split_pauli_enc(input_pauli_encoding) if isinstance(phase_exp, str): phase_exp = pauli_rep.str2exp( pauli_rep.stand_phase_str(phase_exp), encoding=phase_enc ) if phase_exp is None: phase_exp = pauli_rep.exp2exp(0, pauli_rep.INTERNAL_PHASE_ENCODING, phase_enc) matrix, phase_exp = pauli_rep.from_array( matrix, phase_exp, input_pauli_encoding=input_pauli_encoding ) # Error: no input is suitable else: raise QiskitError("Invalid input data for Pauli.") # Add extra qubits if requested if num_qubits is not None and num_qubits > matrix.shape[1] // 2: extend_num = num_qubits - matrix.shape[1] // 2 matrix = pauli_rep._extend_symplectic(matrix, extend_num) # Initialize BasePauli if matrix.shape[0] != 1: raise QiskitError("Input is not a single Pauli") super().__init__(matrix, phase_exp, order=order) self.vlist = self.matrix[0].tolist() # --------------------------------------------------------------------- # Property Methods # --------------------------------------------------------------------- @property def name(self): """Unique string identifier for operation type.""" return "pauli" @property def num_clbits(self): """Number of classical bits.""" return 0 @property def settings(self) -> Dict: """Return settings.""" return {"data": self.to_label()} @property def num_y(self): """Return the number of Y for each operator""" return np.sum(np.logical_and(self.x, self.z), axis=0)
[docs] @classmethod def set_truncation(cls, val): """Set the max number of Pauli characters to display before truncation/ Args: val (int): the number of characters. .. note:: Truncation will be disabled if the truncation value is set to 0. """ cls._truncate__ = int(val)
@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 ) @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, ) @property def phase(self): """Return the complex phase of the Pauli""" return pauli_rep.exp2cpx(self.phase_exp, input_encoding=BasePauli.EXTERNAL_PHASE_ENCODING) @property def x(self): """The x vector for the Pauli.""" 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 Pauli.""" return self.matrix[:, self.num_qubits :][0] @z.setter def z(self, val): self.matrix[:, self.num_qubits :][0] = val # --------------------------------------------------------------------- # Magic Methods # --------------------------------------------------------------------- def __repr__(self): """Display representation.""" return f"Pauli('{self.__str__()}')" def __str__(self): """Print representation.""" if ( self._truncate__ and self.num_qubits > self._truncate__ and BasePauli.EXTERNAL_SYNTAX == pauli_rep.PRODUCT_SYNTAX ): front = self[-self._truncate__ :].to_label() return front + "..." return self.to_label() def __array__(self, dtype=None): if dtype: return np.asarray(self.to_cpx_matrix(), dtype=dtype) return self.to_cpx_matrix() def __eq__(self, other): """Test if two Paulis are equal.""" if not isinstance(other, BasePauli): return False return self._eq(other) def __len__(self): """Return the number of qubits in the Pauli.""" return self.num_qubits def __hash__(self): """Make hashable based on string representation.""" return hash(self.to_label()) def __setitem__(self, qubits, value): """Update the Pauli for a subset of qubits.""" if not isinstance(value, Pauli): value = Pauli(value) self._z[0, qubits] = value._z self._x[0, qubits] = value._x # Add extra phase from new Pauli to current self._phase_exp = (self._phase_exp + value._phase_exp) % 4 def __getitem__( self, qubits: Union[int, np.integer, slice, List[int], List[np.integer]] ) -> "Pauli": """Get no phase Pauli for specific qubits Args: qubits: index of qubit Returns: Pauli: Pauli acting on qubit i """ # This is expensive ~6us compared to ~100ns for a Pauli get from a PauliList. Better # than the ~25mu that the existing Pauli takes in quantum_info # Does allow referencing but limited to single indexing. # # There are pros and cons associated with how we represent the symplectic # matrix. If you represent this matrix as a X and Z matrix then more slicing options # are possible here that would allow referening but only to a limited extent. # # Linear slicing with steps could be down with full vector representations. Linear slicing # with steps is chosing a contigous block of qubits to select and steping in that block. # for expample say [l:k:s]. To do this you can reshape the full vector from # (1, 2*num_qubits) to (2, num_qubits) and then slicing that matrix with [l:k:s]. This # gives some matrix of size (2, selected_num_qubits) with the 0th row representing the # X part and the 1st row representing the Z part. Yoiu cannot reshape this as it will distroy # the refering. # # This could be used by a second form of the vector (a matrix) would need to be stored and # interpreted appropriately. # # If refercing is not necessary then it is easy to extract the necessary value in what # ever form is desired. # # Suggest that __getitem__ be as general as possible to allow all sorts of possible selctions # some of which will and some will not provide referencing. Then have some specific # fast functions # that check less and can do less. # # With relative speeds # # __getitem__ 6us (will get slower as more checks and capability added) # getitem 6us # fast_getitem_int 1.5us # fast_getitem_str 1.5us # etc # If you just reture the tuple (x,z) value then you get # getxz # # One can get faster results if for Paulis we also store a list version # # vlist_getitem_raw 530ns # vlist_getitem_int 580ns # # These results are fore small dimensions (and using nump arrays). For different situtations # different methods may should be used. if isinstance(qubits, (int, np.integer)): qubits = [qubits] return Pauli((self.z[qubits], self.x[qubits])) def _getitem(self, i): """Get Pauli for qubit i Args: i (int): index of qubit Returns: Pauli: Pauli acting on qubit i """ return Pauli(self.matrix[0][i : self.matrix.shape[1] : self.num_qubits]) def _fast_getitem_str(self, i): """Get Pauli for qubit i Args: i (int): index of qubit Returns: str: String representing the Pauli acting on qubit i, (0,0):"I", (1,0):"X", (0,1):"Z", (1,1):"Y" """ return Pauli.pltb_str[(self.matrix[0][i], self.matrix[0][i + self.num_qubits])] def _fast_getitem_int(self, i): """Get Pauli for qubit i Args: i (int): index of qubit Returns: int: Integer representing the Pauli acting on qubit i, (I:0, X:1, Z:2, Y:4) """ return Pauli.pltb_int[(self.matrix[0][i], self.matrix[0][i + self.num_qubits])] def _getitem_raw(self, i): """Get Pauli symplectic (x,z)-tuple for qubit i Args: i (int): index of qubit Returns: (list): Symplectic (x,z)-tuple for the Pauli on qubit i """ return self.matrix[0][i], self.matrix[0][i + self.num_qubits] def _vlist_getitem_raw(self, i): """Get Pauli symplectic (x,z)-tuple for qubit i. Requires extra storage for the list representation of the Pauli Args: i (int): index of qubit Returns: [tuple]: Symplectic (x,z)-tuple for the Pauli on qubit i """ return self.vlist[i], self.vlist[i + self.num_qubits] def _vlist_getitem_int(self, i): """Get Pauli for qubit i using a stored list of the Pauli. Args: i (int): index of qubit Returns: int: Integer representing the Pauli acting on qubit i, (I:0, X:1, Z:2, Y:4) """ return Pauli.pltb_int[(self.vlist[i], self.vlist[i + self.num_qubits])] # --------------------------------------------------------------------- # Insert/Delete Methods # ---------------------------------------------------------------------
[docs] def delete(self, qubits: Union[int, List[int]]) -> "Pauli": """Return a Pauli with qubits deleted. Args: qubits (int or list): qubits to delete from Pauli. Returns: Pauli: the resulting Pauli with the specified qubits removed. Raises: QiskitError: if ind is out of bounds for the array size or number of qubits. """ if isinstance(qubits, (int, np.integer)): qubits = [qubits] if max(qubits) > self.num_qubits - 1: raise QiskitError( f"Qubit index is larger than the number of qubits \ ({max(qubits)}>{self.num_qubits - 1})." ) if len(qubits) == self.num_qubits: raise QiskitError("Cannot delete all qubits of Pauli") z = np.delete(self._z, qubits, axis=1) x = np.delete(self._x, qubits, axis=1) matrix = np.hstack((x, z)) return Pauli(matrix, phase_exp=self.phase_exp)
[docs] def insert(self, qubits: Union[int, List[int]], value: "Pauli") -> "Pauli": """Insert a Pauli at specific qubit value. Args: qubits (int or list): qubits index to insert at. value (Pauli): value to insert. Returns: Pauli: the resulting Pauli with the entries inserted. Raises: QiskitError: if the insertion qubits are invalid. """ if not isinstance(value, Pauli): value = Pauli(value) # Initialize empty operator ret_qubits = self.num_qubits + value.num_qubits ret = Pauli(np.zeros(2 * ret_qubits, dtype=bool)) if isinstance(qubits, (int, np.integer)): if value.num_qubits == 1: qubits = [qubits] else: qubits = list(range(qubits, qubits + value.num_qubits)) if len(qubits) != value.num_qubits: raise QiskitError( f"Number of indices does not match number of qubits for \ the inserted Pauli ({len(qubits)}!={value.num_qubits})" ) if max(qubits) > ret.num_qubits - 1: raise QiskitError( f"Index is too larger for combined Pauli number of qubits \ ({max(qubits)}>{ret.num_qubits - 1})." ) # Qubit positions for original op self_qubits = [i for i in range(ret.num_qubits) if i not in qubits] ret[self_qubits] = self ret[qubits] = value return ret
[docs] def equiv(self, other): """Return True if Pauli's are equivalent up to group phase. Args: other (Pauli): an operator object. Returns: bool: True if the Pauli's are equivalent up to group phase. """ if not isinstance(other, Pauli): try: other = Pauli(other) except QiskitError: return False return np.all(self._z == other._z) and np.all(self._x == other._x)
@classmethod def _from_scalar_op(cls, op): return pauli_rep.scalar_op2symplectic(op) @classmethod def _from_pauli_instruction(cls, instr): return pauli_rep.gate2symplectic(instr) @classmethod def _from_circuit(cls, instr): return cls.instrs2symplectic(instr) # --------------------------------------------------------------------- # Output representation methods: Can also use the pauli_rep methods # --------------------------------------------------------------------- # Should be deprecated in favour of to_cpx_matrix
[docs] def to_matrix(self, sparse: bool = False) -> np.ndarray: """_summary_ Args: sparse (bool, optional): _description_. Defaults to False. Returns: np.ndarray: _description_ """ return self.to_cpx_matrix(sparse=sparse)
[docs] def to_cpx_matrix(self, sparse: bool = False) -> np.ndarray: """_summary_ Args: sparse (bool, optional): _description_. Defaults to False. Returns: np.ndarray: _description_ """ return pauli_rep.to_cpx_matrix(self.matrix, self._phase_exp, sparse=sparse)
[docs] def to_instruction(self): """Convert to Pauli circuit instruction.""" from math import pi pauli, phase_exp = self.to_label( output_pauli_encoding=pauli_rep.DEFAULT_EXTERNAL_PAULI_ENCODING, no_phase=True, return_phase=True, syntax=pauli_rep.PRODUCT_SYNTAX, qubit_order="right-to-left", ) if len(pauli) == 1: gate = {"I": IGate(), "X": XGate(), "Y": YGate(), "Z": ZGate()}[pauli] else: gate = PauliGate(pauli) if not phase_exp[0]: return gate # Add global phase circuit = QuantumCircuit(self.num_qubits, name=str(self)) circuit.global_phase = -phase_exp[0] * pi / 2 circuit.append(gate, range(self.num_qubits)) return circuit.to_instruction()
# --------------------------------------------------------------------- # BaseOperator methods # ---------------------------------------------------------------------
[docs] def compose( self, other: Union["Pauli", BasePauli], qargs: Optional[List[int]] = None, front: bool = False, inplace: bool = False, ) -> "Pauli": """Return the operator composition with another Pauli. Args: other (Pauli): a Pauli 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: Pauli: The composed Pauli. 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, Pauli): other = Pauli(other) return Pauli(super().compose(other, qargs=qargs, front=front, inplace=inplace))
# pylint: disable=arguments-differ
[docs] def dot( self, other: Union["Pauli", BasePauli], qargs: Optional[List[int]] = None, inplace: bool = False, ) -> "Pauli": """Return the right multiplied operator self * other. Args: other (Pauli): an operator object. qargs (list or None): Optional, qubits to apply dot product on (default: None). inplace (bool): If True update in-place (default: False). Returns: Pauli: The operator self * other. """ return self.compose(other, qargs=qargs, front=True, inplace=inplace)
[docs] def tensor(self, other) -> "Pauli": if not isinstance(other, Pauli): other = Pauli(other) return Pauli(super().tensor(other))
[docs] def expand(self, other: Union["Pauli", BasePauli]) -> "Pauli": if not isinstance(other, Pauli): other = Pauli(other) return Pauli(super().expand(other))
def _multiply(self, phase: Union["Pauli", BasePauli]) -> "Pauli": return Pauli(super()._multiply(phase))
[docs] def conjugate(self): return Pauli(super().conjugate())
[docs] def transpose(self) -> "Pauli": return Pauli(super().transpose())
[docs] def adjoint(self) -> "Pauli": return Pauli(super().adjoint())
[docs] def inverse(self) -> "Pauli": """Return the inverse of the Pauli.""" return Pauli(super().adjoint())
# --------------------------------------------------------------------- # Utility methods # ---------------------------------------------------------------------
[docs] def commutes(self, other, qargs=None): """Return True if the Pauli commutes with other. Args: other (Pauli or PauliList): another Pauli operator. qargs (list): qubits to apply dot product on (default: None). Returns: bool: True if Pauli's commute, False if they anti-commute. """ if qargs is None: qargs = getattr(other, "qargs", None) if not isinstance(other, BasePauli): other = Pauli(other) ret = super().commutes(other, qargs=qargs) if len(ret) == 1: return ret[0] return ret
[docs] def anticommutes(self, other, qargs=None): """Return True if other Pauli anticommutes with self. Args: other (Pauli): another Pauli operator. qargs (list): qubits to apply dot product on (default: None). Returns: bool: True if Pauli's anticommute, False if they commute. """ return np.logical_not(self.commutes(other, qargs=qargs))
[docs] def evolve(self, other, qargs=None, frame="h"): r"""Heisenberg picture evolution of a Pauli by a Clifford. This returns the Pauli :math:`P^\prime = C^\dagger.P.C`. By choosing the parameter frame='s', this function returns the Schrödinger evolution of the Pauli :math:`P^\prime = C.P.C^\dagger`. This option yields a faster calculation. Args: other (Pauli or Clifford or QuantumCircuit): The Clifford operator to evolve by. qargs (list): a list of qubits to apply the Clifford to. frame (string): 'h' for Heisenberg or 's' for Schrödinger framework. Returns: Pauli: the Pauli :math:`C^\dagger.P.C`. Raises: QiskitError: if the Clifford number of qubits and qargs don't match. """ if qargs is None: qargs = getattr(other, "qargs", None) if not isinstance(other, (Pauli, Instruction, QuantumCircuit)): # Convert to a Pauli other = Pauli(other) return Pauli(super().evolve(other, qargs=qargs, frame=frame))
[docs] @staticmethod def instrs2symplectic(instr: Union[Instruction, QuantumCircuit]): """Convert a Pauli circuit to BasePauli data.""" # Try and convert single instruction if isinstance(instr, (PauliGate, IGate, XGate, YGate, ZGate)): return pauli_rep.gate2symplectic(instr) if isinstance(instr, Instruction): # Convert other instructions to circuit definition if instr.definition is None: raise QiskitError(f"Cannot apply Instruction: {instr.name}") # Convert to circuit instr = instr.definition # Initialize identity Pauli ret = Pauli(np.zeros((1, 2 * instr.num_qubits), dtype=bool), phase_exp=0) # Add circuit global phase if specified if instr.global_phase: ret._phase_exp = pauli_rep.cpx2exp( np.exp(1j * float(instr.global_phase)), output_encoding=pauli_rep.INTERNAL_PHASE_ENCODING, ) # Recursively apply instructions # for dinstr, qregs, cregs in instr.data: for instruction in instr.data: dinstr = instruction.operation qregs = instruction.qubits cregs = instruction.clbits if cregs: raise QiskitError( f"Cannot apply instruction with classical registers: {dinstr.name}" ) if not isinstance(dinstr, (Barrier, Delay)): next_instr = BasePauli(*Pauli.instrs2symplectic(dinstr)) if next_instr is not None: qargs = [instr.find_bit(tup)[0] for tup in qregs] ret = ret.compose(next_instr, qargs=qargs) return ret.matrix, ret._phase_exp
# --------------------------------------------------------------------- # 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, qubit_order): # Deprecated wrapper of `_from_label` so that a deprecation warning # can be displaced during initialization with deprecated kwarg return pauli_rep.str2symplectic(label, qubit_order=qubit_order) @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_split_array_deprecated(cls, z, x, phase_exp: int = 0): # Deprecated wrapper of `_from_array` so that a deprecation warning # can be displaced during initialization with deprecated kwarg return pauli_rep.from_split_array(x, z, phase_exp) @staticmethod def _make_np_bool(arr): if not isinstance(arr, (list, np.ndarray, tuple)): arr = [arr] arr = np.asarray(arr).astype(bool) return arr
[docs] @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)
[docs] @staticmethod @deprecate_function( "sgn_prod is deprecated and will be removed no earlier than \ 3 months after the release date. Use `dot` instead." ) # pylint: disable=invalid-name def sgn_prod(p1, p2): # pylint: disable=invalid-name 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
[docs] @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 pauli_rep.to_cpx_matrix(self.matrix, self._phase_exp, sparse=True)
[docs] @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.matrix = pauli.matrix self._phase_exp = pauli._phase_exp self._op_shape = self._op_shape.expand(other._op_shape) return self
[docs] @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_exp = self._phase_exp 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_exp = phase_exp return self
[docs] @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_exp = self._phase_exp 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_exp = phase_exp return self
[docs] @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) matrix = np.hstack((x, z)) pauli = Pauli(matrix, phase_exp=self._phase_exp + paulis._phase_exp) self.z = pauli.z self.x = pauli.x self._phase_exp = pauli._phase_exp self._op_shape = pauli._op_shape return self
[docs] @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)
[docs] @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 # pylint: disable=invalid-name self._x = pauli._x # pylint: disable=invalid-name self._phase_exp = pauli._phase_exp self._op_shape = pauli._op_shape return self
[docs] @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
[docs] @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)
# Update docstrings for API docs generate_apidocs(Pauli)