Repository: quantumlib/Cirq

Issue:
xmon simulator should support schedules
Allow a schedule to be passed in and then greedily construct moments for this simulation.


Discussion:
I can take a crack at this if no one else is.
That would be great if you have any cycles.

Resolving patch:
diff --git a/cirq/schedules/schedule.py b/cirq/schedules/schedule.py
--- a/cirq/schedules/schedule.py
+++ b/cirq/schedules/schedule.py
@@ -17,6 +17,7 @@
 
 from sortedcontainers import SortedListWithKey
 
+from cirq.circuits import Circuit
 from cirq.devices import Device
 from cirq.ops import QubitId
 from cirq.schedules.scheduled_operation import ScheduledOperation
@@ -160,3 +161,22 @@ def exclude(self, scheduled_operation: ScheduledOperation) -> bool:
             return True
         except ValueError:
             return False
+
+    def to_circuit(self) -> Circuit:
+        """Convert the schedule to a circuit.
+
+        This discards most timing information from the schedule, but does place
+        operations that are scheduled at the same time in the same Moment.
+        """
+        circuit = Circuit()
+        ops = []
+        time = None  # type: Timestamp
+        for so in self.scheduled_operations:
+            if so.time != time:
+                circuit.append(ops)
+                ops = [so.operation]
+                time = so.time
+            else:
+                ops.append(so.operation)
+        circuit.append(ops)
+        return circuit
diff --git a/cirq/sim/google/xmon_simulator.py b/cirq/sim/google/xmon_simulator.py
--- a/cirq/sim/google/xmon_simulator.py
+++ b/cirq/sim/google/xmon_simulator.py
@@ -37,6 +37,7 @@
 from cirq.circuits import Circuit, ExpandComposite
 from cirq.google import xmon_gates, xmon_gate_ext
 from cirq.ops import raw_types
+from cirq.schedules import Schedule
 from cirq.sim.google.xmon_stepper import Stepper
 from cirq.study import Executor
 from cirq.study.resolver import ParamResolver
@@ -117,6 +118,25 @@ def run(
             all_step_results)
         return context, TrialResult(final_step_result)
 
+    def run_schedule(
+            self,
+            schedule: Schedule,
+            **kw
+    ) -> Tuple['TrialContext', 'TrialResult']:
+        """Simulates the entire supplied Schedule.
+
+        This takes into account the order of scheduled operations, but not the
+        actual timing, which does not matter for ideal qubits and gates.
+
+        Args:
+            schedule: The schedule to simulate.
+            **kw: Additional keyword args to pass to run.
+
+        Returns:
+            A tuple (context, result). See run for more info.
+        """
+        return self.run(schedule.to_circuit(), **kw)
+
     def moment_steps(
             self,
             program: Circuit,


Test patch:
diff --git a/cirq/sim/google/xmon_simulator_test.py b/cirq/sim/google/xmon_simulator_test.py
--- a/cirq/sim/google/xmon_simulator_test.py
+++ b/cirq/sim/google/xmon_simulator_test.py
@@ -21,10 +21,12 @@
 import pytest
 
 from cirq.circuits import Circuit
+from cirq.devices import UnconstrainedDevice
 from cirq.google import (
     ExpWGate, ExpZGate, Exp11Gate, XmonMeasurementGate, XmonQubit,
 )
 from cirq.ops.common_gates import CNOT, X
+from cirq.schedules import moment_by_moment_schedule
 from cirq.sim.google import xmon_simulator
 from cirq.study import ParameterizedValue
 from cirq.study.resolver import ParamResolver
@@ -82,14 +84,27 @@ def assert_empty_context(context):
     assert xmon_simulator.TrialContext(param_dict={}) == context
 
 
-def test_run_no_results():
+def run(simulator, circuit, scheduler, **kw):
+    if scheduler is None:
+        return simulator.run(circuit, **kw)
+    else:
+        schedule = scheduler(UnconstrainedDevice, circuit)
+        return simulator.run_schedule(schedule, **kw)
+
+
+SCHEDULERS = [None, moment_by_moment_schedule]
+
+
+@pytest.mark.parametrize('scheduler', SCHEDULERS)
+def test_run_no_results(scheduler):
     simulator = xmon_simulator.Simulator()
-    context, result = simulator.run(basic_circuit())
+    context, result = run(simulator, basic_circuit(), scheduler)
     assert len(result.measurements) == 0
     assert_empty_context(context)
 
 
-def test_run():
+@pytest.mark.parametrize('scheduler', SCHEDULERS)
+def test_run(scheduler):
     np.random.seed(0)
     circuit = basic_circuit()
     circuit.append(
@@ -97,49 +112,58 @@ def test_run():
          XmonMeasurementGate(key='b')(Q2),])
 
     simulator = xmon_simulator.Simulator()
-    context, result = simulator.run(circuit)
+    context, result = run(simulator, circuit, scheduler)
     assert result.measurements == {'a': [False], 'b': [False]}
     assert_empty_context(context)
 
 
-def test_run_state():
+@pytest.mark.parametrize('scheduler', SCHEDULERS)
+def test_run_state(scheduler):
     simulator = xmon_simulator.Simulator()
-    context, result = simulator.run(basic_circuit(), qubits=[Q1, Q2])
+    context, result = run(simulator, basic_circuit(), scheduler,
+                          qubits=[Q1, Q2])
     np.testing.assert_almost_equal(result.final_state,
                                    np.array([-0.5j, 0.5, -0.5, 0.5j]))
     assert_empty_context(context)
 
 
-def test_run_state_different_order_of_qubits():
+@pytest.mark.parametrize('scheduler', SCHEDULERS)
+def test_run_state_different_order_of_qubits(scheduler):
     simulator = xmon_simulator.Simulator()
-    context, result = simulator.run(basic_circuit(), qubits=[Q2, Q1])
+    context, result = run(simulator, basic_circuit(), scheduler,
+                          qubits=[Q2, Q1])
     np.testing.assert_almost_equal(result.final_state,
                                    np.array([-0.5j, -0.5, 0.5, 0.5j]))
     assert_empty_context(context)
 
 
-def test_consistent_seeded_run_sharded():
+@pytest.mark.parametrize('scheduler', SCHEDULERS)
+def test_consistent_seeded_run_sharded(scheduler):
     circuit = large_circuit()
 
     simulator = xmon_simulator.Simulator()
-    context, result = simulator.run(circuit)
+    context, result = run(simulator, circuit, scheduler)
     assert result.measurements == {
         'meas': [True, False, False, True, False, False, True, False, False,
                  False]}
     assert_empty_context(context)
 
 
-def test_consistent_seeded_run_no_sharding():
+@pytest.mark.parametrize('scheduler', SCHEDULERS)
+def test_consistent_seeded_run_no_sharding(scheduler):
     circuit = large_circuit()
 
     simulator = xmon_simulator.Simulator()
-    _, result = simulator.run(circuit,
-                              xmon_simulator.Options(num_shards=1))
+    _, result = run(simulator,
+                    circuit,
+                    scheduler,
+                    options=xmon_simulator.Options(num_shards=1))
     assert result.measurements == {
         'meas': [True, False, False, True, False, False, True, False, False,
                  False]}
 
-def test_run_no_sharing_few_qubits():
+@pytest.mark.parametrize('scheduler', SCHEDULERS)
+def test_run_no_sharing_few_qubits(scheduler):
     np.random.seed(0)
     circuit = basic_circuit()
     circuit.append(
@@ -148,7 +172,7 @@ def test_run_no_sharing_few_qubits():
 
     simulator = xmon_simulator.Simulator()
     options = xmon_simulator.Options(min_qubits_before_shard=0)
-    context, result = simulator.run(circuit, options=options)
+    context, result = run(simulator, circuit, scheduler, options=options)
     assert result.measurements == {'a': [False], 'b': [False]}
     assert_empty_context(context)
 
@@ -308,14 +332,15 @@ def test_param_resolver_param_dict(offset):
     assert context.param_dict == {'a': 0.5}
 
 
-def test_composite_gates():
+@pytest.mark.parametrize('scheduler', SCHEDULERS)
+def test_composite_gates(scheduler):
     circuit = Circuit()
     circuit.append([X(Q1), CNOT(Q1, Q2)])
     m = XmonMeasurementGate('a')
     circuit.append([m(Q1), m(Q2)])
 
     simulator = xmon_simulator.Simulator()
-    _, result = simulator.run(circuit)
+    _, result = run(simulator, circuit, scheduler)
     assert result.measurements['a'] == [True, True]