From 74146f97eb96417b6f5c23c49e05fc059f2441ab Mon Sep 17 00:00:00 2001 From: Matthew Silverman Date: Fri, 15 Dec 2023 12:11:26 -0500 Subject: [PATCH 01/10] try using the latest black action (#83) --- .github/workflows/format.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index b26cdb2..0c1fa56 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -8,6 +8,6 @@ jobs: steps: - uses: actions/checkout@v1 - name: Black Code Formatter - uses: lgeiger/black-action@v1.0.1 + uses: lgeiger/black-action@master with: args: "-l 100 pennylane_ionq/ --check" From 2bb136f5a17da896ab11a4dd3c8e3475dad717b8 Mon Sep 17 00:00:00 2001 From: lillian542 <38584660+lillian542@users.noreply.github.com> Date: Fri, 15 Dec 2023 16:53:44 -0500 Subject: [PATCH 02/10] Dependabot updates (#84) * Bump certifi from 2022.5.18.1 to 2022.12.7 (#67) Bumps [certifi](https://github.com/certifi/python-certifi) from 2022.5.18.1 to 2022.12.7. - [Release notes](https://github.com/certifi/python-certifi/releases) - [Commits](https://github.com/certifi/python-certifi/compare/2022.05.18.1...2022.12.07) --- updated-dependencies: - dependency-name: certifi dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: lillian542 <38584660+lillian542@users.noreply.github.com> * Bump certifi from 2022.5.18.1 to 2022.12.7 in /doc (#66) Bumps [certifi](https://github.com/certifi/python-certifi) from 2022.5.18.1 to 2022.12.7. - [Release notes](https://github.com/certifi/python-certifi/releases) - [Commits](https://github.com/certifi/python-certifi/compare/2022.05.18.1...2022.12.07) --- updated-dependencies: - dependency-name: certifi dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: lillian542 <38584660+lillian542@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- doc/requirements.txt | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/requirements.txt b/doc/requirements.txt index 2a9f447..697f1e4 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -4,7 +4,7 @@ autograd==1.4 autoray==0.3.2 Babel==2.10.3 cachetools==5.2.0 -certifi==2022.5.18.1 +certifi==2022.12.7 charset-normalizer==2.0.12 docutils==0.18.1 future==0.18.2 diff --git a/requirements.txt b/requirements.txt index fdeb5fe..cb87098 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ appdirs==1.4.4 autograd==1.5 autoray==0.3.2 cachetools==5.2.0 -certifi==2022.5.18.1 +certifi==2022.12.7 charset-normalizer==2.0.12 future==0.18.2 idna==3.3 From 82ace771f87cb83f6f1833b1d20503ea7252b6f7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Dec 2023 10:53:15 -0500 Subject: [PATCH 03/10] Bump certifi from 2022.12.7 to 2023.7.22 (#90) * Bump certifi from 2022.12.7 to 2023.7.22 Bumps [certifi](https://github.com/certifi/python-certifi) from 2022.12.7 to 2023.7.22. - [Commits](https://github.com/certifi/python-certifi/compare/2022.12.07...2023.07.22) --- updated-dependencies: - dependency-name: certifi dependency-type: direct:production ... Signed-off-by: dependabot[bot] * Bump certifi from 2022.12.7 to 2023.7.22 in /doc (#89) Bumps [certifi](https://github.com/certifi/python-certifi) from 2022.12.7 to 2023.7.22. - [Commits](https://github.com/certifi/python-certifi/compare/2022.12.07...2023.07.22) --- updated-dependencies: - dependency-name: certifi dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- doc/requirements.txt | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/requirements.txt b/doc/requirements.txt index 697f1e4..623de0d 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -4,7 +4,7 @@ autograd==1.4 autoray==0.3.2 Babel==2.10.3 cachetools==5.2.0 -certifi==2022.12.7 +certifi==2023.7.22 charset-normalizer==2.0.12 docutils==0.18.1 future==0.18.2 diff --git a/requirements.txt b/requirements.txt index cb87098..f43e8b9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ appdirs==1.4.4 autograd==1.5 autoray==0.3.2 cachetools==5.2.0 -certifi==2022.12.7 +certifi==2023.7.22 charset-normalizer==2.0.12 future==0.18.2 idna==3.3 From e592b060bacb104c141c228a64f7894d986dc636 Mon Sep 17 00:00:00 2001 From: Spencer Churchill Date: Tue, 2 Jan 2024 14:09:28 -0800 Subject: [PATCH 04/10] Open v0.3 API + debiasing (#75) * create job with debias * get sharpened results * add error_mitigation and sharpen to the end Co-authored-by: Matthew Silverman * use userwarning Co-authored-by: Matthew Silverman * fix undefined params and sharpen Co-authored-by: Matthew Silverman * reorder args Co-authored-by: Matthew Silverman * final arg reorder Co-authored-by: Matthew Silverman * remove unnecessary kwargs pop * use new fields for tests * add to contributors * format code + add params * Update CHANGELOG.md Co-authored-by: Matthew Silverman * increase test coverage * update changelog * add error_mitigation testing * expand on docstrings * black formatting * update overview doc to refer to docstrings for details * more black formatting * update hyperlink * troubleshoot docstring formatting problem * troubleshoot docstring formatting problem 2 * update changelog and docstrings * black formatting for tests * correct wording * default backend is harmony * try to please codecov * revert and simplify default target --------- Co-authored-by: Matthew Silverman Co-authored-by: lillian542 <38584660+lillian542@users.noreply.github.com> Co-authored-by: lillian542 Co-authored-by: Matthew Silverman --- CHANGELOG.md | 17 +++++ doc/devices.rst | 14 ++-- pennylane_ionq/api_client.py | 21 +++--- pennylane_ionq/device.py | 90 ++++++++++++++++++----- tests/conftest.py | 4 +- tests/test_api_client.py | 67 +++++++++++++----- tests/test_device.py | 134 ++++++++++++++++++++++------------- 7 files changed, 244 insertions(+), 103 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 41c34de..adcc5cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,24 @@ ### New features since last release +* Application of debiasing and sharpening for error mitigation is made available, with parameters set on device initialization. Error mitigation strategies that + need to be set at runtime are defined in the `error_mitigation` dictionary (currently a single strategy, `debias`, is available). Whether or not to + apply sharpening to the returned results is set via the parameter `sharpen`. A device using debiasing and sharpening to mitigate errors can be initialized as: + + ```python + import pennylane as qml + + dev = qml.device("ionq.qpu", wires=2, error_mitigation={"debias": True}, sharpen=True) + ``` + + For more details, see the [IonQ Guide on sharpening and debiasing](https://ionq.com/resources/debiasing-and-sharpening), or refer to the publication + [(#75)](https://github.com/PennyLaneAI/PennyLane-IonQ/pull/75) + ### Improvements 🛠 +* The IonQ API version accessed via the plugin is updated from 0.1 to 0.3 + [(#75)](https://github.com/PennyLaneAI/PennyLane-IonQ/pull/75) + * Use new `backend` field to specify `qpu`. [(#81)](https://github.com/PennyLaneAI/PennyLane-IonQ/pull/81) @@ -20,6 +36,7 @@ This release contains contributions from (in alphabetical order): Spencer Churchill +Lillian Frederiksen --- # Release 0.32.0 diff --git a/doc/devices.rst b/doc/devices.rst index 0d3cb28..0cda9f5 100644 --- a/doc/devices.rst +++ b/doc/devices.rst @@ -4,8 +4,8 @@ IonQ Devices The PennyLane-IonQ plugin provides the ability for PennyLane to access devices available via IonQ's online API. -Currently, access is available to two remote devices: an ideal and -a noisy trapped-ion simulator. +Currently, access is available to two remote devices: one to access an ideal +trapped-ion simulator and another to access to IonQ's trapped-ion QPUs. .. raw::html
@@ -13,9 +13,8 @@ a noisy trapped-ion simulator. Ideal trapped-ion simulator ------------------------ -This device provides an ideal noiseless trapped-ion simulation. -Once the plugin has been installed, you can use this device -directly in PennyLane by specifying ``"ionq.simulator"``: +The :class:`~.pennylane_ionq.SimulatorDevice` provides an ideal noiseless trapped-ion simulation. +Once the plugin has been installed, you can use this device directly in PennyLane by specifying ``"ionq.simulator"``: .. code-block:: python @@ -38,9 +37,8 @@ directly in PennyLane by specifying ``"ionq.simulator"``: Trapped-Ion QPU --------------- -This device provides access to IonQ's trapped-ion QPUs. -Once the plugin has been installed, you can use this device -directly in PennyLane by specifying ``"ionq.qpu"`` with a +The :class:`~.pennylane_ionq.QPUDevice` provides access to IonQ's trapped-ion QPUs. Once the plugin has been +installed, you can use this device directly in PennyLane by specifying ``"ionq.qpu"`` with a ``"backend"`` from `available backends `_: .. code-block:: python diff --git a/pennylane_ionq/api_client.py b/pennylane_ionq/api_client.py index 08f9365..4f6b19d 100644 --- a/pennylane_ionq/api_client.py +++ b/pennylane_ionq/api_client.py @@ -112,12 +112,16 @@ class APIClient: api_key (str): IonQ cloud platform API key """ - USER_AGENT = "pennylane-ionq-api-client/0.1" - HOSTNAME = "api.ionq.co/v0.1" + USER_AGENT = "pennylane-ionq-api-client/0.3" + HOSTNAME = "api.ionq.co/v0.3" BASE_URL = "https://{}".format(HOSTNAME) def __init__(self, **kwargs): - self.AUTHENTICATION_TOKEN = os.getenv("IONQ_API_KEY") or kwargs.get("api_key", None) + self.AUTHENTICATION_TOKEN = ( + kwargs.get("api_key", None) + or os.getenv("PENNYLANE_IONQ_API_KEY") + or os.getenv("IONQ_API_KEY") + ) self.DEBUG = False if "IONQ_DEBUG" in os.environ: @@ -196,17 +200,18 @@ def request(self, method, **params): return response - def get(self, path): + def get(self, path, params=None): """ Sends a GET request to the provided path. Returns a response object. Args: path (str): path to send the GET request to + params (dict): parameters to include in the request Returns: requests.Response: A response object, or None if no response could be fetched """ - return self.request(requests.get, url=self.join_path(path)) + return self.request(requests.get, url=self.join_path(path), params=params) def post(self, path, payload): """ @@ -249,7 +254,7 @@ def join_path(self, path): """ return join_path(self.resource.PATH, path) - def get(self, resource_id=None): + def get(self, resource_id=None, params=None): """ Attempts to retrieve a particular record by sending a GET request to the appropriate endpoint. If successful, the resource @@ -262,9 +267,9 @@ def get(self, resource_id=None): raise MethodNotSupportedException("GET method on this resource is not supported") if resource_id is not None: - response = self.client.get(self.join_path(str(resource_id))) + response = self.client.get(self.join_path(str(resource_id)), params=params) else: - response = self.client.get(self.resource.PATH) + response = self.client.get(self.resource.PATH, params=params) self.handle_response(response) def create(self, **params): diff --git a/pennylane_ionq/device.py b/pennylane_ionq/device.py index 76e6f97..026f699 100644 --- a/pennylane_ionq/device.py +++ b/pennylane_ionq/device.py @@ -14,7 +14,7 @@ """ This module contains the device class for constructing IonQ devices for PennyLane. """ -import os, warnings +import warnings from time import sleep import numpy as np @@ -63,16 +63,27 @@ class IonQDevice(QubitDevice): r"""IonQ device for PennyLane. Args: - target (str): the target device, either ``"simulator"`` or ``"qpu"`` wires (int or Iterable[Number, str]]): Number of wires to initialize the device with, or iterable that contains unique labels for the subsystems as numbers (i.e., ``[-1, 0, 2]``) or strings (``['ancilla', 'q1', 'q2']``). - gateset (str): the target gateset, either ``"qis"`` or ``"native"``. + + Kwargs: + target (str): the target device, either ``"simulator"`` or ``"qpu"``. Defaults to ``simulator``. + gateset (str): the target gateset, either ``"qis"`` or ``"native"``. Defaults to ``qis``. shots (int, list[int]): Number of circuit evaluations/random samples used to estimate - expectation values of observables. + expectation values of observables. Defaults to 1024. If a list of integers is passed, the circuit evaluations are batched over the list of shots. api_key (str): The IonQ API key. If not provided, the environment variable ``IONQ_API_KEY`` is used. + error_mitigation (dict): settings for error mitigation when creating a job. Defaults to None. + Not available on all backends. Set by default on some hardware systems. See + `IonQ API Job Creation `_ and + `IonQ Debiasing and Sharpening `_ for details. + Valid keys include: ``debias`` (bool). + sharpen (bool): whether to use sharpening when accessing the results of an executed job. Defaults to None + (no value passed at job retrieval). Will generally return more accurate results if your expected output + distribution has peaks. See `IonQ Debiasing and Sharpening + `_ for details. """ # pylint: disable=too-many-instance-attributes name = "IonQ PennyLane plugin" @@ -91,7 +102,17 @@ class IonQDevice(QubitDevice): # and therefore does not support the Hermitian observable. observables = {"PauliX", "PauliY", "PauliZ", "Hadamard", "Identity"} - def __init__(self, wires, *, target="simulator", gateset="qis", shots=1024, api_key=None): + def __init__( + self, + wires, + *, + target="simulator", + gateset="qis", + shots=1024, + api_key=None, + error_mitigation=None, + sharpen=False, + ): if shots is None: raise ValueError("The ionq device does not support analytic expectation values.") @@ -99,6 +120,8 @@ def __init__(self, wires, *, target="simulator", gateset="qis", shots=1024, api_ self.target = target self.api_key = api_key self.gateset = gateset + self.error_mitigation = error_mitigation + self.sharpen = sharpen self._operation_map = _GATESET_OPS[gateset] self.reset() @@ -107,16 +130,25 @@ def reset(self): self._prob_array = None self.histogram = None self.circuit = { + "format": "ionq.circuit.v0", "qubits": self.num_wires, "circuit": [], "gateset": self.gateset, } self.job = { - "lang": "json", - "body": self.circuit, + "input": self.circuit, "target": self.target, "shots": self.shots, } + if self.error_mitigation is not None: + self.job["error_mitigation"] = self.error_mitigation + if self.job["target"] == "qpu": + self.job["target"] = "qpu.harmony" + warnings.warn( + "The ionq_qpu backend is deprecated. Defaulting to ionq_qpu.harmony.", + UserWarning, + stacklevel=2, + ) @property def operations(self): @@ -190,7 +222,9 @@ def _submit_job(self): if job.is_failed: raise JobExecutionError("Job failed") - job.manager.get(job.id.value) + params = {} if self.sharpen is None else {"sharpen": self.sharpen} + + job.manager.get(resource_id=job.id.value, params=params) # The returned job histogram is of the form # dict[str, float], and maps the computational basis @@ -242,19 +276,26 @@ class SimulatorDevice(IonQDevice): wires (int or Iterable[Number, str]]): Number of wires to initialize the device with, or iterable that contains unique labels for the subsystems as numbers (i.e., ``[-1, 0, 2]``) or strings (``['ancilla', 'q1', 'q2']``). - gateset (str): the target gateset, either ``"qis"`` or ``"native"``. - shots (int, list[int]): Number of circuit evaluations/random samples used to estimate + gateset (str): the target gateset, either ``"qis"`` or ``"native"``. Defaults to ``qis``. + shots (int, list[int], None): Number of circuit evaluations/random samples used to estimate expectation values of observables. If ``None``, the device calculates probability, expectation values, and variances analytically. If an integer, it specifies the number of samples to estimate these quantities. If a list of integers is passed, the circuit evaluations are batched over the list of shots. + Defaults to 1024. api_key (str): The IonQ API key. If not provided, the environment variable ``IONQ_API_KEY`` is used. """ name = "IonQ Simulator PennyLane plugin" short_name = "ionq.simulator" - def __init__(self, wires, *, target="simulator", gateset="qis", shots=1024, api_key=None): - super().__init__(wires=wires, target=target, gateset=gateset, shots=shots, api_key=api_key) + def __init__(self, wires, *, gateset="qis", shots=1024, api_key=None): + super().__init__( + wires=wires, + target="simulator", + gateset=gateset, + shots=shots, + api_key=api_key, + ) def generate_samples(self): """Generates samples by random sampling with the probabilities returned by the simulator.""" @@ -270,14 +311,23 @@ class QPUDevice(IonQDevice): wires (int or Iterable[Number, str]]): Number of wires to initialize the device with, or iterable that contains unique labels for the subsystems as numbers (i.e., ``[-1, 0, 2]``) or strings (``['ancilla', 'q1', 'q2']``). - gateset (str): the target gateset, either ``"qis"`` or ``"native"``. + gateset (str): the target gateset, either ``"qis"`` or ``"native"``. Defaults to ``qis``. backend (str): Optional specifier for an IonQ backend. Can be ``"harmony"``, ``"aria-1"``, etc. + Default to ``harmony``. shots (int, list[int]): Number of circuit evaluations/random samples used to estimate - expectation values of observables. If ``None``, the device calculates probability, expectation values, - and variances analytically. If an integer, it specifies the number of samples to estimate these quantities. - If a list of integers is passed, the circuit evaluations are batched over the list of shots. + expectation values of observables. Defaults to 1024. If a list of integers is passed, the + circuit evaluations are batched over the list of shots. api_key (str): The IonQ API key. If not provided, the environment variable ``IONQ_API_KEY`` is used. + error_mitigation (dict): settings for error mitigation when creating a job. Defaults to None. + Not available on all backends. Set by default on some hardware systems. See + `IonQ API Job Creation `_ and + `IonQ Debiasing and Sharpening `_ for details. + Valid keys include: ``debias`` (bool). + sharpen (bool): whether to use sharpening when accessing the results of an executed job. + Defaults to None (no value passed at job retrieval). Will generally return more accurate results if + your expected output distribution has peaks. See `IonQ Debiasing and Sharpening + `_ for details. """ name = "IonQ QPU PennyLane plugin" short_name = "ionq.qpu" @@ -286,12 +336,14 @@ def __init__( self, wires, *, - target="qpu", - backend=None, gateset="qis", shots=1024, + backend="harmony", + error_mitigation=None, + sharpen=None, api_key=None, ): + target = "qpu" self.backend = backend if self.backend is not None: target += "." + self.backend @@ -301,6 +353,8 @@ def __init__( gateset=gateset, shots=shots, api_key=api_key, + error_mitigation=error_mitigation, + sharpen=sharpen, ) def generate_samples(self): diff --git a/tests/conftest.py b/tests/conftest.py index b59ea8d..2e46b6a 100755 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -36,9 +36,7 @@ U2 = np.array([[0, 1, 1, 1], [1, 0, 1, -1], [1, -1, 0, 1], [1, 1, -1, 0]]) / np.sqrt(3) # single qubit Hermitian observable -A = np.array( - [[1.02789352, 1.61296440 - 0.3498192j], [1.61296440 + 0.3498192j, 1.23920938 + 0j]] -) +A = np.array([[1.02789352, 1.61296440 - 0.3498192j], [1.61296440 + 0.3498192j, 1.23920938 + 0j]]) # ========================================================== diff --git a/tests/test_api_client.py b/tests/test_api_client.py index e30d8e8..e95e720 100755 --- a/tests/test_api_client.py +++ b/tests/test_api_client.py @@ -22,6 +22,8 @@ from pennylane_ionq.api_client import ( requests, Job, + Resource, + Field, ResourceManager, ObjectAlreadyCreatedException, MethodNotSupportedException, @@ -122,17 +124,13 @@ def test_set_authorization_header(self): authentication_token = MagicMock() client.set_authorization_header(authentication_token) - assert client.HEADERS["Authorization"] == "apiKey {}".format( - authentication_token - ) + assert client.HEADERS["Authorization"] == "apiKey {}".format(authentication_token) def test_join_path(self, client): """ Test that two paths can be joined and separated by a forward slash. """ - assert client.join_path("jobs") == "{client.BASE_URL}/jobs".format( - client=client - ) + assert client.join_path("jobs") == "{client.BASE_URL}/jobs".format(client=client) class TestResourceManager: @@ -169,7 +167,9 @@ def test_get_unsupported(self): with pytest.raises(MethodNotSupportedException): manager.get(1) - def test_get(self, monkeypatch): + @pytest.mark.parametrize("resource_id", [1, None]) + @pytest.mark.parametrize("params", [{}, {"sharpen": True}, {"sharpen": False}]) + def test_get(self, monkeypatch, resource_id, params): """ Test a successful GET request. Tests that manager.handle_response is being called with the correct Response object. @@ -184,7 +184,7 @@ def test_get(self, monkeypatch): manager = ResourceManager(mock_resource, mock_client) monkeypatch.setattr(manager, "handle_response", MagicMock()) - manager.get(1) + manager.get(resource_id=resource_id, params=params) # TODO test that this is called with correct path mock_client.get.assert_called_once() @@ -248,13 +248,9 @@ def test_handle_response(self, monkeypatch): manager = ResourceManager(mock_resource, mock_client) - monkeypatch.setattr( - manager, "handle_success_response", mock_handle_success_response - ) + monkeypatch.setattr(manager, "handle_success_response", mock_handle_success_response) - monkeypatch.setattr( - manager, "handle_error_response", mock_handle_error_response - ) + monkeypatch.setattr(manager, "handle_error_response", mock_handle_error_response) manager.handle_response(mock_response) assert manager.http_response_data == mock_response.json() @@ -303,12 +299,14 @@ def mock_raise(exception): mock_get_response = MockGETResponse(200) monkeypatch.setattr( - requests, "get", lambda url, timeout, headers: mock_get_response + requests, + "get", + lambda url, params=None, timeout=None, headers=None: mock_get_response, ) monkeypatch.setattr( requests, "post", - lambda url, timeout, headers, data: mock_raise(MockException), + lambda url, data=None, timeout=None, headers=None: mock_raise(MockException), ) client = api_client.APIClient(debug=True, api_key="test") @@ -327,7 +325,40 @@ def mock_raise(exception): assert len(client.errors) == 1 -class TestJob: +class TestResource: + + def test_resource_reloaading(self, monkeypatch): + """Test that ID must be set on resource types when reloading.""" + + class NoID(Resource): + """Dummy API resource without ID set.""" + + def __init__(self, client=None, api_key=None): + """Dummy init.""" + self.fields = (Field("foo", str),) + super().__init__(client=client, api_key=api_key) + + class WithID(Resource): + """Dummy API resource without ID set.""" + + def __init__(self, client=None, api_key=None): + """Dummy init.""" + self.fields = (Field("foo", str), Field("id", str)) + super().__init__(client=client, api_key=api_key) + + monkeypatch.setattr( + requests, "post", lambda url, timeout, headers, data: MockPOSTResponse(201) + ) + + res = NoID(api_key="test") + + with pytest.raises(TypeError, match="Resource does not have an ID"): + res.reload() + + res = WithID(api_key="test") + res.reload() + + def test_create_created(self, monkeypatch): """ Tests a successful Job creatioin with a mock POST response. Asserts that all fields on @@ -338,6 +369,8 @@ def test_create_created(self, monkeypatch): ) job = Job(api_key="test") job.manager.create(params={}) + assert not job.is_complete + assert not job.is_failed keys_to_check = SAMPLE_JOB_CREATE_RESPONSE.keys() for key in keys_to_check: diff --git a/tests/test_device.py b/tests/test_device.py index e50ef4c..847a768 100755 --- a/tests/test_device.py +++ b/tests/test_device.py @@ -17,6 +17,7 @@ import pennylane as qml import pytest import requests +from unittest.mock import PropertyMock, patch from conftest import shortnames from pennylane_ionq.api_client import JobExecutionError, ResourceManager, Job, Field @@ -51,15 +52,11 @@ def test_generate_samples_qpu_device(self, wires, histogram): unique_outcomes1 = np.unique(sample1, axis=0) unique_outcomes2 = np.unique(sample2, axis=0) - assert np.all( - unique_outcomes1 == unique_outcomes2 - ) # possible outcomes are the same + assert np.all(unique_outcomes1 == unique_outcomes2) # possible outcomes are the same sorted_outcomes1 = np.sort(sample1, axis=0) sorted_outcomes2 = np.sort(sample2, axis=0) - assert np.all( - sorted_outcomes1 == sorted_outcomes2 - ) # set of outcomes is the same + assert np.all(sorted_outcomes1 == sorted_outcomes2) # set of outcomes is the same class TestDeviceIntegration: @@ -99,9 +96,7 @@ def test_failedcircuit(self, monkeypatch): monkeypatch.setattr( requests, "post", lambda url, timeout, data, headers: (url, data, headers) ) - monkeypatch.setattr( - ResourceManager, "handle_response", lambda self, response: None - ) + monkeypatch.setattr(ResourceManager, "handle_response", lambda self, response: None) monkeypatch.setattr(Job, "is_complete", False) monkeypatch.setattr(Job, "is_failed", True) @@ -116,17 +111,13 @@ def test_shots(self, shots, monkeypatch, mocker, tol): monkeypatch.setattr( requests, "post", lambda url, timeout, data, headers: (url, data, headers) ) - monkeypatch.setattr( - ResourceManager, "handle_response", lambda self, response: None - ) + monkeypatch.setattr(ResourceManager, "handle_response", lambda self, response: None) monkeypatch.setattr(Job, "is_complete", True) - def fake_response(self, resource_id=None): + def fake_response(self, resource_id=None, params=None): """Return fake response data""" fake_json = {"histogram": {"0": 1}} - setattr( - self.resource, "data", type("data", tuple(), {"value": fake_json})() - ) + setattr(self.resource, "data", type("data", tuple(), {"value": fake_json})()) monkeypatch.setattr(ResourceManager, "get", fake_response) @@ -142,6 +133,45 @@ def circuit(): circuit() assert json.loads(spy.call_args[1]["data"])["shots"] == shots + @pytest.mark.parametrize("error_mitigation", [None, {"debias": True}, {"debias": False}]) + def test_error_mitigation(self, error_mitigation, monkeypatch, mocker): + """Test that shots are correctly specified when submitting a job to the API.""" + + monkeypatch.setattr( + requests, "post", lambda url, timeout, data, headers: (url, data, headers) + ) + monkeypatch.setattr(ResourceManager, "handle_response", lambda self, response: None) + monkeypatch.setattr(Job, "is_complete", True) + + def fake_response(self, resource_id=None, params=None): + """Return fake response data""" + fake_json = {"histogram": {"0": 1}} + setattr(self.resource, "data", type("data", tuple(), {"value": fake_json})()) + + monkeypatch.setattr(ResourceManager, "get", fake_response) + + dev = qml.device( + "ionq.qpu", + wires=1, + shots=5000, + api_key="test", + error_mitigation=error_mitigation, + ) + + @qml.qnode(dev) + def circuit(): + """Reference QNode""" + qml.PauliX(wires=0) + return qml.expval(qml.PauliZ(0)) + + spy = mocker.spy(requests, "post") + circuit() + if error_mitigation is not None: + assert json.loads(spy.call_args[1]["data"])["error_mitigation"] == error_mitigation + else: + with pytest.raises(KeyError, match="error_mitigation"): + json.loads(spy.call_args[1]["data"])["error_mitigation"] + @pytest.mark.parametrize("shots", [8192]) def test_one_qubit_circuit(self, shots, requires_api, tol): """Test that devices provide correct result for a simple circuit""" @@ -181,22 +211,28 @@ def test_prob_no_results(self, d): dev = qml.device(d, wires=1, shots=1) assert dev.prob is None - @pytest.mark.parametrize( - "backend", ["harmony", "aria-1", "aria-2", "forte-1", None] - ) + def test_probability(self): + """Test that device.probability works.""" + dev = qml.device("ionq.simulator", wires=2) + dev._samples = np.array([[1, 1], [1, 1], [0, 0], [0, 0]]) + assert np.array_equal(dev.probability(shot_range=(0, 2)), [0, 0, 0, 1]) + + uniform_prob = [0.25] * 4 + with patch("pennylane_ionq.device.SimulatorDevice.prob", new_callable=PropertyMock) as mock_prob: + mock_prob.return_value = uniform_prob + assert np.array_equal(dev.probability(), uniform_prob) + + + @pytest.mark.parametrize("backend", ["harmony", "aria-1", "aria-2", "forte-1", None]) def test_backend_initialization(self, backend): """Test that the device initializes with the correct backend.""" - if backend: - dev = qml.device( - "ionq.qpu", - wires=2, - shots=1000, - backend=backend, - ) - assert dev.backend == backend - else: - dev = qml.device("ionq.qpu", wires=2, shots=1000) - assert dev.backend == None + dev = qml.device( + "ionq.qpu", + wires=2, + shots=1000, + backend=backend, + ) + assert dev.backend == backend class TestJobAttribute: @@ -216,13 +252,13 @@ def mock_submit_job(*args): dev.apply(tape.operations) - assert dev.job["lang"] == "json" - assert dev.job["body"]["gateset"] == "qis" + assert dev.job["input"]["format"] == "ionq.circuit.v0" + assert dev.job["input"]["gateset"] == "qis" assert dev.job["target"] == "foo" - assert dev.job["body"]["qubits"] == 1 + assert dev.job["input"]["qubits"] == 1 - assert len(dev.job["body"]["circuit"]) == 1 - assert dev.job["body"]["circuit"][0] == {"gate": "x", "target": 0} + assert len(dev.job["input"]["circuit"]) == 1 + assert dev.job["input"]["circuit"][0] == {"gate": "x", "target": 0} def test_parameterized_op(self, mocker): """Tests job attribute several parameterized operations.""" @@ -239,17 +275,17 @@ def mock_submit_job(*args): dev.apply(tape.operations) - assert dev.job["lang"] == "json" - assert dev.job["body"]["gateset"] == "qis" - assert dev.job["body"]["qubits"] == 1 + assert dev.job["input"]["format"] == "ionq.circuit.v0" + assert dev.job["input"]["gateset"] == "qis" + assert dev.job["input"]["qubits"] == 1 - assert len(dev.job["body"]["circuit"]) == 2 - assert dev.job["body"]["circuit"][0] == { + assert len(dev.job["input"]["circuit"]) == 2 + assert dev.job["input"]["circuit"][0] == { "gate": "rx", "target": 0, "rotation": 1.2345, } - assert dev.job["body"]["circuit"][1] == { + assert dev.job["input"]["circuit"][1] == { "gate": "ry", "target": 0, "rotation": 2.3456, @@ -269,24 +305,24 @@ def mock_submit_job(*args): GPI2(0.2, wires=[1]) MS(0.2, 0.3, wires=[1, 2]) - assert dev.job["lang"] == "json" - assert dev.job["body"]["gateset"] == "native" - assert dev.job["body"]["qubits"] == 3 - dev.apply(tape.operations) - assert len(dev.job["body"]["circuit"]) == 3 - assert dev.job["body"]["circuit"][0] == { + assert dev.job["input"]["format"] == "ionq.circuit.v0" + assert dev.job["input"]["gateset"] == "native" + assert dev.job["input"]["qubits"] == 3 + + assert len(dev.job["input"]["circuit"]) == 3 + assert dev.job["input"]["circuit"][0] == { "gate": "gpi", "target": 0, "phase": 0.1, } - assert dev.job["body"]["circuit"][1] == { + assert dev.job["input"]["circuit"][1] == { "gate": "gpi2", "target": 1, "phase": 0.2, } - assert dev.job["body"]["circuit"][2] == { + assert dev.job["input"]["circuit"][2] == { "gate": "ms", "targets": [1, 2], "phases": [0.2, 0.3], From eac7e264bad427737be1da61c0f6f9f72dbef36d Mon Sep 17 00:00:00 2001 From: lillian542 <38584660+lillian542@users.noreply.github.com> Date: Thu, 4 Jan 2024 15:55:14 -0500 Subject: [PATCH 05/10] Update to retrieve results with v0.3 (#96) * update to retrieve results with v0.3 * update mocker for test * fix another mocker thing * more mocker changes * add to changelog * add comment regarding ordering of fields tuple --- CHANGELOG.md | 2 ++ pennylane_ionq/api_client.py | 21 ++++++++++++++------- pennylane_ionq/device.py | 2 +- tests/test_api_client.py | 4 ++-- tests/test_device.py | 4 ++-- 5 files changed, 21 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index adcc5cd..225d546 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,11 +14,13 @@ For more details, see the [IonQ Guide on sharpening and debiasing](https://ionq.com/resources/debiasing-and-sharpening), or refer to the publication [(#75)](https://github.com/PennyLaneAI/PennyLane-IonQ/pull/75) + [(#96)](https://github.com/PennyLaneAI/PennyLane-IonQ/pull/96) ### Improvements 🛠 * The IonQ API version accessed via the plugin is updated from 0.1 to 0.3 [(#75)](https://github.com/PennyLaneAI/PennyLane-IonQ/pull/75) + [(#96)](https://github.com/PennyLaneAI/PennyLane-IonQ/pull/96) * Use new `backend` field to specify `qpu`. [(#81)](https://github.com/PennyLaneAI/PennyLane-IonQ/pull/81) diff --git a/pennylane_ionq/api_client.py b/pennylane_ionq/api_client.py index 4f6b19d..363ca39 100644 --- a/pennylane_ionq/api_client.py +++ b/pennylane_ionq/api_client.py @@ -270,7 +270,9 @@ def get(self, resource_id=None, params=None): response = self.client.get(self.join_path(str(resource_id)), params=params) else: response = self.client.get(self.resource.PATH, params=params) - self.handle_response(response) + + # we need params later, unfortuantely + self.handle_response(response, params) def create(self, **params): """ @@ -290,7 +292,7 @@ def create(self, **params): self.handle_response(response) - def handle_response(self, response): + def handle_response(self, response, params=None): """ Store the status code on the manager object and handle the response based on the status code. @@ -303,7 +305,7 @@ def handle_response(self, response): self.http_response_status_code = response.status_code if response.status_code in (200, 201): - self.handle_success_response(response) + self.handle_success_response(response, params=params) else: self.handle_error_response(response) else: @@ -315,14 +317,14 @@ def handle_no_response(self): """ warnings.warn("Your request could not be completed") - def handle_success_response(self, response): + def handle_success_response(self, response, params=None): """ Handles a successful response by refreshing the instance fields. Args: response (requests.Response): a response object to be parsed """ - self.refresh_data(response.json()) + self.refresh_data(response.json(), params=params) def handle_error_response(self, response): """ @@ -339,7 +341,7 @@ def handle_error_response(self, response): except Exception as e: raise Exception(response.text) from e - def refresh_data(self, data): + def refresh_data(self, data, params=None): """ Refreshes the instance's attributes with the provided data and converts it to the correct type. @@ -350,6 +352,10 @@ def refresh_data(self, data): for field in self.resource.fields: field.set(data.get(field.name, None)) + if "results_url" in data.keys(): + result = self.client.get(self.join_path(data["results_url"]), params=params) + self.resource.fields[-1].set(result.json()) + if hasattr(self.resource, "refresh_data"): self.resource.refresh_data() @@ -449,10 +455,11 @@ def __init__(self, client=None, api_key=None): """ self.fields = ( Field("id", str), - Field("type", str), Field("status", str), Field("request", dateutil.parser.parse), Field("response", dateutil.parser.parse), + # it is important that data remain the final item in + # this tuple to ensure storing results in the correct entry Field("data"), ) diff --git a/pennylane_ionq/device.py b/pennylane_ionq/device.py index 026f699..5d19c08 100644 --- a/pennylane_ionq/device.py +++ b/pennylane_ionq/device.py @@ -231,7 +231,7 @@ def _submit_job(self): # state (as a base-10 integer string) to the probability # as a floating point value between 0 and 1. # e.g., {"0": 0.413, "9": 0.111, "17": 0.476} - self.histogram = job.data.value["histogram"] + self.histogram = job.data.value @property def prob(self): diff --git a/tests/test_api_client.py b/tests/test_api_client.py index e95e720..c465f01 100755 --- a/tests/test_api_client.py +++ b/tests/test_api_client.py @@ -188,7 +188,7 @@ def test_get(self, monkeypatch, resource_id, params): # TODO test that this is called with correct path mock_client.get.assert_called_once() - manager.handle_response.assert_called_once_with(mock_response) + manager.handle_response.assert_called_once_with(mock_response, params) def test_create_unsupported(self): """ @@ -259,7 +259,7 @@ def test_handle_response(self, monkeypatch): mock_response.status_code = 200 manager.handle_response(mock_response) - mock_handle_success_response.assert_called_once_with(mock_response) + mock_handle_success_response.assert_called_once_with(mock_response, params=None) def test_handle_refresh_data(self): """ diff --git a/tests/test_device.py b/tests/test_device.py index 847a768..6333fe6 100755 --- a/tests/test_device.py +++ b/tests/test_device.py @@ -116,7 +116,7 @@ def test_shots(self, shots, monkeypatch, mocker, tol): def fake_response(self, resource_id=None, params=None): """Return fake response data""" - fake_json = {"histogram": {"0": 1}} + fake_json = {"0": 1} setattr(self.resource, "data", type("data", tuple(), {"value": fake_json})()) monkeypatch.setattr(ResourceManager, "get", fake_response) @@ -145,7 +145,7 @@ def test_error_mitigation(self, error_mitigation, monkeypatch, mocker): def fake_response(self, resource_id=None, params=None): """Return fake response data""" - fake_json = {"histogram": {"0": 1}} + fake_json = {"0": 1} setattr(self.resource, "data", type("data", tuple(), {"value": fake_json})()) monkeypatch.setattr(ResourceManager, "get", fake_response) From 7de67afd27459bcf3a793982c5f7da1f1908fb10 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 5 Jan 2024 16:35:42 -0500 Subject: [PATCH 06/10] Bump future from 0.18.2 to 0.18.3 (#94) * Bump future from 0.18.2 to 0.18.3 Bumps [future](https://github.com/PythonCharmers/python-future) from 0.18.2 to 0.18.3. - [Release notes](https://github.com/PythonCharmers/python-future/releases) - [Changelog](https://github.com/PythonCharmers/python-future/blob/master/docs/changelog.rst) - [Commits](https://github.com/PythonCharmers/python-future/compare/v0.18.2...v0.18.3) --- updated-dependencies: - dependency-name: future dependency-type: direct:production ... Signed-off-by: dependabot[bot] * Bump future from 0.18.2 to 0.18.3 in /doc (#93) * Bump future from 0.18.2 to 0.18.3 in /doc Bumps [future](https://github.com/PythonCharmers/python-future) from 0.18.2 to 0.18.3. - [Release notes](https://github.com/PythonCharmers/python-future/releases) - [Changelog](https://github.com/PythonCharmers/python-future/blob/master/docs/changelog.rst) - [Commits](https://github.com/PythonCharmers/python-future/compare/v0.18.2...v0.18.3) --- updated-dependencies: - dependency-name: future dependency-type: direct:production ... Signed-off-by: dependabot[bot] * Bump pygments from 2.12.0 to 2.15.0 in /doc (#86) Bumps [pygments](https://github.com/pygments/pygments) from 2.12.0 to 2.15.0. - [Release notes](https://github.com/pygments/pygments/releases) - [Changelog](https://github.com/pygments/pygments/blob/master/CHANGES) - [Commits](https://github.com/pygments/pygments/compare/2.12.0...2.15.0) --- updated-dependencies: - dependency-name: pygments dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump scipy from 1.8.1 to 1.10.0 in /doc (#87) Bumps [scipy](https://github.com/scipy/scipy) from 1.8.1 to 1.10.0. - [Release notes](https://github.com/scipy/scipy/releases) - [Commits](https://github.com/scipy/scipy/compare/v1.8.1...v1.10.0) --- updated-dependencies: - dependency-name: scipy dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: lillian542 <38584660+lillian542@users.noreply.github.com> * Bump requests from 2.28.0 to 2.31.0 (#91) * Bump requests from 2.28.0 to 2.31.0 Bumps [requests](https://github.com/psf/requests) from 2.28.0 to 2.31.0. - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.28.0...v2.31.0) --- updated-dependencies: - dependency-name: requests dependency-type: direct:production ... Signed-off-by: dependabot[bot] * Bump requests from 2.28.0 to 2.31.0 in /doc (#85) Bumps [requests](https://github.com/psf/requests) from 2.28.0 to 2.31.0. - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.28.0...v2.31.0) --- updated-dependencies: - dependency-name: requests dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump urllib3 from 1.26.9 to 1.26.18 (#88) Bumps [urllib3](https://github.com/urllib3/urllib3) from 1.26.9 to 1.26.18. - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst) - [Commits](https://github.com/urllib3/urllib3/compare/1.26.9...1.26.18) --- updated-dependencies: - dependency-name: urllib3 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: lillian542 <38584660+lillian542@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: lillian542 <38584660+lillian542@users.noreply.github.com> --- doc/requirements.txt | 10 +++++----- requirements.txt | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/doc/requirements.txt b/doc/requirements.txt index 623de0d..c711409 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -7,7 +7,7 @@ cachetools==5.2.0 certifi==2023.7.22 charset-normalizer==2.0.12 docutils==0.18.1 -future==0.18.2 +future==0.18.3 idna==3.3 imagesize==1.3.0 Jinja2==3.1.2 @@ -18,13 +18,13 @@ numpy==1.22.4 packaging==21.3 PennyLane==0.24.0 PennyLane-Lightning==0.24.0 -Pygments==2.12.0 +Pygments==2.15.0 pyparsing==3.0.9 python-dateutil==2.8.2 pytz==2022.1 -requests==2.28.0 +requests==2.31.0 retworkx==0.11.0 -scipy==1.8.1 +scipy==1.10.0 semantic-version==2.6.0 six==1.16.0 snowballstemmer==2.2.0 @@ -37,6 +37,6 @@ sphinxcontrib-jsmath==1.0.1 sphinxcontrib-qthelp==1.0.3 sphinxcontrib-serializinghtml==1.1.5 toml==0.10.2 -urllib3==1.26.9 +urllib3==1.26.18 # do not pin pennylane-sphinx-theme diff --git a/requirements.txt b/requirements.txt index f43e8b9..10000fa 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,7 @@ autoray==0.3.2 cachetools==5.2.0 certifi==2023.7.22 charset-normalizer==2.0.12 -future==0.18.2 +future==0.18.3 idna==3.3 networkx==2.6.0 ninja==1.10.2.3 @@ -12,10 +12,10 @@ numpy==1.23.5 PennyLane==0.24.0 PennyLane-Lightning==0.24.0 python-dateutil==2.8.2 -requests==2.28.0 +requests==2.31.0 retworkx==0.12.1 scipy==1.10.1 semantic-version==2.6.0 six==1.16.0 toml==0.10.2 -urllib3==1.26.9 +urllib3==1.26.18 From 33540911255e452bf17c45ab78f389671125bdd8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 8 Jan 2024 16:15:59 -0500 Subject: [PATCH 07/10] Version Bump (#97) * pre release version bump * version number is 0.34, not 0.35 --------- Co-authored-by: lillian542 Co-authored-by: lillian542 <38584660+lillian542@users.noreply.github.com> --- CHANGELOG.md | 10 +--------- pennylane_ionq/_version.py | 2 +- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 225d546..3105f1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -# Release 0.33.0-dev +# Release 0.34.0 ### New features since last release @@ -25,14 +25,6 @@ * Use new `backend` field to specify `qpu`. [(#81)](https://github.com/PennyLaneAI/PennyLane-IonQ/pull/81) -### Breaking changes 💔 - -### Deprecations 👋 - -### Documentation 📝 - -### Bug fixes 🐛 - ### Contributors ✍️ This release contains contributions from (in alphabetical order): diff --git a/pennylane_ionq/_version.py b/pennylane_ionq/_version.py index 4e6a936..f3fcfbf 100644 --- a/pennylane_ionq/_version.py +++ b/pennylane_ionq/_version.py @@ -16,4 +16,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.33.0-dev" +__version__ = "0.34.0" From e200ad2c6f3411e33e9c43dc195110db10b8ba95 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 8 Jan 2024 20:37:04 -0500 Subject: [PATCH 08/10] Version Bump (#98) * post release version bump * trigger ci * add pytest-benchmark to test workflow --------- Co-authored-by: lillian542 Co-authored-by: lillian542 --- .github/workflows/tests.yml | 2 +- CHANGELOG.md | 19 +++++++++++++++++++ pennylane_ionq/_version.py | 2 +- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index dced3a5..419c962 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -26,7 +26,7 @@ jobs: run: | python -m pip install --upgrade pip pip install -r requirements.txt - pip install wheel pytest pytest-cov pytest-mock --upgrade + pip install wheel pytest pytest-cov pytest-mock pytest-benchmark --upgrade - name: Install Plugin run: | python setup.py bdist_wheel diff --git a/CHANGELOG.md b/CHANGELOG.md index 3105f1d..9ed88df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,22 @@ +# Release 0.35.0-dev + +### New features since last release + +### Improvements 🛠 + +### Breaking changes 💔 + +### Deprecations 👋 + +### Documentation 📝 + +### Bug fixes 🐛 + +### Contributors ✍️ + +This release contains contributions from (in alphabetical order): + +--- # Release 0.34.0 ### New features since last release diff --git a/pennylane_ionq/_version.py b/pennylane_ionq/_version.py index f3fcfbf..b733b3b 100644 --- a/pennylane_ionq/_version.py +++ b/pennylane_ionq/_version.py @@ -16,4 +16,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.34.0" +__version__ = "0.35.0-dev" From 0adf43dff13654a803a8415ffe79c260b7190a62 Mon Sep 17 00:00:00 2001 From: Astral Cai Date: Tue, 16 Apr 2024 15:57:50 -0400 Subject: [PATCH 09/10] Add Prod observable support (#100) * Add Prod observable support * Trigger CI * Trigger CI * Update tests.yml --- .github/workflows/tests.yml | 5 +++-- pennylane_ionq/device.py | 5 ++++- pennylane_ionq/ops.py | 6 ++++++ tests/test_api_client.py | 1 - tests/test_device.py | 5 +++-- 5 files changed, 16 insertions(+), 6 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 419c962..9a60849 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -61,7 +61,7 @@ jobs: run: | python -m pip install --upgrade pip pip install -r requirements.txt - pip install wheel pytest pytest-cov pytest-mock flaky --upgrade + pip install wheel pytest pytest-benchmark pytest-cov pytest-mock flaky --upgrade - name: Install Plugin run: | @@ -75,6 +75,7 @@ jobs: pl-device-test --device=ionq.simulator --tb=short --skip-ops --shots=10000 - name: Upload coverage to Codecov - uses: codecov/codecov-action@v1 + uses: codecov/codecov-action@v4 with: + token: ${{ secrets.codecov_token }} file: ./coverage.xml diff --git a/pennylane_ionq/device.py b/pennylane_ionq/device.py index 5d19c08..4f46216 100644 --- a/pennylane_ionq/device.py +++ b/pennylane_ionq/device.py @@ -85,6 +85,7 @@ class IonQDevice(QubitDevice): distribution has peaks. See `IonQ Debiasing and Sharpening `_ for details. """ + # pylint: disable=too-many-instance-attributes name = "IonQ PennyLane plugin" short_name = "ionq" @@ -100,7 +101,7 @@ class IonQDevice(QubitDevice): # Note: unlike QubitDevice, IonQ does not support QubitUnitary, # and therefore does not support the Hermitian observable. - observables = {"PauliX", "PauliY", "PauliZ", "Hadamard", "Identity"} + observables = {"PauliX", "PauliY", "PauliZ", "Hadamard", "Identity", "Prod"} def __init__( self, @@ -285,6 +286,7 @@ class SimulatorDevice(IonQDevice): api_key (str): The IonQ API key. If not provided, the environment variable ``IONQ_API_KEY`` is used. """ + name = "IonQ Simulator PennyLane plugin" short_name = "ionq.simulator" @@ -329,6 +331,7 @@ class QPUDevice(IonQDevice): your expected output distribution has peaks. See `IonQ Debiasing and Sharpening `_ for details. """ + name = "IonQ QPU PennyLane plugin" short_name = "ionq.qpu" diff --git a/pennylane_ionq/ops.py b/pennylane_ionq/ops.py index 8a11d63..d826d58 100644 --- a/pennylane_ionq/ops.py +++ b/pennylane_ionq/ops.py @@ -33,6 +33,7 @@ class GPI(Operation): # pylint: disable=too-few-public-methods phi (float): phase :math:`\phi` wires (Sequence[int]): the subsystems the operation acts on """ + num_params = 1 num_wires = 1 grad_method = None @@ -53,6 +54,7 @@ class GPI2(Operation): # pylint: disable=too-few-public-methods phi (float): phase :math:`\phi` wires (Sequence[int]): the subsystems the operation acts on """ + num_params = 1 num_wires = 1 grad_method = None @@ -76,6 +78,7 @@ class MS(Operation): # pylint: disable=too-few-public-methods phi1 (float): phase of the second qubit :math:`\phi` wires (Sequence[int]): the subsystems the operation acts on """ + num_params = 2 num_wires = 2 grad_method = None @@ -101,6 +104,7 @@ class XX(Operation): phi (float): rotation angle :math:`\phi` wires (Sequence[int]): the subsystems the operation acts on """ + num_params = 1 num_wires = 2 grad_method = "A" @@ -123,6 +127,7 @@ class YY(Operation): phi (float): rotation angle :math:`\phi` wires (Sequence[int]): the subsystems the operation acts on """ + num_params = 1 num_wires = 2 grad_method = "A" @@ -145,6 +150,7 @@ class ZZ(Operation): phi (float): rotation angle :math:`\phi` wires (Sequence[int]): the subsystems the operation acts on """ + num_params = 1 num_wires = 2 grad_method = "A" diff --git a/tests/test_api_client.py b/tests/test_api_client.py index c465f01..4ef7926 100755 --- a/tests/test_api_client.py +++ b/tests/test_api_client.py @@ -358,7 +358,6 @@ def __init__(self, client=None, api_key=None): res = WithID(api_key="test") res.reload() - def test_create_created(self, monkeypatch): """ Tests a successful Job creatioin with a mock POST response. Asserts that all fields on diff --git a/tests/test_device.py b/tests/test_device.py index 6333fe6..f50f782 100755 --- a/tests/test_device.py +++ b/tests/test_device.py @@ -218,11 +218,12 @@ def test_probability(self): assert np.array_equal(dev.probability(shot_range=(0, 2)), [0, 0, 0, 1]) uniform_prob = [0.25] * 4 - with patch("pennylane_ionq.device.SimulatorDevice.prob", new_callable=PropertyMock) as mock_prob: + with patch( + "pennylane_ionq.device.SimulatorDevice.prob", new_callable=PropertyMock + ) as mock_prob: mock_prob.return_value = uniform_prob assert np.array_equal(dev.probability(), uniform_prob) - @pytest.mark.parametrize("backend", ["harmony", "aria-1", "aria-2", "forte-1", None]) def test_backend_initialization(self, backend): """Test that the device initializes with the correct backend.""" From a7d2d30bd8d4aa4e481e7b393c248dfdd97ac92b Mon Sep 17 00:00:00 2001 From: Alex Preciado Date: Fri, 19 Apr 2024 13:44:08 -0600 Subject: [PATCH 10/10] Update to codecov-action v4 (#102) * Update to codecov-action v4 * Update tests.yml * Update .github/workflows/tests.yml Co-authored-by: Astral Cai --------- Co-authored-by: Astral Cai --- .github/workflows/tests.yml | 163 ++++++++++++++++++------------------ 1 file changed, 82 insertions(+), 81 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9a60849..a6e7a27 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,81 +1,82 @@ -name: Tests -on: - push: - branches: - - master - pull_request: - -jobs: - tests: - runs-on: ubuntu-latest - strategy: - matrix: - python-version: [3.9, '3.10', '3.11'] - - steps: - - name: Cancel Previous Runs - uses: styfle/cancel-workflow-action@0.4.1 - with: - access_token: ${{ github.token }} - - uses: actions/checkout@v1 - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install -r requirements.txt - pip install wheel pytest pytest-cov pytest-mock pytest-benchmark --upgrade - - name: Install Plugin - run: | - python setup.py bdist_wheel - pip install dist/PennyLane*.whl - - name: Run tests - run: python -m pytest tests --cov=pennylane_ionq --cov-report=term-missing --cov-report=xml -p no:warnings --tb=native - env: - IONQ_API_KEY: ${{ secrets.IONQ_API_KEY }} - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v1.0.12 - with: - file: ./coverage.xml - - integration-tests: - runs-on: ubuntu-latest - if: github.event.pull_request.head.repo.full_name == github.repository - - steps: - - name: Cancel Previous Runs - uses: styfle/cancel-workflow-action@0.4.1 - with: - access_token: ${{ github.token }} - - - uses: actions/checkout@v2 - - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: 3.9 - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install -r requirements.txt - pip install wheel pytest pytest-benchmark pytest-cov pytest-mock flaky --upgrade - - - name: Install Plugin - run: | - python setup.py bdist_wheel - pip install dist/PennyLane*.whl - - - name: Run tests - env: - IONQ_API_KEY: ${{ secrets.IONQ_API_KEY }} - run: | - pl-device-test --device=ionq.simulator --tb=short --skip-ops --shots=10000 - - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v4 - with: - token: ${{ secrets.codecov_token }} - file: ./coverage.xml +name: Tests +on: + push: + branches: + - master + pull_request: + +jobs: + tests: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: [3.9, '3.10', '3.11'] + + steps: + - name: Cancel Previous Runs + uses: styfle/cancel-workflow-action@0.4.1 + with: + access_token: ${{ github.token }} + - uses: actions/checkout@v1 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + pip install wheel pytest pytest-cov pytest-mock pytest-benchmark --upgrade + - name: Install Plugin + run: | + python setup.py bdist_wheel + pip install dist/PennyLane*.whl + - name: Run tests + run: python -m pytest tests --cov=pennylane_ionq --cov-report=term-missing --cov-report=xml -p no:warnings --tb=native + env: + IONQ_API_KEY: ${{ secrets.IONQ_API_KEY }} + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.codecov_token }} + file: ./coverage.xml + + integration-tests: + runs-on: ubuntu-latest + if: github.event.pull_request.head.repo.full_name == github.repository + + steps: + - name: Cancel Previous Runs + uses: styfle/cancel-workflow-action@0.4.1 + with: + access_token: ${{ github.token }} + + - uses: actions/checkout@v2 + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: 3.9 + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + pip install wheel pytest pytest-benchmark pytest-cov pytest-mock flaky --upgrade + + - name: Install Plugin + run: | + python setup.py bdist_wheel + pip install dist/PennyLane*.whl + + - name: Run tests + env: + IONQ_API_KEY: ${{ secrets.IONQ_API_KEY }} + run: | + pl-device-test --device=ionq.simulator --tb=short --skip-ops --shots=10000 + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.codecov_token }} + file: ./coverage.xml