Skip to content

Commit 14b62c4

Browse files
authored
Merge pull request #1684 from qiboteam/metrics
Add `Backend.calculate_matrix_log` and `Backend.calculate_matrix_sqrt` + refactor `Backend.calculate_matrix_exp` + add `quantum_info.matrix_logarithm` and `quantum_info.matrix_sqrt`
2 parents 954e388 + f67a735 commit 14b62c4

File tree

14 files changed

+277
-416
lines changed

14 files changed

+277
-416
lines changed

doc/source/api-reference/qibo.rst

Lines changed: 12 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1814,27 +1814,12 @@ von Neumann entropy
18141814

18151815
.. autofunction:: qibo.quantum_info.von_neumann_entropy
18161816

1817-
.. note::
1818-
``check_hermitian`` flag allows the user to choose if the function will check if input
1819-
``state`` is Hermitian or not. Default option is ``check_hermitian=False``, i.e. the
1820-
assumption of Hermiticity. This is faster and, more importantly,
1821-
this function are intended to be used on Hermitian inputs. When ``check_hermitian=True``
1822-
and ``state`` is non-Hermitian, an error will be raised when using `cupy` backend.
1823-
18241817

18251818
Relative von Neumann entropy
18261819
""""""""""""""""""""""""""""
18271820

18281821
.. autofunction:: qibo.quantum_info.relative_von_neumann_entropy
18291822

1830-
.. note::
1831-
``check_hermitian`` flag allows the user to choose if the function will check if input
1832-
``state`` is Hermitian or not. Default option is ``check_hermitian=False``, i.e. the
1833-
assumption of Hermiticity. This is faster and, more importantly,
1834-
this function are intended to be used on Hermitian inputs. When ``check_hermitian=True``
1835-
and either ``state`` or ``target`` is non-Hermitian,
1836-
an error will be raised when using `cupy` backend.
1837-
18381823

18391824
Mutual information
18401825
""""""""""""""""""
@@ -1871,15 +1856,6 @@ Entanglement entropy
18711856

18721857
.. autofunction:: qibo.quantum_info.entanglement_entropy
18731858

1874-
.. note::
1875-
``check_hermitian`` flag allows the user to choose if the function will check if
1876-
the reduced density matrix resulting from tracing out ``bipartition`` from input
1877-
``state`` is Hermitian or not. Default option is ``check_hermitian=False``, i.e. the
1878-
assumption of Hermiticity. This is faster and, more importantly,
1879-
this function are intended to be used on Hermitian inputs. When ``check_hermitian=True``
1880-
and the reduced density matrix is non-Hermitian, an error will be raised
1881-
when using `cupy` backend.
1882-
18831859

18841860
Metrics
18851861
^^^^^^^
@@ -1904,14 +1880,6 @@ Trace distance
19041880

19051881
.. autofunction:: qibo.quantum_info.trace_distance
19061882

1907-
.. note::
1908-
``check_hermitian`` flag allows the user to choose if the function will check if difference
1909-
between inputs, ``state - target``, is Hermitian or not. Default option is
1910-
``check_hermitian=False``, i.e. the assumption of Hermiticity, because it is faster and,
1911-
more importantly, the functions are intended to be used on Hermitian inputs.
1912-
When ``check_hermitian=True`` and ``state - target`` is non-Hermitian, an error will be
1913-
raised when using `cupy` backend.
1914-
19151883

19161884
Hilbert-Schmidt inner product
19171885
"""""""""""""""""""""""""""""
@@ -2033,12 +2001,24 @@ Matrix exponentiation
20332001
.. autofunction:: qibo.quantum_info.matrix_exponentiation
20342002

20352003

2004+
Matrix logarithm
2005+
""""""""""""""""
2006+
2007+
.. autofunction:: qibo.quantum_info.matrix_logarithm
2008+
2009+
20362010
Matrix power
20372011
""""""""""""
20382012

20392013
.. autofunction:: qibo.quantum_info.matrix_power
20402014

20412015

2016+
Matrix square root
2017+
""""""""""""""""""
2018+
2019+
.. autofunction:: qibo.quantum_info.matrix_sqrt
2020+
2021+
20422022
Singular value decomposition
20432023
""""""""""""""""""""""""""""
20442024

src/qibo/backends/abstract.py

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -418,9 +418,27 @@ def calculate_expectation_density_matrix(
418418

419419
@abc.abstractmethod
420420
def calculate_matrix_exp(
421-
self, a, matrix, eigenvectors=None, eigenvalues=None
421+
self,
422+
matrix,
423+
phase: Union[float, int, complex] = 1,
424+
eigenvectors=None,
425+
eigenvalues=None,
426+
): # pragma: no cover
427+
"""Calculate the exponential :math:`e^{\\theta \\, A}` of a matrix :math:`A`
428+
and ``phase`` :math:`\\theta`.
429+
430+
If the eigenvectors and eigenvalues are given the matrix diagonalization is
431+
used for exponentiation.
432+
"""
433+
raise_error(NotImplementedError)
434+
435+
@abc.abstractmethod
436+
def calculate_matrix_log(
437+
self, matrix, base: Union[float, int] = 2, eigenvectors=None, eigenvalues=None
422438
): # pragma: no cover
423-
"""Calculate matrix exponential of a matrix.
439+
"""Calculate the logarithm :math:`\\log_{b}(A)` with a ``base`` :math:`b`
440+
of a matrix :math:`A`.
441+
424442
If the eigenvectors and eigenvalues are given the matrix diagonalization is
425443
used for exponentiation.
426444
"""
@@ -441,6 +459,19 @@ def calculate_matrix_power(
441459
"""
442460
raise_error(NotImplementedError)
443461

462+
@abc.abstractmethod
463+
def calculate_matrix_sqrt(
464+
self, matrix, precision_singularity: float = 1e-14
465+
): # pragma: no cover
466+
"""Calculate the square root of ``matrix`` :math:`A`, i.e. :math:`A^{1/2}`.
467+
468+
.. note::
469+
For the ``pytorch`` backend, this method relies on a copy of the original tensor.
470+
This may break the gradient flow. For the GPU backends (i.e. ``cupy`` and
471+
``cuquantum``), this method falls back to CPU.
472+
"""
473+
raise_error(NotImplementedError)
474+
444475
@abc.abstractmethod
445476
def calculate_singular_value_decomposition(self, matrix): # pragma: no cover
446477
"""Calculate the Singular Value Decomposition of ``matrix``."""

src/qibo/backends/numpy.py

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import numpy as np
88
from scipy import sparse
9-
from scipy.linalg import block_diag, fractional_matrix_power
9+
from scipy.linalg import block_diag, fractional_matrix_power, logm
1010

1111
from qibo import __version__
1212
from qibo.backends import einsum_utils
@@ -793,16 +793,39 @@ def calculate_expectation_density_matrix(self, hamiltonian, state, normalize):
793793
ev /= norm
794794
return ev
795795

796-
def calculate_matrix_exp(self, a, matrix, eigenvectors=None, eigenvalues=None):
796+
def calculate_matrix_exp(
797+
self,
798+
matrix,
799+
phase: Union[float, int, complex] = 1,
800+
eigenvectors=None,
801+
eigenvalues=None,
802+
):
797803
if eigenvectors is None or self.is_sparse(matrix):
798804
if self.is_sparse(matrix):
799805
from scipy.sparse.linalg import expm
800806
else:
801807
from scipy.linalg import expm
802-
return expm(-1j * a * matrix)
803-
expd = self.np.diag(self.np.exp(-1j * a * eigenvalues))
808+
809+
return expm(phase * matrix)
810+
811+
expd = self.np.exp(phase * eigenvalues)
812+
ud = self.np.transpose(np.conj(eigenvectors))
813+
814+
return (eigenvectors * expd) @ ud
815+
816+
def calculate_matrix_log(self, matrix, base=2, eigenvectors=None, eigenvalues=None):
817+
if eigenvectors is None:
818+
# to_numpy and cast needed for GPUs
819+
matrix_log = logm(self.to_numpy(matrix)) / float(np.log(base))
820+
matrix_log = self.cast(matrix_log, dtype=matrix_log.dtype)
821+
822+
return matrix_log
823+
824+
# log = self.np.diag(self.np.log(eigenvalues) / float(np.log(base)))
825+
log = self.np.log(eigenvalues) / float(np.log(base))
804826
ud = self.np.transpose(np.conj(eigenvectors))
805-
return self.np.matmul(eigenvectors, self.np.matmul(expd, ud))
827+
828+
return (eigenvectors * log) @ ud
806829

807830
def calculate_matrix_power(
808831
self,
@@ -826,6 +849,9 @@ def calculate_matrix_power(
826849

827850
return fractional_matrix_power(matrix, power)
828851

852+
def calculate_matrix_sqrt(self, matrix):
853+
return self.calculate_matrix_power(matrix, power=0.5)
854+
829855
def calculate_singular_value_decomposition(self, matrix):
830856
return self.np.linalg.svd(matrix)
831857

src/qibo/callbacks.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,13 +101,11 @@ def __init__(
101101
partition: Optional[List[int]] = None,
102102
compute_spectrum: bool = False,
103103
base: float = 2,
104-
check_hermitian: bool = False,
105104
):
106105
super().__init__()
107106
self.partition = partition
108107
self.compute_spectrum = compute_spectrum
109108
self.base = base
110-
self.check_hermitian = check_hermitian
111109
self.spectrum = list()
112110

113111
@Callback.nqubits.setter
@@ -132,7 +130,6 @@ def apply(self, backend, state):
132130
state,
133131
bipartition=self.partition,
134132
base=self.base,
135-
check_hermitian=self.check_hermitian,
136133
return_spectrum=True,
137134
backend=backend,
138135
)

src/qibo/hamiltonians/hamiltonians.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,11 @@ def exp(self, a):
9292
if self._exp.get("a") != a:
9393
self._exp["a"] = a
9494
self._exp["result"] = matrix_exponentiation(
95-
a, self.matrix, self._eigenvectors, self._eigenvalues, self.backend
95+
self.matrix,
96+
-1j * a,
97+
self._eigenvectors,
98+
self._eigenvalues,
99+
self.backend,
96100
)
97101
return self._exp.get("result")
98102

src/qibo/hamiltonians/terms.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ def gate(self):
6262

6363
def exp(self, x):
6464
"""Matrix exponentiation of the term."""
65-
return self.backend.calculate_matrix_exp(x, self.matrix)
65+
return self.backend.calculate_matrix_exp(self.matrix, phase=-1j * x)
6666

6767
def expgate(self, x):
6868
""":class:`qibo.gates.gates.Unitary` gate implementing the action of exp(term) on states."""

src/qibo/quantum_info/entanglement.py

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -153,9 +153,7 @@ def negativity(state, bipartition, backend=None):
153153
return backend.np.real((norm - 1) / 2)
154154

155155

156-
def entanglement_fidelity(
157-
channel, nqubits: int, state=None, check_hermitian: bool = False, backend=None
158-
):
156+
def entanglement_fidelity(channel, nqubits: int, state=None, backend=None):
159157
"""Entanglement fidelity :math:`F_{\\mathcal{E}}` of a ``channel`` :math:`\\mathcal{E}`
160158
on ``state`` :math:`\\rho` is given by
161159
@@ -175,9 +173,6 @@ def entanglement_fidelity(
175173
by ``channel``. If ``None``, defaults to the maximally entangled state
176174
:math:`\\frac{1}{2^{n}} \\, \\sum_{k} \\, \\ket{k}\\ket{k}`, where
177175
:math:`n` is ``nqubits``. Defaults to ``None``.
178-
check_hermitian (bool, optional): if ``True``, checks if the final state
179-
:math:`\\rho_{f}` is Hermitian. If ``False``, it assumes it is Hermitian.
180-
Defaults to ``False``.
181176
backend (:class:`qibo.backends.abstract.Backend`, optional): backend to be used
182177
in the execution. If ``None``, it uses the current backend.
183178
Defaults to ``None``.
@@ -205,12 +200,6 @@ def entanglement_fidelity(
205200
f"state must have dims either (k,) or (k,k), but have dims {state.shape}.",
206201
)
207202

208-
if not isinstance(check_hermitian, bool):
209-
raise_error(
210-
TypeError,
211-
f"check_hermitian must be type bool, but it is type {type(check_hermitian)}.",
212-
)
213-
214203
backend = _check_backend(backend)
215204

216205
if state is None:
@@ -223,9 +212,7 @@ def entanglement_fidelity(
223212

224213
state_final = backend.apply_channel_density_matrix(channel, state, nqubits)
225214

226-
entang_fidelity = fidelity(
227-
state_final, state, check_hermitian=check_hermitian, backend=backend
228-
)
215+
entang_fidelity = fidelity(state_final, state, backend=backend)
229216

230217
return entang_fidelity
231218

0 commit comments

Comments
 (0)