Skip to content

Reuse released QDMI provider adapters with MQT Core 4.1 - #147

Merged
DougSO merged 9 commits into
openQSE:mainfrom
marcelwa:codex/streamline-qdmi
Oct 15, 2026
Merged

DougSO merged 9 commits into
openQSE:mainfrom
marcelwa:codex/streamline-qdmi

Conversation

@marcelwa

@marcelwa marcelwa commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

🤖 AI text below 🤖

Replace duplicated IQM and Amazon Braket device registration and session setup with their released MQT Core-backed Qiskit adapters. Pin MQT Core 4.1.1, Amazon Braket QDMI 1.3.0, and IQM QDMI 1.5.0 in setup/qdmi-requirements.txt.

Keep reservation-specific credentials and sessions, shot limits, deadlines, cancellation, and native job options in QFw. Reuse the Amazon Braket adapter’s gate aliases and serializer where possible, preserve the broader non-verbatim compiler vocabulary, and fix classical measurement mapping and ordered multi-qubit operation loci. Apply QDMI’s duration scale factor to exported coherence times and gate durations. Session creation requires a device representable by the provider’s Qiskit adapter.

Keep IQM circuit conversion and preserve Qiskit classical-bit ordering in Amazon Braket results. Submit jobs directly through QDMI so each resource’s configured S3 destination remains effective.

@DougSO

DougSO commented Oct 14, 2026

Copy link
Copy Markdown
Collaborator

Reviewed this with particular attention to the reservation and session handling, since #129, #130 and #131 landed in this file last week and the refactor removes most of what they touched. Confirming those invariants was the main thing I wanted to check, so leading with it.

They hold, and the new tests pin them. _backend() keeps the _credential_cache_key keying, the lock, and the double-check inside it, and evict_reservation still pops by reservation key. test_repeated_backend_construction_keeps_separate_sessions and test_iqm_rejects_ambient_session_settings guard exactly the two properties #129 and #130 established. reservation_arn also survives, as a named kwarg rather than a hand-stuffed custom3, with a test, so Braket reservations are not lost.

A few other things I checked rather than took on trust:

  • The dependency set resolves against the real setup/requirements.txt with no conflict. qiskit holds at its pinned 2.2.3 and iqm-client at 34.0.1, since mqt-core[qiskit]==4.0.0 only needs qiskit>=2.1. Nothing floats.
  • All four adapter imports load in that exact combination, installed and run on the cluster's base image. The bare iqm-qdmi without its [qiskit] extra does still provide iqm.qdmi.qiskit.IQMBackend, so the workaround your comment describes is sound.
  • core#2626 and amazon-braket-qdmi-device#242 are both still open, so the note that the temporary IQM conversion and Braket remapping must stay is accurate.
  • ci-qdmi using setup/requirements.txt as a -c constraint is the right call. It pins qiskit without pulling pyscf and pennylane into the job. That job passes.

ci-mock is red, and it is a one-line cause

Now that the workflows have been approved, ci-mock fails on both 3.10 and 3.12: 2 failed, 636 passed.

  • tests/mock/test_circuit_payload.py::test_the_qdmi_driver_transcodes_the_declared_circuit
  • tests/mock/test_shim_cancel.py::test_a_cancel_before_submission_submits_no_qdmi_job

Both fail with the same error, QDMI driver could not resolve device access for provider 'iqm' ... credential DB does not contain an enabled entitlement and API key for user 'runner'.

The cause is the test seam rather than the driver logic. run_circuit now calls self._backend(credential=credential) where it used to call self._device(credential=credential). Keeping _device() as a wrapper preserves the other callers, but these two files stub the seam:

  • tests/mock/test_circuit_payload.py:313 does driver._device = lambda credential=None: object()
  • tests/mock/test_shim_cancel.py:214 does driver._device = lambda credential=None: device

Neither file is in this PR's changed set, so the stubs are now bypassed and the real _backend() resolves device access for real. Stubbing _backend instead, returning something with a .device, should be all it takes. Worth a grep for other _device stubs while you are in there.

To be clear about the scope: this looks like a test-seam problem, not a production behaviour change. run_circuit resolved access inside the acquire phase before the cancel check on main too, so the ordering is unchanged for a real deployment.

One thing worth changing in the driver

Separately from the CI failure, in _backend() the lock-free cache pre-check is gone and self._access(credential) now runs on every call. Previously _device() returned from self._devices before touching the lock or resolving access.

This matters more than it looks, because QdmiIqmProfile.access() falls through to resolve_device_access(...) whenever the credential does not already carry both a url and a token, and that reads the device-access YAML and the credential DB. So an introspection call made outside a full reservation now does file I/O every time where it previously did none after the first open, and every call serializes on _backends_lock. Restoring the early self._backends.get(cache_key) return and building access inside the lock on a miss would keep the warm path as cheap as it was.

Two questions

Why mqt-core==4.0.0 rather than 4.1.1? v4.1.0 and v4.1.1 both shipped on 2026-10-13. Happy if it is deliberate, I just want to be sure it is not simply the version that was current when the branch started.

The from-source MQT Core pin for the compiler venv (MQT_CORE_REF=151a69f9, a 2026-09-22 commit) is untouched, so the wheel and the source build sit on different versions. That split predates this PR so it is not a regression, but the Dockerfile comment that explained the pinning rationale is removed in the companion cluster PR, and that was the only place it was written down. Worth a line somewhere.

Nothing else blocking from me beyond the CI failure. The structure is a clear improvement over the duplicated registration and session code. I have not done an image build or any site validation, which your description correctly lists as outstanding.

@DougSO

DougSO commented Oct 14, 2026

Copy link
Copy Markdown
Collaborator

The ci-mock failure is fixed on main now, in #148, so this just needs a merge from main and the red should clear.

I checked rather than guessed: merging this branch's a5c9a75 into current main (6131ff9) is conflict-free, none of your eleven files overlap the three #148 touched, and the full mock suite comes out 636 passed and 4 skipped.

Nothing on your side needs changing. The two tests now call stub_qdmi_session() from tests/mock/fakes.py, which sets both _device and _backend, so they no longer care which accessor run_circuit reaches the session through.

One entirely optional follow-up: there are three more _device stubs in test_braket_profile.py and test_qdmi_profiles.py. I left those alone because this PR rewrites both files and converting them would only have caused conflicts. If you want them on the helper too, your branch is the right place for it.

The two other points from my earlier review still stand on their own: the _access call moving onto the warm path in _backend(), and the question about mqt-core 4.0.0 versus 4.1.1.

@denialhaag

Copy link
Copy Markdown
Contributor

Hi, @DougSO! Thanks a lot for the review. I'll update to MQT Core 4.1.1 later today and address your other points in the process! 🙂

denialhaag pushed a commit to marcelwa/QFw that referenced this pull request Oct 14, 2026
Two mock tests stop the QDMI driver short of a real session by replacing
_device(), the private accessor run_circuit reaches the session through.
That couples them to an implementation detail, and the coupling has now
cost something: openQSE#147 moves run_circuit onto _backend(), keeping
_device() as a wrapper for the other callers, and these two tests stop
intercepting. The real accessor then runs and resolves device access for
real, so both fail on a runner with an empty credential DB with "QPU
credential DB does not contain an enabled entitlement and API key for
user 'runner'". The driver is behaving correctly; the stand-in simply
stopped being in the way.

Give the intention a name instead. stub_qdmi_session() in tests/mock/fakes
sets both accessors, the one in use and the one that work moves to, and
returns the device so a caller can still assert against it. A test says
what it wants, a session that opens nothing, rather than which method to
patch, and the next move of that seam does not reach the tests.

Only the two files openQSE#147 does not touch are changed. The three other
_device stubs live in test_braket_profile.py and test_qdmi_profiles.py,
which openQSE#147 rewrites, so converting those here would only create conflicts.
They can move to the helper afterwards.

Tested in the container. On main the full mock suite is 626 passed and 4
skipped, unchanged, and ci-syntax is clean. On openQSE#147's head a5c9a75 the
suite is 2 failed and 634 passed before this change, matching the CI
failure exactly, and 636 passed and 4 skipped with it. So this closes that
PR's ci-mock without touching its code.

Signed-off-by: Doug Oucharek <dougso@me.com>
Assisted-by: GPT-6.1 Sol via Codex
Assisted-by: GPT-6.1 Sol via Codex
Assisted-by: GPT-6.1 Sol via Codex
@denialhaag

Copy link
Copy Markdown
Contributor

Thanks again for your review, @DougSO! 🙂

The two other points from my earlier review still stand on their own: the _access call moving onto the warm path in _backend(), and the question about mqt-core 4.0.0 versus 4.1.1.

The former is fixed by 5e9de3d, and b527e67 updates MQT Core to 4.1.1

@denialhaag

Copy link
Copy Markdown
Contributor

I already asked @marcelwa to have another look at the PR from our side and update its title and description. 🙂

@marcelwa marcelwa changed the title Reuse released QDMI provider adapters with MQT Core 4.0 Reuse released QDMI provider adapters with MQT Core 4.1 Oct 15, 2026
@DougSO
DougSO merged commit 0f59fa2 into openQSE:main Oct 15, 2026
11 checks passed
@DougSO

DougSO commented Oct 15, 2026

Copy link
Copy Markdown
Collaborator

Closing out the one question I left open, and passing on something the real device showed.

The duration scale is right. I checked ee709a7 against the ORNL q20 through the merged code, with mqt-core 4.1.1 and iqm-qdmi 1.5.0:

duration_unit         : 'us'
duration_scale_factor : 1.0
raw T1 per site       : 30, 56, 53, 31, 41 ...
T1 range              : 30 to 66 us across all 20 qubits
T2 <= T1              : 20 of 20 qubits

fomac_normalize.extract_calibration reports exactly those values with duration_unit: us, and the magnitudes are right for a superconducting device, so nothing is off by a factor anywhere.

The reason this settles it: the device declares us and a scale of 1.0 for values that are already in microseconds. Had the scale been meant to reach seconds it would have been 1e-6. So the factor maps raw into duration_unit, which is what your change implements. One caveat for the record, a scale of 1.0 makes both readings numerically identical on this device, so what I confirmed is that the pairing is self-consistent rather than the semantics by arithmetic. A device reporting a scale other than 1.0 would be the real test and I do not have one.

Separately, something only the real device surfaced. Opening the q20 through iqm.qdmi.qiskit.IQMBackend warns:

Device operation 'prx_12' cannot be mapped to a Qiskit gate and will be skipped

prx_12 is genuinely in the q20's operation set, alongside cc_prx, cz, measure, measure_fidelity, prx and reset_wait. Skipping it when building the Qiskit target looks correct to me, since it is the 1 to 2 transition gate and no standard circuit uses it, so I am not reporting it as a problem. Flagging it because it is the kind of thing that never shows up against a fixture, and you may want it either documented or demoted below a warning if it is expected.

Nothing else outstanding from my side. Thanks for turning both review points round so quickly, and for the duration-scale fix nobody asked for.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants