Source code for povm_toolbox.quantum_info.multi_qubit_frame
# (C) Copyright IBM 2024.## 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."""MultiQubitFrame."""from__future__importannotationsimportsysifsys.version_info<(3,11):fromtyping_extensionsimportSelfelse:fromtypingimportSelf# pragma: no coverifsys.version_info<(3,12):fromtyping_extensionsimportoverrideelse:fromtypingimportoverride# pragma: no coverimportnumpyasnpfromqiskit.exceptionsimportQiskitErrorfromqiskit.quantum_infoimportOperator,SparsePauliOpfrompovm_toolbox.utilitiesimportmatrix_to_double_ketfrom.baseimportBaseFrame
[docs]classMultiQubitFrame(BaseFrame[int]):"""Class that collects all information that any frame of multiple qubits should specify. This is a representation of an operator-valued vector space frame. The effects are specified as a list of :class:`~qiskit.quantum_info.Operator`. .. note:: This is a base class which collects functionality common to various subclasses. As an end-user you would not use this class directly. Check out :mod:`povm_toolbox.quantum_info` for more general information. """def__init__(self,list_operators:list[Operator])->None:"""Initialize from explicit operators. Args: list_operators: list that contains the explicit frame operators. The length of the list is the number of operators of the frame. Raises: ValueError: if the frame operators do not have a correct shape. They should all be hermitian and of the same dimension. """self._num_operators:intself._dimension:intself._operators:list[Operator]self._pauli_operators:list[dict[str,complex]]|Noneself._array:np.ndarrayself._informationally_complete:boolself.operators=list_operatorsdef__repr__(self)->str:"""Return the string representation of a :class:`.MultiQubitFrame` instance."""f_subsystems=f"(num_qubits={self.num_subsystems})"ifself.num_subsystems>1else""returnf"{self.__class__.__name__}{f_subsystems}<{self.num_operators}> at {hex(id(self))}"@propertydefinformationally_complete(self)->bool:"""If the frame spans the entire Hilbert space."""returnself._informationally_complete@propertydefdimension(self)->int:"""The dimension of the Hilbert space on which the effects act."""returnself._dimension@propertydefnum_operators(self)->int:"""The number of effects of the frame."""returnself._num_operators@propertydefoperators(self)->list[Operator]:"""Return the list of frame operators."""returnself._operators@operators.setterdefoperators(self,new_operators:list[Operator]):"""Set the frame operators."""self._num_operators=len(new_operators)self._dimension=new_operators[0].dim[0]forframe_opinnew_operators:ifnot(self._dimension==frame_op.dim[0]andself._dimension==frame_op.dim[1]):raiseValueError(f"Frame operators need to be square ({frame_op.dim[0]},{frame_op.dim[1]}) and ""all of the same dimension.")self._operators=new_operatorsself._pauli_operators=Noneself._array=np.ndarray((self.dimension**2,self.num_operators),dtype=complex)fork,frame_opinenumerate(new_operators):self._array[:,k]=matrix_to_double_ket(frame_op.data)self._informationally_complete=bool(np.linalg.matrix_rank(self._array)==self.dimension**2)self._check_validity()@propertydefpauli_operators(self)->list[dict[str,complex]]:"""Convert the internal frame operators to Pauli form. .. warning:: The conversion to Pauli form can be computationally intensive. Returns: The frame operators in Pauli form. Each frame operator is returned as a dictionary mapping Pauli labels to coefficients. Raises: QiskitError: when the frame operators could not be converted to Pauli form (e.g. when they are not N-qubit operators). """ifself._pauli_operatorsisNone:try:self._pauli_operators=[dict(SparsePauliOp.from_operator(op).label_iter())foropinself.operators]exceptQiskitErrorasexc:raiseQiskitError(f"Failed to convert frame operators to Pauli form: {exc.message}")fromexcreturnself._pauli_operatorsdef_check_validity(self)->None:"""Check if frame axioms are fulfilled. Raises: ValueError: if any one of the frame operators is not hermitian. """fork,opinenumerate(self.operators):ifnotnp.allclose(op,op.adjoint(),atol=1e-5):raiseValueError(f"The {k}-the frame operator is not hermitian.")def__getitem__(self,index:slice)->Operator|list[Operator]:"""Return a frame operator or a list of frame operators. Args: index: indicate the operator(s) to be returned. Returns: The operator or list of operators corresponding to the index. """returnself.operators[index]def__len__(self)->int:"""Return the number of operators of the frame."""returnself.num_operatorsdef__array__(self)->np.ndarray:"""Return the array representation of the frame. The array has a shape :math:`(``self.dimension``**2, ``self.num_operators``)`. """returnself._array
[docs]@overridedefanalysis(self,hermitian_op:SparsePauliOp|Operator,frame_op_idx:int|set[int]|None=None,)->float|dict[int,float]|np.ndarray:ifisinstance(hermitian_op,SparsePauliOp):hermitian_op=hermitian_op.to_operator()op_vectorized=np.conj(matrix_to_double_ket(hermitian_op.data))ifisinstance(frame_op_idx,int):returnfloat(np.dot(op_vectorized,self._array[:,frame_op_idx]).real)ifisinstance(frame_op_idx,set):return{idx:float(np.dot(op_vectorized,self._array[:,idx]).real)foridxinframe_op_idx}ifframe_op_idxisNone:returnnp.array(np.dot(op_vectorized,self._array).real)raiseTypeError("The optional `frame_op_idx` can either be a single or set of integers, not a "f"{type(frame_op_idx)}.")
[docs]@classmethoddeffrom_vectors(cls,frame_vectors:np.ndarray)->Self:r"""Initialize a frame from non-normalized bloch vectors. The non-normalized Bloch vectors are given by :math:`|\tilde{\psi}_k \rangle = \sqrt{\gamma_k} |\psi_k \rangle`. The resulting frame operators are :math:`F_k = \gamma_k |\psi_k \rangle \langle \psi_k |` where :math:`\gamma_k` is the trace of the :math:`k`'th frame operator. Args: frame_vectors: list of vectors :math:`|\tilde{\psi_k} \rangle`. The length of the list corresponds to the number of operators of the frame. Each vector is of shape :math:`(\mathrm{dim},)` where :math:`\mathrm{dim}` is the :attr:`.dimension` of the Hilbert space on which the frame acts. Returns: The frame corresponding to the vectors. """returncls([Operator(np.outer(vec,vec.conj()))forvecinframe_vectors])