Repository: quantumlib/Cirq

Issue:
ZerosSampler
Often it is useful to test your workflow can successfully collect data without caring too much about what that data is. 

I propose a `ZerosSampler` that just immediately returns a bunch of zeros but otherwise follows the `cirq.Sampler` interface. This can also run the program through the proto serialization routines to verify that the gateset is correct. 

One final use case for this is profiling the "classical overhead" when running a complicated pipeline by removing the quantum part


Discussion:
Hi. I would like to implment this. Can you please assign me?
@dstrain115 
Assigned.  You were not able to assign yourself?

Resolving patch:
diff --git a/cirq/__init__.py b/cirq/__init__.py
--- a/cirq/__init__.py
+++ b/cirq/__init__.py
@@ -512,6 +512,7 @@
     PauliSumCollector,
     Sampler,
     Collector,
+    ZerosSampler,
 )
 
 # pylint: enable=redefined-builtin
diff --git a/cirq/work/__init__.py b/cirq/work/__init__.py
--- a/cirq/work/__init__.py
+++ b/cirq/work/__init__.py
@@ -6,3 +6,5 @@
     PauliSumCollector,)
 from cirq.work.sampler import (
     Sampler,)
+from cirq.work.zeros_sampler import (
+    ZerosSampler,)
diff --git a/cirq/work/zeros_sampler.py b/cirq/work/zeros_sampler.py
new file mode 100644
--- /dev/null
+++ b/cirq/work/zeros_sampler.py
@@ -0,0 +1,69 @@
+# Copyright 2020 The Cirq Developers
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import abc
+from typing import (Dict, List, TYPE_CHECKING)
+
+import numpy as np
+
+from cirq import work, study, protocols
+
+if TYPE_CHECKING:
+    import cirq.google
+
+
+class ZerosSampler(work.Sampler, metaclass=abc.ABCMeta):
+    """A dummy sampler for testing. Immediately returns zeroes."""
+
+    def __init__(self, gate_set: 'cirq.google.SerializableGateSet' = None):
+        """
+        Args:
+            gate_set: `SerializableGateSet`. If set, sampler will validate that
+                all gates in the circuit are from the given gate set.
+        """
+        self.gate_set = gate_set
+
+    def run_sweep(
+            self,
+            program: 'cirq.Circuit',
+            params: study.Sweepable,
+            repetitions: int = 1,
+    ) -> List[study.TrialResult]:
+        """Samples circuit as if every measurement resulted in zero.
+
+        Args:
+            program: The circuit to sample from.
+            params: Parameters to run with the program.
+            repetitions: The number of times to sample.
+
+        Returns:
+            TrialResult list for this run; one for each possible parameter
+            resolver.
+        """
+        if self.gate_set is not None:
+            for op in program.all_operations():
+                assert self.gate_set.is_supported_operation(op), (
+                    "Unsupported operation: %s" % op)
+
+        measurements = {}  # type: Dict[str, np.ndarray]
+        for op in program.all_operations():
+            key = protocols.measurement_key(op, default=None)
+            if key is not None:
+                measurements[key] = np.zeros((repetitions, len(op.qubits)),
+                                             dtype=np.int8)
+        return [
+            study.TrialResult.from_single_parameter_set(
+                params=param_resolver, measurements=measurements)
+            for param_resolver in study.to_resolvers(params)
+        ]


Test patch:
diff --git a/cirq/protocols/json_serialization_test.py b/cirq/protocols/json_serialization_test.py
--- a/cirq/protocols/json_serialization_test.py
+++ b/cirq/protocols/json_serialization_test.py
@@ -329,6 +329,7 @@ def test_mutually_exclusive_blacklist():
     'WaveFunctionTrialResult',
     'XmonDevice',
     'XMON',
+    'ZerosSampler',
     'Zip',
 ]
 
diff --git a/cirq/work/zeros_sampler_test.py b/cirq/work/zeros_sampler_test.py
new file mode 100644
--- /dev/null
+++ b/cirq/work/zeros_sampler_test.py
@@ -0,0 +1,72 @@
+# Copyright 2020 The Cirq Developers
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import numpy as np
+import pytest
+import sympy
+
+import cirq
+from cirq import study
+from cirq.google.common_serializers import (SINGLE_QUBIT_SERIALIZERS,
+                                            SINGLE_QUBIT_DESERIALIZERS)
+from cirq.google.serializable_gate_set import SerializableGateSet
+
+
+def test_run_sweep():
+    a, b, c = [cirq.NamedQubit(s) for s in ['a', 'b', 'c']]
+    circuit = cirq.Circuit([cirq.measure(a)], [cirq.measure(b, c)])
+    sampler = cirq.ZerosSampler()
+
+    result = sampler.run_sweep(circuit, None, 3)
+
+    assert len(result) == 1
+    assert result[0].measurements.keys() == {'a', 'b,c'}
+    assert result[0].measurements['a'].shape == (3, 1)
+    assert np.all(result[0].measurements['a'] == 0)
+    assert result[0].measurements['b,c'].shape == (3, 2)
+    assert np.all(result[0].measurements['b,c'] == 0)
+
+
+def test_sample():
+    # Create a circuit whose measurements are always zeros, and check that
+    # results of ZeroSampler on this circuit are identical to results of
+    # actual simulation.
+    qs = cirq.LineQubit.range(6)
+    c = cirq.Circuit([cirq.CNOT(qs[0], qs[1]), cirq.X(qs[2]), cirq.X(qs[2])])
+    c += cirq.Z(qs[3])**sympy.Symbol('p')
+    c += [cirq.measure(q) for q in qs[0:3]]
+    c += cirq.measure(qs[4], qs[5])
+    # Z to even power is an identity.
+    params = study.Points(sympy.Symbol('p'), [0, 2, 4, 6])
+
+    result1 = cirq.ZerosSampler().sample(c, repetitions=10,
+                                         params=params).sort_index(axis=1)
+    result2 = cirq.Simulator().sample(c, repetitions=10,
+                                      params=params).sort_index(axis=1)
+
+    assert np.all(result1 == result2)
+
+
+def test_sample_with_gate_set():
+    gate_set = SerializableGateSet('test', SINGLE_QUBIT_SERIALIZERS,
+                                   SINGLE_QUBIT_DESERIALIZERS)
+    sampler = cirq.ZerosSampler(gate_set=gate_set)
+    a, b = cirq.LineQubit.range(2)
+    circuit1 = cirq.Circuit([cirq.X(a)])
+    circuit2 = cirq.Circuit([cirq.CX(a, b)])
+
+    sampler.sample(circuit1)
+
+    with pytest.raises(AssertionError, match='Unsupported operation'):
+        sampler.sample(circuit2)