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. Aqibo.gates.SWAPgate counts as three CZ gates and anqibo.gates.iSWAPgate 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.CZandqibo.gates.Unitarygates, so this pass is meant to be used beforeqibo.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_iterationsiterations. 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
connectivitythatqibo.transpiler.pipeline.Passessets on this optimizer is set onoptimizerstoo.- 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
commutationisTrue, 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_bya 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.RXgates with angles \(x\) and \(-x\) cancel, and aqibo.gates.RZgate with angle \(x\) is skipped over by a controlled-NOT gate on its control qubit ifcommutationisTrue, 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 toFalse.
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=Truethe 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.iSWAPorqibo.gates.gates.CNOT), and single-qubit gates that can implement any single-qubit unitary. These areqibo.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, seeqibo.transpiler.unitary_decompositions.single_qubit_decomposition(). For example,qibo.gates.gates.RXandqibo.gates.gates.RZ, orqibo.gates.gates.RZandqibo.gates.gates.H.qibo.gates.gates.Zandqibo.gates.gates.RZare virtual gates, which are assumed to be available together withqibo.gates.gates.GPI2andqibo.gates.gates.U3.- Possible gates are:
qibo.gates.gates.Mqibo.gates.gates.SDGqibo.gates.gates.TDGqibo.gates.gates.SXDG
- 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.NativeGatesobject from aqibo.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.iSWAPandqibo.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, seesingle_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).
- 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
basisand the shortest result is kept. The Euler bases are:"U3": oneqibo.gates.U3gate;"U321": oneqibo.gates.U1,qibo.gates.U2orqibo.gates.U3gate, the simplest one;"ZYZ","ZXZ","XYX","XZX": three rotations around the named axes, withqibo.gates.RX,qibo.gates.RYandqibo.gates.RZgates;"ZSX":qibo.gates.RZgates separated byqibo.gates.SXgates, where SX is the square root of the Pauli X gate;"ZSXX": as"ZSX", with aqibo.gates.Xgate replacing two SX gates when possible.
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. Ifbasiscontains 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. IfNone, all Euler bases are used and every gate counts as in the basis. Defaults toNone.atol (float, optional) – Tolerance to decide that an angle is zero, that \(\theta\) (the Y rotation angle of the equivalent
qibo.gates.U3gate) 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 toTrue.
Example
The Hadamard, phase and Pauli X gates on the first qubit become an
qibo.gates.RZgate and anqibo.gates.SXgate, 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.RYgate with angle \(\alpha\) followed by aqibo.gates.RYgate with angle \(\beta\) on the same qubit becomes oneqibo.gates.RYgate 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:
the single-qubit rotations
qibo.gates.RX,qibo.gates.RY,qibo.gates.RZandqibo.gates.U1;the controlled rotations
qibo.gates.CRX,qibo.gates.CRY,qibo.gates.CRZandqibo.gates.CU1;the two-qubit rotations
qibo.gates.RXX,qibo.gates.RYY,qibo.gates.RZZ,qibo.gates.RZX,qibo.gates.RXXYY,qibo.gates.RBSandqibo.gates.GIVENS;qibo.gates.fSim, where both the swap angle and the phase add up;qibo.gates.PRXandqibo.gates.U1q, where the rotation angle adds up as long as the phase angle of both gates is the same up toatol.
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 gatesqibo.gates.U2andqibo.gates.U3are 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 asqibo.gates.RZZ) are also merged when their qubits are listed in a different order, and so areqibo.gates.RBSandqibo.gates.GIVENS, whose angle changes sign when the qubits are exchanged.The general single-qubit gates
qibo.gates.U2andqibo.gates.U3, and their controlled versionsqibo.gates.CU2andqibo.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.U2gate with angles \(\phi'\) and \(\lambda'\) if \(\theta' = \pi / 2\), and aqibo.gates.U3gate otherwise. This merge is exact, including the global phase, for gates without control qubits and for controlled gates alike.The phase gate
qibo.gates.U1and its controlled versionqibo.gates.CU1differ 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 ifup_to_global_phaseisTrueand 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 theqibo.gates.U1gate with angle \(\phi' + \lambda'\) only ifup_to_global_phaseisTrueand the gates have no control qubits.All other gates, such as
qibo.gates.GPI2orqibo.gates.U1qandqibo.gates.PRXwith 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, forqibo.gates.MS, could leave the range of allowed angles.Only gates with the same
trainableflag 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 laterset_parameterscall 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.PRXorqibo.gates.U1qgates 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 aqibo.gates.U1gate is merged with aqibo.gates.U2orqibo.gates.U3gate. Defaults toFalse.
Example
The two
qibo.gates.RYgates on the first qubit are merged. The twoqibo.gates.RBSgates act on the same qubits in reversed order, so the angle of the second one enters with the opposite sign. The twoqibo.gates.RZgates 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.U3gate followed by aqibo.gates.U2gate gives oneqibo.gates.U3gate, 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 toNone.connectivity (
networkx.Graph, optional) – Hardware connectivity.native_gates (
qibo.transpiler.unroller.NativeGates, optional) – Native gates supported by the hardware. Defaults toqibo.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 toNone.
- 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:
Trueif the circuit respects the hardware connectivity and native gates,Falseotherwise.- 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.
- 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 toNone.- 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 ofqibo.gates.SWAPwhen 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:
the single-qubit gates
qibo.gates.Z,qibo.gates.S,qibo.gates.SDG,qibo.gates.T,qibo.gates.TDG,qibo.gates.RZ(rotation around the Z axis) andqibo.gates.U1(phase gate);the two-qubit gates
qibo.gates.CZ(controlled-Z),qibo.gates.CRZ(controlled-RZ),qibo.gates.CU1(controlled-U1) andqibo.gates.RZZ(rotation generated by the product of the Pauli-Z operators of the two qubits);the three-qubit gate
qibo.gates.CCZ(controlled-controlled-Z).
Any of these gates with further control qubits added through
controlled_byis still diagonal, so it is removed as well. For example, aqibo.gates.Sgate 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.RZgate followed by aqibo.gates.Zgate and then by a measurement keeps theqibo.gates.RZgate. 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.RZgate is removed because a measurement follows it on the first qubit, while theqibo.gates.Sgate 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.ResetChannelthat 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_bythat 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.ResetChannelthat 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.ResetChannelthat 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 aqibo.gates.U3gate 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 thesymbolsof 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.Routerclasses.- 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
Nonejust 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 toNone.
- 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.Tgates 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, seeqibo.transpiler.unroller.NativeGates.is_universal.backend (
qibo.backends.abstract.Backend, optional) – Backend to use for gate matrix. Defaults toNone.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-controlledqibo.gates.Xlinear 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 toFalse.
- 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
freequbits 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, andfreeandcleanare ignored. This is the decomposition used byqibo.gates.abstract.Gate.decompose()for gates controlled by more than one qubit.If
minimize_toffolisisTrue, 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_depthisTrue, 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.
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_toffolisisTrue. 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 toFalse.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 withminimize_toffolis. Defaults toFalse.backend (
qibo.backends.abstract.Backend, optional) – Backend ofunitary, which is only used to move it to the CPU, since the decomposition is always computed with NumPy. IfNone, it uses the current backend. Defaults toNone.
- 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#
Placer#
Router#
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,ntestsrandom states will be tested. Defauts toNone.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:
circuit (
qibo.models.circuit.Circuit) – Circuit to check.connectivity (
networkx.Graph) – Hardware connectivity.
Assert decomposition#
- qibo.transpiler.asserts.assert_decomposition(circuit, native_gates)[source]#
Checks if a circuit has been correctly decomposed into native gates.
- Parameters:
circuit (
qibo.models.circuit.Circuit) – Circuit to check.native_gates (
qibo.transpiler.unroller.NativeGates) – Native gates supported by the hardware.
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, optional) – Hardware connectivity.
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:
original_circuit (
qibo.models.circuit.Circuit) – Circuit before transpiling.transpiled_circuit (
qibo.models.circuit.Circuit) – Circuit after transpiling.connectivity (
networkx.Graph) – Hardware connectivity.final_layout (dict) – Final {logical: physical} qubit mapping.
native_gates (
qibo.transpiler.unroller.NativeGates, optional) – Native gates supported by the hardware. Defaults toqibo.transpiler.unroller.NativeGates.default().check_circuit_equivalence (bool, optional) – Check if the transpiled circuit is equivalent to the original one. Defaults to
True.
Blocks#
Block#
- class qibo.transpiler.blocks.Block(qubits, gates, name=None)[source]#
A block contains a subset of gates acting on two qubits.
- Parameters:
- property entangled#
Returns
Trueif 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:
block (
qibo.transpiler.blocks.Block) – block to fuse.name (str, optional) – name of the fused block. Defaults to
None.
- Returns:
fusion of the two input blocks.
- Return type:
- 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.Unitarygates [1]. It replaces the gates of the block only if it has fewer CZ gates than the block, where aqibo.gates.SWAPgate counts as three CZ gates, anqibo.gates.iSWAPgate 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. IfNone, defaults to the global backend. Defaults toNone.
- 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:
circuit (
qibo.models.circuit.Circuit) – circuit to be decomposed.index_names (bool, optional) – assign names to the blocks. Defaults to
False.
- search_by_index(index)[source]#
Find a block from its index, requires index_names to be
True.- Parameters:
index (int) –
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:
circuit (
qibo.models.circuit.Circuit) – circuit to be decomposed.fuse (bool, optional) – fuse adjacent blocks acting on the same qubits.
- Returns:
List of blocks that act on two qubits.
- Return type:
Decompositions#
Gate decompositions#
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
freequbits 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, andfreeandcleanare ignored. This is the decomposition used byqibo.gates.abstract.Gate.decompose()for gates controlled by more than one qubit.If
minimize_toffolisisTrue, 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_depthisTrue, 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.
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_toffolisisTrue. 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 toFalse.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 withminimize_toffolis. Defaults toFalse.backend (
qibo.backends.abstract.Backend, optional) – Backend ofunitary, which is only used to move it to the CPU, since the decomposition is always computed with NumPy. IfNone, it uses the current backend. Defaults toNone.
- 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
commutationisTrue, 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_bya 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.RXgates with angles \(x\) and \(-x\) cancel, and aqibo.gates.RZgate with angle \(x\) is skipped over by a controlled-NOT gate on its control qubit ifcommutationisTrue, 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 toFalse.
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=Truethe 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
basisand the shortest result is kept. The Euler bases are:"U3": oneqibo.gates.U3gate;"U321": oneqibo.gates.U1,qibo.gates.U2orqibo.gates.U3gate, the simplest one;"ZYZ","ZXZ","XYX","XZX": three rotations around the named axes, withqibo.gates.RX,qibo.gates.RYandqibo.gates.RZgates;"ZSX":qibo.gates.RZgates separated byqibo.gates.SXgates, where SX is the square root of the Pauli X gate;"ZSXX": as"ZSX", with aqibo.gates.Xgate replacing two SX gates when possible.
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. Ifbasiscontains 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. IfNone, all Euler bases are used and every gate counts as in the basis. Defaults toNone.atol (float, optional) – Tolerance to decide that an angle is zero, that \(\theta\) (the Y rotation angle of the equivalent
qibo.gates.U3gate) 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 toTrue.
Example
The Hadamard, phase and Pauli X gates on the first qubit become an
qibo.gates.RZgate and anqibo.gates.SXgate, 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.RYgate with angle \(\alpha\) followed by aqibo.gates.RYgate with angle \(\beta\) on the same qubit becomes oneqibo.gates.RYgate 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:
the single-qubit rotations
qibo.gates.RX,qibo.gates.RY,qibo.gates.RZandqibo.gates.U1;the controlled rotations
qibo.gates.CRX,qibo.gates.CRY,qibo.gates.CRZandqibo.gates.CU1;the two-qubit rotations
qibo.gates.RXX,qibo.gates.RYY,qibo.gates.RZZ,qibo.gates.RZX,qibo.gates.RXXYY,qibo.gates.RBSandqibo.gates.GIVENS;qibo.gates.fSim, where both the swap angle and the phase add up;qibo.gates.PRXandqibo.gates.U1q, where the rotation angle adds up as long as the phase angle of both gates is the same up toatol.
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 gatesqibo.gates.U2andqibo.gates.U3are 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 asqibo.gates.RZZ) are also merged when their qubits are listed in a different order, and so areqibo.gates.RBSandqibo.gates.GIVENS, whose angle changes sign when the qubits are exchanged.The general single-qubit gates
qibo.gates.U2andqibo.gates.U3, and their controlled versionsqibo.gates.CU2andqibo.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.U2gate with angles \(\phi'\) and \(\lambda'\) if \(\theta' = \pi / 2\), and aqibo.gates.U3gate otherwise. This merge is exact, including the global phase, for gates without control qubits and for controlled gates alike.The phase gate
qibo.gates.U1and its controlled versionqibo.gates.CU1differ 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 ifup_to_global_phaseisTrueand 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 theqibo.gates.U1gate with angle \(\phi' + \lambda'\) only ifup_to_global_phaseisTrueand the gates have no control qubits.All other gates, such as
qibo.gates.GPI2orqibo.gates.U1qandqibo.gates.PRXwith 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, forqibo.gates.MS, could leave the range of allowed angles.Only gates with the same
trainableflag 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 laterset_parameterscall 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.PRXorqibo.gates.U1qgates 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 aqibo.gates.U1gate is merged with aqibo.gates.U2orqibo.gates.U3gate. Defaults toFalse.
Example
The two
qibo.gates.RYgates on the first qubit are merged. The twoqibo.gates.RBSgates act on the same qubits in reversed order, so the angle of the second one enters with the opposite sign. The twoqibo.gates.RZgates 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.U3gate followed by aqibo.gates.U2gate gives oneqibo.gates.U3gate, 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#
Rearrange#
- class qibo.transpiler.optimizer.Rearrange(max_qubits=1)[source]#
Rearranges gates using
qibo’s fusion algorithm. May reduce number ofqibo.gates.SWAPwhen 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:
the single-qubit gates
qibo.gates.Z,qibo.gates.S,qibo.gates.SDG,qibo.gates.T,qibo.gates.TDG,qibo.gates.RZ(rotation around the Z axis) andqibo.gates.U1(phase gate);the two-qubit gates
qibo.gates.CZ(controlled-Z),qibo.gates.CRZ(controlled-RZ),qibo.gates.CU1(controlled-U1) andqibo.gates.RZZ(rotation generated by the product of the Pauli-Z operators of the two qubits);the three-qubit gate
qibo.gates.CCZ(controlled-controlled-Z).
Any of these gates with further control qubits added through
controlled_byis still diagonal, so it is removed as well. For example, aqibo.gates.Sgate 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.RZgate followed by aqibo.gates.Zgate and then by a measurement keeps theqibo.gates.RZgate. 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.RZgate is removed because a measurement follows it on the first qubit, while theqibo.gates.Sgate 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.ResetChannelthat 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_bythat 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.ResetChannelthat 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.ResetChannelthat 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 aqibo.gates.U3gate 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 thesymbolsof 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.Tgates 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 toNone.connectivity (
networkx.Graph, optional) – Hardware connectivity.native_gates (
qibo.transpiler.unroller.NativeGates, optional) – Native gates supported by the hardware. Defaults toqibo.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 toNone.
- 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:
Trueif the circuit respects the hardware connectivity and native gates,Falseotherwise.- 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.
- 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 toNone.- 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.Routerclasses.- 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
Nonejust 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#
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. IfNone, the blocks will be computed from the circuit. Defaults toNone.temp (bool | None) –
- 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:
- update(logical_swap)[source]#
Updates the qubit mapping after applying a
SWAPAdds the
qibo.gates.gates.SWAPgate to the routed blocks. 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:
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 toNone.
- 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.iSWAPorqibo.gates.gates.CNOT), and single-qubit gates that can implement any single-qubit unitary. These areqibo.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, seeqibo.transpiler.unitary_decompositions.single_qubit_decomposition(). For example,qibo.gates.gates.RXandqibo.gates.gates.RZ, orqibo.gates.gates.RZandqibo.gates.gates.H.qibo.gates.gates.Zandqibo.gates.gates.RZare virtual gates, which are assumed to be available together withqibo.gates.gates.GPI2andqibo.gates.gates.U3.- Possible gates are:
qibo.gates.gates.Mqibo.gates.gates.SDGqibo.gates.gates.TDGqibo.gates.gates.SXDG
- 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.NativeGatesobject from aqibo.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.iSWAPandqibo.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, seesingle_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).
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, seeqibo.transpiler.unroller.NativeGates.is_universal.backend (
qibo.backends.abstract.Backend, optional) – Backend to use for gate matrix. Defaults toNone.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-controlledqibo.gates.Xlinear 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 toFalse.
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. IfNone, defaults to the global backend. Defaults toNone.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.