Skip to content

Commit cd87146

Browse files
authored
🚸 Improve native gate support for the Qiskit-to-OpenQASM3 conversion in the QDMI-Qiskit interface (#1719)
## Description While working on iqm-finland/QDMI-on-IQM#53, I wanted to try and plug an MQT Bench Grover circuit into the DDSIM QDMI device, which screamed at me with an error that turned out to actually be a Qiskit error. Qiskit's support for exporting to OpenQASM3 is still lacking when it comes to custom gates; it fails to provide a proper definition of multi-controlled gates such as MCXGate. However, I found out that one can pass a list of basis gates to the exporter, which it subsequently treats as "builtin" operations which do not necessitate a definition. This lets us work around the limitations of Qiskit in the sense that the DDSIM device claims to natively support the `mcx` gate and its OpenQASM parser (the one here in MQT Core) natively parses this operation. As a result, the export works without problems and simulations can be properly conducted. No AI was used in this pull request. ## Checklist <!--- This checklist serves as a reminder of a couple of things that ensure your pull request will be merged swiftly. --> - [x] The pull request only contains commits that are focused and relevant to this change. - [x] I have added appropriate tests that cover the new/changed functionality. - [x] I have updated the documentation to reflect these changes. - [x] I have added entries to the changelog for any noteworthy additions, changes, fixes, or removals. - [x] I have added migration instructions to the upgrade guide (if needed). - [x] The changes follow the project's style guidelines and introduce no new warnings. - [x] The changes are fully tested and pass the CI checks. - [x] I have reviewed my own code changes. **If PR contains AI-assisted content:** - [x] I have disclosed the use of AI tools in the PR description as per our [AI Usage Guidelines](https://github.com/munich-quantum-toolkit/core/blob/main/docs/ai_usage.md). - [x] AI-assisted commits include an `Assisted-by: [Model Name] via [Tool Name]` footer. - [x] I confirm that I have personally reviewed and understood all AI-generated content, and accept full responsibility for it. --------- Signed-off-by: Lukas Burgholzer <burgholzer@me.com>
1 parent e89c9f3 commit cd87146

4 files changed

Lines changed: 77 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ This project adheres to [Semantic Versioning], with the exception that minor rel
2121

2222
### Changed
2323

24+
- 🚸 Improve native gate support for the Qiskit-to-OpenQASM3 conversion in the QDMI-Qiskit interface ([#1719]) ([**@burgholzer**])
2425
- ⬆️ Require LLVM 22.1 for C++ library builds ([#1549]) ([**@burgholzer**], [**@denialhaag**])
2526
- 📦 Build MLIR by default for C++ library builds ([#1356]) ([**@burgholzer**], [**@denialhaag**])
2627

@@ -388,6 +389,7 @@ _📚 Refer to the [GitHub Release Notes](https://github.com/munich-quantum-tool
388389

389390
<!-- PR links -->
390391

392+
[#1719]: https://github.com/munich-quantum-toolkit/core/pull/1719
391393
[#1709]: https://github.com/munich-quantum-toolkit/core/pull/1709
392394
[#1702]: https://github.com/munich-quantum-toolkit/core/pull/1702
393395
[#1700]: https://github.com/munich-quantum-toolkit/core/pull/1700

include/mqt-core/qasm3/StdGates.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ const std::map<std::string, std::shared_ptr<Gate>> STANDARD_GATES = {
6464
{"u1",
6565
std::make_shared<StandardGate>(StandardGate(
6666
{.nControls = 0, .nTargets = 1, .nParameters = 1, .type = qc::P}))},
67+
{"cu1",
68+
std::make_shared<StandardGate>(StandardGate(
69+
{.nControls = 1, .nTargets = 1, .nParameters = 1, .type = qc::P}))},
6770
{"phase",
6871
std::make_shared<StandardGate>(StandardGate(
6972
{.nControls = 0, .nTargets = 1, .nParameters = 1, .type = qc::P}))},

python/mqt/core/plugins/qiskit/backend.py

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ def _build_gate_mappings_for_backend(
8080
"mcx": MCXGate,
8181
"mcphase": MCPhaseGate,
8282
"mcp": MCPhaseGate,
83+
"mcx_gray": MCXGate,
8384
})
8485

8586
qiskit_to_qdmi: dict[str, set[str]] = {}
@@ -143,6 +144,9 @@ def is_convertible(device: fomac.Device) -> bool:
143144
"gphase": {"global_phase"}, # OpenQASM canonical name
144145
"mcphase": {"mcp"}, # Qiskit canonical name
145146
"mcp": {"mcphase"}, # OpenQASM canonical name
147+
"mcx_gray": {"mcx"}, # Alias for MCX with specific encoding
148+
"mcx_vchain": {"mcx"}, # Alias for MCX with specific encoding
149+
"mcx_recursive": {"mcx"}, # Alias for MCX with specific encoding
146150
}
147151

148152
_QDMI_TO_QISKIT_GATE_MAP: ClassVar[dict[str, str]] = {
@@ -445,16 +449,65 @@ def _convert_circuit(
445449

446450
# Try OpenQASM3
447451
if fomac.ProgramFormat.QASM3 in supported_program_formats:
452+
# Qiskit's OpenQASM3 exporter is fairly limited in terms of which gates it supports natively.
453+
# So it needs some help from us.
454+
exclusion_list = set()
455+
456+
# Qiskit treats "measure", "reset", and "barrier" as keywords rather than gates
457+
exclusion_list.update({"measure", "reset", "barrier"})
458+
459+
# We also need to remove all gates that are defined in the OpenQASM `stdlib.inc`.
460+
# Qiskit's exporter will otherwise complain about duplicate definitions.
461+
exclusion_list.update({
462+
"p",
463+
"x",
464+
"y",
465+
"z",
466+
"h",
467+
"s",
468+
"sdg",
469+
"t",
470+
"tdg",
471+
"sx",
472+
"rx",
473+
"ry",
474+
"rz",
475+
"cx",
476+
"cy",
477+
"cz",
478+
"cp",
479+
"crx",
480+
"cry",
481+
"crz",
482+
"ch",
483+
"swap",
484+
"ccx",
485+
"cswap",
486+
"cu",
487+
"CX",
488+
"phase",
489+
"cphase",
490+
"id",
491+
"u1",
492+
"u2",
493+
"u3",
494+
})
495+
496+
# By excluding already defined gates, we allow the exporter to emit otherwise unsupported gates without
497+
# needing to provide a definition for them. The exporter will then treat them as opaque gates, which is fine
498+
# as long as the target device supports them.
499+
basis_gates = [gate for gate in self.target.operation_names if gate not in exclusion_list] + ["U"]
500+
448501
try:
449-
return str(qasm3.dumps(circuit)), fomac.ProgramFormat.QASM3
502+
return qasm3.dumps(circuit, basis_gates=basis_gates), fomac.ProgramFormat.QASM3
450503
except Exception as exc:
451504
msg = f"Failed to convert circuit to QASM3: {exc}"
452505
raise TranslationError(msg) from exc
453506

454507
# Try OpenQASM2 (legacy)
455508
if fomac.ProgramFormat.QASM2 in supported_program_formats:
456509
try:
457-
return str(qasm2.dumps(circuit)), fomac.ProgramFormat.QASM2
510+
return qasm2.dumps(circuit), fomac.ProgramFormat.QASM2
458511
except Exception as exc:
459512
msg = f"Failed to convert circuit to QASM2: {exc}"
460513
raise TranslationError(msg) from exc

test/python/plugins/qiskit/test_backend.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -581,6 +581,23 @@ def test_backend_supports_multicontrolled_gates(ddsim_backend: QDMIBackend) -> N
581581
assert sum(counts.values()) == 100
582582

583583

584+
def test_backend_openqasm3_translation_works_for_native_gates(ddsim_backend: QDMIBackend) -> None:
585+
"""Ensures the backend can run circuits with gates that are not natively supported by OpenQASM 3.
586+
587+
The DDSIM backend defines support for `mcx` gates, which are not native to OpenQASM3.
588+
Qiskit's OpenQASM3 exporter has problems providing proper definitions for such gates,
589+
which we work around by declaring the device's basis gates in the export call.
590+
This test ensures that this workaround is effective and that the backend can successfully run such circuits.
591+
"""
592+
qc = QuantumCircuit(6)
593+
qc.mcx([0, 1, 2, 3, 4], 5)
594+
qc.measure_all()
595+
596+
job = ddsim_backend.run(qc, shots=100)
597+
counts = job.result().get_counts()
598+
assert sum(counts.values()) == 100
599+
600+
584601
def test_zoned_operation_rejected_at_backend_init() -> None:
585602
"""Backend rejects devices exposing zoned operations."""
586603
session = fomac.Session()

0 commit comments

Comments
 (0)