Skip to content

Commit

Permalink
Tracker functionality (#533)
Browse files Browse the repository at this point in the history
* is this all?

* doc strings

* tests for tracker

* trackers

* trackers

* fixed tests

* removed legacy ci

* Delete .github/workflows/tests.yml

* Deleted use_primitives from tests

* remove use_primitive kwarg and things that depend on it

* fix tests and split_exec

* fixed test

* pylint

* change to v2 prims, del Options

* del Options

* temp changes to options

* naming and 0.46 test

* rename

* update qiskit device

* dep warnings

* pylint

* changes to tests

* deleted options for now

* small changes

* access sampler results

* Sampler tests and functionality

* estimator multi measurement works

* estimator now gives variances

* comments

* removed backend.run() and _execute_runtime

* remove additional stuff

* linter

* docstring changes

* skip additional test

* [skip ci] format is correct, checks probs as well

* [skip ci] docstring

* docstrings

* We delete the Options Handling class because there are no more Options() to handle. Additionally, process_kwargs is left as a stub as a temporary measure while we figure out what to do with kwargs

* changed warnings due to difference in UI for setting shots between Qiskit and Pennylane. Tracking shots has also been updated due to estimatorV2 syntax change

* un did confusing change that didnt do anything

* rerun ci

* Syntax changes due to version change

* Delete for codecov

* Update tests/test_base_device.py

Co-authored-by: lillian542 <38584660+lillian542@users.noreply.github.com>

* Update pennylane_qiskit/qiskit_device2.py

Co-authored-by: lillian542 <38584660+lillian542@users.noreply.github.com>

* Update tests/test_base_device.py

Co-authored-by: lillian542 <38584660+lillian542@users.noreply.github.com>

* docstrings

* Update tests/test_base_device.py

Co-authored-by: lillian542 <38584660+lillian542@users.noreply.github.com>

* Update pennylane_qiskit/qiskit_device2.py

Co-authored-by: lillian542 <38584660+lillian542@users.noreply.github.com>

* Update tests/test_base_device.py

Co-authored-by: lillian542 <38584660+lillian542@users.noreply.github.com>

* Update tests/test_base_device.py

Co-authored-by: Utkarsh <utkarshazad98@gmail.com>

* Update pennylane_qiskit/qiskit_device2.py

Co-authored-by: Utkarsh <utkarshazad98@gmail.com>

* finishing touches

* comments

* [skip ci] formatting

* [skip ci] refactor

* due to the fact that shots are not tracked in the estimator's metadata anymore, variances are calculated a different way using the precision instead

* black

* lint

* docstring changes

* backend options?

* deleting unused tests

* line change

* [skip ci] changed test to be more readable

* New tests for options functionality and edge case

* pylint

* We make sure that transpilation options are not passed to the primitive and that no errors are raised as a result

* Due to changing the signature of get_transpile_args(), we need to fix one of the tests

* warning message for default_shots was unclear. changed to be more clear

* add more comments

* yay

* docstring

* Testing to ensure that options and kwargs combine properly

* edit tests for pylint

* woops

* Update pennylane_qiskit/qiskit_device2.py

Co-authored-by: Utkarsh <utkarshazad98@gmail.com>

* Update pennylane_qiskit/qiskit_device2.py

Co-authored-by: Utkarsh <utkarshazad98@gmail.com>

* edit test regex matching due to changes earlier

* print out some stuff for tests

* refactoring of estimator and sampler

* generate samples tested

* process_estimator_job tests

* comment for clarity

* pylint

* pylint

* [skip ci] temp

* [skip ci] There is a bug due to the post processing of results that is causing some of the assertion statements to fail. We can ignore these assertions for now and address how to rework reorder_fn to avoid this bug

* [skip ci]

* [skip ci] minor formatting

* Fix unintended additonal dimensionality and added test for res != 1 testcase

* fix to transpiles

* comments to explain some stuff

* merge conflicts

* pylint

* formatting

* tracker comments

* black

* comments about the tracker

* bet

* fix to imports

* black

* temp

* baller implementation

* increase shot number to reduce error

* edit function

* black

* better tests

* refactor

* black

* delete print statement

* refactor

* Delete unneeded import

* fix assertion

* Update tests/test_base_device.py

Co-authored-by: lillian542 <38584660+lillian542@users.noreply.github.com>

* Update tests/test_base_device.py

Co-authored-by: lillian542 <38584660+lillian542@users.noreply.github.com>

* new tests

* removed simulations keyword

* some xfails pending discussion

---------

Co-authored-by: lillian542 <38584660+lillian542@users.noreply.github.com>
Co-authored-by: Utkarsh <utkarshazad98@gmail.com>
  • Loading branch information
3 people authored Jul 15, 2024
1 parent 23533e0 commit e9e30de
Show file tree
Hide file tree
Showing 3 changed files with 219 additions and 53 deletions.
31 changes: 30 additions & 1 deletion pennylane_qiskit/qiskit_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import inspect
from typing import Union, Callable, Tuple, Sequence
from contextlib import contextmanager
from functools import wraps

import numpy as np
import pennylane as qml
Expand All @@ -43,8 +44,9 @@
validate_measurements,
validate_device_wires,
)
from pennylane.measurements import ExpectationMP, VarianceMP

from pennylane.measurements import ExpectationMP, VarianceMP
from pennylane.devices.modifiers.simulator_tracking import simulator_tracking
from ._version import __version__
from .converter import QISKIT_OPERATION_MAP, circuit_to_qiskit, mp_to_pauli

Expand All @@ -53,6 +55,32 @@
Result_or_ResultBatch = Union[Result, ResultBatch]


def custom_simulator_tracking(cls):
"""Decorator that adds custom tracking to the device class."""

cls = simulator_tracking(cls)
tracked_execute = cls.execute

@wraps(tracked_execute)
def execute(self, circuits, execution_config=DefaultExecutionConfig):
results = tracked_execute(self, circuits, execution_config)
if self.tracker.active:
res = []
del self.tracker.totals["simulations"]
del self.tracker.history["simulations"]
del self.tracker.latest["simulations"]
for r in self.tracker.history["results"]:
while isinstance(r, (list, tuple)) and len(r) == 1:
r = r[0]
res.append(r)
self.tracker.history["results"] = res
return results

cls.execute = execute

return cls


# pylint: disable=protected-access
@contextmanager
def qiskit_session(device, **kwargs):
Expand Down Expand Up @@ -239,6 +267,7 @@ def reorder_fn(res):
return tapes, reorder_fn


@custom_simulator_tracking
class QiskitDevice(Device):
r"""Hardware/simulator Qiskit device for PennyLane.
Expand Down
4 changes: 2 additions & 2 deletions pennylane_qiskit/remote.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,5 +142,5 @@ def circuit(x):

short_name = "qiskit.remote"

def __init__(self, wires, backend, provider=None, shots=1024, **kwargs):
super().__init__(wires, provider=provider, backend=backend, shots=shots, **kwargs)
def __init__(self, wires, backend, shots=1024, **kwargs):
super().__init__(wires, backend=backend, shots=shots, **kwargs)
237 changes: 187 additions & 50 deletions tests/test_base_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from unittest.mock import patch, Mock
from flaky import flaky
import numpy as np
from pennylane import numpy as pnp
from pydantic_core import ValidationError
import pytest

Expand Down Expand Up @@ -476,8 +477,11 @@ def test_observable_stopping_condition(self, obs, expected):
3,
),
(
[qml.var(qml.X(0) + qml.Y(0) + qml.Z(0))], # Var should not split
1,
pytest.param(
[qml.var(qml.X(0) + qml.Y(0) + qml.Z(0))],
1,
marks=pytest.mark.xfail(reason="Split non commuting discussion pending"),
)
),
(
[
Expand All @@ -497,22 +501,28 @@ def test_observable_stopping_condition(self, obs, expected):
2,
),
(
[
qml.counts(qml.X(0)),
qml.counts(qml.Y(1)),
qml.counts(qml.Z(0) @ qml.Z(1)),
qml.counts(qml.X(0) @ qml.Z(1) + 0.5 * qml.Y(1) + qml.Z(0)),
],
3,
pytest.param(
[
qml.counts(qml.X(0)),
qml.counts(qml.Y(1)),
qml.counts(qml.Z(0) @ qml.Z(1)),
qml.counts(qml.X(0) @ qml.Z(1) + 0.5 * qml.Y(1) + qml.Z(0)),
],
3,
marks=pytest.mark.xfail(reason="Split non commuting discussion pending"),
)
),
(
[
qml.sample(qml.X(0)),
qml.sample(qml.Y(1)),
qml.sample(qml.Z(0) @ qml.Z(1)),
qml.sample(qml.X(0) @ qml.Z(1) + 0.5 * qml.Y(1) + qml.Z(0)),
],
3,
pytest.param(
[
qml.sample(qml.X(0)),
qml.sample(qml.Y(1)),
qml.sample(qml.Z(0) @ qml.Z(1)),
qml.sample(qml.X(0) @ qml.Z(1) + 0.5 * qml.Y(1) + qml.Z(0)),
],
3,
marks=pytest.mark.xfail(reason="Split non commuting discussion pending"),
)
),
],
)
Expand Down Expand Up @@ -729,6 +739,109 @@ def test_num_wires_property(self):
assert dev.num_wires == len(wires)


class TestTrackerFunctionality:
def test_tracker_batched(self):
"""Test that the tracker works for batched circuits"""
dev = qml.device("default.qubit", wires=1, shots=10000)
qiskit_dev = QiskitDevice(wires=1, backend=AerSimulator(), shots=10000)

x = pnp.array(0.1, requires_grad=True)

@qml.qnode(dev, diff_method="parameter-shift")
def circuit(x):
qml.RX(x, wires=0)
return qml.expval(qml.Z(0))

@qml.qnode(qiskit_dev, diff_method="parameter-shift")
def qiskit_circuit(x):
qml.RX(x, wires=0)
return qml.expval(qml.Z(0))

with qml.Tracker(dev) as tracker:
qml.grad(circuit)(x)

with qml.Tracker(qiskit_dev) as qiskit_tracker:
qml.grad(qiskit_circuit)(x)

assert qiskit_tracker.history["batches"] == tracker.history["batches"]
assert tracker.history["shots"] == qiskit_tracker.history["shots"]
assert np.allclose(qiskit_tracker.history["results"], tracker.history["results"], atol=0.1)
assert np.shape(qiskit_tracker.history["results"]) == np.shape(tracker.history["results"])
assert qiskit_tracker.history["resources"][0] == tracker.history["resources"][0]
assert "simulations" not in qiskit_dev.tracker.history
assert "simulations" not in qiskit_dev.tracker.latest
assert "simulations" not in qiskit_dev.tracker.totals

def test_tracker_single_tape(self):
"""Test that the tracker works for a single tape"""
dev = qml.device("default.qubit", wires=1, shots=10000)
qiskit_dev = QiskitDevice(wires=1, backend=AerSimulator(), shots=10000)

tape = qml.tape.QuantumTape([qml.S(0)], [qml.expval(qml.X(0))])
with qiskit_dev.tracker:
qiskit_out = qiskit_dev.execute(tape)

with dev.tracker:
pl_out = dev.execute(tape)

assert (
qiskit_dev.tracker.history["resources"][0].shots
== dev.tracker.history["resources"][0].shots
)
assert np.allclose(pl_out, qiskit_out, atol=0.1)
assert np.allclose(
qiskit_dev.tracker.history["results"], dev.tracker.history["results"], atol=0.1
)

assert np.shape(qiskit_dev.tracker.history["results"]) == np.shape(
dev.tracker.history["results"]
)

assert "simulations" not in qiskit_dev.tracker.history
assert "simulations" not in qiskit_dev.tracker.latest
assert "simulations" not in qiskit_dev.tracker.totals

def test_tracker_split_by_measurement_type(self):
"""Test that the tracker works for as intended for circuits split by measurement type"""
qiskit_dev = QiskitDevice(wires=5, backend=AerSimulator(), shots=10000)

x = 0.1

@qml.qnode(qiskit_dev)
def qiskit_circuit(x):
qml.RX(x, wires=0)
return qml.expval(qml.Z(0)), qml.counts(qml.X(1))

with qml.Tracker(qiskit_dev) as qiskit_tracker:
qiskit_circuit(x)

assert qiskit_tracker.totals["executions"] == 2
assert qiskit_tracker.totals["shots"] == 20000
assert "simulations" not in qiskit_dev.tracker.history
assert "simulations" not in qiskit_dev.tracker.latest
assert "simulations" not in qiskit_dev.tracker.totals

def test_tracker_split_by_non_commute(self):
"""Test that the tracker works for as intended for circuits split by non commute"""
qiskit_dev = QiskitDevice(wires=5, backend=AerSimulator(), shots=10000)

x = 0.1

@qml.qnode(qiskit_dev)
def qiskit_circuit(x):
qml.RX(x, wires=0)
return qml.expval(qml.Z(0)), qml.expval(qml.X(0))

with qml.Tracker(qiskit_dev) as qiskit_tracker:
qiskit_circuit(x)

assert qiskit_tracker.totals["executions"] == 2
assert qiskit_tracker.totals["shots"] == 20000
assert "simulations" not in qiskit_dev.tracker.history
assert "simulations" not in qiskit_dev.tracker.latest
assert "simulations" not in qiskit_dev.tracker.totals


class TestMockedExecution:
def test_get_transpile_args(self):
"""Test that get_transpile_args works as expected by filtering out
Expand Down Expand Up @@ -1351,11 +1464,17 @@ def circuit():
@pytest.mark.parametrize(
"observable",
[
lambda: [qml.counts(qml.X(0) + qml.Y(0)), qml.counts(qml.X(0))],
lambda: [
qml.counts(qml.X(0) @ qml.Z(1) + 0.5 * qml.Y(1) + qml.Z(0)),
qml.counts(0.5 * qml.Y(1)),
],
pytest.param(
lambda: [qml.counts(qml.X(0) + qml.Y(0)), qml.counts(qml.X(0))],
marks=pytest.mark.xfail(reason="Split non commuting discussion pending"),
),
pytest.param(
lambda: [
qml.counts(qml.X(0) @ qml.Z(1) + 0.5 * qml.Y(1) + qml.Z(0)),
qml.counts(0.5 * qml.Y(1)),
],
marks=pytest.mark.xfail(reason="Split non commuting discussion pending"),
),
],
)
@flaky(max_runs=10, min_passes=7)
Expand Down Expand Up @@ -1388,36 +1507,54 @@ def circuit():
@pytest.mark.parametrize(
"observable",
[
lambda: [qml.sample(qml.X(0) + qml.Y(0)), qml.sample(qml.X(0))],
lambda: [qml.sample(qml.X(0) @ qml.Y(1)), qml.sample(qml.X(0))],
lambda: [
qml.sample(qml.X(0) @ qml.Z(1) + 0.5 * qml.Y(1) + qml.Z(0)),
qml.sample(0.5 * qml.Y(1)),
],
lambda: [
qml.sample(qml.X(0)),
qml.sample(qml.Y(1)),
qml.sample(0.5 * qml.Y(1)),
qml.sample(qml.Z(0) @ qml.Z(1)),
qml.sample(qml.X(0) @ qml.Z(1) + 0.5 * qml.Y(1) + qml.Z(0)),
qml.sample(
qml.ops.LinearCombination(
[0.35, 0.46], [qml.X(0) @ qml.Z(1), qml.Z(0) @ qml.X(2)]
)
),
],
lambda: [
qml.sample(
qml.ops.LinearCombination(
[1.0, 2.0, 3.0], [qml.X(0), qml.X(1), qml.Z(0)], grouping_type="qwc"
pytest.param(
lambda: [qml.sample(qml.X(0) + qml.Y(0)), qml.sample(qml.X(0))],
marks=pytest.mark.xfail(reason="Split non commuting discussion pending"),
),
pytest.param(
lambda: [qml.sample(qml.X(0) @ qml.Y(1)), qml.sample(qml.X(0))],
marks=pytest.mark.xfail(reason="Split non commuting discussion pending"),
),
pytest.param(
lambda: [
qml.sample(qml.X(0) @ qml.Z(1) + 0.5 * qml.Y(1) + qml.Z(0)),
qml.sample(0.5 * qml.Y(1)),
],
marks=pytest.mark.xfail(reason="Split non commuting discussion pending"),
),
pytest.param(
lambda: [
qml.sample(qml.X(0)),
qml.sample(qml.Y(1)),
qml.sample(0.5 * qml.Y(1)),
qml.sample(qml.Z(0) @ qml.Z(1)),
qml.sample(qml.X(0) @ qml.Z(1) + 0.5 * qml.Y(1) + qml.Z(0)),
qml.sample(
qml.ops.LinearCombination(
[0.35, 0.46], [qml.X(0) @ qml.Z(1), qml.Z(0) @ qml.X(2)]
)
),
],
marks=pytest.mark.xfail(reason="Split non commuting discussion pending"),
),
pytest.param(
lambda: [
qml.sample(
qml.ops.LinearCombination(
[1.0, 2.0, 3.0], [qml.X(0), qml.X(1), qml.Z(0)], grouping_type="qwc"
)
),
],
marks=pytest.mark.xfail(reason="Split non commuting discussion pending"),
),
pytest.param(
lambda: [
qml.sample(
qml.Hamiltonian([0.35, 0.46], [qml.X(0) @ qml.Z(1), qml.Z(0) @ qml.Y(2)])
)
),
],
lambda: [
qml.sample(
qml.Hamiltonian([0.35, 0.46], [qml.X(0) @ qml.Z(1), qml.Z(0) @ qml.Y(2)])
)
],
],
marks=pytest.mark.xfail(reason="Split non commuting discussion pending"),
),
],
)
@flaky(max_runs=10, min_passes=7)
Expand Down

0 comments on commit e9e30de

Please sign in to comment.