Repository: Qiskit/qiskit

Issue:
Transpiling parameterized circuits for device backends raises
```
>>> qobj = qk.compile(qc, backend=FakeTokyo())
/Users/kevin.krsulichibm.com/q/qiskit-terra/qiskit/tools/compiler.py:50: DeprecationWarning: qiskit.compile() is deprecated and will be removed in Qiskit Terra 0.9. Please use qiskit.transpile() to transform circuits and qiskit.assemble_circuits() to produce qobj.
  DeprecationWarning)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/kevin.krsulichibm.com/q/qiskit-terra/qiskit/tools/compiler.py", line 67, in compile
    initial_layout, seed_mapper, pass_manager)
  File "/Users/kevin.krsulichibm.com/q/qiskit-terra/qiskit/transpiler/transpiler.py", line 85, in transpile
    'pass_manager': pass_manager})
  File "/Users/kevin.krsulichibm.com/q/qiskit-terra/qiskit/tools/parallel.py", line 93, in parallel_map
    return [task(values[0], *task_args, **task_kwargs)]
  File "/Users/kevin.krsulichibm.com/q/qiskit-terra/qiskit/transpiler/transpiler.py", line 143, in _transpilation
    pass_manager=pass_manager)
  File "/Users/kevin.krsulichibm.com/q/qiskit-terra/qiskit/transpiler/transpiler.py", line 234, in transpile_dag
    dag = pass_manager.run_passes(dag)
  File "/Users/kevin.krsulichibm.com/q/qiskit-terra/qiskit/transpiler/passmanager.py", line 129, in run_passes
    dag = self._do_pass(pass_, dag, passset.options)
  File "/Users/kevin.krsulichibm.com/q/qiskit-terra/qiskit/transpiler/passmanager.py", line 169, in _do_pass
    new_dag = pass_.run(dag)
  File "/Users/kevin.krsulichibm.com/q/qiskit-terra/qiskit/transpiler/passes/optimize_1q_gates.py", line 62, in run
    left_parameters = tuple([float(x) for x in left_parameters])
  File "/Users/kevin.krsulichibm.com/q/qiskit-terra/qiskit/transpiler/passes/optimize_1q_gates.py", line 62, in <listcomp>
    left_parameters = tuple([float(x) for x in left_parameters])
  File "/Users/kevin.krsulichibm.com/q/qiskit-terra/lib/python3.5/site-packages/sympy/core/expr.py", line 256, in __float__
    raise TypeError("can't convert expression to float")
TypeError: can't convert expression to float
```


Discussion:
I'm not sure this is going to be fixable, we switched optimize 1q gates in #1738 to resolve things numerically which resulted in a very significant speed up. If we're passing in sympy expressions for parameters which are not able to represented as a float I don't see how we can run optimize 1q on them short of adding back sympy to the pass (which I don't want to do) so that we can call simplify() in the hopes that the terms in the expressions for gates cancel without needing the undefined parameter in the expression to do the evaluation. (I'm not sure if it could provide any meaningful optimization if the parameters aren't defined though)
We need to make the transpiler be smart here and use a PassManager that does not invoke `Optimize1qGates` (or any other pass that involves numerical optimizations based on gate parameters -- another example is the `ConsolidateBlocks` pass in #2134) 

Resolving patch:
diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py
--- a/qiskit/circuit/quantumcircuit.py
+++ b/qiskit/circuit/quantumcircuit.py
@@ -676,6 +676,14 @@ def assign_variables(self, value_dict):
             new_circuit.variable_table[variable] = value_dict
         return new_circuit
 
+    @property
+    def unassigned_variables(self):
+        """Returns a set containing any variables which have not yet been assigned."""
+        return {variable
+                for variable, parameterized_instructions in self.variable_table.items()
+                if any(instruction.params[parameter_index].free_symbols
+                       for instruction, parameter_index in parameterized_instructions)}
+
 
 def _circuit_from_qasm(qasm):
     # pylint: disable=cyclic-import
diff --git a/qiskit/transpiler/preset_passmanagers/default.py b/qiskit/transpiler/preset_passmanagers/default.py
--- a/qiskit/transpiler/preset_passmanagers/default.py
+++ b/qiskit/transpiler/preset_passmanagers/default.py
@@ -25,7 +25,8 @@
 from ..passes.mapping.extend_layout import ExtendLayout
 
 
-def default_pass_manager(basis_gates, coupling_map, initial_layout, seed_mapper):
+def default_pass_manager(basis_gates, coupling_map, initial_layout,
+                         skip_numeric_passes, seed_mapper):
     """
     The default pass manager that maps to the coupling map.
 
@@ -33,6 +34,7 @@ def default_pass_manager(basis_gates, coupling_map, initial_layout, seed_mapper)
         basis_gates (list[str]): list of basis gate names supported by the
             target. Default: ['u1','u2','u3','cx','id']
         initial_layout (Layout or None): If None, trivial layout will be chosen.
+        skip_numeric_passes (bool): If true, skip passes which require fixed parameter values
         coupling_map (CouplingMap): coupling map (perhaps custom) to target
             in mapping.
         seed_mapper (int or None): random seed for the swap_mapper.
@@ -72,8 +74,14 @@ def default_pass_manager(basis_gates, coupling_map, initial_layout, seed_mapper)
     pass_manager.append(Unroller(['u1', 'u2', 'u3', 'id', 'cx']))
 
     # Simplify single qubit gates and CXs
-    pass_manager.append([Optimize1qGates(), CXCancellation(), Depth(), FixedPoint('depth')],
+    if not skip_numeric_passes:
+        simplification_passes = [Optimize1qGates(), CXCancellation()]
+    else:
+        simplification_passes = [CXCancellation()]
+
+    pass_manager.append(simplification_passes + [Depth(), FixedPoint('depth')],
                         do_while=lambda property_set: not property_set['depth_fixed_point'])
+
     return pass_manager
 
 
diff --git a/qiskit/transpiler/transpiler.py b/qiskit/transpiler/transpiler.py
--- a/qiskit/transpiler/transpiler.py
+++ b/qiskit/transpiler/transpiler.py
@@ -101,12 +101,15 @@ def _transpilation(circuit, basis_gates=None, coupling_map=None,
     if pass_manager and not pass_manager.working_list:
         return circuit
 
+    is_parametric_circuit = bool(circuit.unassigned_variables)
+
     dag = circuit_to_dag(circuit)
     del circuit
 
     final_dag = transpile_dag(dag, basis_gates=basis_gates,
                               coupling_map=coupling_map,
                               initial_layout=initial_layout,
+                              skip_numeric_passes=is_parametric_circuit,
                               seed_mapper=seed_mapper,
                               pass_manager=pass_manager)
 
@@ -117,7 +120,8 @@ def _transpilation(circuit, basis_gates=None, coupling_map=None,
 
 # pylint: disable=redefined-builtin
 def transpile_dag(dag, basis_gates=None, coupling_map=None,
-                  initial_layout=None, seed_mapper=None, pass_manager=None):
+                  initial_layout=None, skip_numeric_passes=None,
+                  seed_mapper=None, pass_manager=None):
     """Transform a dag circuit into another dag circuit (transpile), through
     consecutive passes on the dag.
 
@@ -135,6 +139,7 @@ def transpile_dag(dag, basis_gates=None, coupling_map=None,
             eg. [[0, 2], [1, 2], [1, 3], [3, 4]}
 
         initial_layout (Layout or None): A layout object
+        skip_numeric_passes (bool): If true, skip passes which require fixed parameter values
         seed_mapper (int): random seed_mapper for the swap mapper
         pass_manager (PassManager): pass manager instance for the transpilation process
             If None, a default set of passes are run.
@@ -164,6 +169,7 @@ def transpile_dag(dag, basis_gates=None, coupling_map=None,
             pass_manager = default_pass_manager(basis_gates,
                                                 CouplingMap(coupling_map),
                                                 initial_layout,
+                                                skip_numeric_passes,
                                                 seed_mapper=seed_mapper)
         else:
             pass_manager = default_pass_manager_simulator(basis_gates)


Test patch:
diff --git a/test/python/circuit/test_variable_parameters.py b/test/python/circuit/test_variable_parameters.py
--- a/test/python/circuit/test_variable_parameters.py
+++ b/test/python/circuit/test_variable_parameters.py
@@ -177,3 +177,29 @@ def test_parameter_expression_through_composite_instructions(self):
             self.assertEqual(circs[index].data[0][0].params[0], ones)
             self.assertEqual(circs[index].data[0][0].params[1], ones + tens)
             self.assertEqual(circs[index].data[0][0].params[2], -ones)
+
+    def test_unassigned_variables(self):
+        """Test to check for presence of unassigned variables."""
+        x = sympy.Symbol('x')
+        y = sympy.Symbol('y')
+        qr = QuantumRegister(1, name='qr')
+        qc = QuantumCircuit(qr)
+
+        self.assertEqual(len(qc.unassigned_variables), 0)
+
+        qc.h(qr[0])
+        self.assertEqual(len(qc.unassigned_variables), 0)
+
+        qc.rx(x, qr[0])
+        self.assertEqual(len(qc.unassigned_variables), 1)
+        self.assertIs(x, next(iter(qc.unassigned_variables)))
+
+        qc.variable_table[x] = 0
+        self.assertEqual(len(qc.unassigned_variables), 0)
+
+        qc.rx(y, qr[0])
+        self.assertEqual(len(qc.unassigned_variables), 1)
+        self.assertIs(y, next(iter(qc.unassigned_variables)))
+
+        qc.variable_table[y] = 0.1
+        self.assertEqual(len(qc.unassigned_variables), 0)
diff --git a/test/python/transpiler/test_transpile.py b/test/python/transpiler/test_transpile.py
--- a/test/python/transpiler/test_transpile.py
+++ b/test/python/transpiler/test_transpile.py
@@ -12,6 +12,7 @@
 import math
 from unittest.mock import patch
 
+import sympy
 from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit
 from qiskit import compile, BasicAer
 from qiskit.extensions.standard import CnotGate
@@ -492,3 +493,34 @@ def test_wrong_initial_layout(self):
 
         self.assertRaises(TranspilerError, transpile,
                           qc, backend, initial_layout=bad_initial_layout)
+
+    def test_parameterized_circuit_for_simulator(self):
+        """Verify that a parameterized circuit can be transpiled for a simulator backend."""
+        qr = QuantumRegister(2, name='qr')
+        qc = QuantumCircuit(qr)
+
+        theta = sympy.Symbol('theta')
+        qc.rz(theta, qr[0])
+
+        transpiled_qc = transpile(qc, backend=BasicAer.get_backend('qasm_simulator'))
+
+        expected_qc = QuantumCircuit(qr)
+        expected_qc.u1(theta, qr[0])
+
+        self.assertEqual(expected_qc, transpiled_qc)
+
+    def test_parameterized_circuit_for_device(self):
+        """Verify that a parameterized circuit can be transpiled for a device backend."""
+        qr = QuantumRegister(2, name='qr')
+        qc = QuantumCircuit(qr)
+
+        theta = sympy.Symbol('theta')
+        qc.rz(theta, qr[0])
+
+        transpiled_qc = transpile(qc, backend=FakeMelbourne())
+
+        qr = QuantumRegister(14, 'q')
+        expected_qc = QuantumCircuit(qr)
+        expected_qc.u1(theta, qr[0])
+
+        self.assertEqual(expected_qc, transpiled_qc)