The purpose of this article is to explain how to obtain information about devices and circuits using the `qml.specs`

and `qml.Tracker`

functions. Devices are key objects in PennyLane and necessary to execute quantum circuits and represent simulators or hardware devices.

The most basic device is the state-vector simulator
`default.qubit`

. In the following example we are going to import PennyLane and initialize a device with two qubits. Let us recall that we specify the number qubits (wires) in PennyLane via the `wires`

argument passed to the `qml.device`

function. The optional `shots`

argument determines the number of times a circuit is run on a device. For a simulator, it determines the number of samples drawn from an analytic probability distribution.

```
import pennylane as qml
from pennylane import numpy as np
dev = qml.device("default.qubit", wires=2, shots=100)
```

The circuits that we run on a device are implemented in PennyLane as quantum functions. Devices and circuits can be combined to form quantum nodes represented by the `QNode`

class. We do this by applying the `qml.qnode`

decorator to the quantum function which takes a device argument. This device specifies the device on which the circuit will be executed. For illustration purposes, we will build
a small circuit with parametrized `RY`

rotation gates on each of the qubits, followed by a `CNOT`

gate controlled by the first qubit.

```
@qml.qnode(dev)
def circuit(theta):
qml.RY(theta[0], wires=0)
qml.RY(theta[1], wires=1)
qml.CNOT(wires=[0, 1])
return qml.expval(qml.PauliZ(1))
theta = np.array([0.1, 0.3], requires_grad = True)
qml.draw_mpl(circuit)(theta)
```

The specifications of the device and the circuit can be obtained using the `qml.specs`

function. The device specifications that you might find most useful are `device_name`

and `num_device_wires`

. Other important circuit specifications, shown below, are `num_operations`

(unitary operators), `num_observables`

(hermitian operators), `num_trainable_params`

and `depth`

. Note that, since there are two trainable parameters, it takes four evaluations to calculate the gradient using the parameter-shift rule.

```
qml.specs(circuit)(theta)
```

```
{'gate_sizes': defaultdict(int, {1: 2, 2: 1}),
'gate_types': defaultdict(int, {'RY': 2, 'CNOT': 1}),
'num_operations': 3,
'num_observables': 1,
'num_diagonalizing_gates': 0,
'num_used_wires': 2,
'depth': 2,
'num_trainable_params': 2,
'num_device_wires': 2,
'device_name': 'default.qubit',
'expansion_strategy': 'gradient',
'gradient_options': {},
'interface': 'autograd',
'diff_method': 'best',
'gradient_fn': 'pennylane.gradients.parameter_shift.param_shift',
'num_gradient_executions': 4}
```

The information about device executions while running a circuit can be obtained using the `qml.Tracker`

function. For standard devices, the function will track the number of executions, number of shots, number of batch executions, and batch execution length.

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

Querying `totals`

outputs a running sum per keyword, while `history`

stores a list of values passed for each keyword.

```
tracker.totals
```

```
{'executions': 5, 'shots': 500, 'batches': 2, 'batch_len': 5}
```

As expected, five hundred shots were needed in total: one hundred for the evaluation of the quantum function and four hundred for evaluating the circuits with the shifted values. Let’s now see the output of `history`

.

```
tracker.history
```

```
{'executions': [1, 1, 1, 1, 1],
'shots': [100, 100, 100, 100, 100],
'batches': [1, 1],
'batch_len': [1, 4]}
```

The tracker history makes it explicit that the gradient evaluation is batched (batch length = 4) separately from the function evaluation (batch length = 1). The last round of information from the tracker is stored in `latest`

.

```
tracker.latest
```

```
{'batches': 1, 'batch_len': 4}
```

The `callback`

function allows you to track long-running jobs. It requires `totals`

, `history`

, and `latest`

as keyword arguments and is run each time a task, such as function evaluation, is executed on the device.

```
def shots_info(totals, history, latest):
if 'shots' in latest:
print("Total shots: ", totals['shots'])
with qml.Tracker(circuit.device, callback=shots_info) as tracker:
qml.grad(circuit)(theta)
```

```
Total shots: 100
Total shots: 200
Total shots: 300
Total shots: 400
Total shots: 500
```

By default, information recorded by the tracker persists for multiple circuit executions. If we need to reuse the tracker across different circuit runs, we can set `persistent=False`

. Notice below how the number of shots resets when we begin evaluating the circuit with different parameters.

```
with qml.Tracker(circuit.device, persistent=False, callback=shots_info) as tracker:
qml.grad(circuit)(theta)
phi = np.array([0.2, 0.4], requires_grad = True)
with tracker:
qml.grad(circuit)(phi)
```

```
Total shots: 100
Total shots: 200
Total shots: 300
Total shots: 400
Total shots: 500
Total shots: 100
Total shots: 200
Total shots: 300
Total shots: 400
Total shots: 500
```

Now that we know how to get this information out, we can better estimate the resources needed for our quantum and hybrid algorithms. For example, we might ask how many circuits we need to estimate the ground state of a molecule. I invite you to figure this out, you will be surprised by the answer!