qp.CompilePipeline¶
- class CompilePipeline(*transforms, cotransform_cache=None)[source]¶
Bases:
objectA 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, andkwargsrequired 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
CompilePipelineclass 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:
pushing all commuting single-qubit gates as far right as possible (
commute_controlled())cancellation of adjacent inverse gates (
cancel_inverses())merging adjacent rotations of the same type (
merge_rotations())
You can specify a transform program (
pipeline) by passing these transforms to theCompilePipelineclass. By applying the createdpipelinedirectly 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>
Manipulating Compilation Pipelines
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,extendandpop:>>> 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() )
Inspecting and Marking
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
markerusing theremove_markermethod,>>> 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 )
Attributes
Trueif the compile pipeline has a terminal transform.Trueif the compile pipeline is informative.Retrieve list of markers present in the compiliation pipeline.
- has_final_transform¶
Trueif the compile pipeline has a terminal transform.
- is_informative¶
Trueif the compile pipeline is informative.- Returns:
Boolean
- Return type:
bool
- markers¶
Retrieve list of markers present in the compiliation pipeline.
Methods
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.
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 aBoundTransform.- 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.
- 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:
- 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.