Repository: Qiskit/qiskit (Python)

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:

Patch:
diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py
index 029f5de9700b..81fd45f02769 100644
--- 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 []]
 
diff --git a/releasenotes/notes/4697-append-method-d4dc03b70257e99b.yaml b/releasenotes/notes/4697-append-method-d4dc03b70257e99b.yaml
new file mode 100644
index 000000000000..5cfe8c323562
--- /dev/null
+++ b/releasenotes/notes/4697-append-method-d4dc03b70257e99b.yaml
@@ -0,0 +1,14 @@
+---
+fixes:
+  - |
+    The ``append`` method can be used to insert one parameterized
+    gate instance into multiple circuits. This fix makes a copy
+    of parameterized gate instances when appended to a circuit 
+    to ensure that changing a parameter in one circuit will 
+    not affect other circuits. 
+  - |
+    Behavioral change: Altering a parameterized gate instance 
+    after being placed in a circuit will no longer affect the 
+    gate in said circuit. In other words, the parameterized 
+    gate object created outside of the circuit will not match 
+    the object placed in the circuit.


Test patch:
diff --git a/test/python/circuit/test_circuit_data.py b/test/python/circuit/test_circuit_data.py
index a3a4ede07eea..0af2394ea70a 100644
--- 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
index 689f759fcdfc..e1f84130b678 100644
--- 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)"""