Repository: Qiskit/qiskit

Issue:
Incorrectly removing necessary classical conditions
### Environment

- **Qiskit Terra version**: 0.25.2
- **Python version**: 3.10.9
- **Operating system**: MacOS 13.5 Ventura


### What is happening?

Hello, I'm working with the Qiskit transpiler and it erroneously removes necessary `if` conditions even when the optimization level is set to zero.

### How can we reproduce the issue?

To reproduce the issue, run the following Python code:
```python
import qiskit
from qiskit import QuantumCircuit, transpile, Aer

original_qasm = """OPENQASM 2.0;
include "qelib1.inc";
gate cx_o0 q0,q1 { x q0; cx q0,q1; x q0; }
qreg q[2];
creg c[1];
if(c==1) cx_o0 q[1],q[0];
measure q[0] -> c[0];"""

simulator = Aer.get_backend("aer_simulator")
circ = QuantumCircuit.from_qasm_str(original_qasm)
transpiled = transpile(circ, optimization_level=0)

print(transpiled.qasm())
```
### Result
```
OPENQASM 2.0;
include "qelib1.inc";
gate cx_o0 q0,q1 { x q0; cx q0,q1; x q0; }
qreg q[2];
creg c[1];
cx_o0 q[1],q[0];    // REMOVED: "if(c==1)"
measure q[0] -> c[0];
```

### What should happen?

The transpiler should not remove the conditional `if(c==1)` as this could lead to incorrect gate applications. The `cx_o0` gate only acts if the classical register c is equal to 1. Since there are no prior measurements to change the initial state of c, the condition `c==1` should be false and the `cx_o0` gate should not be executed.

The transpiled QASM code lacks the conditional `if(c==1)`, leading to the execution of the cx_o0 gate, which would flip `q[0]` to 1.

### Any suggestions?

_No response_


Discussion:
I think the bug here is actually in the OpenQASM 2 import - the conditional isn't even on `circ`, let alone `transpiled`.  Does that match with what you're seeing?
Oh no, I'm sorry that's not true at all - I was tricked by a separate bug in the circuit drawer, where the condition wasn't drawn!
It turns out that the transpiler is actually doing its job completely fine - the condition is there all the way through the pipelines - but on conversion to a circuit at the end of the transpilation, a (potentially spurious) deepcopy is losing the conditional in this particular case, where the conditioned gate in question is a custom gate generated by the OpenQASM 2 parser.  It's a pretty tricksy bug all in - thanks for the report.

Here's a minimal reproducer of the root cause:
```python
import qiskit.qasm2
import copy

qasm = """OPENQASM 2.0;
include "qelib1.inc";
gate cx_o0 q0,q1 { x q0; cx q0,q1; x q0; }
qreg q[2];
creg c[1];
if(c==1) cx_o0 q[1],q[0];
"""

circ = qiskit.qasm2.loads(qasm)
gate = circ.data[0].operation
print(gate.condition)
print(copy.deepcopy(gate).condition)
```
shows
```
(ClassicalRegister(1, 'c'), 1)
None
```
but the second line should be the same as the first.

Resolving patch:
diff --git a/qiskit/qasm2/parse.py b/qiskit/qasm2/parse.py
--- a/qiskit/qasm2/parse.py
+++ b/qiskit/qasm2/parse.py
@@ -324,14 +324,15 @@ def _define(self):
     # to pickle ourselves, we just eagerly create the definition and pickle that.
 
     def __getstate__(self):
-        return (self.name, self.num_qubits, self.params, self.definition)
+        return (self.name, self.num_qubits, self.params, self.definition, self.condition)
 
     def __setstate__(self, state):
-        name, num_qubits, params, definition = state
+        name, num_qubits, params, definition, condition = state
         super().__init__(name, num_qubits, params)
         self._gates = ()
         self._bytecode = ()
         self._definition = definition
+        self._condition = condition
 
 
 def _gate_builder(name, num_qubits, known_gates, bytecode):


Test patch:
diff --git a/test/python/qasm2/test_structure.py b/test/python/qasm2/test_structure.py
--- a/test/python/qasm2/test_structure.py
+++ b/test/python/qasm2/test_structure.py
@@ -12,6 +12,7 @@
 
 # pylint: disable=missing-module-docstring,missing-class-docstring,missing-function-docstring
 
+import copy
 import io
 import math
 import os
@@ -696,6 +697,32 @@ def test_qpy_double_call_roundtrip(self):
             loaded = qpy.load(fptr)[0]
         self.assertEqual(loaded, qc)
 
+    def test_deepcopy_conditioned_defined_gate(self):
+        program = """
+            include "qelib1.inc";
+            gate my_gate a {
+                x a;
+            }
+            qreg q[1];
+            creg c[1];
+            if (c == 1) my_gate q[0];
+        """
+        parsed = qiskit.qasm2.loads(program)
+        my_gate = parsed.data[0].operation
+
+        self.assertEqual(my_gate.name, "my_gate")
+        self.assertEqual(my_gate.condition, (parsed.cregs[0], 1))
+
+        copied = copy.deepcopy(parsed)
+        copied_gate = copied.data[0].operation
+        self.assertEqual(copied_gate.name, "my_gate")
+        self.assertEqual(copied_gate.condition, (copied.cregs[0], 1))
+
+        pickled = pickle.loads(pickle.dumps(parsed))
+        pickled_gate = pickled.data[0].operation
+        self.assertEqual(pickled_gate.name, "my_gate")
+        self.assertEqual(pickled_gate.condition, (pickled.cregs[0], 1))
+
 
 class TestOpaque(QiskitTestCase):
     def test_simple(self):