From 5eb4572d524acba5837484f3c874ecb571b53cbd Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Fri, 4 Oct 2024 12:57:40 +0400 Subject: [PATCH 01/11] abstract method --- src/qibo/backends/abstract.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/qibo/backends/abstract.py b/src/qibo/backends/abstract.py index 468fa89008..4b168cf0ef 100644 --- a/src/qibo/backends/abstract.py +++ b/src/qibo/backends/abstract.py @@ -371,6 +371,11 @@ def calculate_matrix_power( """ raise_error(NotImplementedError) + @abc.abstractmethod + def calculate_singular_value_decomposition(self, matrix): # pragma: no cover + """Calculate the Singular Value Decomposition of ``matrix``.""" + raise_error(NotImplementedError) + @abc.abstractmethod def calculate_hamiltonian_matrix_product( self, matrix1, matrix2 From fda468a4325d66635ea1300363806293b8f15305 Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Fri, 4 Oct 2024 12:57:47 +0400 Subject: [PATCH 02/11] numpy method --- src/qibo/backends/numpy.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/qibo/backends/numpy.py b/src/qibo/backends/numpy.py index fe1441b7c6..e9e57ca41d 100644 --- a/src/qibo/backends/numpy.py +++ b/src/qibo/backends/numpy.py @@ -777,6 +777,9 @@ def calculate_matrix_power(self, matrix, power: Union[float, int]): ) return fractional_matrix_power(matrix, power) + def calculate_singular_value_decomposition(self, matrix): + return self.np.linalg.svd(matrix) + # TODO: remove this method def calculate_hamiltonian_matrix_product(self, matrix1, matrix2): return matrix1 @ matrix2 From 21186d33eb13f1ff0f9a651ee701c580f2ade6af Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Fri, 4 Oct 2024 12:57:52 +0400 Subject: [PATCH 03/11] tensorflow method --- src/qibo/backends/tensorflow.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/qibo/backends/tensorflow.py b/src/qibo/backends/tensorflow.py index 2088cd69be..16b8ea83b2 100644 --- a/src/qibo/backends/tensorflow.py +++ b/src/qibo/backends/tensorflow.py @@ -192,6 +192,11 @@ def calculate_matrix_exp(self, a, matrix, eigenvectors=None, eigenvalues=None): return self.tf.linalg.expm(-1j * a * matrix) return super().calculate_matrix_exp(a, matrix, eigenvectors, eigenvalues) + def calculate_singular_value_decomposition(self, matrix): + # needed to unify order of return + S, U, V = self.tf.linalg.svd(matrix) + return U, S, self.tf.conj(self.tf.transpose(V)) + def calculate_hamiltonian_matrix_product(self, matrix1, matrix2): if self.is_sparse(matrix1) or self.is_sparse(matrix2): raise_error( From 4ae669aaac799ed4255e8a0c66742d40e6ce3944 Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Fri, 4 Oct 2024 13:19:26 +0400 Subject: [PATCH 04/11] `linalg_operations` --- src/qibo/quantum_info/linalg_operations.py | 26 ++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/qibo/quantum_info/linalg_operations.py b/src/qibo/quantum_info/linalg_operations.py index 6fc48e6e74..02a9ee1395 100644 --- a/src/qibo/quantum_info/linalg_operations.py +++ b/src/qibo/quantum_info/linalg_operations.py @@ -214,3 +214,29 @@ def matrix_power(matrix, power: Union[float, int], backend=None): backend = _check_backend(backend) return backend.calculate_matrix_power(matrix, power) + + +def singular_value_decomposition(matrix, backend=None): + """Calculate the Singular Value Decomposition (SVD) of ``matrix``. + + Given an :math:`M \\times N` complex matrix :math:`A`, its SVD is given by + + .. math: + A = U \\, S \\, V^{\\dagger} \\, , + + where :math:`U` and :math:`V` are, respectively, an :math:`M \\times M` + and an :math:`N \\times N` complex unitary matrices, and :math:`S` is an + :math:`M \\times N` diagonal matrix with the singular values of :math:`A`. + + Args: + matrix (ndarray): matrix whose SVD to calculate. + 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: + ndarray, ndarray, ndarray: Singular value decomposition of :math:`A`. + """ + backend = _check_backend(backend) + + return backend.calculate_singular_value_decomposition(matrix) From 0607ac20fbac4600538e4ed8a5b275bce1f2132f Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Fri, 4 Oct 2024 13:19:36 +0400 Subject: [PATCH 05/11] replacement --- src/qibo/quantum_info/superoperator_transformations.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/qibo/quantum_info/superoperator_transformations.py b/src/qibo/quantum_info/superoperator_transformations.py index 158dd73ac4..18a570af8e 100644 --- a/src/qibo/quantum_info/superoperator_transformations.py +++ b/src/qibo/quantum_info/superoperator_transformations.py @@ -11,6 +11,7 @@ from qibo.gates.abstract import Gate from qibo.gates.gates import Unitary from qibo.gates.special import FusedGate +from qibo.quantum_info.linalg_operations import singular_value_decomposition def vectorization(state, order: str = "row", backend=None): @@ -483,7 +484,7 @@ def choi_to_kraus( warnings.warn("Input choi_super_op is a non-completely positive map.") # using singular value decomposition because choi_super_op is non-CP - U, coefficients, V = np.linalg.svd(backend.to_numpy(choi_super_op)) + U, coefficients, V = singular_value_decomposition(choi_super_op) U = np.transpose(U) coefficients = np.sqrt(coefficients) V = np.conj(V) From 5763341fc55d3c4a001d47d90b16c2df4425cfe4 Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Fri, 4 Oct 2024 13:20:48 +0400 Subject: [PATCH 06/11] pylance --- src/qibo/backends/tensorflow.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/qibo/backends/tensorflow.py b/src/qibo/backends/tensorflow.py index 16b8ea83b2..a21a998b6a 100644 --- a/src/qibo/backends/tensorflow.py +++ b/src/qibo/backends/tensorflow.py @@ -15,7 +15,7 @@ class TensorflowMatrices(NumpyMatrices): def __init__(self, dtype): super().__init__(dtype) import tensorflow as tf # pylint: disable=import-error - import tensorflow.experimental.numpy as tnp # pylint: disable=import-error + import tensorflow.experimental.numpy as tnp # pylint: disable=import-error # type: ignore self.tf = tf self.np = tnp @@ -35,7 +35,7 @@ def __init__(self): os.environ["TF_CPP_MIN_LOG_LEVEL"] = str(TF_LOG_LEVEL) import tensorflow as tf # pylint: disable=import-error - import tensorflow.experimental.numpy as tnp # pylint: disable=import-error + import tensorflow.experimental.numpy as tnp # pylint: disable=import-error # type: ignore if TF_LOG_LEVEL >= 2: tf.get_logger().setLevel("ERROR") From 1bcb1268f3be24d08ed38409d60c4219e1e4332f Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Fri, 4 Oct 2024 13:40:53 +0400 Subject: [PATCH 07/11] fix bug in tensorflow --- src/qibo/backends/tensorflow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qibo/backends/tensorflow.py b/src/qibo/backends/tensorflow.py index a21a998b6a..13a4a5bd25 100644 --- a/src/qibo/backends/tensorflow.py +++ b/src/qibo/backends/tensorflow.py @@ -195,7 +195,7 @@ def calculate_matrix_exp(self, a, matrix, eigenvectors=None, eigenvalues=None): def calculate_singular_value_decomposition(self, matrix): # needed to unify order of return S, U, V = self.tf.linalg.svd(matrix) - return U, S, self.tf.conj(self.tf.transpose(V)) + return U, S, self.np.conj(self.np.transpose(V)) def calculate_hamiltonian_matrix_product(self, matrix1, matrix2): if self.is_sparse(matrix1) or self.is_sparse(matrix2): From fea1b5de33610887c5d70b5369476907fcd45e1e Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Fri, 4 Oct 2024 14:24:38 +0400 Subject: [PATCH 08/11] test --- tests/test_quantum_info_operations.py | 32 +++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tests/test_quantum_info_operations.py b/tests/test_quantum_info_operations.py index 3d97f1f63a..dbb5ba5efb 100644 --- a/tests/test_quantum_info_operations.py +++ b/tests/test_quantum_info_operations.py @@ -7,6 +7,7 @@ commutator, matrix_power, partial_trace, + singular_value_decomposition, ) from qibo.quantum_info.metrics import purity from qibo.quantum_info.random_ensembles import random_density_matrix, random_statevector @@ -130,3 +131,34 @@ def test_matrix_power(backend, power): float(backend.np.real(backend.np.trace(power))), purity(state, backend=backend), ) + + +def test_singular_value_decomposition(backend): + zero = np.array([1, 0], dtype=complex) + one = np.array([0, 1], dtype=complex) + plus = (zero + one) / np.sqrt(2) + minus = (zero - one) / np.sqrt(2) + plus = backend.cast(plus, dtype=plus.dtype) + minus = backend.cast(minus, dtype=minus.dtype) + base = [plus, minus] + + coeffs = np.random.rand(4) + coeffs /= np.sum(coeffs) + coeffs = backend.cast(coeffs, dtype=coeffs.dtype) + + state = np.zeros((4, 4), dtype=complex) + state = backend.cast(state, dtype=state.dtype) + for k, coeff in enumerate(coeffs): + bitstring = f"{k:0{2}b}" + a, b = int(bitstring[0]), int(bitstring[1]) + ket = backend.np.kron(base[a], base[b]) + state = state + coeff * backend.np.outer(ket, ket.T) + + _, S, _ = singular_value_decomposition(state, backend=backend) + + S_sorted = backend.np.sort(S) + coeffs_sorted = backend.np.sort(coeffs) + if backend.name == "pytorch": + S_sorted, coeffs_sorted = S_sorted[0], coeffs_sorted[0] + + backend.assert_allclose(S_sorted, coeffs_sorted) From 0e5262cb4aa013fce41b51ca3191c18e2f79ca26 Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Fri, 4 Oct 2024 14:48:46 +0400 Subject: [PATCH 09/11] fix tests --- src/qibo/backends/pytorch.py | 4 ++-- .../quantum_info/superoperator_transformations.py | 10 ++++++---- ...st_quantum_info_superoperator_transformations.py | 13 +++++++++++-- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/qibo/backends/pytorch.py b/src/qibo/backends/pytorch.py index 22fbbc5640..1a8051ab4e 100644 --- a/src/qibo/backends/pytorch.py +++ b/src/qibo/backends/pytorch.py @@ -16,7 +16,7 @@ class TorchMatrices(NumpyMatrices): """ def __init__(self, dtype, requires_grad): - import torch # pylint: disable=import-outside-toplevel + import torch # pylint: disable=import-outside-toplevel # type: ignore super().__init__(dtype) self.np = torch @@ -38,7 +38,7 @@ def Unitary(self, u): class PyTorchBackend(NumpyBackend): def __init__(self): super().__init__() - import torch # pylint: disable=import-outside-toplevel + import torch # pylint: disable=import-outside-toplevel # type: ignore # Global variable to enable or disable gradient calculation self.gradients = True diff --git a/src/qibo/quantum_info/superoperator_transformations.py b/src/qibo/quantum_info/superoperator_transformations.py index 18a570af8e..7d74a0ef76 100644 --- a/src/qibo/quantum_info/superoperator_transformations.py +++ b/src/qibo/quantum_info/superoperator_transformations.py @@ -484,10 +484,12 @@ def choi_to_kraus( warnings.warn("Input choi_super_op is a non-completely positive map.") # using singular value decomposition because choi_super_op is non-CP - U, coefficients, V = singular_value_decomposition(choi_super_op) - U = np.transpose(U) - coefficients = np.sqrt(coefficients) - V = np.conj(V) + U, coefficients, V = singular_value_decomposition( + choi_super_op, backend=backend + ) + U = U.T + coefficients = backend.np.sqrt(coefficients) + V = backend.np.conj(V) kraus_left, kraus_right = [], [] for coeff, eigenvector_left, eigenvector_right in zip(coefficients, U, V): diff --git a/tests/test_quantum_info_superoperator_transformations.py b/tests/test_quantum_info_superoperator_transformations.py index d93db2e5e6..8ac14aa1fd 100644 --- a/tests/test_quantum_info_superoperator_transformations.py +++ b/tests/test_quantum_info_superoperator_transformations.py @@ -1,5 +1,5 @@ import numpy as np -import pytest +import pytest # type: ignore from qibo import matrices from qibo.config import PRECISION_TOL @@ -377,6 +377,7 @@ def test_choi_to_pauli(backend, normalize, order, pauli_order, test_superop): backend.assert_allclose(test_pauli, pauli_op, atol=PRECISION_TOL) +@pytest.mark.parametrize("test_non_CP", [test_non_CP]) @pytest.mark.parametrize("test_kraus_right", [test_kraus_right]) @pytest.mark.parametrize("test_kraus_left", [test_kraus_left]) @pytest.mark.parametrize("test_a1", [test_a1]) @@ -384,7 +385,14 @@ def test_choi_to_pauli(backend, normalize, order, pauli_order, test_superop): @pytest.mark.parametrize("validate_cp", [False, True]) @pytest.mark.parametrize("order", ["row", "column"]) def test_choi_to_kraus( - backend, order, validate_cp, test_a0, test_a1, test_kraus_left, test_kraus_right + backend, + order, + validate_cp, + test_a0, + test_a1, + test_kraus_left, + test_kraus_right, + test_non_CP, ): axes = [1, 2] if order == "row" else [0, 3] test_choi = backend.cast( @@ -425,6 +433,7 @@ def test_choi_to_kraus( backend.assert_allclose(evolution_a1, test_evolution_a1, atol=2 * PRECISION_TOL) if validate_cp and order == "row": + test_non_CP = backend.cast(test_non_CP, dtype=test_non_CP.dtype) (kraus_left, kraus_right), _ = choi_to_kraus( test_non_CP, order=order, validate_cp=validate_cp, backend=backend ) From 821fa85b43c9be2a814115c8441972e9f2dbba26 Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Mon, 7 Oct 2024 10:01:59 +0400 Subject: [PATCH 10/11] api ref --- doc/source/api-reference/qibo.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/doc/source/api-reference/qibo.rst b/doc/source/api-reference/qibo.rst index e21f0cc19f..869f21d042 100644 --- a/doc/source/api-reference/qibo.rst +++ b/doc/source/api-reference/qibo.rst @@ -1975,6 +1975,12 @@ Matrix power .. autofunction:: qibo.quantum_info.matrix_power +Singular value decomposition +"""""""""""""""""""""""""""" + +.. autofunction:: qibo.quantum_info.singular_value_decomposition + + Quantum Networks ^^^^^^^^^^^^^^^^ From e6e22e4b556ca3eda214249f6dead9636f63fd3d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 8 Oct 2024 00:58:03 +0000 Subject: [PATCH 11/11] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/pre-commit-hooks: v4.6.0 → v5.0.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.6.0...v5.0.0) - [github.com/psf/black: 24.8.0 → 24.10.0](https://github.com/psf/black/compare/24.8.0...24.10.0) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6e046cb240..93da216d44 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,14 +2,14 @@ ci: autofix_prs: true repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.6.0 + rev: v5.0.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer - id: check-yaml - id: debug-statements - repo: https://github.com/psf/black - rev: 24.8.0 + rev: 24.10.0 hooks: - id: black - repo: https://github.com/pycqa/isort