Repository: Qiskit/qiskit

Issue:
ccz roundtrip in OpenQASM 2 broken
@ryahill1 notices that roundtrip in OpenQASM2 for several gates is broken (Originally posted in https://github.com/Qiskit/qiskit-terra/issues/9559#issuecomment-1424824806)

One of those cases is [ccz](https://github.com/Qiskit/qiskit-terra/blob/d4e7144efa9c661817161f84553313bf39406fac/qiskit/circuit/library/standard_gates/z.py#L203):

```python
import qiskit

qc = qiskit.QuantumCircuit(3)
gate = qiskit.circuit.library.CCZGate()
qc.append(gate, qargs=qc.qubits)

qasm = qc.qasm()
print(qasm)
qc_from_qasm = qiskit.QuantumCircuit.from_qasm_str(qasm)
print(qc_from_qasm)
```
```
qiskit.qasm.exceptions.QasmError: "Cannot find gate definition for 'ccz', line 4 file "
```

csdg roundtrip in OpenQASM 2 broken
### Environment

- **Qiskit Terra version**: 0.23.1
- **Python version**: 3.9.15
- **Operating system**: macOS Ventura


### What is happening?

Qiskit supports the [`CSdgGate`](https://github.com/Qiskit/qiskit-terra/blob/d4e7144efa9c661817161f84553313bf39406fac/qiskit/circuit/library/standard_gates/s.py#L228), but raises error when creating a circuit from a qasm file that includes a `csdg` gate. Issue may exist for other gates as well, but so far this is the only one I've identified.

### How can we reproduce the issue?

```python
import os
from qiskit import QuantumCircuit

workdir = os.path.dirname(__file__)
qasm_file = os.path.join(workdir, "test.qasm")

circuit = QuantumCircuit(2)
circuit.csdg(0, 1)

qasm_str = circuit.qasm()

with open(qasm_file, "w") as f:
    f.write(qasm_str)

new_circuit = QuantumCircuit().from_qasm_file(qasm_file)

print(new_circuit)
```
```
Traceback (most recent call last):
...
 ...
  File ".../lib/python3.9/site-packages/qiskit/qasm/qasmparser.py", line 138, in verify_as_gate
    raise QasmError(
qiskit.qasm.exceptions.QasmError: "Cannot find gate definition for 'csdg', line 4 file line 4 file test.qasm"
```

### What should happen?

Program should print out new circuit without error

<img width="177" alt="Screenshot 2023-02-08 at 11 09 51 PM" src="https://user-images.githubusercontent.com/46977852/217723450-2eb6bac6-4364-4ac0-ba41-01907d765944.png">


### Any suggestions?

_No response_


Discussion:
(Copied over): this doesn't need a separate issue.  I commented the fix for #9559 at the bottom, and it's trivially the same for the three concerned gates.
The bug here is actually in the OpenQASM 2 output rather than the input - Qiskit should have emitted a file that includes a definition for `csdg`, because it's not defined in the standard-library `qelib1.inc` file. The parser is correctly complaining that the OpenQASM input doesn't define that gate.
Ok, gotcha. I did some more testing and the same problem seems to occur for 4 other (a total of 5) gates:

- [ ]  [`rzx`](https://github.com/Qiskit/qiskit-terra/blob/d4e7144efa9c661817161f84553313bf39406fac/qiskit/circuit/library/standard_gates/rzx.py#L21)
- [ ]  [`ccz`](https://github.com/Qiskit/qiskit-terra/blob/d4e7144efa9c661817161f84553313bf39406fac/qiskit/circuit/library/standard_gates/z.py#L203) https://github.com/Qiskit/qiskit-terra/issues/9721
- [x]  [`c3sx`](https://github.com/Qiskit/qiskit-terra/blob/d4e7144efa9c661817161f84553313bf39406fac/qiskit/circuit/library/standard_gates/x.py#L487) (#9183)
- [ ]  [`cs`](https://github.com/Qiskit/qiskit-terra/blob/d4e7144efa9c661817161f84553313bf39406fac/qiskit/circuit/library/standard_gates/s.py#L154) https://github.com/Qiskit/qiskit-terra/issues/9722
- [ ]   [`csdg`](https://github.com/Qiskit/qiskit-terra/blob/d4e7144efa9c661817161f84553313bf39406fac/qiskit/circuit/library/standard_gates/s.py#L228) https://github.com/Qiskit/qiskit-terra/issues/9723
Let's break this one in 4 other issues!
rzx seems to work, I think:
```python
import qiskit

qc = qiskit.QuantumCircuit(2)
gate = qiskit.circuit.library.RZXGate(0)
qc.append(gate, qargs=qc.qubits)

qasm = qc.qasm()
print(qasm)
qc_from_qasm = qiskit.QuantumCircuit.from_qasm_str(qasm)
print(qc_from_qasm)
```
```
OPENQASM 2.0;
include "qelib1.inc";
gate rzx(param0) q0,q1 { h q1; cx q0,q1; rz(0) q1; cx q0,q1; h q1; }
qreg q[2];
rzx(0) q[0],q[1];

     ┌─────────┐
q_0: ┤0        ├
     │  rzx(0) │
q_1: ┤1        ├
     └─────────┘
```

Did we need separate issues for all of these?  They're all the exact same thing - the OQ2 exporter considers them "predefined" gates, but they're not.  The solution is just to delete one corresponding line each from `QuantumCircuit.qasm.<locals>.existing_gate_names`.
For completeness, the full list of those that won't work right because of the reason I just gave is "ccz", "cs" and "csdg".  "rzx" will reimport in the expected manner; it won't be mapped to `RZXGate` fully because it's not in (Terra's mutated) `qelib1.inc`, but it will get pulled back in semi-correctly, up to the failures of Terra to handle parametrised gates properly during export.

Resolving patch:
diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py
--- a/qiskit/circuit/quantumcircuit.py
+++ b/qiskit/circuit/quantumcircuit.py
@@ -1623,7 +1623,6 @@ def qasm(
             "sx",
             "sxdg",
             "cz",
-            "ccz",
             "cy",
             "swap",
             "ch",
@@ -1636,8 +1635,6 @@ def qasm(
             "cp",
             "cu3",
             "csx",
-            "cs",
-            "csdg",
             "cu",
             "rxx",
             "rzz",


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
@@ -10,7 +10,7 @@
 # copyright notice, and modified files need to carry a notice indicating
 # that they have been altered from the originals.
 
-"""Test Qiskit's QuantumCircuit class."""
+"""Test Qiskit's gates in QASM2."""
 
 import unittest
 from math import pi
@@ -19,7 +19,7 @@
 from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit
 from qiskit.test import QiskitTestCase
 from qiskit.circuit import Parameter, Qubit, Clbit, Gate
-from qiskit.circuit.library import C3SXGate
+from qiskit.circuit.library import C3SXGate, CCZGate, CSGate, CSdgGate, RZXGate
 from qiskit.qasm.exceptions import QasmError
 
 # Regex pattern to match valid OpenQASM identifiers
@@ -27,7 +27,7 @@
 
 
 class TestCircuitQasm(QiskitTestCase):
-    """QuantumCircuit Qasm tests."""
+    """QuantumCircuit QASM2 tests."""
 
     def test_circuit_qasm(self):
         """Test circuit qasm() method."""
@@ -249,7 +249,9 @@ def test_circuit_qasm_with_composite_circuit_with_many_params_and_qubits(self):
         self.assertEqual(original_str, qc.qasm())
 
     def test_c3sxgate_roundtrips(self):
-        """Test that C3SXGate correctly round trips.  Qiskit gives this gate a different name
+        """Test that C3SXGate correctly round trips.
+
+        Qiskit gives this gate a different name
         ('c3sx') to the name in Qiskit's version of qelib1.inc ('c3sqrtx') gate, which can lead to
         resolution issues."""
         qc = QuantumCircuit(4)
@@ -264,6 +266,58 @@ def test_c3sxgate_roundtrips(self):
         parsed = QuantumCircuit.from_qasm_str(qasm)
         self.assertIsInstance(parsed.data[0].operation, C3SXGate)
 
+    def test_cczgate_qasm(self):
+        """Test that CCZ dumps definition as a non-qelib1 gate."""
+        qc = QuantumCircuit(3)
+        qc.append(CCZGate(), qc.qubits, [])
+        qasm = qc.qasm()
+        expected = """OPENQASM 2.0;
+include "qelib1.inc";
+gate ccz q0,q1,q2 { h q2; ccx q0,q1,q2; h q2; }
+qreg q[3];
+ccz q[0],q[1],q[2];
+"""
+        self.assertEqual(qasm, expected)
+
+    def test_csgate_qasm(self):
+        """Test that CS dumps definition as a non-qelib1 gate."""
+        qc = QuantumCircuit(2)
+        qc.append(CSGate(), qc.qubits, [])
+        qasm = qc.qasm()
+        expected = """OPENQASM 2.0;
+include "qelib1.inc";
+gate cs q0,q1 { p(pi/4) q0; cx q0,q1; p(-pi/4) q1; cx q0,q1; p(pi/4) q1; }
+qreg q[2];
+cs q[0],q[1];
+"""
+        self.assertEqual(qasm, expected)
+
+    def test_csdggate_qasm(self):
+        """Test that CSdg dumps definition as a non-qelib1 gate."""
+        qc = QuantumCircuit(2)
+        qc.append(CSdgGate(), qc.qubits, [])
+        qasm = qc.qasm()
+        expected = """OPENQASM 2.0;
+include "qelib1.inc";
+gate csdg q0,q1 { p(-pi/4) q0; cx q0,q1; p(pi/4) q1; cx q0,q1; p(-pi/4) q1; }
+qreg q[2];
+csdg q[0],q[1];
+"""
+        self.assertEqual(qasm, expected)
+
+    def test_rzxgate_qasm(self):
+        """Test that RZX dumps definition as a non-qelib1 gate."""
+        qc = QuantumCircuit(2)
+        qc.append(RZXGate(0), qc.qubits, [])
+        qasm = qc.qasm()
+        expected = """OPENQASM 2.0;
+include "qelib1.inc";
+gate rzx(param0) q0,q1 { h q1; cx q0,q1; rz(0) q1; cx q0,q1; h q1; }
+qreg q[2];
+rzx(0) q[0],q[1];
+"""
+        self.assertEqual(qasm, expected)
+
     def test_unbound_circuit_raises(self):
         """Test circuits with unbound parameters raises."""
         qc = QuantumCircuit(1)