qml.compile

compile(tape, pipeline=None, basis_set=None, num_passes=1, expand_depth=5)[source]

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

The default set of transforms includes (in order):

Parameters
  • tape (QNode or QuantumTape or Callable) – A quantum circuit.

  • pipeline (list[Callable]) – A list of tape and/or quantum function transforms to apply.

  • basis_set (list[str]) – A list of basis gates. When expanding the tape, expansion will continue until gates in the specific set are reached. If no basis set is specified, a default of pennylane.ops.__all__ will be used. This decomposes templates and operator arithmetic.

  • num_passes (int) – The number of times to apply the set of transforms in pipeline. The default is to perform each transform once; however, doing so may produce a new circuit where applying the set of transforms again may yield further improvement, so the number of such passes can be adjusted.

  • expand_depth (int) – The depth to use for tape expansion into the basis gates.

Returns

The compiled circuit. The output type is explained in qml.transform.

Return type

qnode (QNode) or quantum function (Callable) or tuple[List[QuantumTape], function]

Example

>>> dev = qml.device('default.qubit', wires=[0, 1, 2])

You can apply the transform directly on a QNode:

@compile
@qml.qnode(device=dev)
def circuit(x, y, z):
    qml.Hadamard(wires=0)
    qml.Hadamard(wires=1)
    qml.Hadamard(wires=2)
    qml.RZ(z, wires=2)
    qml.CNOT(wires=[2, 1])
    qml.RX(z, wires=0)
    qml.CNOT(wires=[1, 0])
    qml.RX(x, wires=0)
    qml.CNOT(wires=[1, 0])
    qml.RZ(-z, wires=2)
    qml.RX(y, wires=2)
    qml.Y(2)
    qml.CY(wires=[1, 2])
    return qml.expval(qml.Z(0))

The default compilation pipeline is applied before execution.

Consider the following quantum function:

def qfunc(x, y, z):
    qml.Hadamard(wires=0)
    qml.Hadamard(wires=1)
    qml.Hadamard(wires=2)
    qml.RZ(z, wires=2)
    qml.CNOT(wires=[2, 1])
    qml.RX(z, wires=0)
    qml.CNOT(wires=[1, 0])
    qml.RX(x, wires=0)
    qml.CNOT(wires=[1, 0])
    qml.RZ(-z, wires=2)
    qml.RX(y, wires=2)
    qml.Y(2)
    qml.CY(wires=[1, 2])
    return qml.expval(qml.Z(0))

Visually, the original function looks like this:

>>> qnode = qml.QNode(qfunc, dev)
>>> print(qml.draw(qnode)(0.2, 0.3, 0.4))
0: ──H──RX(0.40)────╭X──────────RX(0.20)─╭X────┤  <Z>
1: ──H───────────╭X─╰●───────────────────╰●─╭●─┤
2: ──H──RZ(0.40)─╰●──RZ(-0.40)──RX(0.30)──Y─╰Y─┤

We can compile it down to a smaller set of gates using the qml.compile transform.

>>> compiled_qfunc = qml.compile(qfunc)
>>> compiled_qnode = qml.QNode(compiled_qfunc, dev)
>>> print(qml.draw(compiled_qnode)(0.2, 0.3, 0.4))
0: ──H──RX(0.60)─────────────────┤  <Z>
1: ──H─╭X──────────────────╭●────┤
2: ──H─╰●─────────RX(0.30)─╰Y──Y─┤

You can change up the set of transforms by passing a custom pipeline to qml.compile. The pipeline is a list of transform functions. Furthermore, you can specify a number of passes (repetitions of the pipeline), and a list of gates into which the compiler will first attempt to decompose the existing operations prior to applying any optimization transforms.

compiled_qfunc = qml.compile(
    pipeline=[
        partial(qml.transforms.commute_controlled, direction="left"),
        partial(qml.transforms.merge_rotations, atol=1e-6),
        qml.transforms.cancel_inverses
    ],
    basis_set=["CNOT", "RX", "RY", "RZ"],
    num_passes=2
)(qfunc)

compiled_qnode = qml.QNode(compiled_qfunc, dev)

print(qml.draw(compiled_qnode)(0.2, 0.3, 0.4))
0: ──RZ(1.57)──RX(1.57)──RZ(1.57)──RX(0.60)─────────────────────────────────────────────────────
1: ──RZ(1.57)──RX(1.57)──RZ(1.57)─╭X─────────RZ(1.57)─────────────────────────────────────────╭●
2: ──RZ(1.57)──RX(1.57)──RZ(1.57)─╰●─────────RX(0.30)──RZ(1.57)──RY(3.14)──RZ(1.57)──RY(1.57)─╰X

────────────────┤  <Z>
─────────────╭●─┤
───RY(-1.57)─╰X─┤