Repository: quantumlib/Cirq

Issue:
Give a descriptive error when asked to simulate a circuit with unresolved parameters
I think the reason https://github.com/quantumlib/Cirq/issues/1514 was reported as a bug is because the current error that results from this mistake is so bad that it looks like a bug in cirq. Both cirq.Simulator and cirq.google.XmonSimulator should check if `cirq.is_parameterized(circuit)` before continuing into the numerical simulation, and if so then raise an exception describing the problem.

This error should raise both when attempting to `run` a parameterized circuit (e.g. you forgot to use `run_sweep`) and when sweeping and you forget a parameter.


Discussion:
Here is a repro:

```
import cirq
import sympy.abc
q = cirq.LineQubit(0)
c = cirq.Circuit.from_ops(cirq.X(q)**sympy.abc.x)
cirq.Simulator().run(c)
```

Errors with this:

```
TypeError: Can't simulate operations that don't implement SupportsUnitary, SupportsApplyUnitary, SupportsMixture, SupportsChannel or is a measurement: (cirq.X**sympy.Symbol('x')).on(cirq.LineQubit(0))
```

But should actually error with this:

```
ValueError: Can't simulate. Circuit has unresolved parameterized operations. Check that you passed a parameter resolver, and that it specifies all the necessary parameters.

Here are the unresolved operations: [...]
```
Thanks very much for the context @Strilanc.  I would be more than happy to give this one a try.
It's all yours!

Resolving patch:
diff --git a/cirq/sim/density_matrix_simulator.py b/cirq/sim/density_matrix_simulator.py
--- a/cirq/sim/density_matrix_simulator.py
+++ b/cirq/sim/density_matrix_simulator.py
@@ -146,6 +146,7 @@ def _run(self, circuit: circuits.Circuit,
         param_resolver = param_resolver or study.ParamResolver({})
         resolved_circuit = protocols.resolve_parameters(circuit,
                                                         param_resolver)
+        self._check_all_resolved(resolved_circuit)
 
         if circuit.are_all_measurements_terminal():
             return self._run_sweep_sample(resolved_circuit, repetitions)
@@ -198,6 +199,7 @@ def _simulator_iterator(self, circuit: circuits.Circuit,
         """
         param_resolver = param_resolver or study.ParamResolver({})
         resolved_circuit = protocols.resolve_parameters(circuit, param_resolver)
+        self._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,
@@ -401,6 +403,17 @@ def compute_displays_sweep(
 
         return compute_displays_results
 
+    def _check_all_resolved(self, circuit):
+        """Raises if the circuit contains unresolved symbols."""
+        if protocols.is_parameterized(circuit):
+            unresolved = [
+                op for moment in circuit for op in moment
+                if protocols.is_parameterized(op)
+            ]
+            raise ValueError(
+                'Circuit contains ops whose symbols were not specified in '
+                'parameter sweep. Ops: {}'.format(unresolved))
+
 
 def _enter_moment_display_values_into_dictionary(
         display_values: Dict,
diff --git a/cirq/sim/sparse_simulator.py b/cirq/sim/sparse_simulator.py
--- a/cirq/sim/sparse_simulator.py
+++ b/cirq/sim/sparse_simulator.py
@@ -148,6 +148,8 @@ def _run(
         """See definition in `cirq.SimulatesSamples`."""
         param_resolver = param_resolver or study.ParamResolver({})
         resolved_circuit = protocols.resolve_parameters(circuit, param_resolver)
+        self._check_all_resolved(resolved_circuit)
+
         def measure_or_mixture(op):
             return protocols.is_measurement(op) or protocols.has_mixture(op)
         if circuit.are_all_matches_terminal(measure_or_mixture):
@@ -206,6 +208,7 @@ def _simulator_iterator(
         """
         param_resolver = param_resolver or study.ParamResolver({})
         resolved_circuit = protocols.resolve_parameters(circuit, param_resolver)
+        self._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,
@@ -324,6 +327,17 @@ def _simulate_mixture(self, op: ops.Operation, data: _StateAndBuffer,
         data.buffer = data.state
         data.state = result
 
+    def _check_all_resolved(self, circuit):
+        """Raises if the circuit contains unresolved symbols."""
+        if protocols.is_parameterized(circuit):
+            unresolved = [
+                op for moment in circuit for op in moment
+                if protocols.is_parameterized(op)
+            ]
+            raise ValueError(
+                'Circuit contains ops whose symbols were not specified in '
+                'parameter sweep. Ops: {}'.format(unresolved))
+
 
 class SparseSimulatorStep(wave_function.StateVectorMixin,
                           wave_function_simulator.WaveFunctionStepResult):


Test patch:
diff --git a/cirq/sim/density_matrix_simulator_test.py b/cirq/sim/density_matrix_simulator_test.py
--- a/cirq/sim/density_matrix_simulator_test.py
+++ b/cirq/sim/density_matrix_simulator_test.py
@@ -675,6 +675,24 @@ def test_density_matrix_trial_result_str():
                                     '[[0.50.5][0.50.5]]')
 
 
+def test_run_sweep_parameters_not_resolved():
+    a = cirq.LineQubit(0)
+    simulator = cirq.DensityMatrixSimulator()
+    circuit = cirq.Circuit.from_ops(
+        cirq.XPowGate(exponent=sympy.Symbol('a'))(a), cirq.measure(a))
+    with pytest.raises(ValueError, match='symbols were not specified'):
+        _ = simulator.run_sweep(circuit, cirq.ParamResolver({}))
+
+
+def test_simulate_sweep_parameters_not_resolved():
+    a = cirq.LineQubit(0)
+    simulator = cirq.DensityMatrixSimulator()
+    circuit = cirq.Circuit.from_ops(
+        cirq.XPowGate(exponent=sympy.Symbol('a'))(a), cirq.measure(a))
+    with pytest.raises(ValueError, match='symbols were not specified'):
+        _ = simulator.simulate_sweep(circuit, cirq.ParamResolver({}))
+
+
 def test_random_seed():
     sim = cirq.DensityMatrixSimulator(seed=1234)
     a = cirq.NamedQubit('a')
diff --git a/cirq/sim/sparse_simulator_test.py b/cirq/sim/sparse_simulator_test.py
--- a/cirq/sim/sparse_simulator_test.py
+++ b/cirq/sim/sparse_simulator_test.py
@@ -698,6 +698,24 @@ def test_compute_amplitudes():
     np.testing.assert_allclose(np.array(result), np.array([-0.5, 0.5, -0.5]))
 
 
+def test_run_sweep_parameters_not_resolved():
+    a = cirq.LineQubit(0)
+    simulator = cirq.Simulator()
+    circuit = cirq.Circuit.from_ops(
+        cirq.XPowGate(exponent=sympy.Symbol('a'))(a), cirq.measure(a))
+    with pytest.raises(ValueError, match='symbols were not specified'):
+        _ = simulator.run_sweep(circuit, cirq.ParamResolver({}))
+
+
+def test_simulate_sweep_parameters_not_resolved():
+    a = cirq.LineQubit(0)
+    simulator = cirq.Simulator()
+    circuit = cirq.Circuit.from_ops(
+        cirq.XPowGate(exponent=sympy.Symbol('a'))(a), cirq.measure(a))
+    with pytest.raises(ValueError, match='symbols were not specified'):
+        _ = simulator.simulate_sweep(circuit, cirq.ParamResolver({}))
+
+
 def test_random_seed():
     sim = cirq.Simulator(seed=1234)
     a = cirq.NamedQubit('a')