qml.op_transform¶
-
class
op_transform
(*args, **kwargs)[source]¶ Bases:
object
Convert a function that applies to operators into a functional transform.
This allows the operator function to be used across PennyLane on both instantiated operators as well as quantum functions.
By default, this decorator creates functional transforms that accept a single operator. However, you can also register how the transform acts on multiple operators. Once this is defined, the transform can be used anywhere in PennyLane — at the operator level for operator arithmetic, or at the qfunc/QNode level.
Warning
This is an experimental feature, and is subject to change.
- Parameters
fn (function) – The function to register as the operator transform. It can have an arbitrary number of arguments, but the first argument must be the input operator.
Example
Consider an operator function that computes the trace of an operator:
@qml.op_transform def trace(op): try: return qml.math.real(qml.math.sum(op.eigvals())) except qml.operation.EigvalsUndefinedError: return qml.math.real(qml.math.trace(op.matrix()))
We can use this function as written:
>>> op = qml.RX(0.5, wires=0) >>> trace(op) 1.9378248434212895
By using the
op_transform
decorator, we also enable it to be used as a functional transform:>>> trace(qml.RX)(0.5, wires=0) 1.9378248434212895
Note that if we apply our function to an operation that does not define its matrix or eigenvalues representation, we get an error:
>>> weights = np.array([[[0.7, 0.6, 0.5], [0.1, 0.2, 0.3]]]) >>> trace(qml.StronglyEntanglingLayers(weights, wires=[0, 1])) pennylane.operation.EigvalsUndefinedError During handling of the above exception, another exception occurred: pennylane.operation.MatrixUndefinedError
The most powerful reason for using
op_transform
is the ability to define how the transform behaves if applied to a datastructure that supports multiple operations, such as a qfunc, tape, or QNode.We do this by defining a tape transform:
@trace.tape_transform def trace(tape): tr = qml.math.trace(qml.matrix(tape)) return qml.math.real(tr)
We can now apply this transform directly to a qfunc:
>>> def circuit(x, y): ... qml.RX(x, wires=0) ... qml.Hadamard(wires=1) ... qml.CNOT(wires=[0, 1]) ... qml.CRY(y, wires=[1, 0]) >>> trace(circuit)(0.1, 0.8) 1.4124461636742214
Our example above, applying our function to an operation that does not define the matrix or eigenvalues, will now work, since PennyLane will decompose the operation automatically into multiple operations:
>>> trace(qml.StronglyEntanglingLayers)(weights, wires=[0, 1]) 0.4253851061350833
Note
If the operator transform takes additional (optional) transform parameters, then the registered tape transform should take the same transform parameters.
E.g., consider a transform that takes the transform parameter
lower
:@qml.op_transform def name(op, lower=True): return op.name().lower() if lower else op.name() @name.tape_transform def name(tape, lower=True): return [name(op, lower=lower) for op in tape.operations]
If the transformation has purely quantum output, we can register the tape transformation as a qfunc transformation in addition:
@qml.op_transform def simplify_rotation(op): if op.name == "Rot": params = op.parameters wires = op.wires if qml.math.allclose(params, 0): return if qml.math.allclose(params[1:2], 0): return qml.RZ(params[0], wires) return op @simplify_rotation.tape_transform @qml.qfunc_transform def simplify_rotation(tape): for op in tape: if op.name == "Rot": simplify_rotation(op) else: qml.apply(op)
We can now use this combined operator and quantum function transform in compilation pipelines:
@qml.qnode(dev) @qml.compile(pipeline=[simplify_rotation]) def circuit(weights): ansatz(weights) qml.CNOT(wires=[0, 1]) qml.Rot(0.0, 0.0, 0.0, wires=0) return qml.expval(qml.PauliX(1))
Attributes
Returns
True
if the operator transform is also a qfunc transform.-
is_qfunc_transform
¶ Returns
True
if the operator transform is also a qfunc transform. That is, it maps one or more quantum operations to one or more quantum operations, allowing the output of the transform to be used as a quantum function.See also
- Type
bool
Methods
fn
(obj, *args, **kwargs)Evaluate the underlying operator transform function.
tape_fn
(obj, *args, **kwargs)The tape transform function.
tape_transform
(fn)Register a tape transformation to enable the operator transform to apply to datastructures containing multiple operations, such as QNodes, qfuncs, and tapes.
-
fn
(obj, *args, **kwargs)[source]¶ Evaluate the underlying operator transform function.
If a corresponding tape transform for the operator has been registered using the
op_transform.tape_transform
decorator, then if an exception is raised while calling the transform function, this method will attempt to decompose the provided object for the tape transform.- Parameters
obj (Operator, pennylane.QNode, QuantumTape, or Callable) – An operator, quantum node, tape, or function that applies quantum operations.
*args – positional arguments to pass to the function
**kwargs – keyword arguments to pass to the function
- Returns
the result of evaluating the transform
- Return type
any
-
tape_fn
(obj, *args, **kwargs)[source]¶ The tape transform function.
This is the function that is called if a datastructure is passed that contains multiple operations.
- Parameters
obj (pennylane.QNode, QuantumTape, or Callable) – A quantum node, tape, or function that applies quantum operations.
*args – positional arguments to pass to the function
**kwargs – keyword arguments to pass to the function
- Returns
the result of evaluating the transform
- Return type
any
- Raises
OperationTransformError – if no tape transform function is defined
See also
-
tape_transform
(fn)[source]¶ Register a tape transformation to enable the operator transform to apply to datastructures containing multiple operations, such as QNodes, qfuncs, and tapes.
Note
The registered tape transform should have the same parameters as the original operation transform function.
Note
If the transformation maps a tape to a tape (or equivalently, a qfunc to a qfunc) then the transformation is simultaneously a
qfunc_transform()
, and can be declared as such. This enables additional functionality, for example the ability to use the transform in a compilation pipeline.- Parameters
fn (callable) – The function to register as the tape transform. This function should accept a
QuantumTape
as the first argument.
Example
@qml.op_transform def name(op, lower=False): if lower: return op.name.lower() return op.name @name.tape_transform def name(tape, lower=True): return [name(op, lower=lower) for op in tape.operations]
We can now use this function on a qfunc, tape, or QNode:
>>> def circuit(x, y): ... qml.RX(x, wires=0) ... qml.Hadamard(wires=1) ... qml.CNOT(wires=[0, 1]) ... qml.CRY(y, wires=[1, 0]) >>> name(circuit, lower=True)(0.1, 0.8) ['rx', 'hadamard', 'cnot', 'cry']
If the transformation has purely quantum output, we can register the tape transformation as a qfunc transformation in addition:
@qml.op_transform def simplify_rotation(op): if op.name == "Rot": params = op.parameters wires = op.wires if qml.math.allclose(params, 0): return if qml.math.allclose(params[1:2], 0): return qml.RZ(params[0], wires) return op @simplify_rotation.tape_transform @qml.qfunc_transform def simplify_rotation(tape): for op in tape: if op.name == "Rot": simplify_rotation(op) else: qml.apply(op)
We can now use this combined operator and quantum function transform in compilation pipelines:
@qml.qnode(dev) @qml.compile(pipeline=[simplify_rotation]) def circuit(weights): ansatz(weights) qml.CNOT(wires=[0, 1]) qml.Rot(0.0, 0.0, 0.0, wires=0) return qml.expval(qml.PauliX(1))