Repository: quantumlib/Cirq

Issue:
Inconsistent use of state argument in SimulationState base and derived classes
**Description of the issue**

pylint complains about missing `state` argument in SimulationState constructor call 

https://github.com/quantumlib/Cirq/blob/de761212dd686443a88def2e1136bfbd5dcecead/cirq-core/cirq/sim/simulation_state.py#L190

pylint report:

```shell
$ pylint --rcfile=dev_tools/conf/.pylintrc cirq-core/cirq/sim/simulation_state.py
************* Module cirq.sim.simulation_state
cirq-core/cirq/sim/simulation_state.py:190:20: E1125: Missing mandatory keyword argument 'state' in constructor call (missing-kwoa)
```

The fix is not apparent, because SimulationState constructor requires the `state` argument

https://github.com/quantumlib/Cirq/blob/de761212dd686443a88def2e1136bfbd5dcecead/cirq-core/cirq/sim/simulation_state.py#L44-L54

however `state` is not accepted by subclasses, for example, in StateVectorSimulationState:

https://github.com/quantumlib/Cirq/blob/de761212dd686443a88def2e1136bfbd5dcecead/cirq-core/cirq/sim/state_vector_simulation_state.py#L321-L330

**Cirq version**

```
$ git describe --tags master
v0.15.0-47-gde761212
```



Discussion:
+1 this was an unfortunate oversight on my part. I think "state" is better than "initial_state" (it sounds nicer when serializing) but could be convinced otherwise. 
We're at the final call for 1.0 changes - if we want this to make it in it's going to be a no-deprecation / break-with-a-hammer style change.
I can think of adding an abstract class method `SimulationState.fromQubits` (or some similar name) which will be only implemented in subclasses to produce `cls(qubits=qubits)` where it will hopefully pass pylint scrutiny.
The `SimulationState.with_qubits` will then use
```python
        new_space = self.fromQubits(qubits)
```
instead of
```python
        new_space = type(self)(qubits=qubits)  # type: ignore
```
Rather than adding a new method like `fromQubits` on all subclasses; I'd suggest changing `with_qubits` to be abstract and implementing that everywhere instead. 
We could also just delete that function. It came from a new contributor PR where I gave this as an example of something that his changes would enable, though I didn't actually intend for him to implement it. Once he did, I figured may as well keep it. But it's not used for anything AFAIK.

(Though either way it would still be nice to make the arg name consistent, regardless) 
Yeah my vote is to remove the method. Even if we changed all the constructor arg names to "state" that still wouldn't work because here we'd do `type(self)(qubits=qubits, state=0)` which still wouldn't type check because zero is not of type TState. Ultimately "state" in the base class and "initial_state" in the subclasses represent different things (the former is exclusively a TState, and the latter is a TState _or_ anything that the subclass knows how to convert into one), so there's no particular need to name them the same.

(Granted I still do wish that they were not called "initial_state" because that makes it necessary for the repr to call the field "initial_state" for eval(repr(x)) to work, which is weird when it's no longer the initial state. But that's a separate, smaller issue). 

Resolving patch:
diff --git a/cirq-core/cirq/sim/simulation_state.py b/cirq-core/cirq/sim/simulation_state.py
--- a/cirq-core/cirq/sim/simulation_state.py
+++ b/cirq-core/cirq/sim/simulation_state.py
@@ -173,27 +173,6 @@ def kronecker_product(self: TSelf, other: TSelf, *, inplace=False) -> TSelf:
         args._set_qubits(self.qubits + other.qubits)
         return args
 
-    def with_qubits(self: TSelf, qubits) -> TSelf:
-        """Extend current state space with added qubits.
-
-        The state of the added qubits is the default value set in the
-        subclasses. A new state space is created as the Kronecker product of
-        the original one and the added one.
-
-        Args:
-            qubits: The qubits to be added to the state space.
-
-        Returns:
-            A new subclass object containing the extended state space.
-        """
-        # TODO(#5721): Fix inconsistent usage of the `state` argument in the
-        # SimulationState base (required) and in its derived classes (unknown
-        # in StateVectorSimulationState), then remove the pylint filter below.
-        # pylint: disable=missing-kwoa
-        new_space = type(self)(qubits=qubits)  # type: ignore
-        # pylint: enable=missing-kwoa
-        return self.kronecker_product(new_space)
-
     def factor(
         self: TSelf, qubits: Sequence['cirq.Qid'], *, validate=True, atol=1e-07, inplace=False
     ) -> Tuple[TSelf, TSelf]:


Test patch:
diff --git a/cirq-core/cirq/sim/density_matrix_simulation_state_test.py b/cirq-core/cirq/sim/density_matrix_simulation_state_test.py
--- a/cirq-core/cirq/sim/density_matrix_simulation_state_test.py
+++ b/cirq-core/cirq/sim/density_matrix_simulation_state_test.py
@@ -73,20 +73,6 @@ class NoDetails:
         cirq.act_on(NoDetails(), args, qubits=())
 
 
-def test_with_qubits():
-    original = cirq.DensityMatrixSimulationState(
-        qubits=cirq.LineQubit.range(1), initial_state=1, dtype=np.complex64
-    )
-    extened = original.with_qubits(cirq.LineQubit.range(1, 2))
-    np.testing.assert_almost_equal(
-        extened.target_tensor,
-        cirq.density_matrix_kronecker_product(
-            np.array([[0, 0], [0, 1]], dtype=np.complex64),
-            np.array([[1, 0], [0, 0]], dtype=np.complex64),
-        ),
-    )
-
-
 def test_qid_shape_error():
     with pytest.raises(ValueError, match="qid_shape must be provided"):
         cirq.sim.density_matrix_simulation_state._BufferedDensityMatrix.create(initial_state=0)
diff --git a/cirq-core/cirq/sim/state_vector_simulation_state_test.py b/cirq-core/cirq/sim/state_vector_simulation_state_test.py
--- a/cirq-core/cirq/sim/state_vector_simulation_state_test.py
+++ b/cirq-core/cirq/sim/state_vector_simulation_state_test.py
@@ -269,20 +269,6 @@ def test_measured_mixture():
     assert results.histogram(key='flip') == results.histogram(key='m')
 
 
-def test_with_qubits():
-    original = cirq.StateVectorSimulationState(
-        qubits=cirq.LineQubit.range(2), initial_state=1, dtype=np.complex64
-    )
-    extened = original.with_qubits(cirq.LineQubit.range(2, 4))
-    np.testing.assert_almost_equal(
-        extened.target_tensor,
-        cirq.state_vector_kronecker_product(
-            np.array([[0.0 + 0.0j, 1.0 + 0.0j], [0.0 + 0.0j, 0.0 + 0.0j]], dtype=np.complex64),
-            np.array([[1.0 + 0.0j, 0.0 + 0.0j], [0.0 + 0.0j, 0.0 + 0.0j]], dtype=np.complex64),
-        ),
-    )
-
-
 def test_qid_shape_error():
     with pytest.raises(ValueError, match="qid_shape must be provided"):
         cirq.sim.state_vector_simulation_state._BufferedStateVector.create(initial_state=0)