Skip to content

feat(executors): add IonQ Direct API executor (closes #1)#37

Open
manasa-manoj-nbr wants to merge 5 commits into
marqov-dev:mainfrom
manasa-manoj-nbr:feat/ionq-direct-executor
Open

feat(executors): add IonQ Direct API executor (closes #1)#37
manasa-manoj-nbr wants to merge 5 commits into
marqov-dev:mainfrom
manasa-manoj-nbr:feat/ionq-direct-executor

Conversation

@manasa-manoj-nbr

Copy link
Copy Markdown

Summary

Closes #1

Adds a native IonQ Direct executor that runs circuits straight against IonQ's REST API, bypassing the AWS Braket intermediary — no AWS account or S3 bucket required. Circuits are converted with circuit.to_qiskit() then dumped to OpenQASM and submitted using IonQ's qasm input format. execute() submits, polls for completion, and decodes IonQ's probability histogram into measurement counts.

Type of change

  • Bug fix
  • New executor
  • Circuit format converter
  • Documentation
  • Other (describe):

Testing

  • I ran pytest tests/ -v and tests pass
  • For new executors: tested against local simulator or QVM (describe below)
  • For circuit converters: roundtrip test passes with known-correct reference circuit

Test details:

  • New mocked suite tests/test_ionq_executor.py (23 tests) injects a fake HTTP session — no network or credentials needed. Covers: execute() happy path, payload uses qasm input format, job polling, noise model, job-failure -> RuntimeError, histogram→counts decoding, API-key resolution, cancel() (success/failure), and get_status() mapping (online/offline/maintenance/error).
  • Factory routing covered in tests/test_executors.py ("IonQ Direct" supported + builds an IonQExecutor).
  • Full suite green locally: 328 passed, 13 skipped. ruff and mypy (strict) clean on the new code.
  • A commented @pytest.mark.integration template is included for running against the live IonQ simulator via IONQ_API_KEY (not run in CI).

Checklist

  • No hardcoded credentials or API keys (key comes from config or IONQ_API_KEY env)
  • Handles the canonical gate set from CONTRIBUTING.md §1 (conversion reuses the
    SDK's existing to_qiskit() path, so the canonical set is preserved)

For new executors only:

  • Registered in ExecutorFactory per CONTRIBUTING.md §3 (provider string "IonQ Direct", _create_ionq_executor(), listed in get_supported_providers(), exported, new optional [ionq] extra)
  • get_status() returns device-level availability ("online"/"offline"/"maintenance") by querying GET /backends/{target} — not job-level status (per CONTRIBUTING.md §2)

Note on the official ionq client

The issue suggested the official ionq Python client. Its only release (0.0.0a15, pre-release) pins pydantic<2, which is unresolvable against marqov's core pydantic>=2 requirement — pip install marqov[ionq] fails with ResolutionImpossible. Transport therefore uses requests` (the same HTTP library the official client uses internally) to call IonQ's REST API directly. This is documented in the executor's module docstring; happy to migrate to the official client once it supports pydantic 2.

AI Disclosure

I used Claude to help implement and debug this PR: studying the existing executor patterns (Braket/IBM/Azure), scaffolding the IonQ executor and mocked tests, and diagnosing the ionq client's pydantic<2 conflict with marqov's pydantic>=2 (which is why transport uses requests directly). I reviewed all generated changes, ran ruff/mypy/pytest locally, and verified the executor end-to-end against a mocked IonQ API before submitting (no live API key, so the live path is untested).

Copilot AI review requested due to automatic review settings June 7, 2026 21:26

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds first-class support for running circuits on IonQ via IonQ’s native REST API, integrates it into the executor factory/public API, and updates docs/dependencies accordingly.

Changes:

  • Introduce IonQExecutor + IonQExecutorConfig with async execute/poll/cancel/status logic.
  • Extend ExecutorFactory routing and public exports to support the “IonQ Direct” provider.
  • Add dependency extras (ionq) and comprehensive unit tests + README updates.

Reviewed changes

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

Show a summary per file
File Description
tests/test_ionq_executor.py Adds new unit tests for IonQ Direct executor behavior with stubbed HTTP session
tests/test_executors.py Adds factory routing tests for “IonQ Direct” provider
pyproject.toml Adds ionq extra (requests + qiskit) and includes requests in all
marqov/executors/ionq.py Implements IonQ Direct REST API executor + config
marqov/executors/factory.py Adds factory support for creating IonQExecutor and lists provider as supported
marqov/executors/init.py Exports IonQ executor/config from package
README.md Documents IonQ Direct usage and marks it available in supported backends table

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

Comment thread marqov/executors/ionq.py Outdated
Comment thread marqov/executors/ionq.py
Comment thread marqov/executors/ionq.py Outdated
Comment thread marqov/executors/ionq.py Outdated
Comment thread marqov/executors/ionq.py Outdated
Comment thread marqov/executors/ionq.py
Comment thread marqov/executors/ionq.py Outdated
@manasa-manoj-nbr manasa-manoj-nbr requested a review from Copilot June 7, 2026 21:46

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

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

Comment thread marqov/executors/ionq.py Outdated
Comment on lines +374 to +376
queue_depth = backend.get("queue_depth") or backend.get("jobs_queued")
avg_queue = backend.get("average_queue_time")
queue_time_seconds = int(avg_queue) if avg_queue is not None else None
Comment thread marqov/executors/ionq.py
Comment on lines +284 to +288
histogram = job.get("data", {}).get("histogram")
if histogram is None:
histogram = await self._request("GET", f"/jobs/{job_id}/results")

counts = self._histogram_to_counts(histogram, shots, num_qubits)
Comment thread marqov/executors/ionq.py Outdated
Comment on lines +176 to +188
Args:
histogram: Mapping of state index strings to probabilities.
shots: Number of shots, used to scale probabilities to counts.
num_qubits: Number of qubits, used to zero-pad bitstrings.

Counts are allocated with the largest-remainder (Hamilton) method so the
totals sum exactly to ``shots`` — naive per-bin rounding can drift above or
below ``shots`` and break downstream "total == shots" assumptions.

Args:
histogram: Mapping of state index strings to probabilities.
shots: Number of shots, used to scale probabilities to counts.
num_qubits: Number of qubits, used to zero-pad bitstrings.
Comment thread marqov/executors/factory.py Outdated
Comment on lines +265 to +272
config = IonQExecutorConfig(
target=backend_config.get("target", backend_slug),
api_key=backend_config.get("api_key"),
base_url=backend_config.get("base_url", "https://api.ionq.co/v0.3"),
poll_interval_seconds=backend_config.get("poll_interval_seconds", 1.0),
timeout_seconds=backend_config.get("timeout_seconds"),
noise_model=backend_config.get("noise_model"),
)
Comment thread marqov/executors/ionq.py
Comment on lines +333 to +346
loop = asyncio.get_running_loop()
session = await loop.run_in_executor(None, self._get_session_sync)
url = f"{self.config.base_url}{path}"
headers = self._auth_headers()

def _call() -> dict[str, Any]:
response = session.request(
method, url, headers=headers, timeout=_HTTP_TIMEOUT_SECONDS, **kwargs
)
response.raise_for_status()
data: dict[str, Any] = response.json()
return data

return await loop.run_in_executor(None, _call)
@manasa-manoj-nbr

Copy link
Copy Markdown
Author

@ddri, could you please review this when you have a chance? Thank you!

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.

feat(executors): IonQ native API executor

2 participants