Transpiler#

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

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.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.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=None, *, 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 two-qubit gate (qibo.gates.gates.CZ, qibo.gates.gates.iSWAP or qibo.gates.gates.CNOT), 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.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:
classmethod default()[source]#

Return default native gates set.

classmethod from_gatelist(gatelist)[source]#

Create a NativeGates object containing all gates from a gatelist.

Parameters:

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

classmethod from_gate(gate)[source]#

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

Parameters:

gate (Gate) –

property is_universal: bool#

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

This holds when the set contains at least one entangling gate among qibo.gates.gates.CZ, qibo.gates.gates.iSWAP and qibo.gates.gates.CNOT, 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.

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.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)[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, default transpiler will be used. Defaults to None.

  • connectivity (networkx.Graph, optional) – Hardware connectivity.

  • 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.

is_satisfied(circuit)[source]#

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

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)[source]#

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

Parameters:

connectivity (networkx.Graph) – Hardware connectivity as a graph.

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

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

Parameters:
  • connectivity (Graph | None) –

  • samples (int) –

  • seed (int | None) –

connectivity#

Hardware connectivity.

Type:

networkx.Graph

samples#

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\).

Type:

int, optional

seed#

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.

Type:

int or numpy.random.Generator, optional

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 and fused gates are never removed. 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)[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\).

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)[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 math:10^{-3}.

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

  • 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\).

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#

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#

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) – Star connectivity graph.

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 (Graph | None) –

connectivity#

Hardware connectivity.

Type:

networkx.Graph

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.Unroller(native_gates, backend=None, use_dirty_ancillas=False)[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.

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

  • 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.

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, no auxiliary qubits are 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]. If free qubits are given, a multi-controlled \(X\) gate uses them as dirty auxiliary qubits, which makes its cost linear following Ref. [3]. Other gates do not benefit from auxiliary qubits, and free and clean are ignored. This is the decomposition used by qibo.gates.abstract.Gate.decompose() for gates controlled by more than one qubit.

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 more CNOTs than the default. 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 unless minimize_toffolis is True. 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.

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) –

Router#

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

Makes the circuit executable on the given topology.

Parameters:

connectivity (Graph) –

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. Defauts to None.

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

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. All two-qubit operations can be performed on hardware.

Parameters:

Assert decomposition#

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

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

Parameters:

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:

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:

Blocks#

Block#

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

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

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#

Returns True if the block contains two-qubit gates.

add_gate(gate)[source]#

Add a new gate to the block.

Parameters:

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

property qubits#

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.

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.

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.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) –

add_block(block)[source]#

Add a two qubits block.

Parameters:

block (Block) –

circuit(circuit_kwargs=None)[source]#

Return the quantum circuit.

Parameters:

circuit_kwargs (dict) – original circuit init_kwargs.

remove_block(block)[source]#

Remove a block from the circuit blocks.

Parameters:

block (Block) –

return_last_block()[source]#

Return the last block in the circuit blocks.

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 qubit.

Parameters:
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.

count_2q(gate, backend)[source]#

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

Parameters:

backend (Backend) –

count_1q(gate, backend)[source]#

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

Multi-controlled gate decomposition#

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

qibo.transpiler.multicontrolled_decompositions.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, no auxiliary qubits are 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]. If free qubits are given, a multi-controlled \(X\) gate uses them as dirty auxiliary qubits, which makes its cost linear following Ref. [3]. Other gates do not benefit from auxiliary qubits, and free and clean are ignored. This is the decomposition used by qibo.gates.abstract.Gate.decompose() for gates controlled by more than one qubit.

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 more CNOTs than the default. 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 unless minimize_toffolis is True. 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.

Optimizer#

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)

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)[source]#

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

Parameters:

connectivity (networkx.Graph) – Hardware connectivity as a graph.

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 and fused gates are never removed. 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─

Pipeline#

Passes#

class qibo.transpiler.pipeline.Passes(passes=None, connectivity=None, native_gates=None, on_qubits=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, default transpiler will be used. Defaults to None.

  • connectivity (networkx.Graph, optional) – Hardware connectivity.

  • 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.

is_satisfied(circuit)[source]#

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

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)

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)

Placer#

Random#

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

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

Parameters:
  • connectivity (Graph | None) –

  • samples (int) –

  • seed (int | None) –

connectivity#

Hardware connectivity.

Type:

networkx.Graph

samples#

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\).

Type:

int, optional

seed#

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.

Type:

int or numpy.random.Generator, optional

Reverse traversal#

class qibo.transpiler.placer.ReverseTraversal(routing_algorithm, connectivity=None, depth=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\).

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 (Graph | None) –

connectivity#

Hardware connectivity.

Type:

networkx.Graph

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) – Star connectivity graph.

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:
property physical_to_logical#

Returns the physical to logical qubit mapping.

property logical_to_physical#

Returns the logical to physical qubit mapping.

blocks_logical_qubits_pairs()[source]#

Returns a list containing the logical qubit pairs of each block.

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.

routed_circuit(circuit_kwargs=None)[source]#

Returns the routed circuit.

Parameters:

circuit_kwargs (dict) – Original circuit init_kwargs.

Returns:

Routed circuit.

Return type:

qibo.models.circuit.Circuit

final_layout()[source]#

Returns the final {logical: physical} qubits mapping.

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:
  • swap (tuple) – Tuple containing the logical qubits to be swapped.

  • logical_swap (tuple) –

undo()[source]#

Undo the last swap. Method works in-place.

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

Sabre#

class qibo.transpiler.router.Sabre(connectivity=None, lookahead=2, decay_lookahead=0.6, delta=0.001, swap_threshold=1.5, seed=None)[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 math:10^{-3}.

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

  • 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\).

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#

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#

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=None, *, 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 two-qubit gate (qibo.gates.gates.CZ, qibo.gates.gates.iSWAP or qibo.gates.gates.CNOT), 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.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:
classmethod default()[source]#

Return default native gates set.

classmethod from_gatelist(gatelist)[source]#

Create a NativeGates object containing all gates from a gatelist.

Parameters:

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

classmethod from_gate(gate)[source]#

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

Parameters:

gate (Gate) –

property is_universal: bool#

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

This holds when the set contains at least one entangling gate among qibo.gates.gates.CZ, qibo.gates.gates.iSWAP and qibo.gates.gates.CNOT, 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.

Unroller#

class qibo.transpiler.unroller.Unroller(native_gates, backend=None, use_dirty_ancillas=False)[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.

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

  • 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.

Translate gate#

qibo.transpiler.unroller.translate_gate(gate, native_gates, backend=None, free=())[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.

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

  • 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.

Returns:

Native gates that decompose the input gate.

Return type:

list[qibo.gates.abstract.Gate]

Raises:

DecompositionError – If the gate acts on more than two qubits and is not a controlled gate that can be decomposed, or if the native gates are not sufficient.