qml.CompilePipeline

class CompilePipeline(*transforms, cotransform_cache=None)[source]

Bases: object

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

Parameters:
  • *transforms (Optional[Sequence[Transform | BoundTransform]]) – A sequence of transforms with which to initialize the program.

  • cotransform_cache (Optional[CotransformCache]) – A named tuple containing the qnode, args, and kwargs required to compute classical cotransforms.

Example:

The CompilePipeline class allows you to chain together multiple quantum function transforms to create custom circuit optimization pipelines.

For example, consider if you wanted to apply the following optimizations to a quantum circuit:

You can specify a transform program (pipeline) by passing these transforms to the CompilePipeline class. By applying the created pipeline directly on a quantum function as a decorator, the circuit will be transformed with each pass within the pipeline sequentially:

pipeline = qml.CompilePipeline(
    qml.transforms.commute_controlled,
    qml.transforms.cancel_inverses(recursive=True),
    qml.transforms.merge_rotations,
)
# Add a marker for inspectibility
pipeline.add_marker("no-transforms", 0)

@pipeline
@qml.qnode(qml.device("default.qubit"))
def circuit(x, y):
    qml.CNOT([1, 0])
    qml.X(0)
    qml.CNOT([1, 0])
    qml.H(0)
    qml.H(0)
    qml.X(0)
    qml.RX(x, wires=0)
    qml.RX(y, wires=0)
    return qml.expval(qml.Z(1))
>>> print(circuit.compile_pipeline)
CompilePipeline(
   ├─▶ no-transforms
  [1] commute_controlled(),
  [2] cancel_inverses(recursive=True),
  [3] merge_rotations()
)
>>> print(qml.draw(circuit, level="no-transforms")(0.1, 0.2)) # or level=0
0: ─╭X──X─╭X──H──H──X──RX(0.10)──RX(0.20)─┤
1: ─╰●────╰●──────────────────────────────┤  <Z>
>>> print(qml.draw(circuit)(0.1, 0.2))
0: ──RX(0.30)─┤
1: ───────────┤  <Z>

Alternatively, the compilation pipeline can be constructed intuitively by combining multiple transforms. For example, the transforms can be added together with +:

>>> pipeline = qml.transforms.merge_rotations + qml.transforms.cancel_inverses(recursive=True)
>>> print(pipeline)
CompilePipeline(
  [1] merge_rotations(),
  [2] cancel_inverses(recursive=True)
)

Or multiplied by a scalar via *:

>>> pipeline += 2 * qml.transforms.commute_controlled
>>> print(pipeline)
CompilePipeline(
  [1] merge_rotations(),
  [2] cancel_inverses(recursive=True),
  [3] commute_controlled(),
  [4] commute_controlled()
)

A compilation pipeline can also be easily modified using operations similar to Python lists, including insert, append, extend and pop:

>>> pipeline.insert(0, qml.transforms.remove_barrier)
>>> print(pipeline)
CompilePipeline(
  [1] remove_barrier(),
  [2] merge_rotations(),
  [3] cancel_inverses(recursive=True),
  [4] commute_controlled(),
  [5] commute_controlled()
)

Additionally, multiple compilation pipelines can be concatenated:

>>> another_pipeline = qml.decompose(gate_set={qml.RX, qml.RZ, qml.CNOT}) + qml.transforms.combine_global_phases
>>> print(another_pipeline + pipeline)
CompilePipeline(
  [1] decompose(gate_set=...),
  [2] combine_global_phases(),
  [3] remove_barrier(),
  [4] merge_rotations(),
  [5] cancel_inverses(recursive=True),
  [6] commute_controlled(),
  [7] commute_controlled()
)

We can create a new pipeline that will do multiple passes of the original with multiplication:

>>> original = qml.transforms.merge_rotations + qml.transforms.cancel_inverses
>>> print(2 * original)
CompilePipeline(
  [1] merge_rotations(),
  [2] cancel_inverses(),
  [3] merge_rotations(),
  [4] cancel_inverses()
)

Let’s create a simple pipeline to inspect,

>>> pipeline = qml.transforms.commute_controlled + qml.transforms.cancel_inverses + qml.transforms.merge_rotations

We can inspect the original pipeline by simply printing it,

>>> print(pipeline)
CompilePipeline(
  [1] commute_controlled(),
  [2] cancel_inverses(),
  [3] merge_rotations()
)

We can add markers (that act as checkpoints) in the pipeline to mark important positions,

>>> pipeline.add_marker("final-transform")
>>> print(pipeline)
CompilePipeline(
  [1] commute_controlled(),
  [2] cancel_inverses(),
  [3] merge_rotations()
   └─▶ final-transform
)
>>> pipeline.add_marker("after-commute-controlled", 1)
>>> print(pipeline)
CompilePipeline(
  [1] commute_controlled(),
   ├─▶ after-commute-controlled
  [2] cancel_inverses(),
  [3] merge_rotations()
   └─▶ final-transform
)

Two different markers can be used to mark the same level, causing them to stack,

>>> pipeline.add_marker("after-merge-rotations")
>>> print(pipeline)
CompilePipeline(
  [1] commute_controlled(),
   ├─▶ after-commute-controlled
  [2] cancel_inverses(),
  [3] merge_rotations()
   └─▶ final-transform, after-merge-rotations
)

A marker’s level (the index of the transform it follows) can be retrieved with,

>>> print(pipeline.get_marker_level("final-transform"))
3
>>> print(pipeline.get_marker_level("after-merge-rotations"))
3

We can remove a marker using the remove_marker method,

>>> pipeline.remove_marker("final-transform")
>>> pipeline.markers
['after-commute-controlled', 'after-merge-rotations']

The pipeline structure and marker placement are represented as follows,

CompilePipeline(
    ├─▶ markers for level 0 (no transforms)
   [1] transform_1(),
    ├─▶ markers for level 1 (after 1st transform)
   [2] transform_2(),
   ...
   [n] transform_n()
    └─▶ markers for level n (after nth transform)
)

Importantly, markers are correctly maintained after pipeline manipulations,

>>> pipeline * 2 # Markers are not duplicated
CompilePipeline(
  [1] <commute_controlled()>,
   ├─▶ after-commute-controlled
  [2] <cancel_inverses()>,
  [3] <merge_rotations()>,
   ├─▶ after-merge-rotations
  [4] <commute_controlled()>,
  [5] <cancel_inverses()>,
  [6] <merge_rotations()>
)
>>> pipeline + qml.transforms.undo_swaps
CompilePipeline(
  [1] <commute_controlled()>,
   ├─▶ after-commute-controlled
  [2] <cancel_inverses()>,
  [3] <merge_rotations()>,
   ├─▶ after-merge-rotations
  [4] <undo_swaps()>
)
>>> pipeline.pop()
<merge_rotations()>
>>> print(pipeline)
CompilePipeline(
  [1] commute_controlled(),
   ├─▶ after-commute-controlled
  [2] cancel_inverses()
   └─▶ after-merge-rotations
)
>>> pipeline[1:] # Get everything after the second transform
CompilePipeline(
   ├─▶ after-commute-controlled
  [1] <cancel_inverses()>
   └─▶ after-merge-rotations
)

has_final_transform

True if the compile pipeline has a terminal transform.

is_informative

True if the compile pipeline is informative.

markers

Retrieve list of markers present in the compiliation pipeline.

has_final_transform

True if the compile pipeline has a terminal transform.

is_informative

True if the compile pipeline is informative.

Returns:

Boolean

Return type:

bool

markers

Retrieve list of markers present in the compiliation pipeline.

add_marker(label[, level])

Add a marker to the compilation pipeline at a given level.

add_transform(transform, *targs, **tkwargs)

Add a transform to the end of the program.

append(transform)

Add a transform to the end of the program.

extend(transforms)

Extend the pipeline by appending transforms from an iterable.

get_marker_level(label)

Retrieve the level corresponding to a marker label.

has_classical_cotransform()

Check if the compile pipeline has some classical cotransforms.

insert(index, transform)

Insert a transform at a given index.

pop([index])

Pop the transform container at a given index of the program.

remove(obj)

In place remove the input containers, specifically, 1.

remove_marker(label)

Remove the marker corresponding to the label.

set_classical_component(qnode, args, kwargs)

Set the classical jacobians and argnums if the transform is hybrid with a classical cotransform.

add_marker(label, level=None)[source]

Add a marker to the compilation pipeline at a given level.

Parameters:
  • label (str) – The label for the marker.

  • level (int | None) – The level position for the marker. If None, the marker will be append to the end of the compilation pipeline.

Raises:

ValueError – If the label corresponds to a protected level, if the label is not unique, or if the level is out of bounds.

Example:

>>> pipeline = CompilePipeline()
>>> pipeline += qml.transforms.merge_rotations
>>> pipeline.add_marker("after-merge-rotations")
>>> print(pipeline)
CompilePipeline(
  [1] merge_rotations()
   └─▶ after-merge-rotations
)
>>> pipeline.add_marker("no-transforms", 0)
>>> print(pipeline)
CompilePipeline(
   ├─▶ no-transforms
  [1] merge_rotations()
   └─▶ after-merge-rotations
)
add_transform(transform, *targs, **tkwargs)[source]

Add a transform to the end of the program.

Note that this should be a function decorated with/called by qml.transform, and not a BoundTransform.

Parameters:
  • transform (Transform) – The transform to add to the compile pipeline.

  • *targs – Any additional arguments that are passed to the transform.

Keyword Arguments:

**tkwargs – Any additional keyword arguments that are passed to the transform.

append(transform)[source]

Add a transform to the end of the program.

Parameters:

transform (Transform or BoundTransform) – A transform represented by its container.

extend(transforms)[source]

Extend the pipeline by appending transforms from an iterable.

Parameters:

transforms (CompilePipeline, or Sequence[BoundTransform | Transform]) – A CompilePipeline or an iterable of transforms to append.

get_marker_level(label)[source]

Retrieve the level corresponding to a marker label.

has_classical_cotransform()[source]

Check if the compile pipeline has some classical cotransforms.

Returns:

Boolean

Return type:

bool

insert(index, transform)[source]

Insert a transform at a given index.

Parameters:
  • index (int) – The index to insert the transform.

  • transform (transform or BoundTransform) – the transform to insert

pop(index=-1)[source]

Pop the transform container at a given index of the program.

Parameters:

index (int) – the index of the transform to remove.

Returns:

The removed transform.

Return type:

BoundTransform

remove(obj)[source]

In place remove the input containers, specifically, 1. if the input is a Transform, remove all containers matching the transform; 2. if the input is a BoundTransform, remove all containers exactly matching the input.

Parameters:

obj (BoundTransform or Transform) – The object to remove from the program.

remove_marker(label)[source]

Remove the marker corresponding to the label.

set_classical_component(qnode, args, kwargs)[source]

Set the classical jacobians and argnums if the transform is hybrid with a classical cotransform.