Repository: Qiskit/qiskit

Issue:
Modify a copy of instruction instead of the same instance in circuit.data in QuantumCircuit.qasm() method.
<!-- ⚠️ If you do not respect this template, your issue will be closed -->
<!-- ⚠️ Make sure to browse the opened and closed issues to confirm this idea does not exist. -->

### What is the expected enhancement?

In `QuantumCircuit.qasm()` method, when id is added to the instruction name to make it unique if it is the same as an existing instruction. See: https://github.com/Qiskit/qiskit-terra/blob/34b8079844fb19ee719015681184233afb4f9f72/qiskit/circuit/quantumcircuit.py#L1465-L1468

It currently modifies the circuit data directly, which is not ideal. We could just store a copy of the instruction where we modified the name instead of storing the same instance that lives in circuit.data. See comment here by @Cryoris: https://github.com/Qiskit/qiskit-terra/pull/5887#discussion_r664023701

Modify a copy of instruction instead of the same instance in circuit.data in QuantumCircuit.qasm() method.
<!-- ⚠️ If you do not respect this template, your issue will be closed -->
<!-- ⚠️ Make sure to browse the opened and closed issues to confirm this idea does not exist. -->

### What is the expected enhancement?

In `QuantumCircuit.qasm()` method, when id is added to the instruction name to make it unique if it is the same as an existing instruction. See: https://github.com/Qiskit/qiskit-terra/blob/34b8079844fb19ee719015681184233afb4f9f72/qiskit/circuit/quantumcircuit.py#L1465-L1468

It currently modifies the circuit data directly, which is not ideal. We could just store a copy of the instruction where we modified the name instead of storing the same instance that lives in circuit.data. See comment here by @Cryoris: https://github.com/Qiskit/qiskit-terra/pull/5887#discussion_r664023701



Discussion:
Grabbing this one for my intern, if that's alright :)
Yes, sure. Please go ahead :)
I have to give it up -- onboarding took up the time I was hoping to use on this. Apologies! I should have anticipated that
Has this been addressed in another issue/PR? If not, I'd love to work on it.
Not that I know of. Go ahead!
Grabbing this one for my intern, if that's alright :)
Yes, sure. Please go ahead :)
I have to give it up -- onboarding took up the time I was hoping to use on this. Apologies! I should have anticipated that
Has this been addressed in another issue/PR? If not, I'd love to work on it.
Not that I know of. Go ahead!

Resolving patch:
diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py
--- a/qiskit/circuit/quantumcircuit.py
+++ b/qiskit/circuit/quantumcircuit.py
@@ -1564,8 +1564,8 @@ def qasm(
                     if instruction.name in [
                         instruction.name for instruction in existing_composite_circuits
                     ]:
-                        # append instruction id to name to make it unique
-                        instruction.name += f"_{id(instruction)}"
+                        # append instruction id to name of instruction copy to make it unique
+                        instruction = instruction.copy(name=f"{instruction.name}_{id(instruction)}")
 
                     existing_composite_circuits.append(instruction)
                     _add_sub_instruction_to_existing_composite_circuits(


Test patch:
diff --git a/test/python/circuit/test_circuit_qasm.py b/test/python/circuit/test_circuit_qasm.py
--- a/test/python/circuit/test_circuit_qasm.py
+++ b/test/python/circuit/test_circuit_qasm.py
@@ -330,3 +330,41 @@ def test_circuit_qasm_with_registerless_bits(self):
             if match:
                 qasm_register_names.add(match.group(1))
         self.assertEqual(len(qasm_register_names), 6)
+
+    def test_circuit_qasm_with_repeated_instruction_names(self):
+        """Test that qasm() doesn't change the name of the instructions that live in circuit.data,
+        but a copy of them when there are repeated names."""
+        qc = QuantumCircuit(2)
+        qc.h(0)
+        qc.x(1)
+        # Create some random custom gate and name it "custom"
+        custom = QuantumCircuit(1)
+        custom.h(0)
+        custom.y(0)
+        gate = custom.to_gate()
+        gate.name = "custom"
+        # Another random custom gate named "custom" as well
+        custom2 = QuantumCircuit(2)
+        custom2.x(0)
+        custom2.z(1)
+        gate2 = custom2.to_gate()
+        gate2.name = "custom"
+        # Append custom gates with same name to original circuit
+        qc.append(gate, [0])
+        qc.append(gate2, [1, 0])
+        # Expected qasm string will append the id to the second gate with repeated name
+        expected_qasm = f"""OPENQASM 2.0;
+include "qelib1.inc";
+gate custom q0 {{ h q0; y q0; }}
+gate custom_{id(gate2)} q0,q1 {{ x q0; z q1; }}
+qreg q[2];
+h q[0];
+x q[1];
+custom q[0];
+custom_{id(gate2)} q[1],q[0];\n"""
+        # Check qasm() produced the correct string
+        self.assertEqual(expected_qasm, qc.qasm())
+        # Check instruction names were not changed by qasm()
+        names = ["h", "x", "custom", "custom"]
+        for idx, (instruction, _, _) in enumerate(qc._data):
+            self.assertEqual(instruction.name, names[idx])