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

Add bures angle #181

Merged
merged 2 commits into from
Jun 25, 2023
Merged
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
6 changes: 6 additions & 0 deletions docs/_autosummary/toqito.state_metrics.bures_angle.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
toqito.state\_metrics.bures\_angle
=====================================

.. currentmodule:: toqito.state_metrics

.. autofunction:: bures_angle
1 change: 1 addition & 0 deletions docs/states.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ Distance Metrics for Quantum States
toqito.state_metrics.sub_fidelity
toqito.state_metrics.trace_distance
toqito.state_metrics.bures_distance
toqito.state_metrics.bures_angle
toqito.state_metrics.matsumoto_fidelity

Optimizations over Quantum States
Expand Down
66 changes: 66 additions & 0 deletions tests/test_state_metrics/test_bures_angle.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
"""Tests for bures_angle."""
import numpy as np


from toqito.state_metrics import bures_angle
from toqito.states import basis


def test_bures_angle_default():
"""Test bures_angle default arguments."""
rho = np.array([[1 / 2, 0, 0, 1 / 2], [0, 0, 0, 0], [0, 0, 0, 0], [1 / 2, 0, 0, 1 / 2]])
sigma = rho

ang = bures_angle(rho, sigma)
np.testing.assert_equal(np.isclose(ang, 0), True)


def test_bures_angle_non_identical_states_1():
"""Test the bures_angle between two non-identical states."""
e_0, e_1 = basis(2, 0), basis(2, 1)
rho = 3 / 4 * e_0 * e_0.conj().T + 1 / 4 * e_1 * e_1.conj().T
sigma = 2 / 3 * e_0 * e_0.conj().T + 1 / 3 * e_1 * e_1.conj().T

ang = bures_angle(rho, sigma)
np.testing.assert_equal(np.isclose(ang, 0.06499, rtol=1e-03), True)


def test_bures_angle_non_identical_states_2():
"""Test the bures_angle between two non-identical states."""
e_0, e_1 = basis(2, 0), basis(2, 1)
rho = 3 / 4 * e_0 * e_0.conj().T + 1 / 4 * e_1 * e_1.conj().T
sigma = 1 / 8 * e_0 * e_0.conj().T + 7 / 8 * e_1 * e_1.conj().T

ang = bures_angle(rho, sigma)
np.testing.assert_equal(np.isclose(ang, 0.4955, rtol=1e-03), True)


def test_bures_angle_pure_states():
"""Test the bures_angle between two pure states."""
e_0, e_1 = basis(2, 0), basis(2, 1)
e_plus = (e_0 + e_1) / np.sqrt(2)
rho = e_plus * e_plus.conj().T
sigma = e_0 * e_0.conj().T

ang = bures_angle(rho, sigma)
np.testing.assert_equal(np.isclose(ang, 0.5718, rtol=1e-03), True)


def test_bures_angle_non_square():
"""Tests for invalid dim."""
rho = np.array([[1 / 2, 0, 0, 1 / 2], [0, 0, 0, 0], [1 / 2, 0, 0, 1 / 2]])
sigma = np.array([[1 / 2, 0, 0, 1 / 2], [0, 0, 0, 0], [1 / 2, 0, 0, 1 / 2]])
with np.testing.assert_raises(ValueError):
bures_angle(rho, sigma)


def test_bures_angle_invalid_dim():
"""Tests for invalid dim."""
rho = np.array([[1 / 2, 0, 0, 1 / 2], [0, 0, 0, 0], [0, 0, 0, 0], [1 / 2, 0, 0, 1 / 2]])
sigma = np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]])
with np.testing.assert_raises(ValueError):
bures_angle(rho, sigma)


if __name__ == "__main__":
np.testing.run_module_suite()
1 change: 1 addition & 0 deletions toqito/state_metrics/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@
from toqito.state_metrics.sub_fidelity import sub_fidelity
from toqito.state_metrics.trace_distance import trace_distance
from toqito.state_metrics.bures_distance import bures_distance
from toqito.state_metrics.bures_angle import bures_angle
from toqito.state_metrics.matsumoto_fidelity import matsumoto_fidelity
70 changes: 70 additions & 0 deletions toqito/state_metrics/bures_angle.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
"""Bures angle metric."""
import numpy as np

from toqito.state_metrics import fidelity


def bures_angle(rho_1: np.ndarray, rho_2: np.ndarray, decimals: int = 10) -> float:
r"""
Compute the Bures angle of two density matrices [WikBures]_.

Calculate the Bures angle between two density matrices :code:`rho_1` and :code:`rho_2`
defined by:

.. math::
\arccos{\sqrt{F (\rho_1, \rho_2)}}

where :math:`F(\cdot)` denotes the fidelity between :math:`\rho_1` and :math:`\rho_2`. The
return is a value between :math:`0` and :math:`\pi / 2`, with :math:`0` corresponding to
matrices :code:`rho_1 = rho_2` and :math:`\pi / 2` corresponding to the case :code: `rho_1`
and :code:`rho_2` with orthogonal support.

Examples
==========

Consider the following Bell state

.. math::
u = \frac{1}{\sqrt{2}} \left( |00 \rangle + |11 \rangle \right) \in \mathcal{X}.

The corresponding density matrix of :math:`u` may be calculated by:

.. math::
\rho = u u^* = \frac{1}{2} \begin{pmatrix}
1 & 0 & 0 & 1 \\
0 & 0 & 0 & 0 \\
0 & 0 & 0 & 0 \\
1 & 0 & 0 & 1
\end{pmatrix} \in \text{D}(\mathcal{X}).

In the event where we calculate the Bures angle between states that are identical, we
should obtain the value of :math:`0`. This can be observed in :code:`toqito` as follows.

>>> from toqito.state_metrics import bures_angle
>>> import numpy as np
>>> rho = 1 / 2 * np.array(
>>> [[1, 0, 0, 1],
>>> [0, 0, 0, 0],
>>> [0, 0, 0, 0],
>>> [1, 0, 0, 1]]
>>> )
>>> sigma = rho
>>> bures_angle(rho, sigma)
0

References
==========
.. [WikBures] Wikipedia: Bures distance
https://en.wikipedia.org/wiki/Bures_metric#Bures_distance

:raises ValueError: If matrices are not of equal dimension.
:param rho_1: Density operator.
:param rho_2: Density operator.
:param decimals: Number of decimal places to round to (default 10).
:return: The Bures angle between :code:`rho_1` and :code:`rho_2`.
"""
# Perform error checking
if not np.all(rho_1.shape == rho_2.shape):
raise ValueError("InvalidDim: `rho_1` and `rho_2` must be matrices of the same size.")
# Round fidelity to only 10 decimals to avoid error when :code:`rho_1 = rho_2`.
return np.real(np.arccos(np.sqrt(np.round(fidelity(rho_1, rho_2), decimals))))