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 or expansion_strategy needs to be provided. If neither is provided, qnode.expansion_strategy will be used instead. Users are encouraged to predominantly use level, as it allows for the same values as expansion_strategy and offers more flexibility in choosing the desired transforms/expansions.

Warning

max_expansion and qnode.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 the level 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}

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 that level=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 and PauliX are not present in the circuit if we set level=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 in RandomLayers:

>>> qml.specs(circuit, level=slice(2, 3))(0.1)["num_trainable_params"]
1

However, if we apply all transforms, RandomLayers is decomposed into an RY and an RX, 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