diff --git a/doc/source/code-examples/advancedexamples.rst b/doc/source/code-examples/advancedexamples.rst index 6ea78b1836..32da8b0129 100644 --- a/doc/source/code-examples/advancedexamples.rst +++ b/doc/source/code-examples/advancedexamples.rst @@ -1314,9 +1314,9 @@ Let's see how to use them. For starters, let's define a dummy circuit with some # visualize the circuit circuit.draw() - # q0: ─RZ─RX─RZ─RX─RZ─o────o────────M─ - # q1: ─RZ─RX─RZ─RX─RZ─X─RZ─X─o────o─M─ - # q2: ─RZ─RX─RZ─RX─RZ────────X─RZ─X─M─ + # 0: ─RZ─RX─RZ─RX─RZ─o────o────────M─ + # 1: ─RZ─RX─RZ─RX─RZ─X─RZ─X─o────o─M─ + # 2: ─RZ─RX─RZ─RX─RZ────────X─RZ─X─M─ .. testoutput:: :hide: @@ -1432,12 +1432,12 @@ number of CNOT or RX pairs (depending on the value of ``insertion_gate``) insert circuit in correspondence to the original ones. Since we decided to simulate noisy CNOTs:: Level 1 - q0: ─X─ --> q0: ─X───X──X─ - q1: ─o─ --> q1: ─o───o──o─ + 0: ─X─ --> 0: ─X───X──X─ + 1: ─o─ --> 1: ─o───o──o─ Level 2 - q0: ─X─ --> q0: ─X───X──X───X──X─ - q1: ─o─ --> q1: ─o───o──o───o──o─ + 0: ─X─ --> 0: ─X───X──X───X──X─ + 1: ─o─ --> 1: ─o───o──o───o──o─ . . @@ -2139,8 +2139,6 @@ Qibo implements a built-in transpiler with customizable options for each step. T be used at each transpiler step are reported below with a short description. The initial placement can be found with one of the following procedures: -- Trivial: logical-physical qubit mapping is an identity. -- Custom: custom logical-physical qubit mapping. - Random greedy: the best mapping is found within a set of random layouts based on a greedy policy. - Subgraph isomorphism: the initial mapping is the one that guarantees the execution of most gates at the beginning of the circuit without introducing any SWAP. @@ -2168,22 +2166,22 @@ 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.pipeline import Passes from qibo.transpiler.optimizer import Preprocessing from qibo.transpiler.router import ShortestPaths from qibo.transpiler.unroller import Unroller, NativeGates from qibo.transpiler.placer import Random + from qibo.transpiler.asserts import assert_transpiling # Define connectivity as nx.Graph def star_connectivity(): - chip = nx.Graph() - chip.add_nodes_from(list(range(5))) - graph_list = [(i, 2) for i in range(5) if i != 2] - chip.add_edges_from(graph_list) + chip = nx.Graph([("q0", "q2"), ("q1", "q2"), ("q2", "q3"), ("q2", "q4")]) return chip # Define the circuit - circuit = Circuit(2) + # wire_names must match nodes in the connectivity graph. + # The index in wire_names represents the logical qubit number in the circuit. + circuit = Circuit(2, wire_names=["q0", "q1"]) circuit.add(gates.H(0)) circuit.add(gates.CZ(0, 1)) @@ -2205,13 +2203,10 @@ Multiple transpilation steps can be implemented using the :class:`qibo.transpile transpiled_circ, final_layout = custom_pipeline(circuit) # Optinally call assert_transpiling to check that the final circuit can be executed on hardware - # For this test it is necessary to get the initial layout - initial_layout = custom_pipeline.get_initial_layout() assert_transpiling( original_circuit=circuit, transpiled_circuit=transpiled_circ, connectivity=star_connectivity(), - initial_layout=initial_layout, final_layout=final_layout, native_gates=NativeGates.default() ) diff --git a/doc/source/code-examples/examples.rst b/doc/source/code-examples/examples.rst index 8e3b849a38..bcfdd8511e 100644 --- a/doc/source/code-examples/examples.rst +++ b/doc/source/code-examples/examples.rst @@ -313,20 +313,20 @@ For example circuit.draw() # Prints ''' - q0: ─H─U1─U1─U1─U1───────────────────────────x─── - q1: ───o──|──|──|──H─U1─U1─U1────────────────|─x─ - q2: ──────o──|──|────o──|──|──H─U1─U1────────|─|─ - q3: ─────────o──|───────o──|────o──|──H─U1───|─x─ - q4: ────────────o──────────o───────o────o──H─x─── + 0: ─H─U1─U1─U1─U1───────────────────────────x─── + 1: ───o──|──|──|──H─U1─U1─U1────────────────|─x─ + 2: ──────o──|──|────o──|──|──H─U1─U1────────|─|─ + 3: ─────────o──|───────o──|────o──|──H─U1───|─x─ + 4: ────────────o──────────o───────o────o──H─x─── ''' .. testoutput:: :hide: - q0: ─H─U1─U1─U1─U1───────────────────────────x─── - q1: ───o──|──|──|──H─U1─U1─U1────────────────|─x─ - q2: ──────o──|──|────o──|──|──H─U1─U1────────|─|─ - q3: ─────────o──|───────o──|────o──|──H─U1───|─x─ - q4: ────────────o──────────o───────o────o──H─x─── + 0: ─H─U1─U1─U1─U1───────────────────────────x─── + 1: ───o──|──|──|──H─U1─U1─U1────────────────|─x─ + 2: ──────o──|──|────o──|──|──H─U1─U1────────|─|─ + 3: ─────────o──|───────o──|────o──|──H─U1───|─x─ + 4: ────────────o──────────o───────o────o──H─x─── How to visualize a circuit with style? -------------------------------------- diff --git a/src/qibo/backends/__init__.py b/src/qibo/backends/__init__.py index 91902da7d5..ada0204318 100644 --- a/src/qibo/backends/__init__.py +++ b/src/qibo/backends/__init__.py @@ -136,7 +136,6 @@ def set_transpiler(cls, transpiler): def _default_transpiler(cls): from qibo.transpiler.optimizer import Preprocessing from qibo.transpiler.pipeline import Passes - from qibo.transpiler.placer import Trivial from qibo.transpiler.router import Sabre from qibo.transpiler.unroller import NativeGates, Unroller @@ -148,21 +147,12 @@ def _default_transpiler(cls): and natives is not None and connectivity_edges is not None ): - # only for q{i} naming - node_mapping = {q: i for i, q in enumerate(qubits)} - edges = [ - (node_mapping[e[0]], node_mapping[e[1]]) for e in connectivity_edges - ] - connectivity = nx.Graph() - connectivity.add_nodes_from(list(node_mapping.values())) - connectivity.add_edges_from(edges) - + connectivity = nx.Graph(connectivity_edges) return Passes( connectivity=connectivity, passes=[ - Preprocessing(connectivity), - Trivial(connectivity), - Sabre(connectivity), + Preprocessing(), + Sabre(), Unroller(NativeGates[natives]), ], ) diff --git a/src/qibo/gates/abstract.py b/src/qibo/gates/abstract.py index 7b24407048..2128f9dc16 100644 --- a/src/qibo/gates/abstract.py +++ b/src/qibo/gates/abstract.py @@ -273,10 +273,10 @@ def on_qubits(self, qubit_map) -> "Gate": circuit.draw() .. testoutput:: - q0: ───X───── - q1: ───|─o─X─ - q2: ─o─|─|─o─ - q3: ─X─o─X─── + 0: ───X───── + 1: ───|─o─X─ + 2: ─o─|─|─o─ + 3: ─X─o─X─── """ if self.is_controlled_by: targets = (qubit_map.get(q) for q in self.target_qubits) diff --git a/src/qibo/gates/measurements.py b/src/qibo/gates/measurements.py index 0e2ccec754..b9c075ce9e 100644 --- a/src/qibo/gates/measurements.py +++ b/src/qibo/gates/measurements.py @@ -252,9 +252,9 @@ def on_qubits(self, qubit_map) -> "Gate": circuit.draw() .. testoutput:: - q0: ─M─ - q1: ─|─ - q2: ─M─ + 0: ─M─ + 1: ─|─ + 2: ─M─ """ qubits = (qubit_map.get(q) for q in self.qubits) diff --git a/src/qibo/models/circuit.py b/src/qibo/models/circuit.py index 4e65259719..31f8570d88 100644 --- a/src/qibo/models/circuit.py +++ b/src/qibo/models/circuit.py @@ -119,8 +119,23 @@ class Circuit: This circuit is symbolic and cannot perform calculations. A specific backend has to be used for performing calculations. + Circuits can be created with a specific number of qubits and wire names. + Example: + .. testcode:: + from qibo import Circuit + c = Circuit(5) # Default wire names are [0, 1, 2, 3, 4] + c = Circuit(["A", "B", "C", "D", "E"]) + c = Circuit(5, wire_names=["A", "B", "C", "D", "E"]) + c = Circuit(wire_names=["A", "B", "C", "D", "E"]) + Args: - nqubits (int): Total number of qubits in the circuit. + nqubits (int | list, optional): Number of qubits in the circuit or a list of wire names. + wire_names (list, optional): List of wire names. + - Either ``nqubits`` or ``wire_names`` must be provided. + - If only ``nqubits`` is provided, wire names will default to [``0``, ``1``, ..., ``nqubits - 1``]. + - If only ``wire_names`` is provided, ``nqubits`` will be set to the length of ``wire_names``. + - ``nqubits`` and ``wire_names`` must be consistent with each other. + init_kwargs (dict): a dictionary with the following keys - *nqubits* @@ -141,11 +156,6 @@ class Circuit: Defaults to ``False``. accelerators (dict, optional): Dictionary that maps device names to the number of times each device will be used. Defaults to ``None``. - wire_names (list or dict, optional): Names for qubit wires. - If ``None``, defaults to (``q0``, ``q1``... ``qn``). - If ``list`` is passed, length of ``list`` must match ``nqubits``. - If ``dict`` is passed, the keys should match the default pattern. - Defaults to ``None``. ndevices (int): Total number of devices. Defaults to ``None``. nglobal (int): Base two logarithm of the number of devices. Defaults to ``None``. nlocal (int): Total number of available qubits in each device. Defaults to ``None``. @@ -155,29 +165,20 @@ class Circuit: def __init__( self, - nqubits: int, + nqubits: Optional[Union[int, list]] = None, accelerators=None, density_matrix: bool = False, - wire_names: Optional[Union[list, dict]] = None, + wire_names: Optional[list] = None, ): - if not isinstance(nqubits, int): - raise_error( - TypeError, - f"Number of qubits must be an integer but is {nqubits}.", - ) - if nqubits < 1: - raise_error( - ValueError, - f"Number of qubits must be positive but is {nqubits}.", - ) + nqubits, wire_names = _resolve_qubits(nqubits, wire_names) self.nqubits = nqubits - self.wire_names = wire_names self.init_kwargs = { "nqubits": nqubits, "accelerators": accelerators, "density_matrix": density_matrix, "wire_names": wire_names, } + self.wire_names = wire_names self.queue = _Queue(nqubits) # Keep track of parametrized gates for the ``set_parameters`` method self.parametrized_gates = _ParametrizedGates() @@ -282,49 +283,29 @@ def __add__(self, circuit): @property def wire_names(self): + if self._wire_names is None: + return list(range(self.nqubits)) return self._wire_names @wire_names.setter - def wire_names(self, wire_names: Union[list, dict]): - if not isinstance(wire_names, (list, dict, type(None))): + def wire_names(self, wire_names: Optional[list]): + if not isinstance(wire_names, (list, type(None))): raise_error( TypeError, - f"``wire_names`` must be type ``list`` or ``dict``, but is {type(wire_names)}.", + f"``wire_names`` must be type ``list``, but is {type(wire_names)}.", ) - if isinstance(wire_names, list): + if wire_names is not None: if len(wire_names) != self.nqubits: raise_error( ValueError, "Number of wire names must be equal to the number of qubits, " f"but is {len(wire_names)}.", ) - - if any([not isinstance(name, str) for name in wire_names]): - raise_error(ValueError, "all wire names must be type ``str``.") - - self._wire_names = wire_names - elif isinstance(wire_names, dict): - if len(wire_names.keys()) > self.nqubits: - raise_error( - ValueError, - "number of elements in the ``wire_names`` dictionary " - + "cannot be bigger than ``nqubits``.", - ) - - if any([not isinstance(name, str) for name in wire_names.keys()]) or any( - [not isinstance(name, str) for name in wire_names.values()] - ): - raise_error( - ValueError, - "all keys and values in the ``wire_names`` dictionary must be type ``str``.", - ) - - self._wire_names = [ - wire_names.get(f"q{i}", f"q{i}") for i in range(self.nqubits) - ] + self._wire_names = wire_names.copy() else: - self._wire_names = [f"q{i}" for i in range(self.nqubits)] + self._wire_names = None + self.init_kwargs["wire_names"] = self._wire_names @property def repeated_execution(self): @@ -407,8 +388,8 @@ def light_cone(self, *qubits): qubit_map = {q: i for i, q in enumerate(sorted(qubits))} kwargs = dict(self.init_kwargs) kwargs["nqubits"] = len(qubits) + kwargs["wire_names"] = [self.wire_names[q] for q in sorted(qubits)] circuit = self.__class__(**kwargs) - circuit.wire_names = [self.wire_names[q] for q in list(sorted(qubits))] circuit.add(gate.on_qubits(qubit_map) for gate in reversed(list_of_gates)) return circuit, qubit_map @@ -1282,6 +1263,7 @@ def diagram(self, line_wrap: int = 70, legend: bool = False) -> str: """Build the string representation of the circuit diagram.""" # build string representation of gates matrix = [[] for _ in range(self.nqubits)] + wire_names = [str(name) for name in self.wire_names] idx = [0] * self.nqubits for gate in self.queue: @@ -1303,12 +1285,12 @@ def diagram(self, line_wrap: int = 70, legend: bool = False) -> str: matrix[row][col] += "─" * (1 + maxlen - len(matrix[row][col])) # Print to terminal - max_name_len = max(len(name) for name in self.wire_names) + max_name_len = max(len(name) for name in wire_names) output = "" for q in range(self.nqubits): output += ( - self.wire_names[q] - + " " * (max_name_len - len(self.wire_names[q])) + wire_names[q] + + " " * (max_name_len - len(wire_names[q])) + ": ─" + "".join(matrix[q]) + "\n" @@ -1350,8 +1332,8 @@ def chunkstring(string, length): loutput += ["" for _ in range(self.nqubits)] suffix = " ...\n" prefix = ( - self.wire_names[row] - + " " * (max_name_len - len(self.wire_names[row])) + wire_names[row] + + " " * (max_name_len - len(wire_names[row])) + ": " ) if i == 0: @@ -1388,3 +1370,32 @@ def draw(self, line_wrap: int = 70, legend: bool = False): String containing text circuit diagram. """ sys.stdout.write(self.diagram(line_wrap, legend) + "\n") + + +def _resolve_qubits(qubits, wire_names): + """Parse the input arguments for defining a circuit. Allows the user to initialize the circuit as follows: + + Example: + .. code-block:: python + from qibo import Circuit + c = Circuit(3) + c = Circuit(3, wire_names=["q0", "q1", "q2"]) + c = Circuit(["q0", "q1", "q2"]) + c = Circuit(wire_names=["q0", "q1", "q2"]) + """ + if qubits is None and wire_names is not None: + return len(wire_names), wire_names + if qubits is not None and wire_names is None: + if isinstance(qubits, int) and qubits > 0: + return qubits, None + if isinstance(qubits, list): + return len(qubits), qubits + if qubits is not None and wire_names is not None: + if isinstance(qubits, int) and isinstance(wire_names, list): + if qubits == len(wire_names): + return qubits, wire_names + + raise_error( + ValueError, + "Invalid input arguments for defining a circuit.", + ) diff --git a/src/qibo/models/error_mitigation.py b/src/qibo/models/error_mitigation.py index 875bf03b52..ac876e84ea 100644 --- a/src/qibo/models/error_mitigation.py +++ b/src/qibo/models/error_mitigation.py @@ -1174,21 +1174,11 @@ def _execute_circuit(circuit, qubit_map, noise_model=None, nshots=10000, backend qibo.states.CircuitResult: The result of the circuit execution. """ from qibo.transpiler.pipeline import Passes - from qibo.transpiler.placer import Custom if backend is None: # pragma: no cover backend = get_backend() elif backend.name == "qibolab": # pragma: no cover - qubits = backend.qubits - connectivity_edges = backend.connectivity - node_mapping = {q: i for i, q in enumerate(qubits)} - edges = [(node_mapping[e[0]], node_mapping[e[1]]) for e in connectivity_edges] - connectivity = nx.Graph(edges) - transpiler = Passes( - connectivity=connectivity, - passes=[Custom(initial_map=qubit_map, connectivity=connectivity)], - ) - circuit, _ = transpiler(circuit) + circuit.wire_names = qubit_map elif noise_model is not None: circuit = noise_model.apply(circuit) diff --git a/src/qibo/tomography/gate_set_tomography.py b/src/qibo/tomography/gate_set_tomography.py index d3b4286f32..5a8331f527 100644 --- a/src/qibo/tomography/gate_set_tomography.py +++ b/src/qibo/tomography/gate_set_tomography.py @@ -1,14 +1,13 @@ from functools import cache from inspect import signature from itertools import product -from random import Random from typing import List, Union import numpy as np from sympy import S from qibo import Circuit, gates, symbols -from qibo.backends import _check_backend +from qibo.backends import _check_backend, get_transpiler from qibo.config import raise_error from qibo.hamiltonians import SymbolicHamiltonian from qibo.transpiler.optimizer import Preprocessing @@ -261,15 +260,7 @@ def GST( backend = _check_backend(backend) if backend.name == "qibolab" and transpiler is None: # pragma: no cover - transpiler = Passes( - connectivity=backend.platform.topology, - passes=[ - Preprocessing(backend.platform.topology), - Random(backend.platform.topology), - Sabre(backend.platform.topology), - Unroller(NativeGates.default()), - ], - ) + transpiler = get_transpiler() matrices = [] empty_matrices = [] diff --git a/src/qibo/transpiler/__init__.py b/src/qibo/transpiler/__init__.py index bfe576776c..40675f0be1 100644 --- a/src/qibo/transpiler/__init__.py +++ b/src/qibo/transpiler/__init__.py @@ -1,12 +1,10 @@ from qibo.transpiler.optimizer import Preprocessing, Rearrange from qibo.transpiler.pipeline import Passes from qibo.transpiler.placer import ( - Custom, Random, ReverseTraversal, StarConnectivityPlacer, Subgraph, - Trivial, ) from qibo.transpiler.router import Sabre, ShortestPaths, StarConnectivityRouter from qibo.transpiler.unroller import NativeGates diff --git a/src/qibo/transpiler/abstract.py b/src/qibo/transpiler/abstract.py index c838262231..11e1641759 100644 --- a/src/qibo/transpiler/abstract.py +++ b/src/qibo/transpiler/abstract.py @@ -12,14 +12,11 @@ def __init__(self, connectivity: nx.Graph, *args): """A placer implements the initial logical-physical qubit mapping""" @abstractmethod - def __call__(self, circuit: Circuit, *args) -> dict: - """Find initial qubit mapping + def __call__(self, circuit: Circuit, *args): + """Find initial qubit mapping. Mapping is saved in the circuit. Args: circuit (:class:`qibo.models.circuit.Circuit`): circuit to be mapped. - - Returns: - (dict): dictionary containing the initial logical to physical qubit mapping. """ @@ -29,17 +26,14 @@ def __init__(self, connectivity: nx.Graph, *args): """A router implements the mapping of a circuit on a specific hardware.""" @abstractmethod - def __call__( - self, circuit: Circuit, initial_layout: dict, *args - ) -> Tuple[Circuit, dict]: + def __call__(self, circuit: Circuit, *args) -> Tuple[Circuit, dict]: """Match circuit to hardware connectivity. Args: - circuit (:class:`qibo.models.Circuit`): circuit to be routed. - initial_layout (dict): dictionary containing the initial logical to physical qubit mapping. + circuit (:class:`qibo.models.circuit.Circuit`): circuit to be routed. Returns: - (:class:`qibo.models.circuit.Circuit`, dict): routed circuit and dictionary containing the final logical to physical qubit mapping. + (:class:`qibo.models.circuit.Circuit`): routed circuit. """ diff --git a/src/qibo/transpiler/asserts.py b/src/qibo/transpiler/asserts.py new file mode 100644 index 0000000000..d988ce6add --- /dev/null +++ b/src/qibo/transpiler/asserts.py @@ -0,0 +1,200 @@ +from typing import Optional + +import networkx as nx +import numpy as np + +from qibo import gates +from qibo.backends.numpy import NumpyBackend +from qibo.config import raise_error +from qibo.models.circuit import Circuit +from qibo.quantum_info.random_ensembles import random_statevector +from qibo.transpiler._exceptions import ( + ConnectivityError, + DecompositionError, + PlacementError, + TranspilerPipelineError, +) +from qibo.transpiler.optimizer import Preprocessing +from qibo.transpiler.unroller import NativeGates + + +def assert_transpiling( + original_circuit: Circuit, + transpiled_circuit: Circuit, + connectivity: nx.Graph, + final_layout: dict, + native_gates: NativeGates = NativeGates.default(), + check_circuit_equivalence=True, +): + """Check that all transpiler passes have been executed correctly. + + Args: + original_circuit (qibo.models.Circuit): circuit before transpiling. + transpiled_circuit (qibo.models.Circuit): circuit after transpiling. + connectivity (networkx.Graph): chip qubits connectivity. + final_layout (dict): final physical-logical qubit mapping. + 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, + native_gates=native_gates, + ) + if original_circuit.nqubits != transpiled_circuit.nqubits: + qubit_matcher = Preprocessing(connectivity=connectivity) + original_circuit = qubit_matcher(circuit=original_circuit) + assert_placement(circuit=original_circuit, connectivity=connectivity) + assert_placement(circuit=transpiled_circuit, connectivity=connectivity) + if check_circuit_equivalence: + assert_circuit_equivalence( + original_circuit=original_circuit, + transpiled_circuit=transpiled_circuit, + final_map=final_layout, + ) + + +def assert_circuit_equivalence( + original_circuit: Circuit, + transpiled_circuit: Circuit, + final_map: dict, + test_states: Optional[list] = None, + ntests: int = 3, +): + """Checks that the transpiled circuit agrees with the original using simulation. + + Args: + original_circuit (:class:`qibo.models.circuit.Circuit`): Original circuit. + transpiled_circuit (:class:`qibo.models.circuit.Circuit`): Transpiled circuit. + final_map (dict): logical-physical qubit mapping after routing. + test_states (list, optional): states on which the test is performed. + If ``None``, ``ntests`` random states will be tested. Defauts to ``None``. + ntests (int, optional): number of random states tested. Defauts to :math:`3`. + """ + backend = NumpyBackend() + if transpiled_circuit.nqubits != original_circuit.nqubits: + raise_error( + ValueError, + "Transpiled and original circuit do not have the same number of qubits.", + ) + + if test_states is None: + test_states = [ + random_statevector(dims=2**original_circuit.nqubits, backend=backend) + for _ in range(ntests) + ] + + ordering = list(final_map.values()) + + for i, state in enumerate(test_states): + target_state = backend.execute_circuit( + original_circuit, initial_state=state + ).state() + final_state = backend.execute_circuit( + transpiled_circuit, + initial_state=state, + ).state() + final_state = _transpose_qubits(final_state, ordering) + fidelity = np.abs(np.dot(np.conj(target_state), final_state)) + try: + np.testing.assert_allclose(fidelity, 1.0) + except AssertionError: + raise_error(TranspilerPipelineError, "Circuit equivalence not satisfied.") + + +def _transpose_qubits(state: np.ndarray, qubits_ordering: np.ndarray): + """Reorders qubits of a given state vector. + + Args: + state (np.ndarray): final state of the circuit. + qubits_ordering (np.ndarray): final qubit ordering. + """ + original_shape = state.shape + state = np.reshape(state, len(qubits_ordering) * (2,)) + state = np.transpose(state, qubits_ordering) + return np.reshape(state, original_shape) + + +def assert_placement(circuit: Circuit, connectivity: nx.Graph): + """Check if the layout of the circuit is consistent with the circuit and connectivity graph. + + Args: + circuit (:class:`qibo.models.circuit.Circuit`): Circuit model to check. + connectivity (:class:`networkx.Graph`, optional): Chip connectivity. + """ + if connectivity is None: + raise_error( + ValueError, + "Connectivity graph is missing.", + ) + + if circuit.nqubits != len(circuit.wire_names) or circuit.nqubits != len( + connectivity.nodes + ): + raise_error( + PlacementError, + f"Number of qubits in the circuit ({circuit.nqubits}) " + + f"does not match the number of qubits in the layout ({len(circuit.wire_names)}) " + + f"or the connectivity graph ({len(connectivity.nodes)}).", + ) + if set(circuit.wire_names) != set(connectivity.nodes): + raise_error( + PlacementError, + "Some physical qubits in the layout may be missing or duplicated.", + ) + + +def assert_connectivity(connectivity: nx.Graph, circuit: Circuit): + """Assert if a circuit can be executed on Hardware. + + No gates acting on more than two qubits. + All two-qubit operations can be performed on hardware. + + Args: + circuit (:class:`qibo.models.circuit.Circuit`): circuit model to check. + connectivity (:class:`networkx.Graph`): chip connectivity. + """ + layout = circuit.wire_names + for gate in circuit.queue: + if len(gate.qubits) > 2 and not isinstance(gate, gates.M): + raise_error(ConnectivityError, f"{gate.name} acts on more than two qubits.") + if len(gate.qubits) == 2: + physical_qubits = (layout[gate.qubits[0]], layout[gate.qubits[1]]) + if physical_qubits not in connectivity.edges: + raise_error( + ConnectivityError, + f"The circuit does not respect the connectivity. {gate.name} acts on {physical_qubits} but only the following qubits are directly connected: {connectivity.edges}.", + ) + + +def assert_decomposition( + circuit: Circuit, + 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. + 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) <= 2: + try: + native_type_gate = NativeGates.from_gate(gate) + if not native_type_gate & native_gates: + raise_error( + DecompositionError, + f"{gate.name} is not a native gate.", + ) + except ValueError: + raise_error( + DecompositionError, + f"{gate.name} is not a native gate.", + ) + else: + raise_error( + DecompositionError, f"{gate.name} acts on more than two qubits." + ) diff --git a/src/qibo/transpiler/optimizer.py b/src/qibo/transpiler/optimizer.py index 7008957627..741078e5e9 100644 --- a/src/qibo/transpiler/optimizer.py +++ b/src/qibo/transpiler/optimizer.py @@ -1,3 +1,5 @@ +from typing import Optional + import networkx as nx from qibo import gates @@ -13,10 +15,16 @@ class Preprocessing(Optimizer): connectivity (:class:`networkx.Graph`): hardware chip connectivity. """ - def __init__(self, connectivity: nx.Graph): + def __init__(self, connectivity: Optional[nx.Graph] = None): self.connectivity = connectivity def __call__(self, circuit: Circuit) -> Circuit: + if not all(qubit in self.connectivity.nodes for qubit in circuit.wire_names): + raise_error( + ValueError, + "The circuit qubits are not in the connectivity graph.", + ) + physical_qubits = self.connectivity.number_of_nodes() logical_qubits = circuit.nqubits if logical_qubits > physical_qubits: @@ -27,7 +35,10 @@ def __call__(self, circuit: Circuit) -> Circuit: ) if logical_qubits == physical_qubits: return circuit - new_circuit = Circuit(physical_qubits) + new_wire_names = circuit.wire_names + list( + self.connectivity.nodes - circuit.wire_names + ) + new_circuit = Circuit(nqubits=physical_qubits, wire_names=new_wire_names) for gate in circuit.queue: new_circuit.add(gate) return new_circuit @@ -48,7 +59,7 @@ def __init__(self, max_qubits: int = 1): def __call__(self, circuit: Circuit): fused_circuit = circuit.fuse(max_qubits=self.max_qubits) - new = circuit.__class__(circuit.nqubits) + new = circuit.__class__(nqubits=circuit.nqubits, wire_names=circuit.wire_names) for fgate in fused_circuit.queue: if isinstance(fgate, gates.FusedGate): new.add(gates.Unitary(fgate.matrix(), *fgate.qubits)) diff --git a/src/qibo/transpiler/pipeline.py b/src/qibo/transpiler/pipeline.py index c033850557..c1aae06775 100644 --- a/src/qibo/transpiler/pipeline.py +++ b/src/qibo/transpiler/pipeline.py @@ -1,147 +1,22 @@ -from typing import Optional - import networkx as nx -import numpy as np -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._exceptions import TranspilerPipelineError -from qibo.transpiler.abstract import Optimizer, Placer, Router -from qibo.transpiler.optimizer import Preprocessing -from qibo.transpiler.placer import StarConnectivityPlacer, Trivial, assert_placement -from qibo.transpiler.router import ( +from qibo.transpiler._exceptions import ( ConnectivityError, - StarConnectivityRouter, - assert_connectivity, + PlacementError, + TranspilerPipelineError, ) -from qibo.transpiler.unroller import ( - DecompositionError, - NativeGates, - Unroller, +from qibo.transpiler.abstract import Optimizer, Placer, Router +from qibo.transpiler.asserts import ( + assert_connectivity, assert_decomposition, + assert_placement, ) +from qibo.transpiler.unroller import DecompositionError, NativeGates, Unroller -def assert_circuit_equivalence( - original_circuit: Circuit, - transpiled_circuit: Circuit, - final_map: dict, - initial_map: Optional[dict] = None, - test_states: Optional[list] = None, - ntests: int = 3, -): - """Checks that the transpiled circuit agrees with the original using simulation. - - Args: - original_circuit (:class:`qibo.models.circuit.Circuit`): Original circuit. - transpiled_circuit (:class:`qibo.models.circuit.Circuit`): Transpiled circuit. - final_map (dict): logical-physical qubit mapping after routing. - initial_map (dict, optional): logical_physical qubit mapping before routing. - If ``None``, trivial initial map is used. Defauts to ``None``. - test_states (list, optional): states on which the test is performed. - If ``None``, ``ntests`` random states will be tested. Defauts to ``None``. - ntests (int, optional): number of random states tested. Defauts to :math:`3`. - """ - backend = NumpyBackend() - ordering = np.argsort(np.array(list(final_map.values()))) - if transpiled_circuit.nqubits != original_circuit.nqubits: - raise_error( - ValueError, - "Transpiled and original circuit do not have the same number of qubits.", - ) - - if test_states is None: - test_states = [ - random_statevector(dims=2**original_circuit.nqubits, backend=backend) - for _ in range(ntests) - ] - if initial_map is not None: - reordered_test_states = [] - initial_map = np.array(list(initial_map.values())) - reordered_test_states = [ - _transpose_qubits(initial_state, initial_map) - for initial_state in test_states - ] - else: - reordered_test_states = test_states - - for i in range(len(test_states)): - target_state = backend.execute_circuit( - original_circuit, initial_state=test_states[i] - ).state() - final_state = backend.execute_circuit( - transpiled_circuit, initial_state=reordered_test_states[i] - ).state() - final_state = _transpose_qubits(final_state, ordering) - fidelity = np.abs(np.dot(np.conj(target_state), final_state)) - try: - np.testing.assert_allclose(fidelity, 1.0) - except AssertionError: - raise_error(TranspilerPipelineError, "Circuit equivalence not satisfied.") - - -def _transpose_qubits(state: np.ndarray, qubits_ordering: np.ndarray): - """Reorders qubits of a given state vector. - - Args: - state (np.ndarray): final state of the circuit. - qubits_ordering (np.ndarray): final qubit ordering. - """ - original_shape = state.shape - state = np.reshape(state, len(qubits_ordering) * (2,)) - state = np.transpose(state, qubits_ordering) - return np.reshape(state, original_shape) - - -def assert_transpiling( - original_circuit: Circuit, - transpiled_circuit: Circuit, - connectivity: nx.Graph, - initial_layout: dict, - final_layout: dict, - native_gates: NativeGates = NativeGates.default(), - check_circuit_equivalence=True, -): - """Check that all transpiler passes have been executed correctly. - - Args: - original_circuit (:class:`qibo.models.Circuit`): circuit before transpiling. - transpiled_circuit (:class:`qibo.models.Circuit`): circuit after transpiling. - connectivity (:class:`networkx.Graph`): chip qubits connectivity. - initial_layout (dict): initial physical-logical qubit mapping. - final_layout (dict): final physical-logical qubit mapping. - native_gates (:class:`qibo.transpiler.unroller.NativeGates`): native gates - supported by the hardware. Defaults to - :meth:`qibo.transpiler.unroller.NativeGates.default`. - check_circuit_equivalence (bool, optional): use simulations to check if the - transpiled circuit is the same as the original. Defaults to ``True``. - """ - assert_connectivity(circuit=transpiled_circuit, connectivity=connectivity) - assert_decomposition( - circuit=transpiled_circuit, - native_gates=native_gates, - ) - if original_circuit.nqubits != transpiled_circuit.nqubits: - qubit_matcher = Preprocessing(connectivity=connectivity) - original_circuit = qubit_matcher(circuit=original_circuit) - assert_placement( - circuit=original_circuit, layout=initial_layout, connectivity=connectivity - ) - assert_placement( - circuit=transpiled_circuit, layout=final_layout, connectivity=connectivity - ) - if check_circuit_equivalence: - assert_circuit_equivalence( - original_circuit=original_circuit, - transpiled_circuit=transpiled_circuit, - initial_map=initial_layout, - final_map=final_layout, - ) - - -def restrict_connectivity_qubits(connectivity: nx.Graph, qubits: list): +def restrict_connectivity_qubits(connectivity: nx.Graph, qubits: list[str]): """Restrict the connectivity to selected qubits. Args: @@ -177,14 +52,10 @@ class Passes: If ``None``, default transpiler will be used. Defaults to ``None``. connectivity (:class:`networkx.Graph`, optional): physical qubits connectivity. - If ``None``, :class:`` is used. - Defaults to ``None``. native_gates (:class:`qibo.transpiler.unroller.NativeGates`, optional): native gates. Defaults to :math:`qibo.transpiler.unroller.NativeGates.default`. on_qubits (list, optional): list of physical qubits to be used. If "None" all qubits are used. Defaults to ``None``. - int_qubit_name (bool, optional): if `True` the `final_layout` keys are - cast to integers. """ def __init__( @@ -193,68 +64,30 @@ def __init__( connectivity: nx.Graph = None, native_gates: NativeGates = NativeGates.default(), on_qubits: list = None, - int_qubit_names: bool = False, ): if on_qubits is not None: connectivity = restrict_connectivity_qubits(connectivity, on_qubits) self.connectivity = connectivity self.native_gates = native_gates - self.passes = self.default() if passes is None else passes - self.initial_layout = None - self.int_qubit_names = int_qubit_names - - def default(self): - """Return the default transpiler pipeline for the required hardware connectivity.""" - if not isinstance(self.connectivity, nx.Graph): - raise_error( - TranspilerPipelineError, - "Define the hardware chip connectivity to use default transpiler", - ) - default_passes = [] - # preprocessing - default_passes.append(Preprocessing(connectivity=self.connectivity)) - # default placer pass - default_passes.append(StarConnectivityPlacer()) - # default router pass - default_passes.append(StarConnectivityRouter()) - # default unroller pass - default_passes.append(Unroller(native_gates=self.native_gates)) - - return default_passes + self.passes = [] if passes is None else passes def __call__(self, circuit): """ This function returns the compiled circuits and the dictionary mapping - physical (keys) to logical (values) qubit. If `int_qubit_name` is `True` - each key `i` correspond to the `i-th` qubit in the graph. + physical (keys) to logical (values) qubit. """ - final_layout = self.initial_layout = None + + final_layout = None for transpiler_pass in self.passes: if isinstance(transpiler_pass, Optimizer): transpiler_pass.connectivity = self.connectivity circuit = transpiler_pass(circuit) elif isinstance(transpiler_pass, Placer): transpiler_pass.connectivity = self.connectivity - if self.initial_layout is None: - self.initial_layout = transpiler_pass(circuit) - final_layout = ( - self.initial_layout - ) # This way the final layout will be the same as the initial layout if no router is used - else: - raise_error( - TranspilerPipelineError, - "You are defining more than one placer pass.", - ) + final_layout = transpiler_pass(circuit) elif isinstance(transpiler_pass, Router): transpiler_pass.connectivity = self.connectivity - if self.initial_layout is not None: - circuit, final_layout = transpiler_pass( - circuit, self.initial_layout - ) - else: - raise_error( - TranspilerPipelineError, "Use a placement pass before routing." - ) + circuit, final_layout = transpiler_pass(circuit) elif isinstance(transpiler_pass, Unroller): circuit = transpiler_pass(circuit) else: @@ -262,8 +95,6 @@ def __call__(self, circuit): TranspilerPipelineError, f"Unrecognised transpiler pass: {transpiler_pass}", ) - if self.int_qubit_names and final_layout is not None: - final_layout = {int(key[1:]): value for key, value in final_layout.items()} return circuit, final_layout def is_satisfied(self, circuit: Circuit): @@ -276,14 +107,9 @@ def is_satisfied(self, circuit: Circuit): (bool): satisfiability condition. """ try: + assert_placement(circuit=circuit, connectivity=self.connectivity) assert_connectivity(circuit=circuit, connectivity=self.connectivity) assert_decomposition(circuit=circuit, native_gates=self.native_gates) return True - except ConnectivityError: + except (ConnectivityError, DecompositionError, PlacementError, ValueError): return False - except DecompositionError: - return False - - def get_initial_layout(self): - """Return initial qubit layout""" - return self.initial_layout diff --git a/src/qibo/transpiler/placer.py b/src/qibo/transpiler/placer.py index 7944ea81f2..48e2cbd8ec 100644 --- a/src/qibo/transpiler/placer.py +++ b/src/qibo/transpiler/placer.py @@ -1,71 +1,17 @@ -from typing import Optional, Union +from typing import Optional import networkx as nx from qibo import gates from qibo.backends import _check_backend_and_local_state -from qibo.config import log, raise_error +from qibo.config import raise_error from qibo.models import Circuit from qibo.transpiler._exceptions import PlacementError from qibo.transpiler.abstract import Placer, Router +from qibo.transpiler.asserts import assert_placement from qibo.transpiler.router import _find_connected_qubit -def assert_placement( - circuit: Circuit, layout: dict, connectivity: nx.Graph = None -) -> bool: - """Check if layout is in the correct form and matches the number of qubits of the circuit. - - Args: - circuit (:class:`qibo.models.circuit.Circuit`): Circuit model to check. - layout (dict): physical to logical qubit mapping. - connectivity (:class:`networkx.Graph`, optional): Chip connectivity. - This argument is necessary if the layout is applied to a subset of - qubits of the original connectivity graph. Defaults to ``None``. - """ - assert_mapping_consistency(layout=layout, connectivity=connectivity) - if circuit.nqubits > len(layout): - raise_error( - PlacementError, - "Layout can't be used on circuit. The circuit requires more qubits.", - ) - if circuit.nqubits < len(layout): - raise_error( - PlacementError, - "Layout can't be used on circuit. " - + "Ancillary extra qubits need to be added to the circuit.", - ) - - -def assert_mapping_consistency(layout: dict, connectivity: nx.Graph = None): - """Check if layout is in the correct form. - - Args: - layout (dict): physical to logical qubit mapping. - connectivity (:class:`networkx.Graph`, optional): Chip connectivity. - This argument is necessary if the layout is applied to a subset of - qubits of the original connectivity graph. Defaults to ``None``. - """ - values = sorted(layout.values()) - physical_qubits = list(layout) - nodes = ( - list(range(len(values))) if connectivity is None else list(connectivity.nodes) - ) - ref_keys = ( - ["q" + str(i) for i in nodes] if isinstance(physical_qubits[0], str) else nodes - ) - if sorted(physical_qubits) != sorted(ref_keys): - raise_error( - PlacementError, - "Some physical qubits in the layout may be missing or duplicated.", - ) - if values != list(range(len(values))): - raise_error( - PlacementError, - "Some logical qubits in the layout may be missing or duplicated.", - ) - - def _find_gates_qubits_pairs(circuit: Circuit): """Helper method for :meth:`qibo.transpiler.placer`. Translate circuit into a list of pairs of qubits to be used by the router and placer. @@ -99,17 +45,12 @@ class StarConnectivityPlacer(Placer): q Args: - connectivity (:class:`networkx.Graph`): chip connectivity, not used for this transpiler. - middle_qubit (int, optional): qubit id of the qubit that is in the middle of the star. + connectivity (:class:`networkx.Graph`): star connectivity graph. """ - def __init__(self, connectivity=None, middle_qubit: int = 2): - self.middle_qubit = middle_qubit - if connectivity is not None: # pragma: no cover - log.warning( - "StarConnectivityRouter does not use the connectivity graph." - "The connectivity graph will be ignored." - ) + def __init__(self, connectivity: Optional[nx.Graph] = None): + self.connectivity = connectivity + self.middle_qubit = None def __call__(self, circuit: Circuit): """Apply the transpiler transformation on a given circuit. @@ -117,14 +58,12 @@ def __call__(self, circuit: Circuit): Args: circuit (:class:`qibo.models.circuit.Circuit`): The original Qibo circuit to transform. Only single qubit gates and two qubits gates are supported by the router. - - Returns: - dict: physical to logical qubit mapping. """ + assert_placement(circuit, self.connectivity) + self._check_star_connectivity() - # find the number of qubits for hardware circuit - nqubits = max(circuit.nqubits, self.middle_qubit + 1) - hardware_qubits = list(range(nqubits)) + middle_qubit_idx = circuit.wire_names.index(self.middle_qubit) + wire_names = circuit.wire_names.copy() for i, gate in enumerate(circuit.queue): if len(gate.qubits) > 2: @@ -133,118 +72,35 @@ def __call__(self, circuit: Circuit): "Gates targeting more than 2 qubits are not supported", ) if len(gate.qubits) == 2: - if self.middle_qubit not in gate.qubits: + if middle_qubit_idx not in gate.qubits: new_middle = _find_connected_qubit( gate.qubits, circuit.queue[i + 1 :], - hardware_qubits, error=PlacementError, + mapping=list(range(circuit.nqubits)), ) - hardware_qubits[self.middle_qubit], hardware_qubits[new_middle] = ( - new_middle, - self.middle_qubit, + + ( + wire_names[middle_qubit_idx], + wire_names[new_middle], + ) = ( + wire_names[new_middle], + wire_names[middle_qubit_idx], ) break - return dict(zip(["q" + str(i) for i in range(nqubits)], hardware_qubits)) - - -class Trivial(Placer): - """Place qubits according to the following notation: - - .. math:: - \\{\\textup{"q0"} : 0, \\textup{"q1"} : 1, ..., \\textup{"qn"} : n}. + circuit.wire_names = wire_names - Args: - connectivity (networkx.Graph, optional): chip connectivity. - """ - - def __init__(self, connectivity: nx.Graph = None): - self.connectivity = connectivity - - def __call__(self, circuit: Circuit): - """Find the trivial placement for the circuit. - - Args: - circuit (:class:`qibo.models.circuit.Circuit`): circuit to be transpiled. - - Returns: - (dict): physical to logical qubit mapping. - """ - if self.connectivity is not None: - if self.connectivity.number_of_nodes() != circuit.nqubits: + def _check_star_connectivity(self): + """Check if the connectivity graph is a star graph.""" + for node in self.connectivity.nodes: + if self.connectivity.degree(node) == 4: + self.middle_qubit = node + elif self.connectivity.degree(node) != 1: raise_error( - PlacementError, - "The number of nodes of the connectivity graph must match " - + "the number of qubits in the circuit", - ) - trivial_layout = dict( - zip( - ["q" + str(i) for i in list(self.connectivity.nodes())], - range(circuit.nqubits), - ) - ) - else: - trivial_layout = dict( - zip( - ["q" + str(i) for i in range(circuit.nqubits)], - range(circuit.nqubits), - ) - ) - return trivial_layout - - -class Custom(Placer): - """Define a custom initial qubit mapping. - - Args: - map (list or dict): physical to logical qubit mapping. - Examples: :math:`[1,2,0]` or - :math:`{\\textup{"q0"}: 1, \\textup{"q1"}: 2, \\textup{"q2"}:0}` - to assign the physical qubits :math:`\\{0, 1, 2\\}` - to the logical qubits :math:`[1, 2, 0]`. - connectivity (:class:`networkx.Graph`, optional): chip connectivity. - This argument is necessary if the layout applied to a subset of - qubits of the original connectivity graph. Defaults to ``None``. - """ - - def __init__(self, initial_map: Union[list, dict], connectivity: nx.Graph = None): - self.connectivity = connectivity - self.initial_map = initial_map - - def __call__(self, circuit=None): - """Return the custom placement if it can be applied to the given circuit (if given). - - Args: - circuit (:class:`qibo.models.circuit.Circuit`): circuit to be transpiled. - - Returns: - (dict): physical to logical qubit mapping. - """ - if isinstance(self.initial_map, dict): - pass - elif isinstance(self.initial_map, list): - if self.connectivity is not None: - self.initial_map = dict( - zip( - ["q" + str(i) for i in self.connectivity.nodes()], - self.initial_map, - ) + ValueError, + "This connectivity graph is not a star graph.", ) - else: - self.initial_map = dict( - zip( - ["q" + str(i) for i in range(len(self.initial_map))], - self.initial_map, - ) - ) - else: - raise_error(TypeError, "Use dict or list to define mapping.") - if circuit is not None: - assert_placement(circuit, self.initial_map, connectivity=self.connectivity) - else: - assert_mapping_consistency(self.initial_map, connectivity=self.connectivity) - return self.initial_map class Subgraph(Placer): @@ -258,7 +114,7 @@ class Subgraph(Placer): connectivity (:class:`networkx.Graph`): chip connectivity. """ - def __init__(self, connectivity: nx.Graph): + def __init__(self, connectivity: Optional[nx.Graph] = None): self.connectivity = connectivity def __call__(self, circuit: Circuit): @@ -267,10 +123,8 @@ def __call__(self, circuit: Circuit): Args: circuit (:class:`qibo.models.circuit.Circuit`): circuit to be transpiled. - - Returns: - (dict): physical to logical qubit mapping. """ + assert_placement(circuit, self.connectivity) gates_qubits_pairs = _find_gates_qubits_pairs(circuit) if len(gates_qubits_pairs) < 3: raise_error( @@ -301,9 +155,7 @@ def __call__(self, circuit: Circuit): ): break - sorted_result = dict(sorted(result.mapping.items())) - - return {"q" + str(k): v for k, v in sorted_result.items()} + circuit.wire_names = sorted(result.mapping, key=lambda k: result.mapping[k]) class Random(Placer): @@ -320,7 +172,9 @@ class Random(Placer): initializes a generator with a random seed. Defaults to ``None``. """ - def __init__(self, connectivity, samples: int = 100, seed=None): + def __init__( + self, connectivity: Optional[nx.Graph] = None, samples: int = 100, seed=None + ): self.connectivity = connectivity self.samples = samples self.seed = seed @@ -330,15 +184,12 @@ def __call__(self, circuit): Args: circuit (:class:`qibo.models.circuit.Circuit`): Circuit to be transpiled. - - Returns: - (dict): physical-to-logical qubit mapping. """ + assert_placement(circuit, self.connectivity) _, local_state = _check_backend_and_local_state(self.seed, backend=None) gates_qubits_pairs = _find_gates_qubits_pairs(circuit) nodes = self.connectivity.number_of_nodes() keys = list(self.connectivity.nodes()) - dict_keys = ["q" + str(i) for i in keys] final_mapping = dict(zip(keys, range(nodes))) final_graph = nx.relabel_nodes(self.connectivity, final_mapping) @@ -351,16 +202,17 @@ def __call__(self, circuit): cost = self._cost(graph, gates_qubits_pairs) if cost == 0: - final_layout = dict(zip(dict_keys, list(mapping.values()))) - return dict(sorted(final_layout.items())) + final_layout = dict(zip(keys, list(mapping.values()))) + circuit.wire_names = sorted(final_layout, key=final_layout.get) + return if cost < final_cost: final_graph = graph final_mapping = mapping final_cost = cost - final_layout = dict(zip(dict_keys, list(final_mapping.values()))) - return dict(sorted(final_layout.items())) + final_layout = dict(zip(keys, list(final_mapping.values()))) + circuit.wire_names = sorted(final_layout, key=final_layout.get) def _cost(self, graph: nx.Graph, gates_qubits_pairs: list): """ @@ -405,8 +257,8 @@ class ReverseTraversal(Placer): def __init__( self, - connectivity: nx.Graph, routing_algorithm: Router, + connectivity: Optional[nx.Graph] = None, depth: Optional[int] = None, ): self.connectivity = connectivity @@ -418,17 +270,11 @@ def __call__(self, circuit: Circuit): Args: circuit (:class:`qibo.models.circuit.Circuit`): circuit to be transpiled. - - Returns: - (dict): physical to logical qubit mapping. """ - initial_placer = Trivial(self.connectivity) - initial_placement = initial_placer(circuit=circuit) + assert_placement(circuit, self.connectivity) self.routing_algorithm.connectivity = self.connectivity new_circuit = self._assemble_circuit(circuit) - final_placement = self._routing_step(initial_placement, new_circuit) - - return final_placement + self._routing_step(new_circuit) def _assemble_circuit(self, circuit: Circuit): """Assemble a single circuit to apply Reverse Traversal placement based on depth. @@ -461,20 +307,19 @@ def _assemble_circuit(self, circuit: Circuit): gates_qubits_pairs.reverse() assembled_gates_qubits_pairs += gates_qubits_pairs[0:remainder] - new_circuit = Circuit(circuit.nqubits) + new_circuit = Circuit(circuit.nqubits, wire_names=circuit.wire_names) for qubits in assembled_gates_qubits_pairs: # As only the connectivity is important here we can replace everything with CZ gates new_circuit.add(gates.CZ(qubits[0], qubits[1])) return new_circuit.invert() - def _routing_step(self, layout: dict, circuit: Circuit): + def _routing_step(self, circuit: Circuit): """Perform routing of the circuit. Args: - layout (dict): intial qubit layout. circuit (:class:`qibo.models.circuit.Circuit`): circuit to be routed. """ - _, final_mapping = self.routing_algorithm(circuit, layout) + _, final_mapping = self.routing_algorithm(circuit) return final_mapping diff --git a/src/qibo/transpiler/router.py b/src/qibo/transpiler/router.py index b1aad8fc69..704e4435b8 100644 --- a/src/qibo/transpiler/router.py +++ b/src/qibo/transpiler/router.py @@ -6,62 +6,14 @@ import numpy as np from qibo import gates -from qibo.config import log, raise_error +from qibo.config import raise_error from qibo.models import Circuit from qibo.transpiler._exceptions import ConnectivityError from qibo.transpiler.abstract import Router +from qibo.transpiler.asserts import assert_placement from qibo.transpiler.blocks import Block, CircuitBlocks -def assert_connectivity(connectivity: nx.Graph, circuit: Circuit): - """Assert if a circuit can be executed on Hardware. - - No gates acting on more than two qubits. - All two-qubit operations can be performed on hardware. - - Args: - circuit (:class:`qibo.models.circuit.Circuit`): circuit model to check. - connectivity (:class:`networkx.Graph`): chip connectivity. - """ - if list(connectivity.nodes) != list(range(connectivity.number_of_nodes())): - node_mapping = {node: i for i, node in enumerate(connectivity.nodes)} - new_connectivity = nx.Graph() - new_connectivity.add_edges_from( - [(node_mapping[u], node_mapping[v]) for u, v in connectivity.edges] - ) - connectivity = new_connectivity - for gate in circuit.queue: - if len(gate.qubits) > 2 and not isinstance(gate, gates.M): - raise_error(ConnectivityError, f"{gate.name} acts on more than two qubits.") - if len(gate.qubits) == 2: - # physical_qubits = tuple(sorted((circuit.wire_names[gate.qubits[0]], circuit.wire_names[gate.qubits[1]]))) - physical_qubits = tuple(sorted(gate.qubits)) # for q_i naming - if physical_qubits not in connectivity.edges: - raise_error( - ConnectivityError, - f"The circuit does not respect the connectivity. {gate.name} acts on {physical_qubits} but only the following qubits are directly connected: {connectivity.edges}.", - ) - - -def _relabel_connectivity(connectivity, layout): - """Relabels the connectivity graph using the passed layout. - - Args: - connectivity (nx.Graph): input connectivity. - layout (dict): input qubit layout. - Returns: - (dict) the updated connectivity. - """ - node_mapping = {} - layout = dict( - sorted(layout.items(), key=lambda item: int(item[0][1:])) - ) # for q_i naming - for i, node in enumerate(list(layout.keys())): - node_mapping[int(node[1:])] = i # for q_i naming - new_connectivity = nx.relabel_nodes(connectivity, node_mapping) - return new_connectivity - - class StarConnectivityRouter(Router): """Transforms an arbitrary circuit to one that can be executed on hardware. @@ -76,91 +28,87 @@ class StarConnectivityRouter(Router): by adding SWAP gates when needed. Args: - connectivity (:class:`networkx.Graph`): chip connectivity, not used for this transpiler. - middle_qubit (int, optional): qubit id of the qubit that is in the middle of the star. + connectivity (:class:`networkx.Graph`): star connectivity graph. """ - def __init__(self, connectivity=None, middle_qubit: int = 2): - self.middle_qubit = middle_qubit - if connectivity is not None: # pragma: no cover - log.warning( - "StarConnectivityRouter does not use the connectivity graph." - "The connectivity graph will be ignored." - ) + def __init__(self, connectivity: Optional[nx.Graph] = None): + self.connectivity = connectivity + self.middle_qubit = None - def __call__(self, circuit: Circuit, initial_layout: dict): + def __call__(self, circuit: Circuit): """Apply the transpiler transformation on a given circuit. Args: circuit (:class:`qibo.models.circuit.Circuit`): The original Qibo circuit to transform. Only single qubit gates and two qubits gates are supported by the router. - initial_layout (dict): initial physical-to-logical qubit mapping, - use `qibo.transpiler.placer.StarConnectivityPlacer` for better performance. - - Returns: - (:class:`qibo.models.circuit.Circuit`, list): circuit that performs the same operation - as the original but respects the hardware connectivity, - and list that maps logical to hardware qubits. """ + self._check_star_connectivity() + assert_placement(circuit, self.connectivity) - middle_qubit = self.middle_qubit - nqubits = max(circuit.nqubits, middle_qubit + 1) - # new circuit object that will be compatible with hardware connectivity - new = Circuit(nqubits) - # list to maps logical to hardware qubits - hardware_qubits = list(initial_layout.values()) + middle_qubit_idx = circuit.wire_names.index(self.middle_qubit) + nqubits = circuit.nqubits + new = Circuit(nqubits=nqubits, wire_names=circuit.wire_names) + l2p = list(range(nqubits)) for i, gate in enumerate(circuit.queue): - # map gate qubits to hardware - qubits = tuple(hardware_qubits.index(q) for q in gate.qubits) + routed_qubits = [l2p[q] for q in gate.qubits] + if isinstance(gate, gates.M): - new_gate = gates.M(*qubits, **gate.init_kwargs) + new_gate = gates.M(*routed_qubits, **gate.init_kwargs) new_gate.result = gate.result new.add(new_gate) continue - if len(qubits) > 2: + if len(routed_qubits) > 2: raise_error( ConnectivityError, "Gates targeting more than two qubits are not supported.", ) - if len(qubits) == 2 and middle_qubit not in qubits: + if len(routed_qubits) == 2 and middle_qubit_idx not in routed_qubits: # find which qubit should be moved new_middle = _find_connected_qubit( - qubits, + routed_qubits, circuit.queue[i + 1 :], - hardware_qubits, error=ConnectivityError, + mapping=l2p, ) - # update hardware qubits according to the swap - hardware_qubits[middle_qubit], hardware_qubits[new_middle] = ( - hardware_qubits[new_middle], - hardware_qubits[middle_qubit], - ) - new.add(gates.SWAP(middle_qubit, new_middle)) - # update gate qubits according to the new swap - qubits = tuple(hardware_qubits.index(q) for q in gate.qubits) + + new.add(gates.SWAP(new_middle, middle_qubit_idx)) + idx1, idx2 = l2p.index(middle_qubit_idx), l2p.index(new_middle) + l2p[idx1], l2p[idx2] = l2p[idx2], l2p[idx1] + + routed_qubits = [l2p[q] for q in gate.qubits] # add gate to the hardware circuit if isinstance(gate, gates.Unitary): # gates.Unitary requires matrix as first argument matrix = gate.init_args[0] - new.add(gate.__class__(matrix, *qubits, **gate.init_kwargs)) + new.add(gate.__class__(matrix, *routed_qubits, **gate.init_kwargs)) else: - new.add(gate.__class__(*qubits, **gate.init_kwargs)) - hardware_qubits_keys = ["q" + str(i) for i in range(5)] - return new, dict(zip(hardware_qubits_keys, hardware_qubits)) + new.add(gate.__class__(*routed_qubits, **gate.init_kwargs)) + return new, {circuit.wire_names[i]: l2p[i] for i in range(nqubits)} + + def _check_star_connectivity(self): + """Check if the connectivity graph is a star graph.""" + for node in self.connectivity.nodes: + if self.connectivity.degree(node) == 4: + self.middle_qubit = node + elif self.connectivity.degree(node) != 1: + raise_error( + ValueError, + "This connectivity graph is not a star graph.", + ) -def _find_connected_qubit(qubits, queue, hardware_qubits, error): +def _find_connected_qubit(qubits, queue, error, mapping): """Helper method for :meth:`qibo.transpiler.router.StarConnectivityRouter` and :meth:`qibo.transpiler.router.StarConnectivityPlacer`. Finds which qubit should be mapped to hardware middle qubit by looking at the two-qubit gates that follow. """ - possible_qubits = set(qubits) + possible_qubits = {qubits[0], qubits[1]} for next_gate in queue: if len(next_gate.qubits) > 2: raise_error( @@ -168,11 +116,13 @@ def _find_connected_qubit(qubits, queue, hardware_qubits, error): "Gates targeting more than 2 qubits are not supported", ) if len(next_gate.qubits) == 2: - possible_qubits &= {hardware_qubits.index(q) for q in next_gate.qubits} + possible_qubits &= { + mapping[next_gate.qubits[0]], + mapping[next_gate.qubits[1]], + } - if not possible_qubits: + if len(possible_qubits) == 0: return qubits[0] - if len(possible_qubits) == 1: return possible_qubits.pop() @@ -185,7 +135,6 @@ class CircuitMap: Also implements the initial two-qubit block decompositions. Args: - initial_layout (dict): initial physical to logical qubit mapping. circuit (:class:`qibo.models.circuit.Circuit`): circuit to be routed. blocks (:class:`qibo.transpiler.blocks.CircuitBlocks`, optional): circuit block representation. If ``None``, the blocks will be computed from the circuit. @@ -194,7 +143,6 @@ class CircuitMap: def __init__( self, - initial_layout: Optional[dict] = None, circuit: Optional[Circuit] = None, blocks: Optional[CircuitBlocks] = None, temp: Optional[bool] = False, @@ -204,23 +152,21 @@ def __init__( self._temporary = temp if self._temporary: return - elif circuit is None: + if circuit is None: raise_error(ValueError, "Circuit must be provided.") - if blocks is not None: self.circuit_blocks = blocks else: self.circuit_blocks = CircuitBlocks(circuit, index_names=True) - self._nqubits = circuit.nqubits - self._routed_blocks = CircuitBlocks(Circuit(circuit.nqubits)) + self.nqubits = circuit.nqubits + self._routed_blocks = CircuitBlocks( + Circuit(circuit.nqubits, wire_names=circuit.wire_names) + ) self._swaps = 0 - if initial_layout is None: - return - - self.wire_names = list(initial_layout.keys()) - self.physical_to_logical = list(initial_layout.values()) + self.wire_names = circuit.wire_names.copy() + self.physical_to_logical = list(range(self.nqubits)) @property def physical_to_logical(self): @@ -302,7 +248,7 @@ def routed_circuit(self, circuit_kwargs: Optional[dict] = None): def final_layout(self): """Returns the final physical-logical qubits mapping.""" - return {self.wire_names[i]: self._p2l[i] for i in range(self._nqubits)} + return {self.wire_names[i]: self._l2p[i] for i in range(self.nqubits)} def update(self, logical_swap: tuple): """Updates the qubit mapping after applying a ``SWAP`` @@ -368,7 +314,9 @@ class ShortestPaths(Router): If ``None``, defaults to :math:`42`. Defaults to ``None``. """ - def __init__(self, connectivity: nx.Graph, seed: Optional[int] = None): + def __init__( + self, connectivity: Optional[nx.Graph] = None, seed: Optional[int] = None + ): self.connectivity = connectivity self._front_layer = None self.circuit_map = None @@ -384,19 +332,19 @@ def added_swaps(self): """Returns the number of SWAP gates added to the circuit during routing.""" return self.circuit_map._swaps - def __call__(self, circuit: Circuit, initial_layout: dict): + def __call__(self, circuit: Circuit): """Circuit connectivity matching. Args: circuit (:class:`qibo.models.circuit.Circuit`): circuit to be matched to hardware connectivity. - initial_layout (dict): initial physical-to-logical qubit mapping Returns: (:class:`qibo.models.circuit.Circuit`, dict): circut mapped to hardware topology, and final physical-to-logical qubit mapping. """ - self._preprocessing(circuit=circuit, initial_layout=initial_layout) + assert_placement(circuit, self.connectivity) + self._preprocessing(circuit=circuit) while self._dag.number_of_nodes() != 0: execute_block_list = self._check_execution() if execute_block_list is not None: @@ -405,7 +353,6 @@ def __call__(self, circuit: Circuit, initial_layout: dict): self._find_new_mapping() circuit_kwargs = circuit.init_kwargs - circuit_kwargs["wire_names"] = list(initial_layout.keys()) routed_circuit = self.circuit_map.routed_circuit(circuit_kwargs=circuit_kwargs) if self._final_measurements is not None: routed_circuit = self._append_final_measurements( @@ -490,7 +437,7 @@ def _compute_cost(self, candidate: tuple): (list, int): best path to move qubits and qubit meeting point in the path. """ temporary_circuit = CircuitMap( - circuit=Circuit(self.circuit_map._nqubits), + circuit=Circuit(self.circuit_map.nqubits), blocks=deepcopy(self.circuit_map.circuit_blocks), ) @@ -573,7 +520,7 @@ def _update_front_layer(self): node[0] for node in self._dag.nodes(data="layer") if node[1] == 0 ] - def _preprocessing(self, circuit: Circuit, initial_layout: dict): + def _preprocessing(self, circuit: Circuit): """The following objects will be initialised: - circuit: class to represent circuit and to perform logical-physical qubit mapping. - _final_measurements: measurement gates at the end of the circuit. @@ -581,14 +528,13 @@ def _preprocessing(self, circuit: Circuit, initial_layout: dict): Args: circuit (:class:`qibo.models.circuit.Circuit`): circuit to be preprocessed. - initial_layout (dict): initial physical-to-logical qubit mapping. """ - - self.connectivity = _relabel_connectivity(self.connectivity, initial_layout) - + self.connectivity = nx.relabel_nodes( + self.connectivity, {v: i for i, v in enumerate(circuit.wire_names)} + ) copied_circuit = circuit.copy(deep=True) self._final_measurements = self._detach_final_measurements(copied_circuit) - self.circuit_map = CircuitMap(initial_layout, copied_circuit) + self.circuit_map = CircuitMap(copied_circuit) self._dag = _create_dag(self.circuit_map.blocks_logical_qubits_pairs()) self._update_front_layer() @@ -657,7 +603,7 @@ class Sabre(Router): def __init__( self, - connectivity: nx.Graph, + connectivity: Optional[nx.Graph] = None, lookahead: int = 2, decay_lookahead: float = 0.6, delta: float = 0.001, @@ -679,17 +625,17 @@ def __init__( self._temp_added_swaps = [] random.seed(seed) - def __call__(self, circuit: Circuit, initial_layout: dict): + def __call__(self, circuit: Circuit): """Route the circuit. Args: circuit (:class:`qibo.models.circuit.Circuit`): circuit to be routed. - initial_layout (dict): initial physical to logical qubit mapping. Returns: (:class:`qibo.models.circuit.Circuit`, dict): routed circuit and final layout. """ - self._preprocessing(circuit=circuit, initial_layout=initial_layout) + assert_placement(circuit, self.connectivity) + self._preprocessing(circuit=circuit) longest_path = np.max(self._dist_matrix) while self._dag.number_of_nodes() != 0: @@ -711,7 +657,6 @@ def __call__(self, circuit: Circuit, initial_layout: dict): self._shortest_path_routing() circuit_kwargs = circuit.init_kwargs - circuit_kwargs["wire_names"] = list(initial_layout.keys()) routed_circuit = self.circuit_map.routed_circuit(circuit_kwargs=circuit_kwargs) if self._final_measurements is not None: routed_circuit = self._append_final_measurements( @@ -725,7 +670,7 @@ def added_swaps(self): """Returns the number of SWAP gates added to the circuit during routing.""" return self.circuit_map._swaps - def _preprocessing(self, circuit: Circuit, initial_layout: dict): + def _preprocessing(self, circuit: Circuit): """The following objects will be initialised: - circuit: class to represent circuit and to perform logical-physical qubit mapping. - _final_measurements: measurement gates at the end of the circuit. @@ -738,14 +683,15 @@ def _preprocessing(self, circuit: Circuit, initial_layout: dict): Args: circuit (:class:`qibo.models.circuit.Circuit`): circuit to be preprocessed. - initial_layout (dict): initial physical-to-logical qubit mapping. """ - self.connectivity = _relabel_connectivity(self.connectivity, initial_layout) + self.connectivity = nx.relabel_nodes( + self.connectivity, {v: i for i, v in enumerate(circuit.wire_names)} + ) copied_circuit = circuit.copy(deep=True) self._final_measurements = self._detach_final_measurements(copied_circuit) - self.circuit_map = CircuitMap(initial_layout, copied_circuit) + self.circuit_map = CircuitMap(copied_circuit) self._dist_matrix = nx.floyd_warshall_numpy(self.connectivity) self._dag = _create_dag(self.circuit_map.blocks_logical_qubits_pairs()) self._memory_map = [] @@ -977,7 +923,7 @@ def _create_dag(gates_qubits_pairs: list): dag = nx.DiGraph() dag.add_nodes_from(range(len(gates_qubits_pairs))) - for i in range(len(gates_qubits_pairs)): + for i, _ in enumerate(gates_qubits_pairs): dag.nodes[i]["qubits"] = gates_qubits_pairs[i] # Find all successors @@ -986,7 +932,7 @@ def _create_dag(gates_qubits_pairs: list): saturated_qubits = [] for next_idx, next_gate in enumerate(gates_qubits_pairs[idx + 1 :]): for qubit in gate: - if (qubit in next_gate) and (not qubit in saturated_qubits): + if (qubit in next_gate) and (qubit not in saturated_qubits): saturated_qubits.append(qubit) connectivity_list.append((idx, next_idx + idx + 1)) if len(saturated_qubits) >= 2: diff --git a/src/qibo/transpiler/unroller.py b/src/qibo/transpiler/unroller.py index 17e2e385aa..2e875311fd 100644 --- a/src/qibo/transpiler/unroller.py +++ b/src/qibo/transpiler/unroller.py @@ -125,39 +125,6 @@ def __call__(self, circuit: Circuit): return translated_circuit -def assert_decomposition( - circuit: Circuit, - 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. - 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) <= 2: - try: - native_type_gate = NativeGates.from_gate(gate) - if not native_type_gate & native_gates: - raise_error( - DecompositionError, - f"{gate.name} is not a native gate.", - ) - except ValueError: - raise_error( - DecompositionError, - f"{gate.name} is not a native gate.", - ) - else: - raise_error( - DecompositionError, f"{gate.name} acts on more than two qubits." - ) - - def translate_gate( gate, native_gates: NativeGates, diff --git a/tests/conftest.py b/tests/conftest.py index d15e5164d1..c6bd642750 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -5,6 +5,7 @@ import sys +import networkx as nx import pytest from qibo.backends import _Global, construct_backend @@ -81,6 +82,40 @@ def clear(): _Global._transpiler = None +@pytest.fixture +def star_connectivity(): + def _star_connectivity(names=list(range(5)), middle_qubit_idx=2): + chip = nx.Graph() + chip.add_nodes_from(names) + graph_list = [ + (names[i], names[middle_qubit_idx]) + for i in range(len(names)) + if i != middle_qubit_idx + ] + chip.add_edges_from(graph_list) + return chip + + return _star_connectivity + + +@pytest.fixture +def grid_connectivity(): + def _grid_connectivity(names=list(range(5))): + chip = nx.Graph() + chip.add_nodes_from(names) + graph_list = [ + (names[0], names[1]), + (names[1], names[2]), + (names[2], names[3]), + (names[3], names[0]), + (names[0], names[4]), + ] + chip.add_edges_from(graph_list) + return chip + + return _grid_connectivity + + def pytest_generate_tests(metafunc): module_name = metafunc.module.__name__ diff --git a/tests/test_backends_global.py b/tests/test_backends_global.py index 99c1f9d450..dc0b6dfeaa 100644 --- a/tests/test_backends_global.py +++ b/tests/test_backends_global.py @@ -128,9 +128,9 @@ def test_set_get_transpiler(): transpiler = Passes( connectivity=connectivity, passes=[ - Preprocessing(connectivity), - Random(connectivity, seed=0), - Sabre(connectivity), + Preprocessing(), + Random(seed=0), + Sabre(), Unroller(NativeGates.default()), ], ) @@ -171,8 +171,13 @@ def natives(self): _Global._backend = backend transpiler = _Global.transpiler() - assert list(transpiler.connectivity.nodes) == [0, 1, 2, 3, 4] - assert list(transpiler.connectivity.edges) == [(0, 1), (1, 2), (2, 3), (3, 4)] + assert list(transpiler.connectivity.nodes) == ["A1", "A2", "A3", "A4", "A5"] + assert list(transpiler.connectivity.edges) == [ + ("A1", "A2"), + ("A2", "A3"), + ("A3", "A4"), + ("A4", "A5"), + ] assert ( NativeGates.CZ in transpiler.native_gates and NativeGates.GPI2 in transpiler.native_gates diff --git a/tests/test_measurements.py b/tests/test_measurements.py index 48151b23ab..af5344fb4f 100644 --- a/tests/test_measurements.py +++ b/tests/test_measurements.py @@ -434,7 +434,7 @@ def test_measurement_basis(backend, nqubits, outcome): def test_measurement_basis_list(backend): - c = Circuit(4) + c = Circuit(4, wire_names=["q0", "q1", "q2", "q3"]) c.add(gates.H(0)) c.add(gates.X(2)) c.add(gates.H(2)) diff --git a/tests/test_models_circuit.py b/tests/test_models_circuit.py index c6a72a39ff..b2b2ca904d 100644 --- a/tests/test_models_circuit.py +++ b/tests/test_models_circuit.py @@ -6,6 +6,7 @@ import pytest from qibo import Circuit, gates +from qibo.models.circuit import _resolve_qubits from qibo.models.utils import initialize @@ -48,6 +49,44 @@ def test_circuit_init(): assert c.nqubits == 2 +def test_resolve_qubits(): + nqubits, wire_names = _resolve_qubits(3, None) + assert nqubits == 3 and wire_names is None + nqubits, wire_names = _resolve_qubits(3, ["a", "b", "c"]) + assert nqubits == 3 and wire_names == ["a", "b", "c"] + nqubits, wire_names = _resolve_qubits(["a", "b", "c"], None) + assert nqubits == 3 and wire_names == ["a", "b", "c"] + nqubits, wire_names = _resolve_qubits(None, ["x", "y", "z"]) + assert nqubits == 3 and wire_names == ["x", "y", "z"] + + with pytest.raises(ValueError): + _resolve_qubits(None, None) + with pytest.raises(ValueError): + _resolve_qubits(3, ["a", "b"]) + with pytest.raises(ValueError): + _resolve_qubits(["a", "b", "c"], ["x", "y"]) + + +def test_circuit_init_resolve_qubits(): + a = Circuit(3) + assert a.nqubits == 3 and a.wire_names == [0, 1, 2] + b = Circuit(3, wire_names=["a", "b", "c"]) + assert b.nqubits == 3 and b.wire_names == ["a", "b", "c"] + c = Circuit(["a", "b", "c"]) + assert c.nqubits == 3 and c.wire_names == ["a", "b", "c"] + d = Circuit(wire_names=["x", "y", "z"]) + assert d.nqubits == 3 and d.wire_names == ["x", "y", "z"] + + +def test_circuit_init_resolve_qubits_err(): + with pytest.raises(ValueError): + a = Circuit() + with pytest.raises(ValueError): + b = Circuit(3, wire_names=["a", "b"]) + with pytest.raises(ValueError): + c = Circuit(["a", "b", "c"], wire_names=["x", "y"]) + + def test_eigenstate(backend): nqubits = 3 c = Circuit(nqubits) @@ -625,7 +664,7 @@ def test_circuit_draw(): "q3: ─────────o──|───────o──|────o──|──H─U1───|─x─\n" "q4: ────────────o──────────o───────o────o──H─x───" ) - circuit = Circuit(5) + circuit = Circuit(5, wire_names=["q0", "q1", "q2", "q3", "q4"]) for i1 in range(5): circuit.add(gates.H(i1)) for i2 in range(i1 + 1, 5): @@ -636,17 +675,19 @@ def test_circuit_draw(): assert str(circuit) == ref -def test_circuit_wire_names_errors(): +def test_circuit_wire_names(): + circuit = Circuit(5) + assert circuit.wire_names == [0, 1, 2, 3, 4] + assert circuit._wire_names == None + + circuit.wire_names = ["a", "b", "c", "d", "e"] + assert circuit.wire_names == ["a", "b", "c", "d", "e"] + assert circuit._wire_names == ["a", "b", "c", "d", "e"] + with pytest.raises(TypeError): - circuit = Circuit(5, wire_names=1) - with pytest.raises(ValueError): - circuit = Circuit(5, wire_names=["a", "b", "c"]) + circuit.wire_names = 5 with pytest.raises(ValueError): - circuit = Circuit(2, wire_names={"q0": "1", "q1": "2", "q2": "3"}) - with pytest.raises(ValueError): - circuit = Circuit(2, wire_names={"q0": "1", "q1": 2}) - with pytest.raises(ValueError): - circuit = Circuit(2, wire_names=["1", 2]) + circuit.wire_names = ["a", "b", "c", "d"] def test_circuit_draw_wire_names(): @@ -669,6 +710,24 @@ def test_circuit_draw_wire_names(): assert str(circuit) == ref +def test_circuit_draw_wire_names_int(): + ref = ( + "2133: ─H─U1─U1─U1─U1───────────────────────────x───\n" + + "8 : ───o──|──|──|──H─U1─U1─U1────────────────|─x─\n" + + "2319: ──────o──|──|────o──|──|──H─U1─U1────────|─|─\n" + + "0 : ─────────o──|───────o──|────o──|──H─U1───|─x─\n" + + "1908: ────────────o──────────o───────o────o──H─x───" + ) + circuit = Circuit(5, wire_names=[2133, 8, 2319, 0, 1908]) + for i1 in range(5): + circuit.add(gates.H(i1)) + for i2 in range(i1 + 1, 5): + circuit.add(gates.CU1(i2, i1, theta=0)) + circuit.add(gates.SWAP(0, 4)) + circuit.add(gates.SWAP(1, 3)) + assert str(circuit) == ref + + def test_circuit_draw_line_wrap(capsys): """Test circuit text draw with line wrap.""" ref_line_wrap_50 = ( @@ -705,7 +764,7 @@ def test_circuit_draw_line_wrap(capsys): + "q4: ... ───" ) - circuit = Circuit(5) + circuit = Circuit(5, wire_names=["q0", "q1", "q2", "q3", "q4"]) for i1 in range(5): circuit.add(gates.H(i1)) for i2 in range(i1 + 1, 5): @@ -766,7 +825,7 @@ def test_circuit_draw_line_wrap_names(capsys): + "q4: ... ───" ) - circuit = Circuit(5, wire_names={"q1": "a"}) + circuit = Circuit(5, wire_names=["q0", "a", "q2", "q3", "q4"]) for i1 in range(5): circuit.add(gates.H(i1)) for i2 in range(i1 + 1, 5): @@ -795,7 +854,7 @@ def test_circuit_draw_line_wrap_names(capsys): def test_circuit_draw_channels(capsys, legend): """Check that channels are drawn correctly.""" - circuit = Circuit(2, density_matrix=True) + circuit = Circuit(2, density_matrix=True, wire_names=["q0", "q1"]) circuit.add(gates.H(0)) circuit.add(gates.PauliNoiseChannel(0, list(zip(["X", "Z"], [0.1, 0.2])))) circuit.add(gates.H(1)) @@ -832,7 +891,7 @@ def test_circuit_draw_callbacks(capsys, legend): from qibo.callbacks import EntanglementEntropy entropy = EntanglementEntropy([0]) - c = Circuit(2) + c = Circuit(2, wire_names=["q0", "q1"]) c.add(gates.CallbackGate(entropy)) c.add(gates.H(0)) c.add(gates.CallbackGate(entropy)) @@ -863,7 +922,7 @@ def test_circuit_draw_labels(): + "q3: ─────────o──|───────o──|────o──|──H─G4───|─x─\n" + "q4: ────────────o──────────o───────o────o──H─x───" ) - circuit = Circuit(5) + circuit = Circuit(5, wire_names=["q0", "q1", "q2", "q3", "q4"]) for i1 in range(5): circuit.add(gates.H(i1)) for i2 in range(i1 + 1, 5): @@ -884,7 +943,7 @@ def test_circuit_draw_names(capsys): + "q3: ─────────o──|───────o──|────o──|──H─cx───|─x─\n" + "q4: ────────────o──────────o───────o────o──H─x───" ) - circuit = Circuit(5) + circuit = Circuit(5, wire_names=["q0", "q1", "q2", "q3", "q4"]) for i1 in range(5): circuit.add(gates.H(i1)) for i2 in range(i1 + 1, 5): diff --git a/tests/test_models_circuit_fuse.py b/tests/test_models_circuit_fuse.py index 22e9c74f0e..ae90d42b02 100644 --- a/tests/test_models_circuit_fuse.py +++ b/tests/test_models_circuit_fuse.py @@ -228,7 +228,7 @@ def test_fused_gate_draw(): "q3: ───────────────o──|───────────o──|──[─o──H─]─|──[─U1───]─|─x─\n" "q4: ──────────────────o──────────────o───────────o──[─o──H─]─x───" ) - circuit = Circuit(5) + circuit = Circuit(5, wire_names=["q0", "q1", "q2", "q3", "q4"]) for i1 in range(5): circuit.add(gates.H(i1)) for i2 in range(i1 + 1, 5): diff --git a/tests/test_tomography_gate_set_tomography.py b/tests/test_tomography_gate_set_tomography.py index d6d2ffd2df..be0c4d4fc6 100644 --- a/tests/test_tomography_gate_set_tomography.py +++ b/tests/test_tomography_gate_set_tomography.py @@ -261,7 +261,7 @@ def test_GST_non_invertible_matrix(): matrices = GST(gate_set=[], pauli_liouville=True, gauge_matrix=T) -def test_GST_with_transpiler(backend): +def test_GST_with_transpiler(backend, star_connectivity): import networkx as nx target_gates = [gates.SX(0), gates.Z(0), gates.CNOT(0, 1)] @@ -276,18 +276,15 @@ def test_GST_with_transpiler(backend): transpiler=None, ) # define transpiler - connectivity = nx.Graph() - # star connectivity - connectivity.add_edges_from([(0, 2), (1, 2), (2, 3), (2, 4)]) + connectivity = star_connectivity() transpiler = Passes( connectivity=connectivity, passes=[ - Preprocessing(connectivity), - Random(connectivity), - Sabre(connectivity), + Preprocessing(), + Random(), + Sabre(), Unroller(NativeGates.default(), backend=backend), ], - int_qubit_names=True, ) # transpiled GST T_empty_1q, T_empty_2q, *T_approx_gates = GST( diff --git a/tests/test_transpiler_asserts.py b/tests/test_transpiler_asserts.py new file mode 100644 index 0000000000..8b0b268003 --- /dev/null +++ b/tests/test_transpiler_asserts.py @@ -0,0 +1,151 @@ +import pytest + +from qibo import gates +from qibo.models.circuit import Circuit +from qibo.transpiler._exceptions import ( + ConnectivityError, + DecompositionError, + PlacementError, + TranspilerPipelineError, +) +from qibo.transpiler.asserts import ( + assert_circuit_equivalence, + assert_connectivity, + assert_decomposition, + assert_placement, +) +from qibo.transpiler.pipeline import restrict_connectivity_qubits +from qibo.transpiler.unroller import NativeGates + + +def test_assert_circuit_equivalence_equal(): + circ1 = Circuit(2) + circ2 = Circuit(2) + circ1.add(gates.X(0)) + circ1.add(gates.CZ(0, 1)) + circ2.add(gates.X(0)) + circ2.add(gates.CZ(0, 1)) + final_map = {0: 0, 1: 1} + assert_circuit_equivalence(circ1, circ2, final_map=final_map) + + +def test_assert_circuit_equivalence_swap(): + circ1 = Circuit(2) + circ2 = Circuit(2) + circ1.add(gates.X(0)) + circ2.add(gates.SWAP(0, 1)) + circ2.add(gates.X(1)) + final_map = {0: 1, 1: 0} + assert_circuit_equivalence(circ1, circ2, final_map=final_map) + + +def test_assert_circuit_equivalence_false(): + circ1 = Circuit(2) + circ2 = Circuit(2) + circ1.add(gates.X(0)) + circ2.add(gates.SWAP(0, 1)) + circ2.add(gates.X(1)) + final_map = {0: 0, 1: 1} + with pytest.raises(TranspilerPipelineError): + assert_circuit_equivalence(circ1, circ2, final_map=final_map) + + +def test_assert_placement_true(star_connectivity): + circuit = Circuit(5) + assert_placement(circuit, connectivity=star_connectivity()) + + +@pytest.mark.parametrize( + "qubits, names", [(1, ["A", "B", "C", "D", "E"]), (10, ["A", "B", "C", "D", "E"])] +) +def test_assert_placement_false(qubits, names, star_connectivity): + connectivity = star_connectivity() + circuit = Circuit(5) + circuit.nqubits = qubits + circuit._wire_names = names + with pytest.raises(PlacementError): + assert_placement(circuit, connectivity) + + connectivity = None + with pytest.raises(ValueError): + assert_placement(circuit, connectivity) + + +@pytest.mark.parametrize("qubits", [10, 1]) +def test_assert_placement_error(qubits, star_connectivity): + connectivity = star_connectivity() + circuit = Circuit(qubits) + with pytest.raises(PlacementError): + assert_placement(circuit, connectivity) + + +@pytest.mark.parametrize("names", [["A", "B", "C", "D", "E"], [0, 1, 2, 3, 4]]) +def test_assert_placement_restricted(names, star_connectivity): + connectivity = star_connectivity(names) + on_qubit = [names[0], names[2]] + circuit = Circuit(2, wire_names=on_qubit) + restricted_connectivity = restrict_connectivity_qubits(connectivity, on_qubit) + assert_placement(circuit, restricted_connectivity) + + +@pytest.mark.parametrize("names", [["A", "B", "C", "D", "E"], [0, 1, 2, 3, 4]]) +def test_assert_placement_restricted_error(names, star_connectivity): + connectivity = star_connectivity(names) + on_qubit = [names[0], names[2]] + circuit = Circuit(2, wire_names=[names[3], names[4]]) + restricted_connectivity = restrict_connectivity_qubits(connectivity, on_qubit) + with pytest.raises(PlacementError): + assert_placement(circuit, restricted_connectivity) + + +def test_assert_decomposition(): + circuit = Circuit(2) + circuit.add(gates.CZ(0, 1)) + circuit.add(gates.Z(0)) + circuit.add(gates.M(1)) + 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, native_gates=NativeGates.default()) + + +@pytest.mark.parametrize("gate", [gates.CNOT(0, 1), gates.iSWAP(0, 1)]) +def test_assert_decomposition_fail_2q(gate): + circuit = Circuit(2) + circuit.add(gate) + with pytest.raises(DecompositionError): + 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, native_gates=NativeGates.default()) + + +def test_assert_connectivity(star_connectivity): + names = ["A", "B", "C", "D", "E"] + circuit = Circuit(5, wire_names=names) + circuit.add(gates.CZ(0, 2)) + circuit.add(gates.CZ(1, 2)) + circuit.add(gates.CZ(2, 1)) + assert_connectivity(star_connectivity(names), circuit) + + +def test_assert_connectivity_false(star_connectivity): + circuit = Circuit(5) + circuit.add(gates.CZ(0, 1)) + with pytest.raises(ConnectivityError): + assert_connectivity(star_connectivity(), circuit) + + +def test_assert_connectivity_3q(star_connectivity): + circuit = Circuit(5) + circuit.add(gates.TOFFOLI(0, 1, 2)) + with pytest.raises(ConnectivityError): + assert_connectivity(star_connectivity(), circuit) diff --git a/tests/test_transpiler_decompositions.py b/tests/test_transpiler_decompositions.py index 44c299cc3b..e26af68841 100644 --- a/tests/test_transpiler_decompositions.py +++ b/tests/test_transpiler_decompositions.py @@ -5,7 +5,8 @@ from qibo.backends import NumpyBackend from qibo.models import Circuit from qibo.quantum_info.random_ensembles import random_unitary -from qibo.transpiler.unroller import NativeGates, assert_decomposition, translate_gate +from qibo.transpiler.asserts import assert_decomposition +from qibo.transpiler.unroller import NativeGates, translate_gate default_natives = NativeGates.Z | NativeGates.RZ | NativeGates.M | NativeGates.I diff --git a/tests/test_transpiler_optimizer.py b/tests/test_transpiler_optimizer.py index 171c7516d4..500f507178 100644 --- a/tests/test_transpiler_optimizer.py +++ b/tests/test_transpiler_optimizer.py @@ -6,22 +6,18 @@ from qibo.transpiler.optimizer import Preprocessing, Rearrange -def star_connectivity(): - chip = nx.Graph() - chip.add_nodes_from(list(range(5))) - graph_list = [(i, 2) for i in range(5) if i != 2] - chip.add_edges_from(graph_list) - return chip - - -def test_preprocessing_error(): +def test_preprocessing_error(star_connectivity): circ = Circuit(7) preprocesser = Preprocessing(connectivity=star_connectivity()) with pytest.raises(ValueError): new_circuit = preprocesser(circuit=circ) + circ = Circuit(5, wire_names=[0, 1, 2, "q3", "q4"]) + with pytest.raises(ValueError): + new_circuit = preprocesser(circuit=circ) + -def test_preprocessing_same(): +def test_preprocessing_same(star_connectivity): circ = Circuit(5) circ.add(gates.CNOT(0, 1)) preprocesser = Preprocessing(connectivity=star_connectivity()) @@ -29,7 +25,7 @@ def test_preprocessing_same(): assert new_circuit.ngates == 1 -def test_preprocessing_add(): +def test_preprocessing_add(star_connectivity): circ = Circuit(3) circ.add(gates.CNOT(0, 1)) preprocesser = Preprocessing(connectivity=star_connectivity()) diff --git a/tests/test_transpiler_pipeline.py b/tests/test_transpiler_pipeline.py index bfa13f9e4d..a8e43bc6aa 100644 --- a/tests/test_transpiler_pipeline.py +++ b/tests/test_transpiler_pipeline.py @@ -5,23 +5,17 @@ from qibo import gates from qibo.models import Circuit from qibo.transpiler._exceptions import ConnectivityError, TranspilerPipelineError +from qibo.transpiler.asserts import assert_circuit_equivalence, assert_transpiling from qibo.transpiler.optimizer import Preprocessing -from qibo.transpiler.pipeline import ( - Passes, - assert_circuit_equivalence, - assert_transpiling, - restrict_connectivity_qubits, -) -from qibo.transpiler.placer import Random, ReverseTraversal, Trivial +from qibo.transpiler.pipeline import Passes, restrict_connectivity_qubits +from qibo.transpiler.placer import Random, ReverseTraversal from qibo.transpiler.router import Sabre, ShortestPaths from qibo.transpiler.unroller import NativeGates, Unroller -def generate_random_circuit(nqubits, ngates, seed=None): - """Generate random circuits one-qubit rotations and CZ gates.""" - if seed is not None: # pragma: no cover - np.random.seed(seed) - +def generate_random_circuit(nqubits, ngates, names=None, seed=42): + """Generate a random circuit with RX and CZ gates.""" + np.random.seed(seed) one_qubit_gates = [gates.RX, gates.RY, gates.RZ, gates.X, gates.Y, gates.Z, gates.H] two_qubit_gates = [ gates.CNOT, @@ -34,7 +28,7 @@ def generate_random_circuit(nqubits, ngates, seed=None): ] n1, n2 = len(one_qubit_gates), len(two_qubit_gates) n = n1 + n2 if nqubits > 1 else n1 - circuit = Circuit(nqubits) + circuit = Circuit(nqubits, wire_names=names) for _ in range(ngates): igate = int(np.random.randint(0, n)) if igate >= n1: @@ -47,120 +41,58 @@ def generate_random_circuit(nqubits, ngates, seed=None): gate = one_qubit_gates[igate] if issubclass(gate, gates.ParametrizedGate): theta = 2 * np.pi * np.random.random() - circuit.add(gate(*q, theta=theta)) + circuit.add(gate(*q, theta=theta, trainable=False)) else: circuit.add(gate(*q)) return circuit -def star_connectivity(): - chip = nx.Graph() - chip.add_nodes_from(list(range(5))) - graph_list = [(i, 2) for i in range(5) if i != 2] - chip.add_edges_from(graph_list) - return chip - - -def test_restrict_qubits_error_no_subset(): +def test_restrict_qubits_error_no_subset(star_connectivity): with pytest.raises(ConnectivityError) as excinfo: - restrict_connectivity_qubits(star_connectivity(), [1, 2, 6]) + restrict_connectivity_qubits(star_connectivity(), [0, 1, 5]) assert "Some qubits are not in the original connectivity." in str(excinfo.value) -def test_restrict_qubits_error_not_connected(): +def test_restrict_qubits_error_not_connected(star_connectivity): with pytest.raises(ConnectivityError) as excinfo: - restrict_connectivity_qubits(star_connectivity(), [1, 3]) + restrict_connectivity_qubits(star_connectivity(), [0, 1]) assert "New connectivity graph is not connected." in str(excinfo.value) -def test_restrict_qubits(): - new_connectivity = restrict_connectivity_qubits(star_connectivity(), [1, 2, 3]) - assert list(new_connectivity.nodes) == [1, 2, 3] - assert list(new_connectivity.edges) == [(1, 2), (2, 3)] - - -@pytest.mark.parametrize("ngates", [5, 10, 50]) -def test_pipeline_default(ngates): - circ = generate_random_circuit(nqubits=5, ngates=ngates) - default_transpiler = Passes(passes=None, connectivity=star_connectivity()) - transpiled_circ, final_layout = default_transpiler(circ) - initial_layout = default_transpiler.get_initial_layout() - assert_transpiling( - original_circuit=circ, - transpiled_circuit=transpiled_circ, - connectivity=star_connectivity(), - initial_layout=initial_layout, - final_layout=final_layout, - native_gates=NativeGates.default(), - check_circuit_equivalence=False, +def test_restrict_qubits(star_connectivity): + new_connectivity = restrict_connectivity_qubits( + star_connectivity(["A", "B", "C", "D", "E"]), ["A", "B", "C"] ) - - -def test_assert_circuit_equivalence_equal(): - circ1 = Circuit(2) - circ2 = Circuit(2) - circ1.add(gates.X(0)) - circ1.add(gates.CZ(0, 1)) - circ2.add(gates.X(0)) - circ2.add(gates.CZ(0, 1)) - final_map = {"q0": 0, "q1": 1} - assert_circuit_equivalence(circ1, circ2, final_map=final_map) - - -def test_assert_circuit_equivalence_swap(): - circ1 = Circuit(2) - circ2 = Circuit(2) - circ1.add(gates.X(0)) - circ2.add(gates.SWAP(0, 1)) - circ2.add(gates.X(1)) - final_map = {"q0": 1, "q1": 0} - assert_circuit_equivalence(circ1, circ2, final_map=final_map) - - -def test_assert_circuit_equivalence_false(): - circ1 = Circuit(2) - circ2 = Circuit(2) - circ1.add(gates.X(0)) - circ2.add(gates.SWAP(0, 1)) - circ2.add(gates.X(1)) - final_map = {"q0": 0, "q1": 1} - with pytest.raises(TranspilerPipelineError): - assert_circuit_equivalence(circ1, circ2, final_map=final_map) - - -def test_int_qubit_names(): - circ = Circuit(2) - final_map = {i: i for i in range(5)} - default_transpiler = Passes( - passes=None, connectivity=star_connectivity(), int_qubit_names=True - ) - _, final_layout = default_transpiler(circ) - assert final_map == final_layout + assert list(new_connectivity.nodes) == ["A", "B", "C"] + assert list(new_connectivity.edges) == [("A", "C"), ("B", "C")] def test_assert_circuit_equivalence_wrong_nqubits(): circ1 = Circuit(1) circ2 = Circuit(2) - final_map = {"q0": 0, "q1": 1} + final_map = {0: 0, 1: 1} with pytest.raises(ValueError): assert_circuit_equivalence(circ1, circ2, final_map=final_map) -def test_error_connectivity(): - with pytest.raises(TranspilerPipelineError): - default_transpiler = Passes(passes=None, connectivity=None) - - @pytest.mark.parametrize("qubits", [3, 5]) -def test_is_satisfied(qubits): - default_transpiler = Passes(passes=None, connectivity=star_connectivity()) - circuit = Circuit(qubits) +def test_is_satisfied(qubits, star_connectivity): + default_transpiler = Passes( + passes=None, connectivity=star_connectivity(), on_qubits=list(range(qubits)) + ) + circuit = Circuit(qubits, wire_names=list(range(qubits))) circuit.add(gates.CZ(0, 2)) circuit.add(gates.Z(0)) assert default_transpiler.is_satisfied(circuit) -def test_is_satisfied_false_decomposition(): +def test_is_satisfied_false_placement(star_connectivity): + default_transpiler = Passes(passes=None, connectivity=star_connectivity()) + circuit = Circuit(5, wire_names=["A", "B", "C", "D", "E"]) + assert not default_transpiler.is_satisfied(circuit) + + +def test_is_satisfied_false_decomposition(star_connectivity): default_transpiler = Passes(passes=None, connectivity=star_connectivity()) circuit = Circuit(5) circuit.add(gates.CZ(0, 2)) @@ -168,7 +100,7 @@ def test_is_satisfied_false_decomposition(): assert not default_transpiler.is_satisfied(circuit) -def test_is_satisfied_false_connectivity(): +def test_is_satisfied_false_connectivity(star_connectivity): default_transpiler = Passes(passes=None, connectivity=star_connectivity()) circuit = Circuit(5) circuit.add(gates.CZ(0, 1)) @@ -176,104 +108,76 @@ def test_is_satisfied_false_connectivity(): assert not default_transpiler.is_satisfied(circuit) -@pytest.mark.parametrize("qubits", [2, 5]) -@pytest.mark.parametrize("gates", [5, 20]) -@pytest.mark.parametrize("placer", [Random, Trivial, ReverseTraversal]) -@pytest.mark.parametrize("routing", [ShortestPaths, Sabre]) -def test_custom_passes(placer, routing, gates, qubits): - circ = generate_random_circuit(nqubits=qubits, ngates=gates) +@pytest.mark.parametrize("nqubits", [2, 3, 5]) +@pytest.mark.parametrize("ngates", [5, 20]) +@pytest.mark.parametrize("placer", [Random, ReverseTraversal]) +@pytest.mark.parametrize("router", [ShortestPaths, Sabre]) +def test_custom_passes(placer, router, ngates, nqubits, star_connectivity): + connectivity = star_connectivity() + circ = generate_random_circuit(nqubits=nqubits, ngates=ngates) custom_passes = [] - custom_passes.append(Preprocessing(connectivity=star_connectivity())) + custom_passes.append(Preprocessing()) if placer == ReverseTraversal: custom_passes.append( placer( - connectivity=star_connectivity(), - routing_algorithm=routing(connectivity=star_connectivity()), + routing_algorithm=router(), ) ) else: - custom_passes.append(placer(connectivity=star_connectivity())) - custom_passes.append(routing(connectivity=star_connectivity())) + custom_passes.append(placer()) + custom_passes.append(router()) custom_passes.append(Unroller(native_gates=NativeGates.default())) custom_pipeline = Passes( - custom_passes, - connectivity=star_connectivity(), + passes=custom_passes, + connectivity=connectivity, native_gates=NativeGates.default(), ) transpiled_circ, final_layout = custom_pipeline(circ) - initial_layout = custom_pipeline.get_initial_layout() assert_transpiling( original_circuit=circ, transpiled_circuit=transpiled_circ, - connectivity=star_connectivity(), - initial_layout=initial_layout, + connectivity=connectivity, final_layout=final_layout, native_gates=NativeGates.default(), ) -@pytest.mark.parametrize("gates", [5, 20]) -@pytest.mark.parametrize("placer", [Random, Trivial, ReverseTraversal]) +@pytest.mark.parametrize("ngates", [5, 20]) +@pytest.mark.parametrize("placer", [Random, ReverseTraversal]) @pytest.mark.parametrize("routing", [ShortestPaths, Sabre]) -def test_custom_passes_restrict(gates, placer, routing): - circ = generate_random_circuit(nqubits=3, ngates=gates) +@pytest.mark.parametrize("restrict_names", [[1, 2, 3], [0, 2, 4], [4, 2, 3]]) +def test_custom_passes_restrict( + ngates, placer, routing, restrict_names, star_connectivity +): + connectivity = star_connectivity() + circ = generate_random_circuit(nqubits=3, ngates=ngates, names=restrict_names) custom_passes = [] - custom_passes.append(Preprocessing(connectivity=star_connectivity())) + custom_passes.append(Preprocessing()) if placer == ReverseTraversal: custom_passes.append( placer( - connectivity=star_connectivity(), - routing_algorithm=routing(connectivity=star_connectivity()), + routing_algorithm=routing(), ) ) else: - custom_passes.append(placer(connectivity=star_connectivity())) - custom_passes.append(routing(connectivity=star_connectivity())) + custom_passes.append(placer()) + custom_passes.append(routing()) custom_passes.append(Unroller(native_gates=NativeGates.default())) custom_pipeline = Passes( - custom_passes, - connectivity=star_connectivity(), + passes=custom_passes, + connectivity=connectivity, native_gates=NativeGates.default(), - on_qubits=[1, 2, 3], + on_qubits=restrict_names, ) transpiled_circ, final_layout = custom_pipeline(circ) - initial_layout = custom_pipeline.get_initial_layout() assert_transpiling( original_circuit=circ, transpiled_circuit=transpiled_circ, - connectivity=restrict_connectivity_qubits(star_connectivity(), [1, 2, 3]), - initial_layout=initial_layout, + connectivity=restrict_connectivity_qubits(star_connectivity(), restrict_names), final_layout=final_layout, native_gates=NativeGates.default(), ) - assert transpiled_circ.wire_names == ["q1", "q2", "q3"] - - -def test_custom_passes_multiple_placer(): - custom_passes = [] - custom_passes.append(Random(connectivity=star_connectivity())) - custom_passes.append(Trivial(connectivity=star_connectivity())) - custom_pipeline = Passes( - custom_passes, - connectivity=star_connectivity(), - native_gates=NativeGates.default(), - ) - circ = generate_random_circuit(nqubits=5, ngates=20) - with pytest.raises(TranspilerPipelineError): - transpiled_circ, final_layout = custom_pipeline(circ) - - -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=NativeGates.default(), - ) - circ = generate_random_circuit(nqubits=5, ngates=20) - with pytest.raises(TranspilerPipelineError): - transpiled_circ, final_layout = custom_pipeline(circ) + assert set(transpiled_circ.wire_names) == set(restrict_names) def test_custom_passes_wrong_pass(): @@ -281,32 +185,30 @@ def test_custom_passes_wrong_pass(): custom_pipeline = Passes(passes=custom_passes, connectivity=None) circ = generate_random_circuit(nqubits=5, ngates=5) with pytest.raises(TranspilerPipelineError): - transpiled_circ, final_layout = custom_pipeline(circ) + custom_pipeline(circ) -def test_int_qubit_names(): - connectivity = star_connectivity() +def test_int_qubit_names(star_connectivity): + names = [980, 123, 45, 9, 210464] + connectivity = star_connectivity(names) transpiler = Passes( connectivity=connectivity, passes=[ - Preprocessing(connectivity), - Random(connectivity, seed=0), - Sabre(connectivity), + Preprocessing(), + Random(seed=0), + Sabre(), Unroller(NativeGates.default()), ], - int_qubit_names=True, ) - circuit = Circuit(1) + circuit = Circuit(1, wire_names=[123]) circuit.add(gates.I(0)) circuit.add(gates.H(0)) circuit.add(gates.M(0)) transpiled_circuit, final_map = transpiler(circuit) - initial_layout = transpiler.get_initial_layout() assert_transpiling( original_circuit=circuit, transpiled_circuit=transpiled_circuit, connectivity=connectivity, - initial_layout=initial_layout, final_layout=final_map, native_gates=NativeGates.default(), ) diff --git a/tests/test_transpiler_placer.py b/tests/test_transpiler_placer.py index 6855a7eeac..13fef50904 100644 --- a/tests/test_transpiler_placer.py +++ b/tests/test_transpiler_placer.py @@ -3,98 +3,26 @@ from qibo import gates from qibo.models import Circuit -from qibo.transpiler._exceptions import PlacementError +from qibo.transpiler._exceptions import PlacementError, TranspilerPipelineError +from qibo.transpiler.asserts import assert_placement from qibo.transpiler.pipeline import restrict_connectivity_qubits from qibo.transpiler.placer import ( - Custom, Random, ReverseTraversal, StarConnectivityPlacer, Subgraph, - Trivial, _find_gates_qubits_pairs, - assert_mapping_consistency, - assert_placement, ) from qibo.transpiler.router import ShortestPaths -def star_connectivity(): - chip = nx.Graph() - chip.add_nodes_from(list(range(5))) - graph_list = [(i, 2) for i in range(5) if i != 2] - chip.add_edges_from(graph_list) - return chip - - -def star_circuit(): - circuit = Circuit(5) +def star_circuit(names=[0, 1, 2, 3, 4]): + circuit = Circuit(5, wire_names=names) for i in range(1, 5): circuit.add(gates.CNOT(i, 0)) return circuit -@pytest.mark.parametrize("connectivity", [star_connectivity(), None]) -@pytest.mark.parametrize( - "layout", - [{"q0": 0, "q1": 1, "q2": 2, "q3": 3, "q4": 4}, {0: 0, 1: 1, 2: 2, 3: 3, 4: 4}], -) -def test_assert_placement_true(layout, connectivity): - circuit = Circuit(5) - assert_placement(circuit, layout, connectivity=connectivity) - - -@pytest.mark.parametrize("qubits", [5, 3]) -@pytest.mark.parametrize( - "layout", [{"q0": 0, "q1": 1, "q2": 2, "q3": 3}, {"q0": 0, "q0": 1, "q2": 2}] -) -def test_assert_placement_false(qubits, layout): - circuit = Circuit(qubits) - with pytest.raises(PlacementError): - assert_placement(circuit, layout) - - -@pytest.mark.parametrize( - "layout", - [{"q0": 0, "q1": 1, "q2": 2, "q3": 3, "q4": 4}, {0: 0, 1: 1, 2: 2, 3: 3, 4: 4}], -) -def test_mapping_consistency(layout): - assert_mapping_consistency(layout) - - -@pytest.mark.parametrize( - "layout", - [ - {"q0": 0, "q1": 0, "q2": 1, "q3": 4, "q4": 3}, - {"q0": 0, "q1": 2, "q0": 1, "q3": 4, "q4": 3}, - ], -) -def test_mapping_consistency_error(layout): - with pytest.raises(PlacementError): - assert_mapping_consistency(layout) - - -def test_mapping_consistency_restricted(): - layout = {"q0": 0, "q2": 1} - connectivity = star_connectivity() - restricted_connectivity = restrict_connectivity_qubits(connectivity, [0, 2]) - assert_mapping_consistency(layout, restricted_connectivity) - - -@pytest.mark.parametrize( - "layout", - [ - {"q0": 0, "q2": 2}, - {"q0": 0, "q1": 1}, - ], -) -def test_mapping_consistency_restricted_error(layout): - connectivity = star_connectivity() - restricted_connectivity = restrict_connectivity_qubits(connectivity, [0, 2]) - with pytest.raises(PlacementError): - assert_mapping_consistency(layout, restricted_connectivity) - - def test_gates_qubits_pairs(): circuit = Circuit(5) circuit.add(gates.CNOT(0, 1)) @@ -111,99 +39,13 @@ def test_gates_qubits_pairs_error(): gates_qubits_pairs = _find_gates_qubits_pairs(circuit) -def test_trivial(): - circuit = Circuit(5) - connectivity = star_connectivity() - placer = Trivial(connectivity=connectivity) - layout = placer(circuit) - assert layout == {"q0": 0, "q1": 1, "q2": 2, "q3": 3, "q4": 4} - assert_placement(circuit, layout) - - -def test_trivial_restricted(): - circuit = Circuit(2) - connectivity = star_connectivity() - restricted_connectivity = restrict_connectivity_qubits(connectivity, [0, 2]) - placer = Trivial(connectivity=restricted_connectivity) - layout = placer(circuit) - assert layout == {"q0": 0, "q2": 1} - assert_placement( - circuit=circuit, layout=layout, connectivity=restricted_connectivity - ) - - -def test_trivial_error(): - circuit = Circuit(4) - connectivity = star_connectivity() - placer = Trivial(connectivity=connectivity) - with pytest.raises(PlacementError): - layout = placer(circuit) - - -@pytest.mark.parametrize( - "custom_layout", [[4, 3, 2, 1, 0], {"q0": 4, "q1": 3, "q2": 2, "q3": 1, "q4": 0}] -) -@pytest.mark.parametrize("give_circuit", [True, False]) -@pytest.mark.parametrize("give_connectivity", [True, False]) -def test_custom(custom_layout, give_circuit, give_connectivity): - if give_circuit: - circuit = Circuit(5) - else: - circuit = None - if give_connectivity: - connectivity = star_connectivity() - else: - connectivity = None - placer = Custom(connectivity=connectivity, initial_map=custom_layout) - layout = placer(circuit) - assert layout == {"q0": 4, "q1": 3, "q2": 2, "q3": 1, "q4": 0} - - -@pytest.mark.parametrize("custom_layout", [[1, 0], {"q0": 1, "q2": 0}]) -def test_custom_restricted(custom_layout): - circuit = Circuit(2) - connectivity = star_connectivity() - restricted_connectivity = restrict_connectivity_qubits(connectivity, [0, 2]) - placer = Custom(connectivity=restricted_connectivity, initial_map=custom_layout) - layout = placer(circuit) - assert layout == {"q0": 1, "q2": 0} - assert_placement( - circuit=circuit, layout=layout, connectivity=restricted_connectivity - ) - - -def test_custom_error_circuit(): - circuit = Circuit(3) - custom_layout = [4, 3, 2, 1, 0] - connectivity = star_connectivity() - placer = Custom(connectivity=connectivity, initial_map=custom_layout) - with pytest.raises(PlacementError): - layout = placer(circuit) - - -def test_custom_error_no_circuit(): - connectivity = star_connectivity() - custom_layout = {"q0": 4, "q1": 3, "q2": 2, "q3": 0, "q4": 0} - placer = Custom(connectivity=connectivity, initial_map=custom_layout) - with pytest.raises(PlacementError): - layout = placer() - - -def test_custom_error_type(): - circuit = Circuit(5) - connectivity = star_connectivity() - layout = 1 - placer = Custom(connectivity=connectivity, initial_map=layout) - with pytest.raises(TypeError): - layout = placer(circuit) - - -def test_subgraph_perfect(): +def test_subgraph_perfect(star_connectivity): connectivity = star_connectivity() placer = Subgraph(connectivity=connectivity) - layout = placer(star_circuit()) - assert layout["q2"] == 0 - assert_placement(star_circuit(), layout) + circuit = star_circuit() + placer(circuit) + assert circuit.wire_names[0] == 2 + assert_placement(circuit, connectivity) def imperfect_circuit(): @@ -220,23 +62,24 @@ def imperfect_circuit(): return circuit -def test_subgraph_non_perfect(): +def test_subgraph_non_perfect(star_connectivity): connectivity = star_connectivity() placer = Subgraph(connectivity=connectivity) - layout = placer(imperfect_circuit()) - assert_placement(imperfect_circuit(), layout) + circuit = imperfect_circuit() + placer(circuit) + assert_placement(circuit, connectivity) -def test_subgraph_error(): +def test_subgraph_error(star_connectivity): connectivity = star_connectivity() placer = Subgraph(connectivity=connectivity) circuit = Circuit(5) with pytest.raises(ValueError): - layout = placer(circuit) + placer(circuit) -def test_subgraph_restricted(): - circuit = Circuit(4) +def test_subgraph_restricted(star_connectivity): + circuit = Circuit(4, wire_names=[0, 2, 3, 4]) circuit.add(gates.CNOT(0, 3)) circuit.add(gates.CNOT(0, 1)) circuit.add(gates.CNOT(3, 2)) @@ -246,66 +89,57 @@ def test_subgraph_restricted(): connectivity = star_connectivity() restricted_connectivity = restrict_connectivity_qubits(connectivity, [0, 2, 3, 4]) placer = Subgraph(connectivity=restricted_connectivity) - layout = placer(circuit) - assert_placement( - circuit=circuit, layout=layout, connectivity=restricted_connectivity - ) + placer(circuit) + assert_placement(circuit, restricted_connectivity) @pytest.mark.parametrize("reps", [1, 10, 100]) -def test_random(reps): - connectivity = star_connectivity() +@pytest.mark.parametrize("names", [["A", "B", "C", "D", "E"], [0, 1, 2, 3, 4]]) +def test_random(reps, names, star_connectivity): + connectivity = star_connectivity(names) placer = Random(connectivity=connectivity, samples=reps) - layout = placer(star_circuit()) - assert_placement(star_circuit(), layout) - - -def test_random_perfect(): - circ = Circuit(5) - circ.add(gates.CZ(0, 1)) - connectivity = star_connectivity() - placer = Random(connectivity=connectivity, samples=1000) - layout = placer(circ) - assert_placement(star_circuit(), layout) + circuit = star_circuit(names=names) + placer(circuit) + assert_placement(circuit, connectivity) -def test_random_restricted(): - circuit = Circuit(4) +def test_random_restricted(star_connectivity): + names = [0, 1, 2, 3, 4] + circuit = Circuit(4, wire_names=[0, 2, 3, 4]) circuit.add(gates.CNOT(1, 3)) circuit.add(gates.CNOT(2, 1)) circuit.add(gates.CNOT(3, 2)) circuit.add(gates.CNOT(2, 1)) circuit.add(gates.CNOT(1, 2)) circuit.add(gates.CNOT(3, 1)) - connectivity = star_connectivity() + connectivity = star_connectivity(names) restricted_connectivity = restrict_connectivity_qubits(connectivity, [0, 2, 3, 4]) placer = Random(connectivity=restricted_connectivity, samples=100) - layout = placer(circuit) - assert_placement( - circuit=circuit, layout=layout, connectivity=restricted_connectivity - ) + placer(circuit) + assert_placement(circuit, restricted_connectivity) -@pytest.mark.parametrize("gates", [None, 5, 13]) -def test_reverse_traversal(gates): - circuit = star_circuit() - connectivity = star_connectivity() +@pytest.mark.parametrize("ngates", [None, 5, 13]) +@pytest.mark.parametrize("names", [["A", "B", "C", "D", "E"], [0, 1, 2, 3, 4]]) +def test_reverse_traversal(ngates, names, star_connectivity): + circuit = star_circuit(names=names) + connectivity = star_connectivity(names=names) routing = ShortestPaths(connectivity=connectivity) - placer = ReverseTraversal(connectivity, routing, depth=gates) - layout = placer(circuit) - assert_placement(circuit, layout) + placer = ReverseTraversal(routing, connectivity, depth=ngates) + placer(circuit) + assert_placement(circuit, connectivity) -def test_reverse_traversal_no_gates(): +def test_reverse_traversal_no_gates(star_connectivity): connectivity = star_connectivity() routing = ShortestPaths(connectivity=connectivity) - placer = ReverseTraversal(connectivity, routing, depth=10) + placer = ReverseTraversal(routing, connectivity, depth=10) circuit = Circuit(5) with pytest.raises(ValueError): - layout = placer(circuit) + placer(circuit) -def test_reverse_traversal_restricted(): +def test_reverse_traversal_restricted(star_connectivity): circuit = Circuit(4) circuit.add(gates.CNOT(1, 3)) circuit.add(gates.CNOT(2, 1)) @@ -314,34 +148,42 @@ def test_reverse_traversal_restricted(): circuit.add(gates.CNOT(1, 2)) circuit.add(gates.CNOT(3, 1)) connectivity = star_connectivity() - restricted_connectivity = restrict_connectivity_qubits(connectivity, [0, 2, 3, 4]) + restrict_names = [0, 2, 3, 4] + restricted_connectivity = restrict_connectivity_qubits(connectivity, restrict_names) + circuit.wire_names = restrict_names routing = ShortestPaths(connectivity=restricted_connectivity) placer = ReverseTraversal( connectivity=restricted_connectivity, routing_algorithm=routing, depth=5 ) - layout = placer(circuit) - assert_placement( - circuit=circuit, layout=layout, connectivity=restricted_connectivity - ) + placer(circuit) + assert_placement(circuit, restricted_connectivity) -def test_star_connectivity_placer(): - circ = Circuit(3) +def test_star_connectivity_placer(star_connectivity): + circ = Circuit(5) circ.add(gates.CZ(0, 1)) circ.add(gates.CZ(1, 2)) circ.add(gates.CZ(0, 2)) - placer = StarConnectivityPlacer(middle_qubit=2) - layout = placer(circ) - assert_placement(circ, layout) - assert layout == {"q0": 0, "q1": 2, "q2": 1} + connectivity = star_connectivity() + placer = StarConnectivityPlacer(connectivity) + placer(circ) + assert_placement(circ, connectivity) + assert circ.wire_names == [0, 2, 1, 3, 4] @pytest.mark.parametrize("first", [True, False]) -def test_star_connectivity_placer_error(first): - circ = Circuit(3) +def test_star_connectivity_placer_error(first, star_connectivity): + circ = Circuit(5) if first: circ.add(gates.CZ(0, 1)) circ.add(gates.TOFFOLI(0, 1, 2)) - placer = StarConnectivityPlacer(middle_qubit=2) + connectivity = star_connectivity() + placer = StarConnectivityPlacer(connectivity) with pytest.raises(PlacementError): - layout = placer(circ) + placer(circ) + + chip = nx.Graph() + chip.add_edges_from([(0, 1), (1, 2), (2, 3), (3, 4)]) + with pytest.raises(ValueError): + placer = StarConnectivityPlacer(chip) + placer(circ) diff --git a/tests/test_transpiler_router.py b/tests/test_transpiler_router.py index 80b0e72aa3..06899e78d3 100644 --- a/tests/test_transpiler_router.py +++ b/tests/test_transpiler_router.py @@ -9,61 +9,48 @@ from qibo.models import Circuit from qibo.quantum_info.random_ensembles import random_unitary from qibo.transpiler._exceptions import ConnectivityError -from qibo.transpiler.blocks import Block -from qibo.transpiler.optimizer import Preprocessing -from qibo.transpiler.pipeline import ( +from qibo.transpiler.asserts import ( + _transpose_qubits, assert_circuit_equivalence, - restrict_connectivity_qubits, -) -from qibo.transpiler.placer import ( - Custom, - Random, - StarConnectivityPlacer, - Subgraph, - Trivial, + assert_connectivity, assert_placement, ) +from qibo.transpiler.pipeline import restrict_connectivity_qubits +from qibo.transpiler.placer import Random, StarConnectivityPlacer, Subgraph from qibo.transpiler.router import ( CircuitMap, Sabre, ShortestPaths, StarConnectivityRouter, - assert_connectivity, ) -def star_connectivity(middle_qubit=2): - chip = nx.Graph() - chip.add_nodes_from(list(range(5))) - graph_list = [(i, middle_qubit) for i in range(5) if i != middle_qubit] - chip.add_edges_from(graph_list) - return chip - - -def grid_connectivity(): +def line_connectivity(n, names=None): + if names is None: + names = list(range(n)) chip = nx.Graph() - chip.add_nodes_from(list(range(5))) - graph_list = [(0, 1), (1, 2), (2, 3), (3, 0), (0, 4)] + chip.add_nodes_from(names) + graph_list = [(names[i], names[i + 1]) for i in range(n - 1)] chip.add_edges_from(graph_list) return chip -def line_connectivity(n): - chip = nx.Graph() - chip.add_nodes_from(list(range(n))) - graph_list = [(i, i + 1) for i in range(n - 1)] - chip.add_edges_from(graph_list) - return chip - - -def generate_random_circuit(nqubits, ngates, seed=42): +def generate_random_circuit(nqubits, ngates, names=None, seed=42): """Generate a random circuit with RX and CZ gates.""" np.random.seed(seed) - one_qubit_gates = [gates.RX, gates.RY, gates.RZ] - two_qubit_gates = [gates.CZ, gates.CNOT, gates.SWAP] + one_qubit_gates = [gates.RX, gates.RY, gates.RZ, gates.X, gates.Y, gates.Z, gates.H] + two_qubit_gates = [ + gates.CNOT, + gates.CZ, + gates.SWAP, + gates.iSWAP, + gates.CRX, + gates.CRY, + gates.CRZ, + ] n1, n2 = len(one_qubit_gates), len(two_qubit_gates) n = n1 + n2 if nqubits > 1 else n1 - circuit = Circuit(nqubits) + circuit = Circuit(nqubits, wire_names=names) for _ in range(ngates): igate = int(np.random.randint(0, n)) if igate >= n1: @@ -89,9 +76,9 @@ def star_circuit(): return circuit -def matched_circuit(): +def matched_circuit(names): """Return a simple circuit that can be executed on star connectivity""" - circuit = Circuit(5) + circuit = Circuit(5, wire_names=names) circuit.add(gates.CZ(0, 2)) circuit.add(gates.CZ(1, 2)) circuit.add(gates.Z(1)) @@ -100,111 +87,138 @@ def matched_circuit(): return circuit -def test_assert_connectivity(): - assert_connectivity(star_connectivity(), matched_circuit()) +def test_bell_state_3q(): + circuit = Circuit(3) + circuit.add(gates.H(0)) + circuit.add(gates.CNOT(0, 2)) + circuit.add(gates.X(0)) + circuit.add(gates.M(0, 1, 2)) + c = circuit.copy() + connectivity = line_connectivity(3, None) + router = Sabre(connectivity=connectivity) + routed_circuit, final_map = router(c) -def test_assert_connectivity_false(): - circuit = Circuit(5) - circuit.add(gates.CZ(0, 1)) - with pytest.raises(ConnectivityError): - assert_connectivity(star_connectivity(), circuit) + backend = NumpyBackend() + state = np.array([1, 0, 0, 0, 0, 0, 0, 0]) + original_state = backend.execute_circuit(circuit, state).state() + target_state = backend.execute_circuit(routed_circuit, state).state() + target_state = _transpose_qubits(target_state, list(final_map.values())) + assert np.all(np.isclose(np.real(original_state), np.real(target_state))) -def test_assert_connectivity_3q(): - circuit = Circuit(5) - circuit.add(gates.TOFFOLI(0, 1, 2)) - with pytest.raises(ConnectivityError): - assert_connectivity(star_connectivity(), circuit) +@pytest.mark.parametrize("ngates", [5, 25]) +def test_random_circuits_5q(ngates, star_connectivity): + connectivity = star_connectivity() + placer = Random(connectivity) + transpiler = ShortestPaths(connectivity) + circuit = generate_random_circuit(nqubits=5, ngates=ngates) + original_circuit = circuit.copy() + placer(circuit) + transpiled_circuit, final_qubit_map = transpiler(circuit) + + assert transpiler.added_swaps >= 0 + assert_connectivity(connectivity, transpiled_circuit) + assert_placement(transpiled_circuit, connectivity) + assert ngates + transpiler.added_swaps == transpiled_circuit.ngates + assert_circuit_equivalence( + original_circuit=original_circuit, + transpiled_circuit=transpiled_circuit, + final_map=final_qubit_map, + ) + + +@pytest.mark.parametrize("ngates", [5, 25]) +def test_random_circuits_5q_grid(ngates, grid_connectivity): + connectivity = grid_connectivity() + placer = Random(connectivity) + transpiler = ShortestPaths(connectivity) + + circuit = generate_random_circuit(nqubits=5, ngates=ngates) + original_circuit = circuit.copy() + placer(circuit) + transpiled_circuit, final_qubit_map = transpiler(circuit) -@pytest.mark.parametrize("gates", [5, 25]) -@pytest.mark.parametrize("placer", [Trivial, Random]) -@pytest.mark.parametrize("connectivity", [star_connectivity(), grid_connectivity()]) -def test_random_circuits_5q(gates, placer, connectivity): - placer = placer(connectivity=connectivity) - layout_circ = Circuit(5) - initial_layout = placer(layout_circ) - transpiler = ShortestPaths(connectivity=connectivity) - circuit = generate_random_circuit(nqubits=5, ngates=gates) - transpiled_circuit, final_qubit_map = transpiler(circuit, initial_layout) assert transpiler.added_swaps >= 0 assert_connectivity(connectivity, transpiled_circuit) - assert_placement(transpiled_circuit, final_qubit_map) - assert gates + transpiler.added_swaps == transpiled_circuit.ngates - qubit_matcher = Preprocessing(connectivity=connectivity) - new_circuit = qubit_matcher(circuit=circuit) + assert_placement(transpiled_circuit, connectivity) + assert ngates + transpiler.added_swaps == transpiled_circuit.ngates assert_circuit_equivalence( - original_circuit=new_circuit, + original_circuit=original_circuit, transpiled_circuit=transpiled_circuit, final_map=final_qubit_map, - initial_map=initial_layout, ) -def test_random_circuits_15q_50g(): - nqubits, ngates = 15, 50 - connectivity = line_connectivity(nqubits) +@pytest.mark.parametrize("nqubits", [11, 12, 13, 14, 15]) +@pytest.mark.parametrize("ngates", [30, 50]) +def test_random_circuits_15q_50g(nqubits, ngates): + connectivity = line_connectivity(nqubits, None) placer = Random(connectivity=connectivity) - layout_circ = Circuit(nqubits) - initial_layout = placer(layout_circ) transpiler = Sabre(connectivity=connectivity) circuit = generate_random_circuit(nqubits=nqubits, ngates=ngates) - transpiled_circuit, final_qubit_map = transpiler(circuit, initial_layout) + original_circuit = circuit.copy() + + placer(circuit) + transpiled_circuit, final_qubit_map = transpiler(circuit) + assert transpiler.added_swaps >= 0 assert_connectivity(connectivity, transpiled_circuit) - assert_placement(transpiled_circuit, final_qubit_map) + assert_placement(transpiled_circuit, connectivity) assert ngates + transpiler.added_swaps == transpiled_circuit.ngates - qubit_matcher = Preprocessing(connectivity=connectivity) - new_circuit = qubit_matcher(circuit=circuit) assert_circuit_equivalence( - original_circuit=new_circuit, + original_circuit=original_circuit, transpiled_circuit=transpiled_circuit, final_map=final_qubit_map, - initial_map=initial_layout, ) -def test_star_circuit(): - placer = Subgraph(star_connectivity()) - initial_layout = placer(star_circuit()) - transpiler = ShortestPaths(connectivity=star_connectivity()) - transpiled_circuit, final_qubit_map = transpiler(star_circuit(), initial_layout) +def test_star_circuit(star_connectivity): + connectivity = star_connectivity() + circuit = star_circuit() + placer = Subgraph(connectivity=connectivity) + transpiler = ShortestPaths(connectivity=connectivity) + + placer(circuit) + transpiled_circuit, final_qubit_map = transpiler(circuit) + assert transpiler.added_swaps == 0 assert_connectivity(star_connectivity(), transpiled_circuit) - assert_placement(transpiled_circuit, final_qubit_map) + assert_placement(transpiled_circuit, connectivity) assert_circuit_equivalence( original_circuit=star_circuit(), transpiled_circuit=transpiled_circuit, final_map=final_qubit_map, - initial_map=initial_layout, ) -def test_star_circuit_custom_map(): - placer = Custom(initial_map=[1, 0, 2, 3, 4], connectivity=star_connectivity()) - initial_layout = placer() - transpiler = ShortestPaths(connectivity=star_connectivity()) - transpiled_circuit, final_qubit_map = transpiler(star_circuit(), initial_layout) +def test_star_circuit_custom_map(star_connectivity): + connectivity = star_connectivity() + circuit = star_circuit() + circuit.wire_names = [1, 0, 2, 3, 4] + transpiler = ShortestPaths(connectivity=connectivity) + transpiled_circuit, final_qubit_map = transpiler(circuit) + assert transpiler.added_swaps == 1 assert_connectivity(star_connectivity(), transpiled_circuit) - assert_placement(transpiled_circuit, final_qubit_map) + assert_placement(transpiled_circuit, connectivity) assert_circuit_equivalence( original_circuit=star_circuit(), transpiled_circuit=transpiled_circuit, final_map=final_qubit_map, - initial_map=initial_layout, ) -def test_routing_with_measurements(): - placer = Trivial(connectivity=star_connectivity()) +def test_routing_with_measurements(star_connectivity): + connectivity = star_connectivity() circuit = Circuit(5) circuit.add(gates.CNOT(0, 1)) circuit.add(gates.M(0, 2, 3)) - initial_layout = placer(circuit=circuit) - transpiler = ShortestPaths(connectivity=star_connectivity()) - transpiled_circuit, final_qubit_map = transpiler(circuit, initial_layout) + + transpiler = ShortestPaths(connectivity) + transpiled_circuit, final_qubit_map = transpiler(circuit) + assert transpiled_circuit.ngates == 3 measured_qubits = transpiled_circuit.queue[2].qubits assert measured_qubits == (0, 1, 3) @@ -212,37 +226,26 @@ def test_routing_with_measurements(): original_circuit=circuit, transpiled_circuit=transpiled_circuit, final_map=final_qubit_map, - initial_map=initial_layout, ) def test_sabre_looping(): # Setup where the looping occurs - # Line connectivity, gates with gate_array, Trivial placer + # Line connectivity, gates with gate_array + + connectivity = line_connectivity(10, None) gate_array = [(7, 2), (6, 0), (5, 6), (4, 8), (3, 5), (9, 1)] loop_circ = Circuit(10) for qubits in gate_array: loop_circ.add(gates.CZ(*qubits)) - chip = nx.Graph() - chip.add_nodes_from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) - chip.add_edges_from( - [(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 7), (7, 8), (8, 9)] - ) - - placer = Trivial(connectivity=chip) - initial_layout = placer(loop_circ) router_no_threshold = Sabre( - connectivity=chip, swap_threshold=np.inf + connectivity=connectivity, swap_threshold=np.inf ) # Without reset - router_threshold = Sabre(connectivity=chip) # With reset + router_threshold = Sabre(connectivity=connectivity) # With reset - routed_no_threshold, final_mapping_no_threshold = router_no_threshold( - loop_circ, initial_layout=initial_layout - ) - routed_threshold, final_mapping_threshold = router_threshold( - loop_circ, initial_layout=initial_layout - ) + routed_no_threshold, final_mapping_no_threshold = router_no_threshold(loop_circ) + routed_threshold, final_mapping_threshold = router_threshold(loop_circ) count_no_threshold = router_no_threshold.added_swaps count_threshold = router_threshold.added_swaps @@ -252,13 +255,11 @@ def test_sabre_looping(): original_circuit=loop_circ, transpiled_circuit=routed_no_threshold, final_map=final_mapping_no_threshold, - initial_map=initial_layout, ) assert_circuit_equivalence( original_circuit=loop_circ, transpiled_circuit=routed_threshold, final_map=final_mapping_threshold, - initial_map=initial_layout, ) @@ -269,18 +270,11 @@ def test_sabre_shortest_path_routing(): for qubits in gate_array: loop_circ.add(gates.CZ(*qubits)) - # line connectivity - chip = nx.Graph() - chip.add_nodes_from(range(10)) - chip.add_edges_from( - [(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 7), (7, 8), (8, 9)] - ) + connectivity = line_connectivity(10, None) - placer = Trivial(connectivity=chip) - initial_layout = placer(loop_circ) - router = Sabre(connectivity=chip) + router = Sabre(connectivity) - router._preprocessing(circuit=loop_circ, initial_layout=initial_layout) + router._preprocessing(circuit=loop_circ) router._shortest_path_routing() # q2 should be moved adjacent to q8 gate_28 = router.circuit_map.circuit_blocks.block_list[2] @@ -300,14 +294,15 @@ def test_circuit_map(): circ.add(gates.CZ(1, 2)) circ.add(gates.CZ(0, 1)) circ.add(gates.CZ(2, 3)) - initial_layout = {"q0": 2, "q1": 0, "q2": 1, "q3": 3} - circuit_map = CircuitMap(initial_layout=initial_layout, circuit=circ) + circ.wire_names = ["q1", "q2", "q0", "q3"] + + circuit_map = CircuitMap(circuit=circ) block_list = circuit_map.circuit_blocks # test blocks_qubits_pairs assert circuit_map.blocks_logical_qubits_pairs() == [(0, 1), (1, 2), (0, 1), (2, 3)] # test execute_block and routed_circuit circuit_map.execute_block(block_list.search_by_index(0)) - routed_circuit = circuit_map.routed_circuit() + routed_circuit = circuit_map.routed_circuit(circuit_kwargs=circ.init_kwargs) assert isinstance(routed_circuit.queue[0], gates.H) assert len(routed_circuit.queue) == 4 qubits = routed_circuit.queue[2].qubits @@ -318,7 +313,7 @@ def test_circuit_map(): # test update 1 circuit_map.update((0, 2)) - routed_circuit = circuit_map.routed_circuit() + routed_circuit = circuit_map.routed_circuit(circuit_kwargs=circ.init_kwargs) assert isinstance(routed_circuit.queue[4], gates.SWAP) qubits = routed_circuit.queue[4].qubits assert ( @@ -326,12 +321,12 @@ def test_circuit_map(): and routed_circuit.wire_names[qubits[1]] == "q0" ) assert circuit_map._swaps == 1 - assert circuit_map.physical_to_logical == [0, 2, 1, 3] - assert circuit_map.logical_to_physical == [0, 2, 1, 3] + assert circuit_map.physical_to_logical == [2, 1, 0, 3] + assert circuit_map.logical_to_physical == [2, 1, 0, 3] # test update 2 circuit_map.update((1, 2)) - routed_circuit = circuit_map.routed_circuit() + routed_circuit = circuit_map.routed_circuit(circuit_kwargs=circ.init_kwargs) assert isinstance(routed_circuit.queue[5], gates.SWAP) qubits = routed_circuit.queue[5].qubits assert ( @@ -339,14 +334,14 @@ def test_circuit_map(): and routed_circuit.wire_names[qubits[1]] == "q1" ) assert circuit_map._swaps == 2 - assert circuit_map.physical_to_logical == [0, 1, 2, 3] - assert circuit_map.logical_to_physical == [0, 1, 2, 3] + assert circuit_map.physical_to_logical == [1, 2, 0, 3] + assert circuit_map.logical_to_physical == [2, 0, 1, 3] - # # test execute_block after multiple swaps + # test execute_block after multiple swaps circuit_map.execute_block(block_list.search_by_index(1)) circuit_map.execute_block(block_list.search_by_index(2)) circuit_map.execute_block(block_list.search_by_index(3)) - routed_circuit = circuit_map.routed_circuit() + routed_circuit = circuit_map.routed_circuit(circuit_kwargs=circ.init_kwargs) assert isinstance(routed_circuit.queue[6], gates.CZ) qubits = routed_circuit.queue[6].qubits @@ -366,83 +361,112 @@ def test_circuit_map(): ) assert len(circuit_map.circuit_blocks()) == 0 # test final layout - assert circuit_map.final_layout() == {"q0": 0, "q1": 1, "q2": 2, "q3": 3} + assert circuit_map.final_layout() == {"q0": 1, "q1": 2, "q2": 0, "q3": 3} -def test_sabre_matched(): - placer = Trivial() - layout_circ = Circuit(5) - initial_layout = placer(layout_circ) - router = Sabre(connectivity=star_connectivity()) - routed_circuit, final_map = router( - circuit=matched_circuit(), initial_layout=initial_layout - ) +@pytest.mark.parametrize( + "names", + [["q0", "q1", "q2", "q3", "q4"], [0, 1, 2, 3, 4], ["A", "B", "C", "D", "E"]], +) +def test_sabre_matched(names, star_connectivity): + connectivity = star_connectivity(names=names) + circuit = matched_circuit(names) + original_circuit = circuit.copy() + + router = Sabre(connectivity=connectivity) + routed_circuit, final_map = router(circuit) + assert router.added_swaps == 0 - assert final_map == {"q0": 0, "q1": 1, "q2": 2, "q3": 3, "q4": 4} - assert_connectivity(circuit=routed_circuit, connectivity=star_connectivity()) + assert_connectivity(circuit=routed_circuit, connectivity=connectivity) assert_circuit_equivalence( - original_circuit=matched_circuit(), + original_circuit=original_circuit, transpiled_circuit=routed_circuit, final_map=final_map, - initial_map=initial_layout, ) @pytest.mark.parametrize("seed", [42]) -def test_sabre_simple(seed): - placer = Trivial() +def test_sabre_simple(seed, star_connectivity): + connectivity = star_connectivity() circ = Circuit(5) circ.add(gates.CZ(0, 1)) - initial_layout = placer(circ) - router = Sabre(connectivity=star_connectivity(), seed=seed) - routed_circuit, final_map = router(circuit=circ, initial_layout=initial_layout) + original_circuit = circ.copy() + + router = Sabre(connectivity=connectivity, seed=seed) + routed_circuit, final_map = router(circ) + assert router.added_swaps == 1 - assert final_map == {"q0": 2, "q1": 1, "q2": 0, "q3": 3, "q4": 4} + assert final_map == {0: 2, 1: 1, 2: 0, 3: 3, 4: 4} assert routed_circuit.queue[0].qubits == (0, 2) assert isinstance(routed_circuit.queue[0], gates.SWAP) assert isinstance(routed_circuit.queue[1], gates.CZ) - assert_connectivity(circuit=routed_circuit, connectivity=star_connectivity()) + assert_connectivity(circuit=routed_circuit, connectivity=connectivity) assert_circuit_equivalence( - original_circuit=circ, + original_circuit=original_circuit, transpiled_circuit=routed_circuit, final_map=final_map, - initial_map=initial_layout, ) @pytest.mark.parametrize("n_gates", [10, 40]) @pytest.mark.parametrize("look", [0, 5]) @pytest.mark.parametrize("decay", [0.5, 1.0]) -@pytest.mark.parametrize("placer", [Trivial, Random]) -@pytest.mark.parametrize("connectivity", [star_connectivity(), grid_connectivity()]) -def test_sabre_random_circuits(n_gates, look, decay, placer, connectivity): - placer = placer(connectivity=connectivity) - layout_circ = Circuit(5) - initial_layout = placer(layout_circ) - router = Sabre(connectivity=connectivity, lookahead=look, decay_lookahead=decay) +def test_sabre_random_circuits(n_gates, look, decay, star_connectivity): + connectivity = star_connectivity() circuit = generate_random_circuit(nqubits=5, ngates=n_gates) measurement = gates.M(*range(5)) circuit.add(measurement) - transpiled_circuit, final_qubit_map = router(circuit, initial_layout) + original_circuit = circuit.copy() + placer = Random(connectivity) + router = Sabre(connectivity, lookahead=look, decay_lookahead=decay) + + placer(circuit) + transpiled_circuit, final_qubit_map = router(circuit) + assert router.added_swaps >= 0 assert_connectivity(connectivity, transpiled_circuit) - assert_placement(transpiled_circuit, final_qubit_map) + assert_placement(transpiled_circuit, connectivity) assert n_gates + router.added_swaps + 1 == transpiled_circuit.ngates assert_circuit_equivalence( - original_circuit=circuit, + original_circuit=original_circuit, + transpiled_circuit=transpiled_circuit, + final_map=final_qubit_map, + ) + assert transpiled_circuit.queue[-1].register_name == measurement.register_name + + +@pytest.mark.parametrize("n_gates", [10, 40]) +@pytest.mark.parametrize("look", [0, 5]) +@pytest.mark.parametrize("decay", [0.5, 1.0]) +def test_sabre_random_circuits_grid(n_gates, look, decay, grid_connectivity): + connectivity = grid_connectivity() + circuit = generate_random_circuit(nqubits=5, ngates=n_gates) + measurement = gates.M(*range(5)) + circuit.add(measurement) + original_circuit = circuit.copy() + placer = Random(connectivity) + router = Sabre(connectivity, lookahead=look, decay_lookahead=decay) + + placer(circuit) + transpiled_circuit, final_qubit_map = router(circuit) + + assert router.added_swaps >= 0 + assert_connectivity(connectivity, transpiled_circuit) + assert_placement(transpiled_circuit, connectivity) + assert n_gates + router.added_swaps + 1 == transpiled_circuit.ngates + assert_circuit_equivalence( + original_circuit=original_circuit, transpiled_circuit=transpiled_circuit, final_map=final_qubit_map, - initial_map=initial_layout, ) assert transpiled_circuit.queue[-1].register_name == measurement.register_name -def test_sabre_memory_map(): - placer = Trivial() +def test_sabre_memory_map(star_connectivity): + connectivity = star_connectivity() layout_circ = Circuit(5) - initial_layout = placer(layout_circ) - router = Sabre(connectivity=star_connectivity()) - router._preprocessing(circuit=star_circuit(), initial_layout=initial_layout) + router = Sabre(connectivity=connectivity) + router._preprocessing(circuit=star_circuit()) router._memory_map = [[1, 0, 2, 3, 4]] value = router._compute_cost((0, 1)) assert value == float("inf") @@ -458,53 +482,65 @@ def test_sabre_intermediate_measurements(): connectivity.add_nodes_from([0, 1, 2]) connectivity.add_edges_from([(0, 1), (1, 2)]) router = Sabre(connectivity=connectivity) - initial_layout = {"q0": 0, "q1": 1, "q2": 2} - routed_circ, _ = router(circuit=circ, initial_layout=initial_layout) + routed_circ, _ = router(circuit=circ) assert routed_circ.queue[3].register_name == measurement.register_name @pytest.mark.parametrize("router_algorithm", [Sabre, ShortestPaths]) -def test_restrict_qubits(router_algorithm): +def test_restrict_qubits(router_algorithm, star_connectivity): circ = Circuit(3) circ.add(gates.CZ(0, 1)) circ.add(gates.CZ(0, 2)) circ.add(gates.CZ(2, 1)) - initial_layout = {"q0": 0, "q2": 2, "q3": 1} - connectivity = star_connectivity() - restricted_connectivity = restrict_connectivity_qubits(connectivity, [0, 2, 3]) + circ.wire_names = ["q0", "q3", "q2"] + connectivity = star_connectivity(["q0", "q1", "q2", "q3", "q4"]) + restricted_connectivity = restrict_connectivity_qubits( + connectivity, ["q0", "q2", "q3"] + ) router = router_algorithm(connectivity=restricted_connectivity) - routed_circ, final_layout = router(circuit=circ, initial_layout=initial_layout) + routed_circ, final_layout = router(circuit=circ) assert_circuit_equivalence( original_circuit=circ, transpiled_circuit=routed_circ, final_map=final_layout, - initial_map=initial_layout, ) assert_connectivity(restricted_connectivity, routed_circ) - assert_placement(routed_circ, final_layout, connectivity=restricted_connectivity) - assert routed_circ.wire_names == ["q0", "q2", "q3"] + assert_placement(routed_circ, connectivity=restricted_connectivity) + assert routed_circ.wire_names == ["q0", "q3", "q2"] -def test_star_error_multi_qubit(): - circuit = Circuit(3) +def test_star_error_multi_qubit(star_connectivity): + circuit = Circuit(5) circuit.add(gates.TOFFOLI(0, 1, 2)) - transpiler = StarConnectivityRouter(middle_qubit=2) + connectivity = star_connectivity(middle_qubit_idx=2) + transpiler = StarConnectivityRouter(connectivity) with pytest.raises(ConnectivityError): - transpiled, hardware_qubits = transpiler( - initial_layout={"q0": 0, "q1": 1, "q2": 2}, circuit=circuit - ) + transpiled, hardware_qubits = transpiler(circuit=circuit) + + chip = nx.Graph() + chip.add_edges_from([(0, 1), (1, 2), (2, 3), (3, 4)]) + with pytest.raises(ValueError): + router = StarConnectivityRouter(chip) + _, _ = router(circuit=circuit) -@pytest.mark.parametrize("nqubits", [1, 3, 5]) +# @pytest.mark.parametrize("nqubits", [1, 3, 5]) +@pytest.mark.parametrize("nqubits", [5]) @pytest.mark.parametrize("middle_qubit", [0, 2, 4]) @pytest.mark.parametrize("depth", [2, 10]) @pytest.mark.parametrize("measurements", [True, False]) @pytest.mark.parametrize("unitaries", [True, False]) -def test_star_router(nqubits, depth, middle_qubit, measurements, unitaries): +@pytest.mark.parametrize( + "names", + [["q0", "q1", "q2", "q3", "q4"], [0, 1, 2, 3, 4], ["A", "B", "C", "D", "E"]], +) +def test_star_router( + nqubits, depth, middle_qubit, measurements, unitaries, names, star_connectivity +): unitary_dim = min(2, nqubits) - connectivity = star_connectivity(middle_qubit) + connectivity = star_connectivity(names=names, middle_qubit_idx=middle_qubit) if unitaries: - circuit = Circuit(nqubits) + circuit = Circuit(nqubits, wire_names=names) pairs = list(itertools.combinations(range(nqubits), unitary_dim)) for _ in range(depth): qubits = pairs[int(np.random.randint(len(pairs)))] @@ -514,32 +550,28 @@ def test_star_router(nqubits, depth, middle_qubit, measurements, unitaries): ) ) else: - circuit = generate_random_circuit(nqubits, depth) + circuit = generate_random_circuit(nqubits, depth, names) if measurements: circuit.add(gates.M(0)) - transpiler = StarConnectivityRouter(middle_qubit=middle_qubit) - placer = StarConnectivityPlacer(middle_qubit=middle_qubit) - initial_layout = placer(circuit=circuit) - transpiled_circuit, final_qubit_map = transpiler( - circuit=circuit, initial_layout=initial_layout - ) + original_circuit = circuit.copy() + transpiler = StarConnectivityRouter(connectivity) + placer = StarConnectivityPlacer(connectivity) + + placer(circuit) + transpiled_circuit, final_qubit_map = transpiler(circuit) assert_connectivity(connectivity, transpiled_circuit) - assert_placement(transpiled_circuit, final_qubit_map) - matched_original = Circuit(max(circuit.nqubits, middle_qubit + 1)) - for gate in circuit.queue: - matched_original.add(gate) + assert_placement(transpiled_circuit, connectivity) assert_circuit_equivalence( - original_circuit=matched_original, + original_circuit=original_circuit, transpiled_circuit=transpiled_circuit, final_map=final_qubit_map, - initial_map=initial_layout, ) def test_undo(): circ = Circuit(4) - initial_layout = {"q0": 0, "q1": 1, "q2": 2, "q3": 3} - circuit_map = CircuitMap(initial_layout=initial_layout, circuit=circ) + circ.wire_names = ["q0", "q1", "q2", "q3"] + circuit_map = CircuitMap(circuit=circ) # Two SWAP gates are added circuit_map.update((1, 2)) @@ -572,8 +604,8 @@ def test_circuitmap_no_circuit(): def test_logical_to_physical_setter(): circ = Circuit(4) - initial_layout = {"q0": 0, "q1": 3, "q2": 2, "q3": 1} - circuit_map = CircuitMap(initial_layout=initial_layout, circuit=circ) + circ.wire_names = ["q0", "q3", "q2", "q1"] + circuit_map = CircuitMap(circuit=circ) circuit_map.logical_to_physical = [2, 0, 1, 3] assert circuit_map.logical_to_physical == [2, 0, 1, 3] assert circuit_map.physical_to_logical == [1, 2, 0, 3] diff --git a/tests/test_transpiler_unitary_decompositions.py b/tests/test_transpiler_unitary_decompositions.py index 49f8cf2fe9..25a0ec25f3 100644 --- a/tests/test_transpiler_unitary_decompositions.py +++ b/tests/test_transpiler_unitary_decompositions.py @@ -3,7 +3,6 @@ from scipy.linalg import expm from qibo import Circuit, gates, matrices -from qibo.config import PRECISION_TOL from qibo.quantum_info.linalg_operations import partial_trace from qibo.quantum_info.metrics import purity from qibo.quantum_info.random_ensembles import random_unitary diff --git a/tests/test_transpiler_unroller.py b/tests/test_transpiler_unroller.py index e09147b179..181a348e82 100644 --- a/tests/test_transpiler_unroller.py +++ b/tests/test_transpiler_unroller.py @@ -3,12 +3,8 @@ from qibo import gates from qibo.models import Circuit from qibo.transpiler._exceptions import DecompositionError -from qibo.transpiler.unroller import ( - NativeGates, - Unroller, - assert_decomposition, - translate_gate, -) +from qibo.transpiler.asserts import assert_decomposition +from qibo.transpiler.unroller import NativeGates, Unroller, translate_gate def test_native_gates_from_gatelist(): @@ -43,36 +39,6 @@ def test_translate_gate_error_2q(): translate_gate(gates.CZ(0, 1), natives) -def test_assert_decomposition(): - circuit = Circuit(2) - circuit.add(gates.CZ(0, 1)) - circuit.add(gates.Z(0)) - circuit.add(gates.M(1)) - 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, native_gates=NativeGates.default()) - - -@pytest.mark.parametrize("gate", [gates.CNOT(0, 1), gates.iSWAP(0, 1)]) -def test_assert_decomposition_fail_2q(gate): - circuit = Circuit(2) - circuit.add(gate) - with pytest.raises(DecompositionError): - 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, native_gates=NativeGates.default()) - - @pytest.mark.parametrize( "natives_2q", [NativeGates.CZ, NativeGates.iSWAP, NativeGates.CZ | NativeGates.iSWAP], @@ -146,8 +112,8 @@ def test_temp_cnot_decomposition(): glist = [gates.GPI2, gates.RZ, gates.Z, gates.M, gates.CNOT] native_gates = NativeGates(0).from_gatelist(glist) - custom_pipeline = Passes([Unroller(native_gates=native_gates)]) - transpiled_circuit, _ = custom_pipeline(circ) + unroller = Unroller(native_gates=native_gates) + transpiled_circuit = unroller(circ) # H assert transpiled_circuit.queue[0].name == "z"