qml.transforms

This subpackage contains PennyLane transforms and their building blocks.

Custom transforms

qml.transform can be used to define custom transformations that work with PennyLane QNodes; such transformations can map a circuit to one or many new circuits alongside associated classical post-processing.

transform(quantum_transform[, …])

Generalizes a function that transforms tapes to work with additional circuit-like objects such as a QNode.

Transforms library

A range of ready-to-use transforms are available in PennyLane.

Transforms for circuit compilation

A set of transforms to perform basic circuit compilation tasks.

compile(tape[, pipeline, basis_set, …])

Compile a circuit by applying a series of transforms to a quantum function.

cancel_inverses(tape)

Quantum function transform to remove any operations that are applied next to their (self-)inverses or adjoint.

commute_controlled(tape[, direction])

Quantum transform to move commuting gates past control and target qubits of controlled operations.

merge_rotations(tape[, atol, include_gates])

Quantum transform to combine rotation gates of the same type that act sequentially.

single_qubit_fusion(tape[, atol, exclude_gates])

Quantum function transform to fuse together groups of single-qubit operations into a general single-qubit unitary operation (Rot).

unitary_to_rot(tape)

Quantum function transform to decomposes all instances of single-qubit and select instances of two-qubit QubitUnitary operations to parametrized single-qubit operations.

merge_amplitude_embedding(tape)

Quantum function transform to combine amplitude embedding templates that act on different qubits.

remove_barrier(tape)

Quantum transform to remove Barrier gates.

undo_swaps(tape)

Quantum function transform to remove SWAP gates by running from right to left through the circuit changing the position of the qubits accordingly.

pattern_matching_optimization(tape, …[, …])

Quantum function transform to optimize a circuit given a list of patterns (templates).

transpile(tape, coupling_map[, device])

Transpile a circuit according to a desired coupling map

There are also utility functions and decompositions available that assist with both transforms, and decompositions within the larger PennyLane codebase.

set_decomposition(custom_decomps, dev[, …])

Context manager for setting custom decompositions.

pattern_matching(circuit_dag, pattern_dag)

Function that applies the pattern matching algorithm and returns the list of maximal matches.

to_zx(tape[, expand_measurements])

This transform converts a PennyLane quantum tape to a ZX-Graph in the PyZX framework.

from_zx(graph[, decompose_phases])

Converts a graph from PyZX to a PennyLane tape, if the graph is diagram-like.

There are also utility functions that take a circuit and return a DAG.

commutation_dag(tape)

Construct the pairwise-commutation DAG (directed acyclic graph) representation of a quantum circuit.

CommutationDAG(tape)

Class to represent a quantum circuit as a directed acyclic graph (DAG).

CommutationDAGNode([op, wires, …])

Class to store information about a quantum operation in a node of the commutation DAG.

Transform for Clifford+T decomposition

This transform accepts quantum circuits and decomposes them to the Clifford+T basis.

clifford_t_decomposition(tape[, epsilon, …])

Decomposes a circuit into the Clifford+T basis.

Transforms for error mitigation

mitigate_with_zne(tape, scale_factors, …)

Mitigate an input circuit using zero-noise extrapolation.

fold_global(tape, scale_factor)

Differentiable circuit folding of the global unitary circuit.

poly_extrapolate(x, y, order)

Extrapolator to \(f(0)\) for polynomial fit.

richardson_extrapolate(x, y)

Polynomial fit where the degree of the polynomial is fixed to being equal to the length of x.

exponential_extrapolate(x, y[, asymptote, eps])

Extrapolate to the zero-noise limit using an exponential model (\(Ae^{Bx} + C\)).

Other transforms

These transforms use the pennylane.transform() function/decorator and can be used on pennylane.tape.QuantumTape, pennylane.QNode. They fulfill multiple purposes like circuit preprocessing, getting information from a circuit, and more.

batch_params(tape[, all_operations])

Transform a QNode to support an initial batch dimension for operation parameters.

batch_input(tape, argnum)

Transform a circuit to support an initial batch dimension for gate inputs.

insert(tape, op, op_args[, position, before])

Insert an operation into specified points in an input circuit.

add_noise(tape, noise_model[, level])

Insert operations according to a provided noise model.

defer_measurements(tape[, reduce_postselected])

Quantum function transform that substitutes operations conditioned on measurement outcomes to controlled operations.

diagonalize_measurements(tape[, …])

Diagonalize a set of measurements into the standard basis.

split_non_commuting(tape[, grouping_strategy])

Splits a circuit into tapes measuring groups of commuting observables.

split_to_single_terms(tape)

Splits any expectation values of multi-term observables in a circuit into single term expectation values for devices that don’t natively support measuring expectation values of sums of observables.

broadcast_expand(tape)

Expand a broadcasted tape into multiple tapes and a function that stacks and squeezes the results.

hamiltonian_expand(tape[, group])

Splits a tape measuring a Hamiltonian expectation into mutliple tapes of Pauli expectations, and provides a function to recombine the results.

sign_expand(tape[, circuit, J, delta, controls])

Splits a tape measuring a (fast-forwardable) Hamiltonian expectation into mutliple tapes of the Xi or sgn decomposition, and provides a function to recombine the results.

sum_expand(tape[, group])

Splits a quantum tape measuring a Sum expectation into multiple tapes of summand expectations, and provides a function to recombine the results.

convert_to_numpy_parameters(tape)

Transforms a circuit to one with purely numpy parameters.

apply_controlled_Q(tape, wires, target_wire, …)

Applies the transform that performs a controlled version of the \(\mathcal{Q}\) unitary defined in this paper.

quantum_monte_carlo(tape, wires, …)

Applies the transform quantum Monte Carlo estimation algorithm.

Transforms that act only on QNodes

These transforms only accept QNodes, and return new transformed functions that compute the desired quantity.

batch_partial(qnode[, all_operations, …])

Create a batched partial callable object from the QNode specified.

draw(qnode[, wire_order, show_all_wires, …])

Create a function that draws the given qnode or quantum function.

draw_mpl(qnode[, wire_order, …])

Draw a qnode with matplotlib

Decorators and utility functions

The following decorators and convenience functions are provided to help build custom QNode, quantum function, and tape transforms:

make_tape(fn[, shots])

Returns a function that generates a qscript from a quantum function without any operation queuing taking place.

create_expand_fn(depth[, stop_at, device, …])

Create a function for expanding a tape to a given depth, and with a specific stopping criterion.

create_decomp_expand_fn(custom_decomps, dev)

Creates a custom expansion function for a device that applies a set of specified custom decompositions.

expand_invalid_trainable(tape[, depth])

Expand out a tape so that it supports differentiation of requested operations.

expand_invalid_trainable_hadamard_gradient(tape)

Expand out a tape so that it supports differentiation of requested operations with the Hadamard test gradient.

expand_multipar(tape[, depth])

Expand out a tape so that all its parametrized operations have a single parameter.

expand_trainable_multipar(tape[, depth])

Expand out a tape so that all its trainable operations have a single parameter.

expand_nonunitary_gen(tape[, depth])

Expand out a tape so that all its parametrized operations have a unitary generator.

Transforms developer functions

TransformContainer, TransformDispatcher, and TransformProgram are developer-facing objects that allow the creation, dispatching, and composability of transforms. If you would like to make a custom transform, refer instead to the documentation of qml.transform.

transform_dispatcher

This module contains the transform dispatcher and the transform container.

transform_program

This module contains the TransformProgram class.

Transforming circuits

A quantum transform is a crucial concept in PennyLane, and refers to mapping a quantum circuit to one or more circuits, alongside a classical post-processing function. Once a transform is registered with PennyLane, the transformed circuits will be executed, and the classical post-processing function automatically applied to the outputs. This becomes particularly valuable when a transform generates multiple circuits, requiring a method to aggregate or reduce the results (e.g., applying the parameter-shift rule or computing the expectation value of a Hamiltonian term-by-term).

Note

For examples of built-in transforms that come with PennyLane, see the Compiling circuits documentation.

Creating your own transform

To streamline the creation of transforms and ensure their versatility across various circuit abstractions in PennyLane, the pennylane.transform() is available.

This decorator registers transforms that accept a QuantumTape as its primary input and returns a sequence of QuantumTape and an associated processing function.

To illustrate the process of creating a quantum transform, let’s consider an example. Suppose we want a transform that removes all RX operations from a given circuit. In this case, we merely need to filter the original QuantumTape and return a new one without the filtered operations. As we don’t require a specific processing function in this scenario, we include a function that simply returns the first and only result.

from pennylane.tape import QuantumScript, QuantumScriptBatch
from pennylane.typing import PostprocessingFn

def remove_rx(tape: QuantumScript) -> tuple[QuantumScriptBatch, PostprocessingFn]:

    operations = filter(lambda op: op.name != "RX", tape.operations)
    new_tape = type(tape)(operations, tape.measurements, shots=tape.shots)

    def null_postprocessing(results):
        return results[0]

    return [new_tape], null_postprocessing

To make your transform applicable to both QNode and quantum functions, you can use the pennylane.transform() decorator.

dispatched_transform = qml.transform(remove_rx)

For a more advanced example, let’s consider a transform that sums a circuit with its adjoint. We define the adjoint of the tape operations, create a new tape with these new operations, and return both tapes. The processing function then sums the results of the original and the adjoint tape. In this example, we use qml.transform in the form of a decorator in order to turn the custom function into a quantum transform.

from pennylane.tape import QuantumScript, QuantumScriptBatch
from pennylane.typing import PostprocessingFn

@qml.transform
def sum_circuit_and_adjoint(tape: QuantumScript) -> tuple[QuantumScriptBatch, PostprocessingFn]:

    operations = [qml.adjoint(op) for op in tape.operation]
    new_tape = type(tape)(operations, tape.measurements, shots=tape.shots)

    def sum_postprocessing(results):
        return qml.sum(results)

    return [tape, new_tape], sum_postprocessing

Composability of transforms

Transforms are inherently composable on a QNode, meaning that transforms with compatible post-processing functions can be successively applied to QNodes. For example, this allows for the application of multiple compilation passes on a QNode to maximize gate reduction before execution.

dev = qml.device("default.qubit", wires=1)

@qml.transforms.merge_rotations
@qml.transforms.cancel_inverses
@qml.qnode(device=dev)
def circuit(x, y):
    qml.Hadamard(wires=0)
    qml.Hadamard(wires=0)
    qml.RX(x, wires=0)
    qml.RY(y, wires=0)
    qml.RZ(y, wires=0)
    qml.RY(x, wires=0)
    return qml.expval(qml.Z(0))

In this example, inverses are canceled, leading to the removal of two Hadamard gates. Subsequently, rotations are merged into a single qml.Rot gate. Consequently, two transforms are successfully applied to the circuit.

Passing arguments to transforms

We can decorate a QNode with @partial(transform_fn, **transform_kwargs) to provide additional keyword arguments to a transform function. In the following example, we pass the keyword argument grouping_strategy="wires" to the split_non_commuting() quantum transform, which splits a circuit into tapes measuring groups of commuting observables.

from functools import partial

dev = qml.device("default.qubit", wires=2)

@partial(qml.transforms.split_non_commuting, grouping_strategy="wires")
@qml.qnode(dev)
def circuit(params):
    qml.RX(params[0], wires=0)
    qml.RZ(params[1], wires=1)
    return [
        qml.expval(qml.X(0)),
        qml.expval(qml.Y(1)),
        qml.expval(qml.Z(0) @ qml.Z(1)),
        qml.expval(qml.X(0) @ qml.Z(1) + 0.5 * qml.Y(1) + qml.Z(0)),
    ]

Additional information

Explore practical examples of transforms focused on compiling circuits in the compiling circuits documentation. For gradient transforms, refer to the examples in the gradients documentation. Discover quantum information transformations in the quantum information documentation. Finally, for a comprehensive overview of transforms and core functionalities, consult the summary above.