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_transpiler_service/wrappers/__init__.py b/qiskit_transpiler_service/wrappers/__init__.py
index 43e101a..1c55c95 100644
--- a/qiskit_transpiler_service/wrappers/__init__.py
+++ b/qiskit_transpiler_service/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_transpiler_service/wrappers/transpile.py b/qiskit_transpiler_service/wrappers/transpile.py
index 95a0a1d..74edd4f 100644
--- a/qiskit_transpiler_service/wrappers/transpile.py
+++ b/qiskit_transpiler_service/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 94fa8df..d140d47 100644
--- a/tests/test_transpiler_service.py
+++ b/tests/test_transpiler_service.py
@@ -16,13 +16,18 @@
 import pytest
 from warnings import catch_warnings
 from qiskit import QuantumCircuit, qasm2, qasm3
+from qiskit.circuit.library import IQP, EfficientSU2, QuantumVolume, ECRGate
+from qiskit.circuit import Gate
 from qiskit.circuit.library import IQP, EfficientSU2, QuantumVolume
 from qiskit.circuit.random import random_circuit
 from qiskit.compiler import transpile
 from qiskit.quantum_info import SparsePauliOp, random_hermitian
 
 from qiskit_transpiler_service.transpiler_service import TranspilerService
-from qiskit_transpiler_service.wrappers import _get_circuit_from_result
+from qiskit_transpiler_service.wrappers import (
+    _get_circuit_from_result,
+    _get_circuit_from_qasm,
+)
 
 
 @pytest.mark.parametrize(
@@ -390,3 +395,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)