qml.decomposition.register_resources¶
- register_resources(ops, qfunc=None, *, work_wires=None, exact=True)[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 usingqml.register_resources.- Parameters:
ops (dict or Callable) – a dictionary mapping unique operators within the given
qfuncto 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.
- Keyword Arguments:
work_wires (dict or Callable) – a dictionary declaring the number of work wires of each type required to perform this decomposition. Accepted work wire types include
"zeroed","borrowed","burnable", and"garbage". For more information, consult the “Dynamic Allocation of Work Wires” section below.exact (bool) – whether the resources are computed exactly (
True, default) or estimated heuristically (False). This information is only relevant for testing and validation purposes.
- Returns:
a data structure that represents a decomposition rule, which contains a PennyLane quantum function representing the decomposition, and its resource function.
- Return type:
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.
from functools import partial 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, gate_set={qml.CZ, qml.H}, 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 as Decomposition Rules
Quantum functions representing decomposition rules within the new decomposition system are expected to take
(*op.parameters, op.wires, **op.hyperparameters)as arguments, whereopis an instance of the operator type that the decomposition is for.Operators with Dynamic Resource Requirements
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_keysattribute. For example, the number of gates in the decomposition forqml.MultiRZchanges based on the number of wires it acts on, in contrast to the decomposition forqml.CNOT:>>> qml.CNOT.resource_keys set() >>> qml.MultiRZ.resource_keys {'num_wires'}
The output of
resource_keysindicates 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 a decomposition rule produces a
MultiRZgate, it is not sufficient to declare the existence of aMultiRZin 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(theta, wires): qml.MultiRZ(theta, wires=wires[:-1]) qml.MultiRZ(theta, wires=wires) qml.MultiRZ(theta, wires=wires[1:])
It contains two
MultiRZgates acting onlen(wires) - 1wires (the first and lastMultiRZ) and oneMultiRZgate acting on exactlylen(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
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 theresource_keysattribute of theOperatorclass. Operators with non-emptyresource_keysmust be declared usingqml.resource_rep, with keyword arguments matching itsresource_keysexactly.See also
Dynamically Allocated Wires as a Resource
Some decomposition rules make use of work wires, which can be dynamically requested within the quantum function using
allocate(). Such decomposition rules should register the number of work wires they require so that the decomposition algorithm is able to budget the use of work wires across decomposition rules.There are four types of work wires:
“zeroed” wires are guaranteed to be in the \(|0\rangle\) state initially, and they must be restored to the \(|0\rangle\) state before deallocation.
“borrowed” wires are allocated in an arbitrary state, but they must be restored to the same initial state before deallocation.
“burnable” wires are guaranteed to be in the \(|0\rangle\) state initially, but they can be deallocated in any arbitrary state.
“garbage” wires can be allocated in any state, and can be deallocated in any state.
Here’s a decomposition for a multi-controlled
Rotthat uses a zeroed work wire:from functools import partial import pennylane as qml from pennylane.allocation import allocate from pennylane.decomposition import controlled_resource_rep qml.decomposition.enable_graph() def _ops_fn(num_control_wires, **_): return { controlled_resource_rep(qml.X, {}, num_control_wires): 2, qml.CRot: 1 } @qml.register_condition(lambda num_control_wires, **_: num_control_wires > 1) @qml.register_resources(ops=_ops_fn, work_wires={"zeroed": 1}) def _controlled_rot_decomp(*params, wires, **_): with allocate(1, require_zeros=True, restored=True) as work_wires: qml.ctrl(qml.X(work_wires[0]), control=wires[:-1]) qml.CRot(*params, wires=[work_wires[0], wires[-1]]) qml.ctrl(qml.X(work_wires[0]), control=wires[:-1]) @partial(qml.transforms.decompose, fixed_decomps={"C(Rot)": _controlled_rot_decomp}) @qml.qnode(qml.device("default.qubit")) def circuit(): qml.ctrl(qml.Rot(0.1, 0.2, 0.3, wires=3), control=[0, 1, 2]) return qml.probs(wires=[0, 1, 2, 3])
>>> print(qml.draw(circuit)()) <DynamicWire>: ──Allocate─╭X─╭●───────────────────╭X──Deallocate─┤ 0: ───────────├●─│────────────────────├●─────────────┤ ╭Probs 1: ───────────├●─│────────────────────├●─────────────┤ ├Probs 2: ───────────╰●─│────────────────────╰●─────────────┤ ├Probs 3: ──────────────╰Rot(0.10,0.20,0.30)────────────────┤ ╰Probs