Transpiler#
This module provides tools for transpilation of generic quantum circuits into a given set of native gates.
- class qibo.transpiler.BarrierBeforeFinalMeasurements[source]#
Inserts a barrier before the final measurements of a circuit.
A measurement or a barrier is final if no other gate acts on its qubits after it. The final measurements and barriers are placed after a new
qibo.gates.special.Barrierthat acts on all the qubits of the circuit, in their original order, while the other gates stay before the barrier. A circuit without final measurements is not changed.The barrier keeps the tools that rearrange the gates, such as the routers, from moving them across the final measurements. It can be removed afterwards with
qibo.transpiler.optimizer.FilterGates.Example
The measurement of the first qubit is not final, because a Hadamard gate acts on the qubit after it, so it stays before the barrier.
from qibo import Circuit, gates from qibo.transpiler import BarrierBeforeFinalMeasurements circuit = Circuit(2) circuit.add(gates.M(0)) circuit.add(gates.H(0)) circuit.add(gates.CNOT(0, 1)) circuit.add(gates.M(0, 1)) new_circuit = BarrierBeforeFinalMeasurements()(circuit) print([gate.name for gate in new_circuit.queue])
['measure', 'h', 'cx', 'barrier', 'measure']
- class qibo.transpiler.CommutativeCancellation(atol=1e-12, up_to_global_phase=True)[source]#
Merges rotations and cancels pairs of gates through the gates that commute with them.
A rotation is a single-qubit gate without control qubits that is a rotation around the \(X\), \(Y\) or \(Z\) axis, up to a global phase, e.g.
qibo.gates.RZ,qibo.gates.T,qibo.gates.SXorqibo.gates.Y. Two rotations around the same axis on the same qubit are merged into a singleqibo.gates.RX,qibo.gates.RYorqibo.gates.RZgate if all the gates between them commute with the second one, where two gates commute when applying them in either order gives the same matrix. The merged rotation is left out if it is the identity. Then, the pairs of gates whose product is the identity are cancelled through the gates that commute with them, seeqibo.transpiler.optimizer.InverseCancellation.Measurements, alignments, barriers, noise channels and fused gates are never merged nor cancelled, and stop the search across them on their qubits. Gates with sympy parameters are not merged, but the gates that commute with them do not stop the search. The gates are compared through their matrices at their current parameter values, so a merged rotation is not tracked by the returned circuit anymore. The pass is applied once, use
qibo.transpiler.optimizer.FixedPointto repeat it.Example
The two
qibo.gates.RZgates on the first qubit merge, as the controlled-NOT gate between them commutes with them.from qibo import Circuit, gates from qibo.transpiler import CommutativeCancellation circuit = Circuit(2) circuit.add(gates.RZ(0, 0.3)) circuit.add(gates.CNOT(0, 1)) circuit.add(gates.RZ(0, 0.4)) CommutativeCancellation()(circuit).draw()
0: ─RZ─o─ 1: ────X─
- Parameters:
atol (float, optional) – Tolerance on the angle of a rotation to be the identity and on the difference between the matrices that are compared. Defaults to \(10^{-12}\).
up_to_global_phase (bool, optional) – If
False, two rotations are merged only when the new rotation has exactly the same matrix as their product, which keeps the global phase of the circuit. Defaults toTrue.
- 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.decompositions.unitary_decompositions.calculate_psi(). Defaults to \(\sqrt{2}\).
Example
A SWAP gate followed by a controlled-NOT (CNOT) gate costs four CZ gates, which the decomposition reduces to two.
from qibo import Circuit, gates from qibo.transpiler import ConsolidateBlocks circuit = Circuit(2) circuit.add(gates.SWAP(0, 1)) circuit.add(gates.CNOT(0, 1)) consolidated = ConsolidateBlocks()(circuit) print([gate.name for gate in consolidated.queue if len(gate.qubits) == 2])
['cz', 'cz']
References
1. F. Vatan and C. Williams, Optimal quantum circuits for general two-qubit gates, Phys. Rev. A 69, 032315 (2004).
- class qibo.transpiler.Dense(connectivity=None)[source]#
Places the qubits on the most connected region of the hardware.
The \(n\) qubits that take part in two-qubit gates are placed on a connected region of \(n\) physical qubits with the largest number of connections among them. The qubits with more two-qubit gates are placed on the physical qubits with more connections in the region, and the other qubits take the remaining physical qubits in the order of the nodes of the connectivity.
- Parameters:
connectivity (
networkx.Graph, optional) – Hardware connectivity. Defaults toNone.
Example
The three qubits of a triangle of CNOT gates are placed on the triangle of the connectivity, which needs no SWAP gate, instead of on the first three nodes.
import networkx as nx from qibo import Circuit, gates from qibo.transpiler import Dense connectivity = nx.Graph([(0, 1), (1, 2), (2, 3), (3, 4), (2, 4)]) circuit = Circuit(5) circuit.add(gates.CNOT(0, 1)) circuit.add(gates.CNOT(1, 2)) circuit.add(gates.CNOT(0, 2)) Dense(connectivity)(circuit) print(circuit.wire_names)
[2, 3, 4, 0, 1]
- class qibo.transpiler.FilterGates(predicate)[source]#
Removes the gates of a circuit that do not satisfy a condition.
- Parameters:
predicate (Callable) – Function that takes a
qibo.gates.abstract.Gateand returnsTrueif the gate is kept in the circuit andFalseif it is removed.
Example
The barrier is removed, while the other gates are kept.
from qibo import Circuit, gates from qibo.transpiler import FilterGates circuit = Circuit(2) circuit.add(gates.H(0)) circuit.add(gates.Barrier(0, 1)) circuit.add(gates.CNOT(0, 1)) filter_barriers = FilterGates(lambda gate: not isinstance(gate, gates.Barrier)) print([gate.name for gate in filter_barriers(circuit).queue])
['h', 'cx']
- class qibo.transpiler.FixedPoint(optimizers, max_iterations=10)[source]#
Applies a list of optimizers repeatedly until the circuit stops changing.
Each iteration applies the optimizers in order. The loop ends when an iteration leaves all the gates unchanged, i.e. with the same classes, qubits and parameters up to numerical tolerance, or after
max_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=<not given>, *values, module=None, qualname=None, type=None, start=1, boundary=None)[source]#
Define native gates supported by the unroller.
A native gate set should contain at least one entangling two-qubit gate, which is any of the two-qubit gates below except
qibo.gates.gates.SWAP, and single-qubit gates that can implement any single-qubit unitary. These 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.decompositions.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.SXDGqibo.gates.gates.CSXDGqibo.gates.gates.SiSWAPDG
If several entangling gates are native, any other gate is decomposed into the first one among
qibo.gates.gates.CZ,qibo.gates.gates.iSWAP,qibo.gates.gates.CNOTand the gates above, in this order. A native two-qubit gate is never decomposed.- classmethod from_gatelist(gatelist)[source]#
Create a NativeGates object containing all gates from a
gatelist.- Parameters:
gatelist (list[qibo.gates.abstract.Gate]) –
- Return type:
- classmethod from_gate(gate)[source]#
Create a
qibo.transpiler.unroller.NativeGatesobject from aqibo.gates.gates.Gate.- Parameters:
gate (Gate | type[qibo.gates.abstract.Gate]) –
- Return type:
- property is_universal: bool#
Whether the native gates can implement any two-qubit unitary.
This holds when the set contains at least one entangling two-qubit gate, which is any of them except
qibo.gates.gates.SWAP, and single-qubit gates that implement any single-qubit unitary exactly:qibo.gates.gates.U3,qibo.gates.gates.GPI2, or gates with two rotations about orthogonal axes with free angles, 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).
- property single_qubit_gates: tuple[type[qibo.gates.abstract.Gate], ...]#
Classes of the single-qubit gates in the native gates.
- property two_qubit_gates: tuple[type[qibo.gates.abstract.Gate], ...]#
Classes of the two-qubit gates in the native gates.
- property gate_names: tuple[str, ...]#
Names of the gates in the native gates, without the measurement.
The names are the ones of
qibo.gates.abstract.Gate.name, e.g."cx"forqibo.gates.gates.CNOT. They can be thebasisofqibo.transpiler.optimizer.Optimize1qGatesDecomposition.
- class qibo.transpiler.NoRouting(connectivity=None)[source]#
Leaves the circuit unchanged, and raises an error if it cannot run on the hardware.
- Parameters:
connectivity (
networkx.Graph, optional) – Hardware connectivity. Defaults toNone.
Example
The two-qubit gates of the circuit act on connected qubits, so the circuit is not changed and no qubit is moved.
import networkx as nx from qibo import Circuit, gates from qibo.transpiler import NoRouting circuit = Circuit(3) circuit.add(gates.CNOT(0, 1)) circuit.add(gates.CNOT(1, 2)) routed_circuit, final_layout = NoRouting(nx.path_graph(3))(circuit) print(final_layout)
{0: 0, 1: 1, 2: 2}
- class qibo.transpiler.Optimize1qGatesDecomposition(basis=None, atol=1e-12, up_to_global_phase=True)[source]#
Rewrites runs of consecutive single-qubit gates with as few gates as possible.
A run is a sequence of single-qubit gates without control qubits on one qubit with no other gate on that qubit in between. The matrix of the run is decomposed in every Euler basis allowed by
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;"ZGPI2": as"ZSX", withqibo.gates.GPI2gates of angle zero replacing the SX gates, as they are equal up to a global phase.
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.Optimize1qGatesSimpleCommutation(basis=None, atol=1e-12, up_to_global_phase=True)[source]#
Rewrites runs of single-qubit gates after moving gates through commuting gates.
A run is a sequence of single-qubit gates without control qubits on one qubit with no other gate on that qubit in between, as in
qibo.transpiler.optimizer.Optimize1qGatesDecomposition. Consider three consecutive runs on a qubit separated by two gates, the preceding and succeeding blockers. The gates at the start of the middle run that commute with the preceding blocker, up to the first one that does not, are moved to the end of the preceding run, and the gates at its end that commute with the succeeding blocker are moved to the start of the succeeding run. The three runs are rewritten withqibo.transpiler.optimizer.Optimize1qGatesDecomposition, and replaced if they have fewer gates, or if they had gates that are not inbasis. For example, aqibo.gates.RZgate commutes with the control of aqibo.gates.CNOTgate.Two gates commute when applying them in either order gives the same matrix. Gates are moved only into runs that already have gates, only the runs next to a gate that they commute with are rewritten, and the pass is applied once, so use
qibo.transpiler.optimizer.FixedPointto repeat it. Measurements, alignments, barriers, noise channels and gates with sympy parameters are never moved through nor rewritten. The input circuit is not modified.Example
The
qibo.gates.RZgate before the controlled-NOT gate moves through its control into the run after it, where it is rewritten with the other gates as a singleqibo.gates.U2gate.from qibo import Circuit, gates from qibo.transpiler import Optimize1qGatesSimpleCommutation circuit = Circuit(2) circuit.add(gates.H(0)) circuit.add(gates.RZ(0, 0.3)) circuit.add(gates.CNOT(0, 1)) circuit.add(gates.RZ(0, 0.4)) circuit.add(gates.H(0)) optimizer = Optimize1qGatesSimpleCommutation() new_circuit = optimizer(circuit) new_circuit.draw()
0: ─H─o─U2─ 1: ───X────
- Parameters:
basis (list[str], optional) – Names of the gates the circuit may contain, as in
qibo.transpiler.optimizer.Optimize1qGatesDecomposition. Defaults toNone.atol (float, optional) – Tolerance to decide that two gates commute, and the tolerance of
qibo.transpiler.optimizer.Optimize1qGatesDecomposition. Defaults to \(10^{-12}\).up_to_global_phase (bool, optional) – If
False, a run is rewritten only when the new gates have exactly the same matrix, as inqibo.transpiler.optimizer.Optimize1qGatesDecomposition. Defaults toTrue.
- class qibo.transpiler.OptimizeSwapBeforeMeasure[source]#
Removes the SWAP gates that are only followed by measurements.
A
qibo.gates.SWAPgate is removed if the gates after it on its two qubits are measurements, and if there is at least one, and the two qubits of each of these measurements are exchanged instead. Thus, the outcomes of the measurements are the same, and the order of their bits is kept, but the state of the circuit before them has the two qubits exchanged. Swaps that become followed only by measurements, because of the removal of other swaps, are removed as well.The input circuit is not modified.
Example
The swap is removed, and the measurement is on the qubits in the opposite order.
from qibo import Circuit, gates from qibo.transpiler import OptimizeSwapBeforeMeasure circuit = Circuit(3) circuit.add(gates.X(0)) circuit.add(gates.SWAP(0, 1)) circuit.add(gates.SWAP(1, 2)) circuit.add(gates.M(0, 1, 2)) optimized = OptimizeSwapBeforeMeasure()(circuit) for gate in optimized.queue: print(gate.name, gate.qubits)
x (0,) measure (1, 2, 0)
- class qibo.transpiler.ParametrizedGateFusion(atol=1e-12, up_to_global_phase=False)[source]#
Merges consecutive rotation gates of the same kind into a single gate.
Two gates are merged when no other gate acts on their qubits in between and both are of the same gate class acting on the same qubits with the same control qubits. The merged gate has the sum of the rotation angles, for example a
qibo.gates.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, callback=None)[source]#
Define a transpiler pipeline consisting of smaller transpiler steps that are applied sequentially.
- Parameters:
passes (list, optional) – List of transpiler passes to be applied sequentially. If
None, no pass is applied. Seeqibo.transpiler.pipeline.generate_preset_passes()for a ready-to-use list. Defaults toNone.connectivity (
networkx.Graph, optional) – Hardware connectivity. It can be a directed graph, in which case an edge(a, b)means that a two-qubit gate is supported with its first qubit onaand its second onb, seeqibo.transpiler.optimizer.GateDirection. It is required ifon_qubitsis given. Defaults toNone.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.callback (Callable, optional) – Function called after each pass that runs, with the keyword arguments
transpiler_pass, the pass that has run,circuit, the circuit after the pass,time, the seconds that the pass has taken, andcount, the position of the pass inpasses. A router is skipped, and the callback is not called for it, if the circuit already respects the connectivity, since no qubit has to be moved. Defaults toNone.
Example
The callback prints the passes of the pipeline as they run. The circuit already respects the connectivity, so the router is skipped and it is not printed.
import networkx as nx from qibo import Circuit, gates from qibo.transpiler import Passes, Preprocessing, Sabre circuit = Circuit(2) circuit.add(gates.CNOT(0, 1)) def callback(transpiler_pass, circuit, time, count): print(count, type(transpiler_pass).__name__, circuit.nqubits) pipeline = Passes( [Preprocessing(), Sabre()], connectivity=nx.path_graph(3), callback=callback ) transpiled_circuit, final_layout = pipeline(circuit)
0 Preprocessing 3
- is_satisfied(circuit)[source]#
Check if the circuit respects the hardware connectivity and native gates.
If there is no connectivity, only the native gates are checked, because there is nothing to place and route.
- Parameters:
circuit (
qibo.models.circuit.Circuit) – Circuit to be checked.- Returns:
Trueif the circuit respects the hardware connectivity and native gates,Falseotherwise.- Return type:
(bool)
- class qibo.transpiler.Preprocessing(connectivity=None, initial_layout=None)[source]#
Pad the circuit with unused qubits to match the number of physical qubits.
- Parameters:
connectivity (
networkx.Graph) – Hardware connectivity as a graph.initial_layout (list or dict, optional) – Physical qubits on which the logical qubits of the circuit are placed. It is either a list, where the \(i\)-th logical qubit is placed on the physical qubit
initial_layout[i], or a dictionary{i: node}with the same meaning, which may leave logical qubits out. These are placed on the nodes of the connectivity that are not in the dictionary, in the order of the connectivity. In both cases every logical qubit must be placed on a distinct node of the connectivity, and the layout replaces thewire_namesof the circuit, which are set in place. IfNone, thewire_namesof the circuit are used if they are all in the connectivity, and the first nodes of the connectivity otherwise. Defaults toNone.
Example
The first and the third qubits of the circuit are placed on the nodes
3and1, and the second one on the first free node, which is0.import networkx as nx from qibo import Circuit, gates from qibo.transpiler import Preprocessing circuit = Circuit(3) circuit.add(gates.CNOT(0, 1)) preprocessing = Preprocessing(nx.path_graph(5), initial_layout={0: 3, 2: 1}) print(preprocessing(circuit).wire_names)
[3, 0, 1, 2, 4]
- class qibo.transpiler.Random(connectivity=None, samples=100, seed=None)[source]#
Random initialization with greedy policy, so that a maximum number of 2-qubit gates can be applied without introducing any SWAP gate.
- Parameters:
connectivity (
networkx.Graph, optional) – Hardware connectivity. Defaults toNone.samples (int, optional) – Number of random initializations to try. If \(0\), no random initialization is tried and the qubits are placed following the order of the nodes of
connectivity, i.e. the \(i\)-th qubit is placed on the \(i\)-th node. Defaults to \(100\).seed (int or
numpy.random.Generator, optional) – Either a generator of random numbers or a fixed seed to initialize a generator. IfNone, initializes a generator with a random seed. Defaults toNone.
- 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, fused gates and global phase gates are never removed, the latter to keep the global phase of the circuit. Gates are evaluated at their current parameter values, so a removed parametrized gate is not tracked by the returned circuit anymore.
A gate with sympy parameters is evaluated at fixed random values of its symbols. It is removed only if it is the identity there up to numerical precision, whatever the value of
approximation_degree, because the true values of the symbols are unknown.- Parameters:
approximation_degree (float, optional) – Value in \([0, 1]\) that sets the tolerance as \(\epsilon = 1 -\)
approximation_degree. With the default value only gates equal to the identity up to numerical precision (\(\epsilon = 10^{-14}\)) are removed. Defaults to \(1.0\).
Example
The rotation by \(10^{-3}\) radians differs from the identity by an infidelity of about \(1.7 \times 10^{-7}\), so it is removed only when the tolerance allows it. The identity gate and the rotation by \(2\pi\) (minus the identity) are removed in both cases.
from math import pi from qibo import Circuit, gates from qibo.transpiler import RemoveIdentityEquivalent circuit = Circuit(2) circuit.add(gates.I(0)) circuit.add(gates.RZ(0, 2 * pi)) circuit.add(gates.RX(1, 1e-3)) circuit.add(gates.CNOT(0, 1)) print(RemoveIdentityEquivalent()(circuit).ngates) print(RemoveIdentityEquivalent(approximation_degree=1 - 1e-6)(circuit).ngates)
2 1
- class qibo.transpiler.RemoveResetInZeroState[source]#
Removes the resets acting on qubits that are still in the zero state.
A reset is the single-qubit
qibo.gates.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, trials=1, seed=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\).trials (int, optional) – Number of independent layout attempts. The first one starts from the layout of the circuit, and the others from random permutations of it. Each attempt finds a layout with the reverse traversal, and the one for which the router adds the fewest SWAP gates to the circuit is kept, the first one among ties. With one attempt, no routing of the circuit is needed to choose the layout. Defaults to \(1\).
seed (int, optional) – Seed of the random permutations of the attempts, which makes them reproducible. Defaults to
None.
References
1. G. Li, Y. Ding, and Y. Xie, Tackling the Qubit Mapping Problem for NISQ-Era Quantum Devices. arXiv:1809.02573 [cs.ET].
- class qibo.transpiler.Sabre(connectivity=None, lookahead=2, decay_lookahead=0.6, delta=0.001, swap_threshold=1.5, seed=None, trials=1, heuristic='decay')[source]#
Routing algorithm proposed in Ref [1].
- Parameters:
connectivity (
networkx.Graph) – Hardware chip connectivity.lookahead (int, optional) – Lookahead factor, how many dag layers will be considered in computing the cost. Defaults to \(2\).
decay_lookahead (float, optional) – Value in interval \([0, 1]\). How the weight of the distance in the dag layers decays in computing the cost. Defaults to \(0.6\).
delta (float, optional) – Defines the number of SWAPs vs depth trade-off by deciding how the algorithm tends to select non-overlapping SWAPs. Defaults to \(10^{-3}\).
swap_threshold (float, optional) – Limits the number of added SWAPs in every routing iteration. This threshold is multiplied by the length of the longest path in the circuit connectivity. If the number of added SWAPs exceeds the threshold before a gate is routed, shortestpath routing is applied. Defaults to \(1.5\).
seed (int, optional) – Seed for the candidate random choice as tiebreaker. Defaults to
None.trials (int, optional) – Number of independent routing attempts, which differ in the random choices among equally good candidates. The attempt with the lowest number of added SWAPs is returned, and the first one among ties. The attempts are reproducible if
seedis given, and the first one does not depend ontrials. Defaults to \(1\).heuristic (str, optional) –
Cost that selects the SWAP gate to be added. It is the distance between the qubits of the gates in the front layer of the circuit, plus the terms below:
"basic": no other term, solookahead,decay_lookaheadanddeltaare not used."lookahead": the distances of the gates in the nextlookaheadlayers, weighted bydecay_lookahead.deltais not used."decay": the terms of"lookahead", with the distances of the qubits that were recently swapped increased bydelta, which favors non-overlapping SWAPs.
Defaults to
"decay".
- Raises:
ValueError – If
trialsis not positive, orheuristicis not"basic","lookahead"or"decay".
References
1. G. Li, Y. Ding, and Y. Xie, Tackling the Qubit Mapping Problem for NISQ-Era Quantum Devices. arXiv:1809.02573 [cs.ET].
- 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.
- class qibo.transpiler.StarConnectivityPlacer(connectivity=None)[source]#
Find an optimized qubit placement for the following connectivity:
q | q -- q -- q | q
- Parameters:
connectivity (
networkx.Graph, optional) – Star connectivity graph. Defaults toNone.
- class qibo.transpiler.StarConnectivityRouter(connectivity=None)[source]#
Transforms an arbitrary circuit to one that can be executed on hardware.
This transpiler produces a circuit that respects the following connectivity by adding SWAP gates when needed:
q | q -- q -- q | q
- Parameters:
connectivity (
networkx.Graph) – Star connectivity graph.
- class qibo.transpiler.Subgraph(connectivity=None)[source]#
Subgraph isomorphism qubit placer.
Since it is a \(NP\)-complete problem, it can take exponential time for large circuits. This initialization method may fail for very short circuits.
- Parameters:
connectivity (
networkx.Graph, optional) – Hardware connectivity. Defaults toNone.
- 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.TranspileLayout(final_layout, initial_layout, nqubits_input)[source]#
Layout of the qubits of a circuit before and after the routing.
It is the final layout returned by
qibo.transpiler.pipeline.Passes. As a dictionary, it maps the label of the physical qubit on which each qubit of the circuit is placed before the routing, to the index of the physical qubit on which it ends after the routing. It can therefore be used wherever a final {logical: physical} qubit mapping is expected. It also stores the layout before the routing. If the pipeline has no router, the qubits are not moved, so the mapping and the routing permutation are the identity, and the initial layout is the one of the transpiled circuit.Example
A circuit on four qubits is transpiled to a line of five qubits. It is drawn before and after the transpilation with
parallel=True, so that the gates on disjoint qubits share a column, and withline_wrap=12, which splits the wider transpiled circuit in two blocks. The routing adds the SWAP gates, drawn asx, and the preprocessing adds the fifth qubit of the line as an auxiliary qubit, which is not used. The \(i\)-th element of the routing permutation is the physical qubit on which the \(i\)-th qubit ends: the first qubit does not move, while the second, third and fourth qubits end on the third, fourth and second physical qubits.import networkx as nx from qibo import Circuit, gates from qibo.transpiler import Passes, Preprocessing, ShortestPaths circuit = Circuit(4) circuit.add(gates.H(0)) circuit.add(gates.H(2)) circuit.add(gates.CNOT(0, 1)) circuit.add(gates.CNOT(2, 3)) circuit.add(gates.CNOT(0, 3)) circuit.add(gates.CNOT(1, 2)) circuit.add(gates.M(0, 1, 2, 3)) circuit.draw(parallel=True) print() pipeline = Passes( [Preprocessing(), ShortestPaths(seed=1)], connectivity=nx.path_graph(5) ) transpiled_circuit, layout = pipeline(circuit) transpiled_circuit.draw(parallel=True) print() print(dict(layout)) print(layout.initial_layout) print(layout.routing_permutation) print(layout.nqubits_input) print(layout.auxiliary_qubits) print(layout.initial_logical_layout(filter_auxiliary=True)) print(layout.final_logical_layout(filter_auxiliary=True))
0: ─H─o─o───M─ 1: ───X─|─o─M─ 2: ─H─o─|─X─M─ 3: ───X─X───M─ 0: ─H─o───────o─M─ 1: ───X─o───x─X─M─ 2: ─H─o─X─x─x───M─ 3: ───X───x─────M─ 4: ─────────────── {0: 0, 1: 2, 2: 3, 3: 1, 4: 4} [0, 1, 2, 3, 4] [0, 2, 3, 1, 4] 4 [4] {0: 0, 1: 1, 2: 2, 3: 3} {0: 0, 1: 2, 2: 3, 3: 1}- Parameters:
final_layout (dict) – Mapping from the label of the physical qubit on which each qubit is placed before the routing, to the index of the physical qubit on which it ends after the routing.
initial_layout (list) – Labels of the physical qubits on which the qubits of the circuit are placed before the routing. The \(i\)-th element is the physical qubit of the \(i\)-th qubit of the circuit.
nqubits_input (int) – Number of qubits of the circuit before it is padded with the unused qubits of the hardware, which are the auxiliary qubits.
- property auxiliary_qubits: list#
Labels of the physical qubits that the preprocessing adds to the circuit as auxiliary qubits.
- property final_layout: dict#
Mapping from the physical qubit on which each qubit is placed before the routing, to the one on which it ends after the routing, as a plain dictionary.
- property routing_permutation: list[int]#
Permutation of the qubits that the routing applies. Its \(i\)-th element is the index of the physical qubit on which the \(i\)-th qubit of the circuit ends after the routing. The elements from
nqubits_inputonward are the auxiliary qubits.
- final_logical_layout(filter_auxiliary=False)[source]#
Index of the physical qubit on which each qubit of the circuit ends.
- Parameters:
filter_auxiliary (bool, optional) – If
True, the auxiliary qubits are left out. Defaults toFalse.- Returns:
Mapping from the index of each qubit of the circuit to the index of the physical qubit on which it ends after the routing, see
qibo.transpiler.layout.TranspileLayout.routing_permutation.- Return type:
- class qibo.transpiler.Unroller(native_gates, use_dirty_ancillas=False, min_qubits=1, qubits_initially_zero=False, minimize_toffolis=False, minimize_depth=False, approximation_degree=1.0, threshold=None, weight=None, backend=None)[source]#
Decomposes a circuit to native gates.
- Parameters:
native_gates (
qibo.transpiler.unroller.NativeGates) – Native gates to use in the transpiled circuit. They must be universal, seeqibo.transpiler.unroller.NativeGates.is_universal.use_dirty_ancillas (bool, optional) – If
True, the qubits of the circuit that a multi-controlled gate does not act on are used as dirty auxiliary qubits in its decomposition, which makes the cost of a multi-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.min_qubits (int, optional) – Only gates acting on at least
min_qubitsqubits are decomposed, the others are left unchanged. For example,min_qubits=3decomposes only gates acting on three or more qubits, which is useful to make a circuit executable by a router before routing. Defaults to \(1\), which decomposes every gate.qubits_initially_zero (bool, optional) – If
True, the circuit is assumed to start in the state \(\ket{0}^{\otimes n}\). The qubits on which no previous gate of the circuit acts are then known to be in the state \(\ket{0}\), and are used as clean auxiliary qubits in the decomposition of multi-controlled gates, being left in that state. They are used as clean qubits if that lowers the number of native gates, and as dirty ones otherwise. It must beFalseif the circuit is executed with another initial state. Defaults toFalse.minimize_toffolis (bool, optional) – If
True, a multi-controlledqibo.gates.Xwith at least one auxiliary qubit is decomposed with the lowest number of Toffoli gates, which is useful if they are expensive. This is not the decomposition with the fewest native gates, which is the default one, and it needs more of them with dirty auxiliary qubits. With clean auxiliary qubits (seequbits_initially_zero), it is shallower. It cannot be used together withminimize_depth. Defaults toFalse.minimize_depth (bool, optional) – If
True, a multi-controlledqibo.gates.Xwith at least one auxiliary qubit is decomposed with a depth that is logarithmic in the number of controls. It cannot be used together withminimize_toffolis. Seeqibo.transpiler.multi_controlled_decomposition(). Defaults toFalse.approximation_degree (float, optional) – Fidelity of each
qibo.gates.CZgate, between \(0\) and \(1\). If it is lower than \(1\), the two-qubit gates that are decomposed from their matrix, i.e. the unitaries, theqibo.gates.fSimandqibo.gates.GeneralizedfSimgates and the gates without a known decomposition, can use fewerqibo.gates.CZgates than the exact decomposition, if it increases the expected fidelity, seeqibo.transpiler.decompositions.unitary_decompositions.two_qubit_decomposition(). The other gates are decomposed exactly. Defaults to \(1\), which is the exact decomposition.threshold (float, optional) – Tolerance of the decomposition of the two-qubit gates that are decomposed from their matrix, like for
approximation_degree, seeqibo.transpiler.decompositions.unitary_decompositions.two_qubit_decomposition(). Defaults toNone, which uses its default value.weight (float, optional) – Weight of the imaginary part in the matrix that is diagonalized to decompose those gates, see
qibo.transpiler.decompositions.unitary_decompositions.two_qubit_decomposition(). Defaults toNone, which uses its default value.backend (
qibo.backends.abstract.Backend, optional) – Backend to use for gate matrix. Defaults toNone.
- Raises:
ValueError – If
approximation_degreeis not between \(0\) and \(1\).
- class qibo.transpiler.Validation(connectivity=None, initial_layout=None)[source]#
Checks that the circuit can be transpiled, before any other pass runs.
Without it, a circuit that cannot be transpiled fails deep in the pipeline. This pass raises a clear error at the start instead. It does not change the circuit. It checks that:
the circuit has no noise channel. The transpiler works with unitary gates, so the only channel that it accepts is
qibo.gates.channels.ResetChannel.the circuit has no more qubits than the hardware, if there is a connectivity.
the initial layout, if there is one, places every qubit of the circuit on a distinct node of the connectivity, see
qibo.transpiler.optimizer.Preprocessing.
- Parameters:
connectivity (
networkx.Graph, optional) – Hardware connectivity. IfNone, neither the number of qubits nor the initial layout are checked. It is set byqibo.transpiler.pipeline.Passes. Defaults toNone.initial_layout (list, optional) – Physical qubits on which the logical qubits of the circuit are placed, in the format of
qibo.transpiler.optimizer.Preprocessing. Defaults toNone.
Example
The pass raises an error for the noise channel before the circuit is transpiled.
from qibo import Circuit, gates from qibo.transpiler import Validation circuit = Circuit(1) circuit.add(gates.H(0)) circuit.add(gates.PauliNoiseChannel([0], [("X", 0.1)])) try: Validation()(circuit) except Exception as error: print(type(error).__name__)
DecompositionError
- qibo.transpiler.generate_preset_passes(connectivity=None, native_gates=None, initial_layout=None, seed=None, on_qubits=None, qubits_initially_zero=False, layout_method=None, routing_method=None, layout_trials=None, callback=None, approximation_degree=1.0, threshold=None, weight=None, use_dirty_ancillas=True, minimize_toffolis=None, minimize_depth=False, routing_trials=5, optimization_level=0, sampling_only=False, backend=None)[source]#
Generate a ready-to-use transpiler pipeline.
With the default
optimization_level=0, the pipeline only makes the circuit executable on the hardware, with the following passes:qibo.transpiler.optimizer.Validationraises an error at the start if the circuit cannot be transpiled, e.g. if it has a noise channel, more qubits than the hardware, or an invalidinitial_layout.qibo.transpiler.unroller.Unrollerdecomposes the gates acting on three or more qubits, which the router does not support. The qubits of the circuit that a multi-controlled gate does not act on are used as auxiliary qubits, seequbits_initially_zero.qibo.transpiler.optimizer.Preprocessingplaces the qubits oninitial_layout, or on the first nodes of the connectivity if there is none, and pads the circuit with the unused qubits of the hardware.The placer selected by
layout_method, if it is not the trivial one.The router selected by
routing_method, which adds the SWAP gates that make all the gates executable. By default, it isqibo.transpiler.router.Sabrewith the best ofrouting_trialstrials.qibo.transpiler.unroller.Unrollerdecomposes the circuit to the native gates, approximating some two-qubit gates with fewer entangling gates ifapproximation_degreeis lower than \(1\).
If
connectivityis directed, the pipeline ends with aqibo.transpiler.optimizer.GateDirectionpass and an unroller, so that the two-qubit gates act in the direction of the edges.The optimization levels \(1\) and \(2\) add optimizers to the pipeline. The ones that can write gates that are not native run before the last unroller. Level \(1\) adds:
qibo.transpiler.optimizer.InverseCancellationandqibo.transpiler.optimizer.CommutativeCancellationbefore the layout.qibo.transpiler.optimizer.ConsolidateBlocksbefore the last unroller, which rewrites the blocks of two-qubit gates that have fewerqibo.gates.CZgates.A loop of
qibo.transpiler.optimizer.Optimize1qGatesDecomposition, with the native gates asbasis, andqibo.transpiler.optimizer.InverseCancellationat the end. The first one is left out if the native gates have no Euler basis.The
"reverse_traversal"placer with five trials, unlesslayout_methodorinitial_layoutis given.
Level \(2\) adds, to the optimizers of level \(1\):
qibo.transpiler.optimizer.RemoveIdentityEquivalentbefore the layout, which also removes the gates close to the identity ifapproximation_degreeis lower than \(1\).qibo.transpiler.optimizer.CommutativeCancellationbefore the last unroller.A second loop at the end, of
qibo.transpiler.optimizer.Optimize1qGatesDecomposition,qibo.transpiler.optimizer.Optimize1qGatesSimpleCommutationandqibo.transpiler.optimizer.InverseCancellationwith commutation. It is also left out if the native gates have no Euler basis.
The optimizers can change the global phase of the circuit, and the gates that they merge do not keep their trainable parameters.
Without
connectivitythere is nothing to place and route, so the pipeline only validates the circuit and decomposes it to the native gates.- Parameters:
connectivity (
networkx.Graph, optional) – Hardware connectivity. It can be directed, see above. Defaults toNone.native_gates (
qibo.transpiler.unroller.NativeGates, optional) – Native gates supported by the hardware. Defaults toqibo.transpiler.unroller.NativeGates.default().initial_layout (list or dict, optional) – Physical qubits on which the logical qubits of the circuit are placed, as a list or as a dictionary that may leave qubits out, see
qibo.transpiler.optimizer.Preprocessing. It requiresconnectivity, and it cannot be used with a non-triviallayout_method. Defaults toNone.seed (int, optional) – Seed of the random choices of the placer and the router, which makes the transpilation reproducible. Defaults to
None.on_qubits (list, optional) – List of qubits to be used in the transpiler, see
qibo.transpiler.pipeline.Passes. It requiresconnectivity. Defaults toNone.qubits_initially_zero (bool, optional) – If
True, the circuit is assumed to start in the state \(\ket{0}^{\otimes n}\). The qubits on which no previous gate acts are then used as clean auxiliary qubits to decompose multi-controlled gates, with the construction that minimizes the number of Toffoli gates, seeqibo.transpiler.unroller.Unroller. This makes the decomposition shallower and, for many controls, also shorter. If there are no clean qubits for a gate, its dirty auxiliary qubits are used with the same construction, which has more CNOT gates than the default one. Withoutqubits_initially_zerothe other qubits are used as dirty auxiliary qubits, which can be in any state. It must beFalseif the circuit is executed with another initial state. Defaults toFalse.layout_method (str or
qibo.transpiler.abstract.Placer, optional) – Placer that finds the initial layout. It can be"trivial", which keeps thewire_namesof the circuit if they are nodes of the connectivity and uses the first nodes otherwise,"dense"(qibo.transpiler.placer.Dense),"random"(qibo.transpiler.placer.Random),"subgraph"(qibo.transpiler.placer.Subgraph),"reverse_traversal"(qibo.transpiler.placer.ReverseTraversalwithqibo.transpiler.router.Sabre),"star"(qibo.transpiler.placer.StarConnectivityPlacer) or a placer. It requiresconnectivity. Defaults toNone, which is"trivial", or"reverse_traversal"ifoptimization_levelis at least \(1\),initial_layoutisNoneandrouting_methodisNoneor"sabre".routing_method (str or
qibo.transpiler.abstract.Router, optional) – Router that makes the circuit executable. It can be"sabre"(qibo.transpiler.router.Sabrewithrouting_trialstrials),"shortest_paths"(qibo.transpiler.router.ShortestPaths),"star"(qibo.transpiler.router.StarConnectivityRouter),"none"(qibo.transpiler.router.NoRouting, which raises an error if the circuit needs routing) or a router. A router is skipped if the circuit already respects the connectivity. It requiresconnectivity. Defaults toNone, which is"sabre".layout_trials (int, optional) – Number of layout attempts of the
"reverse_traversal"placer, seeqibo.transpiler.placer.ReverseTraversal. It is not used by the other placers. Defaults toNone, which is \(1\) ifoptimization_levelis \(0\) and \(5\) otherwise.callback (Callable, optional) – Function called after each pass that runs, see
qibo.transpiler.pipeline.Passes. Defaults toNone.approximation_degree (float, optional) – Fidelity of each entangling gate, between \(0\) and \(1\). If it is lower than \(1\), the unroller that decomposes the circuit to the native gates approximates the two-qubit gates that it decomposes from their matrix with fewer
qibo.gates.CZgates, if it increases the expected fidelity, seeqibo.transpiler.unroller.Unroller. The multi-qubit gates are decomposed exactly. Defaults to \(1\), which is the exact decomposition.threshold (float, optional) – Tolerance of the decomposition of the two-qubit gates that are decomposed from their matrix, like for
approximation_degree, seeqibo.transpiler.unroller.Unroller. Defaults toNone, which uses the default value of the decomposition.weight (float, optional) – Weight of the imaginary part in the matrix that is diagonalized to decompose those gates, like for
approximation_degree, seeqibo.transpiler.unroller.Unroller. Defaults toNone, which uses the default value of the decomposition.use_dirty_ancillas (bool, optional) – If
True, the qubits that a multi-controlled gate does not act on are used as dirty auxiliary qubits in its decomposition, seeqibo.transpiler.unroller.Unroller. Defaults toTrue.minimize_toffolis (bool, optional) – If
True, the multi-controlledqibo.gates.Xgates with auxiliary qubits are decomposed with the lowest number of Toffoli gates, seeqibo.transpiler.unroller.Unroller. It cannot be used together withminimize_depth. Defaults toNone, which isqubits_initially_zerounlessminimize_depthisTrue.minimize_depth (bool, optional) – If
True, the multi-controlledqibo.gates.Xgates with auxiliary qubits are decomposed with a depth that is logarithmic in the number of controls, seeqibo.transpiler.unroller.Unroller. It cannot be used together withminimize_toffolis. Defaults toFalse.routing_trials (int, optional) – Number of independent routing attempts of the
"sabre"router, which keeps the best one, seeqibo.transpiler.router.Sabre. It is not used by the other routers, nor by the router of the"reverse_traversal"placer. Defaults to \(5\).optimization_level (int, optional) – Optimization level of the pipeline. It can be \(0\), which is the pipeline described above, that only makes the circuit executable on the hardware and does not optimize it, or \(1\) or \(2\), which add the optimizers described above. Defaults to \(0\).
sampling_only (bool, optional) – If
True, the optimizers of levels \(1\) and \(2\) also use the rewrites that keep the distribution of the measured qubits, but can change the state of the circuit before the measurements:qibo.transpiler.optimizer.OptimizeSwapBeforeMeasure,qibo.transpiler.optimizer.RemoveDiagonalGatesBeforeMeasurementandqibo.transpiler.optimizer.RemoveFinalReset, before the layout. It has no effect ifoptimization_levelis \(0\). Defaults toFalse.backend (
qibo.backends.abstract.Backend, optional) – Backend used by the unrollers to compute the gate matrices, unless another one is given when the pipeline is called. IfNone, it uses the global backend. Defaults toNone.
- Returns:
Transpiler pipeline.
- Return type:
- Raises:
ConnectivityError – If
initial_layout,layout_methodorrouting_methodis given withoutconnectivity.PlacementError – If
initial_layoutis given with a non-triviallayout_method.ValueError – If
layout_methodorrouting_methodis not a valid method, ifapproximation_degreeis not between \(0\) and \(1\), ifminimize_toffolisandminimize_depthare bothTrue, ifrouting_trialsis not positive and the router is"sabre", or ifoptimization_levelis not a valid level.
- qibo.transpiler.multi_controlled_decomposition(unitary, controls, target, free=(), *, clean=(), minimize_toffolis=False, minimize_depth=False, backend=None)[source]#
Decomposes a multi-controlled single-qubit gate, with or without auxiliary qubits.
The decomposition is exact, including the global phase. By default, the one with the fewest CNOTs is used. Gates with \(\det(U) = 1\) are decomposed with a number of CNOTs that grows linearly with the number of controls following Ref. [1], and other gates quadratically following Ref. [2]. This is the decomposition used by
qibo.gates.abstract.Gate.decompose()for gates controlled by more than one qubit.A multi-controlled \(X\) gate has dedicated decompositions that profit from auxiliary qubits, and
freeandcleanare ignored for other gates. Without auxiliary qubits, the gates with three and four controls use \(14\) and \(36\) CNOTs, respectively. For more controls, the one with the fewest CNOTs for the given auxiliary qubits is chosen, where \(k\) is the number of controls:With at least \(k - 2\) auxiliary qubits, clean or dirty, the cost is linear with a small slope following Ref. [3].
With fewer qubits, but at least one clean qubit, Ref. [4] uses \(2k - 3\) Toffoli gates. Two clean qubits make the depth logarithmic in \(k\), and one makes it linear.
With dirty qubits only, the cost is linear following Ref. [3] with one auxiliary qubit.
Clean qubits are only used as such if they are needed to reduce the number of CNOTs. Otherwise, they are used as dirty qubits.
If
minimize_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 not necessarily the lowest number of CNOTs. Two auxiliary qubits make the depth logarithmic in \(k\), and one makes it linear. Clean qubits are preferred over dirty ones.If
minimize_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 when this is cheaper. Defaults to
().minimize_toffolis (bool, optional) – If
True, a multi-controlled \(X\) gate with auxiliary qubits minimizes the number of Toffoli gates instead of the number of CNOTs. Defaults 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.
- qibo.transpiler.quantum_shannon_decomposition(qubits, unitary, backend=None)[source]#
Quantum Shannon decomposition of a unitary matrix acting on several qubits.
The cosine-sine decomposition of an \(n\)-qubit unitary \(U\), whose blocks are defined by the first qubit, is
\[\begin{split}U = (A_{1} \oplus A_{2}) \, \begin{pmatrix} C & -S \\ S & C \end{pmatrix} \, (B_{1} \oplus B_{2}) \, ,\end{split}\]where the middle matrix is a multiplexed
qibo.gates.gates.RYrotation of the first qubit, controlled by the other \(n - 1\) qubits. Each block-diagonal matrix is decomposed as\[X_{1} \oplus X_{2} = (\mathbb{I} \otimes V) \, (D \oplus D^{\dagger}) \, (\mathbb{I} \otimes W) \, ,\]with \(V D^{2} V^{\dagger} = X_{1} X_{2}^{\dagger}\) and \(W = D V^{\dagger} X_{2}\), where \(D \oplus D^{\dagger}\) is a multiplexed
qibo.gates.gates.RZrotation of the first qubit. The result has four unitaries on the last \(n - 1\) qubits and three multiplexed rotations, i.e.qibo.gates.gates.Multiplexorgates. Decomposing the unitaries in the same way until they act on two qubits uses \((9 / 16) \, 4^{n} - (3 / 2) \, 2^{n}\) two-qubit gates, if the unitaries on two qubits are decomposed with three of them. It does not use auxiliary qubits.- Parameters:
qubits (tuple[int, ...]) – Ids of the qubits that
unitaryacts on, with at least two. The first one is the most significant bit of the indices ofunitary.unitary (ArrayLike) – Unitary matrix of shape \((2^{n}, 2^{n})\), with \(n\) the number of
qubits.backend (
qibo.backends.abstract.Backend, optional) – Backend to use for the matrices. IfNone, defaults to the global backend. Defaults toNone.
- Returns:
Gates, in order of application, that implement
unitary: fourqibo.gates.gates.Unitarygates onqubits[1:]and threeqibo.gates.gates.Multiplexorgates. Each of them acts on less qubits thanunitary, so they can be decomposed in turn.- Return type:
list[
qibo.gates.abstract.Gate]- Raises:
ValueError – If there are less than two
qubits, orunitarydoes not have the shape \((2^{n}, 2^{n})\).
References
1. V. V. Shende, S. S. Bullock, and I. L. Markov, Synthesis of quantum-logic circuits, IEEE Trans. Comput.-Aided Des. Integr. Circuits Syst. 25, 1000 (2006).
- qibo.transpiler.transpile(circuits, connectivity=None, native_gates=None, initial_layout=None, seed=None, on_qubits=None, qubits_initially_zero=False, layout_method=None, routing_method=None, layout_trials=None, callback=None, approximation_degree=1.0, threshold=None, weight=None, use_dirty_ancillas=True, minimize_toffolis=None, minimize_depth=False, routing_trials=5, optimization_level=0, sampling_only=False, num_processes=None, backend=None)[source]#
Transpile one or more circuits in a single call.
It builds the pipeline of
qibo.transpiler.pipeline.generate_preset_passes()once and applies it to the circuits, which makes them executable on the hardware, and optimizes them ifoptimization_levelis higher than \(0\). All the arguments exceptcircuitsandnum_processesare the ones ofqibo.transpiler.pipeline.generate_preset_passes(), and they are shared by all the circuits.The circuits of a list can be transpiled in parallel with
num_processes. Each of them is then transpiled by a worker process with its own copy of the pipeline, so the result for a circuit does not depend on the other circuits nor onnum_processes, and, with aseed, it is the one of transpiling the circuit alone. This can differ from the sequential result, where the random state of the placer and the router continues from one circuit to the next. The worker processes are started with thespawnmethod, so the script that callsqibo.transpiler.pipeline.transpile()must protect the call withif __name__ == "__main__":. They do not share the global backend either, sobackendshould be given if it is not the default one.Example
A rotation, a Toffoli gate and a CNOT gate between qubits that are not neighbors are transpiled to a line of four qubits, where
qibo.gates.CNOTandqibo.gates.U3are the native gates. The Toffoli gate is decomposed, the SWAP gates that the routing adds are decomposed too, and the transpiled circuit is drawn. The decomposition of the Toffoli gate leaves a global phase, which the drawing reports. Ifcircuitsis a list, the result is a list with a transpiled circuit and a final layout for each circuit.import networkx as nx from qibo import Circuit, gates from qibo.backends import NumpyBackend from qibo.transpiler import NativeGates, transpile from qibo.transpiler.asserts import assert_transpiling backend = NumpyBackend() circuit = Circuit(4) circuit.add(gates.RY(0, 0.5)) circuit.add(gates.TOFFOLI(0, 1, 2)) circuit.add(gates.CNOT(0, 3)) circuit.add(gates.M(*range(4))) connectivity = nx.path_graph(4) natives = NativeGates.from_gatelist([gates.U3, gates.CNOT, gates.M]) transpiled_circuit, final_layout = transpile( circuit, connectivity, natives, seed=42, backend=backend ) # Raises an error if the circuits are not equivalent, or if the transpiled circuit # does not respect the connectivity and the native gates assert_transpiling( original_circuit=circuit, transpiled_circuit=transpiled_circuit, connectivity=connectivity, final_layout=final_layout, native_gates=natives, ) print(final_layout) transpiled_circuit.draw(line_wrap=1000, parallel=True)
{0: 1, 1: 3, 2: 0, 3: 2} Global phase = exp(i * 9π/8) 0: ─U3────────────o─────────o───────o─X─o────────────────M─ 1: ────o────o─X─o─X─RZ─X─RZ─X─RZ─U3─X─o─X─o─RZ─o───────o─M─ 2: ─U3─X─RZ─X─o─X──────o─RZ───────────────X─RZ─X─o─X─o─X─M─ 3: ──────────────────────────────────────────────X─o─X───M─- Parameters:
circuits (
qibo.models.circuit.Circuitor list or tuple) – Circuit, or list of circuits, to be transpiled.connectivity (
networkx.Graph, optional) – Hardware connectivity. Defaults toNone.native_gates (
qibo.transpiler.unroller.NativeGates, optional) – Native gates supported by the hardware. Defaults toqibo.transpiler.unroller.NativeGates.default().initial_layout (list or dict, optional) – Physical qubits on which the logical qubits of the circuits are placed, see
qibo.transpiler.optimizer.Preprocessing. Defaults toNone.seed (int, optional) – Seed of the random choices of the placer and the router. Defaults to
None.on_qubits (list, optional) – List of qubits to be used in the transpiler. Defaults to
None.qubits_initially_zero (bool, optional) – If
True, the circuits are assumed to start in the state \(\ket{0}^{\otimes n}\). Defaults toFalse.layout_method (str or
qibo.transpiler.abstract.Placer, optional) – Placer that finds the initial layout. Defaults toNone.routing_method (str or
qibo.transpiler.abstract.Router, optional) – Router that makes the circuits executable. Defaults toNone.layout_trials (int, optional) – Number of layout attempts of the
"reverse_traversal"placer. Defaults toNone, which is \(1\) ifoptimization_levelis \(0\) and \(5\) otherwise.callback (Callable, optional) – Function called after each pass that runs, for each circuit, see
qibo.transpiler.pipeline.Passes. It cannot be used with more than one process. Defaults toNone.approximation_degree (float, optional) – Fidelity of each entangling gate, between \(0\) and \(1\). If it is lower than \(1\), the two-qubit gates that are decomposed from their matrix are approximated with fewer entangling gates, if it increases the expected fidelity, see
qibo.transpiler.pipeline.generate_preset_passes(). Defaults to \(1\), which is the exact decomposition.threshold (float, optional) – Tolerance of the decomposition of the two-qubit gates that are decomposed from their matrix, like for
approximation_degree. Defaults toNone, which uses the default value of the decomposition.weight (float, optional) – Weight of the imaginary part in the matrix that is diagonalized to decompose those gates, like for
approximation_degree. Defaults toNone, which uses the default value of the decomposition.use_dirty_ancillas (bool, optional) – If
True, the qubits that a multi-controlled gate does not act on are used as dirty auxiliary qubits in its decomposition. Defaults toTrue.minimize_toffolis (bool, optional) – If
True, the multi-controlledqibo.gates.Xgates with auxiliary qubits are decomposed with the lowest number of Toffoli gates. It cannot be used together withminimize_depth. Defaults toNone, which isqubits_initially_zerounlessminimize_depthisTrue.minimize_depth (bool, optional) – If
True, the multi-controlledqibo.gates.Xgates with auxiliary qubits are decomposed with a depth that is logarithmic in the number of controls. It cannot be used together withminimize_toffolis. Defaults toFalse.routing_trials (int, optional) – Number of independent routing attempts of the
"sabre"router, which keeps the best one. It is not used by the other routers. Defaults to \(5\).optimization_level (int, optional) – Optimization level of the pipeline, see
qibo.transpiler.pipeline.generate_preset_passes(). It can be \(0\), \(1\) or \(2\). Defaults to \(0\), which does not optimize the circuits.sampling_only (bool, optional) – If
True, the optimizers also use the rewrites that keep the distribution of the measured qubits but can change the state of the circuit before the measurements, seeqibo.transpiler.pipeline.generate_preset_passes(). It logs a warning for the circuits without measurements, and it has no effect ifoptimization_levelis \(0\). Defaults toFalse.num_processes (int, optional) – Number of worker processes that transpile the circuits of a list in parallel. If
Noneor \(1\), the circuits are transpiled one after the other in the current process. It has no effect on a single circuit, and no more processes than circuits are started. Defaults toNone.backend (
qibo.backends.abstract.Backend, optional) – Backend used by the unrollers to compute the gate matrices, and passed to the other passes that take one. IfNone, they use the global backend. Defaults toNone.
- Returns:
(
qibo.models.circuit.Circuit,qibo.transpiler.layout.TranspileLayout) or list: Transpiled circuit and final {logical: physical} qubit mapping, seeqibo.transpiler.pipeline.Passes. Ifcircuitsis a list or tuple, the list of these pairs, in the same order.- Raises:
ValueError – If
num_processesis not a positive integer, ifcallbackis given with more than one process, ifapproximation_degreeis not between \(0\) and \(1\), ifminimize_toffolisandminimize_depthare bothTrue, ifrouting_trialsis not positive and the router is"sabre", or ifoptimization_levelis not a valid level.BrokenProcessPool – If a worker process ends abruptly. A common cause is a script that calls this function with several processes without the
if __name__ == "__main__":guard.
- Return type:
tuple[qibo.models.circuit.Circuit, dict | None] | list[tuple[qibo.models.circuit.Circuit, dict | None]]
Abstract classes#
Optimizer#
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. Defaults toNone.ntests (int, optional) – Number of random states to test the equivalence. Defaults to \(3\).
- Return type:
None
Assert connectivity#
- qibo.transpiler.asserts.assert_connectivity(connectivity, circuit)[source]#
Assert if a circuit can be executed on Hardware.
No gates acting on more than two qubits, except for measurements and barriers. All two-qubit operations can be performed on hardware. Measurements and barriers do not require the qubits to be connected.
- Parameters:
circuit (
qibo.models.circuit.Circuit) – Circuit to check.connectivity (
networkx.Graph) – Hardware connectivity.
- Return type:
None
Assert decomposition#
- qibo.transpiler.asserts.assert_decomposition(circuit, native_gates)[source]#
Checks if a circuit has been correctly decomposed into native gates.
The identity gate, the global phase gates, the measurements, the resets and the directives are not checked, and
qibo.gates.gates.Zandqibo.gates.gates.RZare also accepted ifqibo.gates.gates.U3orqibo.gates.gates.GPI2are native, because they are virtual gates that are assumed to be available together with them, as inqibo.transpiler.unroller.Unroller.- Parameters:
circuit (
qibo.models.circuit.Circuit) – Circuit to check.native_gates (
qibo.transpiler.unroller.NativeGates) – Native gates supported by the hardware.
- Return type:
None
Assert placement#
- qibo.transpiler.asserts.assert_placement(circuit, connectivity)[source]#
Check if the layout of the circuit is consistent with the circuit and connectivity graph.
- Parameters:
circuit (
qibo.models.circuit.Circuit) – Circuit to check.connectivity (
networkx.Graph) – Hardware connectivity. AValueErroris raised if it isNone.
- Return type:
None
Assert transpilation#
- qibo.transpiler.asserts.assert_transpiling(original_circuit, transpiled_circuit, connectivity, final_layout, native_gates=None, check_circuit_equivalence=True)[source]#
Check that all transpiler passes have been executed correctly.
- Parameters:
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.
- Return type:
None
Blocks#
Block#
- class qibo.transpiler.blocks.Block(qubits, gates, name=None)[source]#
A block contains a subset of gates acting on two qubits.
The only blocks that act on more than two qubits are the ones with a
qibo.gates.special.Barrieron more than two qubits, which have no other gate acting on more than one qubit.- Parameters:
- property entangled: bool#
Returns
Trueif the block contains two-qubit gates, which are not barriers.
- add_gate(gate)[source]#
Add a new gate to the block.
- Parameters:
gate (
qibo.gates.abstract.Gate) – gate to be added.- Return type:
None
- 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 or int, optional) – name of the fused block. Defaults to
None.
- Returns:
fusion of the two input blocks.
- Return type:
- commute(block)[source]#
Check if a block commutes with the current one.
- Parameters:
block (
qibo.transpiler.blocks.Block) – block to check commutation.- Returns:
True if the two blocks don’t share any qubit. False otherwise.
- Return type:
- 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.decompositions.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.
- circuit(circuit_kwargs=None)[source]#
Return the quantum circuit.
- Parameters:
circuit_kwargs (dict, optional) – original circuit init_kwargs. If
None, a circuit with the same number of qubits as the decomposed circuit is created. Defaults toNone.- Returns:
Circuit made of the gates of the blocks.
- Return type:
Block decomposition#
- qibo.transpiler.blocks.block_decomposition(circuit, fuse=True)[source]#
Decompose a circuit into blocks of gates acting on two qubits. Break measurements on multiple qubits into measurements of single qubits.
- Parameters:
circuit (
qibo.models.circuit.Circuit) – circuit to be decomposed.fuse (bool, optional) – fuse adjacent blocks acting on the same qubits. Defaults to
True.
- Returns:
List of blocks that act on two qubits.
- Return type:
Decompositions#
Gate decompositions#
- class qibo.transpiler.decompositions.GateDecompositions[source]#
Abstract data structure that holds decompositions of gates.
- get_precision()[source]#
Return the precision of the Clifford +
qibo.gates.gates.Tdecompositions.- Returns:
precision \(\epsilon\) of the approximation of each gate, which is an
mpmathnumber, and number of decimal places used bympmath. Both areNoneif the precision was not set.- Return type:
- set_precision(epsilon=1e-16, mpmath_dps=256)[source]#
Set the precision of the Clifford +
qibo.gates.gates.Tdecompositions.
Multi-controlled gate decomposition#
See How to decompose multi-controlled gates? for examples of all the options.
- qibo.transpiler.multi_controlled_decomposition(unitary, controls, target, free=(), *, clean=(), minimize_toffolis=False, minimize_depth=False, backend=None)[source]#
Decomposes a multi-controlled single-qubit gate, with or without auxiliary qubits.
The decomposition is exact, including the global phase. By default, the one with the fewest CNOTs is used. Gates with \(\det(U) = 1\) are decomposed with a number of CNOTs that grows linearly with the number of controls following Ref. [1], and other gates quadratically following Ref. [2]. This is the decomposition used by
qibo.gates.abstract.Gate.decompose()for gates controlled by more than one qubit.A multi-controlled \(X\) gate has dedicated decompositions that profit from auxiliary qubits, and
freeandcleanare ignored for other gates. Without auxiliary qubits, the gates with three and four controls use \(14\) and \(36\) CNOTs, respectively. For more controls, the one with the fewest CNOTs for the given auxiliary qubits is chosen, where \(k\) is the number of controls:With at least \(k - 2\) auxiliary qubits, clean or dirty, the cost is linear with a small slope following Ref. [3].
With fewer qubits, but at least one clean qubit, Ref. [4] uses \(2k - 3\) Toffoli gates. Two clean qubits make the depth logarithmic in \(k\), and one makes it linear.
With dirty qubits only, the cost is linear following Ref. [3] with one auxiliary qubit.
Clean qubits are only used as such if they are needed to reduce the number of CNOTs. Otherwise, they are used as dirty qubits.
If
minimize_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 not necessarily the lowest number of CNOTs. Two auxiliary qubits make the depth logarithmic in \(k\), and one makes it linear. Clean qubits are preferred over dirty ones.If
minimize_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 when this is cheaper. Defaults to
().minimize_toffolis (bool, optional) – If
True, a multi-controlled \(X\) gate with auxiliary qubits minimizes the number of Toffoli gates instead of the number of CNOTs. Defaults 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.
Quantum Shannon decomposition#
- qibo.transpiler.quantum_shannon_decomposition(qubits, unitary, backend=None)[source]#
Quantum Shannon decomposition of a unitary matrix acting on several qubits.
The cosine-sine decomposition of an \(n\)-qubit unitary \(U\), whose blocks are defined by the first qubit, is
\[\begin{split}U = (A_{1} \oplus A_{2}) \, \begin{pmatrix} C & -S \\ S & C \end{pmatrix} \, (B_{1} \oplus B_{2}) \, ,\end{split}\]where the middle matrix is a multiplexed
qibo.gates.gates.RYrotation of the first qubit, controlled by the other \(n - 1\) qubits. Each block-diagonal matrix is decomposed as\[X_{1} \oplus X_{2} = (\mathbb{I} \otimes V) \, (D \oplus D^{\dagger}) \, (\mathbb{I} \otimes W) \, ,\]with \(V D^{2} V^{\dagger} = X_{1} X_{2}^{\dagger}\) and \(W = D V^{\dagger} X_{2}\), where \(D \oplus D^{\dagger}\) is a multiplexed
qibo.gates.gates.RZrotation of the first qubit. The result has four unitaries on the last \(n - 1\) qubits and three multiplexed rotations, i.e.qibo.gates.gates.Multiplexorgates. Decomposing the unitaries in the same way until they act on two qubits uses \((9 / 16) \, 4^{n} - (3 / 2) \, 2^{n}\) two-qubit gates, if the unitaries on two qubits are decomposed with three of them. It does not use auxiliary qubits.- Parameters:
qubits (tuple[int, ...]) – Ids of the qubits that
unitaryacts on, with at least two. The first one is the most significant bit of the indices ofunitary.unitary (ArrayLike) – Unitary matrix of shape \((2^{n}, 2^{n})\), with \(n\) the number of
qubits.backend (
qibo.backends.abstract.Backend, optional) – Backend to use for the matrices. IfNone, defaults to the global backend. Defaults toNone.
- Returns:
Gates, in order of application, that implement
unitary: fourqibo.gates.gates.Unitarygates onqubits[1:]and threeqibo.gates.gates.Multiplexorgates. Each of them acts on less qubits thanunitary, so they can be decomposed in turn.- Return type:
list[
qibo.gates.abstract.Gate]- Raises:
ValueError – If there are less than two
qubits, orunitarydoes not have the shape \((2^{n}, 2^{n})\).
References
1. V. V. Shende, S. S. Bullock, and I. L. Markov, Synthesis of quantum-logic circuits, IEEE Trans. Comput.-Aided Des. Integr. Circuits Syst. 25, 1000 (2006).
Optimizer#
Barrier before final measurements#
- class qibo.transpiler.optimizer.BarrierBeforeFinalMeasurements[source]#
Inserts a barrier before the final measurements of a circuit.
A measurement or a barrier is final if no other gate acts on its qubits after it. The final measurements and barriers are placed after a new
qibo.gates.special.Barrierthat acts on all the qubits of the circuit, in their original order, while the other gates stay before the barrier. A circuit without final measurements is not changed.The barrier keeps the tools that rearrange the gates, such as the routers, from moving them across the final measurements. It can be removed afterwards with
qibo.transpiler.optimizer.FilterGates.Example
The measurement of the first qubit is not final, because a Hadamard gate acts on the qubit after it, so it stays before the barrier.
from qibo import Circuit, gates from qibo.transpiler import BarrierBeforeFinalMeasurements circuit = Circuit(2) circuit.add(gates.M(0)) circuit.add(gates.H(0)) circuit.add(gates.CNOT(0, 1)) circuit.add(gates.M(0, 1)) new_circuit = BarrierBeforeFinalMeasurements()(circuit) print([gate.name for gate in new_circuit.queue])
['measure', 'h', 'cx', 'barrier', 'measure']
Commutative cancellation#
- class qibo.transpiler.optimizer.CommutativeCancellation(atol=1e-12, up_to_global_phase=True)[source]#
Merges rotations and cancels pairs of gates through the gates that commute with them.
A rotation is a single-qubit gate without control qubits that is a rotation around the \(X\), \(Y\) or \(Z\) axis, up to a global phase, e.g.
qibo.gates.RZ,qibo.gates.T,qibo.gates.SXorqibo.gates.Y. Two rotations around the same axis on the same qubit are merged into a singleqibo.gates.RX,qibo.gates.RYorqibo.gates.RZgate if all the gates between them commute with the second one, where two gates commute when applying them in either order gives the same matrix. The merged rotation is left out if it is the identity. Then, the pairs of gates whose product is the identity are cancelled through the gates that commute with them, seeqibo.transpiler.optimizer.InverseCancellation.Measurements, alignments, barriers, noise channels and fused gates are never merged nor cancelled, and stop the search across them on their qubits. Gates with sympy parameters are not merged, but the gates that commute with them do not stop the search. The gates are compared through their matrices at their current parameter values, so a merged rotation is not tracked by the returned circuit anymore. The pass is applied once, use
qibo.transpiler.optimizer.FixedPointto repeat it.Example
The two
qibo.gates.RZgates on the first qubit merge, as the controlled-NOT gate between them commutes with them.from qibo import Circuit, gates from qibo.transpiler import CommutativeCancellation circuit = Circuit(2) circuit.add(gates.RZ(0, 0.3)) circuit.add(gates.CNOT(0, 1)) circuit.add(gates.RZ(0, 0.4)) CommutativeCancellation()(circuit).draw()
0: ─RZ─o─ 1: ────X─
- Parameters:
atol (float, optional) – Tolerance on the angle of a rotation to be the identity and on the difference between the matrices that are compared. Defaults to \(10^{-12}\).
up_to_global_phase (bool, optional) – If
False, two rotations are merged only when the new rotation has exactly the same matrix as their product, which keeps the global phase of the circuit. Defaults toTrue.
Filter gates#
- class qibo.transpiler.optimizer.FilterGates(predicate)[source]#
Removes the gates of a circuit that do not satisfy a condition.
- Parameters:
predicate (Callable) – Function that takes a
qibo.gates.abstract.Gateand returnsTrueif the gate is kept in the circuit andFalseif it is removed.
Example
The barrier is removed, while the other gates are kept.
from qibo import Circuit, gates from qibo.transpiler import FilterGates circuit = Circuit(2) circuit.add(gates.H(0)) circuit.add(gates.Barrier(0, 1)) circuit.add(gates.CNOT(0, 1)) filter_barriers = FilterGates(lambda gate: not isinstance(gate, gates.Barrier)) print([gate.name for gate in filter_barriers(circuit).queue])
['h', 'cx']
Gate direction#
- class qibo.transpiler.optimizer.GateDirection(connectivity=None)[source]#
Reverses the two-qubit gates that act against the direction of the connectivity.
A directed edge
(a, b)of the connectivity means that a two-qubit gate is only supported with its first qubit onaand its second qubit onb. The gates that act on the opposite direction of an edge are replaced as follows:the gates that are symmetric, i.e. that do not change when their qubits are swapped, such as
qibo.gates.gates.CZ, have their qubits swapped. This includes the controlled gates, such as a phase gate with one control;the native gates that are not symmetric, which are
qibo.gates.gates.CNOT,qibo.gates.gates.CH,qibo.gates.gates.CSX,qibo.gates.gates.CSXDG,qibo.gates.gates.CYandqibo.gates.gates.ECR, are replaced by the same gate acting in the right direction and single-qubit gates, seeqibo.transpiler.decompositions.direction_dec.
The gates that act in the direction of an edge, or on qubits that are not connected, are not changed. The added single-qubit gates are not necessarily native, so this pass should be followed by an
qibo.transpiler.unroller.Unroller, and it should come after the one that decomposes the gates into native gates, because that one does not choose the direction of the two-qubit gates. If the connectivity is not directed, the circuit is not changed.- Parameters:
connectivity (
networkx.DiGraph, optional) – Hardware connectivity, whose nodes are thewire_namesof the circuit.qibo.transpiler.pipeline.Passessets it. Defaults toNone.
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;"ZGPI2": as"ZSX", withqibo.gates.GPI2gates of angle zero replacing the SX gates, as they are equal up to a global phase.
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)
Optimize 1-qubit gates simple commutation#
- class qibo.transpiler.optimizer.Optimize1qGatesSimpleCommutation(basis=None, atol=1e-12, up_to_global_phase=True)[source]#
Rewrites runs of single-qubit gates after moving gates through commuting gates.
A run is a sequence of single-qubit gates without control qubits on one qubit with no other gate on that qubit in between, as in
qibo.transpiler.optimizer.Optimize1qGatesDecomposition. Consider three consecutive runs on a qubit separated by two gates, the preceding and succeeding blockers. The gates at the start of the middle run that commute with the preceding blocker, up to the first one that does not, are moved to the end of the preceding run, and the gates at its end that commute with the succeeding blocker are moved to the start of the succeeding run. The three runs are rewritten withqibo.transpiler.optimizer.Optimize1qGatesDecomposition, and replaced if they have fewer gates, or if they had gates that are not inbasis. For example, aqibo.gates.RZgate commutes with the control of aqibo.gates.CNOTgate.Two gates commute when applying them in either order gives the same matrix. Gates are moved only into runs that already have gates, only the runs next to a gate that they commute with are rewritten, and the pass is applied once, so use
qibo.transpiler.optimizer.FixedPointto repeat it. Measurements, alignments, barriers, noise channels and gates with sympy parameters are never moved through nor rewritten. The input circuit is not modified.Example
The
qibo.gates.RZgate before the controlled-NOT gate moves through its control into the run after it, where it is rewritten with the other gates as a singleqibo.gates.U2gate.from qibo import Circuit, gates from qibo.transpiler import Optimize1qGatesSimpleCommutation circuit = Circuit(2) circuit.add(gates.H(0)) circuit.add(gates.RZ(0, 0.3)) circuit.add(gates.CNOT(0, 1)) circuit.add(gates.RZ(0, 0.4)) circuit.add(gates.H(0)) optimizer = Optimize1qGatesSimpleCommutation() new_circuit = optimizer(circuit) new_circuit.draw()
0: ─H─o─U2─ 1: ───X────
- Parameters:
basis (list[str], optional) – Names of the gates the circuit may contain, as in
qibo.transpiler.optimizer.Optimize1qGatesDecomposition. Defaults toNone.atol (float, optional) – Tolerance to decide that two gates commute, and the tolerance of
qibo.transpiler.optimizer.Optimize1qGatesDecomposition. Defaults to \(10^{-12}\).up_to_global_phase (bool, optional) – If
False, a run is rewritten only when the new gates have exactly the same matrix, as inqibo.transpiler.optimizer.Optimize1qGatesDecomposition. Defaults toTrue.
Optimize swap before measure#
- class qibo.transpiler.optimizer.OptimizeSwapBeforeMeasure[source]#
Removes the SWAP gates that are only followed by measurements.
A
qibo.gates.SWAPgate is removed if the gates after it on its two qubits are measurements, and if there is at least one, and the two qubits of each of these measurements are exchanged instead. Thus, the outcomes of the measurements are the same, and the order of their bits is kept, but the state of the circuit before them has the two qubits exchanged. Swaps that become followed only by measurements, because of the removal of other swaps, are removed as well.The input circuit is not modified.
Example
The swap is removed, and the measurement is on the qubits in the opposite order.
from qibo import Circuit, gates from qibo.transpiler import OptimizeSwapBeforeMeasure circuit = Circuit(3) circuit.add(gates.X(0)) circuit.add(gates.SWAP(0, 1)) circuit.add(gates.SWAP(1, 2)) circuit.add(gates.M(0, 1, 2)) optimized = OptimizeSwapBeforeMeasure()(circuit) for gate in optimized.queue: print(gate.name, gate.qubits)
x (0,) measure (1, 2, 0)
Parametrized gate fusion#
- class qibo.transpiler.optimizer.ParametrizedGateFusion(atol=1e-12, up_to_global_phase=False)[source]#
Merges consecutive rotation gates of the same kind into a single gate.
Two gates are merged when no other gate acts on their qubits in between and both are of the same gate class acting on the same qubits with the same control qubits. The merged gate has the sum of the rotation angles, for example a
qibo.gates.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#
- class qibo.transpiler.optimizer.Preprocessing(connectivity=None, initial_layout=None)[source]#
Pad the circuit with unused qubits to match the number of physical qubits.
- Parameters:
connectivity (
networkx.Graph) – Hardware connectivity as a graph.initial_layout (list or dict, optional) – Physical qubits on which the logical qubits of the circuit are placed. It is either a list, where the \(i\)-th logical qubit is placed on the physical qubit
initial_layout[i], or a dictionary{i: node}with the same meaning, which may leave logical qubits out. These are placed on the nodes of the connectivity that are not in the dictionary, in the order of the connectivity. In both cases every logical qubit must be placed on a distinct node of the connectivity, and the layout replaces thewire_namesof the circuit, which are set in place. IfNone, thewire_namesof the circuit are used if they are all in the connectivity, and the first nodes of the connectivity otherwise. Defaults toNone.
Example
The first and the third qubits of the circuit are placed on the nodes
3and1, and the second one on the first free node, which is0.import networkx as nx from qibo import Circuit, gates from qibo.transpiler import Preprocessing circuit = Circuit(3) circuit.add(gates.CNOT(0, 1)) preprocessing = Preprocessing(nx.path_graph(5), initial_layout={0: 3, 2: 1}) print(preprocessing(circuit).wire_names)
[3, 0, 1, 2, 4]
Rearrange#
- class qibo.transpiler.optimizer.Rearrange(max_qubits=1)[source]#
Rearranges gates using
qibo’s fusion algorithm. May reduce number 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, fused gates and global phase gates are never removed, the latter to keep the global phase of the circuit. Gates are evaluated at their current parameter values, so a removed parametrized gate is not tracked by the returned circuit anymore.
A gate with sympy parameters is evaluated at fixed random values of its symbols. It is removed only if it is the identity there up to numerical precision, whatever the value of
approximation_degree, because the true values of the symbols are unknown.- Parameters:
approximation_degree (float, optional) – Value in \([0, 1]\) that sets the tolerance as \(\epsilon = 1 -\)
approximation_degree. With the default value only gates equal to the identity up to numerical precision (\(\epsilon = 10^{-14}\)) are removed. Defaults to \(1.0\).
Example
The rotation by \(10^{-3}\) radians differs from the identity by an infidelity of about \(1.7 \times 10^{-7}\), so it is removed only when the tolerance allows it. The identity gate and the rotation by \(2\pi\) (minus the identity) are removed in both cases.
from math import pi from qibo import Circuit, gates from qibo.transpiler import RemoveIdentityEquivalent circuit = Circuit(2) circuit.add(gates.I(0)) circuit.add(gates.RZ(0, 2 * pi)) circuit.add(gates.RX(1, 1e-3)) circuit.add(gates.CNOT(0, 1)) print(RemoveIdentityEquivalent()(circuit).ngates) print(RemoveIdentityEquivalent(approximation_degree=1 - 1e-6)(circuit).ngates)
2 1
Remove reset in zero state#
- class qibo.transpiler.optimizer.RemoveResetInZeroState[source]#
Removes the resets acting on qubits that are still in the zero state.
A reset is the single-qubit
qibo.gates.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─
Validation#
- class qibo.transpiler.optimizer.Validation(connectivity=None, initial_layout=None)[source]#
Checks that the circuit can be transpiled, before any other pass runs.
Without it, a circuit that cannot be transpiled fails deep in the pipeline. This pass raises a clear error at the start instead. It does not change the circuit. It checks that:
the circuit has no noise channel. The transpiler works with unitary gates, so the only channel that it accepts is
qibo.gates.channels.ResetChannel.the circuit has no more qubits than the hardware, if there is a connectivity.
the initial layout, if there is one, places every qubit of the circuit on a distinct node of the connectivity, see
qibo.transpiler.optimizer.Preprocessing.
- Parameters:
connectivity (
networkx.Graph, optional) – Hardware connectivity. IfNone, neither the number of qubits nor the initial layout are checked. It is set byqibo.transpiler.pipeline.Passes. Defaults toNone.initial_layout (list, optional) – Physical qubits on which the logical qubits of the circuit are placed, in the format of
qibo.transpiler.optimizer.Preprocessing. Defaults toNone.
Example
The pass raises an error for the noise channel before the circuit is transpiled.
from qibo import Circuit, gates from qibo.transpiler import Validation circuit = Circuit(1) circuit.add(gates.H(0)) circuit.add(gates.PauliNoiseChannel([0], [("X", 0.1)])) try: Validation()(circuit) except Exception as error: print(type(error).__name__)
DecompositionError
Pipeline#
Passes#
- class qibo.transpiler.pipeline.Passes(passes=None, connectivity=None, native_gates=None, on_qubits=None, callback=None)[source]#
Define a transpiler pipeline consisting of smaller transpiler steps that are applied sequentially.
- Parameters:
passes (list, optional) – List of transpiler passes to be applied sequentially. If
None, no pass is applied. Seeqibo.transpiler.pipeline.generate_preset_passes()for a ready-to-use list. Defaults toNone.connectivity (
networkx.Graph, optional) – Hardware connectivity. It can be a directed graph, in which case an edge(a, b)means that a two-qubit gate is supported with its first qubit onaand its second onb, seeqibo.transpiler.optimizer.GateDirection. It is required ifon_qubitsis given. Defaults toNone.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.callback (Callable, optional) – Function called after each pass that runs, with the keyword arguments
transpiler_pass, the pass that has run,circuit, the circuit after the pass,time, the seconds that the pass has taken, andcount, the position of the pass inpasses. A router is skipped, and the callback is not called for it, if the circuit already respects the connectivity, since no qubit has to be moved. Defaults toNone.
Example
The callback prints the passes of the pipeline as they run. The circuit already respects the connectivity, so the router is skipped and it is not printed.
import networkx as nx from qibo import Circuit, gates from qibo.transpiler import Passes, Preprocessing, Sabre circuit = Circuit(2) circuit.add(gates.CNOT(0, 1)) def callback(transpiler_pass, circuit, time, count): print(count, type(transpiler_pass).__name__, circuit.nqubits) pipeline = Passes( [Preprocessing(), Sabre()], connectivity=nx.path_graph(3), callback=callback ) transpiled_circuit, final_layout = pipeline(circuit)
0 Preprocessing 3
- is_satisfied(circuit)[source]#
Check if the circuit respects the hardware connectivity and native gates.
If there is no connectivity, only the native gates are checked, because there is nothing to place and route.
- Parameters:
circuit (
qibo.models.circuit.Circuit) – Circuit to be checked.- Returns:
Trueif the circuit respects the hardware connectivity and native gates,Falseotherwise.- Return type:
(bool)
Preset passes#
- qibo.transpiler.pipeline.generate_preset_passes(connectivity=None, native_gates=None, initial_layout=None, seed=None, on_qubits=None, qubits_initially_zero=False, layout_method=None, routing_method=None, layout_trials=None, callback=None, approximation_degree=1.0, threshold=None, weight=None, use_dirty_ancillas=True, minimize_toffolis=None, minimize_depth=False, routing_trials=5, optimization_level=0, sampling_only=False, backend=None)[source]#
Generate a ready-to-use transpiler pipeline.
With the default
optimization_level=0, the pipeline only makes the circuit executable on the hardware, with the following passes:qibo.transpiler.optimizer.Validationraises an error at the start if the circuit cannot be transpiled, e.g. if it has a noise channel, more qubits than the hardware, or an invalidinitial_layout.qibo.transpiler.unroller.Unrollerdecomposes the gates acting on three or more qubits, which the router does not support. The qubits of the circuit that a multi-controlled gate does not act on are used as auxiliary qubits, seequbits_initially_zero.qibo.transpiler.optimizer.Preprocessingplaces the qubits oninitial_layout, or on the first nodes of the connectivity if there is none, and pads the circuit with the unused qubits of the hardware.The placer selected by
layout_method, if it is not the trivial one.The router selected by
routing_method, which adds the SWAP gates that make all the gates executable. By default, it isqibo.transpiler.router.Sabrewith the best ofrouting_trialstrials.qibo.transpiler.unroller.Unrollerdecomposes the circuit to the native gates, approximating some two-qubit gates with fewer entangling gates ifapproximation_degreeis lower than \(1\).
If
connectivityis directed, the pipeline ends with aqibo.transpiler.optimizer.GateDirectionpass and an unroller, so that the two-qubit gates act in the direction of the edges.The optimization levels \(1\) and \(2\) add optimizers to the pipeline. The ones that can write gates that are not native run before the last unroller. Level \(1\) adds:
qibo.transpiler.optimizer.InverseCancellationandqibo.transpiler.optimizer.CommutativeCancellationbefore the layout.qibo.transpiler.optimizer.ConsolidateBlocksbefore the last unroller, which rewrites the blocks of two-qubit gates that have fewerqibo.gates.CZgates.A loop of
qibo.transpiler.optimizer.Optimize1qGatesDecomposition, with the native gates asbasis, andqibo.transpiler.optimizer.InverseCancellationat the end. The first one is left out if the native gates have no Euler basis.The
"reverse_traversal"placer with five trials, unlesslayout_methodorinitial_layoutis given.
Level \(2\) adds, to the optimizers of level \(1\):
qibo.transpiler.optimizer.RemoveIdentityEquivalentbefore the layout, which also removes the gates close to the identity ifapproximation_degreeis lower than \(1\).qibo.transpiler.optimizer.CommutativeCancellationbefore the last unroller.A second loop at the end, of
qibo.transpiler.optimizer.Optimize1qGatesDecomposition,qibo.transpiler.optimizer.Optimize1qGatesSimpleCommutationandqibo.transpiler.optimizer.InverseCancellationwith commutation. It is also left out if the native gates have no Euler basis.
The optimizers can change the global phase of the circuit, and the gates that they merge do not keep their trainable parameters.
Without
connectivitythere is nothing to place and route, so the pipeline only validates the circuit and decomposes it to the native gates.- Parameters:
connectivity (
networkx.Graph, optional) – Hardware connectivity. It can be directed, see above. Defaults toNone.native_gates (
qibo.transpiler.unroller.NativeGates, optional) – Native gates supported by the hardware. Defaults toqibo.transpiler.unroller.NativeGates.default().initial_layout (list or dict, optional) – Physical qubits on which the logical qubits of the circuit are placed, as a list or as a dictionary that may leave qubits out, see
qibo.transpiler.optimizer.Preprocessing. It requiresconnectivity, and it cannot be used with a non-triviallayout_method. Defaults toNone.seed (int, optional) – Seed of the random choices of the placer and the router, which makes the transpilation reproducible. Defaults to
None.on_qubits (list, optional) – List of qubits to be used in the transpiler, see
qibo.transpiler.pipeline.Passes. It requiresconnectivity. Defaults toNone.qubits_initially_zero (bool, optional) – If
True, the circuit is assumed to start in the state \(\ket{0}^{\otimes n}\). The qubits on which no previous gate acts are then used as clean auxiliary qubits to decompose multi-controlled gates, with the construction that minimizes the number of Toffoli gates, seeqibo.transpiler.unroller.Unroller. This makes the decomposition shallower and, for many controls, also shorter. If there are no clean qubits for a gate, its dirty auxiliary qubits are used with the same construction, which has more CNOT gates than the default one. Withoutqubits_initially_zerothe other qubits are used as dirty auxiliary qubits, which can be in any state. It must beFalseif the circuit is executed with another initial state. Defaults toFalse.layout_method (str or
qibo.transpiler.abstract.Placer, optional) – Placer that finds the initial layout. It can be"trivial", which keeps thewire_namesof the circuit if they are nodes of the connectivity and uses the first nodes otherwise,"dense"(qibo.transpiler.placer.Dense),"random"(qibo.transpiler.placer.Random),"subgraph"(qibo.transpiler.placer.Subgraph),"reverse_traversal"(qibo.transpiler.placer.ReverseTraversalwithqibo.transpiler.router.Sabre),"star"(qibo.transpiler.placer.StarConnectivityPlacer) or a placer. It requiresconnectivity. Defaults toNone, which is"trivial", or"reverse_traversal"ifoptimization_levelis at least \(1\),initial_layoutisNoneandrouting_methodisNoneor"sabre".routing_method (str or
qibo.transpiler.abstract.Router, optional) – Router that makes the circuit executable. It can be"sabre"(qibo.transpiler.router.Sabrewithrouting_trialstrials),"shortest_paths"(qibo.transpiler.router.ShortestPaths),"star"(qibo.transpiler.router.StarConnectivityRouter),"none"(qibo.transpiler.router.NoRouting, which raises an error if the circuit needs routing) or a router. A router is skipped if the circuit already respects the connectivity. It requiresconnectivity. Defaults toNone, which is"sabre".layout_trials (int, optional) – Number of layout attempts of the
"reverse_traversal"placer, seeqibo.transpiler.placer.ReverseTraversal. It is not used by the other placers. Defaults toNone, which is \(1\) ifoptimization_levelis \(0\) and \(5\) otherwise.callback (Callable, optional) – Function called after each pass that runs, see
qibo.transpiler.pipeline.Passes. Defaults toNone.approximation_degree (float, optional) – Fidelity of each entangling gate, between \(0\) and \(1\). If it is lower than \(1\), the unroller that decomposes the circuit to the native gates approximates the two-qubit gates that it decomposes from their matrix with fewer
qibo.gates.CZgates, if it increases the expected fidelity, seeqibo.transpiler.unroller.Unroller. The multi-qubit gates are decomposed exactly. Defaults to \(1\), which is the exact decomposition.threshold (float, optional) – Tolerance of the decomposition of the two-qubit gates that are decomposed from their matrix, like for
approximation_degree, seeqibo.transpiler.unroller.Unroller. Defaults toNone, which uses the default value of the decomposition.weight (float, optional) – Weight of the imaginary part in the matrix that is diagonalized to decompose those gates, like for
approximation_degree, seeqibo.transpiler.unroller.Unroller. Defaults toNone, which uses the default value of the decomposition.use_dirty_ancillas (bool, optional) – If
True, the qubits that a multi-controlled gate does not act on are used as dirty auxiliary qubits in its decomposition, seeqibo.transpiler.unroller.Unroller. Defaults toTrue.minimize_toffolis (bool, optional) – If
True, the multi-controlledqibo.gates.Xgates with auxiliary qubits are decomposed with the lowest number of Toffoli gates, seeqibo.transpiler.unroller.Unroller. It cannot be used together withminimize_depth. Defaults toNone, which isqubits_initially_zerounlessminimize_depthisTrue.minimize_depth (bool, optional) – If
True, the multi-controlledqibo.gates.Xgates with auxiliary qubits are decomposed with a depth that is logarithmic in the number of controls, seeqibo.transpiler.unroller.Unroller. It cannot be used together withminimize_toffolis. Defaults toFalse.routing_trials (int, optional) – Number of independent routing attempts of the
"sabre"router, which keeps the best one, seeqibo.transpiler.router.Sabre. It is not used by the other routers, nor by the router of the"reverse_traversal"placer. Defaults to \(5\).optimization_level (int, optional) – Optimization level of the pipeline. It can be \(0\), which is the pipeline described above, that only makes the circuit executable on the hardware and does not optimize it, or \(1\) or \(2\), which add the optimizers described above. Defaults to \(0\).
sampling_only (bool, optional) – If
True, the optimizers of levels \(1\) and \(2\) also use the rewrites that keep the distribution of the measured qubits, but can change the state of the circuit before the measurements:qibo.transpiler.optimizer.OptimizeSwapBeforeMeasure,qibo.transpiler.optimizer.RemoveDiagonalGatesBeforeMeasurementandqibo.transpiler.optimizer.RemoveFinalReset, before the layout. It has no effect ifoptimization_levelis \(0\). Defaults toFalse.backend (
qibo.backends.abstract.Backend, optional) – Backend used by the unrollers to compute the gate matrices, unless another one is given when the pipeline is called. IfNone, it uses the global backend. Defaults toNone.
- Returns:
Transpiler pipeline.
- Return type:
- Raises:
ConnectivityError – If
initial_layout,layout_methodorrouting_methodis given withoutconnectivity.PlacementError – If
initial_layoutis given with a non-triviallayout_method.ValueError – If
layout_methodorrouting_methodis not a valid method, ifapproximation_degreeis not between \(0\) and \(1\), ifminimize_toffolisandminimize_depthare bothTrue, ifrouting_trialsis not positive and the router is"sabre", or ifoptimization_levelis not a valid level.
Restrict qubit connectivity#
- qibo.transpiler.pipeline.restrict_connectivity_qubits(connectivity, qubits)[source]#
Restrict the connectivity to selected qubits.
- Parameters:
connectivity (
networkx.Graph) – Hardware connectivity.qubits (list) – List of qubits to select.
- Returns:
New restricted connectivity.
- Return type:
(
networkx.Graph)
Transpile#
- qibo.transpiler.transpile(circuits, connectivity=None, native_gates=None, initial_layout=None, seed=None, on_qubits=None, qubits_initially_zero=False, layout_method=None, routing_method=None, layout_trials=None, callback=None, approximation_degree=1.0, threshold=None, weight=None, use_dirty_ancillas=True, minimize_toffolis=None, minimize_depth=False, routing_trials=5, optimization_level=0, sampling_only=False, num_processes=None, backend=None)[source]#
Transpile one or more circuits in a single call.
It builds the pipeline of
qibo.transpiler.pipeline.generate_preset_passes()once and applies it to the circuits, which makes them executable on the hardware, and optimizes them ifoptimization_levelis higher than \(0\). All the arguments exceptcircuitsandnum_processesare the ones ofqibo.transpiler.pipeline.generate_preset_passes(), and they are shared by all the circuits.The circuits of a list can be transpiled in parallel with
num_processes. Each of them is then transpiled by a worker process with its own copy of the pipeline, so the result for a circuit does not depend on the other circuits nor onnum_processes, and, with aseed, it is the one of transpiling the circuit alone. This can differ from the sequential result, where the random state of the placer and the router continues from one circuit to the next. The worker processes are started with thespawnmethod, so the script that callsqibo.transpiler.pipeline.transpile()must protect the call withif __name__ == "__main__":. They do not share the global backend either, sobackendshould be given if it is not the default one.Example
A rotation, a Toffoli gate and a CNOT gate between qubits that are not neighbors are transpiled to a line of four qubits, where
qibo.gates.CNOTandqibo.gates.U3are the native gates. The Toffoli gate is decomposed, the SWAP gates that the routing adds are decomposed too, and the transpiled circuit is drawn. The decomposition of the Toffoli gate leaves a global phase, which the drawing reports. Ifcircuitsis a list, the result is a list with a transpiled circuit and a final layout for each circuit.import networkx as nx from qibo import Circuit, gates from qibo.backends import NumpyBackend from qibo.transpiler import NativeGates, transpile from qibo.transpiler.asserts import assert_transpiling backend = NumpyBackend() circuit = Circuit(4) circuit.add(gates.RY(0, 0.5)) circuit.add(gates.TOFFOLI(0, 1, 2)) circuit.add(gates.CNOT(0, 3)) circuit.add(gates.M(*range(4))) connectivity = nx.path_graph(4) natives = NativeGates.from_gatelist([gates.U3, gates.CNOT, gates.M]) transpiled_circuit, final_layout = transpile( circuit, connectivity, natives, seed=42, backend=backend ) # Raises an error if the circuits are not equivalent, or if the transpiled circuit # does not respect the connectivity and the native gates assert_transpiling( original_circuit=circuit, transpiled_circuit=transpiled_circuit, connectivity=connectivity, final_layout=final_layout, native_gates=natives, ) print(final_layout) transpiled_circuit.draw(line_wrap=1000, parallel=True)
{0: 1, 1: 3, 2: 0, 3: 2} Global phase = exp(i * 9π/8) 0: ─U3────────────o─────────o───────o─X─o────────────────M─ 1: ────o────o─X─o─X─RZ─X─RZ─X─RZ─U3─X─o─X─o─RZ─o───────o─M─ 2: ─U3─X─RZ─X─o─X──────o─RZ───────────────X─RZ─X─o─X─o─X─M─ 3: ──────────────────────────────────────────────X─o─X───M─- Parameters:
circuits (
qibo.models.circuit.Circuitor list or tuple) – Circuit, or list of circuits, to be transpiled.connectivity (
networkx.Graph, optional) – Hardware connectivity. Defaults toNone.native_gates (
qibo.transpiler.unroller.NativeGates, optional) – Native gates supported by the hardware. Defaults toqibo.transpiler.unroller.NativeGates.default().initial_layout (list or dict, optional) – Physical qubits on which the logical qubits of the circuits are placed, see
qibo.transpiler.optimizer.Preprocessing. Defaults toNone.seed (int, optional) – Seed of the random choices of the placer and the router. Defaults to
None.on_qubits (list, optional) – List of qubits to be used in the transpiler. Defaults to
None.qubits_initially_zero (bool, optional) – If
True, the circuits are assumed to start in the state \(\ket{0}^{\otimes n}\). Defaults toFalse.layout_method (str or
qibo.transpiler.abstract.Placer, optional) – Placer that finds the initial layout. Defaults toNone.routing_method (str or
qibo.transpiler.abstract.Router, optional) – Router that makes the circuits executable. Defaults toNone.layout_trials (int, optional) – Number of layout attempts of the
"reverse_traversal"placer. Defaults toNone, which is \(1\) ifoptimization_levelis \(0\) and \(5\) otherwise.callback (Callable, optional) – Function called after each pass that runs, for each circuit, see
qibo.transpiler.pipeline.Passes. It cannot be used with more than one process. Defaults toNone.approximation_degree (float, optional) – Fidelity of each entangling gate, between \(0\) and \(1\). If it is lower than \(1\), the two-qubit gates that are decomposed from their matrix are approximated with fewer entangling gates, if it increases the expected fidelity, see
qibo.transpiler.pipeline.generate_preset_passes(). Defaults to \(1\), which is the exact decomposition.threshold (float, optional) – Tolerance of the decomposition of the two-qubit gates that are decomposed from their matrix, like for
approximation_degree. Defaults toNone, which uses the default value of the decomposition.weight (float, optional) – Weight of the imaginary part in the matrix that is diagonalized to decompose those gates, like for
approximation_degree. Defaults toNone, which uses the default value of the decomposition.use_dirty_ancillas (bool, optional) – If
True, the qubits that a multi-controlled gate does not act on are used as dirty auxiliary qubits in its decomposition. Defaults toTrue.minimize_toffolis (bool, optional) – If
True, the multi-controlledqibo.gates.Xgates with auxiliary qubits are decomposed with the lowest number of Toffoli gates. It cannot be used together withminimize_depth. Defaults toNone, which isqubits_initially_zerounlessminimize_depthisTrue.minimize_depth (bool, optional) – If
True, the multi-controlledqibo.gates.Xgates with auxiliary qubits are decomposed with a depth that is logarithmic in the number of controls. It cannot be used together withminimize_toffolis. Defaults toFalse.routing_trials (int, optional) – Number of independent routing attempts of the
"sabre"router, which keeps the best one. It is not used by the other routers. Defaults to \(5\).optimization_level (int, optional) – Optimization level of the pipeline, see
qibo.transpiler.pipeline.generate_preset_passes(). It can be \(0\), \(1\) or \(2\). Defaults to \(0\), which does not optimize the circuits.sampling_only (bool, optional) – If
True, the optimizers also use the rewrites that keep the distribution of the measured qubits but can change the state of the circuit before the measurements, seeqibo.transpiler.pipeline.generate_preset_passes(). It logs a warning for the circuits without measurements, and it has no effect ifoptimization_levelis \(0\). Defaults toFalse.num_processes (int, optional) – Number of worker processes that transpile the circuits of a list in parallel. If
Noneor \(1\), the circuits are transpiled one after the other in the current process. It has no effect on a single circuit, and no more processes than circuits are started. Defaults toNone.backend (
qibo.backends.abstract.Backend, optional) – Backend used by the unrollers to compute the gate matrices, and passed to the other passes that take one. IfNone, they use the global backend. Defaults toNone.
- Returns:
(
qibo.models.circuit.Circuit,qibo.transpiler.layout.TranspileLayout) or list: Transpiled circuit and final {logical: physical} qubit mapping, seeqibo.transpiler.pipeline.Passes. Ifcircuitsis a list or tuple, the list of these pairs, in the same order.- Raises:
ValueError – If
num_processesis not a positive integer, ifcallbackis given with more than one process, ifapproximation_degreeis not between \(0\) and \(1\), ifminimize_toffolisandminimize_depthare bothTrue, ifrouting_trialsis not positive and the router is"sabre", or ifoptimization_levelis not a valid level.BrokenProcessPool – If a worker process ends abruptly. A common cause is a script that calls this function with several processes without the
if __name__ == "__main__":guard.
- Return type:
tuple[qibo.models.circuit.Circuit, dict | None] | list[tuple[qibo.models.circuit.Circuit, dict | None]]
Transpile layout#
- class qibo.transpiler.layout.TranspileLayout(final_layout, initial_layout, nqubits_input)[source]#
Layout of the qubits of a circuit before and after the routing.
It is the final layout returned by
qibo.transpiler.pipeline.Passes. As a dictionary, it maps the label of the physical qubit on which each qubit of the circuit is placed before the routing, to the index of the physical qubit on which it ends after the routing. It can therefore be used wherever a final {logical: physical} qubit mapping is expected. It also stores the layout before the routing. If the pipeline has no router, the qubits are not moved, so the mapping and the routing permutation are the identity, and the initial layout is the one of the transpiled circuit.Example
A circuit on four qubits is transpiled to a line of five qubits. It is drawn before and after the transpilation with
parallel=True, so that the gates on disjoint qubits share a column, and withline_wrap=12, which splits the wider transpiled circuit in two blocks. The routing adds the SWAP gates, drawn asx, and the preprocessing adds the fifth qubit of the line as an auxiliary qubit, which is not used. The \(i\)-th element of the routing permutation is the physical qubit on which the \(i\)-th qubit ends: the first qubit does not move, while the second, third and fourth qubits end on the third, fourth and second physical qubits.import networkx as nx from qibo import Circuit, gates from qibo.transpiler import Passes, Preprocessing, ShortestPaths circuit = Circuit(4) circuit.add(gates.H(0)) circuit.add(gates.H(2)) circuit.add(gates.CNOT(0, 1)) circuit.add(gates.CNOT(2, 3)) circuit.add(gates.CNOT(0, 3)) circuit.add(gates.CNOT(1, 2)) circuit.add(gates.M(0, 1, 2, 3)) circuit.draw(parallel=True) print() pipeline = Passes( [Preprocessing(), ShortestPaths(seed=1)], connectivity=nx.path_graph(5) ) transpiled_circuit, layout = pipeline(circuit) transpiled_circuit.draw(parallel=True) print() print(dict(layout)) print(layout.initial_layout) print(layout.routing_permutation) print(layout.nqubits_input) print(layout.auxiliary_qubits) print(layout.initial_logical_layout(filter_auxiliary=True)) print(layout.final_logical_layout(filter_auxiliary=True))
0: ─H─o─o───M─ 1: ───X─|─o─M─ 2: ─H─o─|─X─M─ 3: ───X─X───M─ 0: ─H─o───────o─M─ 1: ───X─o───x─X─M─ 2: ─H─o─X─x─x───M─ 3: ───X───x─────M─ 4: ─────────────── {0: 0, 1: 2, 2: 3, 3: 1, 4: 4} [0, 1, 2, 3, 4] [0, 2, 3, 1, 4] 4 [4] {0: 0, 1: 1, 2: 2, 3: 3} {0: 0, 1: 2, 2: 3, 3: 1}- Parameters:
final_layout (dict) – Mapping from the label of the physical qubit on which each qubit is placed before the routing, to the index of the physical qubit on which it ends after the routing.
initial_layout (list) – Labels of the physical qubits on which the qubits of the circuit are placed before the routing. The \(i\)-th element is the physical qubit of the \(i\)-th qubit of the circuit.
nqubits_input (int) – Number of qubits of the circuit before it is padded with the unused qubits of the hardware, which are the auxiliary qubits.
- property auxiliary_qubits: list#
Labels of the physical qubits that the preprocessing adds to the circuit as auxiliary qubits.
- property final_layout: dict#
Mapping from the physical qubit on which each qubit is placed before the routing, to the one on which it ends after the routing, as a plain dictionary.
- property routing_permutation: list[int]#
Permutation of the qubits that the routing applies. Its \(i\)-th element is the index of the physical qubit on which the \(i\)-th qubit of the circuit ends after the routing. The elements from
nqubits_inputonward are the auxiliary qubits.
- final_logical_layout(filter_auxiliary=False)[source]#
Index of the physical qubit on which each qubit of the circuit ends.
- Parameters:
filter_auxiliary (bool, optional) – If
True, the auxiliary qubits are left out. Defaults toFalse.- Returns:
Mapping from the index of each qubit of the circuit to the index of the physical qubit on which it ends after the routing, see
qibo.transpiler.layout.TranspileLayout.routing_permutation.- Return type:
Placer#
Dense#
- class qibo.transpiler.placer.Dense(connectivity=None)[source]#
Places the qubits on the most connected region of the hardware.
The \(n\) qubits that take part in two-qubit gates are placed on a connected region of \(n\) physical qubits with the largest number of connections among them. The qubits with more two-qubit gates are placed on the physical qubits with more connections in the region, and the other qubits take the remaining physical qubits in the order of the nodes of the connectivity.
- Parameters:
connectivity (
networkx.Graph, optional) – Hardware connectivity. Defaults toNone.
Example
The three qubits of a triangle of CNOT gates are placed on the triangle of the connectivity, which needs no SWAP gate, instead of on the first three nodes.
import networkx as nx from qibo import Circuit, gates from qibo.transpiler import Dense connectivity = nx.Graph([(0, 1), (1, 2), (2, 3), (3, 4), (2, 4)]) circuit = Circuit(5) circuit.add(gates.CNOT(0, 1)) circuit.add(gates.CNOT(1, 2)) circuit.add(gates.CNOT(0, 2)) Dense(connectivity)(circuit) print(circuit.wire_names)
[2, 3, 4, 0, 1]
Random#
- class qibo.transpiler.placer.Random(connectivity=None, samples=100, seed=None)[source]#
Random initialization with greedy policy, so that a maximum number of 2-qubit gates can be applied without introducing any SWAP gate.
- Parameters:
connectivity (
networkx.Graph, optional) – Hardware connectivity. Defaults toNone.samples (int, optional) – Number of random initializations to try. If \(0\), no random initialization is tried and the qubits are placed following the order of the nodes of
connectivity, i.e. the \(i\)-th qubit is placed on the \(i\)-th node. Defaults to \(100\).seed (int or
numpy.random.Generator, optional) – Either a generator of random numbers or a fixed seed to initialize a generator. IfNone, initializes a generator with a random seed. Defaults toNone.
Reverse traversal#
- class qibo.transpiler.placer.ReverseTraversal(routing_algorithm, connectivity=None, depth=None, trials=1, seed=None)[source]#
Places qubits based on the algorithm proposed in Reference [1].
Compatible with all the available
qibo.transpiler.abstract.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\).trials (int, optional) – Number of independent layout attempts. The first one starts from the layout of the circuit, and the others from random permutations of it. Each attempt finds a layout with the reverse traversal, and the one for which the router adds the fewest SWAP gates to the circuit is kept, the first one among ties. With one attempt, no routing of the circuit is needed to choose the layout. Defaults to \(1\).
seed (int, optional) – Seed of the random permutations of the attempts, which makes them reproducible. Defaults to
None.
References
1. G. Li, Y. Ding, and Y. Xie, Tackling the Qubit Mapping Problem for NISQ-Era Quantum Devices. arXiv:1809.02573 [cs.ET].
Subgraph#
- class qibo.transpiler.placer.Subgraph(connectivity=None)[source]#
Subgraph isomorphism qubit placer.
Since it is a \(NP\)-complete problem, it can take exponential time for large circuits. This initialization method may fail for very short circuits.
- Parameters:
connectivity (
networkx.Graph, optional) – Hardware connectivity. Defaults toNone.
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, optional) – If
True, only the qubit mappings are stored, which is used to evaluate the cost of a SWAP without modifying the circuit. Defaults toFalse.
- 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.- Return type:
None
- routed_circuit(circuit_kwargs=None)[source]#
Returns the routed circuit.
- Parameters:
circuit_kwargs (dict, optional) – Original circuit init_kwargs. Defaults to
None.- Returns:
Routed circuit.
- Return type:
- update(logical_swap)[source]#
Updates the qubit mapping after applying a
SWAP.Adds the
qibo.gates.gates.SWAPgate to the routed blocks. Method works in-place.- Parameters:
logical_swap (tuple) – Tuple containing the logical qubits to be swapped.
- Return type:
None
- get_physical_qubits(block)[source]#
Returns the physical qubits where a block is acting on.
- Parameters:
block (int or
qibo.transpiler.blocks.Block) – Block to be analysed.- Returns:
Physical qubit numbers where a block is acting on.
- Return type:
No routing#
- class qibo.transpiler.router.NoRouting(connectivity=None)[source]#
Leaves the circuit unchanged, and raises an error if it cannot run on the hardware.
- Parameters:
connectivity (
networkx.Graph, optional) – Hardware connectivity. Defaults toNone.
Example
The two-qubit gates of the circuit act on connected qubits, so the circuit is not changed and no qubit is moved.
import networkx as nx from qibo import Circuit, gates from qibo.transpiler import NoRouting circuit = Circuit(3) circuit.add(gates.CNOT(0, 1)) circuit.add(gates.CNOT(1, 2)) routed_circuit, final_layout = NoRouting(nx.path_graph(3))(circuit) print(final_layout)
{0: 0, 1: 1, 2: 2}
Sabre#
- class qibo.transpiler.router.Sabre(connectivity=None, lookahead=2, decay_lookahead=0.6, delta=0.001, swap_threshold=1.5, seed=None, trials=1, heuristic='decay')[source]#
Routing algorithm proposed in Ref [1].
- Parameters:
connectivity (
networkx.Graph) – Hardware chip connectivity.lookahead (int, optional) – Lookahead factor, how many dag layers will be considered in computing the cost. Defaults to \(2\).
decay_lookahead (float, optional) – Value in interval \([0, 1]\). How the weight of the distance in the dag layers decays in computing the cost. Defaults to \(0.6\).
delta (float, optional) – Defines the number of SWAPs vs depth trade-off by deciding how the algorithm tends to select non-overlapping SWAPs. Defaults to \(10^{-3}\).
swap_threshold (float, optional) – Limits the number of added SWAPs in every routing iteration. This threshold is multiplied by the length of the longest path in the circuit connectivity. If the number of added SWAPs exceeds the threshold before a gate is routed, shortestpath routing is applied. Defaults to \(1.5\).
seed (int, optional) – Seed for the candidate random choice as tiebreaker. Defaults to
None.trials (int, optional) – Number of independent routing attempts, which differ in the random choices among equally good candidates. The attempt with the lowest number of added SWAPs is returned, and the first one among ties. The attempts are reproducible if
seedis given, and the first one does not depend ontrials. Defaults to \(1\).heuristic (str, optional) –
Cost that selects the SWAP gate to be added. It is the distance between the qubits of the gates in the front layer of the circuit, plus the terms below:
"basic": no other term, solookahead,decay_lookaheadanddeltaare not used."lookahead": the distances of the gates in the nextlookaheadlayers, weighted bydecay_lookahead.deltais not used."decay": the terms of"lookahead", with the distances of the qubits that were recently swapped increased bydelta, which favors non-overlapping SWAPs.
Defaults to
"decay".
- Raises:
ValueError – If
trialsis not positive, orheuristicis not"basic","lookahead"or"decay".
References
1. G. Li, Y. Ding, and Y. Xie, Tackling the Qubit Mapping Problem for NISQ-Era Quantum Devices. arXiv:1809.02573 [cs.ET].
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.
Start connectivity#
- class qibo.transpiler.router.StarConnectivityRouter(connectivity=None)[source]#
Transforms an arbitrary circuit to one that can be executed on hardware.
This transpiler produces a circuit that respects the following connectivity by adding SWAP gates when needed:
q | q -- q -- q | q
- Parameters:
connectivity (
networkx.Graph) – Star connectivity graph.
Unroller#
Native gates#
- class qibo.transpiler.unroller.NativeGates(value, names=<not given>, *values, module=None, qualname=None, type=None, start=1, boundary=None)[source]#
Define native gates supported by the unroller.
A native gate set should contain at least one entangling two-qubit gate, which is any of the two-qubit gates below except
qibo.gates.gates.SWAP, and single-qubit gates that can implement any single-qubit unitary. These 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.decompositions.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.SXDGqibo.gates.gates.CSXDGqibo.gates.gates.SiSWAPDG
If several entangling gates are native, any other gate is decomposed into the first one among
qibo.gates.gates.CZ,qibo.gates.gates.iSWAP,qibo.gates.gates.CNOTand the gates above, in this order. A native two-qubit gate is never decomposed.- classmethod from_gatelist(gatelist)[source]#
Create a NativeGates object containing all gates from a
gatelist.- Parameters:
gatelist (list[qibo.gates.abstract.Gate]) –
- Return type:
- classmethod from_gate(gate)[source]#
Create a
qibo.transpiler.unroller.NativeGatesobject from aqibo.gates.gates.Gate.- Parameters:
gate (Gate | type[qibo.gates.abstract.Gate]) –
- Return type:
- property is_universal: bool#
Whether the native gates can implement any two-qubit unitary.
This holds when the set contains at least one entangling two-qubit gate, which is any of them except
qibo.gates.gates.SWAP, and single-qubit gates that implement any single-qubit unitary exactly:qibo.gates.gates.U3,qibo.gates.gates.GPI2, or gates with two rotations about orthogonal axes with free angles, 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).
- property single_qubit_gates: tuple[type[qibo.gates.abstract.Gate], ...]#
Classes of the single-qubit gates in the native gates.
- property two_qubit_gates: tuple[type[qibo.gates.abstract.Gate], ...]#
Classes of the two-qubit gates in the native gates.
- property gate_names: tuple[str, ...]#
Names of the gates in the native gates, without the measurement.
The names are the ones of
qibo.gates.abstract.Gate.name, e.g."cx"forqibo.gates.gates.CNOT. They can be thebasisofqibo.transpiler.optimizer.Optimize1qGatesDecomposition.
Unroller#
- class qibo.transpiler.unroller.Unroller(native_gates, use_dirty_ancillas=False, min_qubits=1, qubits_initially_zero=False, minimize_toffolis=False, minimize_depth=False, approximation_degree=1.0, threshold=None, weight=None, backend=None)[source]#
Decomposes a circuit to native gates.
- Parameters:
native_gates (
qibo.transpiler.unroller.NativeGates) – Native gates to use in the transpiled circuit. They must be universal, seeqibo.transpiler.unroller.NativeGates.is_universal.use_dirty_ancillas (bool, optional) – If
True, the qubits of the circuit that a multi-controlled gate does not act on are used as dirty auxiliary qubits in its decomposition, which makes the cost of a multi-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.min_qubits (int, optional) – Only gates acting on at least
min_qubitsqubits are decomposed, the others are left unchanged. For example,min_qubits=3decomposes only gates acting on three or more qubits, which is useful to make a circuit executable by a router before routing. Defaults to \(1\), which decomposes every gate.qubits_initially_zero (bool, optional) – If
True, the circuit is assumed to start in the state \(\ket{0}^{\otimes n}\). The qubits on which no previous gate of the circuit acts are then known to be in the state \(\ket{0}\), and are used as clean auxiliary qubits in the decomposition of multi-controlled gates, being left in that state. They are used as clean qubits if that lowers the number of native gates, and as dirty ones otherwise. It must beFalseif the circuit is executed with another initial state. Defaults toFalse.minimize_toffolis (bool, optional) – If
True, a multi-controlledqibo.gates.Xwith at least one auxiliary qubit is decomposed with the lowest number of Toffoli gates, which is useful if they are expensive. This is not the decomposition with the fewest native gates, which is the default one, and it needs more of them with dirty auxiliary qubits. With clean auxiliary qubits (seequbits_initially_zero), it is shallower. It cannot be used together withminimize_depth. Defaults toFalse.minimize_depth (bool, optional) – If
True, a multi-controlledqibo.gates.Xwith at least one auxiliary qubit is decomposed with a depth that is logarithmic in the number of controls. It cannot be used together withminimize_toffolis. Seeqibo.transpiler.multi_controlled_decomposition(). Defaults toFalse.approximation_degree (float, optional) – Fidelity of each
qibo.gates.CZgate, between \(0\) and \(1\). If it is lower than \(1\), the two-qubit gates that are decomposed from their matrix, i.e. the unitaries, theqibo.gates.fSimandqibo.gates.GeneralizedfSimgates and the gates without a known decomposition, can use fewerqibo.gates.CZgates than the exact decomposition, if it increases the expected fidelity, seeqibo.transpiler.decompositions.unitary_decompositions.two_qubit_decomposition(). The other gates are decomposed exactly. Defaults to \(1\), which is the exact decomposition.threshold (float, optional) – Tolerance of the decomposition of the two-qubit gates that are decomposed from their matrix, like for
approximation_degree, seeqibo.transpiler.decompositions.unitary_decompositions.two_qubit_decomposition(). Defaults toNone, which uses its default value.weight (float, optional) – Weight of the imaginary part in the matrix that is diagonalized to decompose those gates, see
qibo.transpiler.decompositions.unitary_decompositions.two_qubit_decomposition(). Defaults toNone, which uses its default value.backend (
qibo.backends.abstract.Backend, optional) – Backend to use for gate matrix. Defaults toNone.
- Raises:
ValueError – If
approximation_degreeis not between \(0\) and \(1\).
Translate gate#
- qibo.transpiler.unroller.translate_gate(gate, native_gates, free=(), clean=(), minimize_toffolis=False, minimize_depth=False, approximation_degree=1.0, threshold=None, weight=None, backend=None)[source]#
Maps gates to a hardware-native implementation.
- Parameters:
gate (
qibo.gates.abstract.Gate) – Gate to be decomposed.native_gates (
qibo.transpiler.unroller.NativeGates) – Native gates supported by the hardware.free (tuple[int, ...], optional) – Ids of free qubits that can be used as dirty auxiliary qubits to decompose multi-controlled gates. Defaults to an empty tuple, which uses no auxiliary qubits.
clean (tuple[int, ...], optional) – Ids of free qubits that are known to be in the state \(\ket{0}\) and are left in that state, which can be used as clean auxiliary qubits to decompose multi-controlled gates. They must not be in
free. Defaults to an empty tuple.minimize_toffolis (bool, optional) – If
True, a multi-controlledqibo.gates.Xis decomposed with the lowest number of Toffoli gates when it has auxiliary qubits, seeqibo.transpiler.multi_controlled_decomposition(). Defaults toFalse.minimize_depth (bool, optional) – If
True, a multi-controlledqibo.gates.Xis decomposed with a logarithmic depth when it has auxiliary qubits. Defaults toFalse.approximation_degree (float, optional) – Fidelity of each
qibo.gates.CZgate, between \(0\) and \(1\), used to approximate the two-qubit gates that are decomposed from their matrix with fewerqibo.gates.CZgates, seeqibo.transpiler.unroller.Unroller. Defaults to \(1\), which is the exact decomposition.threshold (float, optional) – Tolerance of the decomposition of the two-qubit gates that are decomposed from their matrix, like for
approximation_degree, seeqibo.transpiler.unroller.Unroller. Defaults toNone, which uses the default value of the decomposition.weight (float, optional) – Weight of the imaginary part in the matrix that is diagonalized to decompose those gates, see
qibo.transpiler.unroller.Unroller. Defaults toNone, which uses the default value of the decomposition.backend (
qibo.backends.abstract.Backend, optional) – Backend to use for gate matrix. IfNone, defaults to the global backend. Defaults toNone.
- Returns:
Native gates that decompose the input gate. If the decomposition is equal to the gate only up to a global phase, its last gate is the
qibo.gates.GlobalPhasegate that corrects it.- Return type:
list[
qibo.gates.abstract.Gate]- Raises:
DecompositionError – If the gate acts on more than two qubits and is not a controlled gate, a fused gate or a unitary that can be decomposed, or if the native gates are not sufficient.