qml.Tracker¶
- class Tracker(dev=None, callback=None, persistent=False)[source]¶
Bases:
object
This class stores information about device executions and allows users to interact with that data upon individual executions and batches, even within parameter-shift gradients and optimization steps.
The information is stored in three class attribute dictionaries:
totals
,history
, andlatest
:latest
tracks the last set of information passed to the tracker.history
stores a list of values passed for each keyword.totals
keeps a running sum per keyword when the values are numeric.
Standard devices will track the number of executions, number of shots, number of batch executions, batch execution length, and results of circuit executions, but plugins may store additional information with no changes to this class.
Information is only stored when the class attribute
active
is set toTrue
. This attribute can be toggled via a context manager and Python’swith
statement. Upon entering a context, the stored information is reset, unlesspersistent=True
. Tracking mode can also be manually triggered by settingtracker.active = True
without the use of a context manager.- Parameters
dev (devices.Device) – A PennyLane compatible device
callback=None (callable or None) – A function of the keywords
totals
,history
andlatest
. Run on eachrecord
call with current values of the corresponding attributes.persistent=False (bool) – Whether to reset stored information upon entering a runtime context.
Example
Using a
with
statement to toggle the active mode, we can see the number of executions and shots used to calculate a parameter-shift derivative.dev = qml.device('default.qubit', wires=1, shots=100) @qml.qnode(dev, diff_method="parameter-shift") def circuit(x): qml.RX(x, wires=0) return qml.expval(qml.Z(0)) x = np.array(0.1, requires_grad=True) with qml.Tracker(dev) as tracker: qml.grad(circuit)(x)
You can then access the tabulated information through
totals
,history
, andlatest
:>>> tracker.totals {'batches': 2, 'simulations': 3, 'executions': 3, 'results': 0.86, 'shots': 300} >>> tracker.latest {'simulations': 1, 'executions': 1, 'results': 0.16, 'shots': 100, 'resources': Resources(num_wires=1, num_gates=1, gate_types=defaultdict(<class 'int'>, {'RX': 1}), gate_sizes=defaultdict(<class 'int'>, {1: 1}), depth=1, shots=Shots(total_shots=100, shot_vector=(ShotCopies(100 shots x 1),))), 'errors': {} } >>> tracker.history.keys() dict_keys(['batches', 'simulations', 'executions', 'results', 'shots', 'resources', 'errors']) >>> tracker.history['results'] [1.0, -0.3, 0.16] >>> print(tracker.history['resources'][0]) wires: 1 gates: 1 depth: 1 shots: Shots(total=100) gate_types: {'RX': 1} gate_sizes: {1: 1}
We can see that calculating the gradient of
circuit
takes three total evaluations: one forward pass and one batch of length two for the derivative ofqml.RX
.Usage Details
Note
With backpropagation, this function should take
qnode.device
instead of the device used to create the QNode.Users can pass a custom callback function to the
callback
keyword. This function is run each time therecord()
method is called, which occurs near the end of a device’sexecute
andbatch_execute
methods. Usingprint
or logging, users can monitor completion during a long set of jobs.The function passed must accept
totals
,history
, andlatest
as keyword arguments. The dictionarylatest
will contain different keywords based on whether whetherexecute
orbatch_execute
last performed an update.>>> def shots_info(totals, history, latest): ... if 'shots' in latest: ... print("Total shots: ", totals['shots']) >>> x = np.array(0.1, requires_grad=True) >>> with qml.Tracker(circuit.device, callback=shots_info) as tracker: ... qml.grad(circuit)(x) Total shots: 100 Total shots: 200 Total shots: 300
By specifying
persistent=False
, you can reuse the same tracker across multiple contexts.>>> with qml.Tracker(circuit.device, persistent=False) as tracker: ... circuit(0.1) >>> with tracker: ... circuit(0.2) >>> tracker.totals['executions'] 2
When used with the null qubit device (eg.
dev = qml.device("null.qubit")
), we can track the resources used in the circuit without execution!>>> dev = qml.device("null.qubit", wires=[0], shots=10) >>> @qml.qnode(dev) ... def circuit(x): ... qml.RX(x, wires=0) ... return qml.expval(qml.Z(0)) ... >>> with qml.Tracker(dev) as tracker: ... circuit(0.1) ... >>> resources_lst = tracker.history['resources'] >>> print(resources_lst[0]) wires: 1 gates: 1 depth: 1 shots: Shots(total=10) gate_types: {"RX": 1} gate_sizes: {1: 1}
Methods
record
()This method allows users to interact with the stored data.
reset
()Resets stored information.
update
(**kwargs)Store passed keyword-value pairs into
totals
,``history``, andlatest
attributes.- record()[source]¶
This method allows users to interact with the stored data. While it’s intended purpose is monitoring large jobs through
print
statements or logging, the function is completely flexible and customizable.If a user provided a
callback
function during initialization, that function is called with the currenttotals
,history
, andlatest
data variables as keyword arguments.
- update(**kwargs)[source]¶
Store passed keyword-value pairs into
totals
,``history``, andlatest
attributes.There is no restriction on the key-value pairs passed, but in the standard devices, the device
execute
method will passexecutions
andshots
, and thebatch_execute
method will passbatches
andbatch_len
.Only numeric values will be added to
totals
.>>> tracker.update(a=1, b=2, c="c") >>> tracker.latest {"a":1, "b":2, "c":"c"} >>> tracker.history {"a": [1], "b": [2], "c": ["c"]} >>> tracker.totals {"a": 1, "b": 2}