From 16bfd94a2ffaf7f35506491ab94a722b1ad7f20b Mon Sep 17 00:00:00 2001 From: Stavros Efthymiou <35475381+stavros11@users.noreply.github.com> Date: Tue, 24 Aug 2021 19:13:11 +0400 Subject: [PATCH 1/5] Add transpose_state and swap_pieces --- src/qibojit/custom_operators/__init__.py | 15 ++++- src/qibojit/custom_operators/backends.py | 22 +++++++ src/qibojit/custom_operators/ops.py | 26 ++++++++ src/qibojit/tests/test_ops.py | 75 +++++++++++++++++++++++- 4 files changed, 134 insertions(+), 4 deletions(-) diff --git a/src/qibojit/custom_operators/__init__.py b/src/qibojit/custom_operators/__init__.py index 3ad22239..020c5c09 100644 --- a/src/qibojit/custom_operators/__init__.py +++ b/src/qibojit/custom_operators/__init__.py @@ -93,8 +93,19 @@ def collapse_state(state, qubits, result, nqubits, normalize=True): return backend.collapse_state(state, qubits, result, nqubits, normalize) +def transpose_state(pieces, state, nqubits, order): + # always fall back to numba CPU backend because for ops not implemented on GPU + numba_backend = backend.get("numba") + return numba_backend.transpose_state(pieces, state, nqubits, order) + + +def swap_pieces(piece0, piece1, new_global, nlocal): + # always fall back to numba CPU backend because for ops not implemented on GPU + numba_backend = backend.get("numba") + return numba_backend.swap_pieces(piece0, piece1, new_global, nlocal) + + def measure_frequencies(frequencies, probs, nshots, nqubits, seed=1234, nthreads=None): - # always fall back to numba CPU backend because this op is not implemented - # on GPU + # always fall back to numba CPU backend because for ops not implemented on GPU numba_backend = backend.get("numba") return numba_backend.measure_frequencies(frequencies, probs, nshots, nqubits, seed, nthreads) diff --git a/src/qibojit/custom_operators/backends.py b/src/qibojit/custom_operators/backends.py index 4292a74c..c5a528ad 100644 --- a/src/qibojit/custom_operators/backends.py +++ b/src/qibojit/custom_operators/backends.py @@ -32,6 +32,14 @@ def initial_state(self, nqubits, dtype, is_matrix=False): # pragma: no cover def collapse_state(self, state, qubits, result, nqubits, normalize): # pragma: no cover raise NotImplementedError + @abstractmethod + def transpose_state(self, pieces, state, nqubits, order): # pragma: no cover + raise NotImplementedError + + @abstractmethod + def swap_pieces(self, piece0, piece1, new_global, nlocal): # pragma: no cover + raise NotImplementedError + @abstractmethod def measure_frequencies(self, frequencies, probs, nshots, nqubits, seed=1234): # pragma: no cover raise NotImplementedError @@ -101,6 +109,12 @@ def collapse_state(self, state, qubits, result, nqubits, normalize=True): return self.ops.collapse_state_normalized(state, qubits, result, nqubits) return self.ops.collapse_state(state, qubits, result, nqubits) + def transpose_state(self, pieces, state, nqubits, order): + return self.ops.transpose_state(pieces, state, nqubits, order) + + def swap_pieces(self, piece0, piece1, new_global, nlocal): + return self.ops.swap_pieces(piece0, piece1, new_global, nlocal) + def measure_frequencies(self, frequencies, probs, nshots, nqubits, seed=1234, nthreads=None): if nthreads is None: import psutil @@ -274,6 +288,14 @@ def collapse_state(self, state, qubits, result, nqubits, normalize=True): state = state / norm return state + def transpose_state(self, pieces, state, nqubits, order): + raise NotImplementedError("`transpose_state` method is not " + "implemented for GPU.") + + def swap_pieces(self, piece0, piece1, new_global, nlocal): + raise NotImplementedError("`swap_pieces` method is not " + "implemented for GPU.") + def measure_frequencies(self, frequencies, probs, nshots, nqubits, seed=1234): raise NotImplementedError("`measure_frequencies` method is not " "implemented for GPU.") diff --git a/src/qibojit/custom_operators/ops.py b/src/qibojit/custom_operators/ops.py index 983656c1..a600081f 100644 --- a/src/qibojit/custom_operators/ops.py +++ b/src/qibojit/custom_operators/ops.py @@ -90,3 +90,29 @@ def measure_frequencies(frequencies, probs, nshots, nqubits, seed, nthreads): frequencies_private[shot] += 1 frequencies += thread_frequencies.sum(axis=0) return frequencies + + +@njit(cache=True, parallel=True) +def transpose_state(pieces, state, nqubits, order): + nstates = 1 << nqubits + ndevices = len(pieces) + npiece = nstates // ndevices + qubit_exponents = [1 << (nqubits - x - 1) for x in order[::-1]] + + for g in prange(nstates): # pylint: disable=not-an-iterable + k = 0 + for q in range(nqubits): + if ((g >> q) % 2): + k += qubit_exponents[q] + state[g] = pieces[k // npiece][k % npiece] + return state + + +@njit(cache=True, parallel=True) +def swap_pieces(piece0, piece1, new_global, nlocal): + m = nlocal - new_global - 1 + tk = 1 << m + nstates = 1 << (nlocal - 1) + for g in prange(nstates): # pylint: disable=not-an-iterable + i = ((g >> m) << (m + 1)) + (g & (tk - 1)) + piece0[i + tk], piece1[i] = piece1[i], piece0[i + tk] diff --git a/src/qibojit/tests/test_ops.py b/src/qibojit/tests/test_ops.py index ba90c287..b94f3090 100644 --- a/src/qibojit/tests/test_ops.py +++ b/src/qibojit/tests/test_ops.py @@ -2,6 +2,7 @@ import itertools import numpy as np from qibojit import custom_operators as op +from qibojit.tests.test_gates import qubits_tensor, random_state @pytest.mark.parametrize("is_matrix", [False, True]) @@ -24,8 +25,7 @@ def test_initial_state(backend, dtype, is_matrix): def test_collapse_state(backend, nqubits, targets, results, normalize, dtype): atol = 1e-7 if dtype == "complex64" else 1e-14 shape = (2 ** nqubits,) - state = np.random.random(shape) + 1j * np.random.random(shape) - state = state.astype(dtype) + state = random_state(nqubits, dtype) slicer = nqubits * [slice(None)] for t, r in zip(targets, results): slicer[t] = r @@ -46,6 +46,25 @@ def test_collapse_state(backend, nqubits, targets, results, normalize, dtype): np.testing.assert_allclose(state, target_state, atol=atol) +@pytest.mark.parametrize("nqubits", [3, 4, 7, 8, 9, 10]) +@pytest.mark.parametrize("ndevices", [2, 4, 8]) +def test_transpose_state(nqubits, ndevices, dtype): + for _ in range(10): + # Generate global qubits randomly + all_qubits = np.arange(nqubits) + np.random.shuffle(all_qubits) + qubit_order = list(all_qubits) + state = random_state(nqubits, dtype) + state_tensor = np.reshape(state, nqubits * (2,)) + target_state = np.transpose(state_tensor, qubit_order).flatten() + + new_state = np.zeros_like(state) + state = np.reshape(state, (ndevices, int(state.shape[0]) // ndevices)) + pieces = [state[i] for i in range(ndevices)] + new_state = op.transpose_state(pieces, new_state, nqubits, qubit_order) + np.testing.assert_allclose(new_state, target_state) + + @pytest.mark.parametrize("realtype", ["float32", "float64"]) @pytest.mark.parametrize("inttype", ["int32", "int64"]) @pytest.mark.parametrize("nthreads", [None, 4]) @@ -62,6 +81,58 @@ def test_measure_frequencies(backend, realtype, inttype, nthreads): np.testing.assert_allclose(frequencies, target_frequencies) +@pytest.mark.parametrize("nqubits", [4, 5, 7, 8, 9, 10]) +def test_swap_pieces_zero_global(nqubits, dtype): + state = random_state(nqubits, dtype) + target_state = np.copy(state) + shape = (2, int(state.shape[0]) // 2) + state = np.reshape(state, shape) + + for _ in range(10): + local = np.random.randint(1, nqubits) + qubits = qubits_tensor(nqubits, [0, local]) + target_state = op.apply_swap(target_state, nqubits, 0, local, qubits) + target_state = np.reshape(op.to_numpy(target_state), shape) + piece0, piece1 = state[0], state[1] + op.swap_pieces(piece0, piece1, local - 1, nqubits - 1) + np.testing.assert_allclose(piece0, target_state[0]) + np.testing.assert_allclose(piece1, target_state[1]) + + +@pytest.mark.skip +@pytest.mark.parametrize("nqubits", [5, 7, 8, 9, 10]) +def test_swap_pieces(nqubits, dtype): + state = random_state(nqubits, dtype) + target_state = np.copy(state) + shape = (2, int(state.shape[0]) // 2) + + for _ in range(10): + global_qubit = np.random.randint(0, nqubits) + local_qubit = np.random.randint(0, nqubits) + while local_qubit == global_qubit: + local_qubit = np.random.randint(0, nqubits) + + transpose_order = ([global_qubit] + list(range(global_qubit)) + + list(range(global_qubit + 1, nqubits))) + + qubits = qubits_tensor(nqubits, [global_qubit, local_qubit]) + target_state = op.apply_swap( + target_state, nqubits, global_qubit, local_qubit, qubits) + target_state = op.to_numpy(target_state) + target_state = np.reshape(target_state, nqubits * (2,)) + target_state = np.transpose(target_state, transpose_order) + target_state = np.reshape(target_state, shape) + + state = np.reshape(state, nqubits * (2,)) + state = np.transpose(state, transpose_order) + state = np.reshape(state, shape) + piece0, piece1 = state[0], state[1] + new_global = local_qubit - int(global_qubit < local_qubit) + op.swap_pieces(piece0, piece1, new_global, nqubits - 1) + np.testing.assert_allclose(piece0, target_state[0]) + np.testing.assert_allclose(piece1, target_state[1]) + + NONZERO = list(itertools.combinations(range(8), r=1)) NONZERO.extend(itertools.combinations(range(8), r=2)) NONZERO.extend(itertools.combinations(range(8), r=3)) From 11079fbdb018f235beeb1f560b21f6d23f19932a Mon Sep 17 00:00:00 2001 From: Stavros Efthymiou <35475381+stavros11@users.noreply.github.com> Date: Tue, 24 Aug 2021 19:29:25 +0400 Subject: [PATCH 2/5] Skip swap_pieces tests --- src/qibojit/tests/test_ops.py | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/src/qibojit/tests/test_ops.py b/src/qibojit/tests/test_ops.py index b94f3090..54c3fd65 100644 --- a/src/qibojit/tests/test_ops.py +++ b/src/qibojit/tests/test_ops.py @@ -65,22 +65,7 @@ def test_transpose_state(nqubits, ndevices, dtype): np.testing.assert_allclose(new_state, target_state) -@pytest.mark.parametrize("realtype", ["float32", "float64"]) -@pytest.mark.parametrize("inttype", ["int32", "int64"]) -@pytest.mark.parametrize("nthreads", [None, 4]) -def test_measure_frequencies(backend, realtype, inttype, nthreads): - probs = np.ones(16, dtype=realtype) / 16 - frequencies = np.zeros(16, dtype=inttype) - frequencies = op.measure_frequencies(frequencies, probs, nshots=1000, - nqubits=4, seed=1234, - nthreads=nthreads) - assert np.sum(frequencies) == 1000 - if nthreads is not None: - target_frequencies = np.array([72, 65, 63, 54, 57, 55, 67, 50, 53, 67, 69, - 68, 64, 68, 66, 62], dtype=inttype) - np.testing.assert_allclose(frequencies, target_frequencies) - - +@pytest.mark.skip @pytest.mark.parametrize("nqubits", [4, 5, 7, 8, 9, 10]) def test_swap_pieces_zero_global(nqubits, dtype): state = random_state(nqubits, dtype) @@ -133,6 +118,22 @@ def test_swap_pieces(nqubits, dtype): np.testing.assert_allclose(piece1, target_state[1]) +@pytest.mark.parametrize("realtype", ["float32", "float64"]) +@pytest.mark.parametrize("inttype", ["int32", "int64"]) +@pytest.mark.parametrize("nthreads", [None, 4]) +def test_measure_frequencies(backend, realtype, inttype, nthreads): + probs = np.ones(16, dtype=realtype) / 16 + frequencies = np.zeros(16, dtype=inttype) + frequencies = op.measure_frequencies(frequencies, probs, nshots=1000, + nqubits=4, seed=1234, + nthreads=nthreads) + assert np.sum(frequencies) == 1000 + if nthreads is not None: + target_frequencies = np.array([72, 65, 63, 54, 57, 55, 67, 50, 53, 67, 69, + 68, 64, 68, 66, 62], dtype=inttype) + np.testing.assert_allclose(frequencies, target_frequencies) + + NONZERO = list(itertools.combinations(range(8), r=1)) NONZERO.extend(itertools.combinations(range(8), r=2)) NONZERO.extend(itertools.combinations(range(8), r=3)) From 5aa8eb0ed940454fde3508fa08d3723df3e6edd4 Mon Sep 17 00:00:00 2001 From: Stavros Efthymiou <35475381+stavros11@users.noreply.github.com> Date: Tue, 24 Aug 2021 19:44:47 +0400 Subject: [PATCH 3/5] Enable swap_pieces tests --- src/qibojit/tests/test_ops.py | 115 +++++++++++++++++----------------- 1 file changed, 57 insertions(+), 58 deletions(-) diff --git a/src/qibojit/tests/test_ops.py b/src/qibojit/tests/test_ops.py index 54c3fd65..7a97784f 100644 --- a/src/qibojit/tests/test_ops.py +++ b/src/qibojit/tests/test_ops.py @@ -46,76 +46,75 @@ def test_collapse_state(backend, nqubits, targets, results, normalize, dtype): np.testing.assert_allclose(state, target_state, atol=atol) -@pytest.mark.parametrize("nqubits", [3, 4, 7, 8, 9, 10]) +def generate_transpose_qubits(nqubits): + """Generates global qubits randomly.""" + qubits = np.arange(nqubits) + np.random.shuffle(qubits) + return qubits + + +CONFIG = ((n, generate_transpose_qubits(n)) + for _ in range(5) for n in range(3, 11)) +@pytest.mark.parametrize("nqubits,qubits", CONFIG) @pytest.mark.parametrize("ndevices", [2, 4, 8]) -def test_transpose_state(nqubits, ndevices, dtype): - for _ in range(10): - # Generate global qubits randomly - all_qubits = np.arange(nqubits) - np.random.shuffle(all_qubits) - qubit_order = list(all_qubits) - state = random_state(nqubits, dtype) - state_tensor = np.reshape(state, nqubits * (2,)) - target_state = np.transpose(state_tensor, qubit_order).flatten() - - new_state = np.zeros_like(state) - state = np.reshape(state, (ndevices, int(state.shape[0]) // ndevices)) - pieces = [state[i] for i in range(ndevices)] - new_state = op.transpose_state(pieces, new_state, nqubits, qubit_order) - np.testing.assert_allclose(new_state, target_state) - - -@pytest.mark.skip -@pytest.mark.parametrize("nqubits", [4, 5, 7, 8, 9, 10]) -def test_swap_pieces_zero_global(nqubits, dtype): +def test_transpose_state(nqubits, qubits, ndevices, dtype): + qubit_order = list(qubits) + state = random_state(nqubits, dtype) + state_tensor = np.reshape(state, nqubits * (2,)) + target_state = np.transpose(state_tensor, qubit_order).flatten() + new_state = np.zeros_like(state) + state = np.reshape(state, (ndevices, int(state.shape[0]) // ndevices)) + pieces = [state[i] for i in range(ndevices)] + new_state = op.transpose_state(pieces, new_state, nqubits, qubit_order) + np.testing.assert_allclose(new_state, target_state) + + +CONFIG = ((n, np.random.randint(1, n)) for _ in range(10) for n in range(4, 11)) +@pytest.mark.parametrize("nqubits,local", CONFIG) +def test_swap_pieces_zero_global(nqubits, local, dtype): state = random_state(nqubits, dtype) target_state = np.copy(state) shape = (2, int(state.shape[0]) // 2) state = np.reshape(state, shape) - for _ in range(10): - local = np.random.randint(1, nqubits) - qubits = qubits_tensor(nqubits, [0, local]) - target_state = op.apply_swap(target_state, nqubits, 0, local, qubits) - target_state = np.reshape(op.to_numpy(target_state), shape) - piece0, piece1 = state[0], state[1] - op.swap_pieces(piece0, piece1, local - 1, nqubits - 1) - np.testing.assert_allclose(piece0, target_state[0]) - np.testing.assert_allclose(piece1, target_state[1]) + qubits = qubits_tensor(nqubits, [0, local]) + target_state = op.apply_swap(target_state, nqubits, 0, local, qubits) + target_state = np.reshape(op.to_numpy(target_state), shape) + piece0, piece1 = state[0], state[1] + op.swap_pieces(piece0, piece1, local - 1, nqubits - 1) + np.testing.assert_allclose(piece0, target_state[0]) + np.testing.assert_allclose(piece1, target_state[1]) -@pytest.mark.skip -@pytest.mark.parametrize("nqubits", [5, 7, 8, 9, 10]) -def test_swap_pieces(nqubits, dtype): +CONFIG = ((n, np.random.randint(0, n), np.random.randint(0, n)) + for _ in range(10) for n in range(5, 11)) +@pytest.mark.parametrize("nqubits,qlocal,qglobal", CONFIG) +def test_swap_pieces(nqubits, qlocal, qglobal, dtype): state = random_state(nqubits, dtype) target_state = np.copy(state) shape = (2, int(state.shape[0]) // 2) - for _ in range(10): - global_qubit = np.random.randint(0, nqubits) - local_qubit = np.random.randint(0, nqubits) - while local_qubit == global_qubit: - local_qubit = np.random.randint(0, nqubits) - - transpose_order = ([global_qubit] + list(range(global_qubit)) + - list(range(global_qubit + 1, nqubits))) - - qubits = qubits_tensor(nqubits, [global_qubit, local_qubit]) - target_state = op.apply_swap( - target_state, nqubits, global_qubit, local_qubit, qubits) - target_state = op.to_numpy(target_state) - target_state = np.reshape(target_state, nqubits * (2,)) - target_state = np.transpose(target_state, transpose_order) - target_state = np.reshape(target_state, shape) - - state = np.reshape(state, nqubits * (2,)) - state = np.transpose(state, transpose_order) - state = np.reshape(state, shape) - piece0, piece1 = state[0], state[1] - new_global = local_qubit - int(global_qubit < local_qubit) - op.swap_pieces(piece0, piece1, new_global, nqubits - 1) - np.testing.assert_allclose(piece0, target_state[0]) - np.testing.assert_allclose(piece1, target_state[1]) + while qlocal == qglobal: + qlocal = np.random.randint(0, nqubits) + + transpose_order = ([qglobal] + list(range(qglobal)) + + list(range(qglobal + 1, nqubits))) + + qubits = qubits_tensor(nqubits, [qglobal, qlocal]) + target_state = op.apply_swap(target_state, nqubits, qglobal, qlocal, qubits) + target_state = op.to_numpy(target_state) + target_state = np.reshape(target_state, nqubits * (2,)) + target_state = np.transpose(target_state, transpose_order) + target_state = np.reshape(target_state, shape) + + state = np.reshape(state, nqubits * (2,)) + state = np.transpose(state, transpose_order) + state = np.reshape(state, shape) + piece0, piece1 = state[0], state[1] + new_global = qlocal - int(qglobal < qlocal) + op.swap_pieces(piece0, piece1, new_global, nqubits - 1) + np.testing.assert_allclose(piece0, target_state[0]) + np.testing.assert_allclose(piece1, target_state[1]) @pytest.mark.parametrize("realtype", ["float32", "float64"]) From bfb8b94043c58e400982ef9a4cae21a4874b57a7 Mon Sep 17 00:00:00 2001 From: Stavros Efthymiou <35475381+stavros11@users.noreply.github.com> Date: Tue, 24 Aug 2021 19:55:12 +0400 Subject: [PATCH 4/5] Remove redudant shape --- src/qibojit/tests/test_ops.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/qibojit/tests/test_ops.py b/src/qibojit/tests/test_ops.py index 7a97784f..6ef34806 100644 --- a/src/qibojit/tests/test_ops.py +++ b/src/qibojit/tests/test_ops.py @@ -24,7 +24,6 @@ def test_initial_state(backend, dtype, is_matrix): @pytest.mark.parametrize("normalize", [False, True]) def test_collapse_state(backend, nqubits, targets, results, normalize, dtype): atol = 1e-7 if dtype == "complex64" else 1e-14 - shape = (2 ** nqubits,) state = random_state(nqubits, dtype) slicer = nqubits * [slice(None)] for t, r in zip(targets, results): From c57659d852cf848b66a9dd2c7f49d66ef18b5c84 Mon Sep 17 00:00:00 2001 From: Stavros Efthymiou <35475381+stavros11@users.noreply.github.com> Date: Tue, 24 Aug 2021 23:14:52 +0400 Subject: [PATCH 5/5] Change list to tuple --- src/qibojit/custom_operators/backends.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qibojit/custom_operators/backends.py b/src/qibojit/custom_operators/backends.py index c5a528ad..077c6e76 100644 --- a/src/qibojit/custom_operators/backends.py +++ b/src/qibojit/custom_operators/backends.py @@ -110,7 +110,7 @@ def collapse_state(self, state, qubits, result, nqubits, normalize=True): return self.ops.collapse_state(state, qubits, result, nqubits) def transpose_state(self, pieces, state, nqubits, order): - return self.ops.transpose_state(pieces, state, nqubits, order) + return self.ops.transpose_state(tuple(pieces), state, nqubits, tuple(order)) def swap_pieces(self, piece0, piece1, new_global, nlocal): return self.ops.swap_pieces(piece0, piece1, new_global, nlocal)