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

Open v0.3 API + debiasing #75

Merged
merged 34 commits into from
Jan 2, 2024
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
fb07c8a
create job with debias
splch Jul 24, 2023
0d01740
get sharpened results
splch Jul 24, 2023
ac9d407
add error_mitigation and sharpen to the end
splch Jul 24, 2023
d54f871
use userwarning
splch Jul 24, 2023
543c885
fix undefined params and sharpen
splch Jul 24, 2023
2386290
reorder args
splch Jul 24, 2023
bdfa904
final arg reorder
splch Jul 24, 2023
2fc6d9b
Merge branch 'master' into v0.3-and-symmetrization
timmysilv Jul 24, 2023
f5503b2
remove unnecessary kwargs pop
splch Jul 24, 2023
a0a77a7
use new fields for tests
splch Aug 3, 2023
e8ad837
add to contributors
splch Aug 7, 2023
1efeb0d
format code + add params
splch Oct 19, 2023
8daacfe
Merge branch 'master' into v0.3-and-symmetrization
splch Oct 19, 2023
ef57f5f
Merge branch 'PennyLaneAI:master' into v0.3-and-symmetrization
splch Oct 30, 2023
be25d2e
Merge branch 'master' into v0.3-and-symmetrization
splch Nov 4, 2023
d4b5d7c
Merge branch 'master' into v0.3-and-symmetrization
lillian542 Dec 15, 2023
075f551
Merge branch 'master' into v0.3-and-symmetrization
lillian542 Dec 18, 2023
10116db
Update CHANGELOG.md
lillian542 Dec 18, 2023
f28f2a9
increase test coverage
lillian542 Dec 18, 2023
ba6dd7e
update changelog
lillian542 Dec 18, 2023
01f6042
add error_mitigation testing
lillian542 Dec 18, 2023
c9750f1
expand on docstrings
lillian542 Dec 18, 2023
6c288f7
black formatting
lillian542 Dec 18, 2023
b3c6f61
update overview doc to refer to docstrings for details
lillian542 Dec 18, 2023
100d6bf
more black formatting
lillian542 Dec 18, 2023
c2a9793
update hyperlink
lillian542 Dec 18, 2023
0bfef67
troubleshoot docstring formatting problem
lillian542 Dec 18, 2023
bed8239
troubleshoot docstring formatting problem 2
lillian542 Dec 18, 2023
27c332f
update changelog and docstrings
lillian542 Dec 18, 2023
b37d18e
black formatting for tests
lillian542 Dec 18, 2023
33ab2da
correct wording
lillian542 Dec 18, 2023
a4712a6
default backend is harmony
timmysilv Jan 2, 2024
7bd4a44
try to please codecov
timmysilv Jan 2, 2024
36b8c96
revert and simplify default target
timmysilv Jan 2, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

This release contains contributions from (in alphabetical order):

Spencer Churchill

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@splch, it might be nice to highlight the error mitigation support in the changelog

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@trbromley, combined the two kwargs into a little blurb and moved to "new features", added a link to an IonQ guide for more info

---
# Release 0.28.0

Expand Down
33 changes: 22 additions & 11 deletions pennylane_ionq/api_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,12 +112,16 @@
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")
)
timmysilv marked this conversation as resolved.
Show resolved Hide resolved
self.DEBUG = False

if "IONQ_DEBUG" in os.environ:
Expand Down Expand Up @@ -196,17 +200,18 @@

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 All @@ -220,7 +225,9 @@
Returns:
requests.Response: A response object, or None if no response could be fetched
"""
return self.request(requests.post, url=self.join_path(path), data=json.dumps(payload))
return self.request(
requests.post, url=self.join_path(path), data=json.dumps(payload)
)


class ResourceManager:
Expand Down Expand Up @@ -249,7 +256,7 @@
"""
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 @@ -259,12 +266,14 @@
resource_id (int): the ID of an object to be retrieved
"""
if "GET" not in self.resource.SUPPORTED_METHODS:
raise MethodNotSupportedException("GET method on this resource is not supported")
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)
lillian542 marked this conversation as resolved.
Show resolved Hide resolved
self.handle_response(response)

def create(self, **params):
Expand All @@ -276,7 +285,9 @@
**params: arbitrary parameters to be passed on to the POST request
"""
if "POST" not in self.resource.SUPPORTED_METHODS:
raise MethodNotSupportedException("POST method on this resource is not supported")
raise MethodNotSupportedException(
"POST method on this resource is not supported"
)

if self.resource.id:
raise ObjectAlreadyCreatedException("ID must be None when calling create")
Expand Down Expand Up @@ -331,7 +342,7 @@
self.errors.append(error)
try:
response.raise_for_status()
except Exception as e:

Check notice on line 345 in pennylane_ionq/api_client.py

View check run for this annotation

codefactor.io / CodeFactor

pennylane_ionq/api_client.py#L345

Raising too general exception: Exception (broad-exception-raised)
raise Exception(response.text) from e

def refresh_data(self, data):
Expand Down
76 changes: 64 additions & 12 deletions pennylane_ionq/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,14 +93,28 @@ 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=None,
lillian542 marked this conversation as resolved.
Show resolved Hide resolved
):
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
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 @@ -109,16 +123,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
lillian542 marked this conversation as resolved.
Show resolved Hide resolved
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 All @@ -134,7 +157,9 @@ def apply(self, operations, **kwargs):
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"}:
Expand Down Expand Up @@ -188,7 +213,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 All @@ -210,7 +237,8 @@ def prob(self):
# 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 +258,9 @@ def probability(self, wires=None, shot_range=None, bin_size=None):
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 +281,12 @@ class SimulatorDevice(IonQDevice):
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 @@ -279,8 +313,26 @@ class QPUDevice(IonQDevice):
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",
gateset="qis",
shots=1024,
api_key=None,
error_mitigation=None,
sharpen=None,
):
super().__init__(
wires=wires,
target=target,
gateset=gateset,
shots=shots,
api_key=api_key,
error_mitigation=error_mitigation,
sharpen=sharpen,
)

def generate_samples(self):
"""Generates samples from the qpu.
Expand Down
44 changes: 20 additions & 24 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 @@ -200,13 +199,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."""
Expand All @@ -223,17 +222,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,
Expand All @@ -246,35 +245,32 @@ def mock_submit_job(*args):
pass

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])
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],
}



Loading