Repository: Qiskit/qiskit

Issue:
QuantumCircuit.bind/assign_parameters do not raise if passed key of invalid type
<!-- ⚠️ If you do not respect this template, your issue will be closed -->
<!-- ⚠️ Make sure to browse the opened and closed issues -->

### Information

- **Qiskit Terra version**: master @  507d3d8a95127064a6e5836d72912d965e0ad796
- **Python version**: 3.6
- **Operating system**: osx

### What is the current behavior?

```
>>> import qiskit as qk
>>> theta = qk.circuit.Parameter('theta')
>>> qc = qk.QuantumCircuit(1)
>>> qc.rx(theta, 0)
>>> qc.draw()
     ┌───────────┐
q_0: ┤ RX(theta) ├
     └───────────┘
```

Attempting to bind a Parameter not in the circuit correctly raises:
```
>>> phi = qk.circuit.Parameter('phi')
>>> qc.bind_parameters({phi: 0.1})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/kevin.krsulichibm.com/q/qiskit-terra/qiskit/circuit/quantumcircuit.py", line 2037, in bind_parameters
    return self.assign_parameters(value_dict)
  File "/Users/kevin.krsulichibm.com/q/qiskit-terra/qiskit/circuit/quantumcircuit.py", line 2015, in assign_parameters
    bound_circuit._assign_parameter(parameter, value)
  File "/Users/kevin.krsulichibm.com/q/qiskit-terra/qiskit/circuit/quantumcircuit.py", line 2061, in _assign_parameter
    for instr, param_index in self._parameter_table[parameter]:
  File "/Users/kevin.krsulichibm.com/q/qiskit-terra/qiskit/circuit/parametertable.py", line 35, in __getitem__
    return self._table[key]
KeyError: Parameter(phi)
```

Likewise attempting to bind a different Parameter instance of the same name correctly raises:
```
>>> qc.bind_parameters({qk.circuit.Parameter('theta'): 0.1})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/kevin.krsulichibm.com/q/qiskit-terra/qiskit/circuit/quantumcircuit.py", line 2037, in bind_parameters
    return self.assign_parameters(value_dict)
  File "/Users/kevin.krsulichibm.com/q/qiskit-terra/qiskit/circuit/quantumcircuit.py", line 2015, in assign_parameters
    bound_circuit._assign_parameter(parameter, value)
  File "/Users/kevin.krsulichibm.com/q/qiskit-terra/qiskit/circuit/quantumcircuit.py", line 2061, in _assign_parameter
    for instr, param_index in self._parameter_table[parameter]:
  File "/Users/kevin.krsulichibm.com/q/qiskit-terra/qiskit/circuit/parametertable.py", line 35, in __getitem__
    return self._table[key]
KeyError: Parameter(theta)
```

But attempting to bind using a non-Parameter type (like a string) silently passes:
```
>>> qc.bind_parameters({'theta': 0.1})
>>> qc.bind_parameters({'theta': 0.1}).draw()
     ┌───────────┐
q_0: ┤ RX(theta) ├
     └───────────┘
>>> qc.bind_parameters({None: 0.1}).draw()
     ┌───────────┐
q_0: ┤ RX(theta) ├
     └───────────┘
```

Likewise for `assign_parameters`:
```
>>> qc.assign_parameters({None: 0.1}).draw()
     ┌───────────┐
q_0: ┤ RX(theta) ├
     └───────────┘
```

### Steps to reproduce the problem



### What is the expected behavior?

Even the examples above which are correctly raising, are raising from the wrong place and with the wrong error type. It's expected we catch these cases here:

https://github.com/Qiskit/qiskit-terra/blob/7e40ed1174938955235a2d9d50ce2729c17c3d3e/qiskit/circuit/quantumcircuit.py#L2010

There are tests covering these cases, but they should updated to check the type of error raised and the message:

https://github.com/Qiskit/qiskit-terra/blob/44462a8b13ea6c2cce0f9c7345c26c15fb0d4ce3/test/python/circuit/test_parameters.py#L299

### Suggested solutions





Discussion:
I can work on this.

Resolving patch:
diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py
--- a/qiskit/circuit/quantumcircuit.py
+++ b/qiskit/circuit/quantumcircuit.py
@@ -1878,10 +1878,12 @@ def assign_parameters(self, param_dict, inplace=False):
         # unroll the parameter dictionary (needed if e.g. it contains a ParameterVector)
         unrolled_param_dict = self._unroll_param_dict(param_dict)
 
-        # check that only existing parameters are in the parameter dictionary
-        if unrolled_param_dict.keys() > self._parameter_table.keys():
+        # check that all param_dict items are in the _parameter_table for this circuit
+        params_not_in_circuit = [param_key for param_key in unrolled_param_dict
+                                 if param_key not in self._parameter_table.keys()]
+        if len(params_not_in_circuit) > 0:
             raise CircuitError('Cannot bind parameters ({}) not present in the circuit.'.format(
-                [str(p) for p in param_dict.keys() - self._parameter_table]))
+                ', '.join(map(str, params_not_in_circuit))))
 
         # replace the parameters with a new Parameter ("substitute") or numeric value ("bind")
         for parameter, value in unrolled_param_dict.items():
@@ -1912,14 +1914,15 @@ def bind_parameters(self, value_dict):
     def _unroll_param_dict(self, value_dict):
         unrolled_value_dict = {}
         for (param, value) in value_dict.items():
-            if isinstance(param, ParameterExpression):
-                unrolled_value_dict[param] = value
             if isinstance(param, ParameterVector):
                 if not len(param) == len(value):
                     raise CircuitError('ParameterVector {} has length {}, which '
                                        'differs from value list {} of '
                                        'len {}'.format(param, len(param), value, len(value)))
                 unrolled_value_dict.update(zip(param, value))
+            # pass anything else except number through. error checking is done in assign_parameter
+            elif isinstance(param, (ParameterExpression, str)) or param is None:
+                unrolled_value_dict[param] = value
         return unrolled_value_dict
 
     def _assign_parameter(self, parameter, value):


Test patch:
diff --git a/test/python/circuit/test_parameters.py b/test/python/circuit/test_parameters.py
--- a/test/python/circuit/test_parameters.py
+++ b/test/python/circuit/test_parameters.py
@@ -300,6 +300,7 @@ def test_raise_if_assigning_params_not_in_circuit(self):
         """Verify binding parameters which are not present in the circuit raises an error."""
         x = Parameter('x')
         y = Parameter('y')
+        z = ParameterVector('z', 3)
         qr = QuantumRegister(1)
         qc = QuantumCircuit(qr)
 
@@ -309,9 +310,12 @@ def test_raise_if_assigning_params_not_in_circuit(self):
             with self.subTest(assign_fun=assign_fun):
                 qc.p(0.1, qr[0])
                 self.assertRaises(CircuitError, getattr(qc, assign_fun), {x: 1})
-
                 qc.p(x, qr[0])
                 self.assertRaises(CircuitError, getattr(qc, assign_fun), {x: 1, y: 2})
+                qc.p(z[1], qr[0])
+                self.assertRaises(CircuitError, getattr(qc, assign_fun), {z: [3, 4, 5]})
+                self.assertRaises(CircuitError, getattr(qc, assign_fun), {'a_str': 6})
+                self.assertRaises(CircuitError, getattr(qc, assign_fun), {None: 7})
 
     def test_gate_multiplicity_binding(self):
         """Test binding when circuit contains multiple references to same gate"""