From 6c84cd84b1e9255561a42d0a05a9fd6940521572 Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Sun, 28 Jan 2024 07:57:41 +0400 Subject: [PATCH 1/8] initial commit --- src/qibo/quantum_info/random_ensembles.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/qibo/quantum_info/random_ensembles.py b/src/qibo/quantum_info/random_ensembles.py index 9ef63a653d..146a3a96f5 100644 --- a/src/qibo/quantum_info/random_ensembles.py +++ b/src/qibo/quantum_info/random_ensembles.py @@ -1281,3 +1281,11 @@ def _super_op_from_bcsz_measure(dims: int, rank: int, order: str, seed, backend) super_op = operator @ super_op @ operator return super_op + + +def _generate_local_state(seed): + local_state = ( + np.random.default_rng(seed) if seed is None or isinstance(seed, int) else seed + ) + + return local_state From b3f192a3cc78d98fe63c99af10d5fed0ac0ef9c9 Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Sun, 28 Jan 2024 07:58:37 +0400 Subject: [PATCH 2/8] add error check to function --- src/qibo/quantum_info/random_ensembles.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/qibo/quantum_info/random_ensembles.py b/src/qibo/quantum_info/random_ensembles.py index 146a3a96f5..2afb29bc9b 100644 --- a/src/qibo/quantum_info/random_ensembles.py +++ b/src/qibo/quantum_info/random_ensembles.py @@ -1284,6 +1284,15 @@ def _super_op_from_bcsz_measure(dims: int, rank: int, order: str, seed, backend) def _generate_local_state(seed): + if ( + seed is not None + and not isinstance(seed, int) + and not isinstance(seed, np.random.Generator) + ): + raise_error( + TypeError, "seed must be either type int or numpy.random.Generator." + ) + local_state = ( np.random.default_rng(seed) if seed is None or isinstance(seed, int) else seed ) From 627c2e991c57abbd34dfe5a8412973221608dc75 Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Sun, 28 Jan 2024 08:32:48 +0400 Subject: [PATCH 3/8] fix test --- tests/test_quantum_info_random.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/tests/test_quantum_info_random.py b/tests/test_quantum_info_random.py index 01000ab822..8b3cd05de9 100644 --- a/tests/test_quantum_info_random.py +++ b/tests/test_quantum_info_random.py @@ -61,11 +61,6 @@ def test_uniform_sampling_U3(backend, seed): backend.assert_allclose(expectation_values[0], expectation_values[1], atol=1e-1) backend.assert_allclose(expectation_values[0], expectation_values[2], atol=1e-1) - # execution for coverage - sampler = _probability_distribution_sin(a=0, b=np.pi, seed=seed) - sampler.pdf(1) - sampler.cdf(1) - @pytest.mark.parametrize("seed", [None, 10, np.random.default_rng(10)]) def test_random_gaussian_matrix(backend, seed): @@ -342,8 +337,10 @@ def test_random_clifford(backend, nqubits, return_circuit, density_matrix, seed) result_single = matrices.Z @ matrices.H - result_two = np.kron(matrices.H, matrices.S) @ np.kron(matrices.S, matrices.Z) - result_two = np.kron(matrices.Z @ matrices.S, matrices.I) @ result_two + # result_two = np.kron(matrices.H, matrices.S) @ np.kron(matrices.S, matrices.Z) + # result_two = np.kron(matrices.Z @ matrices.S, matrices.I) @ result_two + result_two = np.kron(matrices.H, matrices.S) @ np.kron(matrices.S, matrices.Y) + result_two = np.kron(matrices.S @ matrices.X, matrices.I) @ result_two result_two = matrices.CNOT @ matrices.CZ @ result_two result = result_single if nqubits == 1 else result_two @@ -358,6 +355,7 @@ def test_random_clifford(backend, nqubits, return_circuit, density_matrix, seed) ) if return_circuit: + print(matrix.draw()) matrix = matrix.unitary(backend) backend.assert_allclose(matrix, result, atol=PRECISION_TOL) From cfbaf01a3a1e5fe6e742ab64c945e291cd0e2b6e Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Sun, 28 Jan 2024 08:33:15 +0400 Subject: [PATCH 4/8] revamp all seed generations --- src/qibo/quantum_info/random_ensembles.py | 135 ++++++---------------- 1 file changed, 34 insertions(+), 101 deletions(-) diff --git a/src/qibo/quantum_info/random_ensembles.py b/src/qibo/quantum_info/random_ensembles.py index 2afb29bc9b..c7b6d4a689 100644 --- a/src/qibo/quantum_info/random_ensembles.py +++ b/src/qibo/quantum_info/random_ensembles.py @@ -20,7 +20,7 @@ ) -class _probability_distribution_sin(rv_continuous): +class _probability_distribution_sin(rv_continuous): # pragma: no cover def _pdf(self, theta: float): return 0.5 * np.sin(theta) @@ -53,21 +53,7 @@ def uniform_sampling_U3(ngates: int, seed=None, backend=None): elif ngates <= 0: raise_error(ValueError, f"ngates must be non-negative, but it is {ngates}.") - if ( - seed is not None - and not isinstance(seed, int) - and not isinstance(seed, np.random.Generator) - ): - raise_error( - TypeError, "seed must be either type int or numpy.random.Generator." - ) - - if backend is None: # pragma: no cover - backend = GlobalBackend() - - local_state = ( - np.random.default_rng(seed) if seed is None or isinstance(seed, int) else seed - ) + backend, local_state = _set_backend_and_local_state(seed, backend) sampler = _probability_distribution_sin(a=0, b=np.pi, seed=local_state) phases = local_state.random((ngates, 3)) @@ -133,21 +119,7 @@ def random_gaussian_matrix( if stddev is not None and stddev <= 0.0: raise_error(ValueError, "stddev must be a positive float.") - if ( - seed is not None - and not isinstance(seed, int) - and not isinstance(seed, np.random.Generator) - ): - raise_error( - TypeError, "seed must be either type int or numpy.random.Generator." - ) - - if backend is None: # pragma: no cover - backend = GlobalBackend() - - local_state = ( - np.random.default_rng(seed) if seed is None or isinstance(seed, int) else seed - ) + backend, local_state = _set_backend_and_local_state(seed, backend) dims = (dims, rank) @@ -193,10 +165,9 @@ def random_hermitian( if not isinstance(semidefinite, bool) or not isinstance(normalize, bool): raise_error(TypeError, "semidefinite and normalize must be type bool.") - if backend is None: # pragma: no cover - backend = GlobalBackend() + backend, local_state = _set_backend_and_local_state(seed, backend) - matrix = random_gaussian_matrix(dims, dims, seed=seed, backend=backend) + matrix = random_gaussian_matrix(dims, dims, seed=local_state, backend=backend) if semidefinite: matrix = np.dot(np.transpose(np.conj(matrix)), matrix) @@ -241,11 +212,10 @@ def random_unitary(dims: int, measure: Optional[str] = None, seed=None, backend= if measure != "haar": raise_error(ValueError, f"measure {measure} not implemented.") - if backend is None: # pragma: no cover - backend = GlobalBackend() + backend, local_state = _set_backend_and_local_state(seed, backend) if measure == "haar": - unitary = random_gaussian_matrix(dims, dims, seed=seed, backend=backend) + unitary = random_gaussian_matrix(dims, dims, seed=local_state, backend=backend) Q, R = np.linalg.qr(unitary) D = np.diag(R) D = D / np.abs(D) @@ -357,15 +327,14 @@ def random_quantum_channel( NotImplementedError, f"order {order} not implemented for measure {measure}." ) - if backend is None: # pragma: no cover - backend = GlobalBackend() + backend, local_state = _set_backend_and_local_state(seed, backend) if measure == "bcsz": super_op = _super_op_from_bcsz_measure( - dims=dims, rank=rank, order=order, seed=seed, backend=backend + dims=dims, rank=rank, order=order, seed=local_state, backend=backend ) else: - super_op = random_unitary(dims, measure, seed, backend) + super_op = random_unitary(dims, measure, local_state, backend) super_op = vectorization(super_op, order=order, backend=backend) super_op = np.outer(super_op, np.conj(super_op)) @@ -573,28 +542,28 @@ def random_density_matrix( elif normalize is True and basis is None: raise_error(ValueError, "normalize cannot be True when basis=None.") - if backend is None: # pragma: no cover - backend = GlobalBackend() + backend, local_state = _set_backend_and_local_state(seed, backend) if metric == "hilbert-schmidt": rank = None if pure: - state = random_statevector(dims, seed=seed, backend=backend) + state = random_statevector(dims, seed=local_state, backend=backend) state = np.outer(state, np.transpose(np.conj(state))) else: if metric in ["hilbert-schmidt", "ginibre"]: state = random_gaussian_matrix( - dims, rank, mean=0, stddev=1, seed=seed, backend=backend + dims, rank, mean=0, stddev=1, seed=local_state, backend=backend ) state = np.dot(state, np.transpose(np.conj(state))) state = state / np.trace(state) else: nqubits = int(np.log2(dims)) state = backend.identity_density_matrix(nqubits, normalize=False) - state += random_unitary(dims, seed=seed, backend=backend) + state += random_unitary(dims, seed=local_state, backend=backend) state = np.dot( - state, random_gaussian_matrix(dims, rank, seed=seed, backend=backend) + state, + random_gaussian_matrix(dims, rank, seed=local_state, backend=backend), ) state = np.dot(state, np.transpose(np.conj(state))) state = state / np.trace(state) @@ -662,21 +631,7 @@ def random_clifford( f"return_circuit must be type bool, but it is type {type(return_circuit)}.", ) - if ( - seed is not None - and not isinstance(seed, int) - and not isinstance(seed, np.random.Generator) - ): - raise_error( - TypeError, "seed must be either type int or numpy.random.Generator." - ) - - if backend is None: # pragma: no cover - backend = GlobalBackend() - - local_state = ( - np.random.default_rng(seed) if seed is None or isinstance(seed, int) else seed - ) + backend, local_state = _set_backend_and_local_state(seed, backend) hadamards, permutations = _sample_from_quantum_mallows_distribution( nqubits, local_state=local_state @@ -755,7 +710,7 @@ def random_clifford( depth=1, return_circuit=True, density_matrix=density_matrix, - seed=seed, + seed=local_state, backend=backend, ), ) @@ -857,21 +812,7 @@ def random_pauli( "subset argument must be a subset of strings in the set ['I', 'X', 'Y', 'Z'].", ) - if ( - seed is not None - and not isinstance(seed, int) - and not isinstance(seed, np.random.Generator) - ): - raise_error( - TypeError, "seed must be either type int or numpy.random.Generator." - ) - - if backend is None: # pragma: no cover - backend = GlobalBackend() - - local_state = ( - np.random.default_rng(seed) if seed is None or isinstance(seed, int) else seed - ) + backend, local_state = _set_backend_and_local_state(seed, backend) complete_set = {"I": gates.I, "X": gates.X, "Y": gates.Y, "Z": gates.Z} @@ -974,12 +915,11 @@ def random_pauli_hamiltonian( "when normalize=True, gap is = 1, thus max_eigenvalue must be > 1.", ) - if backend is None: # pragma: no cover - backend = GlobalBackend() + backend, local_state = _set_backend_and_local_state(seed, backend) d = 2**nqubits - hamiltonian = random_hermitian(d, normalize=True, seed=seed, backend=backend) + hamiltonian = random_hermitian(d, normalize=True, seed=local_state, backend=backend) eigenvalues, eigenvectors = np.linalg.eigh(hamiltonian) @@ -1079,21 +1019,7 @@ def random_stochastic_matrix( if max_iterations <= 0.0: raise_error(ValueError, "max_iterations must be a positive int.") - if ( - seed is not None - and not isinstance(seed, int) - and not isinstance(seed, np.random.Generator) - ): - raise_error( - TypeError, "seed must be either type int or numpy.random.Generator." - ) - - if backend is None: # pragma: no cover - backend = GlobalBackend() - - local_state = ( - np.random.default_rng(seed) if seed is None or isinstance(seed, int) else seed - ) + backend, local_state = _set_backend_and_local_state(seed, backend) if precision_tol is None: precision_tol = PRECISION_TOL @@ -1283,7 +1209,7 @@ def _super_op_from_bcsz_measure(dims: int, rank: int, order: str, seed, backend) return super_op -def _generate_local_state(seed): +def _set_backend_and_local_state(seed, backend): if ( seed is not None and not isinstance(seed, int) @@ -1293,8 +1219,15 @@ def _generate_local_state(seed): TypeError, "seed must be either type int or numpy.random.Generator." ) - local_state = ( - np.random.default_rng(seed) if seed is None or isinstance(seed, int) else seed - ) + if backend is None: # pragma: no cover + backend = GlobalBackend() + + if seed is None or isinstance(seed, int): + if backend.__class__.__name__ in ["CupyBackend", "CuQuantumBackend"]: + local_state = backend.np.random.default_rng(seed) + else: + local_state = np.random.default_rng(seed) + else: + local_state = seed - return local_state + return backend, local_state From fe51c4d2c25bfabe504f0e46ef07495e66a378b3 Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Sun, 28 Jan 2024 08:34:54 +0400 Subject: [PATCH 5/8] fix coverage --- src/qibo/quantum_info/random_ensembles.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qibo/quantum_info/random_ensembles.py b/src/qibo/quantum_info/random_ensembles.py index c7b6d4a689..aa6a785667 100644 --- a/src/qibo/quantum_info/random_ensembles.py +++ b/src/qibo/quantum_info/random_ensembles.py @@ -1222,7 +1222,7 @@ def _set_backend_and_local_state(seed, backend): if backend is None: # pragma: no cover backend = GlobalBackend() - if seed is None or isinstance(seed, int): + if seed is None or isinstance(seed, int): # pragma: no cover if backend.__class__.__name__ in ["CupyBackend", "CuQuantumBackend"]: local_state = backend.np.random.default_rng(seed) else: From b08034dce6219e295702e158e3608e7cf7d1968e Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Sun, 28 Jan 2024 08:35:27 +0400 Subject: [PATCH 6/8] fix coverage --- src/qibo/quantum_info/random_ensembles.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/qibo/quantum_info/random_ensembles.py b/src/qibo/quantum_info/random_ensembles.py index aa6a785667..84cb79af48 100644 --- a/src/qibo/quantum_info/random_ensembles.py +++ b/src/qibo/quantum_info/random_ensembles.py @@ -1222,8 +1222,11 @@ def _set_backend_and_local_state(seed, backend): if backend is None: # pragma: no cover backend = GlobalBackend() - if seed is None or isinstance(seed, int): # pragma: no cover - if backend.__class__.__name__ in ["CupyBackend", "CuQuantumBackend"]: + if seed is None or isinstance(seed, int): + if backend.__class__.__name__ in [ + "CupyBackend", + "CuQuantumBackend", + ]: # pragma: no cover local_state = backend.np.random.default_rng(seed) else: local_state = np.random.default_rng(seed) From cea28a906768dec86f1b5fe6d55c14c8d9f4cebc Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Mon, 29 Jan 2024 06:20:00 +0000 Subject: [PATCH 7/8] Update tests/test_quantum_info_random.py Co-authored-by: BrunoLiegiBastonLiegi <45011234+BrunoLiegiBastonLiegi@users.noreply.github.com> --- tests/test_quantum_info_random.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/test_quantum_info_random.py b/tests/test_quantum_info_random.py index 8b3cd05de9..73fde46faa 100644 --- a/tests/test_quantum_info_random.py +++ b/tests/test_quantum_info_random.py @@ -337,8 +337,6 @@ def test_random_clifford(backend, nqubits, return_circuit, density_matrix, seed) result_single = matrices.Z @ matrices.H - # result_two = np.kron(matrices.H, matrices.S) @ np.kron(matrices.S, matrices.Z) - # result_two = np.kron(matrices.Z @ matrices.S, matrices.I) @ result_two result_two = np.kron(matrices.H, matrices.S) @ np.kron(matrices.S, matrices.Y) result_two = np.kron(matrices.S @ matrices.X, matrices.I) @ result_two result_two = matrices.CNOT @ matrices.CZ @ result_two From 1a2c6d75edd2afe69dbc937ad54610b2faad3ac3 Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Mon, 29 Jan 2024 15:06:37 +0000 Subject: [PATCH 8/8] Update tests/test_quantum_info_random.py Co-authored-by: BrunoLiegiBastonLiegi <45011234+BrunoLiegiBastonLiegi@users.noreply.github.com> --- tests/test_quantum_info_random.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_quantum_info_random.py b/tests/test_quantum_info_random.py index 101f51f92c..bafb4efa84 100644 --- a/tests/test_quantum_info_random.py +++ b/tests/test_quantum_info_random.py @@ -352,7 +352,6 @@ def test_random_clifford(backend, nqubits, return_circuit, density_matrix, seed) ) if return_circuit: - print(matrix.draw()) matrix = matrix.unitary(backend) backend.assert_allclose(matrix, result, atol=PRECISION_TOL)