Skip to content

Commit

Permalink
shannon_entropy from utils to entropies
Browse files Browse the repository at this point in the history
  • Loading branch information
renatomello committed Jan 30, 2024
1 parent 5f7e7cf commit cbeb4e6
Show file tree
Hide file tree
Showing 6 changed files with 151 additions and 152 deletions.
80 changes: 40 additions & 40 deletions doc/source/api-reference/qibo.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1489,40 +1489,6 @@ The destabilizers can be extracted analogously with :meth:`qibo.quantum_info.cli
:member-order: bysource



Entropy measures
^^^^^^^^^^^^^^^^

Set of functions to calculate entropy measures.

Entropy
"""""""

.. autofunction:: qibo.quantum_info.entropy

.. note::
``validate`` flag allows the user to choose if the function will check if input
``state`` is Hermitian or not. Default option is ``validate=False``, i.e. the
assumption of Hermiticity, because it is faster and, more importantly,
the functions are intended to be used on Hermitian inputs. When ``validate=True``
and ``state`` is non-Hermitian, an error will be raised when using `cupy` backend.


Entanglement entropy
""""""""""""""""""""

.. autofunction:: qibo.quantum_info.entanglement_entropy

.. note::
``validate`` flag allows the user to choose if the function will check if
the reduced density matrix resulting from tracing out ``bipartition`` from input
``state`` is Hermitian or not. Default option is ``validate=False``, i.e. the
assumption of Hermiticity, because it is faster and, more importantly,
the functions are intended to be used on Hermitian inputs. When ``validate=True``
and the reduced density matrix is non-Hermitian, an error will be raised
when using `cupy` backend.


Entanglement measures
^^^^^^^^^^^^^^^^^^^^^

Expand Down Expand Up @@ -1559,6 +1525,46 @@ Entanglement capability
.. autofunction:: qibo.quantum_info.entangling_capability


Entropy measures
^^^^^^^^^^^^^^^^

Set of functions to calculate entropy measures.


Shannon entropy
"""""""""""""""

.. autofunction:: qibo.quantum_info.shannon_entropy


Entropy
"""""""

.. autofunction:: qibo.quantum_info.entropy

.. note::
``validate`` flag allows the user to choose if the function will check if input
``state`` is Hermitian or not. Default option is ``validate=False``, i.e. the
assumption of Hermiticity, because it is faster and, more importantly,
the functions are intended to be used on Hermitian inputs. When ``validate=True``
and ``state`` is non-Hermitian, an error will be raised when using `cupy` backend.


Entanglement Entropy
""""""""""""""""""""

.. autofunction:: qibo.quantum_info.entanglement_entropy

.. note::
``validate`` flag allows the user to choose if the function will check if
the reduced density matrix resulting from tracing out ``bipartition`` from input
``state`` is Hermitian or not. Default option is ``validate=False``, i.e. the
assumption of Hermiticity, because it is faster and, more importantly,
the functions are intended to be used on Hermitian inputs. When ``validate=True``
and the reduced density matrix is non-Hermitian, an error will be raised
when using `cupy` backend.


Metrics
^^^^^^^

Expand Down Expand Up @@ -2047,12 +2053,6 @@ Hadamard Transform
.. autofunction:: qibo.quantum_info.hadamard_transform


Shannon entropy
"""""""""""""""

.. autofunction:: qibo.quantum_info.shannon_entropy


Hellinger distance
""""""""""""""""""

Expand Down
6 changes: 3 additions & 3 deletions src/qibo/quantum_info/entanglement.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,12 +90,12 @@ def entanglement_of_formation(
x = \\frac{1 + \\sqrt{1 - C^{2}(\\rho)}}{2} \\, ,
:math:`C(\\rho)` is the :func:`qibo.quantum_info.concurrence` of :math:`\\rho`,
and :math:`H` is the :func:`qibo.quantum_info.shannon_entropy`.
and :math:`H` is the :func:`qibo.quantum_info.entropies.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.shannon_entropy`.
base (float): the base of the log in :func:`qibo.quantum_info.entropies.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 @@ -110,7 +110,7 @@ def entanglement_of_formation(
if backend is None: # pragma: no cover
backend = GlobalBackend()

from qibo.quantum_info.utils import shannon_entropy # pylint: disable=C0415
from qibo.quantum_info.entropies import shannon_entropy # pylint: disable=C0415

concur = concurrence(
state, bipartition=bipartition, check_purity=check_purity, backend=backend
Expand Down
68 changes: 67 additions & 1 deletion src/qibo/quantum_info/entropies.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,76 @@
import numpy as np

from qibo.backends import GlobalBackend
from qibo.config import raise_error
from qibo.config import PRECISION_TOL, raise_error
from qibo.quantum_info.metrics import _check_hermitian_or_not_gpu, purity


def shannon_entropy(probability_array, base: float = 2, backend=None):
"""Calculate the Shannon entropy of a probability array :math:`\\mathbf{p}`, which is given by
.. math::
H(\\mathbf{p}) = - \\sum_{k = 0}^{d^{2} - 1} \\, p_{k} \\, \\log_{b}(p_{k}) \\, ,
where :math:`d = \\text{dim}(\\mathcal{H})` is the dimension of the
Hilbert space :math:`\\mathcal{H}`, :math:`b` is the log base (default 2),
and :math:`0 \\log_{b}(0) \\equiv 0`.
Args:
probability_array (ndarray or list): a probability array :math:`\\mathbf{p}`.
base (float): the base of the log. Defaults to :math:`2`.
backend (:class:`qibo.backends.abstract.Backend`, optional): backend to be used
in the execution. If ``None``, it uses :class:`qibo.backends.GlobalBackend`.
Defaults to ``None``.
Returns:
(float): Shannon entropy :math:`H(\\mathcal{p})`.
"""
if backend is None: # pragma: no cover
backend = GlobalBackend()

if isinstance(probability_array, list):
probability_array = backend.cast(probability_array, dtype=float)

if base <= 0:
raise_error(ValueError, "log base must be non-negative.")

if len(probability_array.shape) != 1:
raise_error(
TypeError,
f"Probability array must have dims (k,) but it has {probability_array.shape}.",
)

if len(probability_array) == 0:
raise_error(TypeError, "Empty array.")

if any(probability_array < 0) or any(probability_array > 1.0):
raise_error(
ValueError,
"All elements of the probability array must be between 0. and 1..",
)

if np.abs(np.sum(probability_array) - 1.0) > PRECISION_TOL:
raise_error(ValueError, "Probability array must sum to 1.")

if base == 2:
log_prob = np.where(probability_array != 0.0, np.log2(probability_array), 0.0)
elif base == 10:
log_prob = np.where(probability_array != 0, np.log10(probability_array), 0.0)
elif base == np.e:
log_prob = np.where(probability_array != 0, np.log(probability_array), 0.0)
else:
log_prob = np.where(
probability_array != 0, np.log(probability_array) / np.log(base), 0.0
)

entropy = -np.sum(probability_array * log_prob)

# absolute value if entropy == 0.0 to avoid returning -0.0
entropy = np.abs(entropy) if entropy == 0.0 else entropy

return complex(entropy).real


def entropy(
state,
base: float = 2,
Expand Down
66 changes: 0 additions & 66 deletions src/qibo/quantum_info/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,72 +127,6 @@ def hadamard_transform(array, implementation: str = "fast", backend=None):
return array


def shannon_entropy(probability_array, base: float = 2, backend=None):
"""Calculate the Shannon entropy of a probability array :math:`\\mathbf{p}`, which is given by
.. math::
H(\\mathbf{p}) = - \\sum_{k = 0}^{d^{2} - 1} \\, p_{k} \\, \\log_{b}(p_{k}) \\, ,
where :math:`d = \\text{dim}(\\mathcal{H})` is the dimension of the
Hilbert space :math:`\\mathcal{H}`, :math:`b` is the log base (default 2),
and :math:`0 \\log_{b}(0) \\equiv 0`.
Args:
probability_array (ndarray or list): a probability array :math:`\\mathbf{p}`.
base (float): the base of the log. Defaults to :math:`2`.
backend (:class:`qibo.backends.abstract.Backend`, optional): backend to be used
in the execution. If ``None``, it uses :class:`qibo.backends.GlobalBackend`.
Defaults to ``None``.
Returns:
(float): The Shannon entropy :math:`H(\\mathcal{p})`.
"""
if backend is None: # pragma: no cover
backend = GlobalBackend()

if isinstance(probability_array, list):
probability_array = backend.cast(probability_array, dtype=float)

if base <= 0:
raise_error(ValueError, "log base must be non-negative.")

if len(probability_array.shape) != 1:
raise_error(
TypeError,
f"Probability array must have dims (k,) but it has {probability_array.shape}.",
)

if len(probability_array) == 0:
raise_error(TypeError, "Empty array.")

if any(probability_array < 0) or any(probability_array > 1.0):
raise_error(
ValueError,
"All elements of the probability array must be between 0. and 1..",
)

if np.abs(np.sum(probability_array) - 1.0) > PRECISION_TOL:
raise_error(ValueError, "Probability array must sum to 1.")

if base == 2:
log_prob = np.where(probability_array != 0.0, np.log2(probability_array), 0.0)
elif base == 10:
log_prob = np.where(probability_array != 0, np.log10(probability_array), 0.0)
elif base == np.e:
log_prob = np.where(probability_array != 0, np.log(probability_array), 0.0)
else:
log_prob = np.where(
probability_array != 0, np.log(probability_array) / np.log(base), 0.0
)

entropy = -np.sum(probability_array * log_prob)

# absolute value if entropy == 0.0 to avoid returning -0.0
entropy = np.abs(entropy) if entropy == 0.0 else entropy

return complex(entropy).real


def hellinger_distance(prob_dist_p, prob_dist_q, validate: bool = False, backend=None):
"""Calculate the Hellinger distance :math:`H(p, q)` between
two discrete probability distributions, :math:`\\mathbf{p}` and :math:`\\mathbf{q}`.
Expand Down
42 changes: 41 additions & 1 deletion tests/test_quantum_info_entropies.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,50 @@
import pytest

from qibo.config import PRECISION_TOL
from qibo.quantum_info.entropies import entanglement_entropy, entropy
from qibo.quantum_info.entropies import entanglement_entropy, entropy, shannon_entropy
from qibo.quantum_info.random_ensembles import random_statevector, random_unitary


def test_shannon_entropy_errors(backend):
with pytest.raises(ValueError):
prob = np.array([1.0, 0.0])
prob = backend.cast(prob, dtype=prob.dtype)
test = shannon_entropy(prob, -2, backend=backend)
with pytest.raises(TypeError):
prob = np.array([[1.0], [0.0]])
prob = backend.cast(prob, dtype=prob.dtype)
test = shannon_entropy(prob, backend=backend)
with pytest.raises(TypeError):
prob = np.array([])
prob = backend.cast(prob, dtype=prob.dtype)
test = shannon_entropy(prob, backend=backend)
with pytest.raises(ValueError):
prob = np.array([1.0, -1.0])
prob = backend.cast(prob, dtype=prob.dtype)
test = shannon_entropy(prob, backend=backend)
with pytest.raises(ValueError):
prob = np.array([1.1, 0.0])
prob = backend.cast(prob, dtype=prob.dtype)
test = shannon_entropy(prob, backend=backend)
with pytest.raises(ValueError):
prob = np.array([0.5, 0.4999999])
prob = backend.cast(prob, dtype=prob.dtype)
test = shannon_entropy(prob, backend=backend)


@pytest.mark.parametrize("base", [2, 10, np.e, 5])
def test_shannon_entropy(backend, base):
prob_array = [1.0, 0.0]
result = shannon_entropy(prob_array, base, backend=backend)
backend.assert_allclose(result, 0.0)

if base == 2:
prob_array = np.array([0.5, 0.5])
prob_array = backend.cast(prob_array, dtype=prob_array.dtype)
result = shannon_entropy(prob_array, base, backend=backend)
backend.assert_allclose(result, 1.0)


@pytest.mark.parametrize("check_hermitian", [False, True])
@pytest.mark.parametrize("base", [2, 10, np.e, 5])
def test_entropy(backend, base, check_hermitian):
Expand Down
41 changes: 0 additions & 41 deletions tests/test_quantum_info_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
hellinger_distance,
hellinger_fidelity,
pqc_integral,
shannon_entropy,
)


Expand Down Expand Up @@ -90,46 +89,6 @@ def test_hadamard_transform(backend, nqubits, implementation, is_matrix):
backend.assert_allclose(transformed, test_transformed, atol=PRECISION_TOL)


def test_shannon_entropy_errors(backend):
with pytest.raises(ValueError):
prob = np.array([1.0, 0.0])
prob = backend.cast(prob, dtype=prob.dtype)
test = shannon_entropy(prob, -2, backend=backend)
with pytest.raises(TypeError):
prob = np.array([[1.0], [0.0]])
prob = backend.cast(prob, dtype=prob.dtype)
test = shannon_entropy(prob, backend=backend)
with pytest.raises(TypeError):
prob = np.array([])
prob = backend.cast(prob, dtype=prob.dtype)
test = shannon_entropy(prob, backend=backend)
with pytest.raises(ValueError):
prob = np.array([1.0, -1.0])
prob = backend.cast(prob, dtype=prob.dtype)
test = shannon_entropy(prob, backend=backend)
with pytest.raises(ValueError):
prob = np.array([1.1, 0.0])
prob = backend.cast(prob, dtype=prob.dtype)
test = shannon_entropy(prob, backend=backend)
with pytest.raises(ValueError):
prob = np.array([0.5, 0.4999999])
prob = backend.cast(prob, dtype=prob.dtype)
test = shannon_entropy(prob, backend=backend)


@pytest.mark.parametrize("base", [2, 10, np.e, 5])
def test_shannon_entropy(backend, base):
prob_array = [1.0, 0.0]
result = shannon_entropy(prob_array, base, backend=backend)
backend.assert_allclose(result, 0.0)

if base == 2:
prob_array = np.array([0.5, 0.5])
prob_array = backend.cast(prob_array, dtype=prob_array.dtype)
result = shannon_entropy(prob_array, base, backend=backend)
backend.assert_allclose(result, 1.0)


def test_hellinger(backend):
with pytest.raises(TypeError):
prob = np.random.rand(1, 2)
Expand Down

0 comments on commit cbeb4e6

Please sign in to comment.