Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unify notation in the quantum_info documentation #1511

Draft
wants to merge 21 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion doc/source/api-reference/qibo.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1860,7 +1860,7 @@ Entanglement entropy

.. note::
``check_hermitian`` flag allows the user to choose if the function will check if
the reduced density matrix resulting from tracing out ``bipartition`` from input
the reduced density matrix resulting from tracing out ``partition`` from input
``state`` is Hermitian or not. Default option is ``check_hermitian=False``, i.e. the
assumption of Hermiticity. This is faster and, more importantly,
this function are intended to be used on Hermitian inputs. When ``check_hermitian=True``
Expand Down
2 changes: 1 addition & 1 deletion src/qibo/callbacks.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ def apply(self, backend, state):

entropy, spectrum = entanglement_entropy(
state,
bipartition=self.partition,
partition=self.partition,
base=self.base,
check_hermitian=self.check_hermitian,
return_spectrum=True,
Expand Down
76 changes: 48 additions & 28 deletions src/qibo/quantum_info/basis.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,32 +18,46 @@ def pauli_basis(
pauli_order: str = "IXYZ",
backend=None,
):
"""Creates the ``nqubits``-qubit Pauli basis.
"""Create the :math:`n`-qubit Pauli basis.

For :math:`d = 2^{n}`, the returned Pauli basis is represented by the following array:

.. math::
\\mathcal{P} = \\mathcal{N} \\, \\left[ P_{0}, \\, P_{1}, \\,
\\cdots, P_{d^{2} - 1} \\right] \\, ,

where :math:`P_{k}` is the representation of the :math:`k`-th element of the Pauli basis,
and :math:`\\mathcal{N}` is a normalization factor that equals to :math:`1/\\sqrt{d}` if
``normalize=True``, and :math:`1` otherwise.
If ``vectorize=False``, each :math:`P_{k}` is the :math:`d \\times d` matrix representing
the :math:`k`-th Pauli element. If ``vectorize=True``, then the Paulis are vectorized
according to ``order`` (see :func:`qibo.quantum_info.vectorization`).

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
nqubits (int): number of qubits :math:`n`.
normalize (bool, optional): if ``True``, :math:`\\mathcal{N} = 1/\\sqrt{d}`,
and the normalized Pauli 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
row is a vectorized Pauli matrix according to vectorization ``order``.
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
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_order (str, optional): corresponds to the order of :math:`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.
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:
Expand Down Expand Up @@ -136,51 +150,57 @@ def comp_basis_to_pauli(
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.
"""Unitary matrix :math:`U` that converts operators to the Pauli-Liouville representation.

The unitary :math:`U` is given by
For :math:`d = 2^{n}`, the unitary matrix :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.
Pauli operator :math:`P_{k}`, and :math:`|k)` is the :math:`k`-th
computational basis element in :math:`\\mathbb{C}^{d^{2}}`.
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
# Imports below are equivalent to the following:
# from qibo.quantum_info.basis import comp_basis_to_pauli
# from qibo.quantum_info.random_ensembles import random_density_matrix
# from qibo.quantum_info.superoperator_transformations import vectorization
from qibo.quantum_info import comp_basis_to_pauli, random_density_matrix, vectorization

nqubits = 2
d = 2**nqubits
rho = random_density_matrix(d)
U_c2p = comp_basis_to_pauli(nqubits)
dims = 2**nqubits

U_c2p = comp_basis_to_pauli(nqubits, order="system")

rho = random_density_matrix(dims, pure=False)

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``.
nqubits (int): number of qubits :math:`n`.
normalize (bool, optional): If ``True``, returns unitary matrix that converts
to the normalized 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_order (str, optional): corresponds to the order of :math:`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.
tuple is composed of array of non-zero elements and an
array with their row-wise indexes.

"""
backend = _check_backend(backend)
Expand Down
126 changes: 77 additions & 49 deletions src/qibo/quantum_info/entanglement.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Submodules with entanglement measures."""

from typing import List, Tuple, Union

import numpy as np

from qibo.backends import _check_backend
Expand All @@ -12,27 +14,36 @@
from qibo.quantum_info.metrics import fidelity, purity


def concurrence(state, bipartition, check_purity: bool = True, backend=None):
"""Calculates concurrence of a pure bipartite quantum state
:math:`\\rho \\in \\mathcal{H}_{A} \\otimes \\mathcal{H}_{B}` as
def concurrence(
state,
partition: Union[List[int], Tuple[int, ...]],
check_purity: bool = True,
backend=None,
):
"""Calculate concurrence of a pure bipartite quantum state.

For a pure bipartite quantum state
:math:`\\rho \\in \\mathcal{H}_{A} \\otimes \\mathcal{H}_{B}`,
the concurrence :math:`C(\\rho)` can be calculate as

.. math::
C(\\rho) = \\sqrt{2 \\, (\\text{tr}^{2}(\\rho) - \\text{tr}(\\rho_{A}^{2}))} \\, ,
\\operatorname{C}(\\rho) = \\sqrt{2 \\, (\\operatorname{Tr}^{2}(\\rho) -
\\operatorname{Tr}(\\rho_{B}^{2}))} \\, ,

where :math:`\\rho_{A} = \\text{tr}_{B}(\\rho)` is the reduced density operator
obtained by tracing out the qubits in the ``bipartition`` :math:`B`.
where :math:`\\rho_{B} = \\operatorname{Tr}_{A}(\\rho)` is the reduced density operator
obtained by tracing out the qubits in the ``partition`` :math:`A`.

Args:
state (ndarray): statevector or density matrix.
bipartition (list or tuple or ndarray): qubits in the subsystem to be traced out.
partition (list or tuple): qubits in the partition :math:`A` to be traced out.
check_purity (bool, optional): if ``True``, checks if ``state`` is pure. If ``False``,
it assumes ``state`` is pure . Defaults to ``True``.
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:
float: Concurrence of :math:`\\rho`.
float: Concurrence :math:`\\operatorname{C}`.
"""
backend = _check_backend(backend)

Expand Down Expand Up @@ -62,7 +73,7 @@ def concurrence(state, bipartition, check_purity: bool = True, backend=None):
"concurrence only implemented for pure quantum states.",
)

reduced_density_matrix = partial_trace(state, bipartition, backend=backend)
reduced_density_matrix = partial_trace(state, partition, backend=backend)

purity_reduced = purity(reduced_density_matrix, backend=backend)
if purity_reduced - 1.0 > 0.0:
Expand All @@ -74,26 +85,40 @@ def concurrence(state, bipartition, check_purity: bool = True, backend=None):


def entanglement_of_formation(
state, bipartition, base: float = 2, check_purity: bool = True, backend=None
state,
partition: Union[List[int], Tuple[int, ...]],
base: float = 2,
check_purity: bool = True,
backend=None,
):
"""Calculates the entanglement of formation :math:`E_{f}` of a pure bipartite
quantum state :math:`\\rho`, which is given by
"""Calculate the entanglement of formation of a pure bipartite quantum state.

.. math::
E_{f} = H([1 - x, x]) \\, ,

For a pure bipartite quantumm state
:math:`\\rho \\in \\mathcal{H}_{A} \\otimes \\mathcal{H}_{B}`,
the entanglement of formation :math:`E_{f}` can be calculated as
function of its :func:`qibo.quantum_info.concurrence`.
Given a random variable :math:`\\chi \\in \\{0, \\, 1\\}` with
a Bernoulli probability distribution :math:`[1 - x(\\rho), \\, x(\\rho)]`,
where

.. math::
x = \\frac{1 + \\sqrt{1 - C^{2}(\\rho)}}{2} \\, ,
x(\\rho) = \\frac{1 + \\sqrt{1 - \\operatorname{C}^{2}(\\rho)}}{2} \\, ,

then the entanglement of formation :math:`\\operatorname{E}_{f}` of state :math:`\\rho`
is given by

:math:`C(\\rho)` is the :func:`qibo.quantum_info.concurrence` of :math:`\\rho`,
and :math:`H` is the :func:`qibo.quantum_info.entropies.shannon_entropy`.
.. math::
\\operatorname{E}_{f} = \\operatorname{H}_{2}(\\chi) \\, .

:math:`\\operatorname{C}(\\rho)` is the :func:`qibo.quantum_info.concurrence` of :math:`\\rho`,
and :math:`\\operatorname{H}_{2}` is the base-:math:`2`
:func:`qibo.quantum_info.shannon_entropy`.

Args:
state (ndarray): statevector or density matrix.
bipartition (list or tuple or ndarray): qubits in the subsystem to be traced out.
base (float): the base of the log in :func:`qibo.quantum_info.entropies.shannon_entropy`.
state (ndarray): statevector or density matrix :math:`\\rho`.
partition (list or tuple): qubits in the partition :math:`B` to be traced out.
base (float): the base of the :math:`\\log` in :func:`qibo.quantum_info.shannon_entropy`.
Defaults to :math:`2`.
check_purity (bool, optional): if ``True``, checks if ``state`` is pure. If ``False``,
it assumes ``state`` is pure . Default: ``True``.
Expand All @@ -103,14 +128,14 @@ def entanglement_of_formation(


Returns:
float: entanglement of formation of state :math:`\\rho`.
float: Entanglement of formation :math:`\\operatorname{E}_{f}`.
"""
from qibo.quantum_info.entropies import shannon_entropy # pylint: disable=C0415

backend = _check_backend(backend)

concur = concurrence(
state, bipartition=bipartition, check_purity=check_purity, backend=backend
state, partition=partition, check_purity=check_purity, backend=backend
)
concur = (1 + np.sqrt(1 - concur**2)) / 2
probabilities = [1 - concur, concur]
Expand All @@ -120,33 +145,32 @@ def entanglement_of_formation(
return ent_of_form


def negativity(state, bipartition, backend=None):
"""Calculates the negativity of a bipartite quantum state.
def negativity(state, partition: Union[List[int], Tuple[int, ...]], backend=None):
"""Calculate the negativity of a bipartite quantum state.

Given a bipartite state :math:`\\rho \\in \\mathcal{H}_{A} \\otimes \\mathcal{H}_{B}`,
the negativity :math:`\\operatorname{Neg}(\\rho)` is given by

.. math::
\\operatorname{Neg}(\\rho) = \\frac{1}{2} \\,
\\left( \\norm{\\rho_{B}}_{1} - 1 \\right) \\, ,
\\operatorname{Neg}(\\rho) = \\frac{\\|\\rho_{B}\\|_{1} - 1}{2} \\, ,

where :math:`\\rho_{B}` is the reduced density matrix after tracing out qubits in
partition :math:`A`, and :math:`\\norm{\\cdot}_{1}` is the Schatten :math:`1`-norm
partition :math:`A`, and :math:`\\|\\cdot\\|_{1}` is the Schatten :math:`1`-norm
(also known as nuclear norm or trace norm).

Args:
state (ndarray): statevector or density matrix.
bipartition (list or tuple or ndarray): qubits in the subsystem to be traced out.
state (ndarray): statevector or density matrix :math:`\\rho`.
partition (list or tuple): qubits in the partition :math:`A` to be traced out.
backend (:class:`qibo.backends.abstract.Backend`, optional): backend to be used
in the execution. If ``None``, it uses it uses the current backend.
Defaults to ``None``.

Returns:
float: Negativity :math:`\\operatorname{Neg}(\\rho)` of state :math:`\\rho`.
float: Negativity :math:`\\operatorname{Neg}`.
"""
backend = _check_backend(backend)

reduced = partial_transpose(state, bipartition, backend)
reduced = partial_transpose(state, partition, backend)
reduced = backend.np.conj(reduced.T) @ reduced
norm = backend.np.trace(matrix_power(reduced, 1 / 2, backend=backend))

Expand All @@ -156,16 +180,18 @@ def negativity(state, bipartition, backend=None):
def entanglement_fidelity(
channel, nqubits: int, state=None, check_hermitian: bool = False, backend=None
):
"""Entanglement fidelity :math:`F_{\\mathcal{E}}` of a ``channel`` :math:`\\mathcal{E}`
on ``state`` :math:`\\rho` is given by
"""Calculate entanglement fidelity of a quantum channel w.r.t. a quantum state.

Given a quantum ``channel`` :math:`\\mathcal{E}` and a quantum ``state``
:math:`\\rho`, the entanglement fidelity :math:`F_{\\mathcal{E}}` is given by

.. math::
F_{\\mathcal{E}}(\\rho) = F(\\rho_{f}, \\rho)
\\operatorname{F}_{\\mathcal{E}}(\\rho) =
\\operatorname{F}(\\mathcal{E}(\\rho), \\rho) \\, ,

where :math:`F` is the :func:`qibo.quantum_info.fidelity` function for states,
and :math:`\\rho_{f} = \\mathcal{E}_{A} \\otimes I_{B}(\\rho)`
is the state after the channel :math:`\\mathcal{E}` was applied to
partition :math:`A`.
where :math:`\\operatorname{F}` is the state :func:`qibo.quantum_info.fidelity`,
and :math:`\\mathcal{E}(\\rho) \\equiv (\\mathcal{E}_{A} \\otimes I_{B})(\\rho)`
is the state after the channel :math:`\\mathcal{E}` was applied to partition :math:`A`.

Args:
channel (:class:`qibo.gates.channels.Channel`): quantum channel
Expand Down Expand Up @@ -234,11 +260,12 @@ def meyer_wallach_entanglement(state, backend=None):
"""Compute the Meyer-Wallach entanglement :math:`Q` of a ``state``,

.. math::
Q(\\rho) = 2\\left(1 - \\frac{1}{N} \\, \\sum_{k} \\,
\\text{tr}\\left(\\rho_{k}^{2}\\right)\\right) \\, ,
\\operatorname{Q}(\\rho) = 2\\left(1 - \\frac{1}{n} \\, \\sum_{k=0}^{n-1} \\,
\\text{Tr}\\left(\\rho_{k}^{2}\\right)\\right) \\, ,

where :math:`\\rho_{k}` is the reduced density matrix of the :math:`k`-th qubit,
and :math:`n` is the total number of qubits in ``state``.

where :math:`\\rho_{k}^{2}` is the reduced density matrix of qubit :math:`k`,
and :math:`N` is the total number of qubits in ``state``.
We use the definition of the Meyer-Wallach entanglement as the average purity
proposed in `Brennen (2003) <https://dl.acm.org/doi/10.5555/2011556.2011561>`_,
which is equivalent to the definition introduced in `Meyer and Wallach (2002)
Expand Down Expand Up @@ -292,18 +319,19 @@ def meyer_wallach_entanglement(state, backend=None):


def entangling_capability(circuit, samples: int, seed=None, backend=None):
"""Return the entangling capability :math:`\\text{Ent}` of a parametrized circuit.
"""Calculate the entangling capability :math:`\\text{Ent}` of a parametrized circuit.

It is defined as the average Meyer-Wallach entanglement :math:`Q`
(:func:`qibo.quantum_info.meyer_wallach_entanglement`) of the ``circuit``, i.e.
It is defined as the average Meyer-Wallach entanglement :math:`\\operatorname{Q}`
(:func:`qibo.quantum_info.meyer_wallach_entanglement`) of the ``circuit``, *i.e.*

.. math::
\\text{Ent} = \\frac{2}{|\\mathcal{S}|}\\sum_{\\theta_{k} \\in \\mathcal{S}}
\\, Q(\\rho_{k}) \\, ,
\\operatorname{Ent}(\\rho) = \\frac{2}{|\\mathcal{S}|} \\,
\\sum_{\\theta_{k} \\in \\mathcal{S}} \\,
\\operatorname{Q}(\\rho(\\theta_{k})) \\, ,

where :math:`\\mathcal{S}` is the set of sampled circuit parameters,
and :math:`\\rho_{k}` is the state prepared by the circuit with uniformily-sampled
parameters :math:`\\theta_{k}`.
:math:`|\\mathcal{S}|` its cardinality, and :math:`\\rho_{k}` is the
state prepared by the circuit with uniformily-sampled parameters :math:`\\theta_{k}`.

.. note::
Currently, function does not work with ``circuit`` that contains noisy channels.
Expand Down
Loading
Loading