qml.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.
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 = 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>
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 = 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,extendandpop:>>> 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() )
Inspecting and Marking
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
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 + 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 )
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 += 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 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.