qml.specs¶
-
specs
(qnode, **kwargs)[source]¶ Resource information about a quantum circuit.
This transform converts a QNode into a callable that provides resource information about the circuit after applying the specified amount of transforms/expansions first.
- Parameters
qnode (QNode) – the QNode to calculate the specifications for.
- Keyword Arguments
level (None, str, int, slice) – An indication of what transforms to apply before computing the resource information. Check
get_transform_program()
for more information on the allowed values and usage details of this argument.expansion_strategy (str) –
The strategy to use when circuit expansions or decompositions are required.
gradient
: The QNode will attempt to decompose the internal circuit such that all circuit operations are supported by the gradient method.device
: The QNode will attempt to decompose the internal circuit such that all circuit operations are natively supported by the device.
max_expansion (int) – The number of times the internal circuit should be expanded when calculating the specification. Defaults to
qnode.max_expansion
.
- Returns
A function that has the same argument signature as
qnode
. This function returns a dictionary (or a list of dictionaries) of information about qnode structure.
Note
At most, one of
level
orexpansion_strategy
needs to be provided. If neither is provided,qnode.expansion_strategy
will be used instead. Users are encouraged to predominantly uselevel
, as it allows for the same values asexpansion_strategy
and offers more flexibility in choosing the desired transforms/expansions.Warning
max_expansion
andqnode.max_expansion
have no effect on the return of this function and will be ignored.Warning
The
expansion_strategy
argument is deprecated and will be removed in version 0.39. Use thelevel
argument instead to specify the resulting tape you want.Example
from pennylane import numpy as pnp x = pnp.array([0.1, 0.2]) hamiltonian = qml.dot([1.0, 0.5], [qml.X(0), qml.Y(0)]) dev = qml.device('default.qubit', wires=2) @qml.qnode(dev, diff_method="parameter-shift", shifts=pnp.pi / 4) def circuit(x, add_ry=True): qml.RX(x[0], wires=0) qml.CNOT(wires=(0,1)) qml.TrotterProduct(hamiltonian, time=1.0, n=4, order=2) if add_ry: qml.RY(x[1], wires=1) qml.TrotterProduct(hamiltonian, time=1.0, n=4, order=4) return qml.probs(wires=(0,1))
>>> qml.specs(circuit)(x, add_ry=False) {'resources': Resources(num_wires=2, num_gates=98, gate_types=defaultdict(<class 'int'>, {'RX': 1, 'CNOT': 1, 'Exp': 96}), gate_sizes=defaultdict(<class 'int'>, {1: 97, 2: 1}), depth=98, shots=Shots(total_shots=None, shot_vector=())), 'errors': {'SpectralNormError': SpectralNormError(0.42998560822421455)}, 'num_observables': 1, 'num_diagonalizing_gates': 0, 'num_trainable_params': 1, 'num_device_wires': 2, 'num_tape_wires': 2, 'device_name': 'default.qubit', 'level': 'gradient', 'gradient_options': {'shifts': 0.7853981633974483}, 'interface': 'auto', 'diff_method': 'parameter-shift', 'gradient_fn': 'pennylane.gradients.parameter_shift.param_shift', 'num_gradient_executions': 2}
Usage Details
Here you can see how the number of gates and their types change as we apply different amounts of transforms through the
level
argument:@qml.transforms.merge_rotations @qml.transforms.undo_swaps @qml.transforms.cancel_inverses @qml.qnode(qml.device("default.qubit"), diff_method="parameter-shift", shifts=np.pi / 4) def circuit(x): qml.RandomLayers(qml.numpy.array([[1.0, 2.0]]), wires=(0, 1)) qml.RX(x, wires=0) qml.RX(-x, wires=0) qml.SWAP((0, 1)) qml.X(0) qml.X(0) return qml.expval(qml.X(0) + qml.Y(1))
First, we can check the resource information of the
QNode
without any modifications. Note thatlevel=top
would return the same results:>>> print(qml.specs(circuit, level=0)(0.1)["resources"]) wires: 2 gates: 6 depth: 6 shots: Shots(total=None) gate_types: {'RandomLayers': 1, 'RX': 2, 'SWAP': 1, 'PauliX': 2} gate_sizes: {2: 2, 1: 4}
We then check the resources after applying all transforms:
>>> print(qml.specs(circuit, level=None)(0.1)["resources"]) wires: 2 gates: 2 depth: 1 shots: Shots(total=None) gate_types: {'RY': 1, 'RX': 1} gate_sizes: {1: 2}
We can also notice that
SWAP
andPauliX
are not present in the circuit if we setlevel=2
:>>> print(qml.specs(circuit, level=2)(0.1)["resources"]) wires: 2 gates: 3 depth: 3 shots: Shots(total=None) gate_types: {'RandomLayers': 1, 'RX': 2} gate_sizes: {2: 1, 1: 2}
If we attempt to apply only the
merge_rotations
transform, we end up with only one trainable object, which is inRandomLayers
:>>> qml.specs(circuit, level=slice(2, 3))(0.1)["num_trainable_params"] 1
However, if we apply all transforms,
RandomLayers
is decomposed into anRY
and anRX
, giving us two trainable objects:>>> qml.specs(circuit, level=None)(0.1)["num_trainable_params"] 2
If a
QNode
with a tape-splitting transform is supplied to the function, with the transform included in the desired transforms, a dictionary is returned for each resulting tape:H = qml.Hamiltonian([0.2, -0.543], [qml.X(0) @ qml.Z(1), qml.Z(0) @ qml.Y(2)]) @qml.transforms.split_non_commuting @qml.qnode(qml.device("default.qubit"), diff_method="parameter-shift", shifts=np.pi / 4) def circuit(): qml.RandomLayers(qml.numpy.array([[1.0, 2.0]]), wires=(0, 1)) return qml.expval(H)
>>> len(qml.specs(circuit, level="user")()) 2