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 and quantum functions; such transformations can map a circuit to one or many new circuits alongside associated classical post-processing.

transform([tape_transform, pass_name, ...])

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

Compile Pipeline

Multiple transforms can be chained together into a compile pipeline. See Composability of transforms for more details.

CompilePipeline(*transforms[, cotransform_cache])

A sequence of transforms to be applied to a quantum function or a QNode.

Transforms library

A collection 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, num_passes])

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

cancel_inverses(tape[, recursive])

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

combine_global_phases(tape)

Combine all qml.GlobalPhase gates into a single qml.GlobalPhase operation.

commute_controlled(tape[, direction])

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

decompose(tape, *[, gate_set, ...])

Decomposes a quantum circuit into a user-specified gate set.

merge_amplitude_embedding(tape)

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

merge_rotations(tape[, atol, include_gates])

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

pattern_matching_optimization(tape, ...[, ...])

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

remove_barrier(tape)

Quantum transform to remove Barrier gates.

match_relative_phase_toffoli(tape)

Replace 4-qubit relative phase Toffoli gates with their equivalent circuit.

match_controlled_iX_gate(tape[, num_controls])

Quantum transform to replace controlled iX gates with their equivalent circuit.

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

transpile(tape, coupling_map[, device])

Transpile a circuit according to a desired coupling map

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.

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.

rz_phase_gradient(tape, angle_wires, ...)

Quantum function transform to decompose all instances of RZ gates into additions using a phase gradient resource state.

Compilation transforms using ZX calculus

There is a set of transforms that use ZX calculus to optimize circuits.

zx.optimize_t_count(tape)

Reduce the number of T gates in a Clifford + T circuit by using basic commutation and cancellation rules, combined with a dedicated phase-polynomial optimization strategy based on the Third Order Duplicate and Destroy (TODD) algorithm.

zx.push_hadamards(tape)

Push Hadamard gates as far as possible to one side to cancel them and create fewer larger phase-polynomial blocks, improving the effectiveness of phase-polynomial optimization techniques.

zx.reduce_non_clifford(tape)

Reduce the number of non-Clifford gates by applying a combination of phase gadgetization strategies and Clifford gate simplification rules.

zx.todd(tape)

Apply the Third Order Duplicate and Destroy (TODD) algorithm to reduce the number of T gates in a given Clifford + T circuit.

The following utility functions assist when working explicitly with ZX diagrams, for example when writing custom ZX compilation passes. Also see the section on intermediate representations below.

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.

Other compilation utilities

There are additional 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.

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.

Transforms for Clifford+T decomposition and Pauli-based computation

These transforms accept quantum circuits and decomposes them to the Clifford+T basis.

clifford_t_decomposition(tape[, epsilon, ...])

Decomposes a circuit into the Clifford+T basis.

gridsynth(tape, *[, epsilon, ppr_basis])

Decomposes RZ and PhaseShift gates into the Clifford+T basis or the PPR basis.

ppm_compilation([tape, decompose_method, ...])

A quantum compilation pass that transforms Clifford+T gates into Pauli product measurements (PPMs).

to_ppr(tape)

A quantum compilation pass that converts Clifford+T gates into Pauli Product Rotation (PPR) gates.

commute_ppr(tape, *[, max_pauli_size])

A quantum compilation pass that commutes Clifford Pauli product rotation (PPR) gates, \(\exp(-{iP\tfrac{\pi}{4}})\), past non-Clifford PPRs gates, \(\exp(-{iP\tfrac{\pi}{8}})\), where \(P\) is a Pauli word.

ppr_to_ppm([tape, decompose_method, ...])

A quantum compilation pass that decomposes Pauli product rotations (PPRs), \(P(\theta) = \exp(-iP\theta)\), into Pauli product measurements (PPMs).

merge_ppr_ppm([tape, max_pauli_size])

A quantum compilation pass that absorbs Clifford Pauli product rotation (PPR) operations, \(\exp{-iP\tfrac{\pi}{4}}\), into the final Pauli product measurements (PPMs).

reduce_t_depth(qnode)

A quantum compilation pass that reduces the depth and count of non-Clifford Pauli product rotation (PPR, \(P(\theta) = \exp(-iP\theta)\)) operators (e.g., T gates) by commuting PPRs in adjacent layers and merging compatible ones (a layer comprises PPRs that mutually commute).

decompose_arbitrary_ppr(qnode)

A quantum compilation pass that decomposes arbitrary-angle Pauli product rotations (PPRs) into a collection of PPRs (with angles of rotation of \(\tfrac{\pi}{2}\), \(\tfrac{\pi}{4}\), and \(\tfrac{\pi}{8}\)), PPMs and a single-qubit arbitrary-angle PPR in the Z basis.

Other transforms

These are additional transforms that are useful for multiple purposes such as 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.

broadcast_expand(tape)

Expand a broadcasted tape into multiple tapes and a function that stacks and squeezes 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.

convert_to_numpy_parameters(tape)

Transforms a circuit to one with purely numpy parameters.

defer_measurements(tape[, ...])

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[, ...])

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.

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.

resolve_dynamic_wires(tape[, zeroed, ...])

Map dynamic wires to concrete values determined by the provided zeroed and any_state registers.

Transforms for intermediate representations

Intermediate representations (IRs) are alternative representations of quantum circuits, typically offering a more efficient classical description for special classes of circuits. The following functions produce intermediate representations of quantum circuits, or use them internally to produce a new quantum circuit:

parity_matrix(circ[, wire_order])

Compute the parity matrix intermediate representation of a CNOT circuit.

parity_synth(tape)

Pass for applying ParitySynth to phase polynomials in a circuit.

phase_polynomial(circ[, wire_order])

Compute the Phase polynomial intermediate representation for circuits consisting of CNOT and RZ gates.

rowcol(tape[, connectivity])

CNOT routing algorithm RowCol.

In addition, there are the following utility functions to traverse a graph:

intermediate_reps.postorder_traverse(tree, ...)

Post-order traverse a tree graph, starting from (but excluding) the node source.

intermediate_reps.preorder_traverse(tree, source)

Pre-order traverse a tree graph, starting from (but excluding) the node source.

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

Transforms developer classes

BoundTransform(transform[, args, kwargs, ...])

A transform with bound inputs.

Transform([tape_transform, pass_name, ...])

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

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.

Transforms can be applied on QNodes using the decorator syntax:

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

@qml.transforms.split_non_commuting
@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)),
    ]

Passing arguments to transforms

We can pass additional arguments to a transform that accepts them, which binds them with the transform, creating a BoundTransform, which can then be applied on a QNode. In the following example, we pass the keyword argument grouping_strategy="wires" to the split_non_commuting() transform, which splits a circuit into tapes measuring groups of commuting observables.

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

@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)),
    ]

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(recursive=True)
@qml.qnode(device=dev)
def circuit(x, y):
    qml.X(wires=0)
    qml.Hadamard(wires=0)
    qml.Hadamard(wires=0)
    qml.X(wires=0)
    qml.RX(x, wires=0)
    qml.RX(y, wires=0)
    return qml.expval(qml.Z(0))

In this example, cancel_inverses is applied first, which will remove the two Hadamard gates and the two Pauli X gates. Subsequently, merge_rotations will be applied, which will merge the two RX rotations into a single RX gate.

Alternatively, multiple transforms can be chained together to create a CompilePipeline:

>>> pipeline = qml.transforms.cancel_inverses(recursive=True) + qml.transforms.merge_rotations
>>> pipeline
CompilePipeline(cancel_inverses, merge_rotations)

The CompilePipeline can also be applied on a QNode, which will transform the circuit with each pass within the pipeline sequentially.

@pipeline
@qml.qnode(device=dev)
def circuit(x, y):
    qml.X(wires=0)
    qml.Hadamard(wires=0)
    qml.Hadamard(wires=0)
    qml.X(wires=0)
    qml.RX(x, wires=0)
    qml.RX(y, wires=0)
    return qml.expval(qml.Z(0))

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 QuantumScript as its primary input and returns a sequence of QuantumScript 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 QuantumScript 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

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

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

    def null_postprocessing(results):
        return results[0]

    return [new_tape], null_postprocessing

The @qml.transform decorator makes it applicable to a QNode:

@remove_rx
@qml.qnode(qml.device("default.qubit"))
def circuit():
    qml.RX(0.5, wires=0)
    qml.RY(0.5, wires=1)
    qml.CNOT([0, 1])
    return qml.expval(qml.Z(0))

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 = tape.copy(operations=operations)

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

    return [tape, new_tape], sum_postprocessing

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. Finally, for a comprehensive overview of transforms and core functionalities, consult the summary above.

Contents

Using PennyLane

Release news

Development

API

Internals