Transpiler Passes and Pass Manager

Introduction

A central component of Qiskit Terra is the transpiler, which is designed for modularity and extensibility. The goal is to be able to easily write new circuit transformations (known as transpiler passes), and combine them with other existing passes. Which passes are chained together and in which order has a major effect on the final outcome. This pipeline is determined by a pass manager, which schedules the passes and also allows passes to communicate with each other by providing a shared space. In this way, the transpiler opens up the door for research into aggressive optimization of quantum circuits.

In this notebook, we look at the built-in passes, how to use the pass manager, and develop a simple custom transpiler pass. In order to do the latter, we first need to introduce the internal representation of quantum circuits in Qiskit, in the form of a Directed Acyclic Graph, or DAG. Then, we illustrate a simple swap mapper pass, which transforms an input circuit to be compatible with a limited-connectivity quantum device.

[1]:
%matplotlib inline
from qiskit import QuantumCircuit
from qiskit.compiler import transpile
from qiskit.transpiler import PassManager

PassManager object

Lets you specify the set of passes you want.

[2]:
circ = QuantumCircuit(3)
circ.ccx(0, 1, 2)
circ.draw(output='mpl')
[2]:
../../_images/tutorials_circuits_advanced_4_transpiler_passes_and_passmanager_5_0.png
[3]:
from qiskit.transpiler.passes import Unroller
pass_ = Unroller(['u1', 'u2', 'u3', 'cx'])
pm = PassManager(pass_)
new_circ = pm.run(circ)
new_circ.draw(output='mpl')
[3]:
../../_images/tutorials_circuits_advanced_4_transpiler_passes_and_passmanager_6_0.png

All of Qiskit’s transpiler passes are accessible from qiskit.transpiler.passes.

[4]:
from qiskit.transpiler import passes
[pass_ for pass_ in dir(passes) if pass_[0].isupper()]
[4]:
['ApplyLayout',
 'BarrierBeforeFinalMeasurements',
 'BasicSwap',
 'CSPLayout',
 'CXCancellation',
 'CXDirection',
 'CheckCXDirection',
 'CheckMap',
 'Collect2qBlocks',
 'CommutationAnalysis',
 'CommutativeCancellation',
 'ConsolidateBlocks',
 'CountOps',
 'CountOpsLongestPath',
 'CrosstalkAdaptiveSchedule',
 'DAGFixedPoint',
 'DAGLongestPath',
 'Decompose',
 'DenseLayout',
 'Depth',
 'EnlargeWithAncilla',
 'FixedPoint',
 'FullAncillaAllocation',
 'HoareOptimizer',
 'Layout2qDistance',
 'LayoutTransformation',
 'LookaheadSwap',
 'MergeAdjacentBarriers',
 'NoiseAdaptiveLayout',
 'NumTensorFactors',
 'Optimize1qGates',
 'OptimizeSwapBeforeMeasure',
 'RemoveDiagonalGatesBeforeMeasure',
 'RemoveFinalMeasurements',
 'RemoveResetInZeroState',
 'ResourceEstimation',
 'SetLayout',
 'Size',
 'StochasticSwap',
 'TrivialLayout',
 'Unroll3qOrMore',
 'Unroller',
 'Width']

Different Variants of the Same Pass

There can be passes that do the same job, but in different ways. For example, the TrivialLayout, DenseLayout and NoiseAdaptiveLayout all choose a layout (binding of virtual qubits to physical qubits), but use different algorithms and objectives. Similarly, the BasicSwap, LookaheadSwap and StochasticSwap all insert swaps to make the circuit compatible with the coupling map. The modularity of the transpiler allows plug-and-play replacements for each pass.

Below, we show the swapper passes all applied to the same circuit, to transform it to match a linear chain topology. You can see differences in performance, where the StochasticSwap is clearly the best. However, this can vary depending on the input circuit.

[5]:
from qiskit.transpiler import CouplingMap, Layout
from qiskit.transpiler.passes import BasicSwap, LookaheadSwap, StochasticSwap

coupling = [[0, 1], [1, 2], [2, 3], [3, 4], [4, 5], [5, 6]]

circuit = QuantumCircuit(7)
circuit.h(3)
circuit.cx(0, 6)
circuit.cx(6, 0)
circuit.cx(0, 1)
circuit.cx(3, 1)
circuit.cx(3, 0)

coupling_map = CouplingMap(couplinglist=coupling)

bs = BasicSwap(coupling_map=coupling_map)
pass_manager = PassManager(bs)
basic_circ = pass_manager.run(circuit)

ls = LookaheadSwap(coupling_map=coupling_map)
pass_manager = PassManager(ls)
lookahead_circ = pass_manager.run(circuit)

ss = StochasticSwap(coupling_map=coupling_map)
pass_manager = PassManager(ss)
stochastic_circ = pass_manager.run(circuit)
[6]:
circuit.draw(output='mpl')
[6]:
../../_images/tutorials_circuits_advanced_4_transpiler_passes_and_passmanager_11_0.png
[7]:
basic_circ.draw(output='mpl')
[7]:
../../_images/tutorials_circuits_advanced_4_transpiler_passes_and_passmanager_12_0.png
[8]:
lookahead_circ.draw(output='mpl')
[8]:
../../_images/tutorials_circuits_advanced_4_transpiler_passes_and_passmanager_13_0.png
[9]:
stochastic_circ.draw(output='mpl')
[9]:
../../_images/tutorials_circuits_advanced_4_transpiler_passes_and_passmanager_14_0.png

Preset Pass Managers

Qiskit comes with several pre-defined pass managers, corresponding to various levels of optimization achieved through different pipelines of passes. Currently optimization_level 0 through 3 are supported; the higher the number, the more optimized it is, at the expense of more time. Choosing a good pass manager may take trial and error, as it depends heavily on the circuit being transpiled and the backend being targeted.

Here we illustrate the different levels by looking at a state synthesis circuit. We initialize four qubits to an arbitrary state, and then try to optimize the circuit that achieves this.

  • optimization_level=0: just maps the circuit to the backend, with no explicit optimization (except whatever optimizations the mapper does).

  • optimization_level=1: maps the circuit, but also does light-weight optimizations by collapsing adjacent gates.

  • optimization_level=2: medium-weight optimization, including a noise-adaptive layout and a gate-cancellation procedure based on gate commutation relationships.

  • optimization_level=3: heavy-weight optimization, which in addition to previous steps, does resynthesis of two-qubit blocks of gates in the circuit.

[10]:
import math
from qiskit.test.mock import FakeTokyo

backend = FakeTokyo()     # mimics the tokyo device in terms of coupling map and basis gates
backend.properties = {}   # remove fake properties
[37]:
qc = QuantumCircuit(10)

random_state = [
    1 / math.sqrt(4) * complex(0, 1),
    1 / math.sqrt(8) * complex(1, 0),
    0,
    0,
    0,
    0,
    0,
    0,
    1 / math.sqrt(8) * complex(1, 0),
    1 / math.sqrt(8) * complex(0, 1),
    0,
    0,
    0,
    0,
    1 / math.sqrt(4) * complex(1, 0),
    1 / math.sqrt(8) * complex(1, 0)]

qc.initialize(random_state, range(4))
qc.draw()
[37]:
     »
q_0: »
     »
q_1: »
     »
q_2: »
     »
q_3: »
     »
q_4: »
     »
q_5: »
     »
q_6: »
     »
q_7: »
     »
q_8: »
     »
q_9: »
     »
«     ┌────────────────────────────────────────────────────────────────────────────┐
«q_0: ┤0                                                                           ├
«     │                                                                            │
«q_1: ┤1                                                                           ├
«     │  initialize(0.5j,0.35355,0,0,0,0,0,0,0.35355,0.35355j,0,0,0,0,0.5,0.35355) │
«q_2: ┤2                                                                           ├
«     │                                                                            │
«q_3: ┤3                                                                           ├
«     └────────────────────────────────────────────────────────────────────────────┘
«q_4: ──────────────────────────────────────────────────────────────────────────────
«
«q_5: ──────────────────────────────────────────────────────────────────────────────
«
«q_6: ──────────────────────────────────────────────────────────────────────────────
«
«q_7: ──────────────────────────────────────────────────────────────────────────────
«
«q_8: ──────────────────────────────────────────────────────────────────────────────
«
«q_9: ──────────────────────────────────────────────────────────────────────────────
«                                                                                   

Now map this to the 20-qubit Tokyo device, with different optimization levels:

[12]:
optimized_0 = transpile(qc, backend=backend, seed_transpiler=11, optimization_level=0)
print('gates = ', optimized_0.count_ops())
print('depth = ', optimized_0.depth())
gates =  OrderedDict([('cx', 89), ('u3', 15), ('u1', 15)])
depth =  108
[13]:
optimized_1 = transpile(qc, backend=backend, seed_transpiler=11, optimization_level=1)
print('gates = ', optimized_1.count_ops())
print('depth = ', optimized_1.depth())
gates =  OrderedDict([('cx', 77), ('u3', 15), ('u1', 6)])
depth =  87
[14]:
optimized_2 = transpile(qc, backend=backend, seed_transpiler=11, optimization_level=2)
print('gates = ', optimized_2.count_ops())
print('depth = ', optimized_2.depth())
gates =  OrderedDict([('cx', 67), ('u3', 15), ('u1', 6)])
depth =  77
[15]:
optimized_3 = transpile(qc, backend=backend, seed_transpiler=11, optimization_level=3)
print('gates = ', optimized_3.count_ops())
print('depth = ', optimized_3.depth())
gates =  OrderedDict([('cx', 21), ('u3', 16), ('u2', 5), ('u1', 5)])
depth =  39

Introducing the DAG

In Qiskit, we represent circuits internally using a Directed Acyclic Graph (DAG). The advantage of this representation over a pure list of gates (i.e., netlist) is that the flow of information between operations are explicit, making it easier for passes to make transformation decisions without changing the semantics of the circuit.

Let’s start by building a simple circuit, and examining its DAG.

[16]:
from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit
from qiskit.dagcircuit import DAGCircuit
q = QuantumRegister(3, 'q')
c = ClassicalRegister(3, 'c')
circ = QuantumCircuit(q, c)
circ.h(q[0])
circ.cx(q[0], q[1])
circ.measure(q[0], c[0])
circ.rz(0.5, q[1]).c_if(c, 2)
circ.draw(output='mpl')
[16]:
../../_images/tutorials_circuits_advanced_4_transpiler_passes_and_passmanager_25_0.png

In the DAG, there are three kinds of graph nodes: qubit/clbit input nodes (green), operation nodes (blue), and output nodes (red). Each edge indicates data flow (or dependency) between two nodes.

[17]:
from qiskit.converters import circuit_to_dag
from qiskit.tools.visualization import dag_drawer
dag = circuit_to_dag(circ)
dag_drawer(dag)
[17]:
../../_images/tutorials_circuits_advanced_4_transpiler_passes_and_passmanager_27_0.png

Therefore, writing a transpiler pass means using Qiskit’s DAGCircuit API to analyze or transform the circuit. Let’s see some examples of this.

a. Get all op nodes in the DAG:

[18]:
dag.op_nodes()
[18]:
[<qiskit.dagcircuit.dagnode.DAGNode at 0x1a33f0a5d0>,
 <qiskit.dagcircuit.dagnode.DAGNode at 0x1a33f0a550>,
 <qiskit.dagcircuit.dagnode.DAGNode at 0x1a33f0a4d0>,
 <qiskit.dagcircuit.dagnode.DAGNode at 0x1a33f0a650>]

Each node is an instance of the DAGNode class. Let’s examine the information stored in the second op node.

[19]:
node = dag.op_nodes()[3]
print("node name: ", node.name)
print("node op: ", node.op)
print("node qargs: ", node.qargs)
print("node cargs: ", node.cargs)
print("node condition: ", node.condition)
node name:  rz
node op:  <qiskit.circuit.library.standard_gates.rz.RZGate object at 0x1a33e36510>
node qargs:  [Qubit(QuantumRegister(3, 'q'), 1)]
node cargs:  []
node condition:  (ClassicalRegister(3, 'c'), 2)

b. Add an operation to the back:

[20]:
from qiskit.circuit.library.standard_gates import HGate
dag.apply_operation_back(HGate(), qargs=[q[0]])
dag_drawer(dag)
[20]:
../../_images/tutorials_circuits_advanced_4_transpiler_passes_and_passmanager_34_0.png

c. Add an operation to the front:

[21]:
from qiskit.circuit.library.standard_gates import CCXGate
dag.apply_operation_front(CCXGate(), qargs=[q[0], q[1], q[2]], cargs=[])
dag_drawer(dag)
[21]:
../../_images/tutorials_circuits_advanced_4_transpiler_passes_and_passmanager_36_0.png

d. Substitute a node with a subcircuit:

[22]:
from qiskit.extensions.standard import CHGate, U2Gate, CnotGate
mini_dag = DAGCircuit()
p = QuantumRegister(2, "p")
mini_dag.add_qreg(p)
mini_dag.apply_operation_back(CHGate(), qargs=[p[1], p[0]])
mini_dag.apply_operation_back(U2Gate(0.1, 0.2), qargs=[p[1]])

# substitute the cx node with the above mini-dag
cx_node = dag.op_nodes(op=CnotGate).pop()
dag.substitute_node_with_dag(node=cx_node, input_dag=mini_dag, wires=[p[0], p[1]])
dag_drawer(dag)
/opt/miniconda3/envs/qiskit/lib/python3.7/site-packages/ipykernel_launcher.py:1: DeprecationWarning: The module qiskit.extensions.standard is deprecated as of 0.14.0 and will be removed no earlier than 3 months after the release. You should import the standard gates from qiskit.circuit.library.standard_gates instead.
  """Entry point for launching an IPython kernel.
[22]:
../../_images/tutorials_circuits_advanced_4_transpiler_passes_and_passmanager_38_1.png

Finally, after all transformations are complete, we can convert back to a regular QuantumCircuit object. This is what the transpiler does! It takes a circuit, operates on it in DAG form, and outputs a transformed circuit.

[23]:
from qiskit.converters import dag_to_circuit
circuit = dag_to_circuit(dag)
circuit.draw(output='mpl')
[23]:
../../_images/tutorials_circuits_advanced_4_transpiler_passes_and_passmanager_40_0.png

Implementing a BasicMapper Pass

Now that we are familiar with the DAG, let’s use it to write a transpiler pass. Here we will implement a basic pass for mapping an arbitrary circuit to a device with limited qubit connectivity. We call this the BasicMapper. This pass is included in Qiskit Terra as well.

The first thing to do when writing a transpiler pass is to decide whether the pass class derives from a TransformationPass or AnalysisPass. Transformation passes modify the circuit, while analysis passes only collect information about a circuit (to be used by other passes). Then, the run(dag) method is implemented, which does the main task. Finally, the pass is registered inside the qiskit.transpiler.passes module.

This pass functions as follows: it traverses the DAG layer-by-layer (each layer is a group of operations that does not act on independent qubits, so in theory all operations in a layer can be done independently). For each operation, if it does not already meet the coupling map constraints, the pass identifies a swap path and inserts swaps to bring the two qubits close to each other.

Follow the comments in the code for more details.

[24]:
from copy import copy

from qiskit.transpiler.basepasses import TransformationPass
from qiskit.transpiler import Layout
from qiskit.extensions.standard import SwapGate


class BasicSwap(TransformationPass):
    """
    Maps (with minimum effort) a DAGCircuit onto a `coupling_map` adding swap gates.
    """

    def __init__(self,
                 coupling_map,
                 initial_layout=None):
        """
        Maps a DAGCircuit onto a `coupling_map` using swap gates.
        Args:
            coupling_map (CouplingMap): Directed graph represented a coupling map.
            initial_layout (Layout): initial layout of qubits in mapping
        """
        super().__init__()
        self.coupling_map = coupling_map
        self.initial_layout = initial_layout

    def run(self, dag):
        """
        Runs the BasicSwap pass on `dag`.
        Args:
            dag (DAGCircuit): DAG to map.

        Returns:
            DAGCircuit: A mapped DAG.

        Raises:
            TranspilerError: if the coupling map or the layout are not
            compatible with the DAG
        """
        new_dag = DAGCircuit()

        if self.initial_layout is None:
            if self.property_set["layout"]:
                self.initial_layout = self.property_set["layout"]
            else:
                self.initial_layout = Layout.generate_trivial_layout(*dag.qregs.values())

        if len(dag.qubits()) != len(self.initial_layout):
            raise TranspilerError('The layout does not match the amount of qubits in the DAG')

        if len(self.coupling_map.physical_qubits) != len(self.initial_layout):
            raise TranspilerError(
                "Mappers require to have the layout to be the same size as the coupling map")

        current_layout = self.initial_layout.copy()

        for layer in dag.serial_layers():
            subdag = layer['graph']

            for gate in subdag.twoQ_gates():
                physical_q0 = current_layout[gate.qargs[0]]
                physical_q1 = current_layout[gate.qargs[1]]
                if self.coupling_map.distance(physical_q0, physical_q1) != 1:
                    # Insert a new layer with the SWAP(s).
                    swap_layer = DAGCircuit()

                    path = self.coupling_map.shortest_undirected_path(physical_q0, physical_q1)
                    for swap in range(len(path) - 2):
                        connected_wire_1 = path[swap]
                        connected_wire_2 = path[swap + 1]

                        qubit_1 = current_layout[connected_wire_1]
                        qubit_2 = current_layout[connected_wire_2]

                        # create qregs
                        for qreg in current_layout.get_registers():
                            if qreg not in swap_layer.qregs.values():
                                swap_layer.add_qreg(qreg)

                        # create the swap operation
                        swap_layer.apply_operation_back(SwapGate(),
                                                        qargs=[qubit_1, qubit_2],
                                                        cargs=[])

                    # layer insertion
                    edge_map = current_layout.combine_into_edge_map(self.initial_layout)
                    new_dag.compose_back(swap_layer, edge_map)

                    # update current_layout
                    for swap in range(len(path) - 2):
                        current_layout.swap(path[swap], path[swap + 1])

            edge_map = current_layout.combine_into_edge_map(self.initial_layout)
            new_dag.extend_back(subdag, edge_map)

        return new_dag

Let’s test this pass on a small example circuit.

[25]:
q = QuantumRegister(7, 'q')
in_circ = QuantumCircuit(q)
in_circ.h(q[0])
in_circ.cx(q[0], q[4])
in_circ.cx(q[2], q[3])
in_circ.cx(q[6], q[1])
in_circ.cx(q[5], q[0])
in_circ.rz(0.1, q[2])
in_circ.cx(q[5], q[0])
[25]:
<qiskit.circuit.instructionset.InstructionSet at 0x1a3417a8d0>

Now we construct a pass manager that contains our new pass. We pass the example circuit above to this pass manager, and obtain a new, transformed circuit.

[26]:
from qiskit.transpiler import PassManager
from qiskit.transpiler import CouplingMap
from qiskit import BasicAer
pm = PassManager()
coupling = [[0, 1], [1, 2], [2, 3], [3, 4], [4, 5], [5, 6]]
coupling_map = CouplingMap(couplinglist=coupling)

pm.append([BasicSwap(coupling_map)])

out_circ = pm.run(in_circ)
/opt/miniconda3/envs/qiskit/lib/python3.7/site-packages/ipykernel_launcher.py:59: DeprecationWarning: deprecated function, use dag.two_qubit_ops(). filter output by isinstance(op, Gate) to only get unitary Gates.
/opt/miniconda3/envs/qiskit/lib/python3.7/site-packages/ipykernel_launcher.py:92: DeprecationWarning: combine_into_edge_map is deprecated as of 0.14.0 and will be removed in a future release. Instead reorder_bits() should be used
/opt/miniconda3/envs/qiskit/lib/python3.7/site-packages/ipykernel_launcher.py:93: DeprecationWarning: dag.extend_back is deprecated, please use dag.compose.
/opt/miniconda3/envs/qiskit/lib/python3.7/site-packages/qiskit/dagcircuit/dagcircuit.py:488: DeprecationWarning: dag.compose_back is deprecated, please use dag.compose.
  self.compose_back(dag, edge_map)
/opt/miniconda3/envs/qiskit/lib/python3.7/site-packages/qiskit/dagcircuit/dagcircuit.py:495: DeprecationWarning: edge_map arg as a dictionary is deprecated. Use qubits and clbits args to specify a list of self edges to compose onto.
  self.compose(input_circuit, edge_map)
/opt/miniconda3/envs/qiskit/lib/python3.7/site-packages/qiskit/dagcircuit/dagcircuit.py:488: DeprecationWarning: dag.compose_back is deprecated, please use dag.compose.
  self.compose_back(dag, edge_map)
/opt/miniconda3/envs/qiskit/lib/python3.7/site-packages/qiskit/dagcircuit/dagcircuit.py:495: DeprecationWarning: edge_map arg as a dictionary is deprecated. Use qubits and clbits args to specify a list of self edges to compose onto.
  self.compose(input_circuit, edge_map)
/opt/miniconda3/envs/qiskit/lib/python3.7/site-packages/qiskit/dagcircuit/dagcircuit.py:488: DeprecationWarning: dag.compose_back is deprecated, please use dag.compose.
  self.compose_back(dag, edge_map)
/opt/miniconda3/envs/qiskit/lib/python3.7/site-packages/qiskit/dagcircuit/dagcircuit.py:495: DeprecationWarning: edge_map arg as a dictionary is deprecated. Use qubits and clbits args to specify a list of self edges to compose onto.
  self.compose(input_circuit, edge_map)
/opt/miniconda3/envs/qiskit/lib/python3.7/site-packages/ipykernel_launcher.py:85: DeprecationWarning: combine_into_edge_map is deprecated as of 0.14.0 and will be removed in a future release. Instead reorder_bits() should be used
/opt/miniconda3/envs/qiskit/lib/python3.7/site-packages/ipykernel_launcher.py:86: DeprecationWarning: dag.compose_back is deprecated, please use dag.compose.
/opt/miniconda3/envs/qiskit/lib/python3.7/site-packages/qiskit/dagcircuit/dagcircuit.py:495: DeprecationWarning: edge_map arg as a dictionary is deprecated. Use qubits and clbits args to specify a list of self edges to compose onto.
  self.compose(input_circuit, edge_map)
/opt/miniconda3/envs/qiskit/lib/python3.7/site-packages/qiskit/dagcircuit/dagcircuit.py:488: DeprecationWarning: dag.compose_back is deprecated, please use dag.compose.
  self.compose_back(dag, edge_map)
/opt/miniconda3/envs/qiskit/lib/python3.7/site-packages/qiskit/dagcircuit/dagcircuit.py:495: DeprecationWarning: edge_map arg as a dictionary is deprecated. Use qubits and clbits args to specify a list of self edges to compose onto.
  self.compose(input_circuit, edge_map)
/opt/miniconda3/envs/qiskit/lib/python3.7/site-packages/qiskit/dagcircuit/dagcircuit.py:495: DeprecationWarning: edge_map arg as a dictionary is deprecated. Use qubits and clbits args to specify a list of self edges to compose onto.
  self.compose(input_circuit, edge_map)
/opt/miniconda3/envs/qiskit/lib/python3.7/site-packages/qiskit/dagcircuit/dagcircuit.py:488: DeprecationWarning: dag.compose_back is deprecated, please use dag.compose.
  self.compose_back(dag, edge_map)
/opt/miniconda3/envs/qiskit/lib/python3.7/site-packages/qiskit/dagcircuit/dagcircuit.py:495: DeprecationWarning: edge_map arg as a dictionary is deprecated. Use qubits and clbits args to specify a list of self edges to compose onto.
  self.compose(input_circuit, edge_map)
/opt/miniconda3/envs/qiskit/lib/python3.7/site-packages/qiskit/dagcircuit/dagcircuit.py:488: DeprecationWarning: dag.compose_back is deprecated, please use dag.compose.
  self.compose_back(dag, edge_map)
/opt/miniconda3/envs/qiskit/lib/python3.7/site-packages/qiskit/dagcircuit/dagcircuit.py:495: DeprecationWarning: edge_map arg as a dictionary is deprecated. Use qubits and clbits args to specify a list of self edges to compose onto.
  self.compose(input_circuit, edge_map)
/opt/miniconda3/envs/qiskit/lib/python3.7/site-packages/qiskit/dagcircuit/dagcircuit.py:495: DeprecationWarning: edge_map arg as a dictionary is deprecated. Use qubits and clbits args to specify a list of self edges to compose onto.
  self.compose(input_circuit, edge_map)
/opt/miniconda3/envs/qiskit/lib/python3.7/site-packages/qiskit/dagcircuit/dagcircuit.py:488: DeprecationWarning: dag.compose_back is deprecated, please use dag.compose.
  self.compose_back(dag, edge_map)
/opt/miniconda3/envs/qiskit/lib/python3.7/site-packages/qiskit/dagcircuit/dagcircuit.py:495: DeprecationWarning: edge_map arg as a dictionary is deprecated. Use qubits and clbits args to specify a list of self edges to compose onto.
  self.compose(input_circuit, edge_map)
[27]:
in_circ.draw(output='mpl')
[27]:
../../_images/tutorials_circuits_advanced_4_transpiler_passes_and_passmanager_48_0.png
[28]:
out_circ.draw(output='mpl')
[28]:
../../_images/tutorials_circuits_advanced_4_transpiler_passes_and_passmanager_49_0.png

Note that this pass only inserts the swaps necessary to make every two-qubit interaction conform to the device coupling map. It does not, for example, care about the direction of interactions, or the native gate set supported by the device. This is a design philosophy of Qiskit’s transpiler: every pass performs a small, well-defined action, and the aggressive circuit optimization is achieved by the pass manager through combining multiple passes.

Transpiler Logging

Due to the complexity of the internal operations that the transpiler is performing it’s likely that you’ll end up in a situation where you’d like to debug an issue or just understand more of what is happening inside the transpiler when you call it. To facilitate this the transpiler emits log messages as part of its normal operation. This logging uses the Python standard library logging module to emit the log messages. Python’s standard logging was used because it allows Qiskit-Terra’s logging to integrate in a standard way with other applications and libraries.

For a more thorough introduction to Python logging refer to the official documentation and the tutorials and cookbook linked off of there.

Note: Most of the logging module functions used in this section adjust global settings. If you run commands in this section it might effect the output from other cells if they are run in a different order.

Configuring Python Standard Library Logging

By default Python Standard Logging only prints log messages at the WARNING, ERROR, or CRITICAL log levels. Since none of the logs emitted by the transpiler use these log levels (they’re all informative) you need to configure logging.

The simplest way to do this is to just run:

[29]:
import logging

logging.basicConfig(level='DEBUG')

The basicConfig() function (see the docs here: https://docs.python.org/3/library/logging.html#logging.basicConfig) configures a root handler and formatter. We also specify the log level to display with the level kwarg. Setting it to a level will also include and higher levels. For example, if you set it to 'INFO' in addition to the INFO level this will also include the WARNING, ERROR, and CRITICAL log levels.

Now the python environment in this notebook is configured to emit log messages to stderr when you run the transpiler. For example:

Note: basicConfig() will only work when called the first time it’s called. It detects if a root handler and formatter have already been setup (either by using an earlier basicConfig() call or otherwise) and does nothing if they have. Further adjustments will have to by interacting with the handler directly.

[30]:
from qiskit.test.mock import FakeTenerife


log_circ = QuantumCircuit(2, 2)
log_circ.h(0)
log_circ.h(1)
log_circ.h(1)
log_circ.x(1)
log_circ.cx(0, 1)
log_circ.measure([0,1], [0,1])

backend = FakeTenerife()

transpile(log_circ, backend);
INFO:qiskit.transpiler.runningpassmanager:Pass: SetLayout - 0.00596 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: TrivialLayout - 0.04387 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: Layout2qDistance - 0.30208 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: FullAncillaAllocation - 0.05794 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: EnlargeWithAncilla - 0.06509 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: ApplyLayout - 0.66090 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: Unroll3qOrMore - 0.01907 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: CheckMap - 0.03600 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: Unroller - 0.21005 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: CheckCXDirection - 0.07224 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: CXDirection - 0.51904 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: RemoveResetInZeroState - 0.04506 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: Depth - 0.15688 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: FixedPoint - 0.03672 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: Optimize1qGates - 1.45411 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: CXCancellation - 0.17190 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: Depth - 0.03886 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: FixedPoint - 0.01788 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: Optimize1qGates - 0.29898 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: CXCancellation - 0.12302 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: Depth - 0.04911 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: FixedPoint - 0.03004 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: Optimize1qGates - 0.43583 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: CXCancellation - 0.11897 (ms)
INFO:qiskit.compiler.transpile:Total Transpile Time - 43.15710 (ms)

As you can clearly see here when calling transpile() it now prints 2 types of log messages. The fist is at the INFO log level and come from the pass manager. These indicate each pass that was executed and how long that took. The second are at the DEBUG level and come from the StochasticSwap pass and describes the internal operation of that pass. It’s useful for debugging issues in the pass’s operation.

Adjusting the log level for the transpiler

The qiskit transpiler uses a single namespace qiskit.transpiler, as used by logging.getLogger('qiskit.transpiler')). This makes it very easy to adjust the log level for just the transpiler. For example if you only wish to see log messages at the INFO level or above you can run:

[31]:
logging.getLogger('qiskit.transpiler').setLevel('INFO')
transpile(log_circ, backend);
INFO:qiskit.transpiler.runningpassmanager:Pass: SetLayout - 0.00620 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: TrivialLayout - 0.06700 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: Layout2qDistance - 0.55218 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: FullAncillaAllocation - 0.09203 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: EnlargeWithAncilla - 0.13399 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: ApplyLayout - 0.48494 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: Unroll3qOrMore - 0.02384 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: CheckMap - 0.08130 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: Unroller - 0.17691 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: CheckCXDirection - 0.09704 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: CXDirection - 0.63086 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: RemoveResetInZeroState - 0.02623 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: Depth - 0.07987 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: FixedPoint - 0.02098 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: Optimize1qGates - 1.68657 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: CXCancellation - 0.13709 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: Depth - 0.04005 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: FixedPoint - 0.01812 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: Optimize1qGates - 0.56696 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: CXCancellation - 0.07176 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: Depth - 0.06390 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: FixedPoint - 0.01788 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: Optimize1qGates - 0.59581 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: CXCancellation - 0.08512 (ms)
INFO:qiskit.compiler.transpile:Total Transpile Time - 43.03718 (ms)

Setting up logging to deal with parallel execution

When running the transpiler with multiple circuits by default these circuits are transpiled in parallel. If you want to do this with logging enabled and be able to understand the output some additional steps are required.

If you were just to enable logging as above and then pass transpile() multiple circuits you’ll get results that are difficult to decipher. For example:

[32]:
# Change log level back to DEBUG
logging.getLogger('qiskit.transpiler').setLevel('DEBUG')
# Transpile multiple circuits
circuits = [log_circ, log_circ]
transpile(circuits, backend);
INFO:qiskit.transpiler.runningpassmanager:Pass: SetLayout - 0.02789 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: SetLayout - 0.02599 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: TrivialLayout - 0.25392 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: TrivialLayout - 0.28682 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: Layout2qDistance - 1.16301 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: Layout2qDistance - 1.03974 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: FullAncillaAllocation - 0.36311 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: FullAncillaAllocation - 0.19407 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: EnlargeWithAncilla - 0.16212 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: EnlargeWithAncilla - 0.16999 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: ApplyLayout - 0.84186 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: ApplyLayout - 0.64898 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: Unroll3qOrMore - 0.04721 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: Unroll3qOrMore - 0.06008 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: CheckMap - 0.07606 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: CheckMap - 0.07915 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: Unroller - 0.95201 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: Unroller - 0.78106 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: CheckCXDirection - 0.11110 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: CheckCXDirection - 0.14400 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: CXDirection - 1.06406 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: CXDirection - 1.12319 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: RemoveResetInZeroState - 0.07510 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: Depth - 0.12803 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: RemoveResetInZeroState - 0.08607 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: Depth - 0.22912 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: FixedPoint - 0.41389 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: FixedPoint - 0.06032 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: Optimize1qGates - 3.57199 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: Optimize1qGates - 4.03023 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: CXCancellation - 0.11992 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: Depth - 0.09704 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: CXCancellation - 0.24509 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: FixedPoint - 0.06032 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: Depth - 0.10490 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: Optimize1qGates - 0.39506 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: FixedPoint - 0.02623 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: CXCancellation - 0.21505 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: Optimize1qGates - 0.43488 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: Depth - 0.03886 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: FixedPoint - 0.06199 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: CXCancellation - 0.36287 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: Depth - 0.08702 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: Optimize1qGates - 0.59867 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: FixedPoint - 0.11635 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: CXCancellation - 0.17619 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: Optimize1qGates - 0.48089 (ms)
INFO:qiskit.transpiler.runningpassmanager:Pass: CXCancellation - 0.09084 (ms)
INFO:qiskit.compiler.transpile:Total Transpile Time - 164.09874 (ms)

As you can see here we get log messages from all 3 circuits being transpiled together. There is no way to know which pass is part of which circuit’s transpilation. Luckily Python logging provides tools to deal with this. The simplest one is to just change the log formatter so that includes additional information so we can associate a log message with the process it came from.

[33]:
formatter = logging.Formatter('%(name)s - %(processName)-10s - %(levelname)s: %(message)s')
handler = logging.getLogger().handlers[0]
handler.setFormatter(formatter)

Then rerun the transpile() call and see the new log formatter.

[34]:
transpile(circuits, backend);
qiskit.transpiler.runningpassmanager - ForkProcess-6 - INFO: Pass: SetLayout - 0.02909 (ms)
qiskit.transpiler.runningpassmanager - ForkProcess-5 - INFO: Pass: SetLayout - 0.02718 (ms)
qiskit.transpiler.runningpassmanager - ForkProcess-5 - INFO: Pass: TrivialLayout - 0.29492 (ms)
qiskit.transpiler.runningpassmanager - ForkProcess-6 - INFO: Pass: TrivialLayout - 0.23103 (ms)
qiskit.transpiler.runningpassmanager - ForkProcess-5 - INFO: Pass: Layout2qDistance - 2.46024 (ms)
qiskit.transpiler.runningpassmanager - ForkProcess-6 - INFO: Pass: Layout2qDistance - 2.50220 (ms)
qiskit.transpiler.runningpassmanager - ForkProcess-5 - INFO: Pass: FullAncillaAllocation - 0.49615 (ms)
qiskit.transpiler.runningpassmanager - ForkProcess-6 - INFO: Pass: FullAncillaAllocation - 0.32210 (ms)
qiskit.transpiler.runningpassmanager - ForkProcess-6 - INFO: Pass: EnlargeWithAncilla - 0.20599 (ms)
qiskit.transpiler.runningpassmanager - ForkProcess-5 - INFO: Pass: EnlargeWithAncilla - 0.19908 (ms)
qiskit.transpiler.runningpassmanager - ForkProcess-5 - INFO: Pass: ApplyLayout - 0.88406 (ms)
qiskit.transpiler.runningpassmanager - ForkProcess-6 - INFO: Pass: ApplyLayout - 1.16515 (ms)
qiskit.transpiler.runningpassmanager - ForkProcess-6 - INFO: Pass: Unroll3qOrMore - 0.05102 (ms)
qiskit.transpiler.runningpassmanager - ForkProcess-5 - INFO: Pass: Unroll3qOrMore - 0.07915 (ms)
qiskit.transpiler.runningpassmanager - ForkProcess-5 - INFO: Pass: CheckMap - 0.09394 (ms)
qiskit.transpiler.runningpassmanager - ForkProcess-6 - INFO: Pass: CheckMap - 0.08893 (ms)
qiskit.transpiler.runningpassmanager - ForkProcess-5 - INFO: Pass: Unroller - 0.76795 (ms)
qiskit.transpiler.runningpassmanager - ForkProcess-6 - INFO: Pass: Unroller - 0.74792 (ms)
qiskit.transpiler.runningpassmanager - ForkProcess-5 - INFO: Pass: CheckCXDirection - 0.14591 (ms)
qiskit.transpiler.runningpassmanager - ForkProcess-6 - INFO: Pass: CheckCXDirection - 0.17595 (ms)
qiskit.transpiler.runningpassmanager - ForkProcess-6 - INFO: Pass: CXDirection - 1.74809 (ms)
qiskit.transpiler.runningpassmanager - ForkProcess-5 - INFO: Pass: CXDirection - 1.42479 (ms)
qiskit.transpiler.runningpassmanager - ForkProcess-6 - INFO: Pass: RemoveResetInZeroState - 0.08798 (ms)
qiskit.transpiler.runningpassmanager - ForkProcess-5 - INFO: Pass: RemoveResetInZeroState - 0.08392 (ms)
qiskit.transpiler.runningpassmanager - ForkProcess-6 - INFO: Pass: Depth - 0.11921 (ms)
qiskit.transpiler.runningpassmanager - ForkProcess-5 - INFO: Pass: Depth - 0.21601 (ms)
qiskit.transpiler.runningpassmanager - ForkProcess-5 - INFO: Pass: FixedPoint - 0.11492 (ms)
qiskit.transpiler.runningpassmanager - ForkProcess-6 - INFO: Pass: FixedPoint - 0.08917 (ms)
qiskit.transpiler.runningpassmanager - ForkProcess-5 - INFO: Pass: Optimize1qGates - 5.97310 (ms)
qiskit.transpiler.runningpassmanager - ForkProcess-6 - INFO: Pass: Optimize1qGates - 5.64504 (ms)
qiskit.transpiler.runningpassmanager - ForkProcess-6 - INFO: Pass: CXCancellation - 0.11897 (ms)
qiskit.transpiler.runningpassmanager - ForkProcess-5 - INFO: Pass: CXCancellation - 0.14782 (ms)
qiskit.transpiler.runningpassmanager - ForkProcess-6 - INFO: Pass: Depth - 0.07510 (ms)
qiskit.transpiler.runningpassmanager - ForkProcess-5 - INFO: Pass: Depth - 0.04315 (ms)
qiskit.transpiler.runningpassmanager - ForkProcess-5 - INFO: Pass: FixedPoint - 0.05007 (ms)
qiskit.transpiler.runningpassmanager - ForkProcess-6 - INFO: Pass: FixedPoint - 0.05102 (ms)
qiskit.transpiler.runningpassmanager - ForkProcess-6 - INFO: Pass: Optimize1qGates - 2.16222 (ms)
qiskit.transpiler.runningpassmanager - ForkProcess-5 - INFO: Pass: Optimize1qGates - 0.40603 (ms)
qiskit.transpiler.runningpassmanager - ForkProcess-5 - INFO: Pass: CXCancellation - 0.20194 (ms)
qiskit.transpiler.runningpassmanager - ForkProcess-6 - INFO: Pass: CXCancellation - 0.22888 (ms)
qiskit.transpiler.runningpassmanager - ForkProcess-5 - INFO: Pass: Depth - 0.07010 (ms)
qiskit.transpiler.runningpassmanager - ForkProcess-6 - INFO: Pass: Depth - 0.08106 (ms)
qiskit.transpiler.runningpassmanager - ForkProcess-5 - INFO: Pass: FixedPoint - 0.05913 (ms)
qiskit.transpiler.runningpassmanager - ForkProcess-6 - INFO: Pass: FixedPoint - 0.02718 (ms)
qiskit.transpiler.runningpassmanager - ForkProcess-5 - INFO: Pass: Optimize1qGates - 0.34308 (ms)
qiskit.transpiler.runningpassmanager - ForkProcess-6 - INFO: Pass: Optimize1qGates - 0.35501 (ms)
qiskit.transpiler.runningpassmanager - ForkProcess-5 - INFO: Pass: CXCancellation - 0.14210 (ms)
qiskit.transpiler.runningpassmanager - ForkProcess-6 - INFO: Pass: CXCancellation - 0.15473 (ms)
qiskit.compiler.transpile - MainProcess - INFO: Total Transpile Time - 191.32900 (ms)

Now the format for the log messages has been changed and it includes a process name for each of the transpilation processes so it’s at least clear which log messages go together.

There are many different options for how you can configure, this example is pretty limited. Refer to the documentation for more examples and options to build more sophisticated use cases that suit your specific use case or preferences.

[35]:
import qiskit.tools.jupyter
%qiskit_version_table
%qiskit_copyright

Version Information

Qiskit SoftwareVersion
Qiskit0.19.1
Terra0.14.1
Aer0.5.1
Ignis0.3.0
Aqua0.7.0
IBM Q Provider0.7.0
System information
Python3.7.7 (default, Mar 26 2020, 10:32:53) [Clang 4.0.1 (tags/RELEASE_401/final)]
OSDarwin
CPUs4
Memory (Gb)16.0
Mon May 11 16:56:35 2020 EDT

This code is a part of Qiskit

© Copyright IBM 2017, 2020.

This code is licensed under the Apache License, Version 2.0. You may
obtain a copy of this license in the LICENSE.txt file in the root directory
of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.

Any modifications or derivative works of this code must retain this
copyright notice, and modified files need to carry a notice indicating
that they have been altered from the originals.

[ ]: