Repository: Qiskit/qiskit

Issue:
Error with RZXCalibrationBuilder circuit being combined with other circuits
<!-- ⚠️ 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**:0.17.0
- **Python version**:3.9.1
- **Operating system**:macOS Catalina

### What is the current behavior?
When combining a regular QuantumCircuit with a calibrated circuit, the calibrated circuit information is lost and the default gate is used instead


### Steps to reproduce the problem

from qiskit import IBMQ,QuantumCircuit,schedule,transpile
from qiskit.transpiler import PassManager
from qiskit.transpiler.passes import TemplateOptimization
from qiskit.transpiler.passes.scheduling import RZXCalibrationBuilder, rzx_templates


qc = QuantumCircuit(2)
qc.rzx(0.1,0,1)

pass_ = TemplateOptimization(**rzx_templates(['zz2']))
qc_cal = PassManager(pass_).run(qc)
pass_ = RZXCalibrationBuilder(backend)
qc_cal = PassManager(pass_).run(qc_cal)

qc2 = QuantumCircuit(2)
qc2 +=qc_cal
print (schedule(transpile(qc,backend),backend).duration)
print (schedule(transpile(qc_cal,backend),backend).duration)
print (schedule(transpile(qc2,backend),backend).duration)



### What is the expected behavior?
The scheduled circuit duration should be the same for all three schedules.


### Suggested solutions





Discussion:
I can reproduce this without adding the complexity of the transpiler, e.g.
```python
from qiskit import QuantumCircuit
from qiskit.circuit.library import RZXGate
from qiskit.pulse import Schedule

qc_cal = QuantumCircuit(2)
qc_cal.rzx(0.5, 0, 1)
qc_cal.add_calibration(RZXGate, (0, 1), params=[0.5], schedule=Schedule())
print(qc_cal.calibrations)

qc = QuantumCircuit(2)
qc += qc_cal
print(qc.calibrations)
```
which results in
```python
{<class 'qiskit.circuit.library.standard_gates.rzx.RZXGate'>: {((0, 1), (0.5,)): Schedule(, name="sched4")}}
{}
```
So it appears that `QuantumCircuit`'s `+=` is not keeping the calibrations.

Resolving patch:
diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py
--- a/qiskit/circuit/quantumcircuit.py
+++ b/qiskit/circuit/quantumcircuit.py
@@ -566,6 +566,11 @@ def combine(self, rhs):
         for instruction_context in itertools.chain(self.data, rhs.data):
             circuit._append(*instruction_context)
         circuit.global_phase = self.global_phase + rhs.global_phase
+
+        for gate, cals in rhs.calibrations.items():
+            for key, sched in cals.items():
+                circuit.add_calibration(gate, qubits=key[0], schedule=sched, params=key[1])
+
         return circuit
 
     def extend(self, rhs):
@@ -610,6 +615,11 @@ def extend(self, rhs):
         for instruction_context in data:
             self._append(*instruction_context)
         self.global_phase += rhs.global_phase
+
+        for gate, cals in rhs.calibrations.items():
+            for key, sched in cals.items():
+                self.add_calibration(gate, qubits=key[0], schedule=sched, params=key[1])
+
         return self
 
     def compose(self, other, qubits=None, clbits=None, front=False, inplace=False):


Test patch:
diff --git a/test/python/circuit/test_calibrations.py b/test/python/circuit/test_calibrations.py
new file mode 100644
--- /dev/null
+++ b/test/python/circuit/test_calibrations.py
@@ -0,0 +1,51 @@
+# This code is part of Qiskit.
+#
+# (C) Copyright IBM 2021.
+#
+# This code is licensed under the Apache License, Version 2.0. You may
+# obtain a copy of this license in the LICENSE.txt file in the root directory
+# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
+#
+# Any modifications or derivative works of this code must retain this
+# copyright notice, and modified files need to carry a notice indicating
+# that they have been altered from the originals.
+
+"""Test calibrations in quantum circuits."""
+
+import unittest
+
+from qiskit.pulse import Schedule
+from qiskit.circuit import QuantumCircuit
+from qiskit.circuit.library import RZXGate
+from qiskit.test import QiskitTestCase
+
+
+class TestCalibrations(QiskitTestCase):
+    """Test composition of two circuits."""
+
+    def test_iadd(self):
+        """Test that __iadd__ keeps the calibrations."""
+        qc_cal = QuantumCircuit(2)
+        qc_cal.rzx(0.5, 0, 1)
+        qc_cal.add_calibration(RZXGate, (0, 1), params=[0.5], schedule=Schedule())
+
+        qc = QuantumCircuit(2)
+        qc += qc_cal
+
+        self.assertEqual(qc.calibrations[RZXGate], {((0, 1), (0.5,)): Schedule(name="test")})
+        self.assertEqual(qc_cal.calibrations, qc.calibrations)
+
+    def test_add(self):
+        """Test that __add__ keeps the calibrations."""
+        qc_cal = QuantumCircuit(2)
+        qc_cal.rzx(0.5, 0, 1)
+        qc_cal.add_calibration(RZXGate, (0, 1), params=[0.5], schedule=Schedule())
+
+        qc = QuantumCircuit(2) + qc_cal
+
+        self.assertEqual(qc.calibrations[RZXGate], {((0, 1), (0.5,)): Schedule(name="test")})
+        self.assertEqual(qc_cal.calibrations, qc.calibrations)
+
+
+if __name__ == '__main__':
+    unittest.main()