qml.templates.core.Subroutine¶
- class Subroutine(definition, *, setup_inputs=<function _default_setup_inputs>, static_argnames=(), wire_argnames=('wires', ), compute_resources=None)[source]¶
Bases:
objectThe definition of a Subroutine, compatible both with program capture and backwards compatible with operators.
- Parameters:
definition (Callable) – a quantum function that can contain both quantum and classical processing. The definition can return purely classical values or the outputs from mid circuit measurements, but it cannot return terminal statistics.
setup_inputs (Callable) – An function that can run preprocessing on the inputs before hitting definition. This can be used to make static arguments hashable for compatibility with program capture.
static_argnames (str | tuple[str]) – The name of arguments that are treated as static (trace- and compile-time constant).
wire_argnames (str | tuple[str]) – The name of arguments that represent wire registers. While the users can be more permissive in what they provide to wire arguments, the definition should treat all wire arguments as 1D arrays.
compute_resources (None | Callable) – A function for computing resources used by the function. It should only calculate the resources from the static arguments, the length of the wire registers, and the shape and dtype of the dynamic arguments. In the case of the specific resources depending on the specifics of a dynamic argument, a worse case scenario can be used.
For simple cases, a
Subroutinecan simply be created from a single quantum function, like:from pennylane.templates import Subroutine @Subroutine def MyTemplate(x, y, wires): qml.RX(x, wires[0]) qml.RY(y, wires[0]) @qml.qnode(qml.device('default.qubit')) def c(): MyTemplate(0.1, 0.2, 0) return qml.state() c()
>>> print(qml.draw(c)()) 0: ──MyTemplate(0.10,0.20)─┤ State >>> print(qml.draw(c, level="device")()) 0: ──RX(0.10)──RY(0.20)─┤ State >>> print(qml.specs(c)().resources) Total wire allocations: 1 Total gates: 1 Circuit depth: 1 Gate types: MyTemplate: 1 Measurements: state(all wires): 1
For multiple wire register inputs or use of a different name than
"wires", thewire_argnamescan be provided:from functools import partial @partial(Subroutine, wire_argnames=("register1", "register2")) def MultiRegisterTemplate(register1, register2): for wire in register1: qml.X(wire) for wire in register2: qml.Z(wire)
>>> print(qml.draw(MultiRegisterTemplate)(0, [1,2])) 0: ─╭MultiRegisterTemplate─┤ 1: ─├MultiRegisterTemplate─┤ 2: ─╰MultiRegisterTemplate─┤
Static arguments are treated as compile-time constant with
qml.qjit, and must be hashable. These are any inputs that are not numerical data or Operators. In the below example, thepauli_wordargument is a string that is a static argument.@partial(Subroutine, static_argnames="pauli_word") def WithStaticArg(x, wires, pauli_word: str): qml.PauliRot(x, pauli_word, wires)
Setup Inputs:
Sometimes we want to allow the user to be able to provide a static input in a non-hashable format. For example, the user might provide an input as a
listinstead of atuple. This can be done by providing thesetup_inputsfunction. This function should have the same call signature as the template and return a tuple of position arguments and a dictionary of keyword arguments.def setup_inputs(x, wires, pauli_words): return (x, wires, tuple(pauli_words)), {} @partial(Subroutine, static_argnames="pauli_words", setup_inputs=setup_inputs) def WithSetup(x, wires, pauli_words: list[str] | tuple[str,...]): for word in pauli_words: qml.PauliRot(x, word, wires)
>>> print(qml.draw(WithSetup)(0.5, [0, 1], ["XX", "XY", "XZ"])) 0: ─╭WithSetup(0.50)─┤ 1: ─╰WithSetup(0.50)─┤
setup_inputscan also help us set default values for dynamic inputs. If an input is numerical (not static), but needs to default to a value contingent on the other inputs, that is allowed to occur insetup_inputs. This has to happen insetup_inputsbecause a dynamic, numerical input likeycannot beNonewhen it hits the quantum function definition.def setup_default_value(y : int | None = None, wires=()): if y is None: y = len(wires) return (y, wires), {}
setup_inputsshould only interact with with compile-time information like static arguments, pytree structures, shapes, and dtypes, and not interact with any numerical values. Any manipulation or checks on values should occur inside the quantum function definition itself.def BAD(x, wires, metadata): if x < 0: # do something ... def GOOD(x, wires, metadata): if x.shape == (): # do something if metadata: # do something else ...
Computing Resources:
While not currently integrated, a function to compute the resources can also be provided. The calculation of resources should only depend on the static arguments, the number of wires in each register, and the shape and
dtypeof the dynamic arguments. This will allow the calculation of the resources to performed in an abstract way.def RXLayerResources(params, wires): return {qml.RX: qml.math.shape(params)[0]} @partial(qml.templates.Subroutine, compute_resources=RXLayerResources) def RXLayer(params, wires): for i in range(params.shape[0]): qml.RX(params[i], wires[i])
For example, we should be able to calculate the resources using JAX’s
jax.core.ShapedArrayinstead of concrete array with real values.>>> import jax >>> abstract_params = jax.core.ShapedArray((10,), float) >>> abstract_wires = jax.core.ShapedArray((10,), int) >>> RXLayer.compute_resources(abstract_params, abstract_wires) {<class 'pennylane.ops.qubit.parametric_ops_single_qubit.RX'>: 10}
Use of Autograph:
Autograph converts Python control flow (
if,for,while, etc.) into PennyLane’s control flow (for_loop(),cond(),while_loop()) that is compatible with traced arguments. The user’s choice of applying autograph on their workflow inqjit()does not effect the capture of aSubroutine. Autograph should instead be applied manually withrun_autograph()to the quantum function as needed.For example, is we have the template and
qjitworkflow:@qml.templates.Subroutine def f(x, wires): if x < 0: qml.X(wires) else: qml.Y(wires) @qml.qjit(autograph=True) @qml.qnode(qml.device('lightning.qubit', wires=1)) def c(x): f(x, 0) return qml.expval(qml.Z(0))
>>> c(0.5) Traceback (most recent call last): ... CaptureError: Autograph must be used when Python control flow is dependent on a dynamic variable (a function input). Please ensure that autograph is being correctly enabled with `qml.capture.run_autograph` or disabled with `qml.capture.disable_autograph` or consider using PennyLane native control flow functions like `qml.for_loop`, `qml.while_loop`, or `qml.cond`.
In order to support a conditional on a dynamic value, we should either
run_autographto the quantum function definition itself or useqml.condmanually:@qml.templates.Subroutine @qml.capture.run_autograph def UsingAutograph(x, wires): if x < 0: qml.X(wires) else: qml.Y(wires) @qml.templates.Subroutine def UsingCond(x, wires): qml.cond(x > 0, qml.X, qml.Y)(wires)
Attributes
The names of the function arguments that are pytrees of numerical data.
A string representation to label the Subroutine.
"The signature for the definition.
The names of arguments that are compile time constant.
The names for the arguments that represent a register of wires.
- dynamic_argnames¶
The names of the function arguments that are pytrees of numerical data. These are the arguments that are not static or wires.
- name¶
A string representation to label the Subroutine.
- signature¶
“The signature for the definition. Used to preprocess the user inputs.
- static_argnames¶
The names of arguments that are compile time constant.
- wire_argnames¶
The names for the arguments that represent a register of wires.
Methods
compute_resources(*args, **kwargs)Calculate a condensed representation for the resources required for the Subroutine.
definition(*args, **kwargs)The quantum function definition of the subroutine.
operator(*args[, id])Create a
SubroutineOpfrom the template.setup_inputs(*args, **kwargs)Perform and initial setup of the arguments.