Repository: Qiskit/qiskit-terra (python)

Issue:
Quantum circuits with a C3SX gate fail to convert back from qasm
### Information

- **Qiskit Terra version**: 0.18.3
- **Python version**: 3.8.5
- **Operating system**: macOS-10.16-x86_64-i386-64bit

### What is the current behavior?

A circuit that contains a C3SX gate can be translated to qasm, but the generated qasm cannot be translated back to a quantum circuit.
The error stems from a mismatch in the name of this gate between qelib1.inc ("c3sqrtx"):
https://github.com/Qiskit/qiskit-terra/blob/6af71f9d1e249a3747772449f299160df4047b83/qiskit/qasm/libs/qelib1.inc#L242
and qiskit's python code ("c3sx"):
https://github.com/Qiskit/qiskit-terra/blob/cf4005ff9fdc672111a827582e6b7d8ae2683be5/qiskit/circuit/library/standard_gates/x.py#L446
 https://github.com/Qiskit/qiskit-terra/blob/6af71f9d1e249a3747772449f299160df4047b83/qiskit/circuit/quantumcircuit.py#L1563

### Steps to reproduce the problem

```python
import qiskit

qc = qiskit.QuantumCircuit(4)
gate = qiskit.circuit.library.C3SXGate()
qc.append(gate, qargs=qc.qubits)

qasm = qc.qasm()
qc_from_qasm = qiskit.QuantumCircuit.from_qasm_str(qasm)  # fails:
# QasmError(
# qiskit.qasm.exceptions.QasmError: "Cannot find gate definition for 'c3sx', line 4 file "
```

### What is the expected behavior?

I would like the gate names to concur and the qasm to be convertible to a quantum circuit.

### Suggested solutions

- Rename the gate in qelib1.inc to "c3sx".
- Change the python code to use "c3sqrtx".

(This issue probably relates to #4943.)

![image](https://user-images.githubusercontent.com/36337649/137648025-f7a38444-ed6e-4c64-a822-6d5ad87a1277.png)


Pull request:
Fix C3SXGate roundtrip in OpenQASM 2
### Summary

Qiskit and its version of `qelib1.inc` give `C3SXGate` different names: `c3sx` and `c3sqrtx` respectively.  At this point, changing either of these two names is likely to cause user pain, and it should not be difficult for the OpenQASM 2 exporter to simply know about this remapping, like it knows about other standard gate mappings.  In practice, the structure of the exporter makes this rather difficult, but we need not expose that complexity to users.

<!--
⚠️ If you do not respect this template, your pull request will be closed.
⚠️ Your pull request title should be short detailed and understandable for all.
⚠️ Also, please add a release note file using reno if the change needs to be
  documented in the release notes.
⚠️ If your pull request fixes an open issue, please link to the issue.

- [ ] I have added the tests to cover my changes.
- [ ] I have updated the documentation accordingly.
- [ ] I have read the CONTRIBUTING document.
-->

### Details and comments

Fix #7148.
Close #7241 (obsolete).

This PR is a more conservative version of #7148 that does not break backwards compatibility, and does not require changes to either Terra's version of `qelib1.inc` or the name of `C3SXGate`, both of which may cause user headaches.

It should have been easier to do this output mapping (the general exporter should know how classes / names need to be mapped to OQ2 equivalents), but as it stands, it needs to be hacked in a little bit.  This way is cleaner for the user experience, though.

Patch:
diff --git a/qiskit/circuit/library/standard_gates/x.py b/qiskit/circuit/library/standard_gates/x.py
index cc6eebc2f..2ebee7786 100644
--- a/qiskit/circuit/library/standard_gates/x.py
+++ b/qiskit/circuit/library/standard_gates/x.py
@@ -569,6 +569,20 @@ class C3SXGate(ControlledGate):
 
         self.definition = qc
 
+    def qasm(self):
+        # Gross hack to override the Qiskit name with the name this gate has in Terra's version of
+        # 'qelib1.inc'.  In general, the larger exporter mechanism should know about this to do the
+        # mapping itself, but right now that's not possible without a complete rewrite of the OQ2
+        # exporter code (low priority), or we would need to modify 'qelib1.inc' which would be
+        # needlessly disruptive this late in OQ2's lifecycle.  The current OQ2 exporter _always_
+        # outputs the `include 'qelib1.inc' line.  ---Jake, 2022-11-21.
+        try:
+            old_name = self.name
+            self.name = "c3sqrtx"
+            return super().qasm()
+        finally:
+            self.name = old_name
+
 
 class C3XGate(ControlledGate):
     r"""The X gate controlled on 3 qubits.
diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py
index 9144e60ca..17c768412 100644
--- a/qiskit/circuit/quantumcircuit.py
+++ b/qiskit/circuit/quantumcircuit.py
@@ -1644,7 +1644,7 @@ class QuantumCircuit:
             "rccx",
             "rc3x",
             "c3x",
-            "c3sx",
+            "c3sx",  # This is the Qiskit gate name, but the qelib1.inc name is 'c3sqrtx'.
             "c4x",
         ]
 
diff --git a/qiskit/converters/ast_to_dag.py b/qiskit/converters/ast_to_dag.py
index 3883f7c56..eb012ad38 100644
--- a/qiskit/converters/ast_to_dag.py
+++ b/qiskit/converters/ast_to_dag.py
@@ -25,42 +25,7 @@ from qiskit.circuit.measure import Measure
 from qiskit.circuit.reset import Reset
 from qiskit.circuit.barrier import Barrier
 from qiskit.circuit.delay import Delay
-from qiskit.circuit.library.standard_gates.x import CCXGate
-from qiskit.circuit.library.standard_gates.swap import CSwapGate
-from qiskit.circuit.library.standard_gates.x import CXGate
-from qiskit.circuit.library.standard_gates.y import CYGate
-from qiskit.circuit.library.standard_gates.z import CZGate
-from qiskit.circuit.library.standard_gates.swap import SwapGate
-from qiskit.circuit.library.standard_gates.h import HGate
-from qiskit.circuit.library.standard_gates.i import IGate
-from qiskit.circuit.library.standard_gates.s import SGate
-from qiskit.circuit.library.standard_gates.s import SdgGate
-from qiskit.circuit.library.standard_gates.sx import SXGate
-from qiskit.circuit.library.standard_gates.sx import SXdgGate
-from qiskit.circuit.library.standard_gates.t import TGate
-from qiskit.circuit.library.standard_gates.t import TdgGate
-from qiskit.circuit.library.standard_gates.p import PhaseGate
-from qiskit.circuit.library.standard_gates.u1 import U1Gate
-from qiskit.circuit.library.standard_gates.u2 import U2Gate
-from qiskit.circuit.library.standard_gates.u3 import U3Gate
-from qiskit.circuit.library.standard_gates.u import UGate
-from qiskit.circuit.library.standard_gates.x import XGate
-from qiskit.circuit.library.standard_gates.y import YGate
-from qiskit.circuit.library.standard_gates.z import ZGate
-from qiskit.circuit.library.standard_gates.rx import RXGate
-from qiskit.circuit.library.standard_gates.ry import RYGate
-from qiskit.circuit.library.standard_gates.rz import RZGate
-from qiskit.circuit.library.standard_gates.rxx import RXXGate
-from qiskit.circuit.library.standard_gates.rzz import RZZGate
-from qiskit.circuit.library.standard_gates.p import CPhaseGate
-from qiskit.circuit.library.standard_gates.u import CUGate
-from qiskit.circuit.library.standard_gates.u1 import CU1Gate
-from qiskit.circuit.library.standard_gates.u3 import CU3Gate
-from qiskit.circuit.library.standard_gates.h import CHGate
-from qiskit.circuit.library.standard_gates.rx import CRXGate
-from qiskit.circuit.library.standard_gates.ry import CRYGate
-from qiskit.circuit.library.standard_gates.rz import CRZGate
-from qiskit.circuit.library.standard_gates.sx import CSXGate
+from qiskit.circuit.library import standard_gates as std
 
 
 def ast_to_dag(ast):
@@ -102,43 +67,48 @@ class AstInterpreter:
     """Interprets an OpenQASM by expanding subroutines and unrolling loops."""
 
     standard_extension = {
-        "u1": U1Gate,
-        "u2": U2Gate,
-        "u3": U3Gate,
-        "u": UGate,
-        "p": PhaseGate,
-        "x": XGate,
-        "y": YGate,
-        "z": ZGate,
-        "t": TGate,
-        "tdg": TdgGate,
-        "s": SGate,
-        "sdg": SdgGate,
-        "sx": SXGate,
-        "sxdg": SXdgGate,
-        "swap": SwapGate,
-        "rx": RXGate,
-        "rxx": RXXGate,
-        "ry": RYGate,
-        "rz": RZGate,
-        "rzz": RZZGate,
-        "id": IGate,
-        "h": HGate,
-        "cx": CXGate,
-        "cy": CYGate,
-        "cz": CZGate,
-        "ch": CHGate,
-        "crx": CRXGate,
-        "cry": CRYGate,
-        "crz": CRZGate,
-        "csx": CSXGate,
-        "cu1": CU1Gate,
-        "cp": CPhaseGate,
-        "cu": CUGate,
-        "cu3": CU3Gate,
-        "ccx": CCXGate,
-        "cswap": CSwapGate,
+        "u1": std.U1Gate,
+        "u2": std.U2Gate,
+        "u3": std.U3Gate,
+        "u": std.UGate,
+        "p": std.PhaseGate,
+        "x": std.XGate,
+        "y": std.YGate,
+        "z": std.ZGate,
+        "t": std.TGate,
+        "tdg": std.TdgGate,
+        "s": std.SGate,
+        "sdg": std.SdgGate,
+        "sx": std.SXGate,
+        "sxdg": std.SXdgGate,
+        "swap": std.SwapGate,
+        "rx": std.RXGate,
+        "rxx": std.RXXGate,
+        "ry": std.RYGate,
+        "rz": std.RZGate,
+        "rzz": std.RZZGate,
+        "id": std.IGate,
+        "h": std.HGate,
+        "cx": std.CXGate,
+        "cy": std.CYGate,
+        "cz": std.CZGate,
+        "ch": std.CHGate,
+        "crx": std.CRXGate,
+        "cry": std.CRYGate,
+        "crz": std.CRZGate,
+        "csx": std.CSXGate,
+        "cu1": std.CU1Gate,
+        "cp": std.CPhaseGate,
+        "cu": std.CUGate,
+        "cu3": std.CU3Gate,
+        "ccx": std.CCXGate,
+        "cswap": std.CSwapGate,
         "delay": Delay,
+        "rccx": std.RCCXGate,
+        "rc3x": std.RC3XGate,
+        "c3x": std.C3XGate,
+        "c3sqrtx": std.C3SXGate,
+        "c4x": std.C4XGate,
     }
 
     def __init__(self, dag):
@@ -263,7 +233,7 @@ class AstInterpreter:
             )
         maxidx = max([len(id0), len(id1)])
         for idx in range(maxidx):
-            cx_gate = CXGate()
+            cx_gate = std.CXGate()
             cx_gate.condition = self.condition
             if len(id0) > 1 and len(id1) > 1:
                 self.dag.apply_operation_back(cx_gate, [id0[idx], id1[idx]], [])
diff --git a/releasenotes/notes/fix-qasm2-c3sxgate-47171c9d17876219.yaml b/releasenotes/notes/fix-qasm2-c3sxgate-47171c9d17876219.yaml
new file mode 100644
index 000000000..94b3ebd4d
--- /dev/null
+++ b/releasenotes/notes/fix-qasm2-c3sxgate-47171c9d17876219.yaml
@@ -0,0 +1,5 @@
+---
+fixes:
+  - |
+    Circuits containing :class:`.C3SXGate` can now be output and read in again safely from the
+    OpenQASM 2.0 exporter (:meth:`.QuantumCircuit.qasm`) and parser (:meth:`.QuantumCircuit.from_qasm_str`).


Test patch:
diff --git a/test/python/circuit/test_circuit_qasm.py b/test/python/circuit/test_circuit_qasm.py
index 060178eb6..b8089e775 100644
--- a/test/python/circuit/test_circuit_qasm.py
+++ b/test/python/circuit/test_circuit_qasm.py
@@ -19,6 +19,7 @@ import re
 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.qasm.exceptions import QasmError
 
 # Regex pattern to match valid OpenQASM identifiers
@@ -247,6 +248,22 @@ nG0(pi,pi/2) q[0],r[0];\n"""
 
         self.assertEqual(original_str, qc.qasm())
 
+    def test_c3sxgate_roundtrips(self):
+        """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)
+        qc.append(C3SXGate(), qc.qubits, [])
+        qasm = qc.qasm()
+        expected = """OPENQASM 2.0;
+include "qelib1.inc";
+qreg q[4];
+c3sqrtx q[0],q[1],q[2],q[3];
+"""
+        self.assertEqual(qasm, expected)
+        parsed = QuantumCircuit.from_qasm_str(qasm)
+        self.assertIsInstance(parsed.data[0].operation, C3SXGate)
+
     def test_unbound_circuit_raises(self):
         """Test circuits with unbound parameters raises."""
         qc = QuantumCircuit(1)