Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[QI2-1119] Support running in an existing event loop (i.e. jupyter notebook) #36

Merged
merged 4 commits into from
Oct 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions qiskit_quantuminspire/qi_jobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
from qiskit_quantuminspire.api.pagination import PageReader
from qiskit_quantuminspire.api.settings import ApiSettings
from qiskit_quantuminspire.base_provider import BaseProvider
from qiskit_quantuminspire.utils import run_async


@dataclass
Expand Down Expand Up @@ -89,7 +90,7 @@ def __init__(
self.batch_job_id: Union[int, None] = None

def submit(self) -> None:
asyncio.run(self._submit_async())
run_async(self._submit_async())

async def _submit_async(self) -> None:
"""Submit the (batch)job to the quantum inspire backend.
Expand Down Expand Up @@ -226,7 +227,7 @@ def result(self) -> Result:
"""Return the results of the job."""
if not self.done():
raise RuntimeError(f"(Batch)Job status is {self.status()}.")
asyncio.run(self._fetch_job_results())
run_async(self._fetch_job_results())
return self._process_results()

def status(self) -> JobStatus:
Expand All @@ -241,7 +242,7 @@ def status(self) -> JobStatus:
BatchJobStatus.FINISHED: JobStatus.DONE,
}

batch_job = asyncio.run(self._fetch_batchjob_status())
batch_job = run_async(self._fetch_batchjob_status())
return status_map[batch_job.status]

async def _fetch_batchjob_status(self) -> BatchJob:
Expand Down
4 changes: 2 additions & 2 deletions qiskit_quantuminspire/qi_provider.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import asyncio
from typing import Any, List, Optional, Sequence

from compute_api_client import ApiClient, BackendType, BackendTypesApi, PageBackendType
Expand All @@ -7,6 +6,7 @@
from qiskit_quantuminspire.api.pagination import PageReader
from qiskit_quantuminspire.base_provider import BaseProvider
from qiskit_quantuminspire.qi_backend import QIBackend
from qiskit_quantuminspire.utils import run_async


class QIProvider(BaseProvider):
Expand All @@ -30,7 +30,7 @@ async def _fetch_qi_backend_types(self) -> List[BackendType]:

def _construct_backends(self) -> List[QIBackend]:
"""Construct QIBackend using fetched backendtypes and metadata."""
qi_backend_types = asyncio.run(self._fetch_qi_backend_types())
qi_backend_types = run_async(self._fetch_qi_backend_types())
qi_backends = [QIBackend(provider=self, backend_type=backend_type) for backend_type in qi_backend_types]
return qi_backends

Expand Down
13 changes: 13 additions & 0 deletions qiskit_quantuminspire/utils.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import asyncio
import concurrent
from typing import Any, Coroutine

from qiskit.transpiler import CouplingMap


Expand All @@ -10,3 +14,12 @@ def is_coupling_map_complete(coupling_map: CouplingMap) -> bool:
is_semicomplete = all(distance in [1, 0] for distance in distance_matrix.flatten())

return is_semicomplete and coupling_map.is_symmetric


def run_async(async_function: Coroutine[Any, Any, Any]) -> Any:
try:
_ = asyncio.get_running_loop()
with concurrent.futures.ThreadPoolExecutor() as executor:
return executor.submit(asyncio.run, async_function).result()
except RuntimeError:
return asyncio.run(async_function)
2 changes: 1 addition & 1 deletion tests/e2e_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def _get_auth_tokens() -> None:
host = QI2_DEFAULT_HOST
member_id = asyncio.run(_fetch_team_member_id(host=host, access_token=token_info["access_token"]))
auth_settings = AuthSettings(
client_id="compute-job-manager",
client_id="Yz7ni9PUAyT43eUASZfmc1yqI66QxLUJ",
code_challenge_method="S256",
code_verifyer_length=64,
well_known_endpoint=f"{IDP_URL_STAGING}/.well-known/openid-configuration",
Expand Down
4 changes: 3 additions & 1 deletion tests/test_qi_job.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,9 @@ def test_result(mocker: MockerFixture) -> None:


@pytest.mark.asyncio
async def test_result_raises_error_when_status_not_done(mocker: MockerFixture, mock_api_client: MagicMock) -> None:
def test_result_raises_error_when_status_not_done(
mocker: MockerFixture, mock_api_client: MagicMock, mock_batchjob_api: MagicMock
) -> None:
job = QIJob(run_input="", backend=None)
mocker.patch.object(job, "done", return_value=False)
with pytest.raises(RuntimeError):
Expand Down
21 changes: 20 additions & 1 deletion tests/test_utils.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import asyncio

import pytest
from qiskit.transpiler import CouplingMap

from qiskit_quantuminspire.utils import is_coupling_map_complete
from qiskit_quantuminspire.utils import is_coupling_map_complete, run_async


@pytest.mark.parametrize(
Expand All @@ -19,3 +21,20 @@
)
def test_is_coupling_map_complete(coupling_map: CouplingMap, is_complete: bool) -> None:
assert is_coupling_map_complete(coupling_map) == is_complete


def test_async_run_no_loop() -> None:
async def t_coro() -> None:
await asyncio.sleep(1)

run_async(t_coro())


def test_async_run_loop() -> None:
async def t_coro() -> None:
await asyncio.sleep(1)

async def main() -> None:
run_async(t_coro())

asyncio.run(main())