Skip to content

Commit

Permalink
Add job_time (#160)
Browse files Browse the repository at this point in the history
* commit

* black

* test

* checking coverage

* reset test

* qasm_simulator

* test

* test

* change

* update

* record

* Romain changes

* Update ibmq.py

* final change

* Update test_ibmq.py

* Update CHANGELOG.md

Co-authored-by: Romain <[email protected]>
  • Loading branch information
KetpuntoG and rmoyard committed Jan 31, 2022
1 parent 819d966 commit 9f89e04
Show file tree
Hide file tree
Showing 8 changed files with 91 additions and 33 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
* Added support for the `qml.SX` operation to the Qiskit devices.
[(#158)](https://github.com/PennyLaneAI/pennylane-qiskit/pull/158)

* Added support for returning job execution times. [(#160)](https://github.com/PennyLaneAI/pennylane-qiskit/pull/160)

### Documentation

### Bug fixes
Expand All @@ -17,7 +19,7 @@

This release contains contributions from (in alphabetical order):

Antal Száva
Guillermo Alonso-Linaje, Antal Száva

---

Expand Down
21 changes: 21 additions & 0 deletions pennylane_qiskit/ibmq.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,24 @@ def __init__(self, wires, provider=None, backend="ibmq_qasm_simulator", shots=10
p = provider or IBMQ.get_provider(hub=hub, group=group, project=project)

super().__init__(wires=wires, provider=p, backend=backend, shots=shots, **kwargs)

def batch_execute(self, circuits):
res = super().batch_execute(circuits)
if self.tracker.active:
self._track_run()
return res

def _track_run(self):
"""Provide runtime information."""

time_per_step = self._current_job.time_per_step()
job_time = {
"creating": (time_per_step["CREATED"] - time_per_step["CREATING"]).total_seconds(),
"validating": (
time_per_step["VALIDATED"] - time_per_step["VALIDATING"]
).total_seconds(),
"queued": (time_per_step["RUNNING"] - time_per_step["QUEUED"]).total_seconds(),
"running": (time_per_step["COMPLETED"] - time_per_step["RUNNING"]).total_seconds(),
}
self.tracker.update(job_time=job_time)
self.tracker.record()
5 changes: 4 additions & 1 deletion pennylane_qiskit/qiskit_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,6 @@ def generate_samples(self, circuit=None):

# hardware or hardware simulator
samples = self._current_job.result().get_memory(circuit)

# reverse qubit order to match PennyLane convention
return np.vstack([np.array([int(i) for i in s[::-1]]) for s in samples])

Expand Down Expand Up @@ -411,4 +410,8 @@ def batch_execute(self, circuits):
res = self.statistics(circuit.observables)
results.append(res)

if self.tracker.active:
self.tracker.update(batches=1, batch_len=len(circuits))
self.tracker.record()

return results
17 changes: 12 additions & 5 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,12 @@
A = np.array([[1.02789352, 1.61296440 - 0.3498192j], [1.61296440 + 0.3498192j, 1.23920938 + 0j]])


state_backends = ["statevector_simulator", "unitary_simulator", "aer_simulator_statevector", "aer_simulator_unitary"]
state_backends = [
"statevector_simulator",
"unitary_simulator",
"aer_simulator_statevector",
"aer_simulator_unitary",
]
hw_backends = ["qasm_simulator", "aer_simulator"]


Expand Down Expand Up @@ -75,8 +80,9 @@ def device(request, backend, shots):
if backend not in state_backends and shots is None:
pytest.skip("Hardware simulators do not support analytic mode")

if (issubclass(request.param, AerDevice) and "aer" not in backend) \
or (issubclass(request.param, BasicAerDevice) and "aer" in backend):
if (issubclass(request.param, AerDevice) and "aer" not in backend) or (
issubclass(request.param, BasicAerDevice) and "aer" in backend
):
pytest.skip("Only the AerSimulator is supported on AerDevice")

def _device(n, device_options=None):
Expand All @@ -90,8 +96,9 @@ def _device(n, device_options=None):
@pytest.fixture(params=[AerDevice, BasicAerDevice])
def state_vector_device(request, statevector_backend, shots):

if (issubclass(request.param, AerDevice) and "aer" not in statevector_backend) \
or (issubclass(request.param, BasicAerDevice) and "aer" in statevector_backend):
if (issubclass(request.param, AerDevice) and "aer" not in statevector_backend) or (
issubclass(request.param, BasicAerDevice) and "aer" in statevector_backend
):
pytest.skip("Only the AerSimulator is supported on AerDevice")

def _device(n):
Expand Down
26 changes: 25 additions & 1 deletion tests/test_ibmq.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ def circuit(x, y):
x = qml.numpy.array(0.543, requires_grad=True)
y = qml.numpy.array(0.123, requires_grad=True)

res = qml.grad(circuit)(x,y)
res = qml.grad(circuit)(x, y)
expected = np.array([[-np.sin(y) * np.sin(x), np.cos(y) * np.cos(x)]])
assert np.allclose(res, expected, **tol)

Expand All @@ -235,6 +235,7 @@ def circuit(x, y):
# running the circuit
assert spy2.call_count == 2


@pytest.mark.parametrize("shots", [1000])
def test_probability(token, tol, shots):
"""Test that the probs function works."""
Expand All @@ -259,3 +260,26 @@ def circuit(x):
assert np.isclose(hw_prob.sum(), 1, **tol)
assert np.allclose(prob_analytic(x), hw_prob, **tol)
assert not np.array_equal(prob_analytic(x), hw_prob)


def test_track(token):
"""Test that the tracker works."""

IBMQ.enable_account(token)
dev = IBMQDevice(wires=1, backend="ibmq_qasm_simulator", shots=1)
dev.tracker.active = True

@qml.qnode(dev)
def circuit():
qml.PauliX(wires=0)
return qml.probs(wires=0)

circuit()

assert "job_time" in dev.tracker.history
if "job_time" in dev.tracker.history:
assert "creating" in dev.tracker.history["job_time"][0]
assert "validating" in dev.tracker.history["job_time"][0]
assert "queued" in dev.tracker.history["job_time"][0]
assert "running" in dev.tracker.history["job_time"][0]
assert len(dev.tracker.history["job_time"][0]) == 4
36 changes: 21 additions & 15 deletions tests/test_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ class TestDeviceIntegration:
@pytest.mark.parametrize("d", pldevices)
def test_load_device(self, d, backend):
"""Test that the qiskit device loads correctly"""
if (d[0] == "qiskit.aer" and "aer" not in backend) \
or (d[0] == "qiskit.basicaer" and "aer" in backend):
if (d[0] == "qiskit.aer" and "aer" not in backend) or (
d[0] == "qiskit.basicaer" and "aer" in backend
):
pytest.skip("Only the AerSimulator is supported on AerDevice")

dev = qml.device(d[0], wires=2, backend=backend, shots=1024)
Expand All @@ -38,7 +39,9 @@ def test_incorrect_backend(self):

def test_incorrect_backend_wires(self):
"""Test that exception is raised if number of wires is too large"""
with pytest.raises(ValueError, match=r"Backend 'aer_simulator\_statevector' supports maximum"):
with pytest.raises(
ValueError, match=r"Backend 'aer_simulator\_statevector' supports maximum"
):
qml.device("qiskit.aer", wires=100, method="statevector")

def test_args(self):
Expand All @@ -55,8 +58,9 @@ def test_args(self):
@pytest.mark.parametrize("shots", [None, 8192])
def test_one_qubit_circuit(self, shots, d, backend, tol):
"""Test that devices provide correct result for a simple circuit"""
if (d[0] == "qiskit.aer" and "aer" not in backend) \
or (d[0] == "qiskit.basicaer" and "aer" in backend):
if (d[0] == "qiskit.aer" and "aer" not in backend) or (
d[0] == "qiskit.basicaer" and "aer" in backend
):
pytest.skip("Only the AerSimulator is supported on AerDevice")

if backend not in state_backends and shots is None:
Expand All @@ -83,8 +87,9 @@ def circuit(x, y, z):
def test_basis_state_and_rot(self, shots, d, backend, tol):
"""Integration test for the BasisState and Rot operations for non-analytic mode."""

if (d[0] == "qiskit.aer" and "aer" not in backend) \
or (d[0] == "qiskit.basicaer" and "aer" in backend):
if (d[0] == "qiskit.aer" and "aer" not in backend) or (
d[0] == "qiskit.basicaer" and "aer" in backend
):
pytest.skip("Only the AerSimulator is supported on AerDevice")

dev = qml.device(d[0], wires=1, backend=backend, shots=shots)
Expand Down Expand Up @@ -149,9 +154,7 @@ def test_noise_model_qasm_simulator(self, monkeypatch):

cache = []
with monkeypatch.context() as m:
m.setattr(
aer.AerSimulator, "set_options", lambda *args, **kwargs: cache.append(kwargs)
)
m.setattr(aer.AerSimulator, "set_options", lambda *args, **kwargs: cache.append(kwargs))
dev = qml.device("qiskit.aer", wires=2, noise_model="test value")
assert cache[-1] == {"noise_model": "test value"}

Expand Down Expand Up @@ -485,6 +488,7 @@ def circuit():

assert circuit() == -1


class TestBatchExecution:
"""Test the devices work correctly with the batch execution pipeline."""

Expand All @@ -493,8 +497,9 @@ class TestBatchExecution:
def test_one_qubit_circuit_batch_params(self, shots, d, backend, tol, mocker):
"""Test that devices provide correct result for a simple circuit using
the batch_params transform."""
if (d[0] == "qiskit.aer" and "aer" not in backend) \
or (d[0] == "qiskit.basicaer" and "aer" in backend):
if (d[0] == "qiskit.aer" and "aer" not in backend) or (
d[0] == "qiskit.basicaer" and "aer" in backend
):
pytest.skip("Only the AerSimulator is supported on AerDevice")

if backend not in state_backends and shots is None:
Expand Down Expand Up @@ -531,8 +536,9 @@ def circuit(x, y, z):
def test_batch_execute_parameter_shift(self, shots, d, backend, tol, mocker):
"""Test that devices provide correct result computing the gradient of a
circuit using the parameter-shift rule and the batch execution pipeline."""
if (d[0] == "qiskit.aer" and "aer" not in backend) \
or (d[0] == "qiskit.basicaer" and "aer" in backend):
if (d[0] == "qiskit.aer" and "aer" not in backend) or (
d[0] == "qiskit.basicaer" and "aer" in backend
):
pytest.skip("Only the AerSimulator is supported on AerDevice")

if backend not in state_backends and shots is None:
Expand All @@ -553,7 +559,7 @@ def circuit(x, y):
x = qml.numpy.array(0.543, requires_grad=True)
y = qml.numpy.array(0.123, requires_grad=True)

res = qml.grad(circuit)(x,y)
res = qml.grad(circuit)(x, y)
expected = np.array([[-np.sin(y) * np.sin(x), np.cos(y) * np.cos(x)]])
assert np.allclose(res, expected, **tol)

Expand Down
2 changes: 1 addition & 1 deletion tests/test_inverses.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class TestInverses:
("Hadamard", 0),
("S", 1),
("T", 1),
("SX", 0)
("SX", 0),
],
)
def test_supported_gate_inverse_single_wire_no_parameters(self, name, expected_output):
Expand Down
13 changes: 4 additions & 9 deletions tests/test_qiskit_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ def test_backend_options_cleaned(self):
dev2 = qml.device("qiskit.aer", wires=2)
assert dev2.backend.options.get("noise_model") is None


@pytest.mark.parametrize("shots", [None])
class TestBatchExecution:
"""Tests for the batch_execute method."""
Expand Down Expand Up @@ -154,13 +155,9 @@ def test_result(self, device, tol):
tape2_expected = dev.execute(self.tape2)

assert len(res) == 2
assert np.allclose(
res[0], tape1_expected, atol=0
)
assert np.allclose(res[0], tape1_expected, atol=0)

assert np.allclose(
res[1], tape2_expected, atol=0
)
assert np.allclose(res[1], tape2_expected, atol=0)

def test_result_empty_tape(self, device, tol):
"""Tests that the result has the correct shape and entry types for empty tapes."""
Expand All @@ -174,6 +171,4 @@ def test_result_empty_tape(self, device, tol):
# execution
dev.reset()
assert len(res) == 3
assert np.allclose(
res[0], dev.execute(empty_tape), atol=0
)
assert np.allclose(res[0], dev.execute(empty_tape), atol=0)

0 comments on commit 9f89e04

Please sign in to comment.