Transpiler#

This module provides tools for transpilation of generic quantum circuits into a given set of native gates.

class qibo.transpiler.BarrierBeforeFinalMeasurements[source]#

Inserts a barrier before the final measurements of a circuit.

A measurement or a barrier is final if no other gate acts on its qubits after it. The final measurements and barriers are placed after a new qibo.gates.special.Barrier that acts on all the qubits of the circuit, in their original order, while the other gates stay before the barrier. A circuit without final measurements is not changed.

The barrier keeps the tools that rearrange the gates, such as the routers, from moving them across the final measurements. It can be removed afterwards with qibo.transpiler.optimizer.FilterGates.

Example

The measurement of the first qubit is not final, because a Hadamard gate acts on the qubit after it, so it stays before the barrier.

from qibo import Circuit, gates
from qibo.transpiler import BarrierBeforeFinalMeasurements

circuit = Circuit(2)
circuit.add(gates.M(0))
circuit.add(gates.H(0))
circuit.add(gates.CNOT(0, 1))
circuit.add(gates.M(0, 1))

new_circuit = BarrierBeforeFinalMeasurements()(circuit)
print([gate.name for gate in new_circuit.queue])
['measure', 'h', 'cx', 'barrier', 'measure']
class qibo.transpiler.CommutativeCancellation(atol=1e-12, up_to_global_phase=True)[source]#

Merges rotations and cancels pairs of gates through the gates that commute with them.

A rotation is a single-qubit gate without control qubits that is a rotation around the \(X\), \(Y\) or \(Z\) axis, up to a global phase, e.g. qibo.gates.RZ, qibo.gates.T, qibo.gates.SX or qibo.gates.Y. Two rotations around the same axis on the same qubit are merged into a single qibo.gates.RX, qibo.gates.RY or qibo.gates.RZ gate if all the gates between them commute with the second one, where two gates commute when applying them in either order gives the same matrix. The merged rotation is left out if it is the identity. Then, the pairs of gates whose product is the identity are cancelled through the gates that commute with them, see qibo.transpiler.optimizer.InverseCancellation.

Measurements, alignments, barriers, noise channels and fused gates are never merged nor cancelled, and stop the search across them on their qubits. Gates with sympy parameters are not merged, but the gates that commute with them do not stop the search. The gates are compared through their matrices at their current parameter values, so a merged rotation is not tracked by the returned circuit anymore. The pass is applied once, use qibo.transpiler.optimizer.FixedPoint to repeat it.

Example

The two qibo.gates.RZ gates on the first qubit merge, as the controlled-NOT gate between them commutes with them.

from qibo import Circuit, gates
from qibo.transpiler import CommutativeCancellation

circuit = Circuit(2)
circuit.add(gates.RZ(0, 0.3))
circuit.add(gates.CNOT(0, 1))
circuit.add(gates.RZ(0, 0.4))

CommutativeCancellation()(circuit).draw()
0: ─RZ─o─
1: ────X─
Parameters:
  • atol (float, optional) – Tolerance on the angle of a rotation to be the identity and on the difference between the matrices that are compared. Defaults to \(10^{-12}\).

  • up_to_global_phase (bool, optional) – If False, two rotations are merged only when the new rotation has exactly the same matrix as their product, which keeps the global phase of the circuit. Defaults to True.

class qibo.transpiler.ConsolidateBlocks(weight=1.4142135623730951)[source]#

Rewrites blocks of gates acting on two qubits with fewer controlled-Z (CZ) gates.

A block is a run of gates on the same pair of qubits, where single-qubit gates on either qubit are included as long as no other gate acts on that qubit in between. The matrix of a block with several two-qubit gates is decomposed with the KAK (Cartan) decomposition (see qibo.transpiler.blocks.Block.kak_decompose()) into at most three CZ gates and single-qubit gates, and it replaces the block if it has fewer CZ gates. Blocks that are locally equivalent to the identity or to a single CZ gate are written with none or one CZ gate, respectively. A qibo.gates.SWAP gate counts as three CZ gates and an qibo.gates.iSWAP gate as two, which makes this pass useful after routing, where SWAP gates meet the gates of the circuit.

Measurements, alignments, barriers, noise channels, fused gates, gates with sympy parameters and gates acting on more than two qubits are not part of any block and stop the block on their qubits. The new gates are qibo.gates.CZ and qibo.gates.Unitary gates, so this pass is meant to be used before qibo.transpiler.unroller.Unroller.

Parameters:

weight (float, optional) – Weight of the imaginary part in the matrix that is diagonalized to find the local gates of the decomposition, see qibo.transpiler.decompositions.unitary_decompositions.calculate_psi(). Defaults to \(\sqrt{2}\).

Example

A SWAP gate followed by a controlled-NOT (CNOT) gate costs four CZ gates, which the decomposition reduces to two.

from qibo import Circuit, gates
from qibo.transpiler import ConsolidateBlocks

circuit = Circuit(2)
circuit.add(gates.SWAP(0, 1))
circuit.add(gates.CNOT(0, 1))

consolidated = ConsolidateBlocks()(circuit)
print([gate.name for gate in consolidated.queue if len(gate.qubits) == 2])
['cz', 'cz']

References

1. F. Vatan and C. Williams, Optimal quantum circuits for general two-qubit gates, Phys. Rev. A 69, 032315 (2004).

class qibo.transpiler.Dense(connectivity=None)[source]#

Places the qubits on the most connected region of the hardware.

The \(n\) qubits that take part in two-qubit gates are placed on a connected region of \(n\) physical qubits with the largest number of connections among them. The qubits with more two-qubit gates are placed on the physical qubits with more connections in the region, and the other qubits take the remaining physical qubits in the order of the nodes of the connectivity.

Parameters:

connectivity (networkx.Graph, optional) – Hardware connectivity. Defaults to None.

Example

The three qubits of a triangle of CNOT gates are placed on the triangle of the connectivity, which needs no SWAP gate, instead of on the first three nodes.

import networkx as nx

from qibo import Circuit, gates
from qibo.transpiler import Dense

connectivity = nx.Graph([(0, 1), (1, 2), (2, 3), (3, 4), (2, 4)])

circuit = Circuit(5)
circuit.add(gates.CNOT(0, 1))
circuit.add(gates.CNOT(1, 2))
circuit.add(gates.CNOT(0, 2))

Dense(connectivity)(circuit)
print(circuit.wire_names)
[2, 3, 4, 0, 1]
class qibo.transpiler.FilterGates(predicate)[source]#

Removes the gates of a circuit that do not satisfy a condition.

Parameters:

predicate (Callable) – Function that takes a qibo.gates.abstract.Gate and returns True if the gate is kept in the circuit and False if it is removed.

Example

The barrier is removed, while the other gates are kept.

from qibo import Circuit, gates
from qibo.transpiler import FilterGates

circuit = Circuit(2)
circuit.add(gates.H(0))
circuit.add(gates.Barrier(0, 1))
circuit.add(gates.CNOT(0, 1))

filter_barriers = FilterGates(lambda gate: not isinstance(gate, gates.Barrier))
print([gate.name for gate in filter_barriers(circuit).queue])
['h', 'cx']
class qibo.transpiler.FixedPoint(optimizers, max_iterations=10)[source]#

Applies a list of optimizers repeatedly until the circuit stops changing.

Each iteration applies the optimizers in order. The loop ends when an iteration leaves all the gates unchanged, i.e. with the same classes, qubits and parameters up to numerical tolerance, or after max_iterations iterations. This lets an optimizer profit from the simplifications of the ones that come after it, for example a pair of gates that only cancels once the gates between them are merged into the identity.

The connectivity that qibo.transpiler.pipeline.Passes sets on this optimizer is set on optimizers too.

Parameters:
  • optimizers (list[qibo.transpiler.abstract.Optimizer]) – Optimizers applied in order at each iteration.

  • max_iterations (int, optional) – Maximum number of iterations. Defaults to \(10\).

Example

The three rotations around the X axis on the first qubit merge into the identity, which leaves the two controlled-Z (CZ) gates next to each other. The first iteration removes the rotations and the second one cancels the CZ gates.

from qibo import Circuit, gates
from qibo.transpiler import (
    FixedPoint,
    InverseCancellation,
    Optimize1qGatesDecomposition,
)

circuit = Circuit(2)
circuit.add(gates.CZ(0, 1))
circuit.add(gates.RX(0, 0.3))
circuit.add(gates.RX(0, 0.4))
circuit.add(gates.RX(0, -0.7))
circuit.add(gates.CZ(0, 1))

optimizers = [InverseCancellation(), Optimize1qGatesDecomposition()]
print(InverseCancellation()(circuit).ngates)
print(FixedPoint(optimizers)(circuit).ngates)
5
0
class qibo.transpiler.InverseCancellation(atol=1e-12, commutation=False)[source]#

Cancels pairs of gates whose product is the identity.

Two gates cancel when no other gate acts on their qubits in between, both act on the same qubits in the same order with the same control qubits, and the second gate undoes the first one, i.e. the product of their matrices is the identity matrix up to atol. Cancellation is applied repeatedly, so nested pairs are removed completely: a Pauli-X gate, a Pauli-Y gate, a second Pauli-Y gate and a second Pauli-X gate applied in this order on one qubit all disappear.

If commutation is True, the gates in between that commute with the second gate of the pair do not stop the cancellation. Two gates commute when applying them in either order gives the same matrix.

The product must equal the identity itself, not merely the identity times a global phase, because for gates built with controlled_by a phase on the target matrix is a relative phase on the control qubits. Measurements, alignments, barriers, noise channels and fused gates are never cancelled and stop the cancellation across them on their qubits. Gates are compared through their matrices at their current parameter values, so a cancelled pair of parametrized gates is not tracked by the returned circuit anymore.

Gates with sympy parameters take part like any other gate. Their symbols are given fixed random values, the same in all gates, before the matrices are compared. Hence qibo.gates.RX gates with angles \(x\) and \(-x\) cancel, and a qibo.gates.RZ gate with angle \(x\) is skipped over by a controlled-NOT gate on its control qubit if commutation is True, while gates with angles \(x\) and \(y\) do not cancel. A gate whose parameters are not real numbers at these values is never cancelled and stops the cancellation on its qubits.

Parameters:
  • atol (float, optional) – Tolerance on the Frobenius norm (square root of the sum of the squared absolute values of all entries) of the difference between the product of the two gate matrices and the identity matrix, and between the two orders of two gates that are tested for commutation. Defaults to \(10^{-12}\).

  • commutation (bool, optional) – If True, cancel pairs of gates that are separated by gates commuting with them. Defaults to False.

Example

The pair of controlled-NOT (CNOT) gates cancels first, which makes the two Hadamard gates on the first qubit consecutive so they cancel as well. The two rotations around the X axis on the second qubit cancel because their angles add up to zero. The Hadamard gates on the second qubit stay, because the CNOT gate between them acts on that qubit.

from qibo import Circuit, gates
from qibo.transpiler import InverseCancellation

circuit = Circuit(2)
circuit.add(gates.H(0))
circuit.add(gates.CNOT(0, 1))
circuit.add(gates.CNOT(0, 1))
circuit.add(gates.H(0))
circuit.add(gates.RX(1, 0.3))
circuit.add(gates.RX(1, -0.3))
circuit.add(gates.X(0))
circuit.add(gates.H(1))
circuit.add(gates.CNOT(0, 1))
circuit.add(gates.H(1))

circuit.draw()
print()
InverseCancellation()(circuit).draw()
0: ─H─o─o─H──X────o───
1: ───X─X─RX─RX─H─X─H─

0: ─X─o───
1: ─H─X─H─

A rotation around the Z axis on the control qubit and a Pauli-X gate on the target qubit both commute with the CNOT gate, so with commutation=True the two CNOT gates around them cancel.

circuit = Circuit(2)
circuit.add(gates.CNOT(0, 1))
circuit.add(gates.RZ(0, 0.3))
circuit.add(gates.X(1))
circuit.add(gates.CNOT(0, 1))

InverseCancellation(commutation=True)(circuit).draw()
0: ─RZ─
1: ─X──
class qibo.transpiler.NativeGates(value, names=<not given>, *values, module=None, qualname=None, type=None, start=1, boundary=None)[source]#

Define native gates supported by the unroller.

A native gate set should contain at least one entangling two-qubit gate, which is any of the two-qubit gates below except qibo.gates.gates.SWAP, and single-qubit gates that can implement any single-qubit unitary. These are qibo.gates.gates.U3, qibo.gates.gates.GPI2, or a combination of the other single-qubit gates that has two rotations with free angles about orthogonal axes, see qibo.transpiler.decompositions.unitary_decompositions.single_qubit_decomposition(). For example, qibo.gates.gates.RX and qibo.gates.gates.RZ, or qibo.gates.gates.RZ and qibo.gates.gates.H. qibo.gates.gates.Z and qibo.gates.gates.RZ are virtual gates, which are assumed to be available together with qibo.gates.gates.GPI2 and qibo.gates.gates.U3.

Possible gates are:

If several entangling gates are native, any other gate is decomposed into the first one among qibo.gates.gates.CZ, qibo.gates.gates.iSWAP, qibo.gates.gates.CNOT and the gates above, in this order. A native two-qubit gate is never decomposed.

classmethod default()[source]#

Return default native gates set.

Return type:

NativeGates

classmethod from_gatelist(gatelist)[source]#

Create a NativeGates object containing all gates from a gatelist.

Parameters:

gatelist (list[qibo.gates.abstract.Gate]) –

Return type:

NativeGates

classmethod from_gate(gate)[source]#

Create a qibo.transpiler.unroller.NativeGates object from a qibo.gates.gates.Gate.

Parameters:

gate (Gate | type[qibo.gates.abstract.Gate]) –

Return type:

NativeGates

property is_universal: bool#

Whether the native gates can implement any two-qubit unitary.

This holds when the set contains at least one entangling two-qubit gate, which is any of them except qibo.gates.gates.SWAP, and single-qubit gates that implement any single-qubit unitary exactly: qibo.gates.gates.U3, qibo.gates.gates.GPI2, or gates with two rotations about orthogonal axes with free angles, see single_qubit_gates. Single-qubit unitaries together with any entangling two-qubit gate can implement every unitary on any number of connected qubits [1].

References

1. J.-L. Brylinski and R. Brylinski, Universal quantum gates, in Mathematics of Quantum Computation, Chapman & Hall/CRC (2002).

property single_qubit_gates: tuple[type[qibo.gates.abstract.Gate], ...]#

Classes of the single-qubit gates in the native gates.

property two_qubit_gates: tuple[type[qibo.gates.abstract.Gate], ...]#

Classes of the two-qubit gates in the native gates.

property gate_names: tuple[str, ...]#

Names of the gates in the native gates, without the measurement.

The names are the ones of qibo.gates.abstract.Gate.name, e.g. "cx" for qibo.gates.gates.CNOT. They can be the basis of qibo.transpiler.optimizer.Optimize1qGatesDecomposition.

class qibo.transpiler.NoRouting(connectivity=None)[source]#

Leaves the circuit unchanged, and raises an error if it cannot run on the hardware.

Parameters:

connectivity (networkx.Graph, optional) – Hardware connectivity. Defaults to None.

Example

The two-qubit gates of the circuit act on connected qubits, so the circuit is not changed and no qubit is moved.

import networkx as nx

from qibo import Circuit, gates
from qibo.transpiler import NoRouting

circuit = Circuit(3)
circuit.add(gates.CNOT(0, 1))
circuit.add(gates.CNOT(1, 2))

routed_circuit, final_layout = NoRouting(nx.path_graph(3))(circuit)
print(final_layout)
{0: 0, 1: 1, 2: 2}
class qibo.transpiler.Optimize1qGatesDecomposition(basis=None, atol=1e-12, up_to_global_phase=True)[source]#

Rewrites runs of consecutive single-qubit gates with as few gates as possible.

A run is a sequence of single-qubit gates without control qubits on one qubit with no other gate on that qubit in between. The matrix of the run is decomposed in every Euler basis allowed by basis and the shortest result is kept. The Euler bases are:

Rotations by a multiple of \(2 \pi\) are left out, so runs equal to the identity are removed. A run is replaced if it contains a gate whose name is not in basis, or if the new sequence is shorter. Gates with sympy parameters end a run. If basis contains no Euler basis, the circuit is returned unchanged. The input circuit is not modified.

Parameters:
  • basis (list[str], optional) – Names of the gates the circuit may contain, for example ["rz", "sx", "cx"]. Only Euler bases whose gates are all in this list are used. If None, all Euler bases are used and every gate counts as in the basis. Defaults to None.

  • atol (float, optional) – Tolerance to decide that an angle is zero, that \(\theta\) (the Y rotation angle of the equivalent qibo.gates.U3 gate) is \(\pi / 2\) or \(\pi\), and that two matrices are equal (Frobenius norm of their difference). Defaults to \(10^{-12}\).

  • up_to_global_phase (bool, optional) – If False, a run is replaced only when the new sequence has exactly the same matrix. Defaults to True.

Example

The Hadamard, phase and Pauli X gates on the first qubit become an qibo.gates.RZ gate and an qibo.gates.SX gate, and the two Pauli X gates on the second qubit cancel.

from qibo import Circuit, gates
from qibo.transpiler import Optimize1qGatesDecomposition

circuit = Circuit(2)
circuit.add(gates.H(0))
circuit.add(gates.S(0))
circuit.add(gates.X(0))
circuit.add(gates.X(1))
circuit.add(gates.X(1))
circuit.add(gates.CNOT(0, 1))

optimized = Optimize1qGatesDecomposition(basis=["rz", "sx", "cx"])(circuit)
for gate in optimized.queue:
    print(gate.name, gate.qubits)
rz (0,)
sx (0,)
cx (0, 1)
class qibo.transpiler.Optimize1qGatesSimpleCommutation(basis=None, atol=1e-12, up_to_global_phase=True)[source]#

Rewrites runs of single-qubit gates after moving gates through commuting gates.

A run is a sequence of single-qubit gates without control qubits on one qubit with no other gate on that qubit in between, as in qibo.transpiler.optimizer.Optimize1qGatesDecomposition. Consider three consecutive runs on a qubit separated by two gates, the preceding and succeeding blockers. The gates at the start of the middle run that commute with the preceding blocker, up to the first one that does not, are moved to the end of the preceding run, and the gates at its end that commute with the succeeding blocker are moved to the start of the succeeding run. The three runs are rewritten with qibo.transpiler.optimizer.Optimize1qGatesDecomposition, and replaced if they have fewer gates, or if they had gates that are not in basis. For example, a qibo.gates.RZ gate commutes with the control of a qibo.gates.CNOT gate.

Two gates commute when applying them in either order gives the same matrix. Gates are moved only into runs that already have gates, only the runs next to a gate that they commute with are rewritten, and the pass is applied once, so use qibo.transpiler.optimizer.FixedPoint to repeat it. Measurements, alignments, barriers, noise channels and gates with sympy parameters are never moved through nor rewritten. The input circuit is not modified.

Example

The qibo.gates.RZ gate before the controlled-NOT gate moves through its control into the run after it, where it is rewritten with the other gates as a single qibo.gates.U2 gate.

from qibo import Circuit, gates
from qibo.transpiler import Optimize1qGatesSimpleCommutation

circuit = Circuit(2)
circuit.add(gates.H(0))
circuit.add(gates.RZ(0, 0.3))
circuit.add(gates.CNOT(0, 1))
circuit.add(gates.RZ(0, 0.4))
circuit.add(gates.H(0))

optimizer = Optimize1qGatesSimpleCommutation()
new_circuit = optimizer(circuit)
new_circuit.draw()
0: ─H─o─U2─
1: ───X────
Parameters:
class qibo.transpiler.OptimizeSwapBeforeMeasure[source]#

Removes the SWAP gates that are only followed by measurements.

A qibo.gates.SWAP gate is removed if the gates after it on its two qubits are measurements, and if there is at least one, and the two qubits of each of these measurements are exchanged instead. Thus, the outcomes of the measurements are the same, and the order of their bits is kept, but the state of the circuit before them has the two qubits exchanged. Swaps that become followed only by measurements, because of the removal of other swaps, are removed as well.

The input circuit is not modified.

Example

The swap is removed, and the measurement is on the qubits in the opposite order.

from qibo import Circuit, gates
from qibo.transpiler import OptimizeSwapBeforeMeasure

circuit = Circuit(3)
circuit.add(gates.X(0))
circuit.add(gates.SWAP(0, 1))
circuit.add(gates.SWAP(1, 2))
circuit.add(gates.M(0, 1, 2))

optimized = OptimizeSwapBeforeMeasure()(circuit)
for gate in optimized.queue:
    print(gate.name, gate.qubits)
x (0,)
measure (1, 2, 0)
class qibo.transpiler.ParametrizedGateFusion(atol=1e-12, up_to_global_phase=False)[source]#

Merges consecutive rotation gates of the same kind into a single gate.

Two gates are merged when no other gate acts on their qubits in between and both are of the same gate class acting on the same qubits with the same control qubits. The merged gate has the sum of the rotation angles, for example a qibo.gates.RY gate with angle \(\alpha\) followed by a qibo.gates.RY gate with angle \(\beta\) on the same qubit becomes one qibo.gates.RY gate with angle \(\alpha + \beta\). Runs of more than two gates are merged completely. The merge is exact, including the global phase.

The following gates are merged this way:

Gates with sympy parameters of these classes are merged too, and the angle of the merged gate is the sympy sum of the angles. The parameters that must coincide, such as the phase angle of qibo.gates.PRX, must then be the same expression. The gates qibo.gates.U2 and qibo.gates.U3 are not merged when any of their parameters is a sympy expression.

Any of these gates can also be controlled on further qubits with controlled_by, as long as both gates have the same control qubits. Gates that are symmetric under exchanging their two qubits (such as qibo.gates.RZZ) are also merged when their qubits are listed in a different order, and so are qibo.gates.RBS and qibo.gates.GIVENS, whose angle changes sign when the qubits are exchanged.

The general single-qubit gates qibo.gates.U2 and qibo.gates.U3, and their controlled versions qibo.gates.CU2 and qibo.gates.CU3, are merged in any combination into a single gate, whose angles are not the sum of the angles of the two gates. A U3 gate with angles \(\theta\), \(\phi\) and \(\lambda\) applies a rotation around the Z axis by \(\lambda\), then a rotation around the Y axis by \(\theta\) and finally a rotation around the Z axis by \(\phi\). A U2 gate is the U3 gate with \(\theta = \pi / 2\). For a first gate with angles \((\theta_1, \phi_1, \lambda_1)\) and a second gate with angles \((\theta_2, \phi_2, \lambda_2)\), let \(h = (\lambda_2 + \phi_1) / 2\) and let \(\alpha\) and \(\beta\) be the complex numbers

\[\alpha = \cos h \, \cos\frac{\theta_1 + \theta_2}{2} - i \sin h \, \cos\frac{\theta_2 - \theta_1}{2}, \qquad \beta = \cos h \, \sin\frac{\theta_1 + \theta_2}{2} - i \sin h \, \sin\frac{\theta_2 - \theta_1}{2}.\]

Then the merged gate has the angles

\[\theta' = 2 \, \mathrm{atan2}(|\beta|, |\alpha|), \qquad \phi' = \phi_2 + \arg\beta - \arg\alpha, \qquad \lambda' = \lambda_1 - \arg\alpha - \arg\beta,\]

where \(|z|\) and \(\arg z\) are the modulus and the phase angle of the complex number \(z\), and \(\mathrm{atan2}\) is the two-argument arctangent. The merged gate is a qibo.gates.U2 gate with angles \(\phi'\) and \(\lambda'\) if \(\theta' = \pi / 2\), and a qibo.gates.U3 gate otherwise. This merge is exact, including the global phase, for gates without control qubits and for controlled gates alike.

The phase gate qibo.gates.U1 and its controlled version qibo.gates.CU1 differ from the U3 gate with \(\theta = 0\) and \(\phi = 0\) by the global phase \(e^{i \lambda / 2}\). Therefore merging one of them with a U2 or U3 gate changes the global phase of the circuit. This is only done if up_to_global_phase is True and the gates have no control qubits, because for controlled gates the global phase becomes a relative phase. For the same reason, a merged U3 gate with \(\theta' = 0\) becomes the qibo.gates.U1 gate with angle \(\phi' + \lambda'\) only if up_to_global_phase is True and the gates have no control qubits.

All other gates, such as qibo.gates.GPI2 or qibo.gates.U1q and qibo.gates.PRX with different phase angles, are left untouched, because the product of two of them is not a single gate of the same kind with added parameters, or, for qibo.gates.MS, could leave the range of allowed angles.

Only gates with the same trainable flag are merged, and gates with sympy parameters are skipped. The merged gate is a new gate, and the input circuit is not modified. The merged gate holds fewer parameters than the two gates it replaces, so the list of parameters of the returned circuit is shorter than the one of the input circuit. Merging is done for the current parameter values, so a later set_parameters call on the returned circuit does not update the angles of the original gates independently.

Parameters:
  • atol (float, optional) – Tolerance for deciding that the phase angles of two qibo.gates.PRX or qibo.gates.U1q gates are the same, and that the global phase changed by a merge is zero. Defaults to \(10^{-12}\).

  • up_to_global_phase (bool, optional) – If True, gates without control qubits are also merged when this changes the global phase of the circuit, which happens when a qibo.gates.U1 gate is merged with a qibo.gates.U2 or qibo.gates.U3 gate. Defaults to False.

Example

The two qibo.gates.RY gates on the first qubit are merged. The two qibo.gates.RBS gates act on the same qubits in reversed order, so the angle of the second one enters with the opposite sign. The two qibo.gates.RZ gates on the first qubit are not merged, because the CNOT gate acts on that qubit in between.

from qibo import Circuit, gates
from qibo.transpiler import ParametrizedGateFusion

circuit = Circuit(3)
circuit.add(gates.RY(0, 0.1))
circuit.add(gates.RY(0, 0.2))
circuit.add(gates.RBS(1, 2, 0.3))
circuit.add(gates.RBS(2, 1, 0.4))
circuit.add(gates.RZ(0, 0.5))
circuit.add(gates.CNOT(0, 1))
circuit.add(gates.RZ(0, 0.6))

fused = ParametrizedGateFusion()(circuit)
for gate in fused.queue:
    print(gate.name, gate.qubits, [round(p, 3) for p in gate.parameters])
ry (0,) [0.3]
rbs (1, 2) [-0.1]
rz (0,) [0.5]
cx (0, 1) []
rz (0,) [0.6]

A qibo.gates.U3 gate followed by a qibo.gates.U2 gate gives one qibo.gates.U3 gate, which has the same matrix as the two gates.

import numpy as np

circuit = Circuit(1)
circuit.add(gates.U3(0, 0.3, 0.2, 0.1))
circuit.add(gates.U2(0, 0.4, 0.5))

fused = ParametrizedGateFusion()(circuit)
gate = fused.queue[0]
print(gate.name, [round(float(p), 3) for p in gate.parameters])
print(np.allclose(fused.unitary(), circuit.unitary()))
u3 [1.799, 0.597, 0.823]
True
class qibo.transpiler.Passes(passes=None, connectivity=None, native_gates=None, on_qubits=None, callback=None)[source]#

Define a transpiler pipeline consisting of smaller transpiler steps that are applied sequentially.

Parameters:
  • passes (list, optional) – List of transpiler passes to be applied sequentially. If None, no pass is applied. See qibo.transpiler.pipeline.generate_preset_passes() for a ready-to-use list. Defaults to None.

  • connectivity (networkx.Graph, optional) – Hardware connectivity. It can be a directed graph, in which case an edge (a, b) means that a two-qubit gate is supported with its first qubit on a and its second on b, see qibo.transpiler.optimizer.GateDirection. It is required if on_qubits is given. Defaults to None.

  • native_gates (qibo.transpiler.unroller.NativeGates, optional) – Native gates supported by the hardware. Defaults to qibo.transpiler.unroller.NativeGates.default().

  • on_qubits (list, optional) – List of qubits to be used in the transpiler. If None, all qubits in the connectivity will be used. Defaults to None.

  • callback (Callable, optional) – Function called after each pass that runs, with the keyword arguments transpiler_pass, the pass that has run, circuit, the circuit after the pass, time, the seconds that the pass has taken, and count, the position of the pass in passes. A router is skipped, and the callback is not called for it, if the circuit already respects the connectivity, since no qubit has to be moved. Defaults to None.

Example

The callback prints the passes of the pipeline as they run. The circuit already respects the connectivity, so the router is skipped and it is not printed.

import networkx as nx

from qibo import Circuit, gates
from qibo.transpiler import Passes, Preprocessing, Sabre

circuit = Circuit(2)
circuit.add(gates.CNOT(0, 1))

def callback(transpiler_pass, circuit, time, count):
    print(count, type(transpiler_pass).__name__, circuit.nqubits)

pipeline = Passes(
    [Preprocessing(), Sabre()], connectivity=nx.path_graph(3), callback=callback
)
transpiled_circuit, final_layout = pipeline(circuit)
0 Preprocessing 3
is_satisfied(circuit)[source]#

Check if the circuit respects the hardware connectivity and native gates.

If there is no connectivity, only the native gates are checked, because there is nothing to place and route.

Parameters:

circuit (qibo.models.circuit.Circuit) – Circuit to be checked.

Returns:

True if the circuit respects the hardware connectivity and native gates, False otherwise.

Return type:

(bool)

class qibo.transpiler.Preprocessing(connectivity=None, initial_layout=None)[source]#

Pad the circuit with unused qubits to match the number of physical qubits.

Parameters:
  • connectivity (networkx.Graph) – Hardware connectivity as a graph.

  • initial_layout (list or dict, optional) – Physical qubits on which the logical qubits of the circuit are placed. It is either a list, where the \(i\)-th logical qubit is placed on the physical qubit initial_layout[i], or a dictionary {i: node} with the same meaning, which may leave logical qubits out. These are placed on the nodes of the connectivity that are not in the dictionary, in the order of the connectivity. In both cases every logical qubit must be placed on a distinct node of the connectivity, and the layout replaces the wire_names of the circuit, which are set in place. If None, the wire_names of the circuit are used if they are all in the connectivity, and the first nodes of the connectivity otherwise. Defaults to None.

Example

The first and the third qubits of the circuit are placed on the nodes 3 and 1, and the second one on the first free node, which is 0.

import networkx as nx

from qibo import Circuit, gates
from qibo.transpiler import Preprocessing

circuit = Circuit(3)
circuit.add(gates.CNOT(0, 1))

preprocessing = Preprocessing(nx.path_graph(5), initial_layout={0: 3, 2: 1})
print(preprocessing(circuit).wire_names)
[3, 0, 1, 2, 4]
class qibo.transpiler.Random(connectivity=None, samples=100, seed=None)[source]#

Random initialization with greedy policy, so that a maximum number of 2-qubit gates can be applied without introducing any SWAP gate.

Parameters:
  • connectivity (networkx.Graph, optional) – Hardware connectivity. Defaults to None.

  • samples (int, optional) – Number of random initializations to try. If \(0\), no random initialization is tried and the qubits are placed following the order of the nodes of connectivity, i.e. the \(i\)-th qubit is placed on the \(i\)-th node. Defaults to \(100\).

  • seed (int or numpy.random.Generator, optional) – Either a generator of random numbers or a fixed seed to initialize a generator. If None, initializes a generator with a random seed. Defaults to None.

class qibo.transpiler.Rearrange(max_qubits=1)[source]#

Rearranges gates using qibo’s fusion algorithm. May reduce number of qibo.gates.SWAP when fixing for connectivity but this has not been tested.

Parameters:

max_qubits (int, optional) – Maximum number of qubits to fuse. Defaults to \(1\).

class qibo.transpiler.RemoveDiagonalGatesBeforeMeasurement[source]#

Removes diagonal gates that are directly followed by measurements.

A gate is diagonal when its matrix in the computational basis has non-zero entries only on its diagonal, so it merely multiplies each computational basis state by a phase. Such a gate does not change the probability of any outcome of a measurement in the computational basis, so it can be dropped when the next operation on each of its qubits is such a measurement.

The following diagonal gates are removed:

Any of these gates with further control qubits added through controlled_by is still diagonal, so it is removed as well. For example, a qibo.gates.S gate controlled by another qubit is removed.

A gate is removed when, on each qubit it acts on, the next operation is a measurement qibo.gates.M. The measurement may be the same for all the qubits of the gate or a different one for each of them. Any other operation in between keeps the gate, be it a gate, a noise channel, a barrier or an alignment. In particular, a measurement in a basis other than the computational basis is preceded in the circuit by the gates rotating the qubits to that basis, which are not diagonal, so the diagonal gates before them stay.

Only the gates directly before the measurement are removed, so a qibo.gates.RZ gate followed by a qibo.gates.Z gate and then by a measurement keeps the qibo.gates.RZ gate. Applying the pass a second time removes it.

The probability of every measurement outcome is unchanged, but the state of the qubits just before the measurement is not, because its amplitudes change by phases that differ between basis states. The pass is therefore meant for circuits of which only the measurement outcomes are used. For measurements that collapse the state, the state after the measurement changes at most by a global phase. Removed parametrized gates are not tracked by the returned circuit anymore.

Example

The qibo.gates.RZ gate is removed because a measurement follows it on the first qubit, while the qibo.gates.S gate before it stays because a Hadamard gate follows it. The controlled-Z gate is removed because both of its qubits are measured next.

from qibo import Circuit, gates
from qibo.transpiler import RemoveDiagonalGatesBeforeMeasurement

circuit = Circuit(3)
circuit.add(gates.H(qubit) for qubit in range(3))
circuit.add(gates.S(0))
circuit.add(gates.H(0))
circuit.add(gates.RZ(0, 0.3))
circuit.add(gates.CZ(1, 2))
circuit.add(gates.M(0, 1, 2))

circuit.draw()
print()
RemoveDiagonalGatesBeforeMeasurement()(circuit).draw()
0: ─H─S─H─RZ───M─
1: ─H────────o─M─
2: ─H────────Z─M─

0: ─H─S─H─M─
1: ─H─────M─
2: ─H─────M─
class qibo.transpiler.RemoveFinalReset[source]#

Removes the resets at the end of a circuit.

A reset is the single-qubit qibo.gates.ResetChannel that sends the qubit to the \(|0\rangle\) state with probability one, that is with \(p_0 = 1\) and \(p_1 = 0\). A reset is removed when no other operation (gate, measurement or barrier) acts on its qubit afterwards. Resets that are only followed by other final resets are removed as well.

Such a reset does not change the outcome of any measurement made before it, nor the state of the other qubits, but it does change the state of its own qubit in the final state returned by the circuit execution.

Example

The last reset is removed, while the first one is kept because the Hadamard gate acts on the qubit after it.

from qibo import Circuit, gates
from qibo.transpiler import RemoveFinalReset

circuit = Circuit(1, density_matrix=True)
circuit.add(gates.ResetChannel(0, [1.0, 0.0]))
circuit.add(gates.H(0))
circuit.add(gates.ResetChannel(0, [1.0, 0.0]))

print(RemoveFinalReset()(circuit).ngates)
2
class qibo.transpiler.RemoveIdentityEquivalent(approximation_degree=1.0)[source]#

Removes gates whose action is equivalent to the identity.

A gate \(U\), acting on \(n\) qubits in total (targets and controls), is removed when its average gate fidelity with the identity,

\[F(U) = \frac{|\text{Tr}(U)|^2 + D}{D \, (D + 1)},\]

satisfies \(1 - F(U) \leq \epsilon\). Here \(D = 2^n\) is the dimension of the Hilbert space, \(\text{Tr}\) is the matrix trace and \(\epsilon\) is the tolerance. Since \(|\text{Tr}(U)|\) does not depend on a global phase, an uncontrolled gate that equals the identity up to a phase, e.g. a rotation around the Z axis by \(2\pi\), is removed. For a gate built with controlled_by that phase is a relative phase between the control states, so the gate is removed only if its target matrix is the identity.

Measurements, alignments, barriers, noise channels, fused gates and global phase gates are never removed, the latter to keep the global phase of the circuit. Gates are evaluated at their current parameter values, so a removed parametrized gate is not tracked by the returned circuit anymore.

A gate with sympy parameters is evaluated at fixed random values of its symbols. It is removed only if it is the identity there up to numerical precision, whatever the value of approximation_degree, because the true values of the symbols are unknown.

Parameters:

approximation_degree (float, optional) – Value in \([0, 1]\) that sets the tolerance as \(\epsilon = 1 -\) approximation_degree. With the default value only gates equal to the identity up to numerical precision (\(\epsilon = 10^{-14}\)) are removed. Defaults to \(1.0\).

Example

The rotation by \(10^{-3}\) radians differs from the identity by an infidelity of about \(1.7 \times 10^{-7}\), so it is removed only when the tolerance allows it. The identity gate and the rotation by \(2\pi\) (minus the identity) are removed in both cases.

from math import pi

from qibo import Circuit, gates
from qibo.transpiler import RemoveIdentityEquivalent

circuit = Circuit(2)
circuit.add(gates.I(0))
circuit.add(gates.RZ(0, 2 * pi))
circuit.add(gates.RX(1, 1e-3))
circuit.add(gates.CNOT(0, 1))

print(RemoveIdentityEquivalent()(circuit).ngates)
print(RemoveIdentityEquivalent(approximation_degree=1 - 1e-6)(circuit).ngates)
2
1
class qibo.transpiler.RemoveResetInZeroState[source]#

Removes the resets acting on qubits that are still in the zero state.

A reset is the single-qubit qibo.gates.ResetChannel that sends the qubit to the \(|0\rangle\) state with probability one, that is with \(p_0 = 1\) and \(p_1 = 0\). A qubit is known to be in the \(|0\rangle\) state only until the first operation (gate, measurement or barrier) acts on it, so only the resets that precede every other operation on their qubit are removed. Consecutive resets at the start of a qubit are all removed.

Example

The reset at the start of the circuit is removed, while the one after the Hadamard gate is kept.

from qibo import Circuit, gates
from qibo.transpiler import RemoveResetInZeroState

circuit = Circuit(1, density_matrix=True)
circuit.add(gates.ResetChannel(0, [1.0, 0.0]))
circuit.add(gates.H(0))
circuit.add(gates.ResetChannel(0, [1.0, 0.0]))

print(RemoveResetInZeroState()(circuit).ngates)
2
class qibo.transpiler.ResetAfterMeasureSimplification[source]#

Replaces a reset that follows a measurement by a gate conditioned on its outcome.

A reset is the single-qubit qibo.gates.ResetChannel that sends the qubit to the \(|0\rangle\) state with probability one, that is with \(p_0 = 1\) and \(p_1 = 0\). When a reset directly follows a measurement of the same qubit, with no other operation on that qubit in between, the measurement has already collapsed the qubit to the state \(|m\rangle\), where \(m\) is the measured bit. The reset is then equivalent to a Pauli-X gate applied only if \(m = 1\). The pass replaces it by a qibo.gates.U3 gate with angles \(\theta = \lambda = \pi m\) and \(\phi = 0\), which is the identity for \(m = 0\) and exactly the Pauli-X gate for \(m = 1\), with \(m\) given by the symbols of the measurement result, see How to collapse state during measurements?.

Measurements followed by a gate on the same qubit are collapsing measurements in qibo, so every measurement that precedes a reset is handled.

Example

The reset is replaced by a conditional gate, and the circuit ends in the zero state for both measurement outcomes.

from qibo import Circuit, gates
from qibo.transpiler import ResetAfterMeasureSimplification

circuit = Circuit(1, density_matrix=True)
circuit.add(gates.H(0))
circuit.add(gates.M(0))
circuit.add(gates.ResetChannel(0, [1.0, 0.0]))

simplified = ResetAfterMeasureSimplification()(circuit)
print([gate.name for gate in simplified.queue])
['h', 'measure', 'u3']
class qibo.transpiler.ReverseTraversal(routing_algorithm, connectivity=None, depth=None, trials=1, seed=None)[source]#

Places qubits based on the algorithm proposed in Reference [1].

Compatible with all the available qibo.transpiler.abstract.Router classes.

Parameters:
  • connectivity (networkx.Graph) – Hardware connectivity.

  • routing_algorithm (qibo.transpiler.abstract.Router) – Router to be used.

  • depth (int, optional) – Number of two-qubit gates to be considered for routing. If None just one backward step will be implemented. If depth is greater than the number of two-qubit gates in the circuit, the circuit will be routed more than once. Example: on a circuit with four two-qubit gates \(A-B-C-D\) using depth \(d = 6\), the routing will be performed on the circuit \(C-D-D-C-B-A\).

  • trials (int, optional) – Number of independent layout attempts. The first one starts from the layout of the circuit, and the others from random permutations of it. Each attempt finds a layout with the reverse traversal, and the one for which the router adds the fewest SWAP gates to the circuit is kept, the first one among ties. With one attempt, no routing of the circuit is needed to choose the layout. Defaults to \(1\).

  • seed (int, optional) – Seed of the random permutations of the attempts, which makes them reproducible. Defaults to None.

References

1. G. Li, Y. Ding, and Y. Xie, Tackling the Qubit Mapping Problem for NISQ-Era Quantum Devices. arXiv:1809.02573 [cs.ET].

class qibo.transpiler.Sabre(connectivity=None, lookahead=2, decay_lookahead=0.6, delta=0.001, swap_threshold=1.5, seed=None, trials=1, heuristic='decay')[source]#

Routing algorithm proposed in Ref [1].

Parameters:
  • connectivity (networkx.Graph) – Hardware chip connectivity.

  • lookahead (int, optional) – Lookahead factor, how many dag layers will be considered in computing the cost. Defaults to \(2\).

  • decay_lookahead (float, optional) – Value in interval \([0, 1]\). How the weight of the distance in the dag layers decays in computing the cost. Defaults to \(0.6\).

  • delta (float, optional) – Defines the number of SWAPs vs depth trade-off by deciding how the algorithm tends to select non-overlapping SWAPs. Defaults to \(10^{-3}\).

  • swap_threshold (float, optional) – Limits the number of added SWAPs in every routing iteration. This threshold is multiplied by the length of the longest path in the circuit connectivity. If the number of added SWAPs exceeds the threshold before a gate is routed, shortestpath routing is applied. Defaults to \(1.5\).

  • seed (int, optional) – Seed for the candidate random choice as tiebreaker. Defaults to None.

  • trials (int, optional) – Number of independent routing attempts, which differ in the random choices among equally good candidates. The attempt with the lowest number of added SWAPs is returned, and the first one among ties. The attempts are reproducible if seed is given, and the first one does not depend on trials. Defaults to \(1\).

  • heuristic (str, optional) –

    Cost that selects the SWAP gate to be added. It is the distance between the qubits of the gates in the front layer of the circuit, plus the terms below:

    • "basic": no other term, so lookahead, decay_lookahead and delta are not used.

    • "lookahead": the distances of the gates in the next lookahead layers, weighted by decay_lookahead. delta is not used.

    • "decay": the terms of "lookahead", with the distances of the qubits that were recently swapped increased by delta, which favors non-overlapping SWAPs.

    Defaults to "decay".

Raises:

ValueError – If trials is not positive, or heuristic is not "basic", "lookahead" or "decay".

References

1. G. Li, Y. Ding, and Y. Xie, Tackling the Qubit Mapping Problem for NISQ-Era Quantum Devices. arXiv:1809.02573 [cs.ET].

property added_swaps: int#

Returns the number of SWAP gates added to the circuit during routing.

class qibo.transpiler.ShortestPaths(connectivity=None, seed=None)[source]#

A class to perform initial qubit mapping and connectivity matching.

Parameters:
  • connectivity (networkx.Graph) – Chip connectivity.

  • seed (int, optional) – Seed for the random number generator. If None, defaults to \(42\). Defaults to None.

property added_swaps: int#

Returns the number of SWAP gates added to the circuit during routing.

class qibo.transpiler.StarConnectivityPlacer(connectivity=None)[source]#

Find an optimized qubit placement for the following connectivity:

     q
     |
q -- q -- q
     |
     q
Parameters:

connectivity (networkx.Graph, optional) – Star connectivity graph. Defaults to None.

class qibo.transpiler.StarConnectivityRouter(connectivity=None)[source]#

Transforms an arbitrary circuit to one that can be executed on hardware.

This transpiler produces a circuit that respects the following connectivity by adding SWAP gates when needed:

     q
     |
q -- q -- q
     |
     q
Parameters:

connectivity (networkx.Graph) – Star connectivity graph.

class qibo.transpiler.Subgraph(connectivity=None)[source]#

Subgraph isomorphism qubit placer.

Since it is a \(NP\)-complete problem, it can take exponential time for large circuits. This initialization method may fail for very short circuits.

Parameters:

connectivity (networkx.Graph, optional) – Hardware connectivity. Defaults to None.

class qibo.transpiler.TGateRules[source]#

Replaces runs of consecutive qibo.gates.gates.T gates by shorter equivalents.

The \(T\) gate is the diagonal matrix \(\mathrm{diag}(1, e^{i \pi / 4})\), so \(k\) consecutive \(T\) gates on the same qubit give \(\mathrm{diag}(1, e^{i k \pi / 4})\). This only depends on \(k mod 8\), hence eight rules are enough to simplify any number of consecutive \(T\) gates, using the Clifford gates \(S = T^2\) and \(Z = T^4\):

\(k mod 8\)

Replacement

0

identity (no gate)

1

\(T\)

2

\(S\)

3

\(S T\)

4

\(Z\)

5

\(Z T\)

6

\(S^\dagger\)

7

\(T^\dagger\)

All rules are exact, i.e. they hold without any global phase, so they are also correct inside larger circuits. Only \(T\) gates without control qubits are replaced. A run of \(T\) gates on a qubit ends as soon as any other gate, including a measurement or a barrier, acts on that qubit; gates acting on other qubits do not end it.

Example

Five \(T\) gates on the first qubit become \(Z T\). On the second qubit, the controlled-NOT (CNOT) gate ends the run, so the first \(T\) gate stays and the two \(T\) gates after the CNOT become an \(S\) gate.

from qibo import Circuit, gates
from qibo.transpiler import TGateRules

circuit = Circuit(2)
circuit.add(gates.T(0) for _ in range(5))
circuit.add(gates.T(1))
circuit.add(gates.CNOT(0, 1))
circuit.add(gates.T(1))
circuit.add(gates.T(1))

circuit.draw()
print()
TGateRules()(circuit).draw()
0: ─T─T─T─T─T─o─────
1: ─T─────────X─T─T─

0: ─Z─T─o───
1: ─T───X─S─
class qibo.transpiler.TranspileLayout(final_layout, initial_layout, nqubits_input)[source]#

Layout of the qubits of a circuit before and after the routing.

It is the final layout returned by qibo.transpiler.pipeline.Passes. As a dictionary, it maps the label of the physical qubit on which each qubit of the circuit is placed before the routing, to the index of the physical qubit on which it ends after the routing. It can therefore be used wherever a final {logical: physical} qubit mapping is expected. It also stores the layout before the routing. If the pipeline has no router, the qubits are not moved, so the mapping and the routing permutation are the identity, and the initial layout is the one of the transpiled circuit.

Example

A circuit on four qubits is transpiled to a line of five qubits. It is drawn before and after the transpilation with parallel=True, so that the gates on disjoint qubits share a column, and with line_wrap=12, which splits the wider transpiled circuit in two blocks. The routing adds the SWAP gates, drawn as x, and the preprocessing adds the fifth qubit of the line as an auxiliary qubit, which is not used. The \(i\)-th element of the routing permutation is the physical qubit on which the \(i\)-th qubit ends: the first qubit does not move, while the second, third and fourth qubits end on the third, fourth and second physical qubits.

import networkx as nx

from qibo import Circuit, gates
from qibo.transpiler import Passes, Preprocessing, ShortestPaths

circuit = Circuit(4)
circuit.add(gates.H(0))
circuit.add(gates.H(2))
circuit.add(gates.CNOT(0, 1))
circuit.add(gates.CNOT(2, 3))
circuit.add(gates.CNOT(0, 3))
circuit.add(gates.CNOT(1, 2))
circuit.add(gates.M(0, 1, 2, 3))

circuit.draw(parallel=True)
print()

pipeline = Passes(
    [Preprocessing(), ShortestPaths(seed=1)], connectivity=nx.path_graph(5)
)
transpiled_circuit, layout = pipeline(circuit)

transpiled_circuit.draw(parallel=True)
print()

print(dict(layout))
print(layout.initial_layout)
print(layout.routing_permutation)
print(layout.nqubits_input)
print(layout.auxiliary_qubits)
print(layout.initial_logical_layout(filter_auxiliary=True))
print(layout.final_logical_layout(filter_auxiliary=True))
0: ─H─o─o───M─
1: ───X─|─o─M─
2: ─H─o─|─X─M─
3: ───X─X───M─

0: ─H─o───────o─M─
1: ───X─o───x─X─M─
2: ─H─o─X─x─x───M─
3: ───X───x─────M─
4: ───────────────

{0: 0, 1: 2, 2: 3, 3: 1, 4: 4}
[0, 1, 2, 3, 4]
[0, 2, 3, 1, 4]
4
[4]
{0: 0, 1: 1, 2: 2, 3: 3}
{0: 0, 1: 2, 2: 3, 3: 1}
Parameters:
  • final_layout (dict) – Mapping from the label of the physical qubit on which each qubit is placed before the routing, to the index of the physical qubit on which it ends after the routing.

  • initial_layout (list) – Labels of the physical qubits on which the qubits of the circuit are placed before the routing. The \(i\)-th element is the physical qubit of the \(i\)-th qubit of the circuit.

  • nqubits_input (int) – Number of qubits of the circuit before it is padded with the unused qubits of the hardware, which are the auxiliary qubits.

property auxiliary_qubits: list#

Labels of the physical qubits that the preprocessing adds to the circuit as auxiliary qubits.

property final_layout: dict#

Mapping from the physical qubit on which each qubit is placed before the routing, to the one on which it ends after the routing, as a plain dictionary.

property routing_permutation: list[int]#

Permutation of the qubits that the routing applies. Its \(i\)-th element is the index of the physical qubit on which the \(i\)-th qubit of the circuit ends after the routing. The elements from nqubits_input onward are the auxiliary qubits.

final_logical_layout(filter_auxiliary=False)[source]#

Index of the physical qubit on which each qubit of the circuit ends.

Parameters:

filter_auxiliary (bool, optional) – If True, the auxiliary qubits are left out. Defaults to False.

Returns:

Mapping from the index of each qubit of the circuit to the index of the physical qubit on which it ends after the routing, see qibo.transpiler.layout.TranspileLayout.routing_permutation.

Return type:

dict

initial_logical_layout(filter_auxiliary=False)[source]#

Label of the physical qubit on which each qubit of the circuit is placed.

Parameters:

filter_auxiliary (bool, optional) – If True, the auxiliary qubits are left out. Defaults to False.

Returns:

Mapping from the index of each qubit of the circuit to the label of the physical qubit on which it is placed before the routing.

Return type:

dict

class qibo.transpiler.Unroller(native_gates, use_dirty_ancillas=False, min_qubits=1, qubits_initially_zero=False, minimize_toffolis=False, minimize_depth=False, approximation_degree=1.0, threshold=None, weight=None, backend=None)[source]#

Decomposes a circuit to native gates.

Parameters:
  • native_gates (qibo.transpiler.unroller.NativeGates) – Native gates to use in the transpiled circuit. They must be universal, see qibo.transpiler.unroller.NativeGates.is_universal.

  • use_dirty_ancillas (bool, optional) – If True, the qubits of the circuit that a multi-controlled gate does not act on are used as dirty auxiliary qubits in its decomposition, which makes the cost of a multi-controlled qibo.gates.X linear in the number of controls. They can be in any state and are left unchanged. Not using dirty auxiliary qubits makes the gate count quadratic in the number of controls. Defaults to False.

  • min_qubits (int, optional) – Only gates acting on at least min_qubits qubits are decomposed, the others are left unchanged. For example, min_qubits=3 decomposes only gates acting on three or more qubits, which is useful to make a circuit executable by a router before routing. Defaults to \(1\), which decomposes every gate.

  • qubits_initially_zero (bool, optional) – If True, the circuit is assumed to start in the state \(\ket{0}^{\otimes n}\). The qubits on which no previous gate of the circuit acts are then known to be in the state \(\ket{0}\), and are used as clean auxiliary qubits in the decomposition of multi-controlled gates, being left in that state. They are used as clean qubits if that lowers the number of native gates, and as dirty ones otherwise. It must be False if the circuit is executed with another initial state. Defaults to False.

  • minimize_toffolis (bool, optional) – If True, a multi-controlled qibo.gates.X with at least one auxiliary qubit is decomposed with the lowest number of Toffoli gates, which is useful if they are expensive. This is not the decomposition with the fewest native gates, which is the default one, and it needs more of them with dirty auxiliary qubits. With clean auxiliary qubits (see qubits_initially_zero), it is shallower. It cannot be used together with minimize_depth. Defaults to False.

  • minimize_depth (bool, optional) – If True, a multi-controlled qibo.gates.X with at least one auxiliary qubit is decomposed with a depth that is logarithmic in the number of controls. It cannot be used together with minimize_toffolis. See qibo.transpiler.multi_controlled_decomposition(). Defaults to False.

  • approximation_degree (float, optional) – Fidelity of each qibo.gates.CZ gate, between \(0\) and \(1\). If it is lower than \(1\), the two-qubit gates that are decomposed from their matrix, i.e. the unitaries, the qibo.gates.fSim and qibo.gates.GeneralizedfSim gates and the gates without a known decomposition, can use fewer qibo.gates.CZ gates than the exact decomposition, if it increases the expected fidelity, see qibo.transpiler.decompositions.unitary_decompositions.two_qubit_decomposition(). The other gates are decomposed exactly. Defaults to \(1\), which is the exact decomposition.

  • threshold (float, optional) – Tolerance of the decomposition of the two-qubit gates that are decomposed from their matrix, like for approximation_degree, see qibo.transpiler.decompositions.unitary_decompositions.two_qubit_decomposition(). Defaults to None, which uses its default value.

  • weight (float, optional) – Weight of the imaginary part in the matrix that is diagonalized to decompose those gates, see qibo.transpiler.decompositions.unitary_decompositions.two_qubit_decomposition(). Defaults to None, which uses its default value.

  • backend (qibo.backends.abstract.Backend, optional) – Backend to use for gate matrix. Defaults to None.

Raises:

ValueError – If approximation_degree is not between \(0\) and \(1\).

class qibo.transpiler.Validation(connectivity=None, initial_layout=None)[source]#

Checks that the circuit can be transpiled, before any other pass runs.

Without it, a circuit that cannot be transpiled fails deep in the pipeline. This pass raises a clear error at the start instead. It does not change the circuit. It checks that:

  • the circuit has no noise channel. The transpiler works with unitary gates, so the only channel that it accepts is qibo.gates.channels.ResetChannel.

  • the circuit has no more qubits than the hardware, if there is a connectivity.

  • the initial layout, if there is one, places every qubit of the circuit on a distinct node of the connectivity, see qibo.transpiler.optimizer.Preprocessing.

Parameters:
  • connectivity (networkx.Graph, optional) – Hardware connectivity. If None, neither the number of qubits nor the initial layout are checked. It is set by qibo.transpiler.pipeline.Passes. Defaults to None.

  • initial_layout (list, optional) – Physical qubits on which the logical qubits of the circuit are placed, in the format of qibo.transpiler.optimizer.Preprocessing. Defaults to None.

Example

The pass raises an error for the noise channel before the circuit is transpiled.

from qibo import Circuit, gates
from qibo.transpiler import Validation

circuit = Circuit(1)
circuit.add(gates.H(0))
circuit.add(gates.PauliNoiseChannel([0], [("X", 0.1)]))

try:
    Validation()(circuit)
except Exception as error:
    print(type(error).__name__)
DecompositionError
qibo.transpiler.generate_preset_passes(connectivity=None, native_gates=None, initial_layout=None, seed=None, on_qubits=None, qubits_initially_zero=False, layout_method=None, routing_method=None, layout_trials=None, callback=None, approximation_degree=1.0, threshold=None, weight=None, use_dirty_ancillas=True, minimize_toffolis=None, minimize_depth=False, routing_trials=5, optimization_level=0, sampling_only=False, backend=None)[source]#

Generate a ready-to-use transpiler pipeline.

With the default optimization_level=0, the pipeline only makes the circuit executable on the hardware, with the following passes:

  1. qibo.transpiler.optimizer.Validation raises an error at the start if the circuit cannot be transpiled, e.g. if it has a noise channel, more qubits than the hardware, or an invalid initial_layout.

  2. qibo.transpiler.unroller.Unroller decomposes the gates acting on three or more qubits, which the router does not support. The qubits of the circuit that a multi-controlled gate does not act on are used as auxiliary qubits, see qubits_initially_zero.

  3. qibo.transpiler.optimizer.Preprocessing places the qubits on initial_layout, or on the first nodes of the connectivity if there is none, and pads the circuit with the unused qubits of the hardware.

  4. The placer selected by layout_method, if it is not the trivial one.

  5. The router selected by routing_method, which adds the SWAP gates that make all the gates executable. By default, it is qibo.transpiler.router.Sabre with the best of routing_trials trials.

  6. qibo.transpiler.unroller.Unroller decomposes the circuit to the native gates, approximating some two-qubit gates with fewer entangling gates if approximation_degree is lower than \(1\).

If connectivity is directed, the pipeline ends with a qibo.transpiler.optimizer.GateDirection pass and an unroller, so that the two-qubit gates act in the direction of the edges.

The optimization levels \(1\) and \(2\) add optimizers to the pipeline. The ones that can write gates that are not native run before the last unroller. Level \(1\) adds:

Level \(2\) adds, to the optimizers of level \(1\):

The optimizers can change the global phase of the circuit, and the gates that they merge do not keep their trainable parameters.

Without connectivity there is nothing to place and route, so the pipeline only validates the circuit and decomposes it to the native gates.

Parameters:
  • connectivity (networkx.Graph, optional) – Hardware connectivity. It can be directed, see above. Defaults to None.

  • native_gates (qibo.transpiler.unroller.NativeGates, optional) – Native gates supported by the hardware. Defaults to qibo.transpiler.unroller.NativeGates.default().

  • initial_layout (list or dict, optional) – Physical qubits on which the logical qubits of the circuit are placed, as a list or as a dictionary that may leave qubits out, see qibo.transpiler.optimizer.Preprocessing. It requires connectivity, and it cannot be used with a non-trivial layout_method. Defaults to None.

  • seed (int, optional) – Seed of the random choices of the placer and the router, which makes the transpilation reproducible. Defaults to None.

  • on_qubits (list, optional) – List of qubits to be used in the transpiler, see qibo.transpiler.pipeline.Passes. It requires connectivity. Defaults to None.

  • qubits_initially_zero (bool, optional) – If True, the circuit is assumed to start in the state \(\ket{0}^{\otimes n}\). The qubits on which no previous gate acts are then used as clean auxiliary qubits to decompose multi-controlled gates, with the construction that minimizes the number of Toffoli gates, see qibo.transpiler.unroller.Unroller. This makes the decomposition shallower and, for many controls, also shorter. If there are no clean qubits for a gate, its dirty auxiliary qubits are used with the same construction, which has more CNOT gates than the default one. Without qubits_initially_zero the other qubits are used as dirty auxiliary qubits, which can be in any state. It must be False if the circuit is executed with another initial state. Defaults to False.

  • layout_method (str or qibo.transpiler.abstract.Placer, optional) – Placer that finds the initial layout. It can be "trivial", which keeps the wire_names of the circuit if they are nodes of the connectivity and uses the first nodes otherwise, "dense" (qibo.transpiler.placer.Dense), "random" (qibo.transpiler.placer.Random), "subgraph" (qibo.transpiler.placer.Subgraph), "reverse_traversal" (qibo.transpiler.placer.ReverseTraversal with qibo.transpiler.router.Sabre), "star" (qibo.transpiler.placer.StarConnectivityPlacer) or a placer. It requires connectivity. Defaults to None, which is "trivial", or "reverse_traversal" if optimization_level is at least \(1\), initial_layout is None and routing_method is None or "sabre".

  • routing_method (str or qibo.transpiler.abstract.Router, optional) – Router that makes the circuit executable. It can be "sabre" (qibo.transpiler.router.Sabre with routing_trials trials), "shortest_paths" (qibo.transpiler.router.ShortestPaths), "star" (qibo.transpiler.router.StarConnectivityRouter), "none" (qibo.transpiler.router.NoRouting, which raises an error if the circuit needs routing) or a router. A router is skipped if the circuit already respects the connectivity. It requires connectivity. Defaults to None, which is "sabre".

  • layout_trials (int, optional) – Number of layout attempts of the "reverse_traversal" placer, see qibo.transpiler.placer.ReverseTraversal. It is not used by the other placers. Defaults to None, which is \(1\) if optimization_level is \(0\) and \(5\) otherwise.

  • callback (Callable, optional) – Function called after each pass that runs, see qibo.transpiler.pipeline.Passes. Defaults to None.

  • approximation_degree (float, optional) – Fidelity of each entangling gate, between \(0\) and \(1\). If it is lower than \(1\), the unroller that decomposes the circuit to the native gates approximates the two-qubit gates that it decomposes from their matrix with fewer qibo.gates.CZ gates, if it increases the expected fidelity, see qibo.transpiler.unroller.Unroller. The multi-qubit gates are decomposed exactly. Defaults to \(1\), which is the exact decomposition.

  • threshold (float, optional) – Tolerance of the decomposition of the two-qubit gates that are decomposed from their matrix, like for approximation_degree, see qibo.transpiler.unroller.Unroller. Defaults to None, which uses the default value of the decomposition.

  • weight (float, optional) – Weight of the imaginary part in the matrix that is diagonalized to decompose those gates, like for approximation_degree, see qibo.transpiler.unroller.Unroller. Defaults to None, which uses the default value of the decomposition.

  • use_dirty_ancillas (bool, optional) – If True, the qubits that a multi-controlled gate does not act on are used as dirty auxiliary qubits in its decomposition, see qibo.transpiler.unroller.Unroller. Defaults to True.

  • minimize_toffolis (bool, optional) – If True, the multi-controlled qibo.gates.X gates with auxiliary qubits are decomposed with the lowest number of Toffoli gates, see qibo.transpiler.unroller.Unroller. It cannot be used together with minimize_depth. Defaults to None, which is qubits_initially_zero unless minimize_depth is True.

  • minimize_depth (bool, optional) – If True, the multi-controlled qibo.gates.X gates with auxiliary qubits are decomposed with a depth that is logarithmic in the number of controls, see qibo.transpiler.unroller.Unroller. It cannot be used together with minimize_toffolis. Defaults to False.

  • routing_trials (int, optional) – Number of independent routing attempts of the "sabre" router, which keeps the best one, see qibo.transpiler.router.Sabre. It is not used by the other routers, nor by the router of the "reverse_traversal" placer. Defaults to \(5\).

  • optimization_level (int, optional) – Optimization level of the pipeline. It can be \(0\), which is the pipeline described above, that only makes the circuit executable on the hardware and does not optimize it, or \(1\) or \(2\), which add the optimizers described above. Defaults to \(0\).

  • sampling_only (bool, optional) – If True, the optimizers of levels \(1\) and \(2\) also use the rewrites that keep the distribution of the measured qubits, but can change the state of the circuit before the measurements: qibo.transpiler.optimizer.OptimizeSwapBeforeMeasure, qibo.transpiler.optimizer.RemoveDiagonalGatesBeforeMeasurement and qibo.transpiler.optimizer.RemoveFinalReset, before the layout. It has no effect if optimization_level is \(0\). Defaults to False.

  • backend (qibo.backends.abstract.Backend, optional) – Backend used by the unrollers to compute the gate matrices, unless another one is given when the pipeline is called. If None, it uses the global backend. Defaults to None.

Returns:

Transpiler pipeline.

Return type:

qibo.transpiler.pipeline.Passes

Raises:
  • ConnectivityError – If initial_layout, layout_method or routing_method is given without connectivity.

  • PlacementError – If initial_layout is given with a non-trivial layout_method.

  • ValueError – If layout_method or routing_method is not a valid method, if approximation_degree is not between \(0\) and \(1\), if minimize_toffolis and minimize_depth are both True, if routing_trials is not positive and the router is "sabre", or if optimization_level is not a valid level.

qibo.transpiler.multi_controlled_decomposition(unitary, controls, target, free=(), *, clean=(), minimize_toffolis=False, minimize_depth=False, backend=None)[source]#

Decomposes a multi-controlled single-qubit gate, with or without auxiliary qubits.

The decomposition is exact, including the global phase. By default, the one with the fewest CNOTs is used. Gates with \(\det(U) = 1\) are decomposed with a number of CNOTs that grows linearly with the number of controls following Ref. [1], and other gates quadratically following Ref. [2]. This is the decomposition used by qibo.gates.abstract.Gate.decompose() for gates controlled by more than one qubit.

A multi-controlled \(X\) gate has dedicated decompositions that profit from auxiliary qubits, and free and clean are ignored for other gates. Without auxiliary qubits, the gates with three and four controls use \(14\) and \(36\) CNOTs, respectively. For more controls, the one with the fewest CNOTs for the given auxiliary qubits is chosen, where \(k\) is the number of controls:

  • With at least \(k - 2\) auxiliary qubits, clean or dirty, the cost is linear with a small slope following Ref. [3].

  • With fewer qubits, but at least one clean qubit, Ref. [4] uses \(2k - 3\) Toffoli gates. Two clean qubits make the depth logarithmic in \(k\), and one makes it linear.

  • With dirty qubits only, the cost is linear following Ref. [3] with one auxiliary qubit.

Clean qubits are only used as such if they are needed to reduce the number of CNOTs. Otherwise, they are used as dirty qubits.

If minimize_toffolis is True, a multi-controlled \(X\) gate with at least one auxiliary qubit is decomposed following Ref. [4] instead. This uses \(2k - 3\) Toffoli gates for \(k\) controls with clean qubits, and \(4k - 8\) with dirty qubits, which is the lowest count, but not necessarily the lowest number of CNOTs. Two auxiliary qubits make the depth logarithmic in \(k\), and one makes it linear. Clean qubits are preferred over dirty ones.

If minimize_depth is True, a multi-controlled \(X\) gate with at least one auxiliary qubit is decomposed with a depth that is logarithmic in \(k\). With two or more auxiliary qubits this follows Ref. [4], and with one follows Ref. [5] (see also Ref. [6]). The number of gates is linear in \(k\) in both cases, but larger than the default for one auxiliary qubit, so the depth is lower only for tens of controls or more.

Parameters:
  • unitary (ArrayLike) – \(2 \times 2\) unitary matrix of the target gate.

  • controls (tuple[int, ...]) – Ids of the control qubits.

  • target (int) – Id of the target qubit.

  • free (tuple[int, ...], optional) – Ids of free qubits that can be used as dirty auxiliary qubits, that is, they can be in any state and are left unchanged. Defaults to (), which uses no auxiliary qubits.

  • clean (tuple[int, ...], optional) – Ids of free qubits that are known to be in the state \(\ket{0}\), and are left in that state. They are used as dirty qubits when this is cheaper. Defaults to ().

  • minimize_toffolis (bool, optional) – If True, a multi-controlled \(X\) gate with auxiliary qubits minimizes the number of Toffoli gates instead of the number of CNOTs. Defaults to False.

  • minimize_depth (bool, optional) – If True, a multi-controlled \(X\) gate with auxiliary qubits minimizes the depth instead of the number of CNOTs. It cannot be used together with minimize_toffolis. Defaults to False.

  • backend (qibo.backends.abstract.Backend, optional) – Backend of unitary, which is only used to move it to the CPU, since the decomposition is always computed with NumPy. If None, it uses the current backend. Defaults to None.

Returns:

Gates that have the same effect as the multi-controlled gate.

Return type:

list[qibo.gates.abstract.Gate]

References

1. R. Vale, T. M. D. Azevedo, I. C. S. Araújo, I. F. Araujo, and A. J. da Silva, Circuit Decomposition of Multi-Controlled Special Unitary Single-Qubit Gates, IEEE Trans. Comput.-Aided Des. Integr. Circuits Syst.

2. A. J. da Silva and D. K. Park, Linear-depth quantum circuits for multiqubit controlled gates, Phys. Rev. A 106, 042602 (2022).

3. R. Iten, R. Colbeck, I. Kukuljan, J. Home, and M. Christandl, Quantum circuits for isometries, Phys. Rev. A 93, 032318 (2016).

4. T. Khattar and C. Gidney, Rise of conditionally clean ancillae for efficient quantum circuit constructions, Quantum 9, 1752 (2025).

5. J. Nie, W. Zi, and X. Sun, Quantum circuit for multi-qubit Toffoli gate with optimal resource, arXiv:2402.05053.

6. V. Vandaele, Asymptotically optimal quantum circuits for comparators and incrementers, arXiv:2603.12917.

qibo.transpiler.quantum_shannon_decomposition(qubits, unitary, backend=None)[source]#

Quantum Shannon decomposition of a unitary matrix acting on several qubits.

The cosine-sine decomposition of an \(n\)-qubit unitary \(U\), whose blocks are defined by the first qubit, is

\[\begin{split}U = (A_{1} \oplus A_{2}) \, \begin{pmatrix} C & -S \\ S & C \end{pmatrix} \, (B_{1} \oplus B_{2}) \, ,\end{split}\]

where the middle matrix is a multiplexed qibo.gates.gates.RY rotation of the first qubit, controlled by the other \(n - 1\) qubits. Each block-diagonal matrix is decomposed as

\[X_{1} \oplus X_{2} = (\mathbb{I} \otimes V) \, (D \oplus D^{\dagger}) \, (\mathbb{I} \otimes W) \, ,\]

with \(V D^{2} V^{\dagger} = X_{1} X_{2}^{\dagger}\) and \(W = D V^{\dagger} X_{2}\), where \(D \oplus D^{\dagger}\) is a multiplexed qibo.gates.gates.RZ rotation of the first qubit. The result has four unitaries on the last \(n - 1\) qubits and three multiplexed rotations, i.e. qibo.gates.gates.Multiplexor gates. Decomposing the unitaries in the same way until they act on two qubits uses \((9 / 16) \, 4^{n} - (3 / 2) \, 2^{n}\) two-qubit gates, if the unitaries on two qubits are decomposed with three of them. It does not use auxiliary qubits.

Parameters:
  • qubits (tuple[int, ...]) – Ids of the qubits that unitary acts on, with at least two. The first one is the most significant bit of the indices of unitary.

  • unitary (ArrayLike) – Unitary matrix of shape \((2^{n}, 2^{n})\), with \(n\) the number of qubits.

  • backend (qibo.backends.abstract.Backend, optional) – Backend to use for the matrices. If None, defaults to the global backend. Defaults to None.

Returns:

Gates, in order of application, that implement unitary: four qibo.gates.gates.Unitary gates on qubits[1:] and three qibo.gates.gates.Multiplexor gates. Each of them acts on less qubits than unitary, so they can be decomposed in turn.

Return type:

list[qibo.gates.abstract.Gate]

Raises:

ValueError – If there are less than two qubits, or unitary does not have the shape \((2^{n}, 2^{n})\).

References

1. V. V. Shende, S. S. Bullock, and I. L. Markov, Synthesis of quantum-logic circuits, IEEE Trans. Comput.-Aided Des. Integr. Circuits Syst. 25, 1000 (2006).

qibo.transpiler.transpile(circuits, connectivity=None, native_gates=None, initial_layout=None, seed=None, on_qubits=None, qubits_initially_zero=False, layout_method=None, routing_method=None, layout_trials=None, callback=None, approximation_degree=1.0, threshold=None, weight=None, use_dirty_ancillas=True, minimize_toffolis=None, minimize_depth=False, routing_trials=5, optimization_level=0, sampling_only=False, num_processes=None, backend=None)[source]#

Transpile one or more circuits in a single call.

It builds the pipeline of qibo.transpiler.pipeline.generate_preset_passes() once and applies it to the circuits, which makes them executable on the hardware, and optimizes them if optimization_level is higher than \(0\). All the arguments except circuits and num_processes are the ones of qibo.transpiler.pipeline.generate_preset_passes(), and they are shared by all the circuits.

The circuits of a list can be transpiled in parallel with num_processes. Each of them is then transpiled by a worker process with its own copy of the pipeline, so the result for a circuit does not depend on the other circuits nor on num_processes, and, with a seed, it is the one of transpiling the circuit alone. This can differ from the sequential result, where the random state of the placer and the router continues from one circuit to the next. The worker processes are started with the spawn method, so the script that calls qibo.transpiler.pipeline.transpile() must protect the call with if __name__ == "__main__":. They do not share the global backend either, so backend should be given if it is not the default one.

Example

A rotation, a Toffoli gate and a CNOT gate between qubits that are not neighbors are transpiled to a line of four qubits, where qibo.gates.CNOT and qibo.gates.U3 are the native gates. The Toffoli gate is decomposed, the SWAP gates that the routing adds are decomposed too, and the transpiled circuit is drawn. The decomposition of the Toffoli gate leaves a global phase, which the drawing reports. If circuits is a list, the result is a list with a transpiled circuit and a final layout for each circuit.

import networkx as nx

from qibo import Circuit, gates
from qibo.backends import NumpyBackend
from qibo.transpiler import NativeGates, transpile
from qibo.transpiler.asserts import assert_transpiling

backend = NumpyBackend()

circuit = Circuit(4)
circuit.add(gates.RY(0, 0.5))
circuit.add(gates.TOFFOLI(0, 1, 2))
circuit.add(gates.CNOT(0, 3))
circuit.add(gates.M(*range(4)))

connectivity = nx.path_graph(4)
natives = NativeGates.from_gatelist([gates.U3, gates.CNOT, gates.M])
transpiled_circuit, final_layout = transpile(
    circuit, connectivity, natives, seed=42, backend=backend
)

# Raises an error if the circuits are not equivalent, or if the transpiled circuit
# does not respect the connectivity and the native gates
assert_transpiling(
    original_circuit=circuit,
    transpiled_circuit=transpiled_circuit,
    connectivity=connectivity,
    final_layout=final_layout,
    native_gates=natives,
)

print(final_layout)
transpiled_circuit.draw(line_wrap=1000, parallel=True)
{0: 1, 1: 3, 2: 0, 3: 2}
Global phase = exp(i * 9π/8)
0: ─U3────────────o─────────o───────o─X─o────────────────M─
1: ────o────o─X─o─X─RZ─X─RZ─X─RZ─U3─X─o─X─o─RZ─o───────o─M─
2: ─U3─X─RZ─X─o─X──────o─RZ───────────────X─RZ─X─o─X─o─X─M─
3: ──────────────────────────────────────────────X─o─X───M─
Parameters:
  • circuits (qibo.models.circuit.Circuit or list or tuple) – Circuit, or list of circuits, to be transpiled.

  • connectivity (networkx.Graph, optional) – Hardware connectivity. Defaults to None.

  • native_gates (qibo.transpiler.unroller.NativeGates, optional) – Native gates supported by the hardware. Defaults to qibo.transpiler.unroller.NativeGates.default().

  • initial_layout (list or dict, optional) – Physical qubits on which the logical qubits of the circuits are placed, see qibo.transpiler.optimizer.Preprocessing. Defaults to None.

  • seed (int, optional) – Seed of the random choices of the placer and the router. Defaults to None.

  • on_qubits (list, optional) – List of qubits to be used in the transpiler. Defaults to None.

  • qubits_initially_zero (bool, optional) – If True, the circuits are assumed to start in the state \(\ket{0}^{\otimes n}\). Defaults to False.

  • layout_method (str or qibo.transpiler.abstract.Placer, optional) – Placer that finds the initial layout. Defaults to None.

  • routing_method (str or qibo.transpiler.abstract.Router, optional) – Router that makes the circuits executable. Defaults to None.

  • layout_trials (int, optional) – Number of layout attempts of the "reverse_traversal" placer. Defaults to None, which is \(1\) if optimization_level is \(0\) and \(5\) otherwise.

  • callback (Callable, optional) – Function called after each pass that runs, for each circuit, see qibo.transpiler.pipeline.Passes. It cannot be used with more than one process. Defaults to None.

  • approximation_degree (float, optional) – Fidelity of each entangling gate, between \(0\) and \(1\). If it is lower than \(1\), the two-qubit gates that are decomposed from their matrix are approximated with fewer entangling gates, if it increases the expected fidelity, see qibo.transpiler.pipeline.generate_preset_passes(). Defaults to \(1\), which is the exact decomposition.

  • threshold (float, optional) – Tolerance of the decomposition of the two-qubit gates that are decomposed from their matrix, like for approximation_degree. Defaults to None, which uses the default value of the decomposition.

  • weight (float, optional) – Weight of the imaginary part in the matrix that is diagonalized to decompose those gates, like for approximation_degree. Defaults to None, which uses the default value of the decomposition.

  • use_dirty_ancillas (bool, optional) – If True, the qubits that a multi-controlled gate does not act on are used as dirty auxiliary qubits in its decomposition. Defaults to True.

  • minimize_toffolis (bool, optional) – If True, the multi-controlled qibo.gates.X gates with auxiliary qubits are decomposed with the lowest number of Toffoli gates. It cannot be used together with minimize_depth. Defaults to None, which is qubits_initially_zero unless minimize_depth is True.

  • minimize_depth (bool, optional) – If True, the multi-controlled qibo.gates.X gates with auxiliary qubits are decomposed with a depth that is logarithmic in the number of controls. It cannot be used together with minimize_toffolis. Defaults to False.

  • routing_trials (int, optional) – Number of independent routing attempts of the "sabre" router, which keeps the best one. It is not used by the other routers. Defaults to \(5\).

  • optimization_level (int, optional) – Optimization level of the pipeline, see qibo.transpiler.pipeline.generate_preset_passes(). It can be \(0\), \(1\) or \(2\). Defaults to \(0\), which does not optimize the circuits.

  • sampling_only (bool, optional) – If True, the optimizers also use the rewrites that keep the distribution of the measured qubits but can change the state of the circuit before the measurements, see qibo.transpiler.pipeline.generate_preset_passes(). It logs a warning for the circuits without measurements, and it has no effect if optimization_level is \(0\). Defaults to False.

  • num_processes (int, optional) – Number of worker processes that transpile the circuits of a list in parallel. If None or \(1\), the circuits are transpiled one after the other in the current process. It has no effect on a single circuit, and no more processes than circuits are started. Defaults to None.

  • backend (qibo.backends.abstract.Backend, optional) – Backend used by the unrollers to compute the gate matrices, and passed to the other passes that take one. If None, they use the global backend. Defaults to None.

Returns:

(qibo.models.circuit.Circuit, qibo.transpiler.layout.TranspileLayout) or list: Transpiled circuit and final {logical: physical} qubit mapping, see qibo.transpiler.pipeline.Passes. If circuits is a list or tuple, the list of these pairs, in the same order.

Raises:
  • ValueError – If num_processes is not a positive integer, if callback is given with more than one process, if approximation_degree is not between \(0\) and \(1\), if minimize_toffolis and minimize_depth are both True, if routing_trials is not positive and the router is "sabre", or if optimization_level is not a valid level.

  • BrokenProcessPool – If a worker process ends abruptly. A common cause is a script that calls this function with several processes without the if __name__ == "__main__": guard.

Return type:

tuple[qibo.models.circuit.Circuit, dict | None] | list[tuple[qibo.models.circuit.Circuit, dict | None]]

Abstract classes#

Optimizer#

class qibo.transpiler.abstract.Optimizer[source]#

Reduces the number of gates in the circuit.

Placer#

class qibo.transpiler.abstract.Placer(connectivity, *args)[source]#

Maps logical qubits to physical qubits.

Parameters:

connectivity (Graph) –

property connectivity: Graph | None#

Hardware topology. A directed graph is stored as an undirected one, because the direction of the connections does not matter to place the qubits.

Router#

class qibo.transpiler.abstract.Router(connectivity, *args)[source]#

Makes the circuit executable on the given topology.

Parameters:

connectivity (Graph) –

property connectivity: Graph | None#

Hardware topology. A directed graph is stored as an undirected one, because the direction of the connections does not matter to route the qubits.

Asserts#

Assert circuit equivalence#

qibo.transpiler.asserts.assert_circuit_equivalence(original_circuit, transpiled_circuit, final_layout, test_states=None, ntests=3)[source]#

Checks that the transpiled circuit is equivalent to the original one.

Parameters:
  • original_circuit (qibo.models.circuit.Circuit) – Circuit before transpiling.

  • transpiled_circuit (qibo.models.circuit.Circuit) – Circuit after transpiling.

  • final_layout (dict) – Final {logical: physical} qubit mapping.

  • test_states (list, optional) – List of states to test the equivalence. If None, ntests random states will be tested. Defaults to None.

  • ntests (int, optional) – Number of random states to test the equivalence. Defaults to \(3\).

Return type:

None

Assert connectivity#

qibo.transpiler.asserts.assert_connectivity(connectivity, circuit)[source]#

Assert if a circuit can be executed on Hardware.

No gates acting on more than two qubits, except for measurements and barriers. All two-qubit operations can be performed on hardware. Measurements and barriers do not require the qubits to be connected.

Parameters:
Return type:

None

Assert decomposition#

qibo.transpiler.asserts.assert_decomposition(circuit, native_gates)[source]#

Checks if a circuit has been correctly decomposed into native gates.

The identity gate, the global phase gates, the measurements, the resets and the directives are not checked, and qibo.gates.gates.Z and qibo.gates.gates.RZ are also accepted if qibo.gates.gates.U3 or qibo.gates.gates.GPI2 are native, because they are virtual gates that are assumed to be available together with them, as in qibo.transpiler.unroller.Unroller.

Parameters:
Return type:

None

Assert placement#

qibo.transpiler.asserts.assert_placement(circuit, connectivity)[source]#

Check if the layout of the circuit is consistent with the circuit and connectivity graph.

Parameters:
  • circuit (qibo.models.circuit.Circuit) – Circuit to check.

  • connectivity (networkx.Graph) – Hardware connectivity. A ValueError is raised if it is None.

Return type:

None

Assert transpilation#

qibo.transpiler.asserts.assert_transpiling(original_circuit, transpiled_circuit, connectivity, final_layout, native_gates=None, check_circuit_equivalence=True)[source]#

Check that all transpiler passes have been executed correctly.

Parameters:
Return type:

None

Blocks#

Block#

class qibo.transpiler.blocks.Block(qubits, gates, name=None)[source]#

A block contains a subset of gates acting on two qubits.

The only blocks that act on more than two qubits are the ones with a qibo.gates.special.Barrier on more than two qubits, which have no other gate acting on more than one qubit.

Parameters:
  • qubits (tuple) – qubits where the block is acting.

  • gates (list) – list of gates that compose the block.

  • name (str or int, optional) – name of the block. Defaults to None.

property entangled: bool#

Returns True if the block contains two-qubit gates, which are not barriers.

add_gate(gate)[source]#

Add a new gate to the block.

Parameters:

gate (qibo.gates.abstract.Gate) – gate to be added.

Return type:

None

property qubits: tuple#

Returns a sorted tuple with qubits of the block.

fuse(block, name=None)[source]#

Fuses the current block with a new one, the qubits they are acting on must coincide.

Parameters:
Returns:

fusion of the two input blocks.

Return type:

(qibo.transpiler.blocks.Block)

on_qubits(new_qubits)[source]#

Return a new block acting on the new qubits.

Parameters:

new_qubits (tuple) – new qubits where the block is acting.

Return type:

Block

commute(block)[source]#

Check if a block commutes with the current one.

Parameters:

block (qibo.transpiler.blocks.Block) – block to check commutation.

Returns:

True if the two blocks don’t share any qubit. False otherwise.

Return type:

bool

kak_decompose(weight=1.4142135623730951, backend=None)[source]#

Return the gates of the block with as few CZ gates as the KAK decomposition allows.

The KAK (Cartan) decomposition writes the \(4 \times 4\) matrix of the block with at most three controlled-Z (CZ) gates and single-qubit qibo.gates.Unitary gates [1]. It replaces the gates of the block only if it has fewer CZ gates than the block, where a qibo.gates.SWAP gate counts as three CZ gates, an qibo.gates.iSWAP gate as two and any other two-qubit gate as one. Otherwise the gates of the block are returned. All the gates of the block must be unitary.

Parameters:
  • weight (float, optional) – Weight of the imaginary part in the matrix that is diagonalized to find the local gates, see qibo.transpiler.decompositions.unitary_decompositions.calculate_psi(). Defaults to \(\sqrt{2}\).

  • backend (qibo.backends.abstract.Backend, optional) – Backend used to build the matrix of the block. If None, defaults to the global backend. Defaults to None.

Returns:

Gates equivalent to the block.

Return type:

list[qibo.gates.abstract.Gate]

References

1. F. Vatan and C. Williams, Optimal quantum circuits for general two-qubit gates, Phys. Rev. A 69, 032315 (2004).

Circuit blocks#

class qibo.transpiler.blocks.CircuitBlocks(circuit, index_names=False)[source]#

A CircuitBlocks contains a quantum circuit decomposed in two qubits blocks.

Parameters:
search_by_index(index)[source]#

Find a block from its index, requires index_names to be True.

Parameters:

index (int) –

Return type:

Block

add_block(block)[source]#

Add a two qubits block.

Parameters:

block (Block) –

Return type:

None

circuit(circuit_kwargs=None)[source]#

Return the quantum circuit.

Parameters:

circuit_kwargs (dict, optional) – original circuit init_kwargs. If None, a circuit with the same number of qubits as the decomposed circuit is created. Defaults to None.

Returns:

Circuit made of the gates of the blocks.

Return type:

qibo.models.circuit.Circuit

remove_block(block)[source]#

Remove a block from the circuit blocks.

Parameters:

block (Block) –

Return type:

None

return_last_block()[source]#

Return the last block in the circuit blocks.

Return type:

Block

Block decomposition#

qibo.transpiler.blocks.block_decomposition(circuit, fuse=True)[source]#

Decompose a circuit into blocks of gates acting on two qubits. Break measurements on multiple qubits into measurements of single qubits.

Parameters:
  • circuit (qibo.models.circuit.Circuit) – circuit to be decomposed.

  • fuse (bool, optional) – fuse adjacent blocks acting on the same qubits. Defaults to True.

Returns:

List of blocks that act on two qubits.

Return type:

list[qibo.transpiler.blocks.Block]

Decompositions#

Gate decompositions#

class qibo.transpiler.decompositions.GateDecompositions[source]#

Abstract data structure that holds decompositions of gates.

add(gate, decomposition)[source]#

Register a decomposition for a gate.

get_precision()[source]#

Return the precision of the Clifford + qibo.gates.gates.T decompositions.

Returns:

precision \(\epsilon\) of the approximation of each gate, which is an mpmath number, and number of decimal places used by mpmath. Both are None if the precision was not set.

Return type:

tuple

set_precision(epsilon=1e-16, mpmath_dps=256)[source]#

Set the precision of the Clifford + qibo.gates.gates.T decompositions.

Parameters:
  • epsilon (float, optional) – precision \(\epsilon\) of the approximation of each gate. Defaults to \(10^{-16}\).

  • mpmath_dps (int, optional) – number of decimal places used by the mpmath package. Defaults to \(256\).

count_2q(gate, backend=None)[source]#

Count the number of two-qubit gates in the decomposition of the given gate.

Parameters:
  • gate (Gate) –

  • backend (Backend | None) –

Return type:

int

count_1q(gate, backend=None)[source]#

Count the number of single qubit gates in the decomposition of the given gate.

Parameters:
  • gate (Gate) –

  • backend (Backend | None) –

Return type:

int

Multi-controlled gate decomposition#

See How to decompose multi-controlled gates? for examples of all the options.

qibo.transpiler.multi_controlled_decomposition(unitary, controls, target, free=(), *, clean=(), minimize_toffolis=False, minimize_depth=False, backend=None)[source]#

Decomposes a multi-controlled single-qubit gate, with or without auxiliary qubits.

The decomposition is exact, including the global phase. By default, the one with the fewest CNOTs is used. Gates with \(\det(U) = 1\) are decomposed with a number of CNOTs that grows linearly with the number of controls following Ref. [1], and other gates quadratically following Ref. [2]. This is the decomposition used by qibo.gates.abstract.Gate.decompose() for gates controlled by more than one qubit.

A multi-controlled \(X\) gate has dedicated decompositions that profit from auxiliary qubits, and free and clean are ignored for other gates. Without auxiliary qubits, the gates with three and four controls use \(14\) and \(36\) CNOTs, respectively. For more controls, the one with the fewest CNOTs for the given auxiliary qubits is chosen, where \(k\) is the number of controls:

  • With at least \(k - 2\) auxiliary qubits, clean or dirty, the cost is linear with a small slope following Ref. [3].

  • With fewer qubits, but at least one clean qubit, Ref. [4] uses \(2k - 3\) Toffoli gates. Two clean qubits make the depth logarithmic in \(k\), and one makes it linear.

  • With dirty qubits only, the cost is linear following Ref. [3] with one auxiliary qubit.

Clean qubits are only used as such if they are needed to reduce the number of CNOTs. Otherwise, they are used as dirty qubits.

If minimize_toffolis is True, a multi-controlled \(X\) gate with at least one auxiliary qubit is decomposed following Ref. [4] instead. This uses \(2k - 3\) Toffoli gates for \(k\) controls with clean qubits, and \(4k - 8\) with dirty qubits, which is the lowest count, but not necessarily the lowest number of CNOTs. Two auxiliary qubits make the depth logarithmic in \(k\), and one makes it linear. Clean qubits are preferred over dirty ones.

If minimize_depth is True, a multi-controlled \(X\) gate with at least one auxiliary qubit is decomposed with a depth that is logarithmic in \(k\). With two or more auxiliary qubits this follows Ref. [4], and with one follows Ref. [5] (see also Ref. [6]). The number of gates is linear in \(k\) in both cases, but larger than the default for one auxiliary qubit, so the depth is lower only for tens of controls or more.

Parameters:
  • unitary (ArrayLike) – \(2 \times 2\) unitary matrix of the target gate.

  • controls (tuple[int, ...]) – Ids of the control qubits.

  • target (int) – Id of the target qubit.

  • free (tuple[int, ...], optional) – Ids of free qubits that can be used as dirty auxiliary qubits, that is, they can be in any state and are left unchanged. Defaults to (), which uses no auxiliary qubits.

  • clean (tuple[int, ...], optional) – Ids of free qubits that are known to be in the state \(\ket{0}\), and are left in that state. They are used as dirty qubits when this is cheaper. Defaults to ().

  • minimize_toffolis (bool, optional) – If True, a multi-controlled \(X\) gate with auxiliary qubits minimizes the number of Toffoli gates instead of the number of CNOTs. Defaults to False.

  • minimize_depth (bool, optional) – If True, a multi-controlled \(X\) gate with auxiliary qubits minimizes the depth instead of the number of CNOTs. It cannot be used together with minimize_toffolis. Defaults to False.

  • backend (qibo.backends.abstract.Backend, optional) – Backend of unitary, which is only used to move it to the CPU, since the decomposition is always computed with NumPy. If None, it uses the current backend. Defaults to None.

Returns:

Gates that have the same effect as the multi-controlled gate.

Return type:

list[qibo.gates.abstract.Gate]

References

1. R. Vale, T. M. D. Azevedo, I. C. S. Araújo, I. F. Araujo, and A. J. da Silva, Circuit Decomposition of Multi-Controlled Special Unitary Single-Qubit Gates, IEEE Trans. Comput.-Aided Des. Integr. Circuits Syst.

2. A. J. da Silva and D. K. Park, Linear-depth quantum circuits for multiqubit controlled gates, Phys. Rev. A 106, 042602 (2022).

3. R. Iten, R. Colbeck, I. Kukuljan, J. Home, and M. Christandl, Quantum circuits for isometries, Phys. Rev. A 93, 032318 (2016).

4. T. Khattar and C. Gidney, Rise of conditionally clean ancillae for efficient quantum circuit constructions, Quantum 9, 1752 (2025).

5. J. Nie, W. Zi, and X. Sun, Quantum circuit for multi-qubit Toffoli gate with optimal resource, arXiv:2402.05053.

6. V. Vandaele, Asymptotically optimal quantum circuits for comparators and incrementers, arXiv:2603.12917.

Quantum Shannon decomposition#

qibo.transpiler.quantum_shannon_decomposition(qubits, unitary, backend=None)[source]#

Quantum Shannon decomposition of a unitary matrix acting on several qubits.

The cosine-sine decomposition of an \(n\)-qubit unitary \(U\), whose blocks are defined by the first qubit, is

\[\begin{split}U = (A_{1} \oplus A_{2}) \, \begin{pmatrix} C & -S \\ S & C \end{pmatrix} \, (B_{1} \oplus B_{2}) \, ,\end{split}\]

where the middle matrix is a multiplexed qibo.gates.gates.RY rotation of the first qubit, controlled by the other \(n - 1\) qubits. Each block-diagonal matrix is decomposed as

\[X_{1} \oplus X_{2} = (\mathbb{I} \otimes V) \, (D \oplus D^{\dagger}) \, (\mathbb{I} \otimes W) \, ,\]

with \(V D^{2} V^{\dagger} = X_{1} X_{2}^{\dagger}\) and \(W = D V^{\dagger} X_{2}\), where \(D \oplus D^{\dagger}\) is a multiplexed qibo.gates.gates.RZ rotation of the first qubit. The result has four unitaries on the last \(n - 1\) qubits and three multiplexed rotations, i.e. qibo.gates.gates.Multiplexor gates. Decomposing the unitaries in the same way until they act on two qubits uses \((9 / 16) \, 4^{n} - (3 / 2) \, 2^{n}\) two-qubit gates, if the unitaries on two qubits are decomposed with three of them. It does not use auxiliary qubits.

Parameters:
  • qubits (tuple[int, ...]) – Ids of the qubits that unitary acts on, with at least two. The first one is the most significant bit of the indices of unitary.

  • unitary (ArrayLike) – Unitary matrix of shape \((2^{n}, 2^{n})\), with \(n\) the number of qubits.

  • backend (qibo.backends.abstract.Backend, optional) – Backend to use for the matrices. If None, defaults to the global backend. Defaults to None.

Returns:

Gates, in order of application, that implement unitary: four qibo.gates.gates.Unitary gates on qubits[1:] and three qibo.gates.gates.Multiplexor gates. Each of them acts on less qubits than unitary, so they can be decomposed in turn.

Return type:

list[qibo.gates.abstract.Gate]

Raises:

ValueError – If there are less than two qubits, or unitary does not have the shape \((2^{n}, 2^{n})\).

References

1. V. V. Shende, S. S. Bullock, and I. L. Markov, Synthesis of quantum-logic circuits, IEEE Trans. Comput.-Aided Des. Integr. Circuits Syst. 25, 1000 (2006).

Optimizer#

Barrier before final measurements#

class qibo.transpiler.optimizer.BarrierBeforeFinalMeasurements[source]#

Inserts a barrier before the final measurements of a circuit.

A measurement or a barrier is final if no other gate acts on its qubits after it. The final measurements and barriers are placed after a new qibo.gates.special.Barrier that acts on all the qubits of the circuit, in their original order, while the other gates stay before the barrier. A circuit without final measurements is not changed.

The barrier keeps the tools that rearrange the gates, such as the routers, from moving them across the final measurements. It can be removed afterwards with qibo.transpiler.optimizer.FilterGates.

Example

The measurement of the first qubit is not final, because a Hadamard gate acts on the qubit after it, so it stays before the barrier.

from qibo import Circuit, gates
from qibo.transpiler import BarrierBeforeFinalMeasurements

circuit = Circuit(2)
circuit.add(gates.M(0))
circuit.add(gates.H(0))
circuit.add(gates.CNOT(0, 1))
circuit.add(gates.M(0, 1))

new_circuit = BarrierBeforeFinalMeasurements()(circuit)
print([gate.name for gate in new_circuit.queue])
['measure', 'h', 'cx', 'barrier', 'measure']

Commutative cancellation#

class qibo.transpiler.optimizer.CommutativeCancellation(atol=1e-12, up_to_global_phase=True)[source]#

Merges rotations and cancels pairs of gates through the gates that commute with them.

A rotation is a single-qubit gate without control qubits that is a rotation around the \(X\), \(Y\) or \(Z\) axis, up to a global phase, e.g. qibo.gates.RZ, qibo.gates.T, qibo.gates.SX or qibo.gates.Y. Two rotations around the same axis on the same qubit are merged into a single qibo.gates.RX, qibo.gates.RY or qibo.gates.RZ gate if all the gates between them commute with the second one, where two gates commute when applying them in either order gives the same matrix. The merged rotation is left out if it is the identity. Then, the pairs of gates whose product is the identity are cancelled through the gates that commute with them, see qibo.transpiler.optimizer.InverseCancellation.

Measurements, alignments, barriers, noise channels and fused gates are never merged nor cancelled, and stop the search across them on their qubits. Gates with sympy parameters are not merged, but the gates that commute with them do not stop the search. The gates are compared through their matrices at their current parameter values, so a merged rotation is not tracked by the returned circuit anymore. The pass is applied once, use qibo.transpiler.optimizer.FixedPoint to repeat it.

Example

The two qibo.gates.RZ gates on the first qubit merge, as the controlled-NOT gate between them commutes with them.

from qibo import Circuit, gates
from qibo.transpiler import CommutativeCancellation

circuit = Circuit(2)
circuit.add(gates.RZ(0, 0.3))
circuit.add(gates.CNOT(0, 1))
circuit.add(gates.RZ(0, 0.4))

CommutativeCancellation()(circuit).draw()
0: ─RZ─o─
1: ────X─
Parameters:
  • atol (float, optional) – Tolerance on the angle of a rotation to be the identity and on the difference between the matrices that are compared. Defaults to \(10^{-12}\).

  • up_to_global_phase (bool, optional) – If False, two rotations are merged only when the new rotation has exactly the same matrix as their product, which keeps the global phase of the circuit. Defaults to True.

Filter gates#

class qibo.transpiler.optimizer.FilterGates(predicate)[source]#

Removes the gates of a circuit that do not satisfy a condition.

Parameters:

predicate (Callable) – Function that takes a qibo.gates.abstract.Gate and returns True if the gate is kept in the circuit and False if it is removed.

Example

The barrier is removed, while the other gates are kept.

from qibo import Circuit, gates
from qibo.transpiler import FilterGates

circuit = Circuit(2)
circuit.add(gates.H(0))
circuit.add(gates.Barrier(0, 1))
circuit.add(gates.CNOT(0, 1))

filter_barriers = FilterGates(lambda gate: not isinstance(gate, gates.Barrier))
print([gate.name for gate in filter_barriers(circuit).queue])
['h', 'cx']

Gate direction#

class qibo.transpiler.optimizer.GateDirection(connectivity=None)[source]#

Reverses the two-qubit gates that act against the direction of the connectivity.

A directed edge (a, b) of the connectivity means that a two-qubit gate is only supported with its first qubit on a and its second qubit on b. The gates that act on the opposite direction of an edge are replaced as follows:

The gates that act in the direction of an edge, or on qubits that are not connected, are not changed. The added single-qubit gates are not necessarily native, so this pass should be followed by an qibo.transpiler.unroller.Unroller, and it should come after the one that decomposes the gates into native gates, because that one does not choose the direction of the two-qubit gates. If the connectivity is not directed, the circuit is not changed.

Parameters:

connectivity (networkx.DiGraph, optional) – Hardware connectivity, whose nodes are the wire_names of the circuit. qibo.transpiler.pipeline.Passes sets it. Defaults to None.

Inverse cancellation#

class qibo.transpiler.optimizer.InverseCancellation(atol=1e-12, commutation=False)[source]#

Cancels pairs of gates whose product is the identity.

Two gates cancel when no other gate acts on their qubits in between, both act on the same qubits in the same order with the same control qubits, and the second gate undoes the first one, i.e. the product of their matrices is the identity matrix up to atol. Cancellation is applied repeatedly, so nested pairs are removed completely: a Pauli-X gate, a Pauli-Y gate, a second Pauli-Y gate and a second Pauli-X gate applied in this order on one qubit all disappear.

If commutation is True, the gates in between that commute with the second gate of the pair do not stop the cancellation. Two gates commute when applying them in either order gives the same matrix.

The product must equal the identity itself, not merely the identity times a global phase, because for gates built with controlled_by a phase on the target matrix is a relative phase on the control qubits. Measurements, alignments, barriers, noise channels and fused gates are never cancelled and stop the cancellation across them on their qubits. Gates are compared through their matrices at their current parameter values, so a cancelled pair of parametrized gates is not tracked by the returned circuit anymore.

Gates with sympy parameters take part like any other gate. Their symbols are given fixed random values, the same in all gates, before the matrices are compared. Hence qibo.gates.RX gates with angles \(x\) and \(-x\) cancel, and a qibo.gates.RZ gate with angle \(x\) is skipped over by a controlled-NOT gate on its control qubit if commutation is True, while gates with angles \(x\) and \(y\) do not cancel. A gate whose parameters are not real numbers at these values is never cancelled and stops the cancellation on its qubits.

Parameters:
  • atol (float, optional) – Tolerance on the Frobenius norm (square root of the sum of the squared absolute values of all entries) of the difference between the product of the two gate matrices and the identity matrix, and between the two orders of two gates that are tested for commutation. Defaults to \(10^{-12}\).

  • commutation (bool, optional) – If True, cancel pairs of gates that are separated by gates commuting with them. Defaults to False.

Example

The pair of controlled-NOT (CNOT) gates cancels first, which makes the two Hadamard gates on the first qubit consecutive so they cancel as well. The two rotations around the X axis on the second qubit cancel because their angles add up to zero. The Hadamard gates on the second qubit stay, because the CNOT gate between them acts on that qubit.

from qibo import Circuit, gates
from qibo.transpiler import InverseCancellation

circuit = Circuit(2)
circuit.add(gates.H(0))
circuit.add(gates.CNOT(0, 1))
circuit.add(gates.CNOT(0, 1))
circuit.add(gates.H(0))
circuit.add(gates.RX(1, 0.3))
circuit.add(gates.RX(1, -0.3))
circuit.add(gates.X(0))
circuit.add(gates.H(1))
circuit.add(gates.CNOT(0, 1))
circuit.add(gates.H(1))

circuit.draw()
print()
InverseCancellation()(circuit).draw()
0: ─H─o─o─H──X────o───
1: ───X─X─RX─RX─H─X─H─

0: ─X─o───
1: ─H─X─H─

A rotation around the Z axis on the control qubit and a Pauli-X gate on the target qubit both commute with the CNOT gate, so with commutation=True the two CNOT gates around them cancel.

circuit = Circuit(2)
circuit.add(gates.CNOT(0, 1))
circuit.add(gates.RZ(0, 0.3))
circuit.add(gates.X(1))
circuit.add(gates.CNOT(0, 1))

InverseCancellation(commutation=True)(circuit).draw()
0: ─RZ─
1: ─X──

Optimize 1-qubit gates decomposition#

class qibo.transpiler.optimizer.Optimize1qGatesDecomposition(basis=None, atol=1e-12, up_to_global_phase=True)[source]#

Rewrites runs of consecutive single-qubit gates with as few gates as possible.

A run is a sequence of single-qubit gates without control qubits on one qubit with no other gate on that qubit in between. The matrix of the run is decomposed in every Euler basis allowed by basis and the shortest result is kept. The Euler bases are:

Rotations by a multiple of \(2 \pi\) are left out, so runs equal to the identity are removed. A run is replaced if it contains a gate whose name is not in basis, or if the new sequence is shorter. Gates with sympy parameters end a run. If basis contains no Euler basis, the circuit is returned unchanged. The input circuit is not modified.

Parameters:
  • basis (list[str], optional) – Names of the gates the circuit may contain, for example ["rz", "sx", "cx"]. Only Euler bases whose gates are all in this list are used. If None, all Euler bases are used and every gate counts as in the basis. Defaults to None.

  • atol (float, optional) – Tolerance to decide that an angle is zero, that \(\theta\) (the Y rotation angle of the equivalent qibo.gates.U3 gate) is \(\pi / 2\) or \(\pi\), and that two matrices are equal (Frobenius norm of their difference). Defaults to \(10^{-12}\).

  • up_to_global_phase (bool, optional) – If False, a run is replaced only when the new sequence has exactly the same matrix. Defaults to True.

Example

The Hadamard, phase and Pauli X gates on the first qubit become an qibo.gates.RZ gate and an qibo.gates.SX gate, and the two Pauli X gates on the second qubit cancel.

from qibo import Circuit, gates
from qibo.transpiler import Optimize1qGatesDecomposition

circuit = Circuit(2)
circuit.add(gates.H(0))
circuit.add(gates.S(0))
circuit.add(gates.X(0))
circuit.add(gates.X(1))
circuit.add(gates.X(1))
circuit.add(gates.CNOT(0, 1))

optimized = Optimize1qGatesDecomposition(basis=["rz", "sx", "cx"])(circuit)
for gate in optimized.queue:
    print(gate.name, gate.qubits)
rz (0,)
sx (0,)
cx (0, 1)

Optimize 1-qubit gates simple commutation#

class qibo.transpiler.optimizer.Optimize1qGatesSimpleCommutation(basis=None, atol=1e-12, up_to_global_phase=True)[source]#

Rewrites runs of single-qubit gates after moving gates through commuting gates.

A run is a sequence of single-qubit gates without control qubits on one qubit with no other gate on that qubit in between, as in qibo.transpiler.optimizer.Optimize1qGatesDecomposition. Consider three consecutive runs on a qubit separated by two gates, the preceding and succeeding blockers. The gates at the start of the middle run that commute with the preceding blocker, up to the first one that does not, are moved to the end of the preceding run, and the gates at its end that commute with the succeeding blocker are moved to the start of the succeeding run. The three runs are rewritten with qibo.transpiler.optimizer.Optimize1qGatesDecomposition, and replaced if they have fewer gates, or if they had gates that are not in basis. For example, a qibo.gates.RZ gate commutes with the control of a qibo.gates.CNOT gate.

Two gates commute when applying them in either order gives the same matrix. Gates are moved only into runs that already have gates, only the runs next to a gate that they commute with are rewritten, and the pass is applied once, so use qibo.transpiler.optimizer.FixedPoint to repeat it. Measurements, alignments, barriers, noise channels and gates with sympy parameters are never moved through nor rewritten. The input circuit is not modified.

Example

The qibo.gates.RZ gate before the controlled-NOT gate moves through its control into the run after it, where it is rewritten with the other gates as a single qibo.gates.U2 gate.

from qibo import Circuit, gates
from qibo.transpiler import Optimize1qGatesSimpleCommutation

circuit = Circuit(2)
circuit.add(gates.H(0))
circuit.add(gates.RZ(0, 0.3))
circuit.add(gates.CNOT(0, 1))
circuit.add(gates.RZ(0, 0.4))
circuit.add(gates.H(0))

optimizer = Optimize1qGatesSimpleCommutation()
new_circuit = optimizer(circuit)
new_circuit.draw()
0: ─H─o─U2─
1: ───X────
Parameters:

Optimize swap before measure#

class qibo.transpiler.optimizer.OptimizeSwapBeforeMeasure[source]#

Removes the SWAP gates that are only followed by measurements.

A qibo.gates.SWAP gate is removed if the gates after it on its two qubits are measurements, and if there is at least one, and the two qubits of each of these measurements are exchanged instead. Thus, the outcomes of the measurements are the same, and the order of their bits is kept, but the state of the circuit before them has the two qubits exchanged. Swaps that become followed only by measurements, because of the removal of other swaps, are removed as well.

The input circuit is not modified.

Example

The swap is removed, and the measurement is on the qubits in the opposite order.

from qibo import Circuit, gates
from qibo.transpiler import OptimizeSwapBeforeMeasure

circuit = Circuit(3)
circuit.add(gates.X(0))
circuit.add(gates.SWAP(0, 1))
circuit.add(gates.SWAP(1, 2))
circuit.add(gates.M(0, 1, 2))

optimized = OptimizeSwapBeforeMeasure()(circuit)
for gate in optimized.queue:
    print(gate.name, gate.qubits)
x (0,)
measure (1, 2, 0)

Parametrized gate fusion#

class qibo.transpiler.optimizer.ParametrizedGateFusion(atol=1e-12, up_to_global_phase=False)[source]#

Merges consecutive rotation gates of the same kind into a single gate.

Two gates are merged when no other gate acts on their qubits in between and both are of the same gate class acting on the same qubits with the same control qubits. The merged gate has the sum of the rotation angles, for example a qibo.gates.RY gate with angle \(\alpha\) followed by a qibo.gates.RY gate with angle \(\beta\) on the same qubit becomes one qibo.gates.RY gate with angle \(\alpha + \beta\). Runs of more than two gates are merged completely. The merge is exact, including the global phase.

The following gates are merged this way:

Gates with sympy parameters of these classes are merged too, and the angle of the merged gate is the sympy sum of the angles. The parameters that must coincide, such as the phase angle of qibo.gates.PRX, must then be the same expression. The gates qibo.gates.U2 and qibo.gates.U3 are not merged when any of their parameters is a sympy expression.

Any of these gates can also be controlled on further qubits with controlled_by, as long as both gates have the same control qubits. Gates that are symmetric under exchanging their two qubits (such as qibo.gates.RZZ) are also merged when their qubits are listed in a different order, and so are qibo.gates.RBS and qibo.gates.GIVENS, whose angle changes sign when the qubits are exchanged.

The general single-qubit gates qibo.gates.U2 and qibo.gates.U3, and their controlled versions qibo.gates.CU2 and qibo.gates.CU3, are merged in any combination into a single gate, whose angles are not the sum of the angles of the two gates. A U3 gate with angles \(\theta\), \(\phi\) and \(\lambda\) applies a rotation around the Z axis by \(\lambda\), then a rotation around the Y axis by \(\theta\) and finally a rotation around the Z axis by \(\phi\). A U2 gate is the U3 gate with \(\theta = \pi / 2\). For a first gate with angles \((\theta_1, \phi_1, \lambda_1)\) and a second gate with angles \((\theta_2, \phi_2, \lambda_2)\), let \(h = (\lambda_2 + \phi_1) / 2\) and let \(\alpha\) and \(\beta\) be the complex numbers

\[\alpha = \cos h \, \cos\frac{\theta_1 + \theta_2}{2} - i \sin h \, \cos\frac{\theta_2 - \theta_1}{2}, \qquad \beta = \cos h \, \sin\frac{\theta_1 + \theta_2}{2} - i \sin h \, \sin\frac{\theta_2 - \theta_1}{2}.\]

Then the merged gate has the angles

\[\theta' = 2 \, \mathrm{atan2}(|\beta|, |\alpha|), \qquad \phi' = \phi_2 + \arg\beta - \arg\alpha, \qquad \lambda' = \lambda_1 - \arg\alpha - \arg\beta,\]

where \(|z|\) and \(\arg z\) are the modulus and the phase angle of the complex number \(z\), and \(\mathrm{atan2}\) is the two-argument arctangent. The merged gate is a qibo.gates.U2 gate with angles \(\phi'\) and \(\lambda'\) if \(\theta' = \pi / 2\), and a qibo.gates.U3 gate otherwise. This merge is exact, including the global phase, for gates without control qubits and for controlled gates alike.

The phase gate qibo.gates.U1 and its controlled version qibo.gates.CU1 differ from the U3 gate with \(\theta = 0\) and \(\phi = 0\) by the global phase \(e^{i \lambda / 2}\). Therefore merging one of them with a U2 or U3 gate changes the global phase of the circuit. This is only done if up_to_global_phase is True and the gates have no control qubits, because for controlled gates the global phase becomes a relative phase. For the same reason, a merged U3 gate with \(\theta' = 0\) becomes the qibo.gates.U1 gate with angle \(\phi' + \lambda'\) only if up_to_global_phase is True and the gates have no control qubits.

All other gates, such as qibo.gates.GPI2 or qibo.gates.U1q and qibo.gates.PRX with different phase angles, are left untouched, because the product of two of them is not a single gate of the same kind with added parameters, or, for qibo.gates.MS, could leave the range of allowed angles.

Only gates with the same trainable flag are merged, and gates with sympy parameters are skipped. The merged gate is a new gate, and the input circuit is not modified. The merged gate holds fewer parameters than the two gates it replaces, so the list of parameters of the returned circuit is shorter than the one of the input circuit. Merging is done for the current parameter values, so a later set_parameters call on the returned circuit does not update the angles of the original gates independently.

Parameters:
  • atol (float, optional) – Tolerance for deciding that the phase angles of two qibo.gates.PRX or qibo.gates.U1q gates are the same, and that the global phase changed by a merge is zero. Defaults to \(10^{-12}\).

  • up_to_global_phase (bool, optional) – If True, gates without control qubits are also merged when this changes the global phase of the circuit, which happens when a qibo.gates.U1 gate is merged with a qibo.gates.U2 or qibo.gates.U3 gate. Defaults to False.

Example

The two qibo.gates.RY gates on the first qubit are merged. The two qibo.gates.RBS gates act on the same qubits in reversed order, so the angle of the second one enters with the opposite sign. The two qibo.gates.RZ gates on the first qubit are not merged, because the CNOT gate acts on that qubit in between.

from qibo import Circuit, gates
from qibo.transpiler import ParametrizedGateFusion

circuit = Circuit(3)
circuit.add(gates.RY(0, 0.1))
circuit.add(gates.RY(0, 0.2))
circuit.add(gates.RBS(1, 2, 0.3))
circuit.add(gates.RBS(2, 1, 0.4))
circuit.add(gates.RZ(0, 0.5))
circuit.add(gates.CNOT(0, 1))
circuit.add(gates.RZ(0, 0.6))

fused = ParametrizedGateFusion()(circuit)
for gate in fused.queue:
    print(gate.name, gate.qubits, [round(p, 3) for p in gate.parameters])
ry (0,) [0.3]
rbs (1, 2) [-0.1]
rz (0,) [0.5]
cx (0, 1) []
rz (0,) [0.6]

A qibo.gates.U3 gate followed by a qibo.gates.U2 gate gives one qibo.gates.U3 gate, which has the same matrix as the two gates.

import numpy as np

circuit = Circuit(1)
circuit.add(gates.U3(0, 0.3, 0.2, 0.1))
circuit.add(gates.U2(0, 0.4, 0.5))

fused = ParametrizedGateFusion()(circuit)
gate = fused.queue[0]
print(gate.name, [round(float(p), 3) for p in gate.parameters])
print(np.allclose(fused.unitary(), circuit.unitary()))
u3 [1.799, 0.597, 0.823]
True

Preprocessing#

class qibo.transpiler.optimizer.Preprocessing(connectivity=None, initial_layout=None)[source]#

Pad the circuit with unused qubits to match the number of physical qubits.

Parameters:
  • connectivity (networkx.Graph) – Hardware connectivity as a graph.

  • initial_layout (list or dict, optional) – Physical qubits on which the logical qubits of the circuit are placed. It is either a list, where the \(i\)-th logical qubit is placed on the physical qubit initial_layout[i], or a dictionary {i: node} with the same meaning, which may leave logical qubits out. These are placed on the nodes of the connectivity that are not in the dictionary, in the order of the connectivity. In both cases every logical qubit must be placed on a distinct node of the connectivity, and the layout replaces the wire_names of the circuit, which are set in place. If None, the wire_names of the circuit are used if they are all in the connectivity, and the first nodes of the connectivity otherwise. Defaults to None.

Example

The first and the third qubits of the circuit are placed on the nodes 3 and 1, and the second one on the first free node, which is 0.

import networkx as nx

from qibo import Circuit, gates
from qibo.transpiler import Preprocessing

circuit = Circuit(3)
circuit.add(gates.CNOT(0, 1))

preprocessing = Preprocessing(nx.path_graph(5), initial_layout={0: 3, 2: 1})
print(preprocessing(circuit).wire_names)
[3, 0, 1, 2, 4]

Rearrange#

class qibo.transpiler.optimizer.Rearrange(max_qubits=1)[source]#

Rearranges gates using qibo’s fusion algorithm. May reduce number of qibo.gates.SWAP when fixing for connectivity but this has not been tested.

Parameters:

max_qubits (int, optional) – Maximum number of qubits to fuse. Defaults to \(1\).

Remove diagonal gates before measurements#

class qibo.transpiler.optimizer.RemoveDiagonalGatesBeforeMeasurement[source]#

Removes diagonal gates that are directly followed by measurements.

A gate is diagonal when its matrix in the computational basis has non-zero entries only on its diagonal, so it merely multiplies each computational basis state by a phase. Such a gate does not change the probability of any outcome of a measurement in the computational basis, so it can be dropped when the next operation on each of its qubits is such a measurement.

The following diagonal gates are removed:

Any of these gates with further control qubits added through controlled_by is still diagonal, so it is removed as well. For example, a qibo.gates.S gate controlled by another qubit is removed.

A gate is removed when, on each qubit it acts on, the next operation is a measurement qibo.gates.M. The measurement may be the same for all the qubits of the gate or a different one for each of them. Any other operation in between keeps the gate, be it a gate, a noise channel, a barrier or an alignment. In particular, a measurement in a basis other than the computational basis is preceded in the circuit by the gates rotating the qubits to that basis, which are not diagonal, so the diagonal gates before them stay.

Only the gates directly before the measurement are removed, so a qibo.gates.RZ gate followed by a qibo.gates.Z gate and then by a measurement keeps the qibo.gates.RZ gate. Applying the pass a second time removes it.

The probability of every measurement outcome is unchanged, but the state of the qubits just before the measurement is not, because its amplitudes change by phases that differ between basis states. The pass is therefore meant for circuits of which only the measurement outcomes are used. For measurements that collapse the state, the state after the measurement changes at most by a global phase. Removed parametrized gates are not tracked by the returned circuit anymore.

Example

The qibo.gates.RZ gate is removed because a measurement follows it on the first qubit, while the qibo.gates.S gate before it stays because a Hadamard gate follows it. The controlled-Z gate is removed because both of its qubits are measured next.

from qibo import Circuit, gates
from qibo.transpiler import RemoveDiagonalGatesBeforeMeasurement

circuit = Circuit(3)
circuit.add(gates.H(qubit) for qubit in range(3))
circuit.add(gates.S(0))
circuit.add(gates.H(0))
circuit.add(gates.RZ(0, 0.3))
circuit.add(gates.CZ(1, 2))
circuit.add(gates.M(0, 1, 2))

circuit.draw()
print()
RemoveDiagonalGatesBeforeMeasurement()(circuit).draw()
0: ─H─S─H─RZ───M─
1: ─H────────o─M─
2: ─H────────Z─M─

0: ─H─S─H─M─
1: ─H─────M─
2: ─H─────M─

Remove final reset#

class qibo.transpiler.optimizer.RemoveFinalReset[source]#

Removes the resets at the end of a circuit.

A reset is the single-qubit qibo.gates.ResetChannel that sends the qubit to the \(|0\rangle\) state with probability one, that is with \(p_0 = 1\) and \(p_1 = 0\). A reset is removed when no other operation (gate, measurement or barrier) acts on its qubit afterwards. Resets that are only followed by other final resets are removed as well.

Such a reset does not change the outcome of any measurement made before it, nor the state of the other qubits, but it does change the state of its own qubit in the final state returned by the circuit execution.

Example

The last reset is removed, while the first one is kept because the Hadamard gate acts on the qubit after it.

from qibo import Circuit, gates
from qibo.transpiler import RemoveFinalReset

circuit = Circuit(1, density_matrix=True)
circuit.add(gates.ResetChannel(0, [1.0, 0.0]))
circuit.add(gates.H(0))
circuit.add(gates.ResetChannel(0, [1.0, 0.0]))

print(RemoveFinalReset()(circuit).ngates)
2

Remove identity equivalent#

class qibo.transpiler.optimizer.RemoveIdentityEquivalent(approximation_degree=1.0)[source]#

Removes gates whose action is equivalent to the identity.

A gate \(U\), acting on \(n\) qubits in total (targets and controls), is removed when its average gate fidelity with the identity,

\[F(U) = \frac{|\text{Tr}(U)|^2 + D}{D \, (D + 1)},\]

satisfies \(1 - F(U) \leq \epsilon\). Here \(D = 2^n\) is the dimension of the Hilbert space, \(\text{Tr}\) is the matrix trace and \(\epsilon\) is the tolerance. Since \(|\text{Tr}(U)|\) does not depend on a global phase, an uncontrolled gate that equals the identity up to a phase, e.g. a rotation around the Z axis by \(2\pi\), is removed. For a gate built with controlled_by that phase is a relative phase between the control states, so the gate is removed only if its target matrix is the identity.

Measurements, alignments, barriers, noise channels, fused gates and global phase gates are never removed, the latter to keep the global phase of the circuit. Gates are evaluated at their current parameter values, so a removed parametrized gate is not tracked by the returned circuit anymore.

A gate with sympy parameters is evaluated at fixed random values of its symbols. It is removed only if it is the identity there up to numerical precision, whatever the value of approximation_degree, because the true values of the symbols are unknown.

Parameters:

approximation_degree (float, optional) – Value in \([0, 1]\) that sets the tolerance as \(\epsilon = 1 -\) approximation_degree. With the default value only gates equal to the identity up to numerical precision (\(\epsilon = 10^{-14}\)) are removed. Defaults to \(1.0\).

Example

The rotation by \(10^{-3}\) radians differs from the identity by an infidelity of about \(1.7 \times 10^{-7}\), so it is removed only when the tolerance allows it. The identity gate and the rotation by \(2\pi\) (minus the identity) are removed in both cases.

from math import pi

from qibo import Circuit, gates
from qibo.transpiler import RemoveIdentityEquivalent

circuit = Circuit(2)
circuit.add(gates.I(0))
circuit.add(gates.RZ(0, 2 * pi))
circuit.add(gates.RX(1, 1e-3))
circuit.add(gates.CNOT(0, 1))

print(RemoveIdentityEquivalent()(circuit).ngates)
print(RemoveIdentityEquivalent(approximation_degree=1 - 1e-6)(circuit).ngates)
2
1

Remove reset in zero state#

class qibo.transpiler.optimizer.RemoveResetInZeroState[source]#

Removes the resets acting on qubits that are still in the zero state.

A reset is the single-qubit qibo.gates.ResetChannel that sends the qubit to the \(|0\rangle\) state with probability one, that is with \(p_0 = 1\) and \(p_1 = 0\). A qubit is known to be in the \(|0\rangle\) state only until the first operation (gate, measurement or barrier) acts on it, so only the resets that precede every other operation on their qubit are removed. Consecutive resets at the start of a qubit are all removed.

Example

The reset at the start of the circuit is removed, while the one after the Hadamard gate is kept.

from qibo import Circuit, gates
from qibo.transpiler import RemoveResetInZeroState

circuit = Circuit(1, density_matrix=True)
circuit.add(gates.ResetChannel(0, [1.0, 0.0]))
circuit.add(gates.H(0))
circuit.add(gates.ResetChannel(0, [1.0, 0.0]))

print(RemoveResetInZeroState()(circuit).ngates)
2

Reset after measure simplification#

class qibo.transpiler.optimizer.ResetAfterMeasureSimplification[source]#

Replaces a reset that follows a measurement by a gate conditioned on its outcome.

A reset is the single-qubit qibo.gates.ResetChannel that sends the qubit to the \(|0\rangle\) state with probability one, that is with \(p_0 = 1\) and \(p_1 = 0\). When a reset directly follows a measurement of the same qubit, with no other operation on that qubit in between, the measurement has already collapsed the qubit to the state \(|m\rangle\), where \(m\) is the measured bit. The reset is then equivalent to a Pauli-X gate applied only if \(m = 1\). The pass replaces it by a qibo.gates.U3 gate with angles \(\theta = \lambda = \pi m\) and \(\phi = 0\), which is the identity for \(m = 0\) and exactly the Pauli-X gate for \(m = 1\), with \(m\) given by the symbols of the measurement result, see How to collapse state during measurements?.

Measurements followed by a gate on the same qubit are collapsing measurements in qibo, so every measurement that precedes a reset is handled.

Example

The reset is replaced by a conditional gate, and the circuit ends in the zero state for both measurement outcomes.

from qibo import Circuit, gates
from qibo.transpiler import ResetAfterMeasureSimplification

circuit = Circuit(1, density_matrix=True)
circuit.add(gates.H(0))
circuit.add(gates.M(0))
circuit.add(gates.ResetChannel(0, [1.0, 0.0]))

simplified = ResetAfterMeasureSimplification()(circuit)
print([gate.name for gate in simplified.queue])
['h', 'measure', 'u3']

T-gate rules#

class qibo.transpiler.optimizer.TGateRules[source]#

Replaces runs of consecutive qibo.gates.gates.T gates by shorter equivalents.

The \(T\) gate is the diagonal matrix \(\mathrm{diag}(1, e^{i \pi / 4})\), so \(k\) consecutive \(T\) gates on the same qubit give \(\mathrm{diag}(1, e^{i k \pi / 4})\). This only depends on \(k mod 8\), hence eight rules are enough to simplify any number of consecutive \(T\) gates, using the Clifford gates \(S = T^2\) and \(Z = T^4\):

\(k mod 8\)

Replacement

0

identity (no gate)

1

\(T\)

2

\(S\)

3

\(S T\)

4

\(Z\)

5

\(Z T\)

6

\(S^\dagger\)

7

\(T^\dagger\)

All rules are exact, i.e. they hold without any global phase, so they are also correct inside larger circuits. Only \(T\) gates without control qubits are replaced. A run of \(T\) gates on a qubit ends as soon as any other gate, including a measurement or a barrier, acts on that qubit; gates acting on other qubits do not end it.

Example

Five \(T\) gates on the first qubit become \(Z T\). On the second qubit, the controlled-NOT (CNOT) gate ends the run, so the first \(T\) gate stays and the two \(T\) gates after the CNOT become an \(S\) gate.

from qibo import Circuit, gates
from qibo.transpiler import TGateRules

circuit = Circuit(2)
circuit.add(gates.T(0) for _ in range(5))
circuit.add(gates.T(1))
circuit.add(gates.CNOT(0, 1))
circuit.add(gates.T(1))
circuit.add(gates.T(1))

circuit.draw()
print()
TGateRules()(circuit).draw()
0: ─T─T─T─T─T─o─────
1: ─T─────────X─T─T─

0: ─Z─T─o───
1: ─T───X─S─

Validation#

class qibo.transpiler.optimizer.Validation(connectivity=None, initial_layout=None)[source]#

Checks that the circuit can be transpiled, before any other pass runs.

Without it, a circuit that cannot be transpiled fails deep in the pipeline. This pass raises a clear error at the start instead. It does not change the circuit. It checks that:

  • the circuit has no noise channel. The transpiler works with unitary gates, so the only channel that it accepts is qibo.gates.channels.ResetChannel.

  • the circuit has no more qubits than the hardware, if there is a connectivity.

  • the initial layout, if there is one, places every qubit of the circuit on a distinct node of the connectivity, see qibo.transpiler.optimizer.Preprocessing.

Parameters:
  • connectivity (networkx.Graph, optional) – Hardware connectivity. If None, neither the number of qubits nor the initial layout are checked. It is set by qibo.transpiler.pipeline.Passes. Defaults to None.

  • initial_layout (list, optional) – Physical qubits on which the logical qubits of the circuit are placed, in the format of qibo.transpiler.optimizer.Preprocessing. Defaults to None.

Example

The pass raises an error for the noise channel before the circuit is transpiled.

from qibo import Circuit, gates
from qibo.transpiler import Validation

circuit = Circuit(1)
circuit.add(gates.H(0))
circuit.add(gates.PauliNoiseChannel([0], [("X", 0.1)]))

try:
    Validation()(circuit)
except Exception as error:
    print(type(error).__name__)
DecompositionError

Pipeline#

Passes#

class qibo.transpiler.pipeline.Passes(passes=None, connectivity=None, native_gates=None, on_qubits=None, callback=None)[source]#

Define a transpiler pipeline consisting of smaller transpiler steps that are applied sequentially.

Parameters:
  • passes (list, optional) – List of transpiler passes to be applied sequentially. If None, no pass is applied. See qibo.transpiler.pipeline.generate_preset_passes() for a ready-to-use list. Defaults to None.

  • connectivity (networkx.Graph, optional) – Hardware connectivity. It can be a directed graph, in which case an edge (a, b) means that a two-qubit gate is supported with its first qubit on a and its second on b, see qibo.transpiler.optimizer.GateDirection. It is required if on_qubits is given. Defaults to None.

  • native_gates (qibo.transpiler.unroller.NativeGates, optional) – Native gates supported by the hardware. Defaults to qibo.transpiler.unroller.NativeGates.default().

  • on_qubits (list, optional) – List of qubits to be used in the transpiler. If None, all qubits in the connectivity will be used. Defaults to None.

  • callback (Callable, optional) – Function called after each pass that runs, with the keyword arguments transpiler_pass, the pass that has run, circuit, the circuit after the pass, time, the seconds that the pass has taken, and count, the position of the pass in passes. A router is skipped, and the callback is not called for it, if the circuit already respects the connectivity, since no qubit has to be moved. Defaults to None.

Example

The callback prints the passes of the pipeline as they run. The circuit already respects the connectivity, so the router is skipped and it is not printed.

import networkx as nx

from qibo import Circuit, gates
from qibo.transpiler import Passes, Preprocessing, Sabre

circuit = Circuit(2)
circuit.add(gates.CNOT(0, 1))

def callback(transpiler_pass, circuit, time, count):
    print(count, type(transpiler_pass).__name__, circuit.nqubits)

pipeline = Passes(
    [Preprocessing(), Sabre()], connectivity=nx.path_graph(3), callback=callback
)
transpiled_circuit, final_layout = pipeline(circuit)
0 Preprocessing 3
is_satisfied(circuit)[source]#

Check if the circuit respects the hardware connectivity and native gates.

If there is no connectivity, only the native gates are checked, because there is nothing to place and route.

Parameters:

circuit (qibo.models.circuit.Circuit) – Circuit to be checked.

Returns:

True if the circuit respects the hardware connectivity and native gates, False otherwise.

Return type:

(bool)

Preset passes#

qibo.transpiler.pipeline.generate_preset_passes(connectivity=None, native_gates=None, initial_layout=None, seed=None, on_qubits=None, qubits_initially_zero=False, layout_method=None, routing_method=None, layout_trials=None, callback=None, approximation_degree=1.0, threshold=None, weight=None, use_dirty_ancillas=True, minimize_toffolis=None, minimize_depth=False, routing_trials=5, optimization_level=0, sampling_only=False, backend=None)[source]#

Generate a ready-to-use transpiler pipeline.

With the default optimization_level=0, the pipeline only makes the circuit executable on the hardware, with the following passes:

  1. qibo.transpiler.optimizer.Validation raises an error at the start if the circuit cannot be transpiled, e.g. if it has a noise channel, more qubits than the hardware, or an invalid initial_layout.

  2. qibo.transpiler.unroller.Unroller decomposes the gates acting on three or more qubits, which the router does not support. The qubits of the circuit that a multi-controlled gate does not act on are used as auxiliary qubits, see qubits_initially_zero.

  3. qibo.transpiler.optimizer.Preprocessing places the qubits on initial_layout, or on the first nodes of the connectivity if there is none, and pads the circuit with the unused qubits of the hardware.

  4. The placer selected by layout_method, if it is not the trivial one.

  5. The router selected by routing_method, which adds the SWAP gates that make all the gates executable. By default, it is qibo.transpiler.router.Sabre with the best of routing_trials trials.

  6. qibo.transpiler.unroller.Unroller decomposes the circuit to the native gates, approximating some two-qubit gates with fewer entangling gates if approximation_degree is lower than \(1\).

If connectivity is directed, the pipeline ends with a qibo.transpiler.optimizer.GateDirection pass and an unroller, so that the two-qubit gates act in the direction of the edges.

The optimization levels \(1\) and \(2\) add optimizers to the pipeline. The ones that can write gates that are not native run before the last unroller. Level \(1\) adds:

Level \(2\) adds, to the optimizers of level \(1\):

The optimizers can change the global phase of the circuit, and the gates that they merge do not keep their trainable parameters.

Without connectivity there is nothing to place and route, so the pipeline only validates the circuit and decomposes it to the native gates.

Parameters:
  • connectivity (networkx.Graph, optional) – Hardware connectivity. It can be directed, see above. Defaults to None.

  • native_gates (qibo.transpiler.unroller.NativeGates, optional) – Native gates supported by the hardware. Defaults to qibo.transpiler.unroller.NativeGates.default().

  • initial_layout (list or dict, optional) – Physical qubits on which the logical qubits of the circuit are placed, as a list or as a dictionary that may leave qubits out, see qibo.transpiler.optimizer.Preprocessing. It requires connectivity, and it cannot be used with a non-trivial layout_method. Defaults to None.

  • seed (int, optional) – Seed of the random choices of the placer and the router, which makes the transpilation reproducible. Defaults to None.

  • on_qubits (list, optional) – List of qubits to be used in the transpiler, see qibo.transpiler.pipeline.Passes. It requires connectivity. Defaults to None.

  • qubits_initially_zero (bool, optional) – If True, the circuit is assumed to start in the state \(\ket{0}^{\otimes n}\). The qubits on which no previous gate acts are then used as clean auxiliary qubits to decompose multi-controlled gates, with the construction that minimizes the number of Toffoli gates, see qibo.transpiler.unroller.Unroller. This makes the decomposition shallower and, for many controls, also shorter. If there are no clean qubits for a gate, its dirty auxiliary qubits are used with the same construction, which has more CNOT gates than the default one. Without qubits_initially_zero the other qubits are used as dirty auxiliary qubits, which can be in any state. It must be False if the circuit is executed with another initial state. Defaults to False.

  • layout_method (str or qibo.transpiler.abstract.Placer, optional) – Placer that finds the initial layout. It can be "trivial", which keeps the wire_names of the circuit if they are nodes of the connectivity and uses the first nodes otherwise, "dense" (qibo.transpiler.placer.Dense), "random" (qibo.transpiler.placer.Random), "subgraph" (qibo.transpiler.placer.Subgraph), "reverse_traversal" (qibo.transpiler.placer.ReverseTraversal with qibo.transpiler.router.Sabre), "star" (qibo.transpiler.placer.StarConnectivityPlacer) or a placer. It requires connectivity. Defaults to None, which is "trivial", or "reverse_traversal" if optimization_level is at least \(1\), initial_layout is None and routing_method is None or "sabre".

  • routing_method (str or qibo.transpiler.abstract.Router, optional) – Router that makes the circuit executable. It can be "sabre" (qibo.transpiler.router.Sabre with routing_trials trials), "shortest_paths" (qibo.transpiler.router.ShortestPaths), "star" (qibo.transpiler.router.StarConnectivityRouter), "none" (qibo.transpiler.router.NoRouting, which raises an error if the circuit needs routing) or a router. A router is skipped if the circuit already respects the connectivity. It requires connectivity. Defaults to None, which is "sabre".

  • layout_trials (int, optional) – Number of layout attempts of the "reverse_traversal" placer, see qibo.transpiler.placer.ReverseTraversal. It is not used by the other placers. Defaults to None, which is \(1\) if optimization_level is \(0\) and \(5\) otherwise.

  • callback (Callable, optional) – Function called after each pass that runs, see qibo.transpiler.pipeline.Passes. Defaults to None.

  • approximation_degree (float, optional) – Fidelity of each entangling gate, between \(0\) and \(1\). If it is lower than \(1\), the unroller that decomposes the circuit to the native gates approximates the two-qubit gates that it decomposes from their matrix with fewer qibo.gates.CZ gates, if it increases the expected fidelity, see qibo.transpiler.unroller.Unroller. The multi-qubit gates are decomposed exactly. Defaults to \(1\), which is the exact decomposition.

  • threshold (float, optional) – Tolerance of the decomposition of the two-qubit gates that are decomposed from their matrix, like for approximation_degree, see qibo.transpiler.unroller.Unroller. Defaults to None, which uses the default value of the decomposition.

  • weight (float, optional) – Weight of the imaginary part in the matrix that is diagonalized to decompose those gates, like for approximation_degree, see qibo.transpiler.unroller.Unroller. Defaults to None, which uses the default value of the decomposition.

  • use_dirty_ancillas (bool, optional) – If True, the qubits that a multi-controlled gate does not act on are used as dirty auxiliary qubits in its decomposition, see qibo.transpiler.unroller.Unroller. Defaults to True.

  • minimize_toffolis (bool, optional) – If True, the multi-controlled qibo.gates.X gates with auxiliary qubits are decomposed with the lowest number of Toffoli gates, see qibo.transpiler.unroller.Unroller. It cannot be used together with minimize_depth. Defaults to None, which is qubits_initially_zero unless minimize_depth is True.

  • minimize_depth (bool, optional) – If True, the multi-controlled qibo.gates.X gates with auxiliary qubits are decomposed with a depth that is logarithmic in the number of controls, see qibo.transpiler.unroller.Unroller. It cannot be used together with minimize_toffolis. Defaults to False.

  • routing_trials (int, optional) – Number of independent routing attempts of the "sabre" router, which keeps the best one, see qibo.transpiler.router.Sabre. It is not used by the other routers, nor by the router of the "reverse_traversal" placer. Defaults to \(5\).

  • optimization_level (int, optional) – Optimization level of the pipeline. It can be \(0\), which is the pipeline described above, that only makes the circuit executable on the hardware and does not optimize it, or \(1\) or \(2\), which add the optimizers described above. Defaults to \(0\).

  • sampling_only (bool, optional) – If True, the optimizers of levels \(1\) and \(2\) also use the rewrites that keep the distribution of the measured qubits, but can change the state of the circuit before the measurements: qibo.transpiler.optimizer.OptimizeSwapBeforeMeasure, qibo.transpiler.optimizer.RemoveDiagonalGatesBeforeMeasurement and qibo.transpiler.optimizer.RemoveFinalReset, before the layout. It has no effect if optimization_level is \(0\). Defaults to False.

  • backend (qibo.backends.abstract.Backend, optional) – Backend used by the unrollers to compute the gate matrices, unless another one is given when the pipeline is called. If None, it uses the global backend. Defaults to None.

Returns:

Transpiler pipeline.

Return type:

qibo.transpiler.pipeline.Passes

Raises:
  • ConnectivityError – If initial_layout, layout_method or routing_method is given without connectivity.

  • PlacementError – If initial_layout is given with a non-trivial layout_method.

  • ValueError – If layout_method or routing_method is not a valid method, if approximation_degree is not between \(0\) and \(1\), if minimize_toffolis and minimize_depth are both True, if routing_trials is not positive and the router is "sabre", or if optimization_level is not a valid level.

Restrict qubit connectivity#

qibo.transpiler.pipeline.restrict_connectivity_qubits(connectivity, qubits)[source]#

Restrict the connectivity to selected qubits.

Parameters:
  • connectivity (networkx.Graph) – Hardware connectivity.

  • qubits (list) – List of qubits to select.

Returns:

New restricted connectivity.

Return type:

(networkx.Graph)

Transpile#

qibo.transpiler.transpile(circuits, connectivity=None, native_gates=None, initial_layout=None, seed=None, on_qubits=None, qubits_initially_zero=False, layout_method=None, routing_method=None, layout_trials=None, callback=None, approximation_degree=1.0, threshold=None, weight=None, use_dirty_ancillas=True, minimize_toffolis=None, minimize_depth=False, routing_trials=5, optimization_level=0, sampling_only=False, num_processes=None, backend=None)[source]#

Transpile one or more circuits in a single call.

It builds the pipeline of qibo.transpiler.pipeline.generate_preset_passes() once and applies it to the circuits, which makes them executable on the hardware, and optimizes them if optimization_level is higher than \(0\). All the arguments except circuits and num_processes are the ones of qibo.transpiler.pipeline.generate_preset_passes(), and they are shared by all the circuits.

The circuits of a list can be transpiled in parallel with num_processes. Each of them is then transpiled by a worker process with its own copy of the pipeline, so the result for a circuit does not depend on the other circuits nor on num_processes, and, with a seed, it is the one of transpiling the circuit alone. This can differ from the sequential result, where the random state of the placer and the router continues from one circuit to the next. The worker processes are started with the spawn method, so the script that calls qibo.transpiler.pipeline.transpile() must protect the call with if __name__ == "__main__":. They do not share the global backend either, so backend should be given if it is not the default one.

Example

A rotation, a Toffoli gate and a CNOT gate between qubits that are not neighbors are transpiled to a line of four qubits, where qibo.gates.CNOT and qibo.gates.U3 are the native gates. The Toffoli gate is decomposed, the SWAP gates that the routing adds are decomposed too, and the transpiled circuit is drawn. The decomposition of the Toffoli gate leaves a global phase, which the drawing reports. If circuits is a list, the result is a list with a transpiled circuit and a final layout for each circuit.

import networkx as nx

from qibo import Circuit, gates
from qibo.backends import NumpyBackend
from qibo.transpiler import NativeGates, transpile
from qibo.transpiler.asserts import assert_transpiling

backend = NumpyBackend()

circuit = Circuit(4)
circuit.add(gates.RY(0, 0.5))
circuit.add(gates.TOFFOLI(0, 1, 2))
circuit.add(gates.CNOT(0, 3))
circuit.add(gates.M(*range(4)))

connectivity = nx.path_graph(4)
natives = NativeGates.from_gatelist([gates.U3, gates.CNOT, gates.M])
transpiled_circuit, final_layout = transpile(
    circuit, connectivity, natives, seed=42, backend=backend
)

# Raises an error if the circuits are not equivalent, or if the transpiled circuit
# does not respect the connectivity and the native gates
assert_transpiling(
    original_circuit=circuit,
    transpiled_circuit=transpiled_circuit,
    connectivity=connectivity,
    final_layout=final_layout,
    native_gates=natives,
)

print(final_layout)
transpiled_circuit.draw(line_wrap=1000, parallel=True)
{0: 1, 1: 3, 2: 0, 3: 2}
Global phase = exp(i * 9π/8)
0: ─U3────────────o─────────o───────o─X─o────────────────M─
1: ────o────o─X─o─X─RZ─X─RZ─X─RZ─U3─X─o─X─o─RZ─o───────o─M─
2: ─U3─X─RZ─X─o─X──────o─RZ───────────────X─RZ─X─o─X─o─X─M─
3: ──────────────────────────────────────────────X─o─X───M─
Parameters:
  • circuits (qibo.models.circuit.Circuit or list or tuple) – Circuit, or list of circuits, to be transpiled.

  • connectivity (networkx.Graph, optional) – Hardware connectivity. Defaults to None.

  • native_gates (qibo.transpiler.unroller.NativeGates, optional) – Native gates supported by the hardware. Defaults to qibo.transpiler.unroller.NativeGates.default().

  • initial_layout (list or dict, optional) – Physical qubits on which the logical qubits of the circuits are placed, see qibo.transpiler.optimizer.Preprocessing. Defaults to None.

  • seed (int, optional) – Seed of the random choices of the placer and the router. Defaults to None.

  • on_qubits (list, optional) – List of qubits to be used in the transpiler. Defaults to None.

  • qubits_initially_zero (bool, optional) – If True, the circuits are assumed to start in the state \(\ket{0}^{\otimes n}\). Defaults to False.

  • layout_method (str or qibo.transpiler.abstract.Placer, optional) – Placer that finds the initial layout. Defaults to None.

  • routing_method (str or qibo.transpiler.abstract.Router, optional) – Router that makes the circuits executable. Defaults to None.

  • layout_trials (int, optional) – Number of layout attempts of the "reverse_traversal" placer. Defaults to None, which is \(1\) if optimization_level is \(0\) and \(5\) otherwise.

  • callback (Callable, optional) – Function called after each pass that runs, for each circuit, see qibo.transpiler.pipeline.Passes. It cannot be used with more than one process. Defaults to None.

  • approximation_degree (float, optional) – Fidelity of each entangling gate, between \(0\) and \(1\). If it is lower than \(1\), the two-qubit gates that are decomposed from their matrix are approximated with fewer entangling gates, if it increases the expected fidelity, see qibo.transpiler.pipeline.generate_preset_passes(). Defaults to \(1\), which is the exact decomposition.

  • threshold (float, optional) – Tolerance of the decomposition of the two-qubit gates that are decomposed from their matrix, like for approximation_degree. Defaults to None, which uses the default value of the decomposition.

  • weight (float, optional) – Weight of the imaginary part in the matrix that is diagonalized to decompose those gates, like for approximation_degree. Defaults to None, which uses the default value of the decomposition.

  • use_dirty_ancillas (bool, optional) – If True, the qubits that a multi-controlled gate does not act on are used as dirty auxiliary qubits in its decomposition. Defaults to True.

  • minimize_toffolis (bool, optional) – If True, the multi-controlled qibo.gates.X gates with auxiliary qubits are decomposed with the lowest number of Toffoli gates. It cannot be used together with minimize_depth. Defaults to None, which is qubits_initially_zero unless minimize_depth is True.

  • minimize_depth (bool, optional) – If True, the multi-controlled qibo.gates.X gates with auxiliary qubits are decomposed with a depth that is logarithmic in the number of controls. It cannot be used together with minimize_toffolis. Defaults to False.

  • routing_trials (int, optional) – Number of independent routing attempts of the "sabre" router, which keeps the best one. It is not used by the other routers. Defaults to \(5\).

  • optimization_level (int, optional) – Optimization level of the pipeline, see qibo.transpiler.pipeline.generate_preset_passes(). It can be \(0\), \(1\) or \(2\). Defaults to \(0\), which does not optimize the circuits.

  • sampling_only (bool, optional) – If True, the optimizers also use the rewrites that keep the distribution of the measured qubits but can change the state of the circuit before the measurements, see qibo.transpiler.pipeline.generate_preset_passes(). It logs a warning for the circuits without measurements, and it has no effect if optimization_level is \(0\). Defaults to False.

  • num_processes (int, optional) – Number of worker processes that transpile the circuits of a list in parallel. If None or \(1\), the circuits are transpiled one after the other in the current process. It has no effect on a single circuit, and no more processes than circuits are started. Defaults to None.

  • backend (qibo.backends.abstract.Backend, optional) – Backend used by the unrollers to compute the gate matrices, and passed to the other passes that take one. If None, they use the global backend. Defaults to None.

Returns:

(qibo.models.circuit.Circuit, qibo.transpiler.layout.TranspileLayout) or list: Transpiled circuit and final {logical: physical} qubit mapping, see qibo.transpiler.pipeline.Passes. If circuits is a list or tuple, the list of these pairs, in the same order.

Raises:
  • ValueError – If num_processes is not a positive integer, if callback is given with more than one process, if approximation_degree is not between \(0\) and \(1\), if minimize_toffolis and minimize_depth are both True, if routing_trials is not positive and the router is "sabre", or if optimization_level is not a valid level.

  • BrokenProcessPool – If a worker process ends abruptly. A common cause is a script that calls this function with several processes without the if __name__ == "__main__": guard.

Return type:

tuple[qibo.models.circuit.Circuit, dict | None] | list[tuple[qibo.models.circuit.Circuit, dict | None]]

Transpile layout#

class qibo.transpiler.layout.TranspileLayout(final_layout, initial_layout, nqubits_input)[source]#

Layout of the qubits of a circuit before and after the routing.

It is the final layout returned by qibo.transpiler.pipeline.Passes. As a dictionary, it maps the label of the physical qubit on which each qubit of the circuit is placed before the routing, to the index of the physical qubit on which it ends after the routing. It can therefore be used wherever a final {logical: physical} qubit mapping is expected. It also stores the layout before the routing. If the pipeline has no router, the qubits are not moved, so the mapping and the routing permutation are the identity, and the initial layout is the one of the transpiled circuit.

Example

A circuit on four qubits is transpiled to a line of five qubits. It is drawn before and after the transpilation with parallel=True, so that the gates on disjoint qubits share a column, and with line_wrap=12, which splits the wider transpiled circuit in two blocks. The routing adds the SWAP gates, drawn as x, and the preprocessing adds the fifth qubit of the line as an auxiliary qubit, which is not used. The \(i\)-th element of the routing permutation is the physical qubit on which the \(i\)-th qubit ends: the first qubit does not move, while the second, third and fourth qubits end on the third, fourth and second physical qubits.

import networkx as nx

from qibo import Circuit, gates
from qibo.transpiler import Passes, Preprocessing, ShortestPaths

circuit = Circuit(4)
circuit.add(gates.H(0))
circuit.add(gates.H(2))
circuit.add(gates.CNOT(0, 1))
circuit.add(gates.CNOT(2, 3))
circuit.add(gates.CNOT(0, 3))
circuit.add(gates.CNOT(1, 2))
circuit.add(gates.M(0, 1, 2, 3))

circuit.draw(parallel=True)
print()

pipeline = Passes(
    [Preprocessing(), ShortestPaths(seed=1)], connectivity=nx.path_graph(5)
)
transpiled_circuit, layout = pipeline(circuit)

transpiled_circuit.draw(parallel=True)
print()

print(dict(layout))
print(layout.initial_layout)
print(layout.routing_permutation)
print(layout.nqubits_input)
print(layout.auxiliary_qubits)
print(layout.initial_logical_layout(filter_auxiliary=True))
print(layout.final_logical_layout(filter_auxiliary=True))
0: ─H─o─o───M─
1: ───X─|─o─M─
2: ─H─o─|─X─M─
3: ───X─X───M─

0: ─H─o───────o─M─
1: ───X─o───x─X─M─
2: ─H─o─X─x─x───M─
3: ───X───x─────M─
4: ───────────────

{0: 0, 1: 2, 2: 3, 3: 1, 4: 4}
[0, 1, 2, 3, 4]
[0, 2, 3, 1, 4]
4
[4]
{0: 0, 1: 1, 2: 2, 3: 3}
{0: 0, 1: 2, 2: 3, 3: 1}
Parameters:
  • final_layout (dict) – Mapping from the label of the physical qubit on which each qubit is placed before the routing, to the index of the physical qubit on which it ends after the routing.

  • initial_layout (list) – Labels of the physical qubits on which the qubits of the circuit are placed before the routing. The \(i\)-th element is the physical qubit of the \(i\)-th qubit of the circuit.

  • nqubits_input (int) – Number of qubits of the circuit before it is padded with the unused qubits of the hardware, which are the auxiliary qubits.

property auxiliary_qubits: list#

Labels of the physical qubits that the preprocessing adds to the circuit as auxiliary qubits.

property final_layout: dict#

Mapping from the physical qubit on which each qubit is placed before the routing, to the one on which it ends after the routing, as a plain dictionary.

property routing_permutation: list[int]#

Permutation of the qubits that the routing applies. Its \(i\)-th element is the index of the physical qubit on which the \(i\)-th qubit of the circuit ends after the routing. The elements from nqubits_input onward are the auxiliary qubits.

final_logical_layout(filter_auxiliary=False)[source]#

Index of the physical qubit on which each qubit of the circuit ends.

Parameters:

filter_auxiliary (bool, optional) – If True, the auxiliary qubits are left out. Defaults to False.

Returns:

Mapping from the index of each qubit of the circuit to the index of the physical qubit on which it ends after the routing, see qibo.transpiler.layout.TranspileLayout.routing_permutation.

Return type:

dict

initial_logical_layout(filter_auxiliary=False)[source]#

Label of the physical qubit on which each qubit of the circuit is placed.

Parameters:

filter_auxiliary (bool, optional) – If True, the auxiliary qubits are left out. Defaults to False.

Returns:

Mapping from the index of each qubit of the circuit to the label of the physical qubit on which it is placed before the routing.

Return type:

dict

Placer#

Dense#

class qibo.transpiler.placer.Dense(connectivity=None)[source]#

Places the qubits on the most connected region of the hardware.

The \(n\) qubits that take part in two-qubit gates are placed on a connected region of \(n\) physical qubits with the largest number of connections among them. The qubits with more two-qubit gates are placed on the physical qubits with more connections in the region, and the other qubits take the remaining physical qubits in the order of the nodes of the connectivity.

Parameters:

connectivity (networkx.Graph, optional) – Hardware connectivity. Defaults to None.

Example

The three qubits of a triangle of CNOT gates are placed on the triangle of the connectivity, which needs no SWAP gate, instead of on the first three nodes.

import networkx as nx

from qibo import Circuit, gates
from qibo.transpiler import Dense

connectivity = nx.Graph([(0, 1), (1, 2), (2, 3), (3, 4), (2, 4)])

circuit = Circuit(5)
circuit.add(gates.CNOT(0, 1))
circuit.add(gates.CNOT(1, 2))
circuit.add(gates.CNOT(0, 2))

Dense(connectivity)(circuit)
print(circuit.wire_names)
[2, 3, 4, 0, 1]

Random#

class qibo.transpiler.placer.Random(connectivity=None, samples=100, seed=None)[source]#

Random initialization with greedy policy, so that a maximum number of 2-qubit gates can be applied without introducing any SWAP gate.

Parameters:
  • connectivity (networkx.Graph, optional) – Hardware connectivity. Defaults to None.

  • samples (int, optional) – Number of random initializations to try. If \(0\), no random initialization is tried and the qubits are placed following the order of the nodes of connectivity, i.e. the \(i\)-th qubit is placed on the \(i\)-th node. Defaults to \(100\).

  • seed (int or numpy.random.Generator, optional) – Either a generator of random numbers or a fixed seed to initialize a generator. If None, initializes a generator with a random seed. Defaults to None.

Reverse traversal#

class qibo.transpiler.placer.ReverseTraversal(routing_algorithm, connectivity=None, depth=None, trials=1, seed=None)[source]#

Places qubits based on the algorithm proposed in Reference [1].

Compatible with all the available qibo.transpiler.abstract.Router classes.

Parameters:
  • connectivity (networkx.Graph) – Hardware connectivity.

  • routing_algorithm (qibo.transpiler.abstract.Router) – Router to be used.

  • depth (int, optional) – Number of two-qubit gates to be considered for routing. If None just one backward step will be implemented. If depth is greater than the number of two-qubit gates in the circuit, the circuit will be routed more than once. Example: on a circuit with four two-qubit gates \(A-B-C-D\) using depth \(d = 6\), the routing will be performed on the circuit \(C-D-D-C-B-A\).

  • trials (int, optional) – Number of independent layout attempts. The first one starts from the layout of the circuit, and the others from random permutations of it. Each attempt finds a layout with the reverse traversal, and the one for which the router adds the fewest SWAP gates to the circuit is kept, the first one among ties. With one attempt, no routing of the circuit is needed to choose the layout. Defaults to \(1\).

  • seed (int, optional) – Seed of the random permutations of the attempts, which makes them reproducible. Defaults to None.

References

1. G. Li, Y. Ding, and Y. Xie, Tackling the Qubit Mapping Problem for NISQ-Era Quantum Devices. arXiv:1809.02573 [cs.ET].

Subgraph#

class qibo.transpiler.placer.Subgraph(connectivity=None)[source]#

Subgraph isomorphism qubit placer.

Since it is a \(NP\)-complete problem, it can take exponential time for large circuits. This initialization method may fail for very short circuits.

Parameters:

connectivity (networkx.Graph, optional) – Hardware connectivity. Defaults to None.

Star connectivity#

class qibo.transpiler.placer.StarConnectivityPlacer(connectivity=None)[source]#

Find an optimized qubit placement for the following connectivity:

     q
     |
q -- q -- q
     |
     q
Parameters:

connectivity (networkx.Graph, optional) – Star connectivity graph. Defaults to None.

Router#

Circuit map#

class qibo.transpiler.router.CircuitMap(circuit=None, blocks=None, temp=False)[source]#

Class that stores the circuit and physical-logical mapping during routing.

Also implements the initial two-qubit block decompositions.

Parameters:
  • circuit (qibo.models.circuit.Circuit) – Circuit to be routed.

  • blocks (qibo.transpiler.blocks.CircuitBlocks, optional) – Circuit block representation. If None, the blocks will be computed from the circuit. Defaults to None.

  • temp (bool, optional) – If True, only the qubit mappings are stored, which is used to evaluate the cost of a SWAP without modifying the circuit. Defaults to False.

property physical_to_logical: list[int]#

Returns the physical to logical qubit mapping.

property logical_to_physical: list[int]#

Returns the logical to physical qubit mapping.

blocks_logical_qubits_pairs()[source]#

Returns a list containing the logical qubit pairs of each block.

Return type:

list[tuple]

execute_block(block)[source]#

Executes a block by removing it from the circuit representation and adding it to the routed circuit.

Method works in-place.

Parameters:

block (qibo.transpiler.blocks.Block) – Block to be removed.

Return type:

None

routed_circuit(circuit_kwargs=None)[source]#

Returns the routed circuit.

Parameters:

circuit_kwargs (dict, optional) – Original circuit init_kwargs. Defaults to None.

Returns:

Routed circuit.

Return type:

qibo.models.circuit.Circuit

final_layout()[source]#

Returns the final {logical: physical} qubits mapping.

Return type:

dict

update(logical_swap)[source]#

Updates the qubit mapping after applying a SWAP.

Adds the qibo.gates.gates.SWAP gate to the routed blocks. Method works in-place.

Parameters:

logical_swap (tuple) – Tuple containing the logical qubits to be swapped.

Return type:

None

undo()[source]#

Undo the last swap. Method works in-place.

Return type:

None

get_physical_qubits(block)[source]#

Returns the physical qubits where a block is acting on.

Parameters:

block (int or qibo.transpiler.blocks.Block) – Block to be analysed.

Returns:

Physical qubit numbers where a block is acting on.

Return type:

tuple

logical_pair_to_physical(logical_qubits)[source]#

Returns the physical qubits associated to the logical qubit pair.

Parameters:

logical_qubits (tuple) – Logical qubit pair.

Returns:

Physical qubit numbers associated to the logical qubit pair.

Return type:

tuple

No routing#

class qibo.transpiler.router.NoRouting(connectivity=None)[source]#

Leaves the circuit unchanged, and raises an error if it cannot run on the hardware.

Parameters:

connectivity (networkx.Graph, optional) – Hardware connectivity. Defaults to None.

Example

The two-qubit gates of the circuit act on connected qubits, so the circuit is not changed and no qubit is moved.

import networkx as nx

from qibo import Circuit, gates
from qibo.transpiler import NoRouting

circuit = Circuit(3)
circuit.add(gates.CNOT(0, 1))
circuit.add(gates.CNOT(1, 2))

routed_circuit, final_layout = NoRouting(nx.path_graph(3))(circuit)
print(final_layout)
{0: 0, 1: 1, 2: 2}

Sabre#

class qibo.transpiler.router.Sabre(connectivity=None, lookahead=2, decay_lookahead=0.6, delta=0.001, swap_threshold=1.5, seed=None, trials=1, heuristic='decay')[source]#

Routing algorithm proposed in Ref [1].

Parameters:
  • connectivity (networkx.Graph) – Hardware chip connectivity.

  • lookahead (int, optional) – Lookahead factor, how many dag layers will be considered in computing the cost. Defaults to \(2\).

  • decay_lookahead (float, optional) – Value in interval \([0, 1]\). How the weight of the distance in the dag layers decays in computing the cost. Defaults to \(0.6\).

  • delta (float, optional) – Defines the number of SWAPs vs depth trade-off by deciding how the algorithm tends to select non-overlapping SWAPs. Defaults to \(10^{-3}\).

  • swap_threshold (float, optional) – Limits the number of added SWAPs in every routing iteration. This threshold is multiplied by the length of the longest path in the circuit connectivity. If the number of added SWAPs exceeds the threshold before a gate is routed, shortestpath routing is applied. Defaults to \(1.5\).

  • seed (int, optional) – Seed for the candidate random choice as tiebreaker. Defaults to None.

  • trials (int, optional) – Number of independent routing attempts, which differ in the random choices among equally good candidates. The attempt with the lowest number of added SWAPs is returned, and the first one among ties. The attempts are reproducible if seed is given, and the first one does not depend on trials. Defaults to \(1\).

  • heuristic (str, optional) –

    Cost that selects the SWAP gate to be added. It is the distance between the qubits of the gates in the front layer of the circuit, plus the terms below:

    • "basic": no other term, so lookahead, decay_lookahead and delta are not used.

    • "lookahead": the distances of the gates in the next lookahead layers, weighted by decay_lookahead. delta is not used.

    • "decay": the terms of "lookahead", with the distances of the qubits that were recently swapped increased by delta, which favors non-overlapping SWAPs.

    Defaults to "decay".

Raises:

ValueError – If trials is not positive, or heuristic is not "basic", "lookahead" or "decay".

References

1. G. Li, Y. Ding, and Y. Xie, Tackling the Qubit Mapping Problem for NISQ-Era Quantum Devices. arXiv:1809.02573 [cs.ET].

property added_swaps: int#

Returns the number of SWAP gates added to the circuit during routing.

Shorterst paths#

class qibo.transpiler.router.ShortestPaths(connectivity=None, seed=None)[source]#

A class to perform initial qubit mapping and connectivity matching.

Parameters:
  • connectivity (networkx.Graph) – Chip connectivity.

  • seed (int, optional) – Seed for the random number generator. If None, defaults to \(42\). Defaults to None.

property added_swaps: int#

Returns the number of SWAP gates added to the circuit during routing.

Start connectivity#

class qibo.transpiler.router.StarConnectivityRouter(connectivity=None)[source]#

Transforms an arbitrary circuit to one that can be executed on hardware.

This transpiler produces a circuit that respects the following connectivity by adding SWAP gates when needed:

     q
     |
q -- q -- q
     |
     q
Parameters:

connectivity (networkx.Graph) – Star connectivity graph.

Unroller#

Native gates#

class qibo.transpiler.unroller.NativeGates(value, names=<not given>, *values, module=None, qualname=None, type=None, start=1, boundary=None)[source]#

Define native gates supported by the unroller.

A native gate set should contain at least one entangling two-qubit gate, which is any of the two-qubit gates below except qibo.gates.gates.SWAP, and single-qubit gates that can implement any single-qubit unitary. These are qibo.gates.gates.U3, qibo.gates.gates.GPI2, or a combination of the other single-qubit gates that has two rotations with free angles about orthogonal axes, see qibo.transpiler.decompositions.unitary_decompositions.single_qubit_decomposition(). For example, qibo.gates.gates.RX and qibo.gates.gates.RZ, or qibo.gates.gates.RZ and qibo.gates.gates.H. qibo.gates.gates.Z and qibo.gates.gates.RZ are virtual gates, which are assumed to be available together with qibo.gates.gates.GPI2 and qibo.gates.gates.U3.

Possible gates are:

If several entangling gates are native, any other gate is decomposed into the first one among qibo.gates.gates.CZ, qibo.gates.gates.iSWAP, qibo.gates.gates.CNOT and the gates above, in this order. A native two-qubit gate is never decomposed.

classmethod default()[source]#

Return default native gates set.

Return type:

NativeGates

classmethod from_gatelist(gatelist)[source]#

Create a NativeGates object containing all gates from a gatelist.

Parameters:

gatelist (list[qibo.gates.abstract.Gate]) –

Return type:

NativeGates

classmethod from_gate(gate)[source]#

Create a qibo.transpiler.unroller.NativeGates object from a qibo.gates.gates.Gate.

Parameters:

gate (Gate | type[qibo.gates.abstract.Gate]) –

Return type:

NativeGates

property is_universal: bool#

Whether the native gates can implement any two-qubit unitary.

This holds when the set contains at least one entangling two-qubit gate, which is any of them except qibo.gates.gates.SWAP, and single-qubit gates that implement any single-qubit unitary exactly: qibo.gates.gates.U3, qibo.gates.gates.GPI2, or gates with two rotations about orthogonal axes with free angles, see single_qubit_gates. Single-qubit unitaries together with any entangling two-qubit gate can implement every unitary on any number of connected qubits [1].

References

1. J.-L. Brylinski and R. Brylinski, Universal quantum gates, in Mathematics of Quantum Computation, Chapman & Hall/CRC (2002).

property single_qubit_gates: tuple[type[qibo.gates.abstract.Gate], ...]#

Classes of the single-qubit gates in the native gates.

property two_qubit_gates: tuple[type[qibo.gates.abstract.Gate], ...]#

Classes of the two-qubit gates in the native gates.

property gate_names: tuple[str, ...]#

Names of the gates in the native gates, without the measurement.

The names are the ones of qibo.gates.abstract.Gate.name, e.g. "cx" for qibo.gates.gates.CNOT. They can be the basis of qibo.transpiler.optimizer.Optimize1qGatesDecomposition.

Unroller#

class qibo.transpiler.unroller.Unroller(native_gates, use_dirty_ancillas=False, min_qubits=1, qubits_initially_zero=False, minimize_toffolis=False, minimize_depth=False, approximation_degree=1.0, threshold=None, weight=None, backend=None)[source]#

Decomposes a circuit to native gates.

Parameters:
  • native_gates (qibo.transpiler.unroller.NativeGates) – Native gates to use in the transpiled circuit. They must be universal, see qibo.transpiler.unroller.NativeGates.is_universal.

  • use_dirty_ancillas (bool, optional) – If True, the qubits of the circuit that a multi-controlled gate does not act on are used as dirty auxiliary qubits in its decomposition, which makes the cost of a multi-controlled qibo.gates.X linear in the number of controls. They can be in any state and are left unchanged. Not using dirty auxiliary qubits makes the gate count quadratic in the number of controls. Defaults to False.

  • min_qubits (int, optional) – Only gates acting on at least min_qubits qubits are decomposed, the others are left unchanged. For example, min_qubits=3 decomposes only gates acting on three or more qubits, which is useful to make a circuit executable by a router before routing. Defaults to \(1\), which decomposes every gate.

  • qubits_initially_zero (bool, optional) – If True, the circuit is assumed to start in the state \(\ket{0}^{\otimes n}\). The qubits on which no previous gate of the circuit acts are then known to be in the state \(\ket{0}\), and are used as clean auxiliary qubits in the decomposition of multi-controlled gates, being left in that state. They are used as clean qubits if that lowers the number of native gates, and as dirty ones otherwise. It must be False if the circuit is executed with another initial state. Defaults to False.

  • minimize_toffolis (bool, optional) – If True, a multi-controlled qibo.gates.X with at least one auxiliary qubit is decomposed with the lowest number of Toffoli gates, which is useful if they are expensive. This is not the decomposition with the fewest native gates, which is the default one, and it needs more of them with dirty auxiliary qubits. With clean auxiliary qubits (see qubits_initially_zero), it is shallower. It cannot be used together with minimize_depth. Defaults to False.

  • minimize_depth (bool, optional) – If True, a multi-controlled qibo.gates.X with at least one auxiliary qubit is decomposed with a depth that is logarithmic in the number of controls. It cannot be used together with minimize_toffolis. See qibo.transpiler.multi_controlled_decomposition(). Defaults to False.

  • approximation_degree (float, optional) – Fidelity of each qibo.gates.CZ gate, between \(0\) and \(1\). If it is lower than \(1\), the two-qubit gates that are decomposed from their matrix, i.e. the unitaries, the qibo.gates.fSim and qibo.gates.GeneralizedfSim gates and the gates without a known decomposition, can use fewer qibo.gates.CZ gates than the exact decomposition, if it increases the expected fidelity, see qibo.transpiler.decompositions.unitary_decompositions.two_qubit_decomposition(). The other gates are decomposed exactly. Defaults to \(1\), which is the exact decomposition.

  • threshold (float, optional) – Tolerance of the decomposition of the two-qubit gates that are decomposed from their matrix, like for approximation_degree, see qibo.transpiler.decompositions.unitary_decompositions.two_qubit_decomposition(). Defaults to None, which uses its default value.

  • weight (float, optional) – Weight of the imaginary part in the matrix that is diagonalized to decompose those gates, see qibo.transpiler.decompositions.unitary_decompositions.two_qubit_decomposition(). Defaults to None, which uses its default value.

  • backend (qibo.backends.abstract.Backend, optional) – Backend to use for gate matrix. Defaults to None.

Raises:

ValueError – If approximation_degree is not between \(0\) and \(1\).

Translate gate#

qibo.transpiler.unroller.translate_gate(gate, native_gates, free=(), clean=(), minimize_toffolis=False, minimize_depth=False, approximation_degree=1.0, threshold=None, weight=None, backend=None)[source]#

Maps gates to a hardware-native implementation.

Parameters:
  • gate (qibo.gates.abstract.Gate) – Gate to be decomposed.

  • native_gates (qibo.transpiler.unroller.NativeGates) – Native gates supported by the hardware.

  • free (tuple[int, ...], optional) – Ids of free qubits that can be used as dirty auxiliary qubits to decompose multi-controlled gates. Defaults to an empty tuple, which uses no auxiliary qubits.

  • clean (tuple[int, ...], optional) – Ids of free qubits that are known to be in the state \(\ket{0}\) and are left in that state, which can be used as clean auxiliary qubits to decompose multi-controlled gates. They must not be in free. Defaults to an empty tuple.

  • minimize_toffolis (bool, optional) – If True, a multi-controlled qibo.gates.X is decomposed with the lowest number of Toffoli gates when it has auxiliary qubits, see qibo.transpiler.multi_controlled_decomposition(). Defaults to False.

  • minimize_depth (bool, optional) – If True, a multi-controlled qibo.gates.X is decomposed with a logarithmic depth when it has auxiliary qubits. Defaults to False.

  • approximation_degree (float, optional) – Fidelity of each qibo.gates.CZ gate, between \(0\) and \(1\), used to approximate the two-qubit gates that are decomposed from their matrix with fewer qibo.gates.CZ gates, see qibo.transpiler.unroller.Unroller. Defaults to \(1\), which is the exact decomposition.

  • threshold (float, optional) – Tolerance of the decomposition of the two-qubit gates that are decomposed from their matrix, like for approximation_degree, see qibo.transpiler.unroller.Unroller. Defaults to None, which uses the default value of the decomposition.

  • weight (float, optional) – Weight of the imaginary part in the matrix that is diagonalized to decompose those gates, see qibo.transpiler.unroller.Unroller. Defaults to None, which uses the default value of the decomposition.

  • backend (qibo.backends.abstract.Backend, optional) – Backend to use for gate matrix. If None, defaults to the global backend. Defaults to None.

Returns:

Native gates that decompose the input gate. If the decomposition is equal to the gate only up to a global phase, its last gate is the qibo.gates.GlobalPhase gate that corrects it.

Return type:

list[qibo.gates.abstract.Gate]

Raises:

DecompositionError – If the gate acts on more than two qubits and is not a controlled gate, a fused gate or a unitary that can be decomposed, or if the native gates are not sufficient.