Repository: Qiskit/qiskit-ibm-transpiler (language: python)

Issue:
ECR gates returned by the Qiskit transpiler service aren't parsed as gates of type ECR
After some offline discussion, we have discovered that the gates returned by the service in QASM as `'ecr'` gates are not properly parsed by the client so their type is `Gate` instead of `ECRGate`. This is related to using OpenQASM2 and OpenQASM3 instead of a proper way of serializing such as using QPY https://docs.quantum.ibm.com/api/qiskit/qpy

Not using the proper type for ECRGate(s) has side-effects when executing the circuits via primitives.


Discussion / hints:

Fix patch:
diff --git a/qiskit_ibm_transpiler/wrappers/__init__.py b/qiskit_ibm_transpiler/wrappers/__init__.py
index 8e88539..46b1cc3 100644
--- a/qiskit_ibm_transpiler/wrappers/__init__.py
+++ b/qiskit_ibm_transpiler/wrappers/__init__.py
@@ -34,4 +34,4 @@
 from .ai_routing import AIRoutingAPI
 from .ai_synthesis import AICliffordAPI, AILinearFunctionAPI, AIPermutationAPI
 from .base import BackendTaskError, QiskitTranspilerService
-from .transpile import TranspileAPI, _get_circuit_from_result
+from .transpile import TranspileAPI, _get_circuit_from_result, _get_circuit_from_qasm
diff --git a/qiskit_ibm_transpiler/wrappers/transpile.py b/qiskit_ibm_transpiler/wrappers/transpile.py
index 17b8eaf..b52cd8b 100644
--- a/qiskit_ibm_transpiler/wrappers/transpile.py
+++ b/qiskit_ibm_transpiler/wrappers/transpile.py
@@ -15,7 +15,8 @@
 
 import numpy as np
 from qiskit import QuantumCircuit, QuantumRegister, qasm2, qasm3
-from qiskit.circuit import QuantumCircuit, QuantumRegister, Qubit
+from qiskit.circuit import QuantumCircuit, QuantumRegister, Qubit, library
+from qiskit.transpiler.basepasses import TransformationPass
 from qiskit.qasm2 import QASM2ExportError, QASM2ParseError
 from qiskit.transpiler import TranspileLayout
 from qiskit.transpiler.layout import Layout
@@ -193,11 +194,25 @@ def _create_transpile_layout(initial, final, circuit, orig_circuit):
     )
 
 
-def _get_circuit_from_qasm(qasm_string):
+class FixECR(TransformationPass):
+    def run(self, dag):
+        for node in dag.named_nodes("ecr"):
+            dag.substitute_node(node, library.ECRGate())
+        return dag
+
+
+def _get_circuit_from_qasm(qasm_string: str):
     try:
         return qasm2.loads(
             qasm_string,
-            custom_instructions=qasm2.LEGACY_CUSTOM_INSTRUCTIONS,
+            custom_instructions=_get_circuit_from_qasm.QISKIT_INSTRUCTIONS,
         )
     except QASM2ParseError:
-        return qasm3.loads(qasm_string)
+        return _get_circuit_from_qasm.fix_ecr(qasm3.loads(qasm_string))
+
+
+_get_circuit_from_qasm.QISKIT_INSTRUCTIONS = list(qasm2.LEGACY_CUSTOM_INSTRUCTIONS)
+_get_circuit_from_qasm.QISKIT_INSTRUCTIONS.append(
+    qasm2.CustomInstruction("ecr", 0, 2, library.ECRGate)
+)
+_get_circuit_from_qasm.fix_ecr = FixECR()


Test patch:
diff --git a/tests/test_transpiler_service.py b/tests/test_transpiler_service.py
index a3694ad..f0a035c 100644
--- a/tests/test_transpiler_service.py
+++ b/tests/test_transpiler_service.py
@@ -15,13 +15,17 @@
 import numpy as np
 import pytest
 from qiskit import QuantumCircuit, qasm2, qasm3
-from qiskit.circuit.library import IQP, EfficientSU2, QuantumVolume
+from qiskit.circuit.library import IQP, EfficientSU2, QuantumVolume, ECRGate
+from qiskit.circuit import Gate
 from qiskit.circuit.random import random_circuit
 from qiskit.compiler import transpile
 from qiskit.quantum_info import SparsePauliOp, random_hermitian
 
 from qiskit_ibm_transpiler.transpiler_service import TranspilerService
-from qiskit_ibm_transpiler.wrappers import _get_circuit_from_result
+from qiskit_ibm_transpiler.wrappers import (
+    _get_circuit_from_result,
+    _get_circuit_from_qasm,
+)
 
 
 @pytest.mark.parametrize(
@@ -374,3 +378,19 @@ def test_layout_construction_no_service(backend, cmap_backend):
         circuit.cx(1, 2)
         circuit.h(4)
         transpile_and_check_layout(cmap_backend[backend], circuit)
+
+
+def test_fix_ecr_qasm2():
+    qc = QuantumCircuit(5)
+    qc.ecr(0, 2)
+
+    circuit_from_qasm = _get_circuit_from_qasm(qasm2.dumps(qc))
+    assert isinstance(list(circuit_from_qasm)[0].operation, ECRGate)
+
+
+def test_fix_ecr_qasm3():
+    qc = QuantumCircuit(5)
+    qc.ecr(0, 2)
+
+    circuit_from_qasm = _get_circuit_from_qasm(qasm3.dumps(qc))
+    assert isinstance(list(circuit_from_qasm)[0].operation, ECRGate)