Skip to content

Commit

Permalink
Open v0.3 API + debiasing (#75)
Browse files Browse the repository at this point in the history
* create job with debias

* get sharpened results

* add error_mitigation and sharpen to the end

Co-authored-by: Matthew Silverman <[email protected]>

* use userwarning

Co-authored-by: Matthew Silverman <[email protected]>

* fix undefined params and sharpen

Co-authored-by: Matthew Silverman <[email protected]>

* reorder args

Co-authored-by: Matthew Silverman <[email protected]>

* final arg reorder

Co-authored-by: Matthew Silverman <[email protected]>

* remove unnecessary kwargs pop

* use new fields for tests

* add to contributors

* format code + add params

* Update CHANGELOG.md

Co-authored-by: Matthew Silverman <[email protected]>

* 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 <[email protected]>
Co-authored-by: lillian542 <[email protected]>
Co-authored-by: lillian542 <[email protected]>
Co-authored-by: Matthew Silverman <[email protected]>
  • Loading branch information
5 people authored Jan 2, 2024
1 parent 82ace77 commit e592b06
Show file tree
Hide file tree
Showing 7 changed files with 244 additions and 103 deletions.
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <https://arxiv.org/pdf/2301.07233.pdf>
[(#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)

Expand All @@ -20,6 +36,7 @@
This release contains contributions from (in alphabetical order):

Spencer Churchill
Lillian Frederiksen

---
# Release 0.32.0
Expand Down
14 changes: 6 additions & 8 deletions doc/devices.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,17 @@ 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
<section id="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
Expand All @@ -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 <https://docs.ionq.com/#tag/jobs>`_:

.. code-block:: python
Expand Down
21 changes: 13 additions & 8 deletions pennylane_ionq/api_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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):
"""
Expand Down Expand Up @@ -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
Expand All @@ -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):
Expand Down
90 changes: 72 additions & 18 deletions pennylane_ionq/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 <https://docs.ionq.com/#tag/jobs/operation/createJob>`_ and
`IonQ Debiasing and Sharpening <https://ionq.com/resources/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
<https://ionq.com/resources/debiasing-and-sharpening>`_ for details.
"""
# pylint: disable=too-many-instance-attributes
name = "IonQ PennyLane plugin"
Expand All @@ -91,14 +102,26 @@ 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.")

super().__init__(wires=wires, shots=shots)
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()

Expand All @@ -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):
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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."""
Expand All @@ -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 <https://docs.ionq.com/#tag/jobs/operation/createJob>`_ and
`IonQ Debiasing and Sharpening <https://ionq.com/resources/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
<https://ionq.com/resources/debiasing-and-sharpening>`_ for details.
"""
name = "IonQ QPU PennyLane plugin"
short_name = "ionq.qpu"
Expand All @@ -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
Expand All @@ -301,6 +353,8 @@ def __init__(
gateset=gateset,
shots=shots,
api_key=api_key,
error_mitigation=error_mitigation,
sharpen=sharpen,
)

def generate_samples(self):
Expand Down
4 changes: 1 addition & 3 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]])


# ==========================================================
Expand Down
Loading

0 comments on commit e592b06

Please sign in to comment.