qp.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. This argument is for use when applying gradient transforms internally before execution, and is not needed when defining compilation pipelines to decorate a QNode.

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 = qp.CompilePipeline(
    qp.transforms.commute_controlled,
    qp.transforms.cancel_inverses(recursive=True),
    qp.transforms.merge_rotations,
)
# Add a marker for inspectability
pipeline.add_marker("no-transforms", 0)

@pipeline
@qp.qnode(qp.device("default.qubit"))
def circuit(x, y):
    qp.CNOT([1, 0])
    qp.X(0)
    qp.CNOT([1, 0])
    qp.H(0)
    qp.H(0)
    qp.X(0)
    qp.RX(x, wires=0)
    qp.RX(y, wires=0)
    return qp.expval(qp.Z(1))
>>> from pennylane.workflow import get_compile_pipeline
>>> print(get_compile_pipeline(circuit)(0.1, 0.2))
CompilePipeline(
   ├─▶ no-transforms
  [1] commute_controlled(),
  [2] cancel_inverses(recursive=True),
  [3] merge_rotations()
)
>>> print(qp.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(qp.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 = qp.transforms.merge_rotations + qp.transforms.cancel_inverses(recursive=True)
>>> print(pipeline)
CompilePipeline(
  [1] merge_rotations(),
  [2] cancel_inverses(recursive=True)
)

Or multiplied by a scalar via *:

>>> pipeline += 2 * qp.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, qp.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 = qp.decompose(gate_set={qp.RX, qp.RZ, qp.CNOT}) + qp.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 = qp.transforms.merge_rotations + qp.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 = qp.transforms.commute_controlled + qp.transforms.cancel_inverses + qp.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 + qp.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 += qp.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 qp.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.