Repository: Qiskit/qiskit

Issue:
Re-using parameterized gate instances breaks upon in-place parameter assigning
<!-- ⚠️ 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**: e03cb1b7fd 
- **Python version**: 3.7.7
- **Operating system**: macOS catalina

### What is the current behavior?

When placing the same parameterized gate into multiple circuits and binding the parameter value in place, the parameter in the gate instance is updated but the circuits containing this gate still have the old parameter in their parameter table (at least I think that's what's happening). This leads to issues as:
```python
>>> from qiskit.circuit.library import RXGate
>>> from qiskit.circuit import QuantumCircuit, Parameter
>>> a, b = Parameter('a'), Parameter('b')
>>> rx = RXGate(a)
>>> qc0, qc1 = QuantumCircuit(1), QuantumCircuit(1)
>>> qc0.append(rx, [0])
>>> qc1.append(rx, [0])
>>> qc0.assign_parameters({a: b}, inplace=True)
>>> qc1.to_gate()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/jul/Work/Qiskit/qiskit-terra/qiskit/circuit/quantumcircuit.py", line 836, in to_gate
    return circuit_to_gate(self, parameter_map, label=label)
  File "/Users/jul/Work/Qiskit/qiskit-terra/qiskit/converters/circuit_to_gate.py", line 88, in circuit_to_gate
    target = circuit.assign_parameters(parameter_dict, inplace=False)
  File "/Users/jul/Work/Qiskit/qiskit-terra/qiskit/circuit/quantumcircuit.py", line 1669, in assign_parameters
    bound_circuit._substitute_parameter(parameter, value)
  File "/Users/jul/Work/Qiskit/qiskit-terra/qiskit/circuit/quantumcircuit.py", line 1735, in _substitute_parameter
    new_param = instr.params[param_index].subs({old_parameter: new_parameter_expr})
  File "/Users/jul/Work/Qiskit/qiskit-terra/qiskit/circuit/parameter.py", line 55, in subs
    return parameter_map[self]
KeyError: Parameter(b)
```
I don't think this is very pressing but definitely should be fixed in the future.

### Suggested solutions

* Copy the gate parameters upon appending, or
* Make the circuit not explicitly store the parameters since they are already in the gate

These options have different behaviour, in the second one can change the parameter of the gates "from the outside" by modifying the gate instance. Probably the safer behaviour is therefore the first option.


Discussion:

Resolving patch:
diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py
--- a/qiskit/circuit/quantumcircuit.py
+++ b/qiskit/circuit/quantumcircuit.py
@@ -25,6 +25,7 @@
 from qiskit.util import is_main_process
 from qiskit.circuit.instruction import Instruction
 from qiskit.circuit.gate import Gate
+from qiskit.circuit.parameter import Parameter
 from qiskit.qasm.qasm import Qasm
 from qiskit.circuit.exceptions import CircuitError
 from .parameterexpression import ParameterExpression
@@ -823,6 +824,12 @@ def append(self, instruction, qargs=None, cargs=None):
         if not isinstance(instruction, Instruction) and hasattr(instruction, "to_instruction"):
             instruction = instruction.to_instruction()
 
+        # Make copy of parameterized gate instances
+        if hasattr(instruction, 'params'):
+            is_parameter = any([isinstance(param, Parameter) for param in instruction.params])
+            if is_parameter:
+                instruction = copy.deepcopy(instruction)
+
         expanded_qargs = [self.qbit_argument_conversion(qarg) for qarg in qargs or []]
         expanded_cargs = [self.cbit_argument_conversion(carg) for carg in cargs or []]
 


Test patch:
diff --git a/test/python/circuit/test_circuit_data.py b/test/python/circuit/test_circuit_data.py
--- a/test/python/circuit/test_circuit_data.py
+++ b/test/python/circuit/test_circuit_data.py
@@ -12,8 +12,8 @@
 
 """Test operations on circuit.data."""
 
-from qiskit.circuit import QuantumCircuit, QuantumRegister
-from qiskit.circuit.library import HGate, XGate, CXGate
+from qiskit.circuit import QuantumCircuit, QuantumRegister, Parameter
+from qiskit.circuit.library import HGate, XGate, CXGate, RXGate
 
 from qiskit.test import QiskitTestCase
 from qiskit.circuit.exceptions import CircuitError
@@ -396,3 +396,16 @@ def test_setting_data_is_validated(self):
             qc.data = [(HGate(), [qr[0], qr[1]], [])]
         with self.assertRaises(CircuitError):
             qc.data = [(HGate(), [], [qr[0]])]
+
+    def test_param_gate_instance(self):
+        """Verify that the same Parameter gate instance is not being used in
+           multiple circuits."""
+        a, b = Parameter('a'), Parameter('b')
+        rx = RXGate(a)
+        qc0, qc1 = QuantumCircuit(1), QuantumCircuit(1)
+        qc0.append(rx, [0])
+        qc1.append(rx, [0])
+        qc0.assign_parameters({a: b}, inplace=True)
+        qc0_instance = qc0._parameter_table[b][0][0]
+        qc1_instance = qc1._parameter_table[a][0][0]
+        self.assertNotEqual(qc0_instance, qc1_instance)
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
@@ -136,7 +136,7 @@ def test_get_parameters(self):
         vparams = qc._parameter_table
         self.assertEqual(len(vparams), 1)
         self.assertIs(theta, next(iter(vparams)))
-        self.assertIs(rxg, vparams[theta][0][0])
+        self.assertEqual(rxg, vparams[theta][0][0])
 
     def test_is_parameterized(self):
         """Test checking if a gate is parameterized (bound/unbound)"""