Skip to content

Revert resource tracking to once again print to stdout #7392

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
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions doc/releases/changelog-dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,7 @@ Here's a list of deprecations made this release. For a more detailed breakdown o
* `null.qubit` can now support an optional `track_resources` argument which allows it to record which gates are executed.
[(#7226)](https://github.com/PennyLaneAI/pennylane/pull/7226)
[(#7372)](https://github.com/PennyLaneAI/pennylane/pull/7372)
[(#7392)](https://github.com/PennyLaneAI/pennylane/pull/7392)

* A new internal module, `qml.concurrency`, is added to support internal use of multiprocess and multithreaded execution of workloads. This also migrates the use of `concurrent.futures` in `default.qubit` to this new design.
[(#7303)](https://github.com/PennyLaneAI/pennylane/pull/7303)
Expand Down
45 changes: 29 additions & 16 deletions pennylane/devices/null_qubit.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import inspect
import json
import logging
import sys
from collections import defaultdict
from dataclasses import replace
from functools import lru_cache, singledispatch
Expand Down Expand Up @@ -132,7 +133,7 @@ def _interface(config: ExecutionConfig):
return config.interface.get_like() if config.gradient_method == "backprop" else "numpy"


def _simulate_resource_use(circuit, resources_fname="__pennylane_resources_data.json"):
def _simulate_resource_use(circuit, outfile):
num_wires = len(circuit.wires)
gate_types = defaultdict(int)

Expand Down Expand Up @@ -163,17 +164,16 @@ def _simulate_resource_use(circuit, resources_fname="__pennylane_resources_data.
name = f"{controls if controls > 1 else ''}C({name})"

gate_types[name] += 1
# NOTE: For now, this information is being printed to match the behavior of catalyst resource tracking.
# NOTE: For now, this information is being printed to match the behaviour of catalyst resource tracking.
# In the future it may be better to return this information in a more structured way.
with open(resources_fname, "w") as f:
json.dump(
{
"num_wires": num_wires,
"num_gates": sum(gate_types.values()),
"gate_types": gate_types,
},
f,
)
json.dump(
{
"num_wires": num_wires,
"num_gates": sum(gate_types.values()),
"gate_types": gate_types,
},
outfile,
)


@simulator_tracking
Expand All @@ -188,7 +188,7 @@ class NullQubit(Device):
(``['aux_wire', 'q1', 'q2']``). Default ``None`` if not specified.
shots (int, Sequence[int], Sequence[Union[int, Sequence[int]]]): The default number of shots
to use in executions involving this device.
track_resources (bool): If ``True``, track the number of resources used by the device. This argument is experimental and subject to change.
track_resources (bool | str | None): If truthy, track the number of resources used by the device. If a str is provided, use it as a filename in which to write the results. Else print to stdout. This argument is experimental and subject to change.
**Example:**

.. code-block:: python
Expand Down Expand Up @@ -272,20 +272,33 @@ def name(self):
"""The name of the device."""
return "null.qubit"

def __init__(self, wires=None, shots=None, track_resources=False) -> None:
def __init__(self, wires=None, shots=None, track_resources=None) -> None:
super().__init__(wires=wires, shots=shots)
self._debugger = None
self._track_resources = track_resources
if track_resources is True:
self._track_resources = sys.stdout
else:
self._track_resources = track_resources

# this is required by Catalyst to toggle the tracker at runtime
self.device_kwargs = {"track_resources": track_resources}
self.device_kwargs = {
"track_resources": bool(track_resources),
"track_resources_fname": track_resources if isinstance(track_resources, str) else None,
"track_resources_stdout": track_resources is True,
}

def _simulate(self, circuit, interface):
num_device_wires = len(self.wires) if self.wires else len(circuit.wires)
results = []

if self._track_resources:
_simulate_resource_use(circuit)
if isinstance(self._track_resources, str):
# if a string is passed, we assume it is a file name
with open(self._track_resources, "w", encoding="utf-8") as f:
_simulate_resource_use(circuit, f)
else:
# NOTE: This will work even if `_track_resources` was originally passed a file-like object
_simulate_resource_use(circuit, self._track_resources)

for s in circuit.shots or [None]:
r = tuple(
Expand Down
111 changes: 63 additions & 48 deletions tests/devices/test_null_qubit.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@
# limitations under the License.
"""Tests for null.qubit."""

import io
import json
import os
import sys
from collections import defaultdict as dd

import numpy as np
Expand Down Expand Up @@ -66,68 +68,81 @@ def test_debugger_attribute():
def test_resource_tracking_attribute():
"""Test NullQubit track_resources attribute"""
# pylint: disable=protected-access
assert NullQubit()._track_resources is False
assert NullQubit(track_resources=True)._track_resources is True
RESOURCES_FNAME = "__pennylane_resources_data.json"
assert NullQubit()._track_resources is None
assert NullQubit(track_resources=True)._track_resources == sys.stdout
assert NullQubit(track_resources=RESOURCES_FNAME)._track_resources == RESOURCES_FNAME

dev = NullQubit(track_resources=True)
for pre_opened in (False, True):
if pre_opened:
in_arg = io.StringIO()
else:
in_arg = RESOURCES_FNAME

def small_circ(params):
qml.X(0)
qml.H(0)
dev = NullQubit(track_resources=in_arg)

qml.Barrier()
def small_circ(params):
qml.X(0)
qml.H(0)

# Add a more complex operation to check that the innermost operation is counted
op = qml.T(0)
op = qml.adjoint(op)
op = qml.ctrl(op, control=1, control_values=[1])
qml.Barrier()

qml.ctrl(qml.S(0), control=[1, 2], control_values=[1, 1])
# Add a more complex operation to check that the innermost operation is counted
op = qml.T(0)
op = qml.adjoint(op)
op = qml.ctrl(op, control=1, control_values=[1])

qml.CNOT([0, 1])
qml.Barrier()
qml.ctrl(qml.S(0), control=[1, 2], control_values=[1, 1])

qml.ctrl(qml.IsingXX(0, [0, 1]), control=2, control_values=[1])
qml.adjoint(qml.S(0))
qml.CNOT([0, 1])
qml.Barrier()

qml.RX(params[0], wires=0)
qml.RX(params[0] * 2, wires=1)
qml.ctrl(qml.IsingXX(0, [0, 1]), control=2, control_values=[1])
qml.adjoint(qml.S(0))

return qml.expval(qml.PauliZ(0))
qml.RX(params[0], wires=0)
qml.RX(params[0] * 2, wires=1)

qnode = qml.QNode(small_circ, dev, diff_method="backprop")
return qml.expval(qml.PauliZ(0))

inputs = qml.numpy.array([0.5])
qnode = qml.QNode(small_circ, dev, diff_method="backprop")

qnode(inputs)
inputs = qml.numpy.array([0.5])

# Check that resource tracking doesn't interfere with backprop
assert qml.grad(qnode)(inputs) == 0
qnode(inputs)

RESOURCES_FNAME = "__pennylane_resources_data.json"
assert os.path.exists(RESOURCES_FNAME)

with open(RESOURCES_FNAME, "r") as f:
stats = f.read()

os.remove(RESOURCES_FNAME)

assert stats == json.dumps(
{
"num_wires": 3,
"num_gates": 9,
"gate_types": {
"PauliX": 1,
"Hadamard": 1,
"C(Adj(T))": 1,
"2C(S)": 1,
"CNOT": 1,
"C(IsingXX)": 1,
"Adj(S)": 1,
"RX": 2,
},
}
)
if pre_opened:
stats = in_arg.getvalue()
else:
assert os.path.exists(RESOURCES_FNAME)

with open(RESOURCES_FNAME, "r", encoding="utf-8") as f:
stats = f.read()

os.remove(RESOURCES_FNAME)

assert stats == json.dumps(
{
"num_wires": 3,
"num_gates": 9,
"gate_types": {
"PauliX": 1,
"Hadamard": 1,
"C(Adj(T))": 1,
"2C(S)": 1,
"CNOT": 1,
"C(IsingXX)": 1,
"Adj(S)": 1,
"RX": 2,
},
}
)

# Check that resource tracking doesn't interfere with backprop
assert qml.grad(qnode)(inputs) == 0

if pre_opened:
in_arg.close()


@pytest.mark.parametrize("shots", (None, 10))
Expand Down