Repository: Qiskit/qiskit

Issue:
QuantumCircuit `.clear()` and `.copy_empty_like()` functionality
### What should we add?

Sometimes it can be useful to erase all instructions in a QuantumCircuit. This request is for:
1. [Clear functionality](https://github.com/pedrorrivero/qiskit-terra/blob/794db4792e97adca4a78eb4f3411c04e50665df7/qiskit/circuit/quantumcircuit.py#L2311-L2313) to erase all instructions from a QuantumCircuit → `.clear()`.
We not only need to clear all data, but (at least) also update the parameter table:
```python
self.data.clear()
self._parameter_table.clear()
```
2. [Copy empty functionality](https://github.com/Qiskit/qiskit-terra/blob/main/qiskit/dagcircuit/dagcircuit.py#L511-L538) to get a cleared copy of a QuantumCircuit → `.copy_empty_like()`.
This functionality is already implemented for DAGCircuit, and should retain all relevant circuit properties other than the instructions and associated parameters (e.g. registers, bits, name, metadata). Notice that `quantum_circuit.copy().clear()` would give the desired outcome, however `.copy()` performs a deepcopy of the circuit, which we would then erase almost completely when deleting the instructions, so this method should provide a more efficient way to do this.


TODO:
- [ ] `.clear()`
- [ ] `.copy_empty_like()`


Discussion:
@mtreinish 
Sounds like these could be useful, do you need this for e.g. copying attached calibrations or what's the usecase you have in mind? 🙂 
@Cryoris my particular use case is building new circuits based on an original one. For instance to duplicate instructions:
```python
def build_new_circuit(circuit: QuantumCircuit) -> QuantumCircuit:
    new_circuit = circuit.copy_empty_like()
    for instruction, qargs, cargs in circuit:
        new_circuit.append(instruction, qargs, cargs)
        new_circuit.append(instruction, qargs, cargs)
    return new_circuit
```
As you can see, it is important to retain the same quantum and classical registers, but also all relevant metadata as you mention. The easiest way forward is to create a cleared copy of the circuit.

I would therefore aim for `qc.copy_empty_like()` to be logically equivalent to `qc.copy().clear()`, but more efficient. I think this is the same behavior as in `DAGCircuit`.

Resolving patch:
diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py
--- a/qiskit/circuit/quantumcircuit.py
+++ b/qiskit/circuit/quantumcircuit.py
@@ -2118,18 +2118,9 @@ def copy(self, name: Optional[str] = None) -> "QuantumCircuit":
         Returns:
           QuantumCircuit: a deepcopy of the current circuit, with the specified name
         """
-        cpy = copy.copy(self)
-        # copy registers correctly, in copy.copy they are only copied via reference
-        cpy.qregs = self.qregs.copy()
-        cpy.cregs = self.cregs.copy()
-        cpy._qubits = self._qubits.copy()
-        cpy._ancillas = self._ancillas.copy()
-        cpy._clbits = self._clbits.copy()
-        cpy._qubit_indices = self._qubit_indices.copy()
-        cpy._clbit_indices = self._clbit_indices.copy()
+        cpy = self.copy_empty_like(name)
 
         instr_instances = {id(instr): instr for instr, _, __ in self._data}
-
         instr_copies = {id_: instr.copy() for id_, instr in instr_instances.items()}
 
         cpy._parameter_table = ParameterTable(
@@ -2147,6 +2138,35 @@ def copy(self, name: Optional[str] = None) -> "QuantumCircuit":
             for inst, qargs, cargs in self._data
         ]
 
+        return cpy
+
+    def copy_empty_like(self, name: Optional[str] = None) -> "QuantumCircuit":
+        """Return a copy of self with the same structure but empty.
+
+        That structure includes:
+            * name, calibrations and other metadata
+            * global phase
+            * all the qubits and clbits, including the registers
+
+        Args:
+            name (str): Name for the copied circuit. If None, then the name stays the same.
+
+        Returns:
+            QuantumCircuit: An empty copy of self.
+        """
+        cpy = copy.copy(self)
+        # copy registers correctly, in copy.copy they are only copied via reference
+        cpy.qregs = self.qregs.copy()
+        cpy.cregs = self.cregs.copy()
+        cpy._qubits = self._qubits.copy()
+        cpy._ancillas = self._ancillas.copy()
+        cpy._clbits = self._clbits.copy()
+        cpy._qubit_indices = self._qubit_indices.copy()
+        cpy._clbit_indices = self._clbit_indices.copy()
+
+        cpy._parameter_table = ParameterTable()
+        cpy._data = []
+
         cpy._calibrations = copy.deepcopy(self._calibrations)
         cpy._metadata = copy.deepcopy(self._metadata)
 
@@ -2154,6 +2174,14 @@ def copy(self, name: Optional[str] = None) -> "QuantumCircuit":
             cpy.name = name
         return cpy
 
+    def clear(self) -> None:
+        """Clear all instructions in self.
+
+        Clearing the circuits will keep the metadata and calibrations.
+        """
+        self._data.clear()
+        self._parameter_table.clear()
+
     def _create_creg(self, length: int, name: str) -> ClassicalRegister:
         """Creates a creg, checking if ClassicalRegister with same name exists"""
         if name in [creg.name for creg in self.cregs]:


Test patch:
diff --git a/test/python/circuit/test_circuit_operations.py b/test/python/circuit/test_circuit_operations.py
--- a/test/python/circuit/test_circuit_operations.py
+++ b/test/python/circuit/test_circuit_operations.py
@@ -27,6 +27,7 @@
 from qiskit.test import QiskitTestCase
 from qiskit.circuit.library.standard_gates import SGate
 from qiskit.quantum_info import Operator
+from qiskit.pulse import Schedule, Play, Gaussian, DriveChannel
 
 
 @ddt
@@ -478,6 +479,41 @@ def test_copy_copies_registers(self):
         self.assertEqual(len(qc.cregs), 1)
         self.assertEqual(len(copied.cregs), 2)
 
+    def test_copy_empty_like_circuit(self):
+        """Test copy_empty_like method makes a clear copy."""
+        qr = QuantumRegister(2)
+        cr = ClassicalRegister(2)
+        qc = QuantumCircuit(qr, cr, global_phase=1.0, name="qc", metadata={"key": "value"})
+        qc.h(qr[0])
+        qc.measure(qr[0], cr[0])
+        qc.measure(qr[1], cr[1])
+        sched = Schedule(Play(Gaussian(160, 0.1, 40), DriveChannel(0)))
+        qc.add_calibration("h", [0, 1], sched)
+        copied = qc.copy_empty_like()
+        qc.clear()
+
+        self.assertEqual(qc, copied)
+        self.assertEqual(qc.global_phase, copied.global_phase)
+        self.assertEqual(qc.name, copied.name)
+        self.assertEqual(qc.metadata, copied.metadata)
+        self.assertEqual(qc.calibrations, copied.calibrations)
+
+        copied = qc.copy_empty_like("copy")
+        self.assertEqual(copied.name, "copy")
+
+    def test_clear_circuit(self):
+        """Test clear method deletes instructions in circuit."""
+        qr = QuantumRegister(2)
+        cr = ClassicalRegister(2)
+        qc = QuantumCircuit(qr, cr)
+        qc.h(qr[0])
+        qc.measure(qr[0], cr[0])
+        qc.measure(qr[1], cr[1])
+        qc.clear()
+
+        self.assertEqual(len(qc.data), 0)
+        self.assertEqual(len(qc._parameter_table), 0)
+
     def test_measure_active(self):
         """Test measure_active
         Applies measurements only to non-idle qubits. Creates a ClassicalRegister of size equal to