Source code for qibo.quantum_info.basis

from itertools import product
from typing import Optional

import numpy as np

from qibo import matrices
from qibo.backends import _check_backend
from qibo.config import raise_error
from qibo.quantum_info.superoperator_transformations import vectorization


[docs]def pauli_basis( nqubits: int, normalize: bool = False, vectorize: bool = False, sparse: bool = False, order: Optional[str] = None, pauli_order: str = "IXYZ", backend=None, ): """Creates the ``nqubits``-qubit Pauli basis. Args: nqubits (int): number of qubits. normalize (bool, optional): If ``True``, normalized basis is returned. Defaults to False. vectorize (bool, optional): If ``False``, returns a nested array with all Pauli matrices. If ``True``, retuns an array where every row is a vectorized Pauli matrix. Defaults to ``False``. sparse (bool, optional): If ``True``, retuns Pauli basis in a sparse representation. Defaults to ``False``. order (str, optional): If ``"row"``, vectorization of Pauli basis is performed row-wise. If ``"column"``, vectorization is performed column-wise. If ``"system"``, system-wise vectorization is performed. If ``vectorization=False``, then ``order=None`` is forced. Defaults to ``None``. pauli_order (str, optional): corresponds to the order of 4 single-qubit Pauli elements. Defaults to ``"IXYZ"``. backend (:class:`qibo.backends.abstract.Backend`, optional): backend to be used in the execution. If ``None``, it uses the current backend. Defaults to ``None``. Returns: ndarray or tuple: all Pauli matrices forming the basis. If ``sparse=True`` and ``vectorize=True``, tuple is composed of an array of non-zero elements and an array with their row-wise indexes. """ if nqubits <= 0: raise_error(ValueError, "nqubits must be a positive int.") if not isinstance(normalize, bool): raise_error( TypeError, f"normalize must be type bool, but it is type {type(normalize)} instead.", ) if not isinstance(vectorize, bool): raise_error( TypeError, f"vectorize must be type bool, but it is type {type(vectorize)} instead.", ) if not isinstance(sparse, bool): raise_error( TypeError, f"sparse must be type bool, but it is type {type(sparse)} instead.", ) if not isinstance(pauli_order, str): raise_error( TypeError, f"pauli_order must be type str, but it is type {type(pauli_order)} instead.", ) if set(pauli_order) != {"I", "X", "Y", "Z"}: raise_error( ValueError, f"pauli_order has to contain 4 symbols: I, X, Y, Z. Got {pauli_order} instead.", ) if vectorize and order is None: raise_error(ValueError, "when vectorize=True, order must be specified.") if sparse and not vectorize: raise_error( NotImplementedError, "sparse representation is not implemented for unvectorized Pauli basis.", ) backend = _check_backend(backend) pauli_labels = {"I": matrices.I, "X": matrices.X, "Y": matrices.Y, "Z": matrices.Z} dim = 2**nqubits basis_single = backend.cast([pauli_labels[label] for label in pauli_order]) einsum = np.einsum if backend.platform == "tensorflow" else backend.np.einsum if nqubits > 1: input_indices = [range(3 * i, 3 * (i + 1)) for i in range(nqubits)] output_indices = (i for indices in zip(*input_indices) for i in indices) operands = [basis_single for _ in range(nqubits)] inputs = [item for pair in zip(operands, input_indices) for item in pair] basis_full = einsum(*inputs, output_indices).reshape(4**nqubits, dim, dim) else: basis_full = basis_single if vectorize and sparse: if backend.platform == "pytorch": nonzero = lambda x: backend.np.nonzero(x, as_tuple=True) else: nonzero = backend.np.nonzero basis = vectorization(basis_full, order=order, backend=backend) indices = nonzero(backend.np.abs(basis)) # abs needed because of ``tensorflow`` basis = basis[indices].reshape(-1, dim) indices = indices[1].reshape(-1, dim) elif vectorize and not sparse: basis = vectorization(basis_full, order=order, backend=backend) else: basis = basis_full if normalize: basis = basis / np.sqrt(2**nqubits) if vectorize and sparse: return basis, indices return basis
[docs]def comp_basis_to_pauli( nqubits: int, normalize: bool = False, sparse: bool = False, order: str = "row", pauli_order: str = "IXYZ", backend=None, ): """Unitary matrix :math:`U` that converts operators from the Liouville representation in the computational basis to the Pauli-Liouville representation. The unitary :math:`U` is given by .. math:: U = \\sum_{k = 0}^{d^{2} - 1} \\, |k)(P_{k}| \\,\\, , where :math:`|P_{k})` is the vectorization of the :math:`k`-th Pauli operator :math:`P_{k}`, and :math:`|k)` is the vectorization of the :math:`k`-th computational basis element. For a definition of vectorization, see :func:`qibo.quantum_info.vectorization`. Example: .. code-block:: python from qibo.quantum_info import random_density_matrix, vectorization, comp_basis_to_pauli nqubits = 2 d = 2**nqubits rho = random_density_matrix(d) U_c2p = comp_basis_to_pauli(nqubits) rho_liouville = vectorization(rho, order="system") rho_pauli_liouville = U_c2p @ rho_liouville Args: nqubits (int): number of qubits. normalize (bool, optional): If ``True``, converts to the Pauli basis. Defaults to ``False``. sparse (bool, optional): If ``True``, returns unitary matrix in sparse representation. Defaults to ``False``. order (str, optional): If ``"row"``, vectorization of Pauli basis is performed row-wise. If ``"column"``, vectorization is performed column-wise. If ``"system"``, system-wise vectorization is performed. Defaults to ``"row"``. pauli_order (str, optional): corresponds to the order of 4 single-qubit Pauli elements. Defaults to ``"IXYZ"``. backend (:class:`qibo.backends.abstract.Backend`, optional): backend to be used in the execution. If ``None``, it uses the current backend. Defaults to ``None``. Returns: ndarray or tuple: Unitary matrix :math:`U`. If ``sparse=True``, tuple is composed of array of non-zero elements and an array with their row-wise indexes. """ backend = _check_backend(backend) if sparse: elements, indexes = pauli_basis( nqubits, normalize, vectorize=True, sparse=sparse, order=order, pauli_order=pauli_order, backend=backend, ) elements = backend.np.conj(elements) return elements, indexes unitary = pauli_basis( nqubits, normalize, vectorize=True, sparse=sparse, order=order, pauli_order=pauli_order, backend=backend, ) unitary = backend.np.conj(unitary) return unitary
[docs]def pauli_to_comp_basis( nqubits: int, normalize: bool = False, sparse: bool = False, order: str = "row", pauli_order: str = "IXYZ", backend=None, ): """Unitary matrix :math:`U` that converts operators from the Pauli-Liouville representation to the Liouville representation in the computational basis. The unitary :math:`U` is given by .. math:: U = \\sum_{k = 0}^{d^{2} - 1} \\, |P_{k})(b_{k}| \\, , where :math:`|P_{k})` is the vectorization of the :math:`k`-th Pauli operator :math:`P_{k}`, and :math:`|k)` is the vectorization of the :math:`k`-th computational basis element. For a definition of vectorization, see :func:`qibo.quantum_info.vectorization`. Args: nqubits (int): number of qubits. normalize (bool, optional): If ``True``, converts to the Pauli basis. Defaults to ``False``. sparse (bool, optional): If ``True``, returns unitary matrix in sparse representation. Defaults to ``False``. order (str, optional): If ``"row"``, vectorization of Pauli basis is performed row-wise. If ``"column"``, vectorization is performed column-wise. If ``"system"``, system-wise vectorization is performed. Defaults to ``"row"``. pauli_order (str, optional): corresponds to the order of 4 single-qubit Pauli elements. Defaults to ``"IXYZ"``. backend (:class:`qibo.backends.abstract.Backend`, optional): backend to be used in the execution. If ``None``, it uses the current backend. Defaults to ``None``. Returns: ndarray or tuple: Unitary matrix :math:`U`. If ``sparse=True``, tuple is composed of array of non-zero elements and an array with their row-wise indexes. """ backend = _check_backend(backend) unitary = pauli_basis( nqubits, normalize, vectorize=True, sparse=False, order=order, pauli_order=pauli_order, backend=backend, ) unitary = unitary.T if sparse: elements, indexes = [], [] for row in unitary: index_list = backend.np.flatnonzero(row) indexes.append(index_list) elements.append(row[index_list]) elements = backend.cast(elements) indexes = backend.cast(indexes) return elements, indexes return unitary