Repository: quantumlib/Cirq

Issue:
SimulatesIntermediateState._base_iterator docstring says it'll be deprecated
I looked into actually deprecating this but I don't think deprecation is the right thing to do here.

I believe `SimulatesIntermediateState` has too much simulation functionality tied to it; it should just be a wrapper around iterating intermediate states. The rest really belongs in `SimulatorBase`.

I think in the final solution, `_base_iterator` should be an abstract function in `SimulatesIntermediateState`. The implementation of it should be moved to `SimulatorBase`. Upon moving that, we can get rid of the abstract functions `_create_simulation_state`, `_create_act_on_args`, and `_core_iterator` from `SimulatesIntermediateState`, since they were only ever used from `_base_iterator` implementation. This provides better separation of concerns, and allows users more flexibility in implementing `SimulatesIntermediateState`.


Discussion:
Discussion from cirq cync:  This needs a decision before 1.0 since it affects the user-facing interface, but we were unsure whether it should be done without details from @daxfohl .  Keeping it on triage discuss for next time.

Resolving patch:
diff --git a/cirq-core/cirq/sim/simulator.py b/cirq-core/cirq/sim/simulator.py
--- a/cirq-core/cirq/sim/simulator.py
+++ b/cirq-core/cirq/sim/simulator.py
@@ -655,25 +655,17 @@ def simulate_moment_steps(
         resolved_circuit = protocols.resolve_parameters(circuit, param_resolver)
         check_all_resolved(resolved_circuit)
         actual_initial_state = 0 if initial_state is None else initial_state
-        return self._base_iterator(resolved_circuit, qubit_order, actual_initial_state)
+        qubits = ops.QubitOrder.as_qubit_order(qubit_order).order_for(circuit.all_qubits())
+        return self._base_iterator(resolved_circuit, qubits, actual_initial_state)
 
     def _base_iterator(
-        self,
-        circuit: 'cirq.AbstractCircuit',
-        qubit_order: 'cirq.QubitOrderOrList',
-        initial_state: Any,
+        self, circuit: 'cirq.AbstractCircuit', qubits: Tuple['cirq.Qid', ...], initial_state: Any
     ) -> Iterator[TStepResult]:
         """Iterator over StepResult from Moments of a Circuit.
 
-        This is a thin wrapper around `create_act_on_args` and `_core_iterator`.
-        Overriding this method was the old way of creating a circuit iterator,
-        and this method is planned to be formally put on the deprecation path.
-        Going forward, override the aforementioned two methods in custom
-        simulators.
-
         Args:
             circuit: The circuit to simulate.
-            qubit_order: Determines the canonical ordering of the qubits. This
+            qubits: Specifies the canonical ordering of the qubits. This
                 is often used in specifying the initial state, i.e. the
                 ordering of the computational basis states.
             initial_state: The initial state for the simulation. The form of
@@ -683,7 +675,22 @@ def _base_iterator(
         Yields:
             StepResults from simulating a Moment of the Circuit.
         """
-        qubits = ops.QubitOrder.as_qubit_order(qubit_order).order_for(circuit.all_qubits())
+        # In 0.16 (or 1.0) _base_iterator should be made abstract. Then _create_simulation_state,
+        # _create_act_on_args, and _core_iterator can all be removed from this class completely.
+        # (They were only required because of this implementation, which has been moved to
+        # SimulatorBase instead).
+        _compat._warn_or_error(
+            'Custom implementations of `cirq.SimulatesIntermediateState` should implement'
+            ' `_base_iterator` directly rather than implementing both `_create_simulation_state`'
+            ' and `_core_iterator`. The default implementation of `_base_iterator` will be removed'
+            ' in v0.16, and the method will become abstract.'
+        )
+        if not isinstance(qubits, tuple):
+            _compat._warn_or_error(
+                'The `qubits` parameter of `_base_iterator` will expect an explicit'
+                ' `Tuple[cirq.Qid, ...]` beginning in v0.16.'
+            )
+            qubits = ops.QubitOrder.as_qubit_order(qubits).order_for(circuit.all_qubits())
         sim_state = self._create_simulation_state(initial_state, qubits)
         return self._core_iterator(circuit, sim_state)
 
@@ -736,7 +743,6 @@ def _create_simulation_state(
         # remove this implementation, and delete `_create_act_on_args` entirely.
         return self._create_act_on_args(initial_state, qubits)
 
-    @abc.abstractmethod
     def _core_iterator(
         self,
         circuit: 'cirq.AbstractCircuit',
@@ -758,6 +764,7 @@ def _core_iterator(
         Yields:
             StepResults from simulating a Moment of the Circuit.
         """
+        raise NotImplementedError()
 
     @abc.abstractmethod
     def _create_simulator_trial_result(
diff --git a/cirq-core/cirq/sim/simulator_base.py b/cirq-core/cirq/sim/simulator_base.py
--- a/cirq-core/cirq/sim/simulator_base.py
+++ b/cirq-core/cirq/sim/simulator_base.py
@@ -210,6 +210,18 @@ def _can_be_in_run_prefix(self, val: Any):
             `_run` prefix."""
         return protocols.has_unitary(val)
 
+    def _base_iterator(
+        self, circuit: 'cirq.AbstractCircuit', qubits: Tuple['cirq.Qid', ...], initial_state: Any
+    ) -> Iterator[TStepResultBase]:
+        if not isinstance(qubits, tuple):
+            _compat._warn_or_error(
+                'The `qubits` parameter of `_base_iterator` will expect an explicit'
+                ' `Tuple[cirq.Qid, ...]` beginning in v0.16.'
+            )
+            qubits = ops.QubitOrder.as_qubit_order(qubits).order_for(circuit.all_qubits())
+        sim_state = self._create_simulation_state(initial_state, qubits)
+        return self._core_iterator(circuit, sim_state)
+
     def _core_iterator(
         self,
         circuit: 'cirq.AbstractCircuit',


Test patch:
diff --git a/cirq-core/cirq/sim/simulator_base_test.py b/cirq-core/cirq/sim/simulator_base_test.py
--- a/cirq-core/cirq/sim/simulator_base_test.py
+++ b/cirq-core/cirq/sim/simulator_base_test.py
@@ -443,6 +443,21 @@ def _create_simulator_trial_result(self):
         sim.simulate_moment_steps(cirq.Circuit())
 
 
+def test_deprecated_qubits_param():
+    class Sim(cirq.SimulatorBase):
+        def _create_partial_simulation_state(self, initial_state, qubits, classical_data):
+            return 0
+
+        def _create_step_result(self):
+            pass
+
+        def _create_simulator_trial_result(self):
+            pass
+
+    with cirq.testing.assert_deprecated('`qubits` parameter of `_base_iterator', deadline='v0.16'):
+        Sim()._base_iterator(cirq.Circuit(), cirq.QubitOrder.explicit([]), 0)
+
+
 def test_deprecated_setters():
     sim = CountingSimulator()
     with cirq.testing.assert_deprecated(deadline='v0.16'):
diff --git a/cirq-core/cirq/sim/simulator_test.py b/cirq-core/cirq/sim/simulator_test.py
--- a/cirq-core/cirq/sim/simulator_test.py
+++ b/cirq-core/cirq/sim/simulator_test.py
@@ -553,10 +553,27 @@ def _create_simulator_trial_result(self):
             pass
 
     sim = DeprecatedSim()
-    with cirq.testing.assert_deprecated(deadline='v0.16'):
+    with cirq.testing.assert_deprecated(deadline='v0.16', count=2):
         sim.simulate_moment_steps(cirq.Circuit())
 
 
+def test_deprecated_qubits_param():
+    class Sim(cirq.SimulatesIntermediateState):
+        def _create_simulation_state(self, initial_state, qubits):
+            return 0
+
+        def _core_iterator(self, circuit, sim_state):
+            pass
+
+        def _create_simulator_trial_result(self):
+            pass
+
+    with cirq.testing.assert_deprecated(
+        '`qubits` parameter of `_base_iterator', deadline='v0.16', count=2
+    ):
+        Sim()._base_iterator(cirq.Circuit(), cirq.QubitOrder.explicit([]), 0)
+
+
 def test_deprecated_setters():
     step = FakeStepResult()
     result = cirq.SimulationTrialResult(cirq.ParamResolver(), {}, 0)