qml.compile

compile(tape, pipeline=(<function commute_controlled>, <function cancel_inverses>, <function merge_rotations>, <function remove_barrier>), basis_set=None, num_passes=1)[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 (Sequence[TransformDispatcher]) – 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. If an empty basis set (e.g. [], (), or {}) is provided, all operations that can be decomposed will be decomposed.

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

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:

@qml.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_qnode = qml.compile(qnode)
>>> 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_qnode = qml.compile(
    qnode,
    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
)

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─┤