diff --git a/src/qibo/backends/_clifford_operations.py b/src/qibo/backends/_clifford_operations.py index e893631188..efcb212852 100644 --- a/src/qibo/backends/_clifford_operations.py +++ b/src/qibo/backends/_clifford_operations.py @@ -3,6 +3,8 @@ import numpy as np from scipy import sparse +from qibo.config import raise_error + name = "numpy" @@ -424,7 +426,7 @@ def _determined_outcome(state, q, nqubits): return state, state[-1, -1] -def _random_outcome(state, p, q, nqubits): +def _random_outcome(state, p, q, nqubits, outcome): """Extracts the outcome for a measurement in case it is random.""" p = p[0] + nqubits state[p, q] = 0 @@ -439,7 +441,7 @@ def _random_outcome(state, p, q, nqubits): False, ) state[p - nqubits, :] = state[p, :] - outcome = np.random.randint(2, size=1).item() + # outcome = np.random.randint(2, size=1).item() state[p, :] = 0 state[p, -1] = outcome state[p, nqubits + q] = 1 @@ -494,25 +496,46 @@ def _init_state_for_measurements(state, nqubits, collapse): return state.copy() -# valid for a standard basis measurement only -def M(state, qubits, nqubits, collapse=False): +def _sample_random_outcomes(n_samples): + return np.random.randint(2, size=n_samples).tolist() + + +def _M(state, qubits, nqubits, collapse=False): sample = [] state = _init_state_for_measurements(state, nqubits, collapse) - # TODO: parallelize this and get rid of the loop + possible_outcomes = [] for q in qubits: p = state[nqubits:-1, q].nonzero()[0] # random outcome, affects the state if len(p) > 0: - state, outcome = _random_outcome(state, p, q, nqubits) + state, _ = _random_outcome(state, p, q, nqubits, 0) + outcome = None # determined outcome, state unchanged else: _, outcome = _determined_outcome(state, q, nqubits) + outcome = int(outcome) sample.append(outcome) if collapse: state = _packbits(state, axis=0) return sample +# valid for a standard basis measurement only +def M(state, qubits, nqubits, collapse=False, nshots=1): + if collapse and nshots != 1: + raise_error( + RuntimeError, + "Cannot generate multiple shots with a collapsing measurement.", + ) + sample = _M(state, qubits, nqubits, collapse) + samples = [ + (_sample_random_outcomes(nshots) if outcome is None else nshots * [outcome]) + for outcome in sample + ] + samples = list(zip(*samples)) + return samples + + def cast(x, dtype=None, copy: bool = False): if dtype is None: dtype = "complex128" diff --git a/src/qibo/backends/clifford.py b/src/qibo/backends/clifford.py index 237267ed41..b9a31507e2 100644 --- a/src/qibo/backends/clifford.py +++ b/src/qibo/backends/clifford.py @@ -410,14 +410,6 @@ def execute_circuit( # pylint: disable=R1710 if self.platform == "stim": return self._execute_circuit_stim(circuit, initial_state, nshots) - for gate in circuit.queue: - if ( - not gate.clifford - and not gate.__class__.__name__ == "M" - and not isinstance(gate, gates.PauliNoiseChannel) - ): - raise_error(RuntimeError, "Circuit contains non-Clifford gates.") - if circuit.repeated_execution and nshots != 1: return self.execute_circuit_repeated(circuit, nshots, initial_state) @@ -531,10 +523,10 @@ def sample_shots( qubits = tuple(qubits) if collapse: - samples = [self.engine.M(state, qubits, nqubits) for _ in range(nshots - 1)] - samples.append(self.engine.M(state, qubits, nqubits, collapse)) + samples = self.engine.M(state, qubits, nqubits, nshots=nshots - 1) + samples.append(self.engine.M(state, qubits, nqubits, collapse, nshots=1)) else: - samples = [self.engine.M(state, qubits, nqubits) for _ in range(nshots)] + samples = self.engine.M(state, qubits, nqubits, nshots=nshots) return self.engine.cast(samples, dtype=int) def symplectic_matrix_to_generators( diff --git a/tests/test_backends_clifford.py b/tests/test_backends_clifford.py index d5cb19fc5b..d62be0465e 100644 --- a/tests/test_backends_clifford.py +++ b/tests/test_backends_clifford.py @@ -161,7 +161,8 @@ def test_two_qubits_gates(backend, gate): [ 15, 25, - ], + ] + + list(range(100)), ) def test_random_clifford_circuit(backend, prob_qubits, binary, seed): np.random.seed(seed) @@ -287,7 +288,7 @@ def test_non_clifford_error(backend): clifford_bkd = construct_clifford_backend(backend) c = Circuit(1) c.add(gates.T(0)) - with pytest.raises(RuntimeError) as excinfo: + with pytest.raises(AttributeError) as excinfo: clifford_bkd.execute_circuit(c) assert str(excinfo.value) == "Circuit contains non-Clifford gates."