qml.decomposition.register_resources

register_resources(resources, qfunc=None)[source]

Binds a quantum function to its required resources.

Note

This function is only relevant when the new experimental graph-based decomposition system (introduced in v0.41) is enabled via enable_graph(). This new way of doing decompositions is generally more resource efficient and accommodates multiple alternative decomposition rules for an operator. In this new system, custom decomposition rules are defined as quantum functions, and it is currently required that every decomposition rule declares its required resources using qml.register_resources.

Parameters
  • resources (dict or Callable) – a dictionary mapping unique operators within the given qfunc to their number of occurrences therein. If a function is provided instead of a static dictionary, a dictionary must be returned from the function. For more information, consult the Quantum Functions as Decomposition Rules section below.

  • qfunc (Callable) – the quantum function that implements the decomposition. If None, returns a decorator for acting on a function.

Returns

a data structure that represents a decomposition rule, which contains a PennyLane quantum function representing the decomposition, and its resource function.

Return type

DecompositionRule

Example

This function can be used as a decorator to bind a quantum function to its required resources so that it can be used as a decomposition rule within the new graph-based decomposition system.

import pennylane as qml

qml.decomposition.enable_graph()

@qml.register_resources({qml.H: 2, qml.CZ: 1})
def my_cnot(wires):
    qml.H(wires=wires[1])
    qml.CZ(wires=wires)
    qml.H(wires=wires[1])

@partial(qml.transforms.decompose, fixed_decomps={qml.CNOT: my_cnot})
@qml.qnode(qml.device("default.qubit"))
def circuit():
    qml.CNOT(wires=[0, 1])
    return qml.state()
>>> print(qml.draw(circuit, level="device")())
0: ────╭●────┤  State
1: ──H─╰Z──H─┤  State

Alternatively, the decomposition rule can be created in-line:

>>> my_cnot = qml.register_resources({qml.H: 2, qml.CZ: 1}, my_cnot)

Quantum functions representing decomposition rules within the new decomposition system are expected to take (*op.parameters, op.wires, **op.hyperparameters) as arguments, where op is an instance of the operator type that the decomposition is for.

In many cases, the resource requirement of an operator’s decomposition is not static; some operators have properties that directly affect the resource estimate of its decompositions, i.e., the types of gates that exist in the decomposition and their number of occurrences.

For each operator class, the set of parameters that affects the type of gates and their number of occurrences in its decompositions is given by the resource_keys attribute. For example, the number of gates in the decomposition for qml.MultiRZ changes based on the number of wires it acts on, in contrast to the decomposition for qml.CNOT:

>>> qml.CNOT.resource_keys
{}
>>> qml.MultiRZ.resource_keys
{'num_wires'}

The output of resource_keys indicates that custom decompositions for the operator should be registered to a resource function (as opposed to a static dictionary) that accepts those exact arguments and returns a dictionary.

def _multi_rz_resources(num_wires):
    return {
        qml.CNOT: 2 * (num_wires - 1),
        qml.RZ: 1
    }

@qml.register_resources(_multi_rz_resources)
def multi_rz_decomposition(theta, wires, **__):
    for w0, w1 in zip(wires[-1:0:-1], wires[-2::-1]):
        qml.CNOT(wires=(w0, w1))
    qml.RZ(theta, wires=wires[0])
    for w0, w1 in zip(wires[1:], wires[:-1]):
        qml.CNOT(wires=(w0, w1))

Additionally, if a custom decomposition for an operator contains gates that, in turn, have properties that affect their own decompositions, this information must also be included in the resource function. For example, if the decomposition rule produces a MultiRZ gate, it is not sufficient to declare the existence of a MultiRZ in the resource function; the number of wires it acts on must also be specified.

Consider a fictitious operator with the following decomposition:

def my_decomp(thata, wires):
    qml.MultiRZ(theta, wires=wires[:-1])
    qml.MultiRZ(theta, wires=wires)
    qml.MultiRZ(theta, wires=wires[1:])

It contains two MultiRZ gates acting on num_wires - 1 wires (the first and last MultiRZ) and one MultiRZ gate acting on exactly num_wires wires. This distinction must be reflected in the resource function:

def my_resources(num_wires):
    return {
        qml.resource_rep(qml.MultiRZ, num_wires=num_wires - 1): 2,
        qml.resource_rep(qml.MultiRZ, num_wires=num_wires): 1
    }

my_decomp = qml.register_resources(my_resources, my_decomp)

where qml.resource_rep is a utility function that wraps an operator type and any additional information relevant to its resource estimate into a compressed data structure. To check what (if any) additional information is required to declare an operator type in a resource function, refer to the resource_keys attribute of the operator class. Operators with non-empty resource_keys must be declared using qml.resource_rep, with keyword arguments matching its resource_keys exactly.

See also

resource_rep()