From d6940a03d14dab9a04d086e210656b957b9a76fd Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Sep 2024 13:42:51 -0700 Subject: [PATCH 01/10] Add type annotations --- qiskit_ionq/__init__.py | 13 ++++++-- qiskit_ionq/exceptions.py | 10 ++++--- qiskit_ionq/ionq_backend.py | 59 +++++++++++++++++++------------------ qiskit_ionq/ionq_job.py | 2 +- 4 files changed, 49 insertions(+), 35 deletions(-) diff --git a/qiskit_ionq/__init__.py b/qiskit_ionq/__init__.py index 8e4199f..3ede028 100644 --- a/qiskit_ionq/__init__.py +++ b/qiskit_ionq/__init__.py @@ -26,8 +26,6 @@ """Provider for IonQ backends""" -import warnings - # warn if qiskit is not installed try: from qiskit.version import get_version_info # pylint: disable=unused-import @@ -41,3 +39,14 @@ from .ionq_gates import GPIGate, GPI2Gate, MSGate, ZZGate from .constants import ErrorMitigation from .ionq_equivalence_library import add_equivalences + +__all__ = [ + "__version__", + "IonQProvider", + "GPIGate", + "GPI2Gate", + "MSGate", + "ZZGate", + "ErrorMitigation", + "add_equivalences", +] diff --git a/qiskit_ionq/exceptions.py b/qiskit_ionq/exceptions.py index 7dfe55a..8653827 100644 --- a/qiskit_ionq/exceptions.py +++ b/qiskit_ionq/exceptions.py @@ -25,6 +25,8 @@ # limitations under the License. """Exceptions for the IonQ Provider.""" +from __future__ import annotations + import json.decoder as jd import requests @@ -36,10 +38,10 @@ class IonQError(QiskitError): """Base class for errors raised by an IonQProvider.""" - def __str__(self): + def __str__(self) -> str: return f"{self.__class__.__name__}({self.message!r})" - def __repr__(self): + def __repr__(self) -> str: return repr(str(self)) @@ -94,7 +96,7 @@ class IonQAPIError(IonQError): """ @classmethod - def raise_for_status(cls, response): + def raise_for_status(cls, response) -> IonQAPIError: """Raise an instance of the exception class from an API response object if needed. Args: response (:class:`Response `): An IonQ REST API response. @@ -187,7 +189,7 @@ class IonQGateError(IonQError, JobError): gate_name: The name of the gate which caused this error. """ - def __init__(self, gate_name, gateset): + def __init__(self, gate_name: str, gateset): self.gate_name = gate_name self.gateset = gateset super().__init__( diff --git a/qiskit_ionq/ionq_backend.py b/qiskit_ionq/ionq_backend.py index 35fc7d0..08b0def 100644 --- a/qiskit_ionq/ionq_backend.py +++ b/qiskit_ionq/ionq_backend.py @@ -26,10 +26,13 @@ """IonQ provider backends.""" +from __future__ import annotations + import abc from datetime import datetime import warnings +from qiskit.circuit import QuantumCircuit from qiskit.providers import BackendV1 as Backend from qiskit.providers.models.backendconfiguration import BackendConfiguration from qiskit.providers.models.backendstatus import BackendStatus @@ -50,16 +53,16 @@ def __init__(self, data): self._data = data @property - def uuid(self): + def uuid(self) -> str: """The ID of the calibration. Returns: - str: The ID. + str: The ID. """ return self._data["id"] @property - def num_qubits(self): + def num_qubits(self) -> int: """The number of qubits available. Returns: @@ -68,7 +71,7 @@ def num_qubits(self): return int(self._data["qubits"]) @property - def target(self): + def target(self) -> str: """The target calibrated hardware. Returns: @@ -77,7 +80,7 @@ def target(self): return self._data["backend"] @property - def calibration_time(self): + def calibration_time(self) -> datetime: """Time of the measurement, in UTC. Returns: @@ -86,7 +89,7 @@ def calibration_time(self): return datetime.fromtimestamp(self._data["date"]) @property - def fidelities(self): + def fidelities(self) -> dict: """Fidelity for single-qubit (1q) and two-qubit (2q) gates, and State Preparation and Measurement (spam) operations. @@ -99,7 +102,7 @@ def fidelities(self): return self._data["fidelity"] @property - def timings(self): + def timings(self) -> dict: """Various system property timings. All times expressed as seconds. Timings currently include:: @@ -117,7 +120,7 @@ def timings(self): return self._data["timing"] @property - def connectivity(self): + def connectivity(self) -> list[tuple[int, int]]: """Returns connectivity data. Returns: @@ -132,13 +135,13 @@ class IonQBackend(Backend): _client = None - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: # Add IonQ equivalences ionq_equivalence_library.add_equivalences() super().__init__(*args, **kwargs) @classmethod - def _default_options(cls): + def _default_options(cls) -> Options: return Options( shots=1024, job_settings=None, @@ -148,7 +151,7 @@ def _default_options(cls): ) @property - def client(self): + def client(self) -> ionq_client.IonQClient: """A lazily populated IonQ API Client. Returns: @@ -158,7 +161,7 @@ def client(self): self._client = self.create_client() return self._client - def create_client(self): + def create_client(self) -> ionq_client.IonQClient: """Create an IonQ REST API Client using provider credentials. Raises: @@ -196,7 +199,7 @@ def create_client(self): return ionq_client.IonQClient(token, url, self._provider.custom_headers) # pylint: disable=missing-type-doc,missing-param-doc,arguments-differ,arguments-renamed - def run(self, circuit, **kwargs): + def run(self, circuit: QuantumCircuit, **kwargs) -> ionq_job.IonQJob: """Create and run a job on an IonQ Backend. Args: @@ -252,23 +255,23 @@ def run(self, circuit, **kwargs): job.submit() return job - def retrieve_job(self, job_id): + def retrieve_job(self, job_id: str) -> ionq_job.IonQJob: """get a job from a specific backend, by job id.""" return ionq_job.IonQJob(self, job_id, self.client) - def retrieve_jobs(self, job_ids): + def retrieve_jobs(self, job_ids: list[str]) -> list[ionq_job.IonQJob]: """get a list of jobs from a specific backend, job id""" return [ionq_job.IonQJob(self, job_id, self.client) for job_id in job_ids] - def cancel_job(self, job_id): + def cancel_job(self, job_id: str) -> dict: """cancels a job from a specific backend, by job id.""" return self.client.cancel_job(job_id) - def cancel_jobs(self, job_ids): + def cancel_jobs(self, job_ids: list[str]) -> list[dict]: """cancels a list of jobs from a specific backend, job id""" return [self.client.cancel_job(job_id) for job_id in job_ids] - def has_valid_mapping(self, circuit) -> bool: + def has_valid_mapping(self, circuit: QuantumCircuit) -> bool: """checks if the circuit has at least one valid qubit -> bit measurement. @@ -287,7 +290,7 @@ def has_valid_mapping(self, circuit) -> bool: return False # TODO: Implement backend status checks. - def status(self): + def status(self) -> BackendStatus: """Return a backend status object to the caller. Returns: @@ -301,7 +304,7 @@ def status(self): status_msg="", ) - def calibration(self): + def calibration(self) -> Calibration: """Fetch the most recent calibration data for this backend. Returns: @@ -314,7 +317,7 @@ def calibration(self): return Calibration(calibration_data) @abc.abstractmethod - def with_name(self, name, **kwargs): + def with_name(self, name, **kwargs) -> IonQBackend: """Helper method that returns this backend with a more specific target system.""" pass @@ -323,13 +326,13 @@ def gateset(self): """Helper method returning the gateset this backend is targeting.""" pass - def __eq__(self, other): + def __eq__(self, other) -> bool: if isinstance(other, self.__class__): return self.name() == other.name() and self.gateset() == other.gateset() else: return False - def __ne__(self, other): + def __ne__(self, other) -> bool: return not self.__eq__(other) @@ -352,7 +355,7 @@ class IonQSimulatorBackend(IonQBackend): """ @classmethod - def _default_options(cls): + def _default_options(cls) -> Options: return Options( shots=1024, job_settings=None, @@ -363,7 +366,7 @@ def _default_options(cls): ) # pylint: disable=missing-type-doc,missing-param-doc,arguments-differ,useless-super-delegation - def run(self, circuit, **kwargs): + def run(self, circuit: QuantumCircuit, **kwargs) -> ionq_job.IonQJob: """Create and run a job on IonQ's Simulator Backend. .. WARNING: @@ -380,7 +383,7 @@ def run(self, circuit, **kwargs): """ return super().run(circuit, **kwargs) - def calibration(self): + def calibration(self) -> None: """Simulators have no calibration data. Returns: @@ -456,7 +459,7 @@ def __init__(self, provider, name="simulator", gateset="qis"): ) super().__init__(configuration=config, provider=provider) - def with_name(self, name, **kwargs): + def with_name(self, name, **kwargs) -> IonQSimulatorBackend: """Helper method that returns this backend with a more specific target system.""" return IonQSimulatorBackend(self._provider, name, **kwargs) @@ -531,7 +534,7 @@ def __init__(self, provider, name="ionq_qpu", gateset="qis"): ) super().__init__(configuration=config, provider=provider) - def with_name(self, name, **kwargs): + def with_name(self, name, **kwargs) -> IonQQPUBackend: """Helper method that returns this backend with a more specific target system.""" return IonQQPUBackend(self._provider, name, **kwargs) diff --git a/qiskit_ionq/ionq_job.py b/qiskit_ionq/ionq_job.py index 71a555b..69e4975 100644 --- a/qiskit_ionq/ionq_job.py +++ b/qiskit_ionq/ionq_job.py @@ -156,7 +156,7 @@ class IonQJob(JobV1): def __init__( self, backend, - job_id, + job_id: str, client=None, circuit=None, passed_args=None, From f2f236b2f952db0e73a137e29c07df1a4d630594 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Sep 2024 13:52:38 -0700 Subject: [PATCH 02/10] More type hints --- qiskit_ionq/exceptions.py | 10 +++++---- qiskit_ionq/helpers.py | 26 +++++++++++++++------- qiskit_ionq/ionq_backend.py | 27 +++++++++++++++++------ qiskit_ionq/ionq_client.py | 29 ++++++++++++++++--------- qiskit_ionq/ionq_equivalence_library.py | 12 +++++----- qiskit_ionq/ionq_gates.py | 2 +- qiskit_ionq/ionq_job.py | 25 +++++++++++++-------- qiskit_ionq/ionq_provider.py | 24 +++++++++++++++----- 8 files changed, 105 insertions(+), 50 deletions(-) diff --git a/qiskit_ionq/exceptions.py b/qiskit_ionq/exceptions.py index 8653827..b6f9ef3 100644 --- a/qiskit_ionq/exceptions.py +++ b/qiskit_ionq/exceptions.py @@ -25,7 +25,9 @@ # limitations under the License. """Exceptions for the IonQ Provider.""" -from __future__ import annotations +from __future__ import annotations + +from typing import Literal import json.decoder as jd @@ -113,7 +115,7 @@ def raise_for_status(cls, response) -> IonQAPIError: raise res @classmethod - def from_response(cls, response): + def from_response(cls, response: requests.Response) -> IonQAPIError: """Raise an instance of the exception class from an API response object. Args: @@ -189,7 +191,7 @@ class IonQGateError(IonQError, JobError): gate_name: The name of the gate which caused this error. """ - def __init__(self, gate_name: str, gateset): + def __init__(self, gate_name: str, gateset: Literal["qis", "native"]): self.gate_name = gate_name self.gateset = gateset super().__init__( @@ -212,7 +214,7 @@ class IonQMidCircuitMeasurementError(IonQError, JobError): qubit_index: The qubit index to be measured mid-circuit """ - def __init__(self, qubit_index, gate_name): + def __init__(self, qubit_index: int, gate_name: str): self.qubit_index = qubit_index self.gate_name = gate_name super().__init__( diff --git a/qiskit_ionq/helpers.py b/qiskit_ionq/helpers.py index 7982d97..2950442 100644 --- a/qiskit_ionq/helpers.py +++ b/qiskit_ionq/helpers.py @@ -29,6 +29,8 @@ to IonQ REST API compatible values. """ +from __future__ import annotations + import json import gzip import base64 @@ -36,10 +38,16 @@ import warnings import os import requests +from typing import Literal from dotenv import dotenv_values from qiskit import __version__ as qiskit_terra_version -from qiskit.circuit import controlledgate as q_cgates +from qiskit.circuit import ( + controlledgate as q_cgates, + QuantumCircuit, + QuantumRegister, + ClassicalRegister, +) from qiskit.circuit.library import standard_gates as q_gates # Use this to get version instead of __version__ to avoid circular dependency. @@ -133,7 +141,9 @@ } -def qiskit_circ_to_ionq_circ(input_circuit, gateset="qis"): +def qiskit_circ_to_ionq_circ( + input_circuit: QuantumCircuit, gateset: Literal["qis", "native"] = "qis" +): """Build a circuit in IonQ's instruction format from qiskit instructions. .. ATTENTION:: This function ignores the following compiler directives: @@ -273,7 +283,9 @@ def qiskit_circ_to_ionq_circ(input_circuit, gateset="qis"): return output_circuit, num_meas, meas_map -def get_register_sizes_and_labels(registers): +def get_register_sizes_and_labels( + registers: list[QuantumRegister | ClassicalRegister], +) -> tuple[list, list]: """Returns a tuple of sizes and labels in for a given register Args: @@ -299,7 +311,7 @@ def get_register_sizes_and_labels(registers): return sizes, labels -def compress_to_metadata_string(metadata): # pylint: disable=invalid-name +def compress_to_metadata_string(metadata: dict | list) -> str: # pylint: disable=invalid-name """ Convert a metadata object to a compact string format (dumped, gzipped, base64 encoded) for storing in IonQ API metadata @@ -318,7 +330,7 @@ def compress_to_metadata_string(metadata): # pylint: disable=invalid-name return encoded.decode() -def decompress_metadata_string(input_string): # pylint: disable=invalid-name +def decompress_metadata_string(input_string: str) -> dict | list: # pylint: disable=invalid-name """ Convert compact string format (dumped, gzipped, base64 encoded) from IonQ API metadata back into a dict or list of dicts relevant to building @@ -366,7 +378,7 @@ def qiskit_to_ionq( else: ionq_circs, _, meas_map = qiskit_circ_to_ionq_circ(circuit, backend.gateset()) circuit = [circuit] - + circuit: list[QuantumCircuit] | tuple[QuantumCircuit, ...] metadata_list = [ { "memory_slots": circ.num_clbits, # int @@ -479,7 +491,6 @@ def default(self, o): return "unknown" - def resolve_credentials(token: str = None, url: str = None): """Resolve credentials for use in IonQ API calls. @@ -516,7 +527,6 @@ def resolve_credentials(token: str = None, url: str = None): } - def get_n_qubits(backend: str, _fallback=100) -> int: """Get the number of qubits for a given backend. diff --git a/qiskit_ionq/ionq_backend.py b/qiskit_ionq/ionq_backend.py index 08b0def..51bf25b 100644 --- a/qiskit_ionq/ionq_backend.py +++ b/qiskit_ionq/ionq_backend.py @@ -30,6 +30,7 @@ import abc from datetime import datetime +from typing import Literal, TYPE_CHECKING import warnings from qiskit.circuit import QuantumCircuit @@ -41,6 +42,9 @@ from . import exceptions, ionq_client, ionq_job, ionq_equivalence_library from .helpers import GATESET_MAP, get_n_qubits +if TYPE_CHECKING: + from .ionq_provider import IonQProvider + class Calibration: """ @@ -322,9 +326,8 @@ def with_name(self, name, **kwargs) -> IonQBackend: pass @abc.abstractmethod - def gateset(self): + def gateset(self) -> Literal["qis", "native"]: """Helper method returning the gateset this backend is targeting.""" - pass def __eq__(self, other) -> bool: if isinstance(other, self.__class__): @@ -391,10 +394,15 @@ def calibration(self) -> None: """ return None - def gateset(self): + def gateset(self) -> Literal["qis", "native"]: return self._gateset - def __init__(self, provider, name="simulator", gateset="qis"): + def __init__( + self, + provider, + name: str = "simulator", + gateset: Literal["qis", "native"] = "qis", + ): """Base class for interfacing with an IonQ backend""" self._gateset = gateset config = BackendConfiguration.from_dict( @@ -467,10 +475,15 @@ def with_name(self, name, **kwargs) -> IonQSimulatorBackend: class IonQQPUBackend(IonQBackend): """IonQ Backend for running qpu-based jobs.""" - def gateset(self): + def gateset(self) -> Literal["qis", "native"]: return self._gateset - def __init__(self, provider, name="ionq_qpu", gateset="qis"): + def __init__( + self, + provider: IonQProvider, + name: str = "ionq_qpu", + gateset: Literal["qis", "native"] = "qis", + ): self._gateset = gateset config = BackendConfiguration.from_dict( { @@ -534,7 +547,7 @@ def __init__(self, provider, name="ionq_qpu", gateset="qis"): ) super().__init__(configuration=config, provider=provider) - def with_name(self, name, **kwargs) -> IonQQPUBackend: + def with_name(self, name: str, **kwargs) -> IonQQPUBackend: """Helper method that returns this backend with a more specific target system.""" return IonQQPUBackend(self._provider, name, **kwargs) diff --git a/qiskit_ionq/ionq_client.py b/qiskit_ionq/ionq_client.py index f2ca4fb..e1ff8c3 100644 --- a/qiskit_ionq/ionq_client.py +++ b/qiskit_ionq/ionq_client.py @@ -26,9 +26,11 @@ """Basic API Client for IonQ's REST API""" +from __future__ import annotations + import json from collections import OrderedDict -from typing import Optional +from typing import Optional, TYPE_CHECKING from warnings import warn import requests @@ -38,6 +40,8 @@ from .helpers import qiskit_to_ionq, get_user_agent from .exceptions import IonQRetriableError +if TYPE_CHECKING: + from .ionq_job import IonQJob class IonQClient: """IonQ API Client @@ -48,7 +52,12 @@ class IonQClient: _custom_headers(dict): Extra headers to add to the request. """ - def __init__(self, token=None, url=None, custom_headers=None): + def __init__( + self, + token: str | None = None, + url: str | None = None, + custom_headers: dict | None = None, + ): self._token = token self._custom_headers = custom_headers or {} # strip trailing slashes from our base URL. @@ -58,7 +67,7 @@ def __init__(self, token=None, url=None, custom_headers=None): self._user_agent = get_user_agent() @property - def api_headers(self): + def api_headers(self) -> dict: """API Headers needed to make calls to the REST API. Returns: @@ -71,7 +80,7 @@ def api_headers(self): "User-Agent": self._user_agent, } - def make_path(self, *parts): + def make_path(self, *parts: str) -> str: """Make a "/"-delimited path, then append it to :attr:`_url`. Returns: @@ -107,7 +116,7 @@ def _get_with_retry(self, req_path, params=None, headers=None, timeout=30): return res @retry(exceptions=IonQRetriableError, tries=5) - def submit_job(self, job) -> dict: + def submit_job(self, job: IonQJob) -> dict: """Submit job to IonQ API This returns a JSON dict with status "submitted" and the job's id. @@ -139,7 +148,7 @@ def submit_job(self, job) -> dict: return res.json() @retry(exceptions=IonQRetriableError, max_delay=60, backoff=2, jitter=1) - def retrieve_job(self, job_id: str): + def retrieve_job(self, job_id: str) -> dict: """Retrieve job information from the IonQ API. The returned JSON dict will only have data if job has completed. @@ -160,7 +169,7 @@ def retrieve_job(self, job_id: str): return res.json() @retry(exceptions=IonQRetriableError, tries=5) - def cancel_job(self, job_id: str): + def cancel_job(self, job_id: str) -> dict: """Attempt to cancel a job which has not yet run. .. NOTE:: If the job has already reached status "completed", this cancel action is a no-op. @@ -179,7 +188,7 @@ def cancel_job(self, job_id: str): exceptions.IonQAPIError.raise_for_status(res) return res.json() - def cancel_jobs(self, job_ids: list): + def cancel_jobs(self, job_ids: list[str]) -> list[dict]: """Cancel multiple jobs at once. Args: @@ -191,7 +200,7 @@ def cancel_jobs(self, job_ids: list): return [self.cancel_job(job_id) for job_id in job_ids] @retry(exceptions=IonQRetriableError, tries=3) - def delete_job(self, job_id: str): + def delete_job(self, job_id: str) -> dict: """Delete a job and associated data. Args: @@ -235,7 +244,7 @@ def get_results( job_id: str, sharpen: Optional[bool] = None, extra_query_params: Optional[dict] = None, - ): + ) -> dict: """Retrieve job results from the IonQ API. The returned JSON dict will only have data if job has completed. diff --git a/qiskit_ionq/ionq_equivalence_library.py b/qiskit_ionq/ionq_equivalence_library.py index 82b9bd5..f0979be 100644 --- a/qiskit_ionq/ionq_equivalence_library.py +++ b/qiskit_ionq/ionq_equivalence_library.py @@ -33,7 +33,7 @@ from .ionq_gates import GPIGate, GPI2Gate, MSGate -def u_gate_equivalence(): +def u_gate_equivalence() -> None: """Add U gate equivalence to the SessionEquivalenceLibrary.""" q = QuantumRegister(1, "q") theta_param = Parameter("theta_param") @@ -56,7 +56,7 @@ def u_gate_equivalence(): ) -def cx_gate_equivalence(): +def cx_gate_equivalence() -> None: """Add CX gate equivalence to the SessionEquivalenceLibrary.""" q = QuantumRegister(2, "q") cx_gate = QuantumCircuit(q) @@ -70,7 +70,7 @@ def cx_gate_equivalence(): # Below are the rules needed for Aer simulator to simulate circuits containing IonQ native gates -def gpi_gate_equivalence(): +def gpi_gate_equivalence() -> None: """Add GPI gate equivalence to the SessionEquivalenceLibrary.""" q = QuantumRegister(1, "q") phi_param = Parameter("phi_param") @@ -80,7 +80,7 @@ def gpi_gate_equivalence(): SessionEquivalenceLibrary.add_equivalence(GPIGate(phi_param), gpi_gate) -def gpi2_gate_equivalence(): +def gpi2_gate_equivalence() -> None: """Add GPI2 gate equivalence to the SessionEquivalenceLibrary.""" q = QuantumRegister(1, "q") phi_param = Parameter("phi_param") @@ -91,7 +91,7 @@ def gpi2_gate_equivalence(): SessionEquivalenceLibrary.add_equivalence(GPI2Gate(phi_param), gpi2_gate) -def ms_gate_equivalence(): +def ms_gate_equivalence() -> None: """Add MS gate equivalence to the SessionEquivalenceLibrary.""" q = QuantumRegister(2, "q") phi0_param = Parameter("phi0_param") @@ -125,7 +125,7 @@ def ms_gate_equivalence(): ) -def add_equivalences(): +def add_equivalences() -> None: """Add IonQ gate equivalences to the SessionEquivalenceLibrary.""" u_gate_equivalence() cx_gate_equivalence() diff --git a/qiskit_ionq/ionq_gates.py b/qiskit_ionq/ionq_gates.py index 3e7aa23..5ee2f46 100644 --- a/qiskit_ionq/ionq_gates.py +++ b/qiskit_ionq/ionq_gates.py @@ -172,7 +172,7 @@ def __init__(self, theta: ParameterValueType, label: Optional[str] = None): """Create new ZZ gate.""" super().__init__("zz", 2, [theta], label=label) - def __array__(self, dtype=None): + def __array__(self, dtype=None) -> numpy.ndarray: """Return a numpy.array for the ZZ gate.""" itheta2 = 1j * float(self.params[0]) * math.pi return numpy.array( diff --git a/qiskit_ionq/ionq_job.py b/qiskit_ionq/ionq_job.py index 69e4975..08bc7b2 100644 --- a/qiskit_ionq/ionq_job.py +++ b/qiskit_ionq/ionq_job.py @@ -35,17 +35,24 @@ :class:`BaseJob `. """ +from __future__ import annotations + import warnings import numpy as np +from typing import TYPE_CHECKING +from qiskit import QuantumCircuit from qiskit.providers import JobV1, jobstatus from qiskit.providers.exceptions import JobTimeoutError from .ionq_result import IonQResult as Result from .helpers import decompress_metadata_string - from . import constants, exceptions +if TYPE_CHECKING: + from . import ionq_backend + from . import ionq_client + def map_output(data, clbits, num_qubits): """Map histogram according to measured bits""" @@ -155,11 +162,11 @@ class IonQJob(JobV1): def __init__( self, - backend, + backend: ionq_backend.IonQBackend, job_id: str, - client=None, - circuit=None, - passed_args=None, + client: ionq_client.IonQClient | None = None, + circuit: QuantumCircuit | None = None, + passed_args: dict | None = None, ): super().__init__(backend, job_id) self._client = client or backend.client @@ -187,11 +194,11 @@ def __init__( self._job_id = job_id self.status() - def cancel(self): + def cancel(self) -> None: """Cancel this job.""" self._client.cancel_job(self._job_id) - def submit(self): + def submit(self) -> None: """Submit a job to the IonQ API. Raises: @@ -206,7 +213,7 @@ def submit(self): response = self._client.submit_job(job=self) self._job_id = response["id"] - def get_counts(self, circuit=None): + def get_counts(self, circuit: QuantumCircuit | None = None) -> dict: """Return the counts for the job. .. ATTENTION:: @@ -287,7 +294,7 @@ def result(self, sharpen: bool = None, extra_query_params: dict = None, **kwargs return self._result - def status(self, detailed=False): + def status(self, detailed: bool = False) -> jobstatus.JobStatus | dict: """Retrieve the status of a job Args: diff --git a/qiskit_ionq/ionq_provider.py b/qiskit_ionq/ionq_provider.py index 24fd8ee..44b017f 100644 --- a/qiskit_ionq/ionq_provider.py +++ b/qiskit_ionq/ionq_provider.py @@ -28,14 +28,16 @@ import logging +from typing import Callable, Literal + from qiskit.providers.exceptions import QiskitBackendNotFoundError from qiskit.providers.providerutils import filter_backends from .helpers import resolve_credentials from . import ionq_backend -logger = logging.getLogger(__name__) +logger = logging.getLogger(__name__) class IonQProvider: @@ -49,7 +51,12 @@ class IonQProvider: name = "ionq_provider" - def __init__(self, token: str = None, url: str = None, custom_headers: dict = None): + def __init__( + self, + token: str | None = None, + url: str | None = None, + custom_headers: dict | None = None, + ): super().__init__() self.custom_headers = custom_headers self.credentials = resolve_credentials(token, url) @@ -60,7 +67,12 @@ def __init__(self, token: str = None, url: str = None, custom_headers: dict = No ] ) - def get_backend(self, name: str = None, gateset="qis", **kwargs): + def get_backend( + self, + name: str | None = None, + gateset: Literal["qis", "native"] = "qis", + **kwargs, + ) -> ionq_backend.Backend: """Return a single backend matching the specified filtering. Args: name (str): name of the backend. @@ -87,7 +99,7 @@ class BackendService: of backends from provider. """ - def __init__(self, backends): + def __init__(self, backends: list[ionq_backend.Backend]): """Initialize service Parameters: @@ -97,7 +109,9 @@ def __init__(self, backends): for backend in backends: setattr(self, backend.name(), backend) - def __call__(self, name=None, filters=None, **kwargs): + def __call__( + self, name: str | None = None, filters: Callable | None = None, **kwargs + ) -> list[ionq_backend.Backend]: """A listing of all backends from this provider. Parameters: From 27716b4e12dae3c0eea778c0fe1fac095ce6ad05 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Sep 2024 15:06:58 -0700 Subject: [PATCH 03/10] Fix pylint --- qiskit_ionq/helpers.py | 2 +- qiskit_ionq/ionq_job.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit_ionq/helpers.py b/qiskit_ionq/helpers.py index 2950442..708d04e 100644 --- a/qiskit_ionq/helpers.py +++ b/qiskit_ionq/helpers.py @@ -37,8 +37,8 @@ import platform import warnings import os -import requests from typing import Literal +import requests from dotenv import dotenv_values from qiskit import __version__ as qiskit_terra_version diff --git a/qiskit_ionq/ionq_job.py b/qiskit_ionq/ionq_job.py index 08bc7b2..04ffe49 100644 --- a/qiskit_ionq/ionq_job.py +++ b/qiskit_ionq/ionq_job.py @@ -38,8 +38,8 @@ from __future__ import annotations import warnings -import numpy as np from typing import TYPE_CHECKING +import numpy as np from qiskit import QuantumCircuit from qiskit.providers import JobV1, jobstatus From ee076b6d7cd8d6fa22e5ce3db83cd7ec09056ec2 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Sep 2024 15:09:26 -0700 Subject: [PATCH 04/10] Require python >= 3.9 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index d38ec26..460f331 100644 --- a/setup.py +++ b/setup.py @@ -78,7 +78,7 @@ "Topic :: Scientific/Engineering", ], keywords="qiskit sdk quantum", - python_requires=">=3.7", + python_requires=">=3.9", setup_requires=["pytest-runner"], install_requires=REQUIREMENTS, tests_require=TEST_REQUIREMENTS, From 2639a220d19fd541399d177d43bd5ca11d93b522 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Sep 2024 15:18:48 -0700 Subject: [PATCH 05/10] Add py.typed --- qiskit_ionq/py.typed | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 qiskit_ionq/py.typed diff --git a/qiskit_ionq/py.typed b/qiskit_ionq/py.typed new file mode 100644 index 0000000..e69de29 From e19dfd9e452dd407c7b7a293d25e240da3804bf5 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Sep 2024 15:19:40 -0700 Subject: [PATCH 06/10] Add py.typed to package_data --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 460f331..7e3afc9 100644 --- a/setup.py +++ b/setup.py @@ -84,6 +84,7 @@ tests_require=TEST_REQUIREMENTS, zip_safe=False, include_package_data=True, + package_data={"qiskit_ionq": ["py.typed"]}, project_urls={ "Bug Tracker": "https://github.com/qiskit-partners/qiskit-ionq/issues", "Source Code": "https://github.com/qiskit-partners/qiskit-ionq", From 270b775a8117c0075c396878e141c4455120b0f5 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Sep 2024 15:25:34 -0700 Subject: [PATCH 07/10] Remove pytest-runner --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 7e3afc9..54f3839 100644 --- a/setup.py +++ b/setup.py @@ -79,7 +79,7 @@ ], keywords="qiskit sdk quantum", python_requires=">=3.9", - setup_requires=["pytest-runner"], + setup_requires=[], install_requires=REQUIREMENTS, tests_require=TEST_REQUIREMENTS, zip_safe=False, From f7ed2a3ff665439b702fcc0be7ab36701137a6d1 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Sep 2024 15:29:52 -0700 Subject: [PATCH 08/10] Remove tests_require --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 54f3839..7c46ea8 100644 --- a/setup.py +++ b/setup.py @@ -81,7 +81,7 @@ python_requires=">=3.9", setup_requires=[], install_requires=REQUIREMENTS, - tests_require=TEST_REQUIREMENTS, + extras_require={"test": TEST_REQUIREMENTS}, zip_safe=False, include_package_data=True, package_data={"qiskit_ionq": ["py.typed"]}, From 8d23d0180f063e0f56df821c8a992b129fc6275f Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Sep 2024 15:30:31 -0700 Subject: [PATCH 09/10] Change install command --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index c1a571c..e51f738 100644 --- a/tox.ini +++ b/tox.ini @@ -17,7 +17,7 @@ setenv = LANGUAGE=en_US LC_ALL=en_US.utf-8 commands = - python setup.py test + pip install ".[test]" [testenv:lint] deps = From ff931a0d3812cf2b8f1fb3b8f0f05a52ee8ce87f Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 9 Sep 2024 15:31:16 -0700 Subject: [PATCH 10/10] Install using deps --- tox.ini | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index e51f738..d2cd615 100644 --- a/tox.ini +++ b/tox.ini @@ -9,6 +9,7 @@ python = [testenv] deps = + .[test] -r requirements.txt -r requirements-test.txt usedevelop = true @@ -16,8 +17,6 @@ setenv = VIRTUAL_ENV={envdir} LANGUAGE=en_US LC_ALL=en_US.utf-8 -commands = - pip install ".[test]" [testenv:lint] deps =