Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Enable qpu.backend for IonQ backends #81

Merged
merged 15 commits into from
Nov 2, 2023
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

### Improvements 🛠

* Use new `backend` field to specify `qpu`.
[(#81)](https://github.com/PennyLaneAI/PennyLane-IonQ/pull/81)

### Breaking changes 💔

### Deprecations 👋
Expand All @@ -16,6 +19,8 @@

This release contains contributions from (in alphabetical order):

Spencer Churchill

---
# Release 0.32.0

Expand Down
5 changes: 3 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,8 @@ You can instantiate the IonQ devices for PennyLane as follows:

import pennylane as qml
dev1 = qml.device('ionq.simulator', wires=2, shots=1000)
dev2 = qml.device('ionq.qpu', wires=2, shots=1000)
dev2 = qml.device('ionq.qpu.harmony', wires=2, shots=1000)
dev3 = qml.device('ionq.qpu', backend='aria-1', wires=2, shots=1000)
lillian542 marked this conversation as resolved.
Show resolved Hide resolved

These devices can then be used just like other devices for the definition and evaluation of
quantum circuits within PennyLane. For more details and ideas, see the
Expand All @@ -119,7 +120,7 @@ to the `PennyLane documentation <https://pennylane.readthedocs.io>`_.
Contributing
============

We welcome contributionssimply fork the PennyLane-IonQ repository, and then make a
We welcome contributions-simply fork the PennyLane-IonQ repository, and then make a
`pull request <https://help.github.com/articles/about-pull-requests/>`_ containing your contribution.
All contributers to PennyLane-IonQ will be listed as contributors on the releases.

Expand Down
9 changes: 8 additions & 1 deletion pennylane_ionq/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,12 @@
=======================
"""
from .ops import GPI, GPI2, MS, XX, YY, ZZ
from .device import SimulatorDevice, QPUDevice
from .device import (
SimulatorDevice,
QPUDevice,
HarmonyQPUDevice,
Aria1QPUDevice,
Aria2QPUDevice,
Forte1QPUDevice,
)
from ._version import __version__
150 changes: 137 additions & 13 deletions pennylane_ionq/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,7 @@
"""
This module contains the device class for constructing IonQ devices for PennyLane.
"""
import itertools
import functools
import warnings
import os, warnings

Check notice on line 17 in pennylane_ionq/device.py

View check run for this annotation

codefactor.io / CodeFactor

pennylane_ionq/device.py#L17

Unused import os (unused-import)
from time import sleep

import numpy as np
Expand Down Expand Up @@ -93,9 +91,13 @@
# 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
):
if shots is None:
raise ValueError("The ionq device does not support analytic expectation values.")
raise ValueError(
"The ionq device does not support analytic expectation values."
)

super().__init__(wires=wires, shots=shots)
self.target = target
Expand Down Expand Up @@ -134,10 +136,16 @@
rotations = kwargs.pop("rotations", [])

if len(operations) == 0 and len(rotations) == 0:
warnings.warn("Circuit is empty. Empty circuits return failures. Submitting anyway.")
warnings.warn(
"Circuit is empty. Empty circuits return failures. Submitting anyway."
)

for i, operation in enumerate(operations):
if i > 0 and operation.name in {"BasisState", "QubitStateVector", "StatePrep"}:
if i > 0 and operation.name in {
"BasisState",
"QubitStateVector",
"StatePrep",
}:
raise DeviceError(
f"The operation {operation.name} is only supported at the beginning of a circuit."
)
Expand Down Expand Up @@ -210,7 +218,8 @@
# Here, we rearrange the states to match the big-endian ordering
# expected by PennyLane.
basis_states = (
int(bin(int(k))[2:].rjust(self.num_wires, "0")[::-1], 2) for k in self.histogram
int(bin(int(k))[2:].rjust(self.num_wires, "0")[::-1], 2)
for k in self.histogram
)
idx = np.fromiter(basis_states, dtype=int)

Expand All @@ -230,7 +239,9 @@
if shot_range is None and bin_size is None:
return self.marginal_prob(self.prob, wires)

return self.estimate_probability(wires=wires, shot_range=shot_range, bin_size=bin_size)
return self.estimate_probability(
wires=wires, shot_range=shot_range, bin_size=bin_size
)


class SimulatorDevice(IonQDevice):
Expand All @@ -251,8 +262,12 @@
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, *, target="simulator", gateset="qis", shots=1024, api_key=None
):
super().__init__(
wires=wires, target=target, 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 @@ -269,6 +284,7 @@
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"``.
backend (str): Optional specifier for an IonQ backend. Can be ``"harmony"``, ``"aria-1"``, etc.
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.
Expand All @@ -279,8 +295,21 @@
name = "IonQ QPU PennyLane plugin"
short_name = "ionq.qpu"

def __init__(self, wires, *, target="qpu", 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,
*,
target="qpu",
backend=None,
gateset="qis",
shots=1024,
api_key=None,
):
if backend is not None:
target += "." + backend
super().__init__(
wires=wires, target=target, gateset=gateset, shots=shots, api_key=api_key
)

def generate_samples(self):
"""Generates samples from the qpu.
Expand All @@ -298,3 +327,98 @@
samples = np.repeat(np.arange(number_of_states), counts)
np.random.shuffle(samples)
return QubitDevice.states_to_binary(samples, self.num_wires)


# Specific Backends
lillian542 marked this conversation as resolved.
Show resolved Hide resolved


class HarmonyQPUDevice(QPUDevice):
"""
PennyLane device for the IonQ Harmony QPU backend.

Args:
wires (int or Iterable[Number, str]): Number of subsystems represented by the device, or iterable of unique labels for the subsystems.
gateset (str): Target gateset, either "qis" or "native".
shots (int): Number of circuit evaluations/random samples used to estimate expectation values and variances of observables.
api_key (str): The IonQ API key. If not provided, the environment
variable ``IONQ_API_KEY`` is used.
"""

def __init__(self, wires, *, gateset="qis", shots=1024, api_key=None):
super().__init__(
wires,
target="qpu",
backend="harmony",
gateset=gateset,
shots=shots,
api_key=api_key,
)


class Aria1QPUDevice(QPUDevice):
"""
PennyLane device for the IonQ Aria-1 QPU backend.

Args:
wires (int or Iterable[Number, str]): Number of subsystems represented by the device, or iterable of unique labels for the subsystems.
gateset (str): Target gateset, either "qis" or "native".
shots (int): Number of circuit evaluations/random samples used to estimate expectation values and variances of observables.
api_key (str): The IonQ API key. If not provided, the environment
variable ``IONQ_API_KEY`` is used.
"""

def __init__(self, wires, *, gateset="qis", shots=1024, api_key=None):
super().__init__(
wires,
target="qpu",
backend="aria-1",
gateset=gateset,
shots=shots,
api_key=api_key,
)


class Aria2QPUDevice(QPUDevice):
"""
PennyLane device for the IonQ Aria-2 QPU backend.

Args:
wires (int or Iterable[Number, str]): Number of subsystems represented by the device, or iterable of unique labels for the subsystems.
gateset (str): Target gateset, either "qis" or "native".
shots (int): Number of circuit evaluations/random samples used to estimate expectation values and variances of observables.
api_key (str): The IonQ API key. If not provided, the environment
variable ``IONQ_API_KEY`` is used.
"""

def __init__(self, wires, *, gateset="qis", shots=1024, api_key=None):
super().__init__(
wires,
target="qpu",
backend="aria-2",
gateset=gateset,
shots=shots,
api_key=api_key,
)


class Forte1QPUDevice(QPUDevice):
"""
PennyLane device for the IonQ Forte-1 QPU backend.

Args:
wires (int or Iterable[Number, str]): Number of subsystems represented by the device, or iterable of unique labels for the subsystems.
gateset (str): Target gateset, either "qis" or "native".
shots (int): Number of circuit evaluations/random samples used to estimate expectation values and variances of observables.
api_key (str): The IonQ API key. If not provided, the environment
variable ``IONQ_API_KEY`` is used.
"""

def __init__(self, wires, *, gateset="qis", shots=1024, api_key=None):
super().__init__(
wires,
target="qpu",
backend="forte-1",
gateset=gateset,
shots=shots,
api_key=api_key,
)
10 changes: 6 additions & 4 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,15 @@
"maintainer_email": "[email protected]",
"url": "http://xanadu.ai",
"license": "Apache License 2.0",
"packages": [
"pennylane_ionq"
],
"packages": ["pennylane_ionq"],
"entry_points": {
"pennylane.plugins": [
"ionq.simulator = pennylane_ionq:SimulatorDevice",
"ionq.qpu = pennylane_ionq:QPUDevice"
"ionq.qpu = pennylane_ionq:QPUDevice",
"ionq.qpu.harmony = pennylane_ionq:HarmonyQPUDevice",
"ionq.qpu.aria-1 = pennylane_ionq:Aria1QPUDevice",
"ionq.qpu.aria-2 = pennylane_ionq:Aria2QPUDevice",
"ionq.qpu.forte-1 = pennylane_ionq:Forte1QPUDevice",
]
},
"description": "PennyLane plugin for IonQ",
Expand Down
19 changes: 16 additions & 3 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,17 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
splch marked this conversation as resolved.
Show resolved Hide resolved
import numpy as np
import pytest

from pennylane_ionq import SimulatorDevice, QPUDevice
from pennylane_ionq import (
SimulatorDevice,
QPUDevice,
HarmonyQPUDevice,
Aria1QPUDevice,
Aria2QPUDevice,
Forte1QPUDevice,
)


np.random.seed(42)
Expand Down Expand Up @@ -51,7 +57,14 @@
# List of all devices that do *not* support analytic expectation
# value computation. This generally includes hardware devices
# and hardware simulators.
hw_devices = [SimulatorDevice]
hw_devices = [
SimulatorDevice,
QPUDevice,
HarmonyQPUDevice,
Aria1QPUDevice,
Aria2QPUDevice,
Forte1QPUDevice,
]

# List of all device shortnames
shortnames = [d.short_name for d in analytic_devices + hw_devices]
Expand Down
6 changes: 1 addition & 5 deletions tests/test_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,6 @@ def mock_submit_job(*args):
dev.apply([])

def test_failedcircuit(self, monkeypatch):

monkeypatch.setattr(
requests, "post", lambda url, timeout, data, headers: (url, data, headers)
)
Expand Down Expand Up @@ -246,7 +245,7 @@ def mock_submit_job(*args):
pass
lillian542 marked this conversation as resolved.
Show resolved Hide resolved

mocker.patch("pennylane_ionq.device.IonQDevice._submit_job", mock_submit_job)
dev = IonQDevice(wires=(0,1,2), gateset="native")
dev = IonQDevice(wires=(0, 1, 2), gateset="native")

with qml.tape.QuantumTape() as tape:
GPI(0.1, wires=[0])
Expand Down Expand Up @@ -275,6 +274,3 @@ def mock_submit_job(*args):
"targets": [1, 2],
"phases": [0.2, 0.3],
}



Loading