Skip to content

feat: ionq minimal integration into cloud provider platform#725

Open
vprusso wants to merge 11 commits intomainfrom
724-ionq-integration
Open

feat: ionq minimal integration into cloud provider platform#725
vprusso wants to merge 11 commits intomainfrom
724-ionq-integration

Conversation

@vprusso
Copy link
Copy Markdown
Contributor

@vprusso vprusso commented Mar 13, 2026

Closes: #724

Example

Dispatch a small benchmark to IonQ simulator

  mgym job dispatch metriq_gym/schemas/examples/mirror_circuits.example.json -p ionq -d simulator

Then poll the result (replace with your job ID)

  mgym job poll <job-id>

Standalone script:

  from qbraid.runtime import IonQProvider
  from metriq_gym.ionq.device import patch_ionq_device
  from qiskit import QuantumCircuit

  provider = IonQProvider()
  device = provider.get_device("simulator")
  patch_ionq_device(device)

  qc = QuantumCircuit(2, 2)
  qc.h(0)
  qc.cx(0, 1)
  qc.measure([0, 1], [0, 1])

  job = device.run(qc, shots=100)
  result = job.result()
  print(result.data.get_counts())  # {'00': 50, '11': 50}

Using the IonQ Quantum Cloud platform, we can see that launching this job on the (free) simulator, that the result we get back is successful:

image

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 13, 2026

PR Preview Action v1.8.1

QR code for preview link

🚀 View preview at
https://unitaryfoundation.github.io/metriq-gym/pr-preview/pr-725/

Built to branch gh-pages at 2026-03-22 17:46 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

- Add IonQDevice dispatchers for version, connectivity_graph (all-to-all) in qplatform/device.py
- Add IonQJob dispatchers for execution_time and job_status in qplatform/job.py
- Add ionq/device.py workaround for qiskit_ionq bug that sets qubit count to device total instead of circuit size
- Pass shots from params when loading IonQ jobs during polling (IonQ API omits shots in job details)
- Add unit tests for all new IonQ qplatform functions and device patch
@vprusso vprusso force-pushed the 724-ionq-integration branch from 3f30ab0 to dd6e062 Compare March 13, 2026 23:31
@vprusso vprusso requested a review from cosenal March 14, 2026 11:55
@vprusso vprusso marked this pull request as ready for review March 14, 2026 11:55
Copilot AI review requested due to automatic review settings March 14, 2026 11:55
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds minimal IonQ support paths needed for running metriq-gym benchmarks through the qBraid provider layer, including device metadata utilities, job timing/status extraction, and a runtime workaround for an IonQ/qiskit_ionq conversion issue.

Changes:

  • Add IonQ implementations for version(), connectivity_graph(), execution_time(), and job_status() in the qplatform utilities.
  • Patch IonQ device setup to bypass qiskit_ionq’s buggy conversion path by converting Qiskit circuits to QASM2 before submission.
  • Add unit tests covering IonQ device metadata, connectivity, and job timing/status behavior.

Reviewed changes

Copilot reviewed 7 out of 9 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
metriq_gym/qplatform/job.py Adds IonQJob handling for execution time and status extraction.
metriq_gym/qplatform/device.py Adds IonQDevice handling for version and all-to-all connectivity graph.
metriq_gym/run.py Patches IonQ devices at setup time; injects shots into load_job kwargs for IonQ result reconstruction.
metriq_gym/ionq/device.py Introduces IonQDevice.run patch to force QASM2 submission when qiskit_ionq is installed.
metriq_gym/ionq/__init__.py Adds IonQ package module.
tests/unit/qplatform/test_job.py Adds IonQ job tests for execution time and status.
tests/unit/qplatform/test_device.py Adds IonQ device tests for version/connectivity/metadata.
tests/unit/ionq/test_device.py Adds tests for QASM2 conversion helper and device patching behavior.
tests/unit/ionq/__init__.py Adds IonQ test package module.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +55 to +56
exec_time_ms = meta.get("execution_time") or meta.get("predicted_execution_time")
if exec_time_ms is None:
which correctly preserves the circuit's qubit count.
"""

import importlib
Comment on lines +58 to +79
def test_patched_device_converts_qiskit_to_qasm(self):
"""After patching, Qiskit circuits should be converted to QASM2 strings."""
from qbraid.runtime import IonQDevice

class FakeDevice:
pass

FakeDevice.run = IonQDevice.run
device = FakeDevice()

patch_ionq_device(device)

# The patched run should convert QC -> QASM before calling original
qc = QuantumCircuit(2, 2)
qc.h(0)
qc.measure([0, 1], [0, 1])

# Directly test the conversion function instead of the full patch chain
result = _convert_qiskit_to_qasm2(qc)
assert isinstance(result, str)
assert "OPENQASM 2.0" in result

Comment on lines +3 to +6
When qiskit_ionq is installed, qBraid's IonQDevice.run() uses qiskit_ionq's
transpilation path which sets the circuit qubit count to the device's total
qubit count (e.g. 29) rather than the circuit's actual qubit count. This
causes IonQ's simulator to reject circuits that would otherwise run fine.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this something we need to handle in metriq-gym, or raise it with qBraid?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally, we should raise an issue about this on the qBraid issues board.

Copy link
Copy Markdown
Contributor

@cosenal cosenal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested it with

mgym job dispatch config.json -p ionq -d simulator

and config.json being

{
    "benchmark_name": "WIT",
    "num_qubits": 7,
    "shots": 8192
}

I got 0.0 as a result for the expectation value, so something is wrong.

@vprusso vprusso requested a review from cosenal March 17, 2026 11:38
@vprusso
Copy link
Copy Markdown
Contributor Author

vprusso commented Mar 17, 2026

I tested it with

mgym job dispatch config.json -p ionq -d simulator

and config.json being

{
    "benchmark_name": "WIT",
    "num_qubits": 7,
    "shots": 8192
}

I got 0.0 as a result for the expectation value, so something is wrong.

Two separate qBraid issues were causing it:

  1. IonQJob._get_counts() uses normalize_data() which drops leading zeros from bitstrings when only
    one computational state is observed (e.g. returns {"0": 8192} instead of {"0000000": 8192}). Benchmarks that do exact key lookups (mirror circuits, QML kernel) get 0 matches.
  2. IonQ measures all qubits regardless of measurement gates (qBraid's QASM -> IonQ conversion explicitly discards them). So WIT's 7-qubit circuit with measure q[5] -> c[0] returns 7-bit strings, but binary_expectation_value looks for "1" (a 1-bit key).

Both are now fixed with a centralized patch to qBraid's IonQJob class, but ideally, we would want another MR that actually patches qBraid for this. Thoughts, @cosenal ?

vprusso added 3 commits March 17, 2026 07:56
Resolve uv.lock and pyproject.toml conflicts. Remove ionq extra from
qbraid dependency to drop qiskit-ionq per qBraid/qBraid#1141 workaround.
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.

IonQ integration

3 participants