From 08db7125458389566803380103d81e8973ead0c6 Mon Sep 17 00:00:00 2001 From: simone bordoni Date: Wed, 15 Nov 2023 16:00:18 +0400 Subject: [PATCH 01/10] general idea --- src/qibo/transpiler/decompositions.py | 367 +++++++++++++++++++++ src/qibo/transpiler/pipeline.py | 14 +- src/qibo/transpiler/unroller.py | 388 +++-------------------- tests/test_transpiler_pipeline.py | 6 +- tests/test_transpiler_unroller.py | 184 ----------- tests/test_transpilers_decompositions.py | 198 ++++++++++++ 6 files changed, 627 insertions(+), 530 deletions(-) create mode 100644 src/qibo/transpiler/decompositions.py create mode 100644 tests/test_transpilers_decompositions.py diff --git a/src/qibo/transpiler/decompositions.py b/src/qibo/transpiler/decompositions.py new file mode 100644 index 0000000000..c4fea72b8c --- /dev/null +++ b/src/qibo/transpiler/decompositions.py @@ -0,0 +1,367 @@ +import numpy as np + +from qibo import gates +from qibo.backends import NumpyBackend +from qibo.transpiler.unitary_decompositions import ( + two_qubit_decomposition, + u3_decomposition, +) + +backend = NumpyBackend() + + +class GateDecompositions: + """Abstract data structure that holds decompositions of gates.""" + + def __init__(self): + self.decompositions = {} + + def add(self, gate, decomposition): + """Register a decomposition for a gate.""" + self.decompositions[gate] = decomposition + + def count_2q(self, gate): + """Count the number of two-qubit gates in the decomposition of the given gate.""" + if gate.parameters: + decomposition = self.decompositions[gate.__class__](gate) + else: + decomposition = self.decompositions[gate.__class__] + return len(tuple(g for g in decomposition if len(g.qubits) > 1)) + + def count_1q(self, gate): + """Count the number of single qubit gates in the decomposition of the given gate.""" + if gate.parameters: + decomposition = self.decompositions[gate.__class__](gate) + else: + decomposition = self.decompositions[gate.__class__] + return len(tuple(g for g in decomposition if len(g.qubits) == 1)) + + def __call__(self, gate): + """Decompose a gate.""" + decomposition = self.decompositions[gate.__class__] + if callable(decomposition): + decomposition = decomposition(gate) + return [ + g.on_qubits({i: q for i, q in enumerate(gate.qubits)}) + for g in decomposition + ] + + +gpi2_dec = GateDecompositions() +gpi2_dec.add(gates.H, [gates.U3(0, 7 * np.pi / 2, np.pi, 0)]) +gpi2_dec.add(gates.X, [gates.U3(0, np.pi, 0, np.pi)]) +gpi2_dec.add(gates.Y, [gates.U3(0, np.pi, 0, 0)]) +# apply virtually by changing ``phase`` instead of using pulses +gpi2_dec.add(gates.Z, [gates.Z(0)]) +gpi2_dec.add(gates.S, [gates.RZ(0, np.pi / 2)]) +gpi2_dec.add(gates.SDG, [gates.RZ(0, -np.pi / 2)]) +gpi2_dec.add(gates.T, [gates.RZ(0, np.pi / 4)]) +gpi2_dec.add(gates.TDG, [gates.RZ(0, -np.pi / 4)]) +gpi2_dec.add( + gates.RX, lambda gate: [gates.U3(0, gate.parameters[0], -np.pi / 2, np.pi / 2)] +) +gpi2_dec.add(gates.RY, lambda gate: [gates.U3(0, gate.parameters[0], 0, 0)]) +# apply virtually by changing ``phase`` instead of using pulses +gpi2_dec.add(gates.RZ, lambda gate: [gates.RZ(0, gate.parameters[0])]) +# apply virtually by changing ``phase`` instead of using pulses +gpi2_dec.add(gates.GPI2, lambda gate: [gates.GPI2(0, gate.parameters[0])]) +# implemented as single RX90 pulse +gpi2_dec.add(gates.U1, lambda gate: [gates.RZ(0, gate.parameters[0])]) +gpi2_dec.add( + gates.U2, + lambda gate: [gates.U3(0, np.pi / 2, gate.parameters[0], gate.parameters[1])], +) +gpi2_dec.add( + gates.U3, + lambda gate: [ + gates.U3(0, gate.parameters[0], gate.parameters[1], gate.parameters[2]) + ], +) +gpi2_dec.add( + gates.Unitary, + lambda gate: [gates.U3(0, *u3_decomposition(gate.parameters[0]))], +) +gpi2_dec.add( + gates.FusedGate, + lambda gate: [gates.U3(0, *u3_decomposition(gate.matrix(backend)))], +) + + +u3_dec = GateDecompositions() +u3_dec.add(gates.H, [gates.U3(0, 7 * np.pi / 2, np.pi, 0)]) +u3_dec.add(gates.X, [gates.U3(0, np.pi, 0, np.pi)]) +u3_dec.add(gates.Y, [gates.U3(0, np.pi, 0, 0)]) +# apply virtually by changing ``phase`` instead of using pulses +u3_dec.add(gates.Z, [gates.Z(0)]) +u3_dec.add(gates.S, [gates.RZ(0, np.pi / 2)]) +u3_dec.add(gates.SDG, [gates.RZ(0, -np.pi / 2)]) +u3_dec.add(gates.T, [gates.RZ(0, np.pi / 4)]) +u3_dec.add(gates.TDG, [gates.RZ(0, -np.pi / 4)]) +u3_dec.add( + gates.RX, lambda gate: [gates.U3(0, gate.parameters[0], -np.pi / 2, np.pi / 2)] +) +u3_dec.add(gates.RY, lambda gate: [gates.U3(0, gate.parameters[0], 0, 0)]) +# apply virtually by changing ``phase`` instead of using pulses +u3_dec.add(gates.RZ, lambda gate: [gates.RZ(0, gate.parameters[0])]) +# apply virtually by changing ``phase`` instead of using pulses +u3_dec.add(gates.GPI2, lambda gate: [gates.GPI2(0, gate.parameters[0])]) +# implemented as single RX90 pulse +u3_dec.add(gates.U1, lambda gate: [gates.RZ(0, gate.parameters[0])]) +u3_dec.add( + gates.U2, + lambda gate: [gates.U3(0, np.pi / 2, gate.parameters[0], gate.parameters[1])], +) +u3_dec.add( + gates.U3, + lambda gate: [ + gates.U3(0, gate.parameters[0], gate.parameters[1], gate.parameters[2]) + ], +) +u3_dec.add( + gates.Unitary, + lambda gate: [gates.U3(0, *u3_decomposition(gate.parameters[0]))], +) +u3_dec.add( + gates.FusedGate, + lambda gate: [gates.U3(0, *u3_decomposition(gate.matrix(backend)))], +) + +# register the iSWAP decompositions +iswap_dec = GateDecompositions() +iswap_dec.add( + gates.CNOT, + [ + gates.U3(0, 3 * np.pi / 2, np.pi, 0), + gates.U3(1, np.pi / 2, -np.pi, -np.pi), + gates.iSWAP(0, 1), + gates.U3(0, np.pi, 0, np.pi), + gates.U3(1, np.pi / 2, -np.pi, -np.pi), + gates.iSWAP(0, 1), + gates.U3(0, np.pi / 2, np.pi / 2, -np.pi), + gates.U3(1, np.pi / 2, -np.pi, -np.pi / 2), + ], +) +iswap_dec.add( + gates.CZ, + [ + gates.U3(0, 7 * np.pi / 2, np.pi, 0), + gates.U3(1, 7 * np.pi / 2, np.pi, 0), + gates.U3(1, np.pi / 2, -np.pi, -np.pi), + gates.iSWAP(0, 1), + gates.U3(0, np.pi, 0, np.pi), + gates.U3(1, np.pi / 2, -np.pi, -np.pi), + gates.iSWAP(0, 1), + gates.U3(0, np.pi / 2, np.pi / 2, -np.pi), + gates.U3(1, np.pi / 2, -np.pi, -np.pi / 2), + gates.U3(1, 7 * np.pi / 2, np.pi, 0), + ], +) +iswap_dec.add( + gates.SWAP, + [ + gates.iSWAP(0, 1), + gates.U3(1, np.pi / 2, -np.pi / 2, np.pi / 2), + gates.iSWAP(0, 1), + gates.U3(0, np.pi / 2, -np.pi / 2, np.pi / 2), + gates.iSWAP(0, 1), + gates.U3(1, np.pi / 2, -np.pi / 2, np.pi / 2), + ], +) +iswap_dec.add(gates.iSWAP, [gates.iSWAP(0, 1)]) + +# register CZ decompositions +cz_dec = GateDecompositions() +cz_dec.add(gates.CNOT, [gates.H(1), gates.CZ(0, 1), gates.H(1)]) +cz_dec.add(gates.CZ, [gates.CZ(0, 1)]) +cz_dec.add( + gates.SWAP, + [ + gates.H(1), + gates.CZ(0, 1), + gates.H(1), + gates.H(0), + gates.CZ(1, 0), + gates.H(0), + gates.H(1), + gates.CZ(0, 1), + gates.H(1), + ], +) +cz_dec.add( + gates.iSWAP, + [ + gates.U3(0, np.pi / 2.0, 0, -np.pi / 2.0), + gates.U3(1, np.pi / 2.0, 0, -np.pi / 2.0), + gates.CZ(0, 1), + gates.H(0), + gates.H(1), + gates.CZ(0, 1), + gates.H(0), + gates.H(1), + ], +) +cz_dec.add( + gates.CRX, + lambda gate: [ + gates.RX(1, gate.parameters[0] / 2.0), + gates.CZ(0, 1), + gates.RX(1, -gate.parameters[0] / 2.0), + gates.CZ(0, 1), + ], +) +cz_dec.add( + gates.CRY, + lambda gate: [ + gates.RY(1, gate.parameters[0] / 2.0), + gates.CZ(0, 1), + gates.RY(1, -gate.parameters[0] / 2.0), + gates.CZ(0, 1), + ], +) +cz_dec.add( + gates.CRZ, + lambda gate: [ + gates.RZ(1, gate.parameters[0] / 2.0), + gates.H(1), + gates.CZ(0, 1), + gates.RX(1, -gate.parameters[0] / 2.0), + gates.CZ(0, 1), + gates.H(1), + ], +) +cz_dec.add( + gates.CU1, + lambda gate: [ + gates.RZ(0, gate.parameters[0] / 2.0), + gates.H(1), + gates.CZ(0, 1), + gates.RX(1, -gate.parameters[0] / 2.0), + gates.CZ(0, 1), + gates.H(1), + gates.RZ(1, gate.parameters[0] / 2.0), + ], +) +cz_dec.add( + gates.CU2, + lambda gate: [ + gates.RZ(1, (gate.parameters[1] - gate.parameters[0]) / 2.0), + gates.H(1), + gates.CZ(0, 1), + gates.H(1), + gates.U3(1, -np.pi / 4, 0, -(gate.parameters[1] + gate.parameters[0]) / 2.0), + gates.H(1), + gates.CZ(0, 1), + gates.H(1), + gates.U3(1, np.pi / 4, gate.parameters[0], 0), + ], +) +cz_dec.add( + gates.CU3, + lambda gate: [ + gates.RZ(1, (gate.parameters[2] - gate.parameters[1]) / 2.0), + gates.H(1), + gates.CZ(0, 1), + gates.H(1), + gates.U3( + 1, + -gate.parameters[0] / 2.0, + 0, + -(gate.parameters[2] + gate.parameters[1]) / 2.0, + ), + gates.H(1), + gates.CZ(0, 1), + gates.H(1), + gates.U3(1, gate.parameters[0] / 2.0, gate.parameters[1], 0), + ], +) +cz_dec.add( + gates.FSWAP, + [ + gates.U3(0, np.pi / 2, -np.pi / 2, -np.pi), + gates.U3(1, np.pi / 2, np.pi / 2, np.pi / 2), + gates.CZ(0, 1), + gates.U3(0, np.pi / 2, 0, -np.pi / 2), + gates.U3(1, np.pi / 2, 0, np.pi / 2), + gates.CZ(0, 1), + gates.U3(0, np.pi / 2, np.pi / 2, -np.pi), + gates.U3(1, np.pi / 2, 0, -np.pi), + ], +) +cz_dec.add( + gates.RXX, + lambda gate: [ + gates.H(0), + gates.CZ(0, 1), + gates.RX(1, gate.parameters[0]), + gates.CZ(0, 1), + gates.H(0), + ], +) +cz_dec.add( + gates.RYY, + lambda gate: [ + gates.RX(0, np.pi / 2), + gates.U3(1, np.pi / 2, np.pi / 2, -np.pi), + gates.CZ(0, 1), + gates.RX(1, gate.parameters[0]), + gates.CZ(0, 1), + gates.RX(0, -np.pi / 2), + gates.U3(1, np.pi / 2, 0, np.pi / 2), + ], +) +cz_dec.add( + gates.RZZ, + lambda gate: [ + gates.H(1), + gates.CZ(0, 1), + gates.RX(1, gate.parameters[0]), + gates.CZ(0, 1), + gates.H(1), + ], +) +cz_dec.add( + gates.TOFFOLI, + [ + gates.CZ(1, 2), + gates.RX(2, -np.pi / 4), + gates.CZ(0, 2), + gates.RX(2, np.pi / 4), + gates.CZ(1, 2), + gates.RX(2, -np.pi / 4), + gates.CZ(0, 2), + gates.RX(2, np.pi / 4), + gates.RZ(1, np.pi / 4), + gates.H(1), + gates.CZ(0, 1), + gates.RZ(0, np.pi / 4), + gates.RX(1, -np.pi / 4), + gates.CZ(0, 1), + gates.H(1), + ], +) +cz_dec.add( + gates.Unitary, + lambda gate: two_qubit_decomposition(0, 1, gate.parameters[0], backend=backend), +) +cz_dec.add( + gates.fSim, + lambda gate: two_qubit_decomposition(0, 1, gate.matrix(backend), backend=backend), +) +cz_dec.add( + gates.GeneralizedfSim, + lambda gate: two_qubit_decomposition(0, 1, gate.matrix(backend), backend=backend), +) + +# register other optimized gate decompositions +opt_dec = GateDecompositions() +opt_dec.add( + gates.SWAP, + [ + gates.H(0), + gates.SDG(0), + gates.SDG(1), + gates.iSWAP(0, 1), + gates.CZ(0, 1), + gates.H(1), + ], +) diff --git a/src/qibo/transpiler/pipeline.py b/src/qibo/transpiler/pipeline.py index ecc1b8b687..a473aa061a 100644 --- a/src/qibo/transpiler/pipeline.py +++ b/src/qibo/transpiler/pipeline.py @@ -3,6 +3,7 @@ import networkx as nx import numpy as np +from qibo import gates from qibo.backends import NumpyBackend from qibo.config import raise_error from qibo.models import Circuit @@ -97,7 +98,8 @@ def assert_transpiling( connectivity: nx.Graph, initial_layout: dict, final_layout: dict, - native_gates: NativeType = NativeType.CZ, + two_qubit_natives: NativeType = NativeType.CZ, + single_qubit_natives: tuple = (gates.I, gates.Z, gates.RZ, gates.U3), check_circuit_equivalence=True, ): """Check that all transpiler passes have been executed correctly. @@ -108,10 +110,16 @@ def assert_transpiling( connectivity (networkx.Graph): chip qubits connectivity. initial_layout (dict): initial physical-logical qubit mapping. final_layout (dict): final physical-logical qubit mapping. - native_gates: (NativeType): native gates supported by the hardware. + two_qubit_natives (NativeType): two qubit native gates supported by the hardware. + single_qubit_natives (tuple): single qubit native gates supported by the hardware. + check_circuit_equivalence (Bool): use simulations to check if the transpiled circuit is the same as the original. """ assert_connectivity(circuit=transpiled_circuit, connectivity=connectivity) - assert_decomposition(circuit=transpiled_circuit, two_qubit_natives=native_gates) + assert_decomposition( + circuit=transpiled_circuit, + two_qubit_natives=two_qubit_natives, + single_qubit_natives=single_qubit_natives, + ) if original_circuit.nqubits != transpiled_circuit.nqubits: qubit_matcher = Preprocessing(connectivity=connectivity) original_circuit = qubit_matcher(circuit=original_circuit) diff --git a/src/qibo/transpiler/unroller.py b/src/qibo/transpiler/unroller.py index 74f42ace99..03ec73dcfd 100644 --- a/src/qibo/transpiler/unroller.py +++ b/src/qibo/transpiler/unroller.py @@ -1,17 +1,9 @@ -import numpy as np - from qibo import gates -from qibo.backends import NumpyBackend from qibo.config import raise_error from qibo.models import Circuit from qibo.transpiler.abstract import NativeType, Unroller +from qibo.transpiler.decompositions import cz_dec, gpi2_dec, iswap_dec, opt_dec, u3_dec from qibo.transpiler.exceptions import DecompositionError -from qibo.transpiler.unitary_decompositions import ( - two_qubit_decomposition, - u3_decomposition, -) - -backend = NumpyBackend() # TODO: Make setting single-qubit native gates more flexible @@ -24,6 +16,7 @@ class NativeGates(Unroller): single_qubit_natives (tuple): single qubit native gates. two_qubit_natives (:class:`qibo.transpiler.abstract.NativeType`): two-qubit native gates supported by the quantum hardware. + single_qubit_natives (tuple): single qubit native gates. Returns: (:class:`qibo.models.circuit.Circuit`): equivalent circuit with native gates. @@ -32,7 +25,7 @@ class NativeGates(Unroller): def __init__( self, two_qubit_natives: NativeType, - single_qubit_natives=(gates.I, gates.Z, gates.RZ, gates.U3), + single_qubit_natives=(gates.I, gates.Z, gates.RZ, gates.GPI2), translate_single_qubit: bool = True, ): self.two_qubit_natives = two_qubit_natives @@ -45,30 +38,53 @@ def __call__(self, circuit: Circuit): for gate in circuit.queue: if len(gate.qubits) > 1 or self.translate_single_qubit: two_qubit_translated_circuit.add( - translate_gate(gate, self.two_qubit_natives) + translate_gate( + gate, + self.two_qubit_natives, + single_qubit_natives=self.single_qubit_natives, + ) ) else: two_qubit_translated_circuit.add(gate) if self.translate_single_qubit: for gate in two_qubit_translated_circuit.queue: if len(gate.qubits) == 1: - translated_circuit.add(translate_gate(gate, self.two_qubit_natives)) + translated_circuit.add( + translate_gate( + gate, + self.two_qubit_natives, + single_qubit_natives=self.single_qubit_natives, + ) + ) else: translated_circuit.add(gate) else: translated_circuit = two_qubit_translated_circuit return translated_circuit + def is_satisfied(self, circuit: Circuit): + """Return True if a circuit is correctly decomposed into native gates, otherwise False.""" + try: + assert_decomposition( + circuit, self.two_qubit_natives, self.single_qubit_natives + ) + except DecompositionError: + return False + return True + def assert_decomposition( circuit: Circuit, two_qubit_natives: NativeType, - single_qubit_natives=(gates.I, gates.Z, gates.RZ, gates.U3), + single_qubit_natives=(gates.I, gates.Z, gates.RZ, gates.GPI2), ): """Checks if a circuit has been correctly decmposed into native gates. Args: circuit (:class:`qibo.models.circuit.Circuit`): circuit model to check. + two_qubit_natives (:class:`qibo.transpiler.abstract.NativeType`): + two-qubit native gates supported by the quantum hardware. + single_qubit_natives (tuple): single qubit native gates. """ for gate in circuit.queue: if isinstance(gate, gates.M): @@ -85,11 +101,11 @@ def assert_decomposition( if not (native_type_gate in two_qubit_natives): raise_error( DecompositionError, - f"{gate.name} is not a two qubit native gate.", + f"{gate.name} is not a two qubits native gate.", ) except ValueError: raise_error( - DecompositionError, f"{gate.name} is not a two qubit native gate." + DecompositionError, f"{gate.name} is not a two qubits native gate." ) else: raise_error( @@ -97,13 +113,18 @@ def assert_decomposition( ) -def translate_gate(gate, native_gates: NativeType): +def translate_gate( + gate, + two_qubit_natives: NativeType, + single_qubit_natives=(gates.I, gates.Z, gates.RZ, gates.GPI2), +): """Maps gates to a hardware-native implementation. Args: gate (:class:`qibo.gates.abstract.Gate`): gate to be decomposed. - native_gates (:class:`qibo.transpiler.abstract.NativeType`): + two_qubit_natives (:class:`qibo.transpiler.abstract.NativeType`): two-qubit native gates supported by the quantum hardware. + single_qubit_natives (tuple): single qubit native gates. Returns: (list): List of native gates @@ -112,9 +133,14 @@ def translate_gate(gate, native_gates: NativeType): return gate if len(gate.qubits) == 1: - return onequbit_dec(gate) + if gates.U3 in single_qubit_natives: + return u3_dec(gate) + if gates.GPI2 in single_qubit_natives: + return gpi2_dec(gate) + else: + raise DecompositionError("Use U3 or GPI2 as single qubit native gates") - if native_gates is NativeType.CZ | NativeType.iSWAP: + if two_qubit_natives is NativeType.CZ | NativeType.iSWAP: # Check for a special optimized decomposition. if gate.__class__ in opt_dec.decompositions: return opt_dec(gate) @@ -133,9 +159,9 @@ def translate_gate(gate, native_gates: NativeType): return cz_dec(gate) else: # pragma: no cover return iswap_dec(gate) - elif native_gates is NativeType.CZ: + elif two_qubit_natives is NativeType.CZ: return cz_dec(gate) - elif native_gates is NativeType.iSWAP: + elif two_qubit_natives is NativeType.iSWAP: if gate.__class__ in iswap_dec.decompositions: return iswap_dec(gate) else: @@ -149,322 +175,4 @@ def translate_gate(gate, native_gates: NativeType): iswap_decomposed.append(g_translated) return iswap_decomposed else: # pragma: no cover - raise_error(NotImplementedError, "Use only CZ and/or iSWAP as native gates") - - -class GateDecompositions: - """Abstract data structure that holds decompositions of gates.""" - - def __init__(self): - self.decompositions = {} - - def add(self, gate, decomposition): - """Register a decomposition for a gate.""" - self.decompositions[gate] = decomposition - - def count_2q(self, gate): - """Count the number of two-qubit gates in the decomposition of the given gate.""" - if gate.parameters: - decomposition = self.decompositions[gate.__class__](gate) - else: - decomposition = self.decompositions[gate.__class__] - return len(tuple(g for g in decomposition if len(g.qubits) > 1)) - - def count_1q(self, gate): - """Count the number of single qubit gates in the decomposition of the given gate.""" - if gate.parameters: - decomposition = self.decompositions[gate.__class__](gate) - else: - decomposition = self.decompositions[gate.__class__] - return len(tuple(g for g in decomposition if len(g.qubits) == 1)) - - def __call__(self, gate): - """Decompose a gate.""" - decomposition = self.decompositions[gate.__class__] - if callable(decomposition): - decomposition = decomposition(gate) - return [ - g.on_qubits({i: q for i, q in enumerate(gate.qubits)}) - for g in decomposition - ] - - -onequbit_dec = GateDecompositions() -onequbit_dec.add(gates.H, [gates.U3(0, 7 * np.pi / 2, np.pi, 0)]) -onequbit_dec.add(gates.X, [gates.U3(0, np.pi, 0, np.pi)]) -onequbit_dec.add(gates.Y, [gates.U3(0, np.pi, 0, 0)]) -# apply virtually by changing ``phase`` instead of using pulses -onequbit_dec.add(gates.Z, [gates.Z(0)]) -onequbit_dec.add(gates.S, [gates.RZ(0, np.pi / 2)]) -onequbit_dec.add(gates.SDG, [gates.RZ(0, -np.pi / 2)]) -onequbit_dec.add(gates.T, [gates.RZ(0, np.pi / 4)]) -onequbit_dec.add(gates.TDG, [gates.RZ(0, -np.pi / 4)]) -onequbit_dec.add( - gates.RX, lambda gate: [gates.U3(0, gate.parameters[0], -np.pi / 2, np.pi / 2)] -) -onequbit_dec.add(gates.RY, lambda gate: [gates.U3(0, gate.parameters[0], 0, 0)]) -# apply virtually by changing ``phase`` instead of using pulses -onequbit_dec.add(gates.RZ, lambda gate: [gates.RZ(0, gate.parameters[0])]) -# apply virtually by changing ``phase`` instead of using pulses -onequbit_dec.add(gates.GPI2, lambda gate: [gates.GPI2(0, gate.parameters[0])]) -# implemented as single RX90 pulse -onequbit_dec.add(gates.U1, lambda gate: [gates.RZ(0, gate.parameters[0])]) -onequbit_dec.add( - gates.U2, - lambda gate: [gates.U3(0, np.pi / 2, gate.parameters[0], gate.parameters[1])], -) -onequbit_dec.add( - gates.U3, - lambda gate: [ - gates.U3(0, gate.parameters[0], gate.parameters[1], gate.parameters[2]) - ], -) -onequbit_dec.add( - gates.Unitary, - lambda gate: [gates.U3(0, *u3_decomposition(gate.parameters[0]))], -) -onequbit_dec.add( - gates.FusedGate, - lambda gate: [gates.U3(0, *u3_decomposition(gate.matrix(backend)))], -) - -# register the iSWAP decompositions -iswap_dec = GateDecompositions() -iswap_dec.add( - gates.CNOT, - [ - gates.U3(0, 3 * np.pi / 2, np.pi, 0), - gates.U3(1, np.pi / 2, -np.pi, -np.pi), - gates.iSWAP(0, 1), - gates.U3(0, np.pi, 0, np.pi), - gates.U3(1, np.pi / 2, -np.pi, -np.pi), - gates.iSWAP(0, 1), - gates.U3(0, np.pi / 2, np.pi / 2, -np.pi), - gates.U3(1, np.pi / 2, -np.pi, -np.pi / 2), - ], -) -iswap_dec.add( - gates.CZ, - [ - gates.U3(0, 7 * np.pi / 2, np.pi, 0), - gates.U3(1, 7 * np.pi / 2, np.pi, 0), - gates.U3(1, np.pi / 2, -np.pi, -np.pi), - gates.iSWAP(0, 1), - gates.U3(0, np.pi, 0, np.pi), - gates.U3(1, np.pi / 2, -np.pi, -np.pi), - gates.iSWAP(0, 1), - gates.U3(0, np.pi / 2, np.pi / 2, -np.pi), - gates.U3(1, np.pi / 2, -np.pi, -np.pi / 2), - gates.U3(1, 7 * np.pi / 2, np.pi, 0), - ], -) -iswap_dec.add( - gates.SWAP, - [ - gates.iSWAP(0, 1), - gates.U3(1, np.pi / 2, -np.pi / 2, np.pi / 2), - gates.iSWAP(0, 1), - gates.U3(0, np.pi / 2, -np.pi / 2, np.pi / 2), - gates.iSWAP(0, 1), - gates.U3(1, np.pi / 2, -np.pi / 2, np.pi / 2), - ], -) -iswap_dec.add(gates.iSWAP, [gates.iSWAP(0, 1)]) - -# register CZ decompositions -cz_dec = GateDecompositions() -cz_dec.add(gates.CNOT, [gates.H(1), gates.CZ(0, 1), gates.H(1)]) -cz_dec.add(gates.CZ, [gates.CZ(0, 1)]) -cz_dec.add( - gates.SWAP, - [ - gates.H(1), - gates.CZ(0, 1), - gates.H(1), - gates.H(0), - gates.CZ(1, 0), - gates.H(0), - gates.H(1), - gates.CZ(0, 1), - gates.H(1), - ], -) -cz_dec.add( - gates.iSWAP, - [ - gates.U3(0, np.pi / 2.0, 0, -np.pi / 2.0), - gates.U3(1, np.pi / 2.0, 0, -np.pi / 2.0), - gates.CZ(0, 1), - gates.H(0), - gates.H(1), - gates.CZ(0, 1), - gates.H(0), - gates.H(1), - ], -) -cz_dec.add( - gates.CRX, - lambda gate: [ - gates.RX(1, gate.parameters[0] / 2.0), - gates.CZ(0, 1), - gates.RX(1, -gate.parameters[0] / 2.0), - gates.CZ(0, 1), - ], -) -cz_dec.add( - gates.CRY, - lambda gate: [ - gates.RY(1, gate.parameters[0] / 2.0), - gates.CZ(0, 1), - gates.RY(1, -gate.parameters[0] / 2.0), - gates.CZ(0, 1), - ], -) -cz_dec.add( - gates.CRZ, - lambda gate: [ - gates.RZ(1, gate.parameters[0] / 2.0), - gates.H(1), - gates.CZ(0, 1), - gates.RX(1, -gate.parameters[0] / 2.0), - gates.CZ(0, 1), - gates.H(1), - ], -) -cz_dec.add( - gates.CU1, - lambda gate: [ - gates.RZ(0, gate.parameters[0] / 2.0), - gates.H(1), - gates.CZ(0, 1), - gates.RX(1, -gate.parameters[0] / 2.0), - gates.CZ(0, 1), - gates.H(1), - gates.RZ(1, gate.parameters[0] / 2.0), - ], -) -cz_dec.add( - gates.CU2, - lambda gate: [ - gates.RZ(1, (gate.parameters[1] - gate.parameters[0]) / 2.0), - gates.H(1), - gates.CZ(0, 1), - gates.H(1), - gates.U3(1, -np.pi / 4, 0, -(gate.parameters[1] + gate.parameters[0]) / 2.0), - gates.H(1), - gates.CZ(0, 1), - gates.H(1), - gates.U3(1, np.pi / 4, gate.parameters[0], 0), - ], -) -cz_dec.add( - gates.CU3, - lambda gate: [ - gates.RZ(1, (gate.parameters[2] - gate.parameters[1]) / 2.0), - gates.H(1), - gates.CZ(0, 1), - gates.H(1), - gates.U3( - 1, - -gate.parameters[0] / 2.0, - 0, - -(gate.parameters[2] + gate.parameters[1]) / 2.0, - ), - gates.H(1), - gates.CZ(0, 1), - gates.H(1), - gates.U3(1, gate.parameters[0] / 2.0, gate.parameters[1], 0), - ], -) -cz_dec.add( - gates.FSWAP, - [ - gates.U3(0, np.pi / 2, -np.pi / 2, -np.pi), - gates.U3(1, np.pi / 2, np.pi / 2, np.pi / 2), - gates.CZ(0, 1), - gates.U3(0, np.pi / 2, 0, -np.pi / 2), - gates.U3(1, np.pi / 2, 0, np.pi / 2), - gates.CZ(0, 1), - gates.U3(0, np.pi / 2, np.pi / 2, -np.pi), - gates.U3(1, np.pi / 2, 0, -np.pi), - ], -) -cz_dec.add( - gates.RXX, - lambda gate: [ - gates.H(0), - gates.CZ(0, 1), - gates.RX(1, gate.parameters[0]), - gates.CZ(0, 1), - gates.H(0), - ], -) -cz_dec.add( - gates.RYY, - lambda gate: [ - gates.RX(0, np.pi / 2), - gates.U3(1, np.pi / 2, np.pi / 2, -np.pi), - gates.CZ(0, 1), - gates.RX(1, gate.parameters[0]), - gates.CZ(0, 1), - gates.RX(0, -np.pi / 2), - gates.U3(1, np.pi / 2, 0, np.pi / 2), - ], -) -cz_dec.add( - gates.RZZ, - lambda gate: [ - gates.H(1), - gates.CZ(0, 1), - gates.RX(1, gate.parameters[0]), - gates.CZ(0, 1), - gates.H(1), - ], -) -cz_dec.add( - gates.TOFFOLI, - [ - gates.CZ(1, 2), - gates.RX(2, -np.pi / 4), - gates.CZ(0, 2), - gates.RX(2, np.pi / 4), - gates.CZ(1, 2), - gates.RX(2, -np.pi / 4), - gates.CZ(0, 2), - gates.RX(2, np.pi / 4), - gates.RZ(1, np.pi / 4), - gates.H(1), - gates.CZ(0, 1), - gates.RZ(0, np.pi / 4), - gates.RX(1, -np.pi / 4), - gates.CZ(0, 1), - gates.H(1), - ], -) -cz_dec.add( - gates.Unitary, - lambda gate: two_qubit_decomposition(0, 1, gate.parameters[0], backend=backend), -) -cz_dec.add( - gates.fSim, - lambda gate: two_qubit_decomposition(0, 1, gate.matrix(backend), backend=backend), -) -cz_dec.add( - gates.GeneralizedfSim, - lambda gate: two_qubit_decomposition(0, 1, gate.matrix(backend), backend=backend), -) - - -# register other optimized gate decompositions -opt_dec = GateDecompositions() -opt_dec.add( - gates.SWAP, - [ - gates.H(0), - gates.SDG(0), - gates.SDG(1), - gates.iSWAP(0, 1), - gates.CZ(0, 1), - gates.H(1), - ], -) + raise_error(DecompositionError, "Use only CZ and/or iSWAP as native gates") diff --git a/tests/test_transpiler_pipeline.py b/tests/test_transpiler_pipeline.py index f887d4b964..685959eec4 100644 --- a/tests/test_transpiler_pipeline.py +++ b/tests/test_transpiler_pipeline.py @@ -84,7 +84,7 @@ def test_pipeline_default(ngates): connectivity=star_connectivity(), initial_layout=initial_layout, final_layout=final_layout, - native_gates=NativeType.CZ, + two_qubit_natives=NativeType.CZ, check_circuit_equivalence=False, ) @@ -178,7 +178,7 @@ def test_custom_passes(circ): connectivity=star_connectivity(), initial_layout=initial_layout, final_layout=final_layout, - native_gates=NativeType.iSWAP, + two_qubit_natives=NativeType.iSWAP, ) @@ -208,7 +208,7 @@ def test_custom_passes_reverse(circ): connectivity=star_connectivity(), initial_layout=initial_layout, final_layout=final_layout, - native_gates=NativeType.iSWAP, + two_qubit_natives=NativeType.iSWAP, ) diff --git a/tests/test_transpiler_unroller.py b/tests/test_transpiler_unroller.py index 15afe7aed8..4b2a3c16a5 100644 --- a/tests/test_transpiler_unroller.py +++ b/tests/test_transpiler_unroller.py @@ -1,199 +1,15 @@ -import numpy as np import pytest from qibo import gates -from qibo.backends import NumpyBackend from qibo.models import Circuit -from qibo.quantum_info.random_ensembles import random_unitary from qibo.transpiler.abstract import NativeType from qibo.transpiler.unroller import ( DecompositionError, NativeGates, assert_decomposition, - translate_gate, ) -def assert_matrices_allclose(gate, two_qubit_natives, backend): - target_matrix = gate.matrix(backend) - target_matrix = backend.cast(target_matrix, dtype=target_matrix.dtype) - # Remove global phase from target matrix - normalisation = np.power( - np.linalg.det(target_matrix), 1 / float(target_matrix.shape[0]), dtype=complex - ) - normalisation = backend.cast(normalisation, dtype=normalisation.dtype) - target_unitary = target_matrix / normalisation - - circuit = Circuit(len(gate.qubits)) - circuit.add(translate_gate(gate, two_qubit_natives)) - native_matrix = circuit.unitary(backend) - # Remove global phase from native matrix - normalisation = np.power( - np.linalg.det(native_matrix), 1 / float(native_matrix.shape[0]), dtype=complex - ) - normalisation = backend.cast(normalisation, dtype=normalisation.dtype) - native_unitary = native_matrix / normalisation - - # There can still be phase differences of -1, -1j, 1j - c = 0 - for phase in [1, -1, 1j, -1j]: - if np.allclose(phase * native_unitary, target_unitary, atol=1e-12): - c = 1 - backend.assert_allclose(c, 1) - - -@pytest.mark.parametrize("gatename", ["H", "X", "Y", "I"]) -def test_pauli_to_native(backend, gatename): - gate = getattr(gates, gatename)(0) - assert_matrices_allclose(gate, two_qubit_natives=NativeType.CZ, backend=backend) - - -@pytest.mark.parametrize("gatename", ["RX", "RY", "RZ"]) -def test_rotations_to_native(backend, gatename): - gate = getattr(gates, gatename)(0, theta=0.1) - assert_matrices_allclose(gate, two_qubit_natives=NativeType.CZ, backend=backend) - - -@pytest.mark.parametrize("gatename", ["S", "SDG", "T", "TDG"]) -def test_special_single_qubit_to_native(backend, gatename): - gate = getattr(gates, gatename)(0) - assert_matrices_allclose(gate, two_qubit_natives=NativeType.CZ, backend=backend) - - -def test_u1_to_native(backend): - gate = gates.U1(0, theta=0.5) - assert_matrices_allclose(gate, two_qubit_natives=NativeType.CZ, backend=backend) - - -def test_u2_to_native(backend): - gate = gates.U2(0, phi=0.1, lam=0.3) - assert_matrices_allclose(gate, two_qubit_natives=NativeType.CZ, backend=backend) - - -def test_u3_to_native(backend): - gate = gates.U3(0, theta=0.2, phi=0.1, lam=0.3) - assert_matrices_allclose(gate, two_qubit_natives=NativeType.CZ, backend=backend) - - -def test_gpi2_to_native(backend): - gate = gates.GPI2(0, phi=0.123) - assert_matrices_allclose(gate, two_qubit_natives=NativeType.CZ, backend=backend) - - -@pytest.mark.parametrize("gatename", ["CNOT", "CZ", "SWAP", "iSWAP", "FSWAP"]) -@pytest.mark.parametrize( - "natives", - [NativeType.CZ, NativeType.iSWAP, NativeType.CZ | NativeType.iSWAP], -) -def test_two_qubit_to_native(backend, gatename, natives): - gate = getattr(gates, gatename)(0, 1) - assert_matrices_allclose(gate, natives, backend) - - -@pytest.mark.parametrize( - "natives", - [NativeType.CZ, NativeType.iSWAP, NativeType.CZ | NativeType.iSWAP], -) -@pytest.mark.parametrize("gatename", ["CRX", "CRY", "CRZ"]) -def test_controlled_rotations_to_native(backend, gatename, natives): - gate = getattr(gates, gatename)(0, 1, 0.3) - assert_matrices_allclose(gate, natives, backend) - - -@pytest.mark.parametrize( - "natives", - [NativeType.CZ, NativeType.iSWAP, NativeType.CZ | NativeType.iSWAP], -) -def test_cu1_to_native(backend, natives): - gate = gates.CU1(0, 1, theta=0.4) - assert_matrices_allclose(gate, natives, backend) - - -@pytest.mark.parametrize( - "natives", - [NativeType.CZ, NativeType.iSWAP, NativeType.CZ | NativeType.iSWAP], -) -def test_cu2_to_native(backend, natives): - gate = gates.CU2(0, 1, phi=0.2, lam=0.3) - assert_matrices_allclose(gate, natives, backend) - - -@pytest.mark.parametrize( - "natives", - [NativeType.CZ, NativeType.iSWAP, NativeType.CZ | NativeType.iSWAP], -) -def test_cu3_to_native(backend, natives): - gate = gates.CU3(0, 1, theta=0.2, phi=0.3, lam=0.4) - assert_matrices_allclose(gate, natives, backend) - - -@pytest.mark.parametrize( - "natives", - [NativeType.CZ, NativeType.iSWAP, NativeType.CZ | NativeType.iSWAP], -) -def test_fSim_to_native(backend, natives): - gate = gates.fSim(0, 1, theta=0.3, phi=0.1) - assert_matrices_allclose(gate, natives, backend) - - -@pytest.mark.parametrize("seed", [None, 10, np.random.default_rng(10)]) -@pytest.mark.parametrize( - "natives", - [NativeType.CZ, NativeType.iSWAP, NativeType.CZ | NativeType.iSWAP], -) -def test_GeneralizedfSim_to_native(backend, natives, seed): - unitary = random_unitary(2, seed=seed, backend=backend) - gate = gates.GeneralizedfSim(0, 1, unitary, phi=0.1) - assert_matrices_allclose(gate, natives, backend) - - -@pytest.mark.parametrize( - "natives", - [NativeType.CZ, NativeType.iSWAP, NativeType.CZ | NativeType.iSWAP], -) -@pytest.mark.parametrize("gatename", ["RXX", "RZZ", "RYY"]) -def test_rnn_to_native(backend, gatename, natives): - gate = getattr(gates, gatename)(0, 1, theta=0.1) - assert_matrices_allclose(gate, natives, backend) - - -@pytest.mark.parametrize( - "natives", - [NativeType.CZ, NativeType.iSWAP, NativeType.CZ | NativeType.iSWAP], -) -def test_TOFFOLI_to_native(backend, natives): - gate = gates.TOFFOLI(0, 1, 2) - assert_matrices_allclose(gate, natives, backend) - - -@pytest.mark.parametrize("seed", [None, 10, np.random.default_rng(10)]) -@pytest.mark.parametrize( - "natives", - [NativeType.CZ, NativeType.iSWAP, NativeType.CZ | NativeType.iSWAP], -) -@pytest.mark.parametrize("nqubits", [1, 2]) -def test_unitary_to_native(backend, nqubits, natives, seed): - u = random_unitary(2**nqubits, seed=seed, backend=NumpyBackend()) - # transform to SU(2^nqubits) form - u = u / np.sqrt(np.linalg.det(u)) - gate = gates.Unitary(u, *range(nqubits)) - assert_matrices_allclose(gate, natives, backend) - - -def test_count_1q(): - from qibo.transpiler.unroller import cz_dec - - np.testing.assert_allclose(cz_dec.count_1q(gates.CNOT(0, 1)), 2) - np.testing.assert_allclose(cz_dec.count_1q(gates.CRX(0, 1, 0.1)), 2) - - -def test_count_2q(): - from qibo.transpiler.unroller import cz_dec - - np.testing.assert_allclose(cz_dec.count_2q(gates.CNOT(0, 1)), 1) - np.testing.assert_allclose(cz_dec.count_2q(gates.CRX(0, 1, 0.1)), 2) - - def test_assert_decomposition(): circuit = Circuit(2) circuit.add(gates.CZ(0, 1)) diff --git a/tests/test_transpilers_decompositions.py b/tests/test_transpilers_decompositions.py new file mode 100644 index 0000000000..682d317eb9 --- /dev/null +++ b/tests/test_transpilers_decompositions.py @@ -0,0 +1,198 @@ +import numpy as np +import pytest + +from qibo import gates +from qibo.backends import NumpyBackend +from qibo.models import Circuit +from qibo.quantum_info.random_ensembles import random_unitary +from qibo.transpiler.abstract import NativeType +from qibo.transpiler.unroller import translate_gate + + +def assert_matrices_allclose( + gate, + two_qubit_natives, + backend, + single_qubit_natives=(gates.I, gates.Z, gates.RZ, gates.U3), +): + target_matrix = gate.matrix(backend) + target_matrix = backend.cast(target_matrix, dtype=target_matrix.dtype) + # Remove global phase from target matrix + normalisation = np.power( + np.linalg.det(target_matrix), 1 / float(target_matrix.shape[0]), dtype=complex + ) + normalisation = backend.cast(normalisation, dtype=normalisation.dtype) + target_unitary = target_matrix / normalisation + + circuit = Circuit(len(gate.qubits)) + circuit.add( + translate_gate( + gate, two_qubit_natives, single_qubit_natives=single_qubit_natives + ) + ) + native_matrix = circuit.unitary(backend) + # Remove global phase from native matrix + normalisation = np.power( + np.linalg.det(native_matrix), 1 / float(native_matrix.shape[0]), dtype=complex + ) + normalisation = backend.cast(normalisation, dtype=normalisation.dtype) + native_unitary = native_matrix / normalisation + + # There can still be phase differences of -1, -1j, 1j + c = 0 + for phase in [1, -1, 1j, -1j]: + if np.allclose(phase * native_unitary, target_unitary, atol=1e-12): + c = 1 + backend.assert_allclose(c, 1) + + +@pytest.mark.parametrize("gatename", ["H", "X", "Y", "I"]) +def test_pauli_to_native(backend, gatename): + gate = getattr(gates, gatename)(0) + assert_matrices_allclose(gate, two_qubit_natives=NativeType.CZ, backend=backend) + + +@pytest.mark.parametrize("gatename", ["RX", "RY", "RZ"]) +def test_rotations_to_native(backend, gatename): + gate = getattr(gates, gatename)(0, theta=0.1) + assert_matrices_allclose(gate, two_qubit_natives=NativeType.CZ, backend=backend) + + +@pytest.mark.parametrize("gatename", ["S", "SDG", "T", "TDG"]) +def test_special_single_qubit_to_native(backend, gatename): + gate = getattr(gates, gatename)(0) + assert_matrices_allclose(gate, two_qubit_natives=NativeType.CZ, backend=backend) + + +def test_u1_to_native(backend): + gate = gates.U1(0, theta=0.5) + assert_matrices_allclose(gate, two_qubit_natives=NativeType.CZ, backend=backend) + + +def test_u2_to_native(backend): + gate = gates.U2(0, phi=0.1, lam=0.3) + assert_matrices_allclose(gate, two_qubit_natives=NativeType.CZ, backend=backend) + + +def test_u3_to_native(backend): + gate = gates.U3(0, theta=0.2, phi=0.1, lam=0.3) + assert_matrices_allclose(gate, two_qubit_natives=NativeType.CZ, backend=backend) + + +def test_gpi2_to_native(backend): + gate = gates.GPI2(0, phi=0.123) + assert_matrices_allclose(gate, two_qubit_natives=NativeType.CZ, backend=backend) + + +@pytest.mark.parametrize("gatename", ["CNOT", "CZ", "SWAP", "iSWAP", "FSWAP"]) +@pytest.mark.parametrize( + "natives", + [NativeType.CZ, NativeType.iSWAP, NativeType.CZ | NativeType.iSWAP], +) +def test_two_qubit_to_native(backend, gatename, natives): + gate = getattr(gates, gatename)(0, 1) + assert_matrices_allclose(gate, natives, backend) + + +@pytest.mark.parametrize( + "natives", + [NativeType.CZ, NativeType.iSWAP, NativeType.CZ | NativeType.iSWAP], +) +@pytest.mark.parametrize("gatename", ["CRX", "CRY", "CRZ"]) +def test_controlled_rotations_to_native(backend, gatename, natives): + gate = getattr(gates, gatename)(0, 1, 0.3) + assert_matrices_allclose(gate, natives, backend) + + +@pytest.mark.parametrize( + "natives", + [NativeType.CZ, NativeType.iSWAP, NativeType.CZ | NativeType.iSWAP], +) +def test_cu1_to_native(backend, natives): + gate = gates.CU1(0, 1, theta=0.4) + assert_matrices_allclose(gate, natives, backend) + + +@pytest.mark.parametrize( + "natives", + [NativeType.CZ, NativeType.iSWAP, NativeType.CZ | NativeType.iSWAP], +) +def test_cu2_to_native(backend, natives): + gate = gates.CU2(0, 1, phi=0.2, lam=0.3) + assert_matrices_allclose(gate, natives, backend) + + +@pytest.mark.parametrize( + "natives", + [NativeType.CZ, NativeType.iSWAP, NativeType.CZ | NativeType.iSWAP], +) +def test_cu3_to_native(backend, natives): + gate = gates.CU3(0, 1, theta=0.2, phi=0.3, lam=0.4) + assert_matrices_allclose(gate, natives, backend) + + +@pytest.mark.parametrize( + "natives", + [NativeType.CZ, NativeType.iSWAP, NativeType.CZ | NativeType.iSWAP], +) +def test_fSim_to_native(backend, natives): + gate = gates.fSim(0, 1, theta=0.3, phi=0.1) + assert_matrices_allclose(gate, natives, backend) + + +@pytest.mark.parametrize("seed", [None, 10, np.random.default_rng(10)]) +@pytest.mark.parametrize( + "natives", + [NativeType.CZ, NativeType.iSWAP, NativeType.CZ | NativeType.iSWAP], +) +def test_GeneralizedfSim_to_native(backend, natives, seed): + unitary = random_unitary(2, seed=seed, backend=backend) + gate = gates.GeneralizedfSim(0, 1, unitary, phi=0.1) + assert_matrices_allclose(gate, natives, backend) + + +@pytest.mark.parametrize( + "natives", + [NativeType.CZ, NativeType.iSWAP, NativeType.CZ | NativeType.iSWAP], +) +@pytest.mark.parametrize("gatename", ["RXX", "RZZ", "RYY"]) +def test_rnn_to_native(backend, gatename, natives): + gate = getattr(gates, gatename)(0, 1, theta=0.1) + assert_matrices_allclose(gate, natives, backend) + + +@pytest.mark.parametrize( + "natives", + [NativeType.CZ, NativeType.iSWAP, NativeType.CZ | NativeType.iSWAP], +) +def test_TOFFOLI_to_native(backend, natives): + gate = gates.TOFFOLI(0, 1, 2) + assert_matrices_allclose(gate, natives, backend) + + +@pytest.mark.parametrize("seed", [None, 10, np.random.default_rng(10)]) +@pytest.mark.parametrize( + "natives", + [NativeType.CZ, NativeType.iSWAP, NativeType.CZ | NativeType.iSWAP], +) +@pytest.mark.parametrize("nqubits", [1, 2]) +def test_unitary_to_native(backend, nqubits, natives, seed): + u = random_unitary(2**nqubits, seed=seed, backend=NumpyBackend()) + # transform to SU(2^nqubits) form + u = u / np.sqrt(np.linalg.det(u)) + gate = gates.Unitary(u, *range(nqubits)) + assert_matrices_allclose(gate, natives, backend) + + +def test_count_1q(): + from qibo.transpiler.unroller import cz_dec + + np.testing.assert_allclose(cz_dec.count_1q(gates.CNOT(0, 1)), 2) + np.testing.assert_allclose(cz_dec.count_1q(gates.CRX(0, 1, 0.1)), 2) + + +def test_count_2q(): + from qibo.transpiler.unroller import cz_dec + + np.testing.assert_allclose(cz_dec.count_2q(gates.CNOT(0, 1)), 1) + np.testing.assert_allclose(cz_dec.count_2q(gates.CRX(0, 1, 0.1)), 2) From 25f90f79ad45f95f4956fdd3b1bbef6671979df9 Mon Sep 17 00:00:00 2001 From: simone bordoni Date: Fri, 17 Nov 2023 13:25:12 +0400 Subject: [PATCH 02/10] split translate gate function --- src/qibo/transpiler/unroller.py | 48 ++++++++++++++++++++++++++------- 1 file changed, 39 insertions(+), 9 deletions(-) diff --git a/src/qibo/transpiler/unroller.py b/src/qibo/transpiler/unroller.py index 03ec73dcfd..7c7b5c6b97 100644 --- a/src/qibo/transpiler/unroller.py +++ b/src/qibo/transpiler/unroller.py @@ -78,7 +78,7 @@ def assert_decomposition( two_qubit_natives: NativeType, single_qubit_natives=(gates.I, gates.Z, gates.RZ, gates.GPI2), ): - """Checks if a circuit has been correctly decmposed into native gates. + """Checks if a circuit has been correctly decomposed into native gates. Args: circuit (:class:`qibo.models.circuit.Circuit`): circuit model to check. @@ -127,19 +127,49 @@ def translate_gate( single_qubit_natives (tuple): single qubit native gates. Returns: - (list): List of native gates + (list): List of native gates that decompose the input gate. """ if isinstance(gate, (gates.M, gates.I, gates.Align)): return gate + elif len(gate.qubits) == 1: + return _translate_single_qubit_gates(gate, single_qubit_natives) + else: + return _translate_two_qubit_gates(gate, two_qubit_natives) - if len(gate.qubits) == 1: - if gates.U3 in single_qubit_natives: - return u3_dec(gate) - if gates.GPI2 in single_qubit_natives: - return gpi2_dec(gate) - else: - raise DecompositionError("Use U3 or GPI2 as single qubit native gates") +def _translate_single_qubit_gates(gate: gates.Gate, single_qubit_natives): + """Helper method for :meth:`translate_gate`. + + Maps single qubit gates to a hardware-native implementation. + + Args: + gate (:class:`qibo.gates.abstract.Gate`): gate to be decomposed. + single_qubit_natives (tuple): single qubit native gates. + + Returns: + (list): List of native gates that decompose the input gate. + """ + if gates.U3 in single_qubit_natives: + return u3_dec(gate) + elif gates.GPI2 in single_qubit_natives: + return gpi2_dec(gate) + else: + raise DecompositionError("Use U3 or GPI2 as single qubit native gates") + + +def _translate_two_qubit_gates(gate: gates.Gate, two_qubit_natives): + """Helper method for :meth:`translate_gate`. + + Maps two qubit gates to a hardware-native implementation. + + Args: + gate (:class:`qibo.gates.abstract.Gate`): gate to be decomposed. + two_qubit_natives (:class:`qibo.transpiler.abstract.NativeType`): + two-qubit native gates supported by the quantum hardware. + + Returns: + (list): List of native gates that decompose the input gate. + """ if two_qubit_natives is NativeType.CZ | NativeType.iSWAP: # Check for a special optimized decomposition. if gate.__class__ in opt_dec.decompositions: From 416bf7b200a57e5acebbf3d25bd9f1f376e060bd Mon Sep 17 00:00:00 2001 From: simone bordoni Date: Mon, 20 Nov 2023 13:09:32 +0400 Subject: [PATCH 03/10] native gates class rename --- src/qibo/transpiler/abstract.py | 20 ++++++++----- src/qibo/transpiler/unroller.py | 52 ++++++++++++++------------------- 2 files changed, 35 insertions(+), 37 deletions(-) diff --git a/src/qibo/transpiler/abstract.py b/src/qibo/transpiler/abstract.py index d9daae15b0..4d5423c313 100644 --- a/src/qibo/transpiler/abstract.py +++ b/src/qibo/transpiler/abstract.py @@ -9,16 +9,16 @@ from qibo.models import Circuit -class NativeType(Flag): - """Define available types of native gates. +class NativeGates(Flag): + """Define native gates supported by the unroller. + A native gate set should contain at least one two-qubit gate (CZ or iSWAP) + and at least one single qubit gate (GPI2 or U3). Should have the same names with qibo gates. """ - M = auto() - Z = auto() - RZ = auto() GPI2 = auto() + U3 = auto() CZ = auto() iSWAP = auto() @@ -29,6 +29,12 @@ def from_gate(cls, gate: gates.Gate): except AttributeError: raise ValueError(f"Gate {gate} cannot be used as native.") + def single_qubit_natives(self): + return (self.GPI2, self.U3) + + def two_qubit_natives(self): + return (self.CZ, self.iSWAP) + class Placer(ABC): @abstractmethod @@ -72,7 +78,7 @@ class Optimizer(ABC): @abstractmethod def __call__(self, circuit: Circuit, *args) -> Circuit: - """Find initial qubit mapping + """Optimize transpiled circuit. Args: circuit (:class:`qibo.models.circuit.Circuit`): circuit to be optimized @@ -84,7 +90,7 @@ def __call__(self, circuit: Circuit, *args) -> Circuit: class Unroller(ABC): @abstractmethod - def __init__(self, native_gates: NativeType, *args): + def __init__(self, native_gates: NativeGates, *args): """An unroller decomposes gates into native gates.""" @abstractmethod diff --git a/src/qibo/transpiler/unroller.py b/src/qibo/transpiler/unroller.py index 7c7b5c6b97..83d6982cb1 100644 --- a/src/qibo/transpiler/unroller.py +++ b/src/qibo/transpiler/unroller.py @@ -1,22 +1,20 @@ from qibo import gates from qibo.config import raise_error from qibo.models import Circuit -from qibo.transpiler.abstract import NativeType, Unroller +from qibo.transpiler.abstract import NativeGates, Unroller from qibo.transpiler.decompositions import cz_dec, gpi2_dec, iswap_dec, opt_dec, u3_dec from qibo.transpiler.exceptions import DecompositionError # TODO: Make setting single-qubit native gates more flexible -class NativeGates(Unroller): +class DefaultUnroller(Unroller): """Translates a circuit to native gates. Args: circuit (:class:`qibo.models.circuit.Circuit`): circuit model to translate into native gates. single_qubit_natives (tuple): single qubit native gates. - two_qubit_natives (:class:`qibo.transpiler.abstract.NativeType`): two-qubit native gates - supported by the quantum hardware. - single_qubit_natives (tuple): single qubit native gates. + native_gates (:class:`qibo.transpiler.abstract.NativeGates`): native gates Returns: (:class:`qibo.models.circuit.Circuit`): equivalent circuit with native gates. @@ -24,12 +22,10 @@ class NativeGates(Unroller): def __init__( self, - two_qubit_natives: NativeType, - single_qubit_natives=(gates.I, gates.Z, gates.RZ, gates.GPI2), + native_gates: NativeGates, translate_single_qubit: bool = True, ): - self.two_qubit_natives = two_qubit_natives - self.single_qubit_natives = single_qubit_natives + self.native_gates = native_gates self.translate_single_qubit = translate_single_qubit def __call__(self, circuit: Circuit): @@ -40,8 +36,7 @@ def __call__(self, circuit: Circuit): two_qubit_translated_circuit.add( translate_gate( gate, - self.two_qubit_natives, - single_qubit_natives=self.single_qubit_natives, + self.native_gates, ) ) else: @@ -52,8 +47,7 @@ def __call__(self, circuit: Circuit): translated_circuit.add( translate_gate( gate, - self.two_qubit_natives, - single_qubit_natives=self.single_qubit_natives, + self.native_gates, ) ) else: @@ -66,7 +60,7 @@ def is_satisfied(self, circuit: Circuit): """Return True if a circuit is correctly decomposed into native gates, otherwise False.""" try: assert_decomposition( - circuit, self.two_qubit_natives, self.single_qubit_natives + circuit, self.native_gates, native_gates=self.native_gates ) except DecompositionError: return False @@ -75,14 +69,13 @@ def is_satisfied(self, circuit: Circuit): def assert_decomposition( circuit: Circuit, - two_qubit_natives: NativeType, - single_qubit_natives=(gates.I, gates.Z, gates.RZ, gates.GPI2), + native_gates: NativeGates, ): """Checks if a circuit has been correctly decomposed into native gates. Args: circuit (:class:`qibo.models.circuit.Circuit`): circuit model to check. - two_qubit_natives (:class:`qibo.transpiler.abstract.NativeType`): + native_gates (:class:`qibo.transpiler.abstract.NativeGates`): two-qubit native gates supported by the quantum hardware. single_qubit_natives (tuple): single qubit native gates. """ @@ -90,15 +83,15 @@ def assert_decomposition( if isinstance(gate, gates.M): continue if len(gate.qubits) == 1: - if not isinstance(gate, single_qubit_natives): + if not isinstance(gate, native_gates): raise_error( DecompositionError, f"{gate.name} is not a single qubit native gate.", ) elif len(gate.qubits) == 2: try: - native_type_gate = NativeType.from_gate(gate) - if not (native_type_gate in two_qubit_natives): + native_type_gate = NativeGates.from_gate(gate) + if not (native_type_gate in native_gates): raise_error( DecompositionError, f"{gate.name} is not a two qubits native gate.", @@ -115,14 +108,13 @@ def assert_decomposition( def translate_gate( gate, - two_qubit_natives: NativeType, - single_qubit_natives=(gates.I, gates.Z, gates.RZ, gates.GPI2), + native_gates: NativeGates, ): """Maps gates to a hardware-native implementation. Args: gate (:class:`qibo.gates.abstract.Gate`): gate to be decomposed. - two_qubit_natives (:class:`qibo.transpiler.abstract.NativeType`): + two_qubit_natives (:class:`qibo.transpiler.abstract.NativeGates`): two-qubit native gates supported by the quantum hardware. single_qubit_natives (tuple): single qubit native gates. @@ -132,9 +124,9 @@ def translate_gate( if isinstance(gate, (gates.M, gates.I, gates.Align)): return gate elif len(gate.qubits) == 1: - return _translate_single_qubit_gates(gate, single_qubit_natives) + return _translate_single_qubit_gates(gate, native_gates.single_qubit_natives()) else: - return _translate_two_qubit_gates(gate, two_qubit_natives) + return _translate_two_qubit_gates(gate, native_gates.two_qubit_natives()) def _translate_single_qubit_gates(gate: gates.Gate, single_qubit_natives): @@ -164,13 +156,13 @@ def _translate_two_qubit_gates(gate: gates.Gate, two_qubit_natives): Args: gate (:class:`qibo.gates.abstract.Gate`): gate to be decomposed. - two_qubit_natives (:class:`qibo.transpiler.abstract.NativeType`): + native_gates (:class:`qibo.transpiler.abstract.NativeGates`): two-qubit native gates supported by the quantum hardware. Returns: (list): List of native gates that decompose the input gate. """ - if two_qubit_natives is NativeType.CZ | NativeType.iSWAP: + if two_qubit_natives is NativeGates.CZ | NativeGates.iSWAP: # Check for a special optimized decomposition. if gate.__class__ in opt_dec.decompositions: return opt_dec(gate) @@ -189,9 +181,9 @@ def _translate_two_qubit_gates(gate: gates.Gate, two_qubit_natives): return cz_dec(gate) else: # pragma: no cover return iswap_dec(gate) - elif two_qubit_natives is NativeType.CZ: + elif two_qubit_natives is NativeGates.CZ: return cz_dec(gate) - elif two_qubit_natives is NativeType.iSWAP: + elif two_qubit_natives is NativeGates.iSWAP: if gate.__class__ in iswap_dec.decompositions: return iswap_dec(gate) else: @@ -201,7 +193,7 @@ def _translate_two_qubit_gates(gate: gates.Gate, two_qubit_natives): iswap_decomposed = [] for g in cz_decomposed: # Need recursive function as gates.Unitary is not in iswap_dec - for g_translated in translate_gate(g, NativeType.iSWAP): + for g_translated in translate_gate(g, NativeGates.iSWAP): iswap_decomposed.append(g_translated) return iswap_decomposed else: # pragma: no cover From 564b97829a226079e6e075c266ef3246459cca3f Mon Sep 17 00:00:00 2001 From: simone bordoni Date: Tue, 21 Nov 2023 17:27:20 +0400 Subject: [PATCH 04/10] native gates rewritten --- src/qibo/transpiler/abstract.py | 32 ++- src/qibo/transpiler/decompositions.py | 14 +- src/qibo/transpiler/pipeline.py | 22 +-- src/qibo/transpiler/unroller.py | 40 ++-- tests/test_transpiler_decompositions.py | 236 +++++++++++++++++++++++ tests/test_transpiler_pipeline.py | 31 +-- tests/test_transpiler_unroller.py | 16 +- tests/test_transpilers_decompositions.py | 198 ------------------- 8 files changed, 326 insertions(+), 263 deletions(-) create mode 100644 tests/test_transpiler_decompositions.py delete mode 100644 tests/test_transpilers_decompositions.py diff --git a/src/qibo/transpiler/abstract.py b/src/qibo/transpiler/abstract.py index 4d5423c313..7e0fa5c106 100644 --- a/src/qibo/transpiler/abstract.py +++ b/src/qibo/transpiler/abstract.py @@ -13,27 +13,53 @@ class NativeGates(Flag): """Define native gates supported by the unroller. A native gate set should contain at least one two-qubit gate (CZ or iSWAP) and at least one single qubit gate (GPI2 or U3). + Gates I, Z, RZ and M are always included in the single qubit native gates set. Should have the same names with qibo gates. """ + I = auto() + Z = auto() + RZ = auto() + M = auto() GPI2 = auto() U3 = auto() CZ = auto() iSWAP = auto() + # TODO: use GPI2 as default single qubit native gate + @classmethod + def default(cls): + """Return default native gates set.""" + return cls.CZ | cls.U3 | cls.I | cls.Z | cls.RZ | cls.M + + @classmethod + def from_gatelist(cls, gatelist: list): + """Create a NativeGates object containing all gates from a gatelist.""" + natives = cls(0) + for gate in gatelist: + natives |= cls.from_gate(gate) + return natives + @classmethod def from_gate(cls, gate: gates.Gate): + """Create a NativeGates object from a gate. + The gate can be either a class:`qibo.gates.Gate` or an instance of this class. + """ + if isinstance(gate, gates.Gate): + return cls.from_gate(gate.__class__) try: - return getattr(cls, gate.__class__.__name__) + return getattr(cls, gate.__name__) except AttributeError: raise ValueError(f"Gate {gate} cannot be used as native.") def single_qubit_natives(self): - return (self.GPI2, self.U3) + """Return single qubit native gates in the native gates set.""" + return self & (self.GPI2 | self.U3) | (self.I | self.Z | self.RZ | self.M) def two_qubit_natives(self): - return (self.CZ, self.iSWAP) + """Return two qubit native gates in the native gates set.""" + return self & (self.CZ | self.iSWAP) class Placer(ABC): diff --git a/src/qibo/transpiler/decompositions.py b/src/qibo/transpiler/decompositions.py index c4fea72b8c..34e1380e43 100644 --- a/src/qibo/transpiler/decompositions.py +++ b/src/qibo/transpiler/decompositions.py @@ -47,11 +47,11 @@ def __call__(self, gate): ] +# Decompose single qubit gates using GPI2 (more efficient on hardware) gpi2_dec = GateDecompositions() -gpi2_dec.add(gates.H, [gates.U3(0, 7 * np.pi / 2, np.pi, 0)]) +gpi2_dec.add(gates.H, [gates.Z(0), gates.GPI2(0, np.pi / 2)]) gpi2_dec.add(gates.X, [gates.U3(0, np.pi, 0, np.pi)]) gpi2_dec.add(gates.Y, [gates.U3(0, np.pi, 0, 0)]) -# apply virtually by changing ``phase`` instead of using pulses gpi2_dec.add(gates.Z, [gates.Z(0)]) gpi2_dec.add(gates.S, [gates.RZ(0, np.pi / 2)]) gpi2_dec.add(gates.SDG, [gates.RZ(0, -np.pi / 2)]) @@ -61,11 +61,8 @@ def __call__(self, gate): gates.RX, lambda gate: [gates.U3(0, gate.parameters[0], -np.pi / 2, np.pi / 2)] ) gpi2_dec.add(gates.RY, lambda gate: [gates.U3(0, gate.parameters[0], 0, 0)]) -# apply virtually by changing ``phase`` instead of using pulses gpi2_dec.add(gates.RZ, lambda gate: [gates.RZ(0, gate.parameters[0])]) -# apply virtually by changing ``phase`` instead of using pulses gpi2_dec.add(gates.GPI2, lambda gate: [gates.GPI2(0, gate.parameters[0])]) -# implemented as single RX90 pulse gpi2_dec.add(gates.U1, lambda gate: [gates.RZ(0, gate.parameters[0])]) gpi2_dec.add( gates.U2, @@ -86,12 +83,11 @@ def __call__(self, gate): lambda gate: [gates.U3(0, *u3_decomposition(gate.matrix(backend)))], ) - +# Decompose single qubit gates using U3 u3_dec = GateDecompositions() u3_dec.add(gates.H, [gates.U3(0, 7 * np.pi / 2, np.pi, 0)]) u3_dec.add(gates.X, [gates.U3(0, np.pi, 0, np.pi)]) u3_dec.add(gates.Y, [gates.U3(0, np.pi, 0, 0)]) -# apply virtually by changing ``phase`` instead of using pulses u3_dec.add(gates.Z, [gates.Z(0)]) u3_dec.add(gates.S, [gates.RZ(0, np.pi / 2)]) u3_dec.add(gates.SDG, [gates.RZ(0, -np.pi / 2)]) @@ -101,11 +97,9 @@ def __call__(self, gate): gates.RX, lambda gate: [gates.U3(0, gate.parameters[0], -np.pi / 2, np.pi / 2)] ) u3_dec.add(gates.RY, lambda gate: [gates.U3(0, gate.parameters[0], 0, 0)]) -# apply virtually by changing ``phase`` instead of using pulses u3_dec.add(gates.RZ, lambda gate: [gates.RZ(0, gate.parameters[0])]) -# apply virtually by changing ``phase`` instead of using pulses +# TODO: decompose using U3 u3_dec.add(gates.GPI2, lambda gate: [gates.GPI2(0, gate.parameters[0])]) -# implemented as single RX90 pulse u3_dec.add(gates.U1, lambda gate: [gates.RZ(0, gate.parameters[0])]) u3_dec.add( gates.U2, diff --git a/src/qibo/transpiler/pipeline.py b/src/qibo/transpiler/pipeline.py index a473aa061a..b01fc9a142 100644 --- a/src/qibo/transpiler/pipeline.py +++ b/src/qibo/transpiler/pipeline.py @@ -3,12 +3,11 @@ import networkx as nx import numpy as np -from qibo import gates from qibo.backends import NumpyBackend from qibo.config import raise_error from qibo.models import Circuit from qibo.quantum_info.random_ensembles import random_statevector -from qibo.transpiler.abstract import NativeType, Optimizer, Placer, Router, Unroller +from qibo.transpiler.abstract import NativeGates, Optimizer, Placer, Router, Unroller from qibo.transpiler.exceptions import TranspilerPipelineError from qibo.transpiler.optimizer import Preprocessing from qibo.transpiler.placer import Trivial, assert_placement @@ -16,7 +15,7 @@ from qibo.transpiler.star_connectivity import StarConnectivity from qibo.transpiler.unroller import ( DecompositionError, - NativeGates, + DefaultUnroller, assert_decomposition, ) @@ -98,8 +97,7 @@ def assert_transpiling( connectivity: nx.Graph, initial_layout: dict, final_layout: dict, - two_qubit_natives: NativeType = NativeType.CZ, - single_qubit_natives: tuple = (gates.I, gates.Z, gates.RZ, gates.U3), + native_gates: NativeGates = NativeGates.default(), check_circuit_equivalence=True, ): """Check that all transpiler passes have been executed correctly. @@ -110,15 +108,13 @@ def assert_transpiling( connectivity (networkx.Graph): chip qubits connectivity. initial_layout (dict): initial physical-logical qubit mapping. final_layout (dict): final physical-logical qubit mapping. - two_qubit_natives (NativeType): two qubit native gates supported by the hardware. - single_qubit_natives (tuple): single qubit native gates supported by the hardware. + native_gates (NativeGates): native gates supported by the hardware. check_circuit_equivalence (Bool): use simulations to check if the transpiled circuit is the same as the original. """ assert_connectivity(circuit=transpiled_circuit, connectivity=connectivity) assert_decomposition( circuit=transpiled_circuit, - two_qubit_natives=two_qubit_natives, - single_qubit_natives=single_qubit_natives, + native_gates=native_gates, ) if original_circuit.nqubits != transpiled_circuit.nqubits: qubit_matcher = Preprocessing(connectivity=connectivity) @@ -147,7 +143,7 @@ def __init__( self, passes: list = None, connectivity: nx.Graph = None, - native_gates: NativeType = NativeType.CZ, + native_gates: NativeGates = NativeGates.default(), ): self.native_gates = native_gates if passes is None: @@ -171,7 +167,7 @@ def default(self, connectivity: nx.Graph): # default router pass default_passes.append(StarConnectivity()) # default unroller pass - default_passes.append(NativeGates(two_qubit_natives=self.native_gates)) + default_passes.append(DefaultUnroller(native_gates=self.native_gates)) return default_passes def __call__(self, circuit): @@ -211,11 +207,11 @@ def is_satisfied(self, circuit): Args: circuit (qibo.models.Circuit): circuit to be checked. - native_gates (NativeType): two qubit native gates. + native_gates (NativeGates): two qubit native gates. """ try: assert_connectivity(circuit=circuit, connectivity=self.connectivity) - assert_decomposition(circuit=circuit, two_qubit_natives=self.native_gates) + assert_decomposition(circuit=circuit, native_gates=self.native_gates) return True except ConnectivityError: return False diff --git a/src/qibo/transpiler/unroller.py b/src/qibo/transpiler/unroller.py index 83d6982cb1..4f0638d9e4 100644 --- a/src/qibo/transpiler/unroller.py +++ b/src/qibo/transpiler/unroller.py @@ -13,8 +13,7 @@ class DefaultUnroller(Unroller): Args: circuit (:class:`qibo.models.circuit.Circuit`): circuit model to translate into native gates. - single_qubit_natives (tuple): single qubit native gates. - native_gates (:class:`qibo.transpiler.abstract.NativeGates`): native gates + native_gates (:class:`qibo.transpiler.abstract.NativeGates`): native gates supported by the quantum hardware. Returns: (:class:`qibo.models.circuit.Circuit`): equivalent circuit with native gates. @@ -59,9 +58,7 @@ def __call__(self, circuit: Circuit): def is_satisfied(self, circuit: Circuit): """Return True if a circuit is correctly decomposed into native gates, otherwise False.""" try: - assert_decomposition( - circuit, self.native_gates, native_gates=self.native_gates - ) + assert_decomposition(circuit=circuit, native_gates=self.native_gates) except DecompositionError: return False return True @@ -83,7 +80,14 @@ def assert_decomposition( if isinstance(gate, gates.M): continue if len(gate.qubits) == 1: - if not isinstance(gate, native_gates): + try: + native_type_gate = NativeGates.from_gate(gate) + if not (native_type_gate & native_gates.single_qubit_natives()): + raise_error( + DecompositionError, + f"{gate.name} is not a single qubit native gate.", + ) + except ValueError: raise_error( DecompositionError, f"{gate.name} is not a single qubit native gate.", @@ -91,7 +95,7 @@ def assert_decomposition( elif len(gate.qubits) == 2: try: native_type_gate = NativeGates.from_gate(gate) - if not (native_type_gate in native_gates): + if not (native_type_gate & native_gates.two_qubit_natives()): raise_error( DecompositionError, f"{gate.name} is not a two qubits native gate.", @@ -114,9 +118,7 @@ def translate_gate( Args: gate (:class:`qibo.gates.abstract.Gate`): gate to be decomposed. - two_qubit_natives (:class:`qibo.transpiler.abstract.NativeGates`): - two-qubit native gates supported by the quantum hardware. - single_qubit_natives (tuple): single qubit native gates. + native_gates (:class:`qibo.transpiler.abstract.NativeGates`): native gates supported by the quantum hardware. Returns: (list): List of native gates that decompose the input gate. @@ -126,42 +128,42 @@ def translate_gate( elif len(gate.qubits) == 1: return _translate_single_qubit_gates(gate, native_gates.single_qubit_natives()) else: - return _translate_two_qubit_gates(gate, native_gates.two_qubit_natives()) + return _translate_two_qubit_gates(gate, native_gates) -def _translate_single_qubit_gates(gate: gates.Gate, single_qubit_natives): +def _translate_single_qubit_gates(gate: gates.Gate, single_qubit_natives: NativeGates): """Helper method for :meth:`translate_gate`. Maps single qubit gates to a hardware-native implementation. Args: gate (:class:`qibo.gates.abstract.Gate`): gate to be decomposed. - single_qubit_natives (tuple): single qubit native gates. + single_qubit_natives (:class:`qibo.transpiler.abstract.NativeGates`): single qubit native gates. Returns: (list): List of native gates that decompose the input gate. """ - if gates.U3 in single_qubit_natives: + if NativeGates.U3 & single_qubit_natives: return u3_dec(gate) - elif gates.GPI2 in single_qubit_natives: + elif NativeGates.GPI2 & single_qubit_natives: return gpi2_dec(gate) else: raise DecompositionError("Use U3 or GPI2 as single qubit native gates") -def _translate_two_qubit_gates(gate: gates.Gate, two_qubit_natives): +def _translate_two_qubit_gates(gate: gates.Gate, native_gates: NativeGates): """Helper method for :meth:`translate_gate`. Maps two qubit gates to a hardware-native implementation. Args: gate (:class:`qibo.gates.abstract.Gate`): gate to be decomposed. - native_gates (:class:`qibo.transpiler.abstract.NativeGates`): - two-qubit native gates supported by the quantum hardware. + native_gates (:class:`qibo.transpiler.abstract.NativeGates`): native gates supported by the quantum hardware. Returns: (list): List of native gates that decompose the input gate. """ + two_qubit_natives = native_gates.two_qubit_natives() if two_qubit_natives is NativeGates.CZ | NativeGates.iSWAP: # Check for a special optimized decomposition. if gate.__class__ in opt_dec.decompositions: @@ -193,7 +195,7 @@ def _translate_two_qubit_gates(gate: gates.Gate, two_qubit_natives): iswap_decomposed = [] for g in cz_decomposed: # Need recursive function as gates.Unitary is not in iswap_dec - for g_translated in translate_gate(g, NativeGates.iSWAP): + for g_translated in translate_gate(g, native_gates=native_gates): iswap_decomposed.append(g_translated) return iswap_decomposed else: # pragma: no cover diff --git a/tests/test_transpiler_decompositions.py b/tests/test_transpiler_decompositions.py new file mode 100644 index 0000000000..f122fd048d --- /dev/null +++ b/tests/test_transpiler_decompositions.py @@ -0,0 +1,236 @@ +import numpy as np +import pytest + +from qibo import gates +from qibo.backends import NumpyBackend +from qibo.models import Circuit +from qibo.quantum_info.random_ensembles import random_unitary +from qibo.transpiler.abstract import NativeGates +from qibo.transpiler.unroller import translate_gate + + +def assert_matrices_allclose(gate, natives, backend): + target_matrix = gate.matrix(backend) + target_matrix = backend.cast(target_matrix, dtype=target_matrix.dtype) + # Remove global phase from target matrix + normalisation = np.power( + np.linalg.det(target_matrix), 1 / float(target_matrix.shape[0]), dtype=complex + ) + normalisation = backend.cast(normalisation, dtype=normalisation.dtype) + target_unitary = target_matrix / normalisation + + circuit = Circuit(len(gate.qubits)) + circuit.add(translate_gate(gate, natives)) + native_matrix = circuit.unitary(backend) + # Remove global phase from native matrix + normalisation = np.power( + np.linalg.det(native_matrix), 1 / float(native_matrix.shape[0]), dtype=complex + ) + normalisation = backend.cast(normalisation, dtype=normalisation.dtype) + native_unitary = native_matrix / normalisation + + # There can still be phase differences of -1, -1j, 1j + c = 0 + for phase in [1, -1, 1j, -1j]: + if np.allclose(phase * native_unitary, target_unitary, atol=1e-12): + c = 1 + backend.assert_allclose(c, 1) + + +@pytest.mark.parametrize("natives", [NativeGates.U3, NativeGates.GPI2]) +@pytest.mark.parametrize("gatename", ["H", "X", "Y", "I"]) +def test_pauli_to_native(backend, gatename, natives): + gate = getattr(gates, gatename)(0) + assert_matrices_allclose(gate, natives=natives, backend=backend) + + +@pytest.mark.parametrize("natives", [NativeGates.U3, NativeGates.GPI2]) +@pytest.mark.parametrize("gatename", ["RX", "RY", "RZ"]) +def test_rotations_to_native(backend, gatename, natives): + gate = getattr(gates, gatename)(0, theta=0.1) + assert_matrices_allclose(gate, natives=natives, backend=backend) + + +@pytest.mark.parametrize("natives", [NativeGates.U3, NativeGates.GPI2]) +@pytest.mark.parametrize("gatename", ["S", "SDG", "T", "TDG"]) +def test_special_single_qubit_to_native(backend, gatename, natives): + gate = getattr(gates, gatename)(0) + assert_matrices_allclose(gate, natives=natives, backend=backend) + + +@pytest.mark.parametrize("natives", [NativeGates.U3, NativeGates.GPI2]) +def test_u1_to_native(backend, natives): + gate = gates.U1(0, theta=0.5) + assert_matrices_allclose(gate, natives=natives, backend=backend) + + +@pytest.mark.parametrize("natives", [NativeGates.U3, NativeGates.GPI2]) +def test_u2_to_native(backend, natives): + gate = gates.U2(0, phi=0.1, lam=0.3) + assert_matrices_allclose(gate, natives=natives, backend=backend) + + +@pytest.mark.parametrize("natives", [NativeGates.U3, NativeGates.GPI2]) +def test_u3_to_native(backend, natives): + gate = gates.U3(0, theta=0.2, phi=0.1, lam=0.3) + assert_matrices_allclose(gate, natives=natives, backend=backend) + + +@pytest.mark.parametrize("natives", [NativeGates.U3, NativeGates.GPI2]) +def test_gpi2_to_native(backend, natives): + gate = gates.GPI2(0, phi=0.123) + assert_matrices_allclose(gate, natives=natives, backend=backend) + + +@pytest.mark.parametrize("gatename", ["CNOT", "CZ", "SWAP", "iSWAP", "FSWAP"]) +@pytest.mark.parametrize( + "natives_2q", + [NativeGates.CZ, NativeGates.iSWAP, NativeGates.CZ | NativeGates.iSWAP], +) +@pytest.mark.parametrize( + "natives_1q", + [NativeGates.U3, NativeGates.GPI2, NativeGates.U3 | NativeGates.GPI2], +) +def test_two_qubit_to_native(backend, gatename, natives_1q, natives_2q): + gate = getattr(gates, gatename)(0, 1) + assert_matrices_allclose(gate, natives_1q | natives_2q, backend) + + +@pytest.mark.parametrize( + "natives_2q", + [NativeGates.CZ, NativeGates.iSWAP, NativeGates.CZ | NativeGates.iSWAP], +) +@pytest.mark.parametrize( + "natives_1q", + [NativeGates.U3, NativeGates.GPI2, NativeGates.U3 | NativeGates.GPI2], +) +@pytest.mark.parametrize("gatename", ["CRX", "CRY", "CRZ"]) +def test_controlled_rotations_to_native(backend, gatename, natives_1q, natives_2q): + gate = getattr(gates, gatename)(0, 1, 0.3) + assert_matrices_allclose(gate, natives_1q | natives_2q, backend) + + +@pytest.mark.parametrize( + "natives_2q", + [NativeGates.CZ, NativeGates.iSWAP, NativeGates.CZ | NativeGates.iSWAP], +) +@pytest.mark.parametrize( + "natives_1q", + [NativeGates.U3, NativeGates.GPI2, NativeGates.U3 | NativeGates.GPI2], +) +def test_cu1_to_native(backend, natives_1q, natives_2q): + gate = gates.CU1(0, 1, theta=0.4) + assert_matrices_allclose(gate, natives_1q | natives_2q, backend) + + +@pytest.mark.parametrize( + "natives_2q", + [NativeGates.CZ, NativeGates.iSWAP, NativeGates.CZ | NativeGates.iSWAP], +) +@pytest.mark.parametrize( + "natives_1q", + [NativeGates.U3, NativeGates.GPI2, NativeGates.U3 | NativeGates.GPI2], +) +def test_cu2_to_native(backend, natives_1q, natives_2q): + gate = gates.CU2(0, 1, phi=0.2, lam=0.3) + assert_matrices_allclose(gate, natives_1q | natives_2q, backend) + + +@pytest.mark.parametrize( + "natives_2q", + [NativeGates.CZ, NativeGates.iSWAP, NativeGates.CZ | NativeGates.iSWAP], +) +@pytest.mark.parametrize( + "natives_1q", + [NativeGates.U3, NativeGates.GPI2, NativeGates.U3 | NativeGates.GPI2], +) +def test_cu3_to_native(backend, natives_1q, natives_2q): + gate = gates.CU3(0, 1, theta=0.2, phi=0.3, lam=0.4) + assert_matrices_allclose(gate, natives_1q | natives_2q, backend) + + +@pytest.mark.parametrize( + "natives_2q", + [NativeGates.CZ, NativeGates.iSWAP, NativeGates.CZ | NativeGates.iSWAP], +) +@pytest.mark.parametrize( + "natives_1q", + [NativeGates.U3, NativeGates.GPI2, NativeGates.U3 | NativeGates.GPI2], +) +def test_fSim_to_native(backend, natives_1q, natives_2q): + gate = gates.fSim(0, 1, theta=0.3, phi=0.1) + assert_matrices_allclose(gate, natives_1q | natives_2q, backend) + + +@pytest.mark.parametrize("seed", [None, 10, np.random.default_rng(10)]) +@pytest.mark.parametrize( + "natives_2q", + [NativeGates.CZ, NativeGates.iSWAP, NativeGates.CZ | NativeGates.iSWAP], +) +@pytest.mark.parametrize( + "natives_1q", + [NativeGates.U3, NativeGates.GPI2, NativeGates.U3 | NativeGates.GPI2], +) +def test_GeneralizedfSim_to_native(backend, natives_1q, natives_2q, seed): + unitary = random_unitary(2, seed=seed, backend=backend) + gate = gates.GeneralizedfSim(0, 1, unitary, phi=0.1) + assert_matrices_allclose(gate, natives_1q | natives_2q, backend) + + +@pytest.mark.parametrize( + "natives_2q", + [NativeGates.CZ, NativeGates.iSWAP, NativeGates.CZ | NativeGates.iSWAP], +) +@pytest.mark.parametrize( + "natives_1q", + [NativeGates.U3, NativeGates.GPI2, NativeGates.U3 | NativeGates.GPI2], +) +@pytest.mark.parametrize("gatename", ["RXX", "RZZ", "RYY"]) +def test_rnn_to_native(backend, gatename, natives_1q, natives_2q): + gate = getattr(gates, gatename)(0, 1, theta=0.1) + assert_matrices_allclose(gate, natives_1q | natives_2q, backend) + + +@pytest.mark.parametrize( + "natives_2q", + [NativeGates.CZ, NativeGates.iSWAP, NativeGates.CZ | NativeGates.iSWAP], +) +@pytest.mark.parametrize( + "natives_1q", + [NativeGates.U3, NativeGates.GPI2, NativeGates.U3 | NativeGates.GPI2], +) +def test_TOFFOLI_to_native(backend, natives_1q, natives_2q): + gate = gates.TOFFOLI(0, 1, 2) + assert_matrices_allclose(gate, natives_1q | natives_2q, backend) + + +@pytest.mark.parametrize("seed", [None, 10, np.random.default_rng(10)]) +@pytest.mark.parametrize( + "natives_2q", + [NativeGates.CZ, NativeGates.iSWAP, NativeGates.CZ | NativeGates.iSWAP], +) +@pytest.mark.parametrize( + "natives_1q", + [NativeGates.U3, NativeGates.GPI2, NativeGates.U3 | NativeGates.GPI2], +) +@pytest.mark.parametrize("nqubits", [1, 2]) +def test_unitary_to_native(backend, nqubits, natives_1q, natives_2q, seed): + u = random_unitary(2**nqubits, seed=seed, backend=NumpyBackend()) + # transform to SU(2^nqubits) form + u = u / np.sqrt(np.linalg.det(u)) + gate = gates.Unitary(u, *range(nqubits)) + assert_matrices_allclose(gate, natives_1q | natives_2q, backend) + + +def test_count_1q(): + from qibo.transpiler.unroller import cz_dec + + np.testing.assert_allclose(cz_dec.count_1q(gates.CNOT(0, 1)), 2) + np.testing.assert_allclose(cz_dec.count_1q(gates.CRX(0, 1, 0.1)), 2) + + +def test_count_2q(): + from qibo.transpiler.unroller import cz_dec + + np.testing.assert_allclose(cz_dec.count_2q(gates.CNOT(0, 1)), 1) + np.testing.assert_allclose(cz_dec.count_2q(gates.CRX(0, 1, 0.1)), 2) diff --git a/tests/test_transpiler_pipeline.py b/tests/test_transpiler_pipeline.py index 685959eec4..04786c2430 100644 --- a/tests/test_transpiler_pipeline.py +++ b/tests/test_transpiler_pipeline.py @@ -6,7 +6,7 @@ from qibo import gates from qibo.models import Circuit -from qibo.transpiler.abstract import NativeType +from qibo.transpiler.abstract import NativeGates from qibo.transpiler.optimizer import Preprocessing from qibo.transpiler.pipeline import ( Passes, @@ -16,12 +16,11 @@ ) from qibo.transpiler.placer import Random, ReverseTraversal, Trivial from qibo.transpiler.router import ShortestPaths -from qibo.transpiler.unroller import NativeGates +from qibo.transpiler.unroller import DefaultUnroller def generate_random_circuit(nqubits, ngates, seed=None): """Generate random circuits one-qubit rotations and CZ gates.""" - pairs = list(itertools.combinations(range(nqubits), 2)) if seed is not None: # pragma: no cover np.random.seed(seed) @@ -84,7 +83,7 @@ def test_pipeline_default(ngates): connectivity=star_connectivity(), initial_layout=initial_layout, final_layout=final_layout, - two_qubit_natives=NativeType.CZ, + native_gates=NativeGates.default(), check_circuit_equivalence=False, ) @@ -166,9 +165,11 @@ def test_custom_passes(circ): custom_passes.append(Preprocessing(connectivity=star_connectivity())) custom_passes.append(Random(connectivity=star_connectivity())) custom_passes.append(ShortestPaths(connectivity=star_connectivity())) - custom_passes.append(NativeGates(two_qubit_natives=NativeType.iSWAP)) + custom_passes.append(DefaultUnroller(native_gates=NativeGates.default())) custom_pipeline = Passes( - custom_passes, connectivity=star_connectivity(), native_gates=NativeType.iSWAP + custom_passes, + connectivity=star_connectivity(), + native_gates=NativeGates.default(), ) transpiled_circ, final_layout = custom_pipeline(circ) initial_layout = custom_pipeline.get_initial_layout() @@ -178,7 +179,7 @@ def test_custom_passes(circ): connectivity=star_connectivity(), initial_layout=initial_layout, final_layout=final_layout, - two_qubit_natives=NativeType.iSWAP, + native_gates=NativeGates.default(), ) @@ -196,9 +197,11 @@ def test_custom_passes_reverse(circ): ) ) custom_passes.append(ShortestPaths(connectivity=star_connectivity())) - custom_passes.append(NativeGates(two_qubit_natives=NativeType.iSWAP)) + custom_passes.append(DefaultUnroller(native_gates=NativeGates.default())) custom_pipeline = Passes( - custom_passes, connectivity=star_connectivity(), native_gates=NativeType.iSWAP + custom_passes, + connectivity=star_connectivity(), + native_gates=NativeGates.default(), ) transpiled_circ, final_layout = custom_pipeline(circ) initial_layout = custom_pipeline.get_initial_layout() @@ -208,7 +211,7 @@ def test_custom_passes_reverse(circ): connectivity=star_connectivity(), initial_layout=initial_layout, final_layout=final_layout, - two_qubit_natives=NativeType.iSWAP, + native_gates=NativeGates.default(), ) @@ -217,7 +220,9 @@ def test_custom_passes_multiple_placer(): custom_passes.append(Random(connectivity=star_connectivity())) custom_passes.append(Trivial(connectivity=star_connectivity())) custom_pipeline = Passes( - custom_passes, connectivity=star_connectivity(), native_gates=NativeType.CZ + custom_passes, + connectivity=star_connectivity(), + native_gates=NativeGates.default(), ) circ = generate_random_circuit(nqubits=5, ngates=20) with pytest.raises(TranspilerPipelineError): @@ -228,7 +233,9 @@ def test_custom_passes_no_placer(): custom_passes = [] custom_passes.append(ShortestPaths(connectivity=star_connectivity())) custom_pipeline = Passes( - custom_passes, connectivity=star_connectivity(), native_gates=NativeType.CZ + custom_passes, + connectivity=star_connectivity(), + native_gates=NativeGates.default(), ) circ = generate_random_circuit(nqubits=5, ngates=20) with pytest.raises(TranspilerPipelineError): diff --git a/tests/test_transpiler_unroller.py b/tests/test_transpiler_unroller.py index 4b2a3c16a5..dafa888dba 100644 --- a/tests/test_transpiler_unroller.py +++ b/tests/test_transpiler_unroller.py @@ -2,10 +2,10 @@ from qibo import gates from qibo.models import Circuit -from qibo.transpiler.abstract import NativeType +from qibo.transpiler.abstract import NativeGates from qibo.transpiler.unroller import ( DecompositionError, - NativeGates, + DefaultUnroller, assert_decomposition, ) @@ -15,14 +15,14 @@ def test_assert_decomposition(): circuit.add(gates.CZ(0, 1)) circuit.add(gates.Z(0)) circuit.add(gates.M(1)) - assert_decomposition(circuit, two_qubit_natives=NativeType.CZ) + assert_decomposition(circuit, native_gates=NativeGates.default()) def test_assert_decomposition_fail_1q(): circuit = Circuit(1) circuit.add(gates.X(0)) with pytest.raises(DecompositionError): - assert_decomposition(circuit, two_qubit_natives=NativeType.CZ) + assert_decomposition(circuit, native_gates=NativeGates.default()) @pytest.mark.parametrize("gate", [gates.CNOT(0, 1), gates.iSWAP(0, 1)]) @@ -30,19 +30,19 @@ def test_assert_decomposition_fail_2q(gate): circuit = Circuit(2) circuit.add(gate) with pytest.raises(DecompositionError): - assert_decomposition(circuit, two_qubit_natives=NativeType.CZ) + assert_decomposition(circuit, native_gates=NativeGates.default()) def test_assert_decomposition_fail_3q(): circuit = Circuit(3) circuit.add(gates.TOFFOLI(0, 1, 2)) with pytest.raises(DecompositionError): - assert_decomposition(circuit, two_qubit_natives=NativeType.CZ) + assert_decomposition(circuit, native_gates=NativeGates.default()) def test_no_translate_single_qubit(): - unroller = NativeGates( - two_qubit_natives=NativeType.CZ, translate_single_qubit=False + unroller = DefaultUnroller( + native_gates=NativeGates.default(), translate_single_qubit=False ) circuit = Circuit(2) circuit.add(gates.X(0)) diff --git a/tests/test_transpilers_decompositions.py b/tests/test_transpilers_decompositions.py deleted file mode 100644 index 682d317eb9..0000000000 --- a/tests/test_transpilers_decompositions.py +++ /dev/null @@ -1,198 +0,0 @@ -import numpy as np -import pytest - -from qibo import gates -from qibo.backends import NumpyBackend -from qibo.models import Circuit -from qibo.quantum_info.random_ensembles import random_unitary -from qibo.transpiler.abstract import NativeType -from qibo.transpiler.unroller import translate_gate - - -def assert_matrices_allclose( - gate, - two_qubit_natives, - backend, - single_qubit_natives=(gates.I, gates.Z, gates.RZ, gates.U3), -): - target_matrix = gate.matrix(backend) - target_matrix = backend.cast(target_matrix, dtype=target_matrix.dtype) - # Remove global phase from target matrix - normalisation = np.power( - np.linalg.det(target_matrix), 1 / float(target_matrix.shape[0]), dtype=complex - ) - normalisation = backend.cast(normalisation, dtype=normalisation.dtype) - target_unitary = target_matrix / normalisation - - circuit = Circuit(len(gate.qubits)) - circuit.add( - translate_gate( - gate, two_qubit_natives, single_qubit_natives=single_qubit_natives - ) - ) - native_matrix = circuit.unitary(backend) - # Remove global phase from native matrix - normalisation = np.power( - np.linalg.det(native_matrix), 1 / float(native_matrix.shape[0]), dtype=complex - ) - normalisation = backend.cast(normalisation, dtype=normalisation.dtype) - native_unitary = native_matrix / normalisation - - # There can still be phase differences of -1, -1j, 1j - c = 0 - for phase in [1, -1, 1j, -1j]: - if np.allclose(phase * native_unitary, target_unitary, atol=1e-12): - c = 1 - backend.assert_allclose(c, 1) - - -@pytest.mark.parametrize("gatename", ["H", "X", "Y", "I"]) -def test_pauli_to_native(backend, gatename): - gate = getattr(gates, gatename)(0) - assert_matrices_allclose(gate, two_qubit_natives=NativeType.CZ, backend=backend) - - -@pytest.mark.parametrize("gatename", ["RX", "RY", "RZ"]) -def test_rotations_to_native(backend, gatename): - gate = getattr(gates, gatename)(0, theta=0.1) - assert_matrices_allclose(gate, two_qubit_natives=NativeType.CZ, backend=backend) - - -@pytest.mark.parametrize("gatename", ["S", "SDG", "T", "TDG"]) -def test_special_single_qubit_to_native(backend, gatename): - gate = getattr(gates, gatename)(0) - assert_matrices_allclose(gate, two_qubit_natives=NativeType.CZ, backend=backend) - - -def test_u1_to_native(backend): - gate = gates.U1(0, theta=0.5) - assert_matrices_allclose(gate, two_qubit_natives=NativeType.CZ, backend=backend) - - -def test_u2_to_native(backend): - gate = gates.U2(0, phi=0.1, lam=0.3) - assert_matrices_allclose(gate, two_qubit_natives=NativeType.CZ, backend=backend) - - -def test_u3_to_native(backend): - gate = gates.U3(0, theta=0.2, phi=0.1, lam=0.3) - assert_matrices_allclose(gate, two_qubit_natives=NativeType.CZ, backend=backend) - - -def test_gpi2_to_native(backend): - gate = gates.GPI2(0, phi=0.123) - assert_matrices_allclose(gate, two_qubit_natives=NativeType.CZ, backend=backend) - - -@pytest.mark.parametrize("gatename", ["CNOT", "CZ", "SWAP", "iSWAP", "FSWAP"]) -@pytest.mark.parametrize( - "natives", - [NativeType.CZ, NativeType.iSWAP, NativeType.CZ | NativeType.iSWAP], -) -def test_two_qubit_to_native(backend, gatename, natives): - gate = getattr(gates, gatename)(0, 1) - assert_matrices_allclose(gate, natives, backend) - - -@pytest.mark.parametrize( - "natives", - [NativeType.CZ, NativeType.iSWAP, NativeType.CZ | NativeType.iSWAP], -) -@pytest.mark.parametrize("gatename", ["CRX", "CRY", "CRZ"]) -def test_controlled_rotations_to_native(backend, gatename, natives): - gate = getattr(gates, gatename)(0, 1, 0.3) - assert_matrices_allclose(gate, natives, backend) - - -@pytest.mark.parametrize( - "natives", - [NativeType.CZ, NativeType.iSWAP, NativeType.CZ | NativeType.iSWAP], -) -def test_cu1_to_native(backend, natives): - gate = gates.CU1(0, 1, theta=0.4) - assert_matrices_allclose(gate, natives, backend) - - -@pytest.mark.parametrize( - "natives", - [NativeType.CZ, NativeType.iSWAP, NativeType.CZ | NativeType.iSWAP], -) -def test_cu2_to_native(backend, natives): - gate = gates.CU2(0, 1, phi=0.2, lam=0.3) - assert_matrices_allclose(gate, natives, backend) - - -@pytest.mark.parametrize( - "natives", - [NativeType.CZ, NativeType.iSWAP, NativeType.CZ | NativeType.iSWAP], -) -def test_cu3_to_native(backend, natives): - gate = gates.CU3(0, 1, theta=0.2, phi=0.3, lam=0.4) - assert_matrices_allclose(gate, natives, backend) - - -@pytest.mark.parametrize( - "natives", - [NativeType.CZ, NativeType.iSWAP, NativeType.CZ | NativeType.iSWAP], -) -def test_fSim_to_native(backend, natives): - gate = gates.fSim(0, 1, theta=0.3, phi=0.1) - assert_matrices_allclose(gate, natives, backend) - - -@pytest.mark.parametrize("seed", [None, 10, np.random.default_rng(10)]) -@pytest.mark.parametrize( - "natives", - [NativeType.CZ, NativeType.iSWAP, NativeType.CZ | NativeType.iSWAP], -) -def test_GeneralizedfSim_to_native(backend, natives, seed): - unitary = random_unitary(2, seed=seed, backend=backend) - gate = gates.GeneralizedfSim(0, 1, unitary, phi=0.1) - assert_matrices_allclose(gate, natives, backend) - - -@pytest.mark.parametrize( - "natives", - [NativeType.CZ, NativeType.iSWAP, NativeType.CZ | NativeType.iSWAP], -) -@pytest.mark.parametrize("gatename", ["RXX", "RZZ", "RYY"]) -def test_rnn_to_native(backend, gatename, natives): - gate = getattr(gates, gatename)(0, 1, theta=0.1) - assert_matrices_allclose(gate, natives, backend) - - -@pytest.mark.parametrize( - "natives", - [NativeType.CZ, NativeType.iSWAP, NativeType.CZ | NativeType.iSWAP], -) -def test_TOFFOLI_to_native(backend, natives): - gate = gates.TOFFOLI(0, 1, 2) - assert_matrices_allclose(gate, natives, backend) - - -@pytest.mark.parametrize("seed", [None, 10, np.random.default_rng(10)]) -@pytest.mark.parametrize( - "natives", - [NativeType.CZ, NativeType.iSWAP, NativeType.CZ | NativeType.iSWAP], -) -@pytest.mark.parametrize("nqubits", [1, 2]) -def test_unitary_to_native(backend, nqubits, natives, seed): - u = random_unitary(2**nqubits, seed=seed, backend=NumpyBackend()) - # transform to SU(2^nqubits) form - u = u / np.sqrt(np.linalg.det(u)) - gate = gates.Unitary(u, *range(nqubits)) - assert_matrices_allclose(gate, natives, backend) - - -def test_count_1q(): - from qibo.transpiler.unroller import cz_dec - - np.testing.assert_allclose(cz_dec.count_1q(gates.CNOT(0, 1)), 2) - np.testing.assert_allclose(cz_dec.count_1q(gates.CRX(0, 1, 0.1)), 2) - - -def test_count_2q(): - from qibo.transpiler.unroller import cz_dec - - np.testing.assert_allclose(cz_dec.count_2q(gates.CNOT(0, 1)), 1) - np.testing.assert_allclose(cz_dec.count_2q(gates.CRX(0, 1, 0.1)), 2) From 51385efd78e6dceae7dd4e4f7c42c14b543278d2 Mon Sep 17 00:00:00 2001 From: simone bordoni Date: Wed, 22 Nov 2023 15:18:35 +0400 Subject: [PATCH 05/10] completed decompositions and added SX --- src/qibo/transpiler/decompositions.py | 64 ++++++++++++++++++------- tests/test_transpiler_decompositions.py | 2 +- 2 files changed, 48 insertions(+), 18 deletions(-) diff --git a/src/qibo/transpiler/decompositions.py b/src/qibo/transpiler/decompositions.py index 34e1380e43..229f59605c 100644 --- a/src/qibo/transpiler/decompositions.py +++ b/src/qibo/transpiler/decompositions.py @@ -47,40 +47,68 @@ def __call__(self, gate): ] +def _u3_to_gpi2(t, p, l): + """Decompose a U3 gate into GPI2 gates. + + Args: + t (float): theta parameter of U3 gate. + p (float): phi parameter of U3 gate. + l (float): lambda parameter of U3 gate. + + Returns: + list of native gates that decompose the U3 gate. + """ + return [ + gates.RZ(0, l), + gates.GPI2(0, 0), + gates.RZ(0, t + np.pi), + gates.GPI2(0, 0), + gates.RZ(0, p + np.pi), + ] + + # Decompose single qubit gates using GPI2 (more efficient on hardware) gpi2_dec = GateDecompositions() gpi2_dec.add(gates.H, [gates.Z(0), gates.GPI2(0, np.pi / 2)]) -gpi2_dec.add(gates.X, [gates.U3(0, np.pi, 0, np.pi)]) -gpi2_dec.add(gates.Y, [gates.U3(0, np.pi, 0, 0)]) +gpi2_dec.add(gates.X, [gates.GPI2(0, np.pi / 2), gates.GPI2(0, np.pi / 2), gates.Z(0)]) +gpi2_dec.add(gates.Y, [gates.Z(0), gates.GPI2(0, 0), gates.GPI2(0, 0)]) gpi2_dec.add(gates.Z, [gates.Z(0)]) gpi2_dec.add(gates.S, [gates.RZ(0, np.pi / 2)]) gpi2_dec.add(gates.SDG, [gates.RZ(0, -np.pi / 2)]) gpi2_dec.add(gates.T, [gates.RZ(0, np.pi / 4)]) gpi2_dec.add(gates.TDG, [gates.RZ(0, -np.pi / 4)]) +gpi2_dec.add(gates.SX, [gates.GPI2(0, 0)]) gpi2_dec.add( - gates.RX, lambda gate: [gates.U3(0, gate.parameters[0], -np.pi / 2, np.pi / 2)] + gates.RX, + lambda gate: [ + gates.Z(0), + gates.GPI2(0, np.pi / 2), + gates.RZ(0, gate.parameters[0] + np.pi), + gates.GPI2(0, np.pi / 2), + ], +) +gpi2_dec.add( + gates.RY, + lambda gate: [ + gates.GPI2(0, 0), + gates.RZ(0, gate.parameters[0] + np.pi), + gates.GPI2(0, 0), + gates.Z(0), + ], ) -gpi2_dec.add(gates.RY, lambda gate: [gates.U3(0, gate.parameters[0], 0, 0)]) gpi2_dec.add(gates.RZ, lambda gate: [gates.RZ(0, gate.parameters[0])]) gpi2_dec.add(gates.GPI2, lambda gate: [gates.GPI2(0, gate.parameters[0])]) gpi2_dec.add(gates.U1, lambda gate: [gates.RZ(0, gate.parameters[0])]) gpi2_dec.add( gates.U2, - lambda gate: [gates.U3(0, np.pi / 2, gate.parameters[0], gate.parameters[1])], + lambda gate: _u3_to_gpi2(np.pi / 2, gate.parameters[0], gate.parameters[1]), ) +gpi2_dec.add(gates.U3, lambda gate: _u3_to_gpi2(*gate.parameters)) gpi2_dec.add( - gates.U3, - lambda gate: [ - gates.U3(0, gate.parameters[0], gate.parameters[1], gate.parameters[2]) - ], -) -gpi2_dec.add( - gates.Unitary, - lambda gate: [gates.U3(0, *u3_decomposition(gate.parameters[0]))], + gates.Unitary, lambda gate: _u3_to_gpi2(*u3_decomposition(gate.parameters[0])) ) gpi2_dec.add( - gates.FusedGate, - lambda gate: [gates.U3(0, *u3_decomposition(gate.matrix(backend)))], + gates.FusedGate, lambda gate: _u3_to_gpi2(*u3_decomposition(gate.matrix(backend))) ) # Decompose single qubit gates using U3 @@ -93,13 +121,15 @@ def __call__(self, gate): u3_dec.add(gates.SDG, [gates.RZ(0, -np.pi / 2)]) u3_dec.add(gates.T, [gates.RZ(0, np.pi / 4)]) u3_dec.add(gates.TDG, [gates.RZ(0, -np.pi / 4)]) +u3_dec.add(gates.SX, [gates.U3(0, np.pi / 2, -np.pi / 2, np.pi / 2)]) u3_dec.add( gates.RX, lambda gate: [gates.U3(0, gate.parameters[0], -np.pi / 2, np.pi / 2)] ) u3_dec.add(gates.RY, lambda gate: [gates.U3(0, gate.parameters[0], 0, 0)]) u3_dec.add(gates.RZ, lambda gate: [gates.RZ(0, gate.parameters[0])]) -# TODO: decompose using U3 -u3_dec.add(gates.GPI2, lambda gate: [gates.GPI2(0, gate.parameters[0])]) +u3_dec.add( + gates.GPI2, lambda gate: [gates.U3(0, *u3_decomposition(gate.matrix(backend)))] +) u3_dec.add(gates.U1, lambda gate: [gates.RZ(0, gate.parameters[0])]) u3_dec.add( gates.U2, diff --git a/tests/test_transpiler_decompositions.py b/tests/test_transpiler_decompositions.py index f122fd048d..5bf7c32266 100644 --- a/tests/test_transpiler_decompositions.py +++ b/tests/test_transpiler_decompositions.py @@ -52,7 +52,7 @@ def test_rotations_to_native(backend, gatename, natives): @pytest.mark.parametrize("natives", [NativeGates.U3, NativeGates.GPI2]) -@pytest.mark.parametrize("gatename", ["S", "SDG", "T", "TDG"]) +@pytest.mark.parametrize("gatename", ["S", "SDG", "T", "TDG", "SX"]) def test_special_single_qubit_to_native(backend, gatename, natives): gate = getattr(gates, gatename)(0) assert_matrices_allclose(gate, natives=natives, backend=backend) From f3ce2f32ecbcbdb571cb28077400c205f9c50860 Mon Sep 17 00:00:00 2001 From: simone bordoni Date: Wed, 22 Nov 2023 16:18:58 +0400 Subject: [PATCH 06/10] fixed tests --- src/qibo/transpiler/abstract.py | 3 +- src/qibo/transpiler/decompositions.py | 22 ++++++----- src/qibo/transpiler/unroller.py | 43 ++++++++------------- tests/test_transpiler_decompositions.py | 3 +- tests/test_transpiler_unroller.py | 51 +++++++++++++++++++++---- 5 files changed, 75 insertions(+), 47 deletions(-) diff --git a/src/qibo/transpiler/abstract.py b/src/qibo/transpiler/abstract.py index 7e0fa5c106..2ae59c24cc 100644 --- a/src/qibo/transpiler/abstract.py +++ b/src/qibo/transpiler/abstract.py @@ -27,11 +27,10 @@ class NativeGates(Flag): CZ = auto() iSWAP = auto() - # TODO: use GPI2 as default single qubit native gate @classmethod def default(cls): """Return default native gates set.""" - return cls.CZ | cls.U3 | cls.I | cls.Z | cls.RZ | cls.M + return cls.CZ | cls.GPI2 | cls.I | cls.Z | cls.RZ | cls.M @classmethod def from_gatelist(cls, gatelist: list): diff --git a/src/qibo/transpiler/decompositions.py b/src/qibo/transpiler/decompositions.py index 229f59605c..6b874310fc 100644 --- a/src/qibo/transpiler/decompositions.py +++ b/src/qibo/transpiler/decompositions.py @@ -47,24 +47,28 @@ def __call__(self, gate): ] -def _u3_to_gpi2(t, p, l): +def _u3_to_gpi2(t, p, l, optimize=True): """Decompose a U3 gate into GPI2 gates. Args: t (float): theta parameter of U3 gate. p (float): phi parameter of U3 gate. l (float): lambda parameter of U3 gate. + optimize (bool): if True, the decomposition is optimized to use the minimum number of gates. Returns: - list of native gates that decompose the U3 gate. + decomposition (list): list of native gates that decompose the U3 gate. """ - return [ - gates.RZ(0, l), - gates.GPI2(0, 0), - gates.RZ(0, t + np.pi), - gates.GPI2(0, 0), - gates.RZ(0, p + np.pi), - ] + decomposition = [] + if l != 0.0 or not optimize: + decomposition.append(gates.RZ(0, l)) + decomposition.append(gates.GPI2(0, 0)) + if t != -np.pi or not optimize: + decomposition.append(gates.RZ(0, t + np.pi)) + decomposition.append(gates.GPI2(0, 0)) + if p != -np.pi or not optimize: + decomposition.append(gates.RZ(0, p + np.pi)) + return decomposition # Decompose single qubit gates using GPI2 (more efficient on hardware) diff --git a/src/qibo/transpiler/unroller.py b/src/qibo/transpiler/unroller.py index 4f0638d9e4..1c1c342402 100644 --- a/src/qibo/transpiler/unroller.py +++ b/src/qibo/transpiler/unroller.py @@ -22,37 +22,18 @@ class DefaultUnroller(Unroller): def __init__( self, native_gates: NativeGates, - translate_single_qubit: bool = True, ): self.native_gates = native_gates - self.translate_single_qubit = translate_single_qubit def __call__(self, circuit: Circuit): - two_qubit_translated_circuit = circuit.__class__(circuit.nqubits) translated_circuit = circuit.__class__(circuit.nqubits) for gate in circuit.queue: - if len(gate.qubits) > 1 or self.translate_single_qubit: - two_qubit_translated_circuit.add( - translate_gate( - gate, - self.native_gates, - ) + translated_circuit.add( + translate_gate( + gate, + self.native_gates, ) - else: - two_qubit_translated_circuit.add(gate) - if self.translate_single_qubit: - for gate in two_qubit_translated_circuit.queue: - if len(gate.qubits) == 1: - translated_circuit.add( - translate_gate( - gate, - self.native_gates, - ) - ) - else: - translated_circuit.add(gate) - else: - translated_circuit = two_qubit_translated_circuit + ) return translated_circuit def is_satisfied(self, circuit: Circuit): @@ -73,8 +54,7 @@ def assert_decomposition( Args: circuit (:class:`qibo.models.circuit.Circuit`): circuit model to check. native_gates (:class:`qibo.transpiler.abstract.NativeGates`): - two-qubit native gates supported by the quantum hardware. - single_qubit_natives (tuple): single qubit native gates. + native gates supported by the quantum hardware. """ for gate in circuit.queue: if isinstance(gate, gates.M): @@ -128,7 +108,16 @@ def translate_gate( elif len(gate.qubits) == 1: return _translate_single_qubit_gates(gate, native_gates.single_qubit_natives()) else: - return _translate_two_qubit_gates(gate, native_gates) + decomposition_2q = _translate_two_qubit_gates(gate, native_gates) + final_decomposition = [] + for gate in decomposition_2q: + if len(gate.qubits) == 1: + final_decomposition += _translate_single_qubit_gates( + gate, native_gates.single_qubit_natives() + ) + else: + final_decomposition.append(gate) + return final_decomposition def _translate_single_qubit_gates(gate: gates.Gate, single_qubit_natives: NativeGates): diff --git a/tests/test_transpiler_decompositions.py b/tests/test_transpiler_decompositions.py index 5bf7c32266..194e6da420 100644 --- a/tests/test_transpiler_decompositions.py +++ b/tests/test_transpiler_decompositions.py @@ -6,7 +6,7 @@ from qibo.models import Circuit from qibo.quantum_info.random_ensembles import random_unitary from qibo.transpiler.abstract import NativeGates -from qibo.transpiler.unroller import translate_gate +from qibo.transpiler.unroller import assert_decomposition, translate_gate def assert_matrices_allclose(gate, natives, backend): @@ -35,6 +35,7 @@ def assert_matrices_allclose(gate, natives, backend): if np.allclose(phase * native_unitary, target_unitary, atol=1e-12): c = 1 backend.assert_allclose(c, 1) + assert_decomposition(circuit, natives) @pytest.mark.parametrize("natives", [NativeGates.U3, NativeGates.GPI2]) diff --git a/tests/test_transpiler_unroller.py b/tests/test_transpiler_unroller.py index dafa888dba..f5720d5e11 100644 --- a/tests/test_transpiler_unroller.py +++ b/tests/test_transpiler_unroller.py @@ -1,3 +1,4 @@ +import numpy as np import pytest from qibo import gates @@ -40,14 +41,48 @@ def test_assert_decomposition_fail_3q(): assert_decomposition(circuit, native_gates=NativeGates.default()) -def test_no_translate_single_qubit(): - unroller = DefaultUnroller( - native_gates=NativeGates.default(), translate_single_qubit=False - ) - circuit = Circuit(2) +@pytest.mark.parametrize( + "natives_2q", + [NativeGates.CZ, NativeGates.iSWAP, NativeGates.CZ | NativeGates.iSWAP], +) +@pytest.mark.parametrize( + "natives_1q", + [NativeGates.U3, NativeGates.GPI2, NativeGates.U3 | NativeGates.GPI2], +) +def test_unroller(natives_1q, natives_2q): + circuit = Circuit(3) + circuit.add(gates.H(0)) circuit.add(gates.X(0)) + circuit.add(gates.Y(0)) + circuit.add(gates.Z(0)) + circuit.add(gates.S(0)) + circuit.add(gates.T(0)) + circuit.add(gates.SDG(0)) + circuit.add(gates.TDG(0)) + circuit.add(gates.SX(0)) + circuit.add(gates.RX(0, 0.1)) + circuit.add(gates.RY(0, 0.2)) + circuit.add(gates.RZ(0, 0.3)) + circuit.add(gates.U1(0, 0.4)) + circuit.add(gates.U2(0, 0.5, 0.6)) + circuit.add(gates.U3(0, 0.7, 0.8, 0.9)) + circuit.add(gates.GPI2(0, 0.123)) + circuit.add(gates.CZ(0, 1)) circuit.add(gates.CNOT(0, 1)) + circuit.add(gates.SWAP(0, 1)) + circuit.add(gates.iSWAP(0, 1)) + circuit.add(gates.FSWAP(0, 1)) + circuit.add(gates.CRX(0, 1, 0.1)) + circuit.add(gates.CRY(0, 1, 0.2)) + circuit.add(gates.CRZ(0, 1, 0.3)) + circuit.add(gates.CU1(0, 1, 0.4)) + circuit.add(gates.CU2(0, 1, 0.5, 0.6)) + circuit.add(gates.CU3(0, 1, 0.7, 0.8, 0.9)) + circuit.add(gates.RXX(0, 1, 0.1)) + circuit.add(gates.RYY(0, 1, 0.2)) + circuit.add(gates.RZZ(0, 1, 0.3)) + circuit.add(gates.fSim(0, 1, 0.4, 0.5)) + circuit.add(gates.TOFFOLI(0, 1, 2)) + unroller = DefaultUnroller(native_gates=natives_1q | natives_2q) translated_circuit = unroller(circuit) - assert isinstance(translated_circuit.queue[0], gates.X) and isinstance( - translated_circuit.queue[2], gates.CZ - ) + assert_decomposition(translated_circuit, native_gates=natives_1q | natives_2q) From d71fac386019b60e3d2f7df6036830a468a26065 Mon Sep 17 00:00:00 2001 From: simone bordoni Date: Thu, 23 Nov 2023 14:00:49 +0400 Subject: [PATCH 07/10] fix advanced examples --- doc/source/code-examples/advancedexamples.rst | 10 +++++----- tests/test_transpiler_unroller.py | 1 - 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/doc/source/code-examples/advancedexamples.rst b/doc/source/code-examples/advancedexamples.rst index 2adbae6128..6b9b526cca 100644 --- a/doc/source/code-examples/advancedexamples.rst +++ b/doc/source/code-examples/advancedexamples.rst @@ -2011,10 +2011,10 @@ Multiple transpilation steps can be implemented using the :class:`qibo.transpile from qibo import gates from qibo.models import Circuit from qibo.transpiler.pipeline import Passes, assert_transpiling - from qibo.transpiler.abstract import NativeType + from qibo.transpiler.abstract import NativeGates from qibo.transpiler.optimizer import Preprocessing from qibo.transpiler.router import ShortestPaths - from qibo.transpiler.unroller import NativeGates + from qibo.transpiler.unroller import DefaultUnroller from qibo.transpiler.placer import Random # Define connectivity as nx.Graph @@ -2040,10 +2040,10 @@ Multiple transpilation steps can be implemented using the :class:`qibo.transpile # Routing step custom_passes.append(ShortestPaths(connectivity=star_connectivity())) # Gate decomposition step - custom_passes.append(NativeGates(two_qubit_natives=NativeType.iSWAP)) + custom_passes.append(DefaultUnroller(native_gates=NativeGates.default())) # Define the general pipeline - custom_pipeline = Passes(custom_passes, connectivity=star_connectivity(), native_gates=NativeType.iSWAP) + custom_pipeline = Passes(custom_passes, connectivity=star_connectivity(), native_gates=NativeGates.default()) # Call the transpiler pipeline on the circuit transpiled_circ, final_layout = custom_pipeline(circuit) @@ -2057,7 +2057,7 @@ Multiple transpilation steps can be implemented using the :class:`qibo.transpile connectivity=star_connectivity(), initial_layout=initial_layout, final_layout=final_layout, - native_gates=NativeType.iSWAP + native_gates=NativeGates.default() ) In this case circuits will first be transpiled to respect the 5-qubit star connectivity, with qubit 2 as the middle qubit. This will potentially add some SWAP gates. diff --git a/tests/test_transpiler_unroller.py b/tests/test_transpiler_unroller.py index f5720d5e11..99785e4b22 100644 --- a/tests/test_transpiler_unroller.py +++ b/tests/test_transpiler_unroller.py @@ -1,4 +1,3 @@ -import numpy as np import pytest from qibo import gates From dc4f362fc3f51ed282f1045575add26c1c47d569 Mon Sep 17 00:00:00 2001 From: simone bordoni Date: Fri, 24 Nov 2023 15:14:49 +0400 Subject: [PATCH 08/10] corrections from reviews --- doc/source/code-examples/advancedexamples.rst | 7 +- src/qibo/transpiler/abstract.py | 95 ------------------ src/qibo/transpiler/decompositions.py | 11 +-- src/qibo/transpiler/pipeline.py | 7 +- src/qibo/transpiler/placer.py | 3 +- src/qibo/transpiler/router.py | 28 +++++- src/qibo/transpiler/unroller.py | 96 ++++++++++++------- tests/test_transpiler_abstract.py | 23 ----- tests/test_transpiler_blocks.py | 1 - tests/test_transpiler_decompositions.py | 39 ++++---- tests/test_transpiler_pipeline.py | 9 +- tests/test_transpiler_router.py | 21 +--- tests/test_transpiler_unroller.py | 11 ++- 13 files changed, 134 insertions(+), 217 deletions(-) delete mode 100644 tests/test_transpiler_abstract.py diff --git a/doc/source/code-examples/advancedexamples.rst b/doc/source/code-examples/advancedexamples.rst index 6b9b526cca..821b6e1141 100644 --- a/doc/source/code-examples/advancedexamples.rst +++ b/doc/source/code-examples/advancedexamples.rst @@ -2011,10 +2011,9 @@ Multiple transpilation steps can be implemented using the :class:`qibo.transpile from qibo import gates from qibo.models import Circuit from qibo.transpiler.pipeline import Passes, assert_transpiling - from qibo.transpiler.abstract import NativeGates from qibo.transpiler.optimizer import Preprocessing from qibo.transpiler.router import ShortestPaths - from qibo.transpiler.unroller import DefaultUnroller + from qibo.transpiler.unroller import Unroller, NativeGates from qibo.transpiler.placer import Random # Define connectivity as nx.Graph @@ -2040,7 +2039,7 @@ Multiple transpilation steps can be implemented using the :class:`qibo.transpile # Routing step custom_passes.append(ShortestPaths(connectivity=star_connectivity())) # Gate decomposition step - custom_passes.append(DefaultUnroller(native_gates=NativeGates.default())) + custom_passes.append(Unroller(native_gates=NativeGates.default())) # Define the general pipeline custom_pipeline = Passes(custom_passes, connectivity=star_connectivity(), native_gates=NativeGates.default()) @@ -2061,6 +2060,6 @@ Multiple transpilation steps can be implemented using the :class:`qibo.transpile ) In this case circuits will first be transpiled to respect the 5-qubit star connectivity, with qubit 2 as the middle qubit. This will potentially add some SWAP gates. -Then all gates will be converted to native. The :class:`qibo.transpiler.unroller.NativeGates` transpiler used in this example assumes Z, RZ, GPI2 or U3 as +Then all gates will be converted to native. The :class:`qibo.transpiler.unroller.Unroller` transpiler used in this example assumes Z, RZ, GPI2 or U3 as the single-qubit native gates, and supports CZ and iSWAP as two-qubit natives. In this case we restricted the two-qubit gate set to CZ only. The final_layout contains the final logical-physical qubit mapping. diff --git a/src/qibo/transpiler/abstract.py b/src/qibo/transpiler/abstract.py index 2ae59c24cc..439045e72a 100644 --- a/src/qibo/transpiler/abstract.py +++ b/src/qibo/transpiler/abstract.py @@ -1,66 +1,11 @@ from abc import ABC, abstractmethod -from enum import Flag, auto from typing import Tuple import networkx as nx -from qibo import gates -from qibo.config import raise_error from qibo.models import Circuit -class NativeGates(Flag): - """Define native gates supported by the unroller. - A native gate set should contain at least one two-qubit gate (CZ or iSWAP) - and at least one single qubit gate (GPI2 or U3). - Gates I, Z, RZ and M are always included in the single qubit native gates set. - - Should have the same names with qibo gates. - """ - - I = auto() - Z = auto() - RZ = auto() - M = auto() - GPI2 = auto() - U3 = auto() - CZ = auto() - iSWAP = auto() - - @classmethod - def default(cls): - """Return default native gates set.""" - return cls.CZ | cls.GPI2 | cls.I | cls.Z | cls.RZ | cls.M - - @classmethod - def from_gatelist(cls, gatelist: list): - """Create a NativeGates object containing all gates from a gatelist.""" - natives = cls(0) - for gate in gatelist: - natives |= cls.from_gate(gate) - return natives - - @classmethod - def from_gate(cls, gate: gates.Gate): - """Create a NativeGates object from a gate. - The gate can be either a class:`qibo.gates.Gate` or an instance of this class. - """ - if isinstance(gate, gates.Gate): - return cls.from_gate(gate.__class__) - try: - return getattr(cls, gate.__name__) - except AttributeError: - raise ValueError(f"Gate {gate} cannot be used as native.") - - def single_qubit_natives(self): - """Return single qubit native gates in the native gates set.""" - return self & (self.GPI2 | self.U3) | (self.I | self.Z | self.RZ | self.M) - - def two_qubit_natives(self): - """Return two qubit native gates in the native gates set.""" - return self & (self.CZ | self.iSWAP) - - class Placer(ABC): @abstractmethod def __init__(self, connectivity: nx.Graph, *args): @@ -111,43 +56,3 @@ def __call__(self, circuit: Circuit, *args) -> Circuit: Returns: (:class:`qibo.models.circuit.Circuit`): circuit with optimized number of gates. """ - - -class Unroller(ABC): - @abstractmethod - def __init__(self, native_gates: NativeGates, *args): - """An unroller decomposes gates into native gates.""" - - @abstractmethod - def __call__(self, circuit: Circuit, *args) -> Circuit: - """Find initial qubit mapping - - Args: - circuit (:class:`qibo.models.circuit.Circuit`): circuit to be optimized - - Returns: - (:class:`qibo.models.circuit.Circuit`): circuit with native gates. - """ - - -def _find_gates_qubits_pairs(circuit: Circuit): - """Translate qibo circuit into a list of pairs of qubits to be used by the router and placer. - - Args: - circuit (:class:`qibo.models.circuit.Circuit`): circuit to be transpiled. - - Returns: - (list): list containing qubits targeted by two qubit gates. - """ - translated_circuit = [] - for gate in circuit.queue: - if isinstance(gate, gates.M): - pass - elif len(gate.qubits) == 2: - translated_circuit.append(sorted(gate.qubits)) - elif len(gate.qubits) >= 3: - raise_error( - ValueError, "Gates targeting more than 2 qubits are not supported" - ) - - return translated_circuit diff --git a/src/qibo/transpiler/decompositions.py b/src/qibo/transpiler/decompositions.py index 6b874310fc..cb106d423b 100644 --- a/src/qibo/transpiler/decompositions.py +++ b/src/qibo/transpiler/decompositions.py @@ -47,26 +47,25 @@ def __call__(self, gate): ] -def _u3_to_gpi2(t, p, l, optimize=True): - """Decompose a U3 gate into GPI2 gates. +def _u3_to_gpi2(t, p, l): + """Decompose a U3 gate into GPI2 gates, the decomposition is optimized to use the minimum number of gates.. Args: t (float): theta parameter of U3 gate. p (float): phi parameter of U3 gate. l (float): lambda parameter of U3 gate. - optimize (bool): if True, the decomposition is optimized to use the minimum number of gates. Returns: decomposition (list): list of native gates that decompose the U3 gate. """ decomposition = [] - if l != 0.0 or not optimize: + if l != 0.0: decomposition.append(gates.RZ(0, l)) decomposition.append(gates.GPI2(0, 0)) - if t != -np.pi or not optimize: + if t != -np.pi: decomposition.append(gates.RZ(0, t + np.pi)) decomposition.append(gates.GPI2(0, 0)) - if p != -np.pi or not optimize: + if p != -np.pi: decomposition.append(gates.RZ(0, p + np.pi)) return decomposition diff --git a/src/qibo/transpiler/pipeline.py b/src/qibo/transpiler/pipeline.py index b01fc9a142..b88da8da8f 100644 --- a/src/qibo/transpiler/pipeline.py +++ b/src/qibo/transpiler/pipeline.py @@ -7,7 +7,7 @@ from qibo.config import raise_error from qibo.models import Circuit from qibo.quantum_info.random_ensembles import random_statevector -from qibo.transpiler.abstract import NativeGates, Optimizer, Placer, Router, Unroller +from qibo.transpiler.abstract import Optimizer, Placer, Router from qibo.transpiler.exceptions import TranspilerPipelineError from qibo.transpiler.optimizer import Preprocessing from qibo.transpiler.placer import Trivial, assert_placement @@ -15,7 +15,8 @@ from qibo.transpiler.star_connectivity import StarConnectivity from qibo.transpiler.unroller import ( DecompositionError, - DefaultUnroller, + NativeGates, + Unroller, assert_decomposition, ) @@ -167,7 +168,7 @@ def default(self, connectivity: nx.Graph): # default router pass default_passes.append(StarConnectivity()) # default unroller pass - default_passes.append(DefaultUnroller(native_gates=self.native_gates)) + default_passes.append(Unroller(native_gates=self.native_gates)) return default_passes def __call__(self, circuit): diff --git a/src/qibo/transpiler/placer.py b/src/qibo/transpiler/placer.py index cb9ed21a2b..aa33536445 100644 --- a/src/qibo/transpiler/placer.py +++ b/src/qibo/transpiler/placer.py @@ -6,8 +6,9 @@ from qibo import gates from qibo.config import raise_error from qibo.models import Circuit -from qibo.transpiler.abstract import Placer, Router, _find_gates_qubits_pairs +from qibo.transpiler.abstract import Placer, Router from qibo.transpiler.exceptions import PlacementError +from qibo.transpiler.router import _find_gates_qubits_pairs def assert_placement(circuit: Circuit, layout: dict) -> bool: diff --git a/src/qibo/transpiler/router.py b/src/qibo/transpiler/router.py index d0a32e23d9..8bea586444 100644 --- a/src/qibo/transpiler/router.py +++ b/src/qibo/transpiler/router.py @@ -7,10 +7,9 @@ from qibo import gates from qibo.config import log, raise_error from qibo.models import Circuit -from qibo.transpiler.abstract import Router, _find_gates_qubits_pairs +from qibo.transpiler.abstract import Router from qibo.transpiler.blocks import Block, CircuitBlocks from qibo.transpiler.exceptions import ConnectivityError -from qibo.transpiler.placer import assert_placement def assert_connectivity(connectivity: nx.Graph, circuit: Circuit): @@ -248,7 +247,6 @@ def _initial_checks(self, qubits: int): "You are using more physical qubits than required by the circuit, some ancillary qubits will be added to the circuit." ) new_circuit = Circuit(nodes) - assert_placement(new_circuit, self._mapping) self._transpiled_circuit = new_circuit def _add_gates(self, circuit: Circuit, matched_gates: int): @@ -344,6 +342,30 @@ def _remap_circuit(self, qubit_map): return new_circuit +def _find_gates_qubits_pairs(circuit: Circuit): + """Helper method for :meth:`qibo.transpiler.router.ShortestPaths`. + Translate qibo circuit into a list of pairs of qubits to be used by the router and placer. + + Args: + circuit (:class:`qibo.models.circuit.Circuit`): circuit to be transpiled. + + Returns: + (list): list containing qubits targeted by two qubit gates. + """ + translated_circuit = [] + for gate in circuit.queue: + if isinstance(gate, gates.M): + pass + elif len(gate.qubits) == 2: + translated_circuit.append(sorted(gate.qubits)) + elif len(gate.qubits) >= 3: + raise_error( + ValueError, "Gates targeting more than 2 qubits are not supported" + ) + + return translated_circuit + + class CircuitMap: """Class to keep track of the circuit and physical-logical mapping during routing, this class also implements the initial two qubit blocks decomposition. diff --git a/src/qibo/transpiler/unroller.py b/src/qibo/transpiler/unroller.py index 1c1c342402..2965102082 100644 --- a/src/qibo/transpiler/unroller.py +++ b/src/qibo/transpiler/unroller.py @@ -1,19 +1,64 @@ +from enum import Flag, auto + from qibo import gates from qibo.config import raise_error from qibo.models import Circuit -from qibo.transpiler.abstract import NativeGates, Unroller from qibo.transpiler.decompositions import cz_dec, gpi2_dec, iswap_dec, opt_dec, u3_dec from qibo.transpiler.exceptions import DecompositionError +class NativeGates(Flag): + """Define native gates supported by the unroller. + A native gate set should contain at least one two-qubit gate (CZ or iSWAP) + and at least one single qubit gate (GPI2 or U3). + Gates I, Z, RZ and M are always included in the single qubit native gates set. + + Should have the same names with qibo gates. + """ + + I = auto() + Z = auto() + RZ = auto() + M = auto() + GPI2 = auto() + U3 = auto() + CZ = auto() + iSWAP = auto() + + @classmethod + def default(cls): + """Return default native gates set.""" + return cls.CZ | cls.GPI2 | cls.I | cls.Z | cls.RZ | cls.M + + @classmethod + def from_gatelist(cls, gatelist: list): + """Create a NativeGates object containing all gates from a gatelist.""" + natives = cls(0) + for gate in gatelist: + natives |= cls.from_gate(gate) + return natives + + @classmethod + def from_gate(cls, gate: gates.Gate): + """Create a NativeGates object from a gate. + The gate can be either a class:`qibo.gates.Gate` or an instance of this class. + """ + if isinstance(gate, gates.Gate): + return cls.from_gate(gate.__class__) + try: + return getattr(cls, gate.__name__) + except AttributeError: + raise ValueError(f"Gate {gate} cannot be used as native.") + + # TODO: Make setting single-qubit native gates more flexible -class DefaultUnroller(Unroller): +class Unroller: """Translates a circuit to native gates. Args: circuit (:class:`qibo.models.circuit.Circuit`): circuit model to translate into native gates. - native_gates (:class:`qibo.transpiler.abstract.NativeGates`): native gates supported by the quantum hardware. + native_gates (:class:`qibo.transpiler.unroller.NativeGates`): native gates to use in the transpiled circuit. Returns: (:class:`qibo.models.circuit.Circuit`): equivalent circuit with native gates. @@ -53,36 +98,24 @@ def assert_decomposition( Args: circuit (:class:`qibo.models.circuit.Circuit`): circuit model to check. - native_gates (:class:`qibo.transpiler.abstract.NativeGates`): - native gates supported by the quantum hardware. + native_gates (:class:`qibo.transpiler.unroller.NativeGates`): + native gates in the transpiled circuit. """ for gate in circuit.queue: if isinstance(gate, gates.M): continue - if len(gate.qubits) == 1: + if len(gate.qubits) <= 2: try: native_type_gate = NativeGates.from_gate(gate) - if not (native_type_gate & native_gates.single_qubit_natives()): + if not (native_type_gate & native_gates): raise_error( DecompositionError, - f"{gate.name} is not a single qubit native gate.", + f"{gate.name} is not a native gate.", ) except ValueError: raise_error( DecompositionError, - f"{gate.name} is not a single qubit native gate.", - ) - elif len(gate.qubits) == 2: - try: - native_type_gate = NativeGates.from_gate(gate) - if not (native_type_gate & native_gates.two_qubit_natives()): - raise_error( - DecompositionError, - f"{gate.name} is not a two qubits native gate.", - ) - except ValueError: - raise_error( - DecompositionError, f"{gate.name} is not a two qubits native gate." + f"{gate.name} is not a native gate.", ) else: raise_error( @@ -98,7 +131,7 @@ def translate_gate( Args: gate (:class:`qibo.gates.abstract.Gate`): gate to be decomposed. - native_gates (:class:`qibo.transpiler.abstract.NativeGates`): native gates supported by the quantum hardware. + native_gates (:class:`qibo.transpiler.unroller.NativeGates`): native gates to use in the decomposition. Returns: (list): List of native gates that decompose the input gate. @@ -106,15 +139,13 @@ def translate_gate( if isinstance(gate, (gates.M, gates.I, gates.Align)): return gate elif len(gate.qubits) == 1: - return _translate_single_qubit_gates(gate, native_gates.single_qubit_natives()) + return _translate_single_qubit_gates(gate, native_gates) else: decomposition_2q = _translate_two_qubit_gates(gate, native_gates) final_decomposition = [] for gate in decomposition_2q: if len(gate.qubits) == 1: - final_decomposition += _translate_single_qubit_gates( - gate, native_gates.single_qubit_natives() - ) + final_decomposition += _translate_single_qubit_gates(gate, native_gates) else: final_decomposition.append(gate) return final_decomposition @@ -127,7 +158,7 @@ def _translate_single_qubit_gates(gate: gates.Gate, single_qubit_natives: Native Args: gate (:class:`qibo.gates.abstract.Gate`): gate to be decomposed. - single_qubit_natives (:class:`qibo.transpiler.abstract.NativeGates`): single qubit native gates. + single_qubit_natives (:class:`qibo.transpiler.unroller.NativeGates`): single qubit native gates. Returns: (list): List of native gates that decompose the input gate. @@ -147,13 +178,14 @@ def _translate_two_qubit_gates(gate: gates.Gate, native_gates: NativeGates): Args: gate (:class:`qibo.gates.abstract.Gate`): gate to be decomposed. - native_gates (:class:`qibo.transpiler.abstract.NativeGates`): native gates supported by the quantum hardware. + native_gates (:class:`qibo.transpiler.unroller.NativeGates`): native gates supported by the quantum hardware. Returns: (list): List of native gates that decompose the input gate. """ - two_qubit_natives = native_gates.two_qubit_natives() - if two_qubit_natives is NativeGates.CZ | NativeGates.iSWAP: + if ( + native_gates & (NativeGates.CZ | NativeGates.iSWAP) + ) is NativeGates.CZ | NativeGates.iSWAP: # Check for a special optimized decomposition. if gate.__class__ in opt_dec.decompositions: return opt_dec(gate) @@ -172,9 +204,9 @@ def _translate_two_qubit_gates(gate: gates.Gate, native_gates: NativeGates): return cz_dec(gate) else: # pragma: no cover return iswap_dec(gate) - elif two_qubit_natives is NativeGates.CZ: + elif native_gates & NativeGates.CZ: return cz_dec(gate) - elif two_qubit_natives is NativeGates.iSWAP: + elif native_gates & NativeGates.iSWAP: if gate.__class__ in iswap_dec.decompositions: return iswap_dec(gate) else: diff --git a/tests/test_transpiler_abstract.py b/tests/test_transpiler_abstract.py deleted file mode 100644 index 9967e5295a..0000000000 --- a/tests/test_transpiler_abstract.py +++ /dev/null @@ -1,23 +0,0 @@ -import pytest - -from qibo import gates -from qibo.models import Circuit -from qibo.transpiler.abstract import _find_gates_qubits_pairs - - -def test_circuit_representation(): - circuit = Circuit(5) - circuit.add(gates.CNOT(1, 0)) - circuit.add(gates.CNOT(2, 0)) - circuit.add(gates.X(1)) - circuit.add(gates.CZ(3, 0)) - circuit.add(gates.CNOT(4, 0)) - repr = _find_gates_qubits_pairs(circuit) - assert repr == [[0, i + 1] for i in range(4)] - - -def test_circuit_representation_fail(): - circuit = Circuit(5) - circuit.add(gates.TOFFOLI(0, 1, 2)) - with pytest.raises(ValueError): - repr = _find_gates_qubits_pairs(circuit) diff --git a/tests/test_transpiler_blocks.py b/tests/test_transpiler_blocks.py index 4d2446c806..48207890bd 100644 --- a/tests/test_transpiler_blocks.py +++ b/tests/test_transpiler_blocks.py @@ -1,4 +1,3 @@ -import numpy as np import pytest from qibo import Circuit, gates diff --git a/tests/test_transpiler_decompositions.py b/tests/test_transpiler_decompositions.py index 194e6da420..5ce8d97cca 100644 --- a/tests/test_transpiler_decompositions.py +++ b/tests/test_transpiler_decompositions.py @@ -5,8 +5,9 @@ from qibo.backends import NumpyBackend from qibo.models import Circuit from qibo.quantum_info.random_ensembles import random_unitary -from qibo.transpiler.abstract import NativeGates -from qibo.transpiler.unroller import assert_decomposition, translate_gate +from qibo.transpiler.unroller import NativeGates, assert_decomposition, translate_gate + +default_natives = NativeGates.Z | NativeGates.RZ | NativeGates.M | NativeGates.I def assert_matrices_allclose(gate, natives, backend): @@ -42,45 +43,45 @@ def assert_matrices_allclose(gate, natives, backend): @pytest.mark.parametrize("gatename", ["H", "X", "Y", "I"]) def test_pauli_to_native(backend, gatename, natives): gate = getattr(gates, gatename)(0) - assert_matrices_allclose(gate, natives=natives, backend=backend) + assert_matrices_allclose(gate, natives=natives | default_natives, backend=backend) @pytest.mark.parametrize("natives", [NativeGates.U3, NativeGates.GPI2]) @pytest.mark.parametrize("gatename", ["RX", "RY", "RZ"]) def test_rotations_to_native(backend, gatename, natives): gate = getattr(gates, gatename)(0, theta=0.1) - assert_matrices_allclose(gate, natives=natives, backend=backend) + assert_matrices_allclose(gate, natives=natives | default_natives, backend=backend) @pytest.mark.parametrize("natives", [NativeGates.U3, NativeGates.GPI2]) @pytest.mark.parametrize("gatename", ["S", "SDG", "T", "TDG", "SX"]) def test_special_single_qubit_to_native(backend, gatename, natives): gate = getattr(gates, gatename)(0) - assert_matrices_allclose(gate, natives=natives, backend=backend) + assert_matrices_allclose(gate, natives=natives | default_natives, backend=backend) @pytest.mark.parametrize("natives", [NativeGates.U3, NativeGates.GPI2]) def test_u1_to_native(backend, natives): gate = gates.U1(0, theta=0.5) - assert_matrices_allclose(gate, natives=natives, backend=backend) + assert_matrices_allclose(gate, natives=natives | default_natives, backend=backend) @pytest.mark.parametrize("natives", [NativeGates.U3, NativeGates.GPI2]) def test_u2_to_native(backend, natives): gate = gates.U2(0, phi=0.1, lam=0.3) - assert_matrices_allclose(gate, natives=natives, backend=backend) + assert_matrices_allclose(gate, natives=natives | default_natives, backend=backend) @pytest.mark.parametrize("natives", [NativeGates.U3, NativeGates.GPI2]) def test_u3_to_native(backend, natives): gate = gates.U3(0, theta=0.2, phi=0.1, lam=0.3) - assert_matrices_allclose(gate, natives=natives, backend=backend) + assert_matrices_allclose(gate, natives=natives | default_natives, backend=backend) @pytest.mark.parametrize("natives", [NativeGates.U3, NativeGates.GPI2]) def test_gpi2_to_native(backend, natives): gate = gates.GPI2(0, phi=0.123) - assert_matrices_allclose(gate, natives=natives, backend=backend) + assert_matrices_allclose(gate, natives=natives | default_natives, backend=backend) @pytest.mark.parametrize("gatename", ["CNOT", "CZ", "SWAP", "iSWAP", "FSWAP"]) @@ -94,7 +95,7 @@ def test_gpi2_to_native(backend, natives): ) def test_two_qubit_to_native(backend, gatename, natives_1q, natives_2q): gate = getattr(gates, gatename)(0, 1) - assert_matrices_allclose(gate, natives_1q | natives_2q, backend) + assert_matrices_allclose(gate, natives_1q | natives_2q | default_natives, backend) @pytest.mark.parametrize( @@ -108,7 +109,7 @@ def test_two_qubit_to_native(backend, gatename, natives_1q, natives_2q): @pytest.mark.parametrize("gatename", ["CRX", "CRY", "CRZ"]) def test_controlled_rotations_to_native(backend, gatename, natives_1q, natives_2q): gate = getattr(gates, gatename)(0, 1, 0.3) - assert_matrices_allclose(gate, natives_1q | natives_2q, backend) + assert_matrices_allclose(gate, natives_1q | natives_2q | default_natives, backend) @pytest.mark.parametrize( @@ -121,7 +122,7 @@ def test_controlled_rotations_to_native(backend, gatename, natives_1q, natives_2 ) def test_cu1_to_native(backend, natives_1q, natives_2q): gate = gates.CU1(0, 1, theta=0.4) - assert_matrices_allclose(gate, natives_1q | natives_2q, backend) + assert_matrices_allclose(gate, natives_1q | natives_2q | default_natives, backend) @pytest.mark.parametrize( @@ -134,7 +135,7 @@ def test_cu1_to_native(backend, natives_1q, natives_2q): ) def test_cu2_to_native(backend, natives_1q, natives_2q): gate = gates.CU2(0, 1, phi=0.2, lam=0.3) - assert_matrices_allclose(gate, natives_1q | natives_2q, backend) + assert_matrices_allclose(gate, natives_1q | natives_2q | default_natives, backend) @pytest.mark.parametrize( @@ -147,7 +148,7 @@ def test_cu2_to_native(backend, natives_1q, natives_2q): ) def test_cu3_to_native(backend, natives_1q, natives_2q): gate = gates.CU3(0, 1, theta=0.2, phi=0.3, lam=0.4) - assert_matrices_allclose(gate, natives_1q | natives_2q, backend) + assert_matrices_allclose(gate, natives_1q | natives_2q | default_natives, backend) @pytest.mark.parametrize( @@ -160,7 +161,7 @@ def test_cu3_to_native(backend, natives_1q, natives_2q): ) def test_fSim_to_native(backend, natives_1q, natives_2q): gate = gates.fSim(0, 1, theta=0.3, phi=0.1) - assert_matrices_allclose(gate, natives_1q | natives_2q, backend) + assert_matrices_allclose(gate, natives_1q | natives_2q | default_natives, backend) @pytest.mark.parametrize("seed", [None, 10, np.random.default_rng(10)]) @@ -175,7 +176,7 @@ def test_fSim_to_native(backend, natives_1q, natives_2q): def test_GeneralizedfSim_to_native(backend, natives_1q, natives_2q, seed): unitary = random_unitary(2, seed=seed, backend=backend) gate = gates.GeneralizedfSim(0, 1, unitary, phi=0.1) - assert_matrices_allclose(gate, natives_1q | natives_2q, backend) + assert_matrices_allclose(gate, natives_1q | natives_2q | default_natives, backend) @pytest.mark.parametrize( @@ -189,7 +190,7 @@ def test_GeneralizedfSim_to_native(backend, natives_1q, natives_2q, seed): @pytest.mark.parametrize("gatename", ["RXX", "RZZ", "RYY"]) def test_rnn_to_native(backend, gatename, natives_1q, natives_2q): gate = getattr(gates, gatename)(0, 1, theta=0.1) - assert_matrices_allclose(gate, natives_1q | natives_2q, backend) + assert_matrices_allclose(gate, natives_1q | natives_2q | default_natives, backend) @pytest.mark.parametrize( @@ -202,7 +203,7 @@ def test_rnn_to_native(backend, gatename, natives_1q, natives_2q): ) def test_TOFFOLI_to_native(backend, natives_1q, natives_2q): gate = gates.TOFFOLI(0, 1, 2) - assert_matrices_allclose(gate, natives_1q | natives_2q, backend) + assert_matrices_allclose(gate, natives_1q | natives_2q | default_natives, backend) @pytest.mark.parametrize("seed", [None, 10, np.random.default_rng(10)]) @@ -220,7 +221,7 @@ def test_unitary_to_native(backend, nqubits, natives_1q, natives_2q, seed): # transform to SU(2^nqubits) form u = u / np.sqrt(np.linalg.det(u)) gate = gates.Unitary(u, *range(nqubits)) - assert_matrices_allclose(gate, natives_1q | natives_2q, backend) + assert_matrices_allclose(gate, natives_1q | natives_2q | default_natives, backend) def test_count_1q(): diff --git a/tests/test_transpiler_pipeline.py b/tests/test_transpiler_pipeline.py index 04786c2430..1c72b3eeaa 100644 --- a/tests/test_transpiler_pipeline.py +++ b/tests/test_transpiler_pipeline.py @@ -1,12 +1,9 @@ -import itertools - import networkx as nx import numpy as np import pytest from qibo import gates from qibo.models import Circuit -from qibo.transpiler.abstract import NativeGates from qibo.transpiler.optimizer import Preprocessing from qibo.transpiler.pipeline import ( Passes, @@ -16,7 +13,7 @@ ) from qibo.transpiler.placer import Random, ReverseTraversal, Trivial from qibo.transpiler.router import ShortestPaths -from qibo.transpiler.unroller import DefaultUnroller +from qibo.transpiler.unroller import NativeGates, Unroller def generate_random_circuit(nqubits, ngates, seed=None): @@ -165,7 +162,7 @@ def test_custom_passes(circ): custom_passes.append(Preprocessing(connectivity=star_connectivity())) custom_passes.append(Random(connectivity=star_connectivity())) custom_passes.append(ShortestPaths(connectivity=star_connectivity())) - custom_passes.append(DefaultUnroller(native_gates=NativeGates.default())) + custom_passes.append(Unroller(native_gates=NativeGates.default())) custom_pipeline = Passes( custom_passes, connectivity=star_connectivity(), @@ -197,7 +194,7 @@ def test_custom_passes_reverse(circ): ) ) custom_passes.append(ShortestPaths(connectivity=star_connectivity())) - custom_passes.append(DefaultUnroller(native_gates=NativeGates.default())) + custom_passes.append(Unroller(native_gates=NativeGates.default())) custom_pipeline = Passes( custom_passes, connectivity=star_connectivity(), diff --git a/tests/test_transpiler_router.py b/tests/test_transpiler_router.py index 28a0c9b15f..870a5b59db 100644 --- a/tests/test_transpiler_router.py +++ b/tests/test_transpiler_router.py @@ -6,14 +6,7 @@ from qibo.models import Circuit from qibo.transpiler.optimizer import Preprocessing from qibo.transpiler.pipeline import assert_circuit_equivalence -from qibo.transpiler.placer import ( - Custom, - PlacementError, - Random, - Subgraph, - Trivial, - assert_placement, -) +from qibo.transpiler.placer import Custom, Random, Subgraph, Trivial, assert_placement from qibo.transpiler.router import ( CircuitMap, ConnectivityError, @@ -120,18 +113,6 @@ def test_insufficient_qubits(): transpiler(circuit, initial_layout) -def test_incorrect_initial_layout(): - placer = Trivial() - circuit = Circuit(4) - circuit.add(gates.CNOT(1, 0)) - circuit.add(gates.CNOT(2, 0)) - circuit.add(gates.CNOT(3, 0)) - initial_layout = placer(circuit) - transpiler = ShortestPaths(connectivity=star_connectivity()) - with pytest.raises(PlacementError): - transpiler(circuit, initial_layout) - - @pytest.mark.parametrize("gates", [5, 25]) @pytest.mark.parametrize("qubits", [3, 5]) @pytest.mark.parametrize("placer", [Trivial, Random]) diff --git a/tests/test_transpiler_unroller.py b/tests/test_transpiler_unroller.py index 99785e4b22..0a848ddcbc 100644 --- a/tests/test_transpiler_unroller.py +++ b/tests/test_transpiler_unroller.py @@ -2,10 +2,10 @@ from qibo import gates from qibo.models import Circuit -from qibo.transpiler.abstract import NativeGates from qibo.transpiler.unroller import ( DecompositionError, - DefaultUnroller, + NativeGates, + Unroller, assert_decomposition, ) @@ -82,6 +82,9 @@ def test_unroller(natives_1q, natives_2q): circuit.add(gates.RZZ(0, 1, 0.3)) circuit.add(gates.fSim(0, 1, 0.4, 0.5)) circuit.add(gates.TOFFOLI(0, 1, 2)) - unroller = DefaultUnroller(native_gates=natives_1q | natives_2q) + unroller = Unroller(native_gates=natives_1q | natives_2q) translated_circuit = unroller(circuit) - assert_decomposition(translated_circuit, native_gates=natives_1q | natives_2q) + assert_decomposition( + translated_circuit, + native_gates=natives_1q | natives_2q | NativeGates.RZ | NativeGates.Z, + ) From 469ad0e915150dd71cc685ae758a5c638e5a6e09 Mon Sep 17 00:00:00 2001 From: simone bordoni Date: Fri, 24 Nov 2023 15:38:45 +0400 Subject: [PATCH 09/10] improve coverage --- src/qibo/transpiler/unroller.py | 8 -------- tests/test_transpiler_unroller.py | 23 +++++++++++++++++++++++ 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/qibo/transpiler/unroller.py b/src/qibo/transpiler/unroller.py index 2965102082..ef5f0c8d30 100644 --- a/src/qibo/transpiler/unroller.py +++ b/src/qibo/transpiler/unroller.py @@ -81,14 +81,6 @@ def __call__(self, circuit: Circuit): ) return translated_circuit - def is_satisfied(self, circuit: Circuit): - """Return True if a circuit is correctly decomposed into native gates, otherwise False.""" - try: - assert_decomposition(circuit=circuit, native_gates=self.native_gates) - except DecompositionError: - return False - return True - def assert_decomposition( circuit: Circuit, diff --git a/tests/test_transpiler_unroller.py b/tests/test_transpiler_unroller.py index 0a848ddcbc..26529c8f2c 100644 --- a/tests/test_transpiler_unroller.py +++ b/tests/test_transpiler_unroller.py @@ -7,9 +7,32 @@ NativeGates, Unroller, assert_decomposition, + translate_gate, ) +def test_native_gates_from_gatelist(): + natives = NativeGates.from_gatelist([gates.RZ, gates.CZ(0, 1)]) + assert natives == NativeGates.RZ | NativeGates.CZ + + +def test_native_gates_from_gatelist_fail(): + with pytest.raises(ValueError): + NativeGates.from_gatelist([gates.RZ, gates.X(0)]) + + +def test_translate_gate_error_1q(): + natives = NativeGates(0) + with pytest.raises(DecompositionError): + translate_gate(gates.X(0), natives) + + +def test_translate_gate_error_2q(): + natives = NativeGates(0) + with pytest.raises(DecompositionError): + translate_gate(gates.CZ(0, 1), natives) + + def test_assert_decomposition(): circuit = Circuit(2) circuit.add(gates.CZ(0, 1)) From c9b57a3bb3dee40673c929fc55467a33a7b1190f Mon Sep 17 00:00:00 2001 From: simone bordoni Date: Sat, 25 Nov 2023 01:31:59 +0400 Subject: [PATCH 10/10] fixed coverage --- tests/test_transpiler_router.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/test_transpiler_router.py b/tests/test_transpiler_router.py index 870a5b59db..621f7f69e3 100644 --- a/tests/test_transpiler_router.py +++ b/tests/test_transpiler_router.py @@ -12,6 +12,7 @@ ConnectivityError, Sabre, ShortestPaths, + _find_gates_qubits_pairs, assert_connectivity, ) @@ -113,6 +114,13 @@ def test_insufficient_qubits(): transpiler(circuit, initial_layout) +def test_find_pairs_error(): + circuit = Circuit(3) + circuit.add(gates.TOFFOLI(0, 1, 2)) + with pytest.raises(ValueError): + _find_gates_qubits_pairs(circuit) + + @pytest.mark.parametrize("gates", [5, 25]) @pytest.mark.parametrize("qubits", [3, 5]) @pytest.mark.parametrize("placer", [Trivial, Random])