Repository: quantumlib/Cirq

Issue:
Proposal: Merge QuantumEngineSampler and Engine
Often when writing code that uses Engine you really want a Sampler so that you can easily swap in a simulator. It seems unwieldy to have both these classes around and one kind of has to look around or be told about QuantumEngineSampler to realize it exists.


Discussion:
Ok the plan is to make Engine implement Sampler by moving keyword arguments from `Engine.run` to `Engine.__init__`.
It would also be useful to implement a "copy" method that creates a new engine instance with some parameters changed (for example you might want to change `JobConfig` or `priority` but leave auth info the same). Then you can do things like:
```python
engine = Engine(...)
engine.copy(priority=10).run(low_priority_job)
engine.copy(priority=99).run(high_priority_job)
```

Resolving patch:
diff --git a/cirq/google/engine/engine.py b/cirq/google/engine/engine.py
--- a/cirq/google/engine/engine.py
+++ b/cirq/google/engine/engine.py
@@ -41,7 +41,8 @@
 from cirq.google import gate_sets, serializable_gate_set
 from cirq.google.api import v1 as api_v1
 from cirq.google.api import v2 as api_v2
-from cirq.google.engine import calibration, engine_job, engine_program
+from cirq.google.engine import (calibration, engine_job, engine_program,
+                                engine_sampler)
 
 gcs_prefix_pattern = re.compile('gs://[a-z0-9._/-]+')
 TYPE_PREFIX = 'type.googleapis.com/'
@@ -692,3 +693,18 @@ def get_calibration(self, calibration_name: str) -> calibration.Calibration:
         response = self.service.projects().processors().calibrations().get(
             name=calibration_name).execute()
         return calibration.Calibration(response['data']['data'])
+
+    def sampler(self, processor_id: Union[str, List[str]],
+                gate_set: serializable_gate_set.SerializableGateSet
+               ) -> engine_sampler.QuantumEngineSampler:
+        """Returns a sampler backed by the engine.
+
+        Args:
+            processor_id: String identifier, or list of string identifiers,
+                determining which processors may be used when sampling.
+            gate_set: Determines how to serialize circuits when requesting
+                samples.
+        """
+        return engine_sampler.QuantumEngineSampler(engine=self,
+                                                   processor_id=processor_id,
+                                                   gate_set=gate_set)


Test patch:
diff --git a/cirq/google/engine/engine_test.py b/cirq/google/engine/engine_test.py
--- a/cirq/google/engine/engine_test.py
+++ b/cirq/google/engine/engine_test.py
@@ -983,3 +983,45 @@ def test_not_both_version_and_discovery(build):
         cg.Engine(project_id='project-id',
                   version='vNo',
                   discovery_url='funkyDisco')
+
+
+@mock.patch.object(discovery, 'build')
+def test_sampler(build):
+    service = mock.Mock()
+    build.return_value = service
+    programs = service.projects().programs()
+    jobs = programs.jobs()
+    programs.create().execute.return_value = {
+        'name': 'projects/project-id/programs/test'
+    }
+    jobs.create().execute.return_value = {
+        'name': 'projects/project-id/programs/test/jobs/test',
+        'executionStatus': {
+            'state': 'READY'
+        }
+    }
+    jobs.get().execute.return_value = {
+        'name': 'projects/project-id/programs/test/jobs/test',
+        'executionStatus': {
+            'state': 'SUCCESS'
+        }
+    }
+    jobs.getResult().execute.return_value = {'result': _RESULTS}
+    engine = cg.Engine(project_id='project-id')
+    sampler = engine.sampler(processor_id='tmp', gate_set=cg.XMON)
+    results = sampler.run_sweep(
+        program=_SCHEDULE,
+        params=[cirq.ParamResolver({'a': 1}),
+                cirq.ParamResolver({'a': 2})])
+    assert len(results) == 2
+    for i, v in enumerate([1, 2]):
+        assert results[i].repetitions == 1
+        assert results[i].params.param_dict == {'a': v}
+        assert results[i].measurements == {'q': np.array([[0]], dtype='uint8')}
+    build.assert_called_with('quantum',
+                             'v1alpha1',
+                             discoveryServiceUrl=('https://{api}.googleapis.com'
+                                                  '/$discovery/rest?version='
+                                                  '{apiVersion}'),
+                             requestBuilder=mock.ANY)
+    assert programs.create.call_args[1]['parent'] == 'projects/project-id'