diff --git a/doc/source/api-reference/qibo.rst b/doc/source/api-reference/qibo.rst index f0ebc0f7ff..60d852db5f 100644 --- a/doc/source/api-reference/qibo.rst +++ b/doc/source/api-reference/qibo.rst @@ -655,7 +655,7 @@ Parametric ZX interaction (RZX) :member-order: bysource Parametric XX-YY interaction (RXXYY) -"""""""""""""""""""""""""""""""""" +"""""""""""""""""""""""""""""""""""" .. autoclass:: qibo.gates.RXXYY :members: diff --git a/src/qibo/backends/abstract.py b/src/qibo/backends/abstract.py index 14d1dc752c..fe7d09195c 100644 --- a/src/qibo/backends/abstract.py +++ b/src/qibo/backends/abstract.py @@ -326,13 +326,29 @@ def entanglement_entropy(self, rho): # pragma: no cover raise_error(NotImplementedError) @abc.abstractmethod - def calculate_norm(self, state): # pragma: no cover - """Calculate norm of a state vector.""" + def calculate_norm(self, state, order=2): # pragma: no cover + """Calculate norm of a state vector. Default is :math:`2`-norm. + + For specifications on possible values of the parameter ``order`` + for the ``tensorflow`` backend, please refer to + `tensorflow.norm `_. + For all other backends, please refer to + `numpy.linalg.norm `_. + """ raise_error(NotImplementedError) @abc.abstractmethod - def calculate_norm_density_matrix(self, state): # pragma: no cover - """Calculate norm (trace) of a density matrix.""" + def calculate_norm_density_matrix(self, state, order="nuc"): # pragma: no cover + """Calculate norm of a density matrix. Default is the ``nuclear`` norm. + + If ``order="nuc"``, it returns the nuclear norm of ``state``, + assuming ``state`` is Hermitian (also known as trace norm). + For specifications on the other possible values of the + parameter ``order`` for the ``tensorflow`` backend, please refer to + `tensorflow.norm `_. + For all other backends, please refer to + `numpy.linalg.norm `_. + """ raise_error(NotImplementedError) @abc.abstractmethod diff --git a/src/qibo/backends/numpy.py b/src/qibo/backends/numpy.py index b0447bd56e..dc48d71348 100644 --- a/src/qibo/backends/numpy.py +++ b/src/qibo/backends/numpy.py @@ -673,13 +673,13 @@ def entanglement_entropy(self, rho): entropy = self.np.sum(masked_eigvals * spectrum) / self.np.log(2.0) return entropy, spectrum - def calculate_norm(self, state): + def calculate_norm(self, state, order=2): state = self.cast(state) - return self.np.sqrt(self.np.sum(self.np.abs(state) ** 2)) + return self.np.linalg.norm(state, ord=order) - def calculate_norm_density_matrix(self, state): + def calculate_norm_density_matrix(self, state, order="nuc"): state = self.cast(state) - return self.np.trace(state) + return self.np.linalg.norm(state, ord=order) def calculate_overlap(self, state1, state2): state1 = self.cast(state1) diff --git a/src/qibo/backends/tensorflow.py b/src/qibo/backends/tensorflow.py index 9f799c7c69..a4811fbc46 100644 --- a/src/qibo/backends/tensorflow.py +++ b/src/qibo/backends/tensorflow.py @@ -306,6 +306,16 @@ def entanglement_entropy(self, rho): entropy = self.np.sum(masked_eigvals * spectrum) / self.np.log(2.0) return entropy, spectrum + def calculate_norm(self, state, order=2): + state = self.cast(state) + return self.tf.norm(state, ord=order) + + def calculate_norm_density_matrix(self, state, order="nuc"): + state = self.cast(state) + if order == "nuc": + return self.np.trace(state) + return self.tf.norm(state, ord=order) + def calculate_eigenvalues(self, matrix, k=6): return self.tf.linalg.eigvalsh(matrix) diff --git a/src/qibo/quantum_info/metrics.py b/src/qibo/quantum_info/metrics.py index 88e479ef0c..65a11d7c44 100644 --- a/src/qibo/quantum_info/metrics.py +++ b/src/qibo/quantum_info/metrics.py @@ -359,7 +359,11 @@ def trace_distance(state, target, check_hermitian: bool = False, backend=None): difference = state - target if check_hermitian is True: hermitian = bool( - backend.calculate_norm(np.transpose(np.conj(difference)) - difference) + float( + backend.calculate_norm_density_matrix( + np.transpose(np.conj(difference)) - difference, order=2 + ) + ) <= PRECISION_TOL ) if ( @@ -724,14 +728,18 @@ def process_fidelity(channel, target=None, check_unitary: bool = False, backend= dim = int(np.sqrt(channel.shape[0])) if check_unitary is True: - norm_channel = backend.calculate_norm( - np.dot(np.conj(np.transpose(channel)), channel) - np.eye(dim**2) + norm_channel = float( + backend.calculate_norm_density_matrix( + np.dot(np.conj(np.transpose(channel)), channel) - np.eye(dim**2) + ) ) if target is None and norm_channel > PRECISION_TOL: raise_error(TypeError, "Channel is not unitary and Target is None.") if target is not None: - norm_target = backend.calculate_norm( - np.dot(np.conj(np.transpose(target)), target) - np.eye(dim**2) + norm_target = float( + backend.calculate_norm( + np.dot(np.conj(np.transpose(target)), target) - np.eye(dim**2) + ) ) if (norm_channel > PRECISION_TOL) and (norm_target > PRECISION_TOL): raise_error(TypeError, "Neither channel is unitary.") @@ -1131,10 +1139,12 @@ def _check_hermitian_or_not_gpu(matrix, backend=None): if backend is None: # pragma: no cover backend = GlobalBackend() - hermitian = bool( - backend.calculate_norm(np.transpose(np.conj(matrix)) - matrix) < PRECISION_TOL + norm = backend.calculate_norm_density_matrix( + np.transpose(np.conj(matrix)) - matrix, order=2 ) + hermitian = bool(float(norm) <= PRECISION_TOL) + if hermitian is False and backend.__class__.__name__ in [ "CupyBackend", "CuQuantumBackend", diff --git a/src/qibo/quantum_info/superoperator_transformations.py b/src/qibo/quantum_info/superoperator_transformations.py index 7043e84d6e..b91dadbeb1 100644 --- a/src/qibo/quantum_info/superoperator_transformations.py +++ b/src/qibo/quantum_info/superoperator_transformations.py @@ -453,8 +453,10 @@ def choi_to_kraus( backend = GlobalBackend() if validate_cp: - norm = backend.calculate_norm( - choi_super_op - np.transpose(np.conj(choi_super_op)) + norm = float( + backend.calculate_norm_density_matrix( + choi_super_op - np.transpose(np.conj(choi_super_op)), order=2 + ) ) if norm > PRECISION_TOL: non_cp = True @@ -2065,7 +2067,7 @@ def function(x0, operators): for prob, oper in zip(x0, operators): operator += prob * oper - return float(backend.calculate_norm(target - operator)) + return float(backend.calculate_norm_density_matrix(target - operator, order=2)) # initial parameters as flat distribution x0 = [1.0 / (len(kraus_ops) + 1)] * len(kraus_ops) diff --git a/src/qibo/quantum_info/utils.py b/src/qibo/quantum_info/utils.py index 35a61a99d0..de7a73223f 100644 --- a/src/qibo/quantum_info/utils.py +++ b/src/qibo/quantum_info/utils.py @@ -245,11 +245,9 @@ def hellinger_distance(prob_dist_p, prob_dist_q, validate: bool = False, backend if np.abs(np.sum(prob_dist_q) - 1.0) > PRECISION_TOL: raise_error(ValueError, "Second probability array must sum to 1.") - distance = backend.calculate_norm( - np.sqrt(prob_dist_p) - np.sqrt(prob_dist_q) - ) / np.sqrt(2) - - distance = float(distance) + distance = float( + backend.calculate_norm(np.sqrt(prob_dist_p) - np.sqrt(prob_dist_q)) / np.sqrt(2) + ) return distance diff --git a/tests/test_callbacks.py b/tests/test_callbacks.py index 76911f3b2e..d2e58b2cc8 100644 --- a/tests/test_callbacks.py +++ b/tests/test_callbacks.py @@ -7,6 +7,7 @@ # Absolute testing tolerance for the cases of zero entanglement entropy from qibo.config import PRECISION_TOL from qibo.models import AdiabaticEvolution, Circuit +from qibo.quantum_info.random_ensembles import random_density_matrix, random_statevector def test_abstract_callback_properties(): @@ -324,17 +325,18 @@ def test_state_callback(backend, density_matrix, copy): backend.assert_allclose(statec[1], target_state1) +@pytest.mark.parametrize("seed", list(range(1, 5 + 1))) @pytest.mark.parametrize("density_matrix", [False, True]) -def test_norm(backend, density_matrix): +def test_norm(backend, density_matrix, seed): norm = callbacks.Norm() if density_matrix: norm.nqubits = 1 - state = np.random.random((2, 2)) + 1j * np.random.random((2, 2)) + state = random_density_matrix(2**norm.nqubits, seed=seed, backend=backend) target_norm = np.trace(state) final_norm = norm.apply_density_matrix(backend, state) else: norm.nqubits = 2 - state = np.random.random(4) + 1j * np.random.random(4) + state = random_statevector(2**norm.nqubits, seed=seed, backend=backend) target_norm = np.sqrt((np.abs(state) ** 2).sum()) final_norm = norm.apply(backend, state) diff --git a/tests/test_gates_channels.py b/tests/test_gates_channels.py index 29de181aab..d6511f83a5 100644 --- a/tests/test_gates_channels.py +++ b/tests/test_gates_channels.py @@ -94,19 +94,29 @@ def test_kraus_channel(backend, pauli_order): channel = gates.KrausChannel(0, [a_1, a_2]) backend.assert_allclose( - backend.calculate_norm(channel.to_liouville(backend=backend) - test_superop) + float( + backend.calculate_norm_density_matrix( + channel.to_liouville(backend=backend) - test_superop, order=2 + ) + ) < PRECISION_TOL, True, ) backend.assert_allclose( - backend.calculate_norm(channel.to_choi(backend=backend) - test_choi) + float( + backend.calculate_norm_density_matrix( + channel.to_choi(backend=backend) - test_choi, order=2 + ) + ) < PRECISION_TOL, True, ) backend.assert_allclose( - backend.calculate_norm( - channel.to_pauli_liouville(pauli_order=pauli_order, backend=backend) - - test_pauli + float( + backend.calculate_norm( + channel.to_pauli_liouville(pauli_order=pauli_order, backend=backend) + - test_pauli + ) ) < PRECISION_TOL, True, @@ -212,7 +222,11 @@ def test_pauli_noise_channel(backend, pauli_order): liouville = gates.PauliNoiseChannel(0, list(zip(basis, pnp))).to_pauli_liouville( normalize=True, pauli_order=pauli_order, backend=backend ) - norm = backend.calculate_norm(backend.to_numpy(liouville) - test_representation) + norm = float( + backend.calculate_norm_density_matrix( + backend.to_numpy(liouville) - test_representation, order=2 + ) + ) assert norm < PRECISION_TOL @@ -341,7 +355,11 @@ def test_thermal_relaxation_channel(backend, t_1, t_2, time, excpop): target_state = backend.cast(target_state, dtype=target_state.dtype) backend.assert_allclose( - backend.calculate_norm(final_state - target_state) < PRECISION_TOL, True + float( + backend.calculate_norm_density_matrix(final_state - target_state, order=2) + ) + < PRECISION_TOL, + True, ) diff --git a/tests/test_models_circuit_features.py b/tests/test_models_circuit_features.py index 9d22f98ce9..4dd5c0db33 100644 --- a/tests/test_models_circuit_features.py +++ b/tests/test_models_circuit_features.py @@ -352,7 +352,7 @@ def test_repeated_execute_probs_and_freqs(backend, nqubits): test_probabilities = backend.cast(test_probabilities, dtype=float) print(result.probabilities()) backend.assert_allclose( - backend.calculate_norm(result.probabilities() - test_probabilities) + float(backend.calculate_norm(result.probabilities() - test_probabilities)) < PRECISION_TOL, True, ) diff --git a/tests/test_quantum_info_metrics.py b/tests/test_quantum_info_metrics.py index 081bc8e250..9398010463 100644 --- a/tests/test_quantum_info_metrics.py +++ b/tests/test_quantum_info_metrics.py @@ -156,12 +156,7 @@ def test_entropy(backend, base, check_hermitian): test = 0.8613531161467861 backend.assert_allclose( - backend.calculate_norm( - entropy(state, base, check_hermitian=check_hermitian, backend=backend) - - test - ) - < PRECISION_TOL, - True, + entropy(state, base, check_hermitian=check_hermitian, backend=backend), test ) @@ -520,8 +515,8 @@ def test_process_fidelity_and_infidelity(backend): channel = backend.cast(channel, dtype=channel.dtype) test = process_fidelity(channel, check_unitary=True, backend=backend) with pytest.raises(TypeError): - channel = np.random.rand(d**2, d**2) - target = np.random.rand(d**2, d**2) + channel = 10 * np.random.rand(d**2, d**2) + target = 10 * np.random.rand(d**2, d**2) channel = backend.cast(channel, dtype=channel.dtype) target = backend.cast(target, dtype=target.dtype) test = process_fidelity(channel, target, check_unitary=True, backend=backend) diff --git a/tests/test_quantum_info_random.py b/tests/test_quantum_info_random.py index 378cb0e5ad..5f7331dad9 100644 --- a/tests/test_quantum_info_random.py +++ b/tests/test_quantum_info_random.py @@ -72,14 +72,14 @@ def test_random_hermitian(backend): dims = 4 matrix = random_hermitian(dims, backend=backend) matrix_dagger = np.transpose(np.conj(matrix)) - norm = backend.calculate_norm(matrix - matrix_dagger) + norm = float(backend.calculate_norm_density_matrix(matrix - matrix_dagger, order=2)) backend.assert_allclose(norm < PRECISION_TOL, True) # test if function returns semidefinite Hermitian operator dims = 4 matrix = random_hermitian(dims, semidefinite=True, backend=backend) matrix_dagger = np.transpose(np.conj(matrix)) - norm = backend.calculate_norm(matrix - matrix_dagger) + norm = float(backend.calculate_norm_density_matrix(matrix - matrix_dagger, order=2)) backend.assert_allclose(norm < PRECISION_TOL, True) eigenvalues = np.linalg.eigvalsh(matrix) @@ -90,7 +90,7 @@ def test_random_hermitian(backend): dims = 4 matrix = random_hermitian(dims, normalize=True, backend=backend) matrix_dagger = np.transpose(np.conj(matrix)) - norm = backend.calculate_norm(matrix - matrix_dagger) + norm = float(backend.calculate_norm_density_matrix(matrix - matrix_dagger, order=2)) backend.assert_allclose(norm < PRECISION_TOL, True) eigenvalues = np.linalg.eigvalsh(matrix) @@ -101,7 +101,7 @@ def test_random_hermitian(backend): dims = 4 matrix = random_hermitian(dims, semidefinite=True, normalize=True, backend=backend) matrix_dagger = np.transpose(np.conj(matrix)) - norm = backend.calculate_norm(matrix - matrix_dagger) + norm = float(backend.calculate_norm(matrix - matrix_dagger, order=2)) backend.assert_allclose(norm < PRECISION_TOL, True) eigenvalues = np.linalg.eigvalsh(matrix) @@ -133,7 +133,9 @@ def test_random_unitary(backend): matrix = random_unitary(dims, backend=backend) matrix_dagger = np.transpose(np.conj(matrix)) matrix_inv = np.linalg.inv(matrix) - norm = backend.calculate_norm(matrix_inv - matrix_dagger) + norm = float( + backend.calculate_norm_density_matrix(matrix_inv - matrix_dagger, order=2) + ) backend.assert_allclose(norm < PRECISION_TOL, True) # tests if operator is unitary (measure == None) @@ -141,7 +143,7 @@ def test_random_unitary(backend): matrix = random_unitary(dims, measure, backend=backend) matrix_dagger = np.transpose(np.conj(matrix)) matrix_inv = np.linalg.inv(matrix) - norm = backend.calculate_norm(matrix_inv - matrix_dagger) + norm = float(backend.calculate_norm(matrix_inv - matrix_dagger, order=2)) backend.assert_allclose(norm < PRECISION_TOL, True) @@ -239,6 +241,11 @@ def test_random_density_matrix(backend, dims, pure, metric, basis, normalize): with pytest.raises(ValueError): test = random_density_matrix(dims=dims, normalize=True) else: + norm_function = ( + backend.calculate_norm_density_matrix + if basis is None + else backend.calculate_norm + ) state = random_density_matrix( dims, pure=pure, @@ -259,17 +266,13 @@ def test_random_density_matrix(backend, dims, pure, metric, basis, normalize): backend.assert_allclose(purity(state) >= 1.0 - PRECISION_TOL, True) state_dagger = np.transpose(np.conj(state)) - norm = backend.calculate_norm(state - state_dagger) + norm = float(norm_function(state - state_dagger, order=2)) backend.assert_allclose(norm < PRECISION_TOL, True) else: normalization = 1.0 if normalize is False else 1.0 / np.sqrt(dims) - backend.assert_allclose( - backend.calculate_norm(state[0] - normalization) <= PRECISION_TOL, True - ) - assert all( - backend.calculate_norm(exp_value) <= normalization - for exp_value in state[1:] - ) + print(state) + backend.assert_allclose(state[0], normalization) + assert all(np.abs(exp_value) <= normalization for exp_value in state[1:]) @pytest.mark.parametrize("seed", [10]) @@ -370,7 +373,9 @@ def test_pauli_single(backend): matrix = backend.cast(matrix, dtype=matrix.dtype) backend.assert_allclose( - backend.calculate_norm(matrix - result) < PRECISION_TOL, True + float(backend.calculate_norm_density_matrix(matrix - result, order=2)) + < PRECISION_TOL, + True, ) @@ -406,12 +411,23 @@ def test_random_pauli( matrix = backend.cast(matrix, dtype=matrix.dtype) if subset is None: backend.assert_allclose( - backend.calculate_norm(matrix - result_complete_set) < PRECISION_TOL, + float( + backend.calculate_norm_density_matrix( + matrix - result_complete_set, order=2 + ) + ) + < PRECISION_TOL, True, ) else: backend.assert_allclose( - backend.calculate_norm(matrix - result_subset) < PRECISION_TOL, True + float( + backend.calculate_norm_density_matrix( + matrix - result_subset, order=2 + ) + ) + < PRECISION_TOL, + True, ) else: matrix = np.transpose(matrix, (1, 0, 2, 3)) @@ -420,12 +436,23 @@ def test_random_pauli( if subset is None: backend.assert_allclose( - backend.calculate_norm(matrix - result_complete_set) < PRECISION_TOL, + float( + backend.calculate_norm_density_matrix( + matrix - result_complete_set, order=2 + ) + ) + < PRECISION_TOL, True, ) else: backend.assert_allclose( - backend.calculate_norm(matrix - result_subset) < PRECISION_TOL, True + float( + backend.calculate_norm_density_matrix( + matrix - result_subset, order=2 + ) + ) + < PRECISION_TOL, + True, )