Repository: quantumlib/Cirq

Issue:
Clifford simulator ignores random seed
We should pass it through all the way to tableau's `_measure` method.


Discussion:
Hi. Can I fix this?

Resolving patch:
diff --git a/cirq/sim/clifford/clifford_simulator.py b/cirq/sim/clifford/clifford_simulator.py
--- a/cirq/sim/clifford/clifford_simulator.py
+++ b/cirq/sim/clifford/clifford_simulator.py
@@ -43,8 +43,14 @@ class CliffordSimulator(simulator.SimulatesSamples,
                         simulator.SimulatesIntermediateState):
     """An efficient simulator for Clifford circuits."""
 
-    def __init__(self):
+    def __init__(self, seed: value.RANDOM_STATE_LIKE = None):
+        """Creates instance of `CliffordSimulator`.
+
+        Args:
+            seed: The random seed to use for this simulator.
+        """
         self.init = True
+        self._prng = value.parse_random_state(seed)
 
     @staticmethod
     def get_supported_gates() -> List['cirq.Gate']:
@@ -89,7 +95,7 @@ def _base_iterator(self, circuit: circuits.Circuit,
                     elif protocols.is_measurement(op):
                         key = protocols.measurement_key(op)
                         measurements[key].extend(
-                            state.perform_measurement(op.qubits))
+                            state.perform_measurement(op.qubits, self._prng))
 
                 yield CliffordSimulatorStepResult(measurements=measurements,
                                                   state=state)
@@ -234,6 +240,7 @@ def sample(self,
         for _ in range(repetitions):
             measurements.append(
                 self.state.perform_measurement(qubits,
+                                               value.parse_random_state(seed),
                                                collapse_wavefunction=False))
 
         return np.array(measurements, dtype=bool)
@@ -321,6 +328,7 @@ def apply_unitary(self, op: 'cirq.Operation'):
 
     def perform_measurement(self,
                             qubits: Sequence[ops.Qid],
+                            prng: np.random.RandomState,
                             collapse_wavefunction=True):
         results = []
 
@@ -330,7 +338,7 @@ def perform_measurement(self,
             state = self.copy()
 
         for qubit in qubits:
-            result = state.tableau._measure(self.qubit_map[qubit])
+            result = state.tableau._measure(self.qubit_map[qubit], prng)
             state.ch_form.project_Z(self.qubit_map[qubit], result)
             results.append(result)
 
diff --git a/cirq/sim/clifford/clifford_tableau.py b/cirq/sim/clifford/clifford_tableau.py
--- a/cirq/sim/clifford/clifford_tableau.py
+++ b/cirq/sim/clifford/clifford_tableau.py
@@ -203,7 +203,7 @@ def destabilizers(self) -> List[DensePauliString]:
         generators above generate the full Pauli group on n qubits."""
         return [self._row_to_dense_pauli(i) for i in range(0, self.n)]
 
-    def _measure(self, q):
+    def _measure(self, q, prng: np.random.RandomState):
         """ Performs a projective measurement on the q'th qubit.
 
         Returns: the result (0 or 1) of the measurement.
@@ -239,6 +239,6 @@ def _measure(self, q):
 
             self.zs[p, q] = True
 
-            self.rs[p] = bool(np.random.randint(2))
+            self.rs[p] = bool(prng.randint(2))
 
             return int(self.rs[p])
diff --git a/cirq/sim/mux.py b/cirq/sim/mux.py
--- a/cirq/sim/mux.py
+++ b/cirq/sim/mux.py
@@ -72,7 +72,7 @@ def sample(program: 'cirq.Circuit',
         if _is_clifford_circuit(program):
             # If all non-measurement operations are clifford, use the Clifford
             # simulator.
-            return clifford_simulator.CliffordSimulator().run(
+            return clifford_simulator.CliffordSimulator(seed=seed).run(
                 program, param_resolver=param_resolver, repetitions=repetitions)
         if protocols.has_unitary(program):
             return sparse_simulator.Simulator(dtype=dtype, seed=seed).run(


Test patch:
diff --git a/cirq/sim/clifford/clifford_simulator_test.py b/cirq/sim/clifford/clifford_simulator_test.py
--- a/cirq/sim/clifford/clifford_simulator_test.py
+++ b/cirq/sim/clifford/clifford_simulator_test.py
@@ -364,3 +364,13 @@ def test_non_clifford_circuit():
     with pytest.raises(ValueError,
                        match="T cannot be run with Clifford simulator"):
         cirq.CliffordSimulator().simulate(circuit)
+
+
+def test_sample_seed():
+    q = cirq.NamedQubit('q')
+    circuit = cirq.Circuit(cirq.H(q), cirq.measure(q))
+    simulator = cirq.CliffordSimulator(seed=1234)
+    result = simulator.run(circuit, repetitions=20)
+    measured = result.measurements['q']
+    result_string = ''.join(map(lambda x: str(int(x[0])), measured))
+    assert result_string == '11010001111100100000'