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.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))

 is_qfunc_transform 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.

Type

bool

 fn(obj, *args, **kwargs) Evaluate the underlying operator transform function. tape_fn(obj, *args, **kwargs) The tape transform function. 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

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.CNOT(wires=[0, 1])
...     qml.CRY(y, wires=[1, 0])
>>> name(circuit, lower=True)(0.1, 0.8)


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))