Pass Managers
qiskit.passmanager
As of Qiskit v2.6, there are two separate non-interacting pass-manager systems defined in Qiskit, and Qiskit is beginning to transition away from the pure-Python system. The original, earlier pass-manager infrastructure is discussed in Original Python-native pass manager. The new, Rust-native system, which supports multiple IRs and natively handles interoperation with C and Rust code is discussed in Multi-IR lowering pass manager.
Original Python-native pass manager
Overview
The Qiskit pass manager is inspired by the LLVM compiler. The compiler infrastructure separates responsibilities into three main components: tasks, flow controllers, and pass managers.
A compilation pipeline executes a sequence of Task objects, each of which takes an intermediate representation (IR) as input, performs work, and returns a, possibly different, IR as output. Where Task defines the interface, an atomic task is a pass, which subclasses GenericPass and implements its abstract run() method. This is the class that should be used as base class when implementing a custom compiler pass.
Flow controllers provide execution models for a set of tasks. The simplest flow controller is a FlowControllerLinear, which simply executes a set of tasks in a linear sequence. More advanced flow controllers include loops or conditional execution. These are, for example, used in Qiskit’s preset transpiler pipelines for higher optimization levels where optimizations are run until a convergence criterion is met.
Pass managers are responsible for managing the tasks, including scheduling required analyses and enabling modification of the task sequence by the user. Qiskit provides two IR-generic pass managers in this module, and a pass manager specialized to DAGCircuit as IR in qiskit.transpiler. The IR-generic ones are:
-
BasePassManager: a pass manager with fixed IR. This pass manager allows modifying the set of tasks to be run and supports parallel execution of multiple inputs by means ofparallel_map(). This class has support for additional conversion of an input program representation to the internal IR, and a conversion to an output program format.The
BasePassManageris the base class for Qiskit’s preset pass managers forDAGCircuittranspilation, such as returned bygenerate_preset_pass_manager(). There, implicit conversions to and fromQuantumCircuitas input and output program format are used. -
MultiStagePassManager: a staged pass manager where each stage can preserve or lower the IR. A stage is defined by aTaskor an iterable thereof, which can also be grouped inside aBasePassManager. The stages must be set up such that the output IR of the current stage matches the input IR of the next stage, there are (currently) no automatic translations.
Pass managers also provide infrastructure to pass a PropertySet with context-information through every task and a callback function for introspection. The PropertySet is a free-form dictionary, which can be populated and read by a pass during execution, or read by a flow-controller to control pass execution. The property set is portable and handed over from pass to pass at execution. In addition to the property set, tasks also receive a WorkflowStatus data structure. This object is initialized when the pass manager is run and handed over to underlying tasks. The status is updated after every pass is run, and contains information about the pipeline state (number of passes run, failure state, and so on) as opposed to the PropertySet, which contains information about the IR being optimized.
The callback is called by GenericPass instances expecting the following signature:
def callback(
*,
task: Task[IR_IN, IR_OUT],
passmanager_ir: IR_OUT,
property_set: PropertySet,
running_time: float,
count: int
) -> None:
...Note that this signature differs slightly for passes and pass managers defined in the qiskit.transpiler module.
Examples
We look into a toy optimization task, namely, preparing a row of numbers and removing a digit if the number is five. Such a task might be easily done by converting the input numbers into string. We use the pass manager framework here, putting the efficiency aside for a moment to learn how to build a custom Qiskit compiler.
from qiskit.passmanager import BasePassManager, GenericPass, ConditionalController
class ToyPassManager(BasePassManager):
def _passmanager_frontend(self, input_program: int, **kwargs) -> str:
return str(input_program)
def _passmanager_backend(self, passmanager_ir: str, in_program: int, **kwargs) -> int:
return int(passmanager_ir)This pass manager inputs and outputs an integer number, while performing the optimization tasks on a string data. Hence, input, IR, output type are integer, string, integer, respectively. The _passmanager_frontend() method defines the conversion from the input data to IR, and _passmanager_backend() defines the conversion from the IR to output data. The pass manager backend is also given an in_program parameter that contains the original input_program to the front end, for referencing any original metadata of the input program for the final conversion.
Next, we implement a pass that removes a digit when the number is five.
class RemoveFive(GenericPass):
def run(self, passmanager_ir: str):
return passmanager_ir.replace("5", "")
task = RemoveFive()Finally, we instantiate a pass manager and schedule the task with it. Running the pass manager with a random row of numbers returns new numbers that don’t contain five.
pm = ToyPassManager()
pm.append(task)
pm.run([123456789, 45654, 36785554])Output:
[12346789, 464, 36784]Now we consider the case of conditional execution. We avoid execution of the “remove five” task when the input number is six digits or less. Such control can be implemented by a flow controller. We start from an analysis pass that provides the flow controller with information about the number of digits.
class CountDigits(GenericPass):
def run(self, passmanager_ir: str):
self.property_set["ndigits"] = len(passmanager_ir)
analysis_task = CountDigits()Then, we wrap the remove five task with the ConditionalController that runs the stored tasks only when the condition is met.
def digit_condition(property_set):
# Return True when condition is met.
return property_set["ndigits"] > 6
conditional_task = ConditionalController(
tasks=[RemoveFive()],
condition=digit_condition,
)As before, we schedule these passes with the pass manager and run.
pm = ToyPassManager()
pm.append(analysis_task)
pm.append(conditional_task)
pm.run([123456789, 45654, 36785554])Output:
[12346789, 45654, 36784]The “remove five” task is triggered only for the first and third input values, which have more than six digits.
With the pass manager framework, a developer can flexibly customize the optimization task by combining multiple passes and flow controllers. See details in the following class API documentation.
Interface
Passes
Column 1 | Column 2 |
|---|---|
GenericPass() | Base class of a single pass manager task. |
Task() | An interface of the pass manager task. |
Pass managers
Column 1 | Column 2 |
|---|---|
BasePassManager([tasks, max_iteration]) | Pass manager base class. |
MultiStagePassManager(**stages) | A staged pass manager supporting multiple IRs. |
Flow controllers
Column 1 | Column 2 |
|---|---|
BaseController([options]) | Base class of controller. |
FlowControllerLinear([tasks, options]) | A standard flow controller that runs tasks one after the other. |
ConditionalController([tasks, condition, ...]) | A flow controller runs the pipeline once if the condition is true, or does nothing if the condition is false. |
DoWhileController([tasks, do_while, options]) | Run the given tasks in a loop until the do_while condition on the property set becomes False. |
Compilation state
Column 1 | Column 2 |
|---|---|
PropertySet | A default dictionary-like object. |
WorkflowStatus([count, completed_passes, ...]) | Collection of compilation status of workflow, i.e. pass manager run. |
PassManagerState(workflow_status, property_set) | A portable container object that pass manager tasks communicate through generator. |
Exceptions
PassManagerError
exception qiskit.passmanager.PassManagerError(*message)
Bases: QiskitError
Pass manager error.
Set the error message.
Multi-IR lowering pass manager
The “new” Qiskit pass manager is LoweringPassManager. This is newly designed, with the intention to permit native interoperation between compiled code and Python, and to permit a pipeline to lower through a series of intermediate representations (IRs).
From a user perspective, you will create a LoweringPassManager, populate it with a series of Pass instances, each of which defines which IR it accepts and returns, and then simply run() the pipeline. Alternatively, you may receive a LoweringPassManager from a constructor function, which builds a preconfigured pipeline for you.
LoweringPassManager
final class qiskit.passmanager.LoweringPassManager(tasks=())
Bases: object
A multi-step pass manager that may lower through several IRs.
Internally, this holds a handle to a low-level Rust-native pass manager. This class then provides a Pythonic interface for interacting with the base object. The corresponding object of the Rust-native component in the C API is QkPassManager.
Due to internal limitations, users must ensure that this object does not participate in any Python reference cycles; the Python garbage collector cannot fully traverse this object. We intend to lift this restriction in the future.
Parameters
tasks (Iterable[Pass]) – any tasks with which to initialize the pass manager. Passing this is equivalent to calling append() in a loop with the arguments.
Developers will need to implement passes, and potentially introduce new IRs. Both of these base objects define their subclass interfaces.
IR
class qiskit.passmanager.IR
Bases: object
An interface for objects that can be used as IRs by a Pass in a LoweringPassManager.
Note that all methods named _qiskit_*_ are interface methods that are only for Qiskit to call. These are documented for implementers, but are generally not stable for users to call; Qiskit may allow implementations to fulfill one of several contracts for the method, and additional alternatives may be added in new versions. Qiskit will maintain stability within its own use of these protocols, but they are not stable for public consumption.
Implementers of subclasses should override the attributes and methods, and are free to change the methods and signatures in line with this documentation about per-Qiskit-version calling signatures.
Qiskit reserves the attribute and method namespace _qiskit_ir_*_ for future expansion of this interface. Qiskit will not define any attributes or methods in the interface outside this namespace.
The native IR system does not have a concept of Python’s subclasses; an object is identified as being of at most one IR type, which is the first type in its resolution order that sets _qiskit_ir_base_, or the sole direct subclass of IR if there is only one.
_qiskit_ir_base_
Type: ClassVar[type[IR] | None]
Default value: None
Mark a subclass of an existing IR as a new intermediate representation.
You only very rarely need to set this.
There are three cases to think about:
-
You define a new class that subclasses
IRdirectly, and none of its supertypes are instances ofIR(the most common case).You do not need to set this attribute, but may explicitly set it to
None(these have the same meaning). -
You define a new class that subclasses an existing implementation of
IRfor reasons unrelated to theIRsystem. You want instances of your subclass to be treated by the pass-manager system as if they’re compatible with the baseIR.You must not set this attribute (nor override any other attributes or methods).
-
You define a new class that subclasses an existing implementation of
IR, and you want it to be considered as a new IR.You must set this attribute to
None.
After class creation, the value of this class variable will be set to the resolved base IR implementer.
_qiskit_ir_name_
Pass
class qiskit.passmanager.Pass
Bases: Generic[IRIn, IROut], ABC
An interface for defining passes over IRs.
This is primarily an implementation interface. The typical way to safely consume a Pass is to put it into a LoweringPassManager, and call the pass manager’s methods.
Note that all methods named _qiskit_*_ are interface methods that are only for Qiskit to call. These are documented for implementers, but are generally not stable for users to call; Qiskit may allow implementations to fulfill one of several contracts for the method, and additional alternatives may be added in new versions. Qiskit will maintain stability within its own use of these protocols, but they are not stable for public consumption.
Implementers of subclasses should override the methods, and are free to change the methods and signatures in line with this documentation about per-Qiskit-version calling signatures.
Qiskit reserves the attribute and method namespace _qiskit_pass_*_ for future expansion of this interface. Qiskit will not define any attributes or methods in the interface outside this namespace.
_qiskit_pass_ir_in_
_qiskit_pass_ir_out_
_qiskit_pass_name_
Type: str
Default value: ''
A human-readable name for the pass. Optional.
This is provided primarily for debugging purposes in pipelines.
_qiskit_pass_run_
abstractmethod _qiskit_pass_run_(ir, context)
Run the pass on the given IR, returning the next IR object.
The pass “owns” the ir object that comes in, and can do anything it likes with it; passes can assume they hold the only reference to the object. However, the execution environment of passes also assumes that it has sole ownership of the output in a similar manner. This means that you cannot “leak” references to the value returned outside the function; if you store references anywhere, they may see nonsense but valid data after this method has completed, or cause the execution pipeline to raise an access error. If you want to leak out the data, you will need to ensure you produce copies.
Parameters
- ir (IRIn) – the input IR to run the pass on. This can be assumed to be of the correct type as specified by the left generic in the subclass.
- context (PassContextHandle) – a context object for interaction with the executing pipeline. See the type documentation of
PassContextHandlefor more information.
Returns
The next IR object to use. This must match the specified right generic from the subclass of Pass (or the only generic, if used in single-argument form).
Raises
PassError – when the pass encounters a compilation-specific error and wants to provide structured error messages back to the user.
Return type
IROut
Implementers of Pass get a “handle” object, which can be used to interact with the pipeline execution context.
PassContextHandle
final class qiskit.passmanager.PassContextHandle(native)
Bases: object
The execution context for a given pass in a lowering pipeline.
This object is created within the Rust components of the pass manager; you do not need to instantiate one yourself.
This object is given to the Pass._qiskit_pass_run_() method, and passes may use its methods to interact with the execution context. The object is specific to a single call of _qiskit_pass_run_(); its data is invalidated once that method has returned, and attempts to use it will just raise exceptions.
You should not store or leak instances of this object anywhere.
Parameters
native (passmanager.PassContextHandle)
There are two exceptions associated with the lowering pass-manager infrastructure:
PassError
final exception qiskit.passmanager.PassError(*message)
Bases: PassManagerError
Raised by implementers of Pass._qiskit_pass_run_() to provide user-facing errors.
Set the error message.
LoweringPassManagerError
final exception qiskit.passmanager.LoweringPassManagerError(*message)
Bases: PassManagerError
Raised by LoweringPassManager.run() when compilation errors have occurred.
Set the error message.