From 2c7190eb9bf245068b520e1096482bd73c86fc79 Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Thu, 17 Oct 2024 09:43:28 +0400 Subject: [PATCH 01/34] fix: update connectivity in default transpiler --- src/qibo/backends/__init__.py | 8 ++------ src/qibo/models/error_mitigation.py | 5 ++--- tests/test_backends_global.py | 9 +++++++-- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/qibo/backends/__init__.py b/src/qibo/backends/__init__.py index f666bdcd6c..1f898a890e 100644 --- a/src/qibo/backends/__init__.py +++ b/src/qibo/backends/__init__.py @@ -144,12 +144,8 @@ 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(edges) + connectivity = nx.Graph(connectivity_edges) + connectivity.add_nodes_from(qubits) return Passes( connectivity=connectivity, diff --git a/src/qibo/models/error_mitigation.py b/src/qibo/models/error_mitigation.py index 289cf2b7c7..c6d72e2c09 100644 --- a/src/qibo/models/error_mitigation.py +++ b/src/qibo/models/error_mitigation.py @@ -1172,9 +1172,8 @@ def _execute_circuit(circuit, qubit_map, noise_model=None, nshots=10000, 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) + connectivity = nx.Graph(connectivity_edges) + connectivity.add_nodes_from(qubits) transpiler = Passes( connectivity=connectivity, passes=[Custom(initial_map=qubit_map, connectivity=connectivity)], diff --git a/tests/test_backends_global.py b/tests/test_backends_global.py index 45f990eb76..8b73583029 100644 --- a/tests/test_backends_global.py +++ b/tests/test_backends_global.py @@ -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 From e1e769ab03319e8ea1e8afe4c4ec9115af82c8c7 Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Tue, 22 Oct 2024 17:45:49 +0400 Subject: [PATCH 02/34] fix: remove initial_layout & use wire_names & update test files --- src/qibo/models/circuit.py | 31 +- src/qibo/transpiler/abstract.py | 17 +- src/qibo/transpiler/optimizer.py | 7 +- src/qibo/transpiler/pipeline.py | 92 ++--- src/qibo/transpiler/placer.py | 195 ++++------ src/qibo/transpiler/router.py | 176 ++++----- tests/test_models_circuit.py | 6 +- tests/test_tomography_gate_set_tomography.py | 5 +- tests/test_transpiler_optimizer.py | 10 +- tests/test_transpiler_pipeline.py | 157 ++++---- tests/test_transpiler_placer.py | 276 ++++++-------- tests/test_transpiler_router.py | 367 +++++++++++-------- 12 files changed, 598 insertions(+), 741 deletions(-) diff --git a/src/qibo/models/circuit.py b/src/qibo/models/circuit.py index 9b3916b3ae..b3edcb3006 100644 --- a/src/qibo/models/circuit.py +++ b/src/qibo/models/circuit.py @@ -142,9 +142,9 @@ class Circuit: 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 ``None``, defaults to [``q0``, ``q1``... ``q(n-1)``]. If ``list`` is passed, length of ``list`` must match ``nqubits``. - If ``dict`` is passed, the keys should match the default pattern. + If ``dict`` is passed, keys should be wire names and values should be qubit indices. Values should be unique and range from 0 to ``nqubits - 1``. 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``. @@ -171,13 +171,13 @@ def __init__( f"Number of qubits must be positive but is {nqubits}.", ) 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() @@ -300,10 +300,8 @@ def wire_names(self, wire_names: Union[list, dict]): 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 + self._wire_names = wire_names.copy() + # self._wire_names = wire_names elif isinstance(wire_names, dict): if len(wire_names.keys()) > self.nqubits: raise_error( @@ -311,21 +309,25 @@ def wire_names(self, wire_names: Union[list, dict]): "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()] - ): + if not all(isinstance(k, int) for k in wire_names.values()): raise_error( ValueError, - "all keys and values in the ``wire_names`` dictionary must be type ``str``.", + "all values of the ``wire_names`` dictionary must be integers.", + ) + if sorted(wire_names.values()) != list(range(self.nqubits)): + raise_error( + ValueError, + "all values of the ``wire_names`` dictionary must be unique integers from 0 to ``nqubits``.", ) self._wire_names = [ - wire_names.get(f"q{i}", f"q{i}") for i in range(self.nqubits) + k for k, _ in sorted(wire_names.items(), key=lambda x: x[1]) ] else: self._wire_names = [f"q{i}" for i in range(self.nqubits)] + self.init_kwargs["wire_names"] = self._wire_names + @property def repeated_execution(self): return self.has_collapse or ( @@ -406,8 +408,9 @@ 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) + new_wire_names = [self.wire_names[q] for q in list(sorted(qubits))] + kwargs["wire_names"] = new_wire_names 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 diff --git a/src/qibo/transpiler/abstract.py b/src/qibo/transpiler/abstract.py index 439045e72a..e24f7acead 100644 --- a/src/qibo/transpiler/abstract.py +++ b/src/qibo/transpiler/abstract.py @@ -1,5 +1,4 @@ from abc import ABC, abstractmethod -from typing import Tuple import networkx as nx @@ -12,14 +11,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 +25,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) -> Circuit: """Match circuit to hardware connectivity. Args: - circuit (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/optimizer.py b/src/qibo/transpiler/optimizer.py index 7008957627..2ba05895e3 100644 --- a/src/qibo/transpiler/optimizer.py +++ b/src/qibo/transpiler/optimizer.py @@ -27,7 +27,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.copy() + [ + name for name in self.connectivity.nodes if name not in 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 +51,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 a6bf3a7b9d..cdbe2a8567 100644 --- a/src/qibo/transpiler/pipeline.py +++ b/src/qibo/transpiler/pipeline.py @@ -10,7 +10,7 @@ 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.placer import StarConnectivityPlacer, assert_placement from qibo.transpiler.router import ( ConnectivityError, StarConnectivityRouter, @@ -28,7 +28,6 @@ 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, ): @@ -38,14 +37,11 @@ def assert_circuit_equivalence( 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, @@ -57,22 +53,26 @@ def assert_circuit_equivalence( 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)): + # original: list = original_circuit.wire_names + # transpiled: list = transpiled_circuit.wire_names + # initial_map = [0, 1, 2, 3, 4] + # initial_map = [original.index(qubit) for qubit in transpiled] + # reordered_test_states = [] + # reordered_test_states = [ + # _transpose_qubits(initial_state, initial_map) for initial_state in test_states + # ] + + ordering = list(final_map.values()) + + for i, state in enumerate(test_states): target_state = backend.execute_circuit( - original_circuit, initial_state=test_states[i] + original_circuit, initial_state=state ).state() final_state = backend.execute_circuit( - transpiled_circuit, initial_state=reordered_test_states[i] + # transpiled_circuit, initial_state=reordered_test_states[i] + transpiled_circuit, + initial_state=state, ).state() final_state = _transpose_qubits(final_state, ordering) fidelity = np.abs(np.dot(np.conj(target_state), final_state)) @@ -99,7 +99,6 @@ 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, @@ -110,7 +109,6 @@ def assert_transpiling( original_circuit (qibo.models.Circuit): circuit before transpiling. transpiled_circuit (qibo.models.Circuit): circuit after transpiling. connectivity (networkx.Graph): chip qubits connectivity. - initial_layout (dict): initial physical-logical qubit mapping. final_layout (dict): final physical-logical qubit mapping. native_gates (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. @@ -123,22 +121,17 @@ def assert_transpiling( 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 - ) + 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, - 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: @@ -174,14 +167,12 @@ 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. + If ``None``, full connectivity is assumed. 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__( @@ -190,18 +181,15 @@ 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.""" + """Return the default star connectivity transpiler pipeline.""" if not isinstance(self.connectivity, nx.Graph): raise_error( TranspilerPipelineError, @@ -211,9 +199,9 @@ def default(self): # preprocessing default_passes.append(Preprocessing(connectivity=self.connectivity)) # default placer pass - default_passes.append(StarConnectivityPlacer()) + default_passes.append(StarConnectivityPlacer(connectivity=self.connectivity)) # default router pass - default_passes.append(StarConnectivityRouter()) + default_passes.append(StarConnectivityRouter(connectivity=self.connectivity)) # default unroller pass default_passes.append(Unroller(native_gates=self.native_gates)) @@ -222,36 +210,20 @@ def default(self): 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: @@ -259,8 +231,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): @@ -280,7 +250,3 @@ def is_satisfied(self, circuit: Circuit): 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..405a21dadf 100644 --- a/src/qibo/transpiler/placer.py +++ b/src/qibo/transpiler/placer.py @@ -4,25 +4,24 @@ 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.router import _find_connected_qubit -def assert_placement( - circuit: Circuit, layout: dict, connectivity: nx.Graph = None -) -> bool: +def assert_placement(circuit: Circuit, connectivity: nx.Graph): """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. + layout (dict): qubit names. 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``. """ + layout = circuit.wire_names assert_mapping_consistency(layout=layout, connectivity=connectivity) if circuit.nqubits > len(layout): raise_error( @@ -37,33 +36,21 @@ def assert_placement( ) -def assert_mapping_consistency(layout: dict, connectivity: nx.Graph = None): +def assert_mapping_consistency(layout: list, connectivity: nx.Graph): """Check if layout is in the correct form. Args: - layout (dict): physical to logical qubit mapping. + layout (dict): qubit names. 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``. + qubits of the original connectivity graph. """ - 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): + nodes = list(connectivity.nodes) + if sorted(nodes) != sorted(layout): 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): @@ -99,17 +86,19 @@ 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: nx.Graph): + self.connectivity = connectivity + 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 __call__(self, circuit: Circuit): """Apply the transpiler transformation on a given circuit. @@ -117,14 +106,9 @@ 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. """ - # 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) for i, gate in enumerate(circuit.queue): if len(gate.qubits) > 2: @@ -133,118 +117,72 @@ 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, + + ( + circuit.wire_names[middle_qubit_idx], + circuit.wire_names[new_middle], + ) = ( + circuit.wire_names[new_middle], + circuit.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}. - - Args: - connectivity (networkx.Graph, optional): chip connectivity. - """ + """Place qubits according to the order of the qubit names that the user provides.""" def __init__(self, connectivity: nx.Graph = None): self.connectivity = connectivity - def __call__(self, circuit: Circuit): + def __call__(self, circuit: Circuit = None): """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: - 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 + if circuit is None: + raise_error(ValueError, "Circuit must be provided.") + return 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``. + map (list or dict): A mapping between physical and logical qubits. + - If **dict**, the keys should be physical qubit names, and the values should be the corresponding logical qubit numbers. + - If **list**, it should contain logical qubit numbers, arranged in the order of the physical qubits. """ - def __init__(self, initial_map: Union[list, dict], connectivity: nx.Graph = None): - self.connectivity = connectivity + def __init__(self, initial_map: Union[list, dict], connectivity: nx.Graph): self.initial_map = initial_map + self.connectivity = connectivity + if self.initial_map is None: + raise_error(ValueError, "Initial mapping must be provided.") + if self.connectivity is None: + raise_error(ValueError, "Connectivity graph must be provided.") - def __call__(self, circuit=None): - """Return the custom placement if it can be applied to the given circuit (if given). + def __call__(self, circuit: Circuit): + """Apply the custom placement to the given circuit. 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 + circuit.wire_names = sorted(self.initial_map, key=self.initial_map.get) 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, - ) - ) - else: - self.initial_map = dict( - zip( - ["q" + str(i) for i in range(len(self.initial_map))], - self.initial_map, - ) - ) + circuit.wire_names = 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 + + assert_placement(circuit, connectivity=self.connectivity) class Subgraph(Placer): @@ -260,6 +198,8 @@ class Subgraph(Placer): def __init__(self, connectivity: nx.Graph): self.connectivity = connectivity + if self.connectivity is None: + raise_error(ValueError, "Connectivity graph must be provided.") def __call__(self, circuit: Circuit): """Find the initial layout of the given circuit using subgraph isomorphism. @@ -301,9 +241,9 @@ 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]) + # sorted_result = dict(sorted(result.mapping.items())) + # circuit.wire_names = sorted(sorted_result, key=sorted_result.get) class Random(Placer): @@ -320,7 +260,7 @@ 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: nx.Graph, samples: int = 100, seed=None): self.connectivity = connectivity self.samples = samples self.seed = seed @@ -338,7 +278,6 @@ def __call__(self, circuit): 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 +290,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): """ @@ -422,13 +362,11 @@ def __call__(self, circuit: Circuit): Returns: (dict): physical to logical qubit mapping. """ - initial_placer = Trivial(self.connectivity) - initial_placement = initial_placer(circuit=circuit) self.routing_algorithm.connectivity = self.connectivity new_circuit = self._assemble_circuit(circuit) - final_placement = self._routing_step(initial_placement, new_circuit) + final_placement = self._routing_step(new_circuit) - return final_placement + # return final_placement def _assemble_circuit(self, circuit: Circuit): """Assemble a single circuit to apply Reverse Traversal placement based on depth. @@ -461,20 +399,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..4b8276522d 100644 --- a/src/qibo/transpiler/router.py +++ b/src/qibo/transpiler/router.py @@ -6,7 +6,7 @@ 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 @@ -23,19 +23,12 @@ def assert_connectivity(connectivity: nx.Graph, circuit: Circuit): 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 + 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 = tuple(sorted((circuit.wire_names[gate.qubits[0]], circuit.wire_names[gate.qubits[1]]))) - physical_qubits = tuple(sorted(gate.qubits)) # for q_i naming + physical_qubits = (layout[gate.qubits[0]], layout[gate.qubits[1]]) if physical_qubits not in connectivity.edges: raise_error( ConnectivityError, @@ -43,25 +36,6 @@ def assert_connectivity(connectivity: nx.Graph, circuit: Circuit): ) -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 +50,81 @@ 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: nx.Graph): + self.connectivity = connectivity + 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 __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. """ - 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 _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 +132,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 +151,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 +159,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 +168,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 +264,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`` @@ -384,19 +346,18 @@ 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) + 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 +366,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 +450,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 +533,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 +541,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() @@ -679,17 +638,16 @@ 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) + self._preprocessing(circuit=circuit) longest_path = np.max(self._dist_matrix) while self._dag.number_of_nodes() != 0: @@ -711,7 +669,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 +682,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 +695,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 +935,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 +944,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/tests/test_models_circuit.py b/tests/test_models_circuit.py index c6a72a39ff..2485be51a3 100644 --- a/tests/test_models_circuit.py +++ b/tests/test_models_circuit.py @@ -639,14 +639,12 @@ def test_circuit_draw(): def test_circuit_wire_names_errors(): with pytest.raises(TypeError): circuit = Circuit(5, wire_names=1) - with pytest.raises(ValueError): - circuit = Circuit(5, wire_names=["a", "b", "c"]) 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 = Circuit(2, wire_names={"q0": 4, "q1": 5, "q2": 6}) def test_circuit_draw_wire_names(): @@ -766,7 +764,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): diff --git a/tests/test_tomography_gate_set_tomography.py b/tests/test_tomography_gate_set_tomography.py index d6d2ffd2df..de79d67922 100644 --- a/tests/test_tomography_gate_set_tomography.py +++ b/tests/test_tomography_gate_set_tomography.py @@ -278,7 +278,9 @@ def test_GST_with_transpiler(backend): # define transpiler connectivity = nx.Graph() # star connectivity - connectivity.add_edges_from([(0, 2), (1, 2), (2, 3), (2, 4)]) + connectivity.add_edges_from( + [("q0", "q2"), ("q1", "q2"), ("q2", "q3"), ("q2", "q4")] + ) transpiler = Passes( connectivity=connectivity, passes=[ @@ -287,7 +289,6 @@ def test_GST_with_transpiler(backend): Sabre(connectivity), 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_optimizer.py b/tests/test_transpiler_optimizer.py index 171c7516d4..457e25ecf7 100644 --- a/tests/test_transpiler_optimizer.py +++ b/tests/test_transpiler_optimizer.py @@ -6,10 +6,14 @@ from qibo.transpiler.optimizer import Preprocessing, Rearrange -def star_connectivity(): +def star_connectivity(names=["q0", "q1", "q2", "q3", "q4"], middle_qubit_idx=2): chip = nx.Graph() - chip.add_nodes_from(list(range(5))) - graph_list = [(i, 2) for i in range(5) if i != 2] + 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 diff --git a/tests/test_transpiler_pipeline.py b/tests/test_transpiler_pipeline.py index bfa13f9e4d..82fe4c9419 100644 --- a/tests/test_transpiler_pipeline.py +++ b/tests/test_transpiler_pipeline.py @@ -17,11 +17,9 @@ 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 +32,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,49 +45,56 @@ 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(): +def star_connectivity(names=["q0", "q1", "q2", "q3", "q4"], middle_qubit_idx=2): chip = nx.Graph() - chip.add_nodes_from(list(range(5))) - graph_list = [(i, 2) for i in range(5) if i != 2] + 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 def test_restrict_qubits_error_no_subset(): with pytest.raises(ConnectivityError) as excinfo: - restrict_connectivity_qubits(star_connectivity(), [1, 2, 6]) + restrict_connectivity_qubits(star_connectivity(), ["q0", "q1", "q5"]) assert "Some qubits are not in the original connectivity." in str(excinfo.value) def test_restrict_qubits_error_not_connected(): with pytest.raises(ConnectivityError) as excinfo: - restrict_connectivity_qubits(star_connectivity(), [1, 3]) + restrict_connectivity_qubits(star_connectivity(), ["q0", "q1"]) 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)] + new_connectivity = restrict_connectivity_qubits( + star_connectivity(["A", "B", "C", "D", "E"]), ["A", "B", "C"] + ) + assert list(new_connectivity.nodes) == ["A", "B", "C"] + assert list(new_connectivity.edges) == [("A", "C"), ("B", "C")] @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()) +@pytest.mark.parametrize("names", [["A", "B", "C", "D", "E"], [0, 1, 2, 3, 4]]) +def test_pipeline_default(ngates, names): + circ = generate_random_circuit(nqubits=5, ngates=ngates, names=names) + connectivity = star_connectivity(names) + + default_transpiler = Passes(passes=None, connectivity=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, + connectivity=connectivity, final_layout=final_layout, native_gates=NativeGates.default(), check_circuit_equivalence=False, @@ -128,14 +133,13 @@ def test_assert_circuit_equivalence_false(): 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 - ) +def test_int_qubit_names_default(): + names = [1244, 1532, 2315, 6563, 8901] + circ = Circuit(5, wire_names=names) + connectivity = star_connectivity(names) + default_transpiler = Passes(passes=None, connectivity=connectivity) _, final_layout = default_transpiler(circ) - assert final_map == final_layout + assert final_layout == {names[i]: i for i in range(5)} def test_assert_circuit_equivalence_wrong_nqubits(): @@ -148,13 +152,14 @@ def test_assert_circuit_equivalence_wrong_nqubits(): def test_error_connectivity(): with pytest.raises(TranspilerPipelineError): - default_transpiler = Passes(passes=None, connectivity=None) + 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) + circuit.wire_names = ["q0", "q1", "q2", "q3", "q4"][:qubits] circuit.add(gates.CZ(0, 2)) circuit.add(gates.Z(0)) assert default_transpiler.is_satisfied(circuit) @@ -176,104 +181,78 @@ 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("nqubits", [2, 3, 5]) +@pytest.mark.parametrize("ngates", [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("router", [ShortestPaths, Sabre]) +def test_custom_passes(placer, router, ngates, nqubits): + connectivity = star_connectivity() + circ = generate_random_circuit(nqubits=nqubits, ngates=ngates) custom_passes = [] - custom_passes.append(Preprocessing(connectivity=star_connectivity())) + custom_passes.append(Preprocessing(connectivity=connectivity)) if placer == ReverseTraversal: custom_passes.append( placer( - connectivity=star_connectivity(), - routing_algorithm=routing(connectivity=star_connectivity()), + connectivity=connectivity, + routing_algorithm=router(connectivity=connectivity), ) ) else: - custom_passes.append(placer(connectivity=star_connectivity())) - custom_passes.append(routing(connectivity=star_connectivity())) + custom_passes.append(placer(connectivity=connectivity)) + custom_passes.append(router(connectivity=connectivity)) custom_passes.append(Unroller(native_gates=NativeGates.default())) custom_pipeline = Passes( custom_passes, - connectivity=star_connectivity(), + 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("ngates", [5, 20]) @pytest.mark.parametrize("placer", [Random, Trivial, 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", [["q1", "q2", "q3"], ["q0", "q2", "q4"], ["q4", "q2", "q3"]] +) +def test_custom_passes_restrict(ngates, placer, routing, restrict_names): + 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(connectivity=connectivity)) if placer == ReverseTraversal: custom_passes.append( placer( - connectivity=star_connectivity(), - routing_algorithm=routing(connectivity=star_connectivity()), + connectivity=connectivity, + routing_algorithm=routing(connectivity=connectivity), ) ) else: - custom_passes.append(placer(connectivity=star_connectivity())) - custom_passes.append(routing(connectivity=star_connectivity())) + custom_passes.append(placer(connectivity=connectivity)) + custom_passes.append(routing(connectivity=connectivity)) custom_passes.append(Unroller(native_gates=NativeGates.default())) custom_pipeline = Passes( custom_passes, - connectivity=star_connectivity(), + 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,11 +260,12 @@ 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() + names = [980, 123, 45, 9, 210464] + connectivity = star_connectivity(names) transpiler = Passes( connectivity=connectivity, passes=[ @@ -294,19 +274,16 @@ def test_int_qubit_names(): Sabre(connectivity), 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..ac153b8b03 100644 --- a/tests/test_transpiler_placer.py +++ b/tests/test_transpiler_placer.py @@ -19,80 +19,65 @@ from qibo.transpiler.router import ShortestPaths -def star_connectivity(): +def star_connectivity(names=["q0", "q1", "q2", "q3", "q4"], middle_qubit_idx=2): chip = nx.Graph() - chip.add_nodes_from(list(range(5))) - graph_list = [(i, 2) for i in range(5) if i != 2] + 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 -def star_circuit(): - circuit = Circuit(5) +def star_circuit(names=["q0", "q1", "q2", "q3", "q4"]): + 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): +def test_assert_placement_true(): circuit = Circuit(5) - assert_placement(circuit, layout, connectivity=connectivity) + assert_placement(circuit, connectivity=star_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}] + "qubits, names", [(5, ["A", "B", "C", "D", "F"]), (3, ["A", "B", "C"])] ) -def test_assert_placement_false(qubits, layout): - circuit = Circuit(qubits) +def test_assert_placement_false(qubits, names): + connectivity = star_connectivity() + circuit = Circuit(qubits, wire_names=names) with pytest.raises(PlacementError): - assert_placement(circuit, layout) + assert_placement(circuit, connectivity) -@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("names", [["A", "B", "C", "D", "E"], [0, 1, 2, 3, 4]]) +def test_mapping_consistency(names): + assert_mapping_consistency(names, star_connectivity(names)) -@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): +def test_mapping_consistency_error(): with pytest.raises(PlacementError): - assert_mapping_consistency(layout) + assert_mapping_consistency(["A", "B", "C", "D", "F"], star_connectivity()) -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("names", [["A", "B", "C", "D", "E"], [0, 1, 2, 3, 4]]) +def test_mapping_consistency_restricted(names): + connectivity = star_connectivity(names) + on_qubit = [names[0], names[2]] + restricted_connectivity = restrict_connectivity_qubits(connectivity, on_qubit) + assert_mapping_consistency(on_qubit, 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]) +@pytest.mark.parametrize("names", [["A", "B", "C", "D", "E"], [0, 1, 2, 3, 4]]) +def test_mapping_consistency_restricted_error(names): + connectivity = star_connectivity(names) + on_qubit = [names[0], names[2]] + restricted_connectivity = restrict_connectivity_qubits(connectivity, on_qubit) with pytest.raises(PlacementError): - assert_mapping_consistency(layout, restricted_connectivity) + assert_mapping_consistency([names[3], names[4]], restricted_connectivity) def test_gates_qubits_pairs(): @@ -112,98 +97,85 @@ def test_gates_qubits_pairs_error(): def test_trivial(): - circuit = Circuit(5) - connectivity = star_connectivity() + names = ["q4", "q3", "q2", "q1", "q0"] + circuit = Circuit(5, wire_names=names) + connectivity = star_connectivity(names) placer = Trivial(connectivity=connectivity) - layout = placer(circuit) - assert layout == {"q0": 0, "q1": 1, "q2": 2, "q3": 3, "q4": 4} - assert_placement(circuit, layout) + placer(circuit) + assert circuit.wire_names == names + assert_placement(circuit, connectivity) def test_trivial_restricted(): - circuit = Circuit(2) + names = ["q0", "q2"] + circuit = Circuit(2, wire_names=names) connectivity = star_connectivity() - restricted_connectivity = restrict_connectivity_qubits(connectivity, [0, 2]) + restricted_connectivity = restrict_connectivity_qubits(connectivity, names) placer = Trivial(connectivity=restricted_connectivity) - layout = placer(circuit) - assert layout == {"q0": 0, "q2": 1} - assert_placement( - circuit=circuit, layout=layout, connectivity=restricted_connectivity - ) + placer(circuit) + assert circuit.wire_names == names + assert_placement(circuit, 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", + [["E", "D", "C", "B", "A"], {"E": 0, "D": 1, "C": 2, "B": 3, "A": 4}], +) +def test_custom(custom_layout): + circuit = Circuit(5) + connectivity = star_connectivity(["A", "B", "C", "D", "E"]) + placer = Custom(connectivity=connectivity, initial_map=custom_layout) + placer(circuit) + assert circuit.wire_names == ["E", "D", "C", "B", "A"] @pytest.mark.parametrize( - "custom_layout", [[4, 3, 2, 1, 0], {"q0": 4, "q1": 3, "q2": 2, "q3": 1, "q4": 0}] + "custom_layout", [[4, 3, 2, 1, 0], {4: 0, 3: 1, 2: 2, 1: 3, 0: 4}] ) -@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 +def test_custom_int(custom_layout): + names = [0, 1, 2, 3, 4] + circuit = Circuit(5, wire_names=names) + connectivity = star_connectivity(names) placer = Custom(connectivity=connectivity, initial_map=custom_layout) - layout = placer(circuit) - assert layout == {"q0": 4, "q1": 3, "q2": 2, "q3": 1, "q4": 0} + placer(circuit) + assert circuit.wire_names == [4, 3, 2, 1, 0] -@pytest.mark.parametrize("custom_layout", [[1, 0], {"q0": 1, "q2": 0}]) +@pytest.mark.parametrize("custom_layout", [["D", "C"], {"C": 1, "D": 0}]) def test_custom_restricted(custom_layout): - circuit = Circuit(2) - connectivity = star_connectivity() - restricted_connectivity = restrict_connectivity_qubits(connectivity, [0, 2]) + circuit = Circuit(2, wire_names=["C", "D"]) + connectivity = star_connectivity(["A", "B", "C", "D", "E"]) + restricted_connectivity = restrict_connectivity_qubits(connectivity, ["C", "D"]) 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 - ) + placer(circuit) + assert circuit.wire_names == ["D", "C"] + assert_placement(circuit, 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} + connectivity = star_connectivity(names=custom_layout) placer = Custom(connectivity=connectivity, initial_map=custom_layout) - with pytest.raises(PlacementError): - layout = placer() + with pytest.raises(ValueError): + placer(circuit) def test_custom_error_type(): circuit = Circuit(5) connectivity = star_connectivity() - layout = 1 - placer = Custom(connectivity=connectivity, initial_map=layout) + placer = Custom(connectivity=connectivity, initial_map=1) with pytest.raises(TypeError): - layout = placer(circuit) + placer(circuit) def test_subgraph_perfect(): 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] == "q2" + assert_placement(circuit, connectivity) def imperfect_circuit(): @@ -223,8 +195,9 @@ def imperfect_circuit(): def test_subgraph_non_perfect(): 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(): @@ -232,7 +205,7 @@ def test_subgraph_error(): placer = Subgraph(connectivity=connectivity) circuit = Circuit(5) with pytest.raises(ValueError): - layout = placer(circuit) + placer(circuit) def test_subgraph_restricted(): @@ -244,56 +217,49 @@ def test_subgraph_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]) - placer = Subgraph(connectivity=restricted_connectivity) - layout = placer(circuit) - assert_placement( - circuit=circuit, layout=layout, connectivity=restricted_connectivity + restricted_connectivity = restrict_connectivity_qubits( + connectivity, ["q0", "q2", "q3", "q4"] ) + placer = Subgraph(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): + 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) + names = [0, 1, 2, 3, 4] + circuit = Circuit(4, wire_names=names[: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): + 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(connectivity, routing, depth=ngates) + placer(circuit) + assert_placement(circuit, connectivity) def test_reverse_traversal_no_gates(): @@ -302,7 +268,7 @@ def test_reverse_traversal_no_gates(): placer = ReverseTraversal(connectivity, routing, depth=10) circuit = Circuit(5) with pytest.raises(ValueError): - layout = placer(circuit) + placer(circuit) def test_reverse_traversal_restricted(): @@ -314,34 +280,36 @@ 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 = ["q0", "q2", "q3", "q4"] + 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) + 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 == ["q0", "q2", "q1", "q3", "q4"] @pytest.mark.parametrize("first", [True, False]) def test_star_connectivity_placer_error(first): - circ = Circuit(3) + 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) diff --git a/tests/test_transpiler_router.py b/tests/test_transpiler_router.py index 80b0e72aa3..4bcfe949af 100644 --- a/tests/test_transpiler_router.py +++ b/tests/test_transpiler_router.py @@ -32,38 +32,58 @@ ) -def star_connectivity(middle_qubit=2): +def star_connectivity(names=["q0", "q1", "q2", "q3", "q4"], middle_qubit_idx=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_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 -def grid_connectivity(): +def grid_connectivity(names=["q0", "q1", "q2", "q3", "q4"]): 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[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 -def line_connectivity(n): +def line_connectivity(n, names): + if names is None: + names = [f"q{i}" for i in range(n)] chip = nx.Graph() - chip.add_nodes_from(list(range(n))) - graph_list = [(i, i + 1) for i in range(n - 1)] + 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 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 +109,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)) @@ -101,7 +121,8 @@ def matched_circuit(): def test_assert_connectivity(): - assert_connectivity(star_connectivity(), matched_circuit()) + names = ["A", "B", "C", "D", "E"] + assert_connectivity(star_connectivity(names), matched_circuit(names)) def test_assert_connectivity_false(): @@ -118,93 +139,128 @@ def test_assert_connectivity_3q(): assert_connectivity(star_connectivity(), circuit) -@pytest.mark.parametrize("gates", [5, 25]) +def test_bell_state_3q(): + from qibo.transpiler.pipeline import _transpose_qubits + + 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) + + backend = NumpyBackend() + state = np.array([1, 0, 0, 0, 0, 0, 0, 0]) + 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(target_state).round(1), np.array([0, 0.7, 0, 0, 0.7, 0, 0, 0]) + ) + ) + + +@pytest.mark.parametrize("ngates", [5, 25]) @pytest.mark.parametrize("placer", [Trivial, Random]) @pytest.mark.parametrize("connectivity", [star_connectivity(), grid_connectivity()]) -def test_random_circuits_5q(gates, placer, connectivity): +def test_random_circuits_5q(ngates, 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) + + 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, 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) + 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) + connectivity = star_connectivity() + circuit = star_circuit() + placer = Custom( + initial_map=["q1", "q0", "q2", "q3", "q4"], connectivity=connectivity + ) + transpiler = ShortestPaths(connectivity=connectivity) + + placer(circuit) + 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()) + 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) + placer = Trivial(connectivity) + transpiler = ShortestPaths(connectivity) + + placer(circuit=circuit) + 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 +268,28 @@ 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 + + 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) + placer = Trivial(connectivity) 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 - ) + placer(loop_circ) + 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 +299,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 +314,13 @@ 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) + placer = Trivial(connectivity) + router = Sabre(connectivity) - router._preprocessing(circuit=loop_circ, initial_layout=initial_layout) + placer(loop_circ) + 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 +340,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 +359,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 +367,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 +380,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,73 +407,81 @@ 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(): +@pytest.mark.parametrize( + "names", + [["q0", "q1", "q2", "q3", "q4"], [0, 1, 2, 3, 4], ["A", "B", "C", "D", "E"]], +) +def test_sabre_matched(names): + connectivity = star_connectivity(names=names) + circuit = matched_circuit(names) + original_circuit = circuit.copy() 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 - ) + router = Sabre(connectivity=connectivity) + + placer(circuit) + 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() + 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() + placer = Trivial() + router = Sabre(connectivity=connectivity, seed=seed) + + placer(circ) + 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 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("placer_param", [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, placer_param, 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 = placer_param(connectivity=connectivity) + router = Sabre(connectivity=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, - initial_map=initial_layout, ) assert transpiled_circuit.queue[-1].register_name == measurement.register_name @@ -440,9 +489,9 @@ def test_sabre_random_circuits(n_gates, look, decay, placer, connectivity): def test_sabre_memory_map(): placer = Trivial() layout_circ = Circuit(5) - initial_layout = placer(layout_circ) + placer(layout_circ) router = Sabre(connectivity=star_connectivity()) - router._preprocessing(circuit=star_circuit(), initial_layout=initial_layout) + router._preprocessing(circuit=star_circuit()) router._memory_map = [[1, 0, 2, 3, 4]] value = router._compute_cost((0, 1)) assert value == float("inf") @@ -458,8 +507,7 @@ 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 @@ -469,42 +517,47 @@ def test_restrict_qubits(router_algorithm): 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} + circ.wire_names = ["q0", "q3", "q2"] connectivity = star_connectivity() - restricted_connectivity = restrict_connectivity_qubits(connectivity, [0, 2, 3]) + 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) 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) -@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): 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 +567,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 +621,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] From 2a888adad71f62970bbe3393c250332e730f8db6 Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Wed, 23 Oct 2024 09:16:34 +0400 Subject: [PATCH 03/34] fix: update code-examples --- doc/source/code-examples/advancedexamples.rst | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/doc/source/code-examples/advancedexamples.rst b/doc/source/code-examples/advancedexamples.rst index 8a3736b99c..e506bca6c3 100644 --- a/doc/source/code-examples/advancedexamples.rst +++ b/doc/source/code-examples/advancedexamples.rst @@ -2103,13 +2103,14 @@ Multiple transpilation steps can be implemented using the :class:`qibo.transpile # 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.add_nodes_from(["q0", "q1", "q2", "q3", "q4"]) + chip.add_edges_from([("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)) @@ -2131,13 +2132,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() ) From 05b6f68bc684584607e6c21fdeb9f7adca800e4b Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Wed, 23 Oct 2024 09:26:12 +0400 Subject: [PATCH 04/34] fix: update comparison in test_bell_state_3q --- tests/test_transpiler_router.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/tests/test_transpiler_router.py b/tests/test_transpiler_router.py index 4bcfe949af..c598f1344c 100644 --- a/tests/test_transpiler_router.py +++ b/tests/test_transpiler_router.py @@ -155,13 +155,10 @@ def test_bell_state_3q(): 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(target_state).round(1), np.array([0, 0.7, 0, 0, 0.7, 0, 0, 0]) - ) - ) + assert np.all(np.isclose(np.real(original_state), np.real(target_state))) @pytest.mark.parametrize("ngates", [5, 25]) From 876b79c2789a7c66c722442829fd8048ed7d2551 Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Wed, 23 Oct 2024 12:00:57 +0400 Subject: [PATCH 05/34] feat: circuit draw with int qubit names --- src/qibo/models/circuit.py | 7 ++++--- tests/test_models_circuit.py | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/qibo/models/circuit.py b/src/qibo/models/circuit.py index b3edcb3006..e4ea88ee9c 100644 --- a/src/qibo/models/circuit.py +++ b/src/qibo/models/circuit.py @@ -1280,6 +1280,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: @@ -1301,12 +1302,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" diff --git a/tests/test_models_circuit.py b/tests/test_models_circuit.py index 2485be51a3..c423af8428 100644 --- a/tests/test_models_circuit.py +++ b/tests/test_models_circuit.py @@ -667,6 +667,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 = ( From 02e99457a90a37cbb23f391be981f42812159ae0 Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Wed, 23 Oct 2024 12:32:18 +0400 Subject: [PATCH 06/34] fix: placer.py coverage --- src/qibo/transpiler/placer.py | 6 ------ tests/test_transpiler_placer.py | 20 ++++++++++++++++++++ 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/qibo/transpiler/placer.py b/src/qibo/transpiler/placer.py index 405a21dadf..963d47749d 100644 --- a/src/qibo/transpiler/placer.py +++ b/src/qibo/transpiler/placer.py @@ -164,10 +164,6 @@ class Custom(Placer): def __init__(self, initial_map: Union[list, dict], connectivity: nx.Graph): self.initial_map = initial_map self.connectivity = connectivity - if self.initial_map is None: - raise_error(ValueError, "Initial mapping must be provided.") - if self.connectivity is None: - raise_error(ValueError, "Connectivity graph must be provided.") def __call__(self, circuit: Circuit): """Apply the custom placement to the given circuit. @@ -198,8 +194,6 @@ class Subgraph(Placer): def __init__(self, connectivity: nx.Graph): self.connectivity = connectivity - if self.connectivity is None: - raise_error(ValueError, "Connectivity graph must be provided.") def __call__(self, circuit: Circuit): """Find the initial layout of the given circuit using subgraph isomorphism. diff --git a/tests/test_transpiler_placer.py b/tests/test_transpiler_placer.py index ac153b8b03..8a2532583c 100644 --- a/tests/test_transpiler_placer.py +++ b/tests/test_transpiler_placer.py @@ -53,6 +53,14 @@ def test_assert_placement_false(qubits, names): assert_placement(circuit, connectivity) +@pytest.mark.parametrize("qubits", [10, 1]) +def test_assert_placement_error(qubits): + 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_mapping_consistency(names): assert_mapping_consistency(names, star_connectivity(names)) @@ -106,6 +114,13 @@ def test_trivial(): assert_placement(circuit, connectivity) +def test_trivial_error(): + connectivity = star_connectivity() + placer = Trivial(connectivity=connectivity) + with pytest.raises(ValueError): + placer() + + def test_trivial_restricted(): names = ["q0", "q2"] circuit = Circuit(2, wire_names=names) @@ -313,3 +328,8 @@ def test_star_connectivity_placer_error(first): placer = StarConnectivityPlacer(connectivity) with pytest.raises(PlacementError): placer(circ) + + chip = nx.Graph() + chip.add_edges_from([(0, 1), (1, 2), (2, 3), (3, 4)]) + with pytest.raises(ValueError): + StarConnectivityPlacer(chip) From 05f36e70a322af283c38cb034f44c5f19d33f549 Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Wed, 23 Oct 2024 12:41:32 +0400 Subject: [PATCH 07/34] fix: circuit.py coverage --- tests/test_models_circuit.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/test_models_circuit.py b/tests/test_models_circuit.py index c423af8428..6c5864981a 100644 --- a/tests/test_models_circuit.py +++ b/tests/test_models_circuit.py @@ -636,7 +636,12 @@ 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 == ["q0", "q1", "q2", "q3", "q4"] + circuit = Circuit(5, wire_names={"q1": 3, "q3": 1, "q2": 4, "q4": 0, "q0": 2}) + assert circuit.wire_names == ["q4", "q3", "q0", "q1", "q2"] + with pytest.raises(TypeError): circuit = Circuit(5, wire_names=1) with pytest.raises(ValueError): @@ -644,7 +649,7 @@ def test_circuit_wire_names_errors(): with pytest.raises(ValueError): circuit = Circuit(2, wire_names={"q0": "1", "q1": 2}) with pytest.raises(ValueError): - circuit = Circuit(2, wire_names={"q0": 4, "q1": 5, "q2": 6}) + circuit = Circuit(3, wire_names={"q0": 4, "q1": 5, "q2": 6}) def test_circuit_draw_wire_names(): From 01b667d65a1d1323bfc7da26f573bbbd67c8b9f9 Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Wed, 23 Oct 2024 12:44:39 +0400 Subject: [PATCH 08/34] fix: router.py coverage --- tests/test_transpiler_router.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/test_transpiler_router.py b/tests/test_transpiler_router.py index c598f1344c..8583d9577b 100644 --- a/tests/test_transpiler_router.py +++ b/tests/test_transpiler_router.py @@ -539,6 +539,11 @@ def test_star_error_multi_qubit(): with pytest.raises(ConnectivityError): 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): + StarConnectivityRouter(chip) + # @pytest.mark.parametrize("nqubits", [1, 3, 5]) @pytest.mark.parametrize("nqubits", [5]) From 4e21132656e3d1c9a5dc0999ba9929428a91740e Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Thu, 24 Oct 2024 12:50:15 +0400 Subject: [PATCH 09/34] fix: remove dead codes --- src/qibo/models/circuit.py | 1 - src/qibo/transpiler/pipeline.py | 10 ---------- src/qibo/transpiler/placer.py | 15 +-------------- 3 files changed, 1 insertion(+), 25 deletions(-) diff --git a/src/qibo/models/circuit.py b/src/qibo/models/circuit.py index e4ea88ee9c..48523eb6cf 100644 --- a/src/qibo/models/circuit.py +++ b/src/qibo/models/circuit.py @@ -301,7 +301,6 @@ def wire_names(self, wire_names: Union[list, dict]): ) self._wire_names = wire_names.copy() - # self._wire_names = wire_names elif isinstance(wire_names, dict): if len(wire_names.keys()) > self.nqubits: raise_error( diff --git a/src/qibo/transpiler/pipeline.py b/src/qibo/transpiler/pipeline.py index cdbe2a8567..c2f9e2c26a 100644 --- a/src/qibo/transpiler/pipeline.py +++ b/src/qibo/transpiler/pipeline.py @@ -54,15 +54,6 @@ def assert_circuit_equivalence( for _ in range(ntests) ] - # original: list = original_circuit.wire_names - # transpiled: list = transpiled_circuit.wire_names - # initial_map = [0, 1, 2, 3, 4] - # initial_map = [original.index(qubit) for qubit in transpiled] - # reordered_test_states = [] - # reordered_test_states = [ - # _transpose_qubits(initial_state, initial_map) for initial_state in test_states - # ] - ordering = list(final_map.values()) for i, state in enumerate(test_states): @@ -70,7 +61,6 @@ def assert_circuit_equivalence( original_circuit, initial_state=state ).state() final_state = backend.execute_circuit( - # transpiled_circuit, initial_state=reordered_test_states[i] transpiled_circuit, initial_state=state, ).state() diff --git a/src/qibo/transpiler/placer.py b/src/qibo/transpiler/placer.py index 963d47749d..f1ad7c6005 100644 --- a/src/qibo/transpiler/placer.py +++ b/src/qibo/transpiler/placer.py @@ -201,9 +201,6 @@ def __call__(self, circuit: Circuit): Args: circuit (:class:`qibo.models.circuit.Circuit`): circuit to be transpiled. - - Returns: - (dict): physical to logical qubit mapping. """ gates_qubits_pairs = _find_gates_qubits_pairs(circuit) if len(gates_qubits_pairs) < 3: @@ -236,8 +233,6 @@ def __call__(self, circuit: Circuit): break circuit.wire_names = sorted(result.mapping, key=lambda k: result.mapping[k]) - # sorted_result = dict(sorted(result.mapping.items())) - # circuit.wire_names = sorted(sorted_result, key=sorted_result.get) class Random(Placer): @@ -264,9 +259,6 @@ def __call__(self, circuit): Args: circuit (:class:`qibo.models.circuit.Circuit`): Circuit to be transpiled. - - Returns: - (dict): physical-to-logical qubit mapping. """ _, local_state = _check_backend_and_local_state(self.seed, backend=None) gates_qubits_pairs = _find_gates_qubits_pairs(circuit) @@ -352,15 +344,10 @@ def __call__(self, circuit: Circuit): Args: circuit (:class:`qibo.models.circuit.Circuit`): circuit to be transpiled. - - Returns: - (dict): physical to logical qubit mapping. """ self.routing_algorithm.connectivity = self.connectivity new_circuit = self._assemble_circuit(circuit) - final_placement = self._routing_step(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. From 12bf8b0e870bc73ed78848b5f33fa42f0ce3225e Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Thu, 24 Oct 2024 13:35:13 +0400 Subject: [PATCH 10/34] fix: refactor connectivity graphs in test files --- tests/conftest.py | 49 ++++++++++ tests/test_transpiler_optimizer.py | 18 +--- tests/test_transpiler_pipeline.py | 36 +++----- tests/test_transpiler_placer.py | 64 ++++++------- tests/test_transpiler_router.py | 139 ++++++++++++++++------------- 5 files changed, 170 insertions(+), 136 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index db51f97456..5aaaf1a129 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,54 @@ def clear(): _Global._transpiler = None +@pytest.fixture +def star_connectivity(): + def _star_connectivity(names=["q0", "q1", "q2", "q3", "q4"], 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=["q0", "q1", "q2", "q3", "q4"]): + 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 + + +@pytest.fixture +def line_connectivity(): + def _line_connectivity(n, names=None): + if names is None: + names = [f"q{i}" for i in range(n)] + chip = nx.Graph() + 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 + + return _line_connectivity + + def pytest_generate_tests(metafunc): module_name = metafunc.module.__name__ diff --git a/tests/test_transpiler_optimizer.py b/tests/test_transpiler_optimizer.py index 457e25ecf7..cd55115a9b 100644 --- a/tests/test_transpiler_optimizer.py +++ b/tests/test_transpiler_optimizer.py @@ -6,26 +6,14 @@ from qibo.transpiler.optimizer import Preprocessing, Rearrange -def star_connectivity(names=["q0", "q1", "q2", "q3", "q4"], 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 - - -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) -def test_preprocessing_same(): +def test_preprocessing_same(star_connectivity): circ = Circuit(5) circ.add(gates.CNOT(0, 1)) preprocesser = Preprocessing(connectivity=star_connectivity()) @@ -33,7 +21,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 82fe4c9419..4affd8026a 100644 --- a/tests/test_transpiler_pipeline.py +++ b/tests/test_transpiler_pipeline.py @@ -51,31 +51,19 @@ def generate_random_circuit(nqubits, ngates, names=None, seed=42): return circuit -def star_connectivity(names=["q0", "q1", "q2", "q3", "q4"], 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 - - -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(), ["q0", "q1", "q5"]) 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(), ["q0", "q1"]) assert "New connectivity graph is not connected." in str(excinfo.value) -def test_restrict_qubits(): +def test_restrict_qubits(star_connectivity): new_connectivity = restrict_connectivity_qubits( star_connectivity(["A", "B", "C", "D", "E"]), ["A", "B", "C"] ) @@ -85,7 +73,7 @@ def test_restrict_qubits(): @pytest.mark.parametrize("ngates", [5, 10, 50]) @pytest.mark.parametrize("names", [["A", "B", "C", "D", "E"], [0, 1, 2, 3, 4]]) -def test_pipeline_default(ngates, names): +def test_pipeline_default(ngates, names, star_connectivity): circ = generate_random_circuit(nqubits=5, ngates=ngates, names=names) connectivity = star_connectivity(names) @@ -133,7 +121,7 @@ def test_assert_circuit_equivalence_false(): assert_circuit_equivalence(circ1, circ2, final_map=final_map) -def test_int_qubit_names_default(): +def test_int_qubit_names_default(star_connectivity): names = [1244, 1532, 2315, 6563, 8901] circ = Circuit(5, wire_names=names) connectivity = star_connectivity(names) @@ -156,7 +144,7 @@ def test_error_connectivity(): @pytest.mark.parametrize("qubits", [3, 5]) -def test_is_satisfied(qubits): +def test_is_satisfied(qubits, star_connectivity): default_transpiler = Passes(passes=None, connectivity=star_connectivity()) circuit = Circuit(qubits) circuit.wire_names = ["q0", "q1", "q2", "q3", "q4"][:qubits] @@ -165,7 +153,7 @@ def test_is_satisfied(qubits): assert default_transpiler.is_satisfied(circuit) -def test_is_satisfied_false_decomposition(): +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)) @@ -173,7 +161,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)) @@ -185,7 +173,7 @@ def test_is_satisfied_false_connectivity(): @pytest.mark.parametrize("ngates", [5, 20]) @pytest.mark.parametrize("placer", [Random, Trivial, ReverseTraversal]) @pytest.mark.parametrize("router", [ShortestPaths, Sabre]) -def test_custom_passes(placer, router, ngates, nqubits): +def test_custom_passes(placer, router, ngates, nqubits, star_connectivity): connectivity = star_connectivity() circ = generate_random_circuit(nqubits=nqubits, ngates=ngates) custom_passes = [] @@ -222,7 +210,9 @@ def test_custom_passes(placer, router, ngates, nqubits): @pytest.mark.parametrize( "restrict_names", [["q1", "q2", "q3"], ["q0", "q2", "q4"], ["q4", "q2", "q3"]] ) -def test_custom_passes_restrict(ngates, placer, routing, restrict_names): +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 = [] @@ -263,7 +253,7 @@ def test_custom_passes_wrong_pass(): custom_pipeline(circ) -def test_int_qubit_names(): +def test_int_qubit_names(star_connectivity): names = [980, 123, 45, 9, 210464] connectivity = star_connectivity(names) transpiler = Passes( diff --git a/tests/test_transpiler_placer.py b/tests/test_transpiler_placer.py index 8a2532583c..86295022cb 100644 --- a/tests/test_transpiler_placer.py +++ b/tests/test_transpiler_placer.py @@ -19,18 +19,6 @@ from qibo.transpiler.router import ShortestPaths -def star_connectivity(names=["q0", "q1", "q2", "q3", "q4"], 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 - - def star_circuit(names=["q0", "q1", "q2", "q3", "q4"]): circuit = Circuit(5, wire_names=names) for i in range(1, 5): @@ -38,7 +26,7 @@ def star_circuit(names=["q0", "q1", "q2", "q3", "q4"]): return circuit -def test_assert_placement_true(): +def test_assert_placement_true(star_connectivity): circuit = Circuit(5) assert_placement(circuit, connectivity=star_connectivity()) @@ -46,7 +34,7 @@ def test_assert_placement_true(): @pytest.mark.parametrize( "qubits, names", [(5, ["A", "B", "C", "D", "F"]), (3, ["A", "B", "C"])] ) -def test_assert_placement_false(qubits, names): +def test_assert_placement_false(qubits, names, star_connectivity): connectivity = star_connectivity() circuit = Circuit(qubits, wire_names=names) with pytest.raises(PlacementError): @@ -54,7 +42,7 @@ def test_assert_placement_false(qubits, names): @pytest.mark.parametrize("qubits", [10, 1]) -def test_assert_placement_error(qubits): +def test_assert_placement_error(qubits, star_connectivity): connectivity = star_connectivity() circuit = Circuit(qubits) with pytest.raises(PlacementError): @@ -62,17 +50,17 @@ def test_assert_placement_error(qubits): @pytest.mark.parametrize("names", [["A", "B", "C", "D", "E"], [0, 1, 2, 3, 4]]) -def test_mapping_consistency(names): +def test_mapping_consistency(names, star_connectivity): assert_mapping_consistency(names, star_connectivity(names)) -def test_mapping_consistency_error(): +def test_mapping_consistency_error(star_connectivity): with pytest.raises(PlacementError): assert_mapping_consistency(["A", "B", "C", "D", "F"], star_connectivity()) @pytest.mark.parametrize("names", [["A", "B", "C", "D", "E"], [0, 1, 2, 3, 4]]) -def test_mapping_consistency_restricted(names): +def test_mapping_consistency_restricted(names, star_connectivity): connectivity = star_connectivity(names) on_qubit = [names[0], names[2]] restricted_connectivity = restrict_connectivity_qubits(connectivity, on_qubit) @@ -80,7 +68,7 @@ def test_mapping_consistency_restricted(names): @pytest.mark.parametrize("names", [["A", "B", "C", "D", "E"], [0, 1, 2, 3, 4]]) -def test_mapping_consistency_restricted_error(names): +def test_mapping_consistency_restricted_error(names, star_connectivity): connectivity = star_connectivity(names) on_qubit = [names[0], names[2]] restricted_connectivity = restrict_connectivity_qubits(connectivity, on_qubit) @@ -104,7 +92,7 @@ def test_gates_qubits_pairs_error(): gates_qubits_pairs = _find_gates_qubits_pairs(circuit) -def test_trivial(): +def test_trivial(star_connectivity): names = ["q4", "q3", "q2", "q1", "q0"] circuit = Circuit(5, wire_names=names) connectivity = star_connectivity(names) @@ -114,14 +102,14 @@ def test_trivial(): assert_placement(circuit, connectivity) -def test_trivial_error(): +def test_trivial_error(star_connectivity): connectivity = star_connectivity() placer = Trivial(connectivity=connectivity) with pytest.raises(ValueError): placer() -def test_trivial_restricted(): +def test_trivial_restricted(star_connectivity): names = ["q0", "q2"] circuit = Circuit(2, wire_names=names) connectivity = star_connectivity() @@ -136,7 +124,7 @@ def test_trivial_restricted(): "custom_layout", [["E", "D", "C", "B", "A"], {"E": 0, "D": 1, "C": 2, "B": 3, "A": 4}], ) -def test_custom(custom_layout): +def test_custom(custom_layout, star_connectivity): circuit = Circuit(5) connectivity = star_connectivity(["A", "B", "C", "D", "E"]) placer = Custom(connectivity=connectivity, initial_map=custom_layout) @@ -147,7 +135,7 @@ def test_custom(custom_layout): @pytest.mark.parametrize( "custom_layout", [[4, 3, 2, 1, 0], {4: 0, 3: 1, 2: 2, 1: 3, 0: 4}] ) -def test_custom_int(custom_layout): +def test_custom_int(custom_layout, star_connectivity): names = [0, 1, 2, 3, 4] circuit = Circuit(5, wire_names=names) connectivity = star_connectivity(names) @@ -157,7 +145,7 @@ def test_custom_int(custom_layout): @pytest.mark.parametrize("custom_layout", [["D", "C"], {"C": 1, "D": 0}]) -def test_custom_restricted(custom_layout): +def test_custom_restricted(custom_layout, star_connectivity): circuit = Circuit(2, wire_names=["C", "D"]) connectivity = star_connectivity(["A", "B", "C", "D", "E"]) restricted_connectivity = restrict_connectivity_qubits(connectivity, ["C", "D"]) @@ -167,7 +155,7 @@ def test_custom_restricted(custom_layout): assert_placement(circuit, restricted_connectivity) -def test_custom_error_circuit(): +def test_custom_error_circuit(star_connectivity): circuit = Circuit(3) custom_layout = [4, 3, 2, 1, 0] connectivity = star_connectivity(names=custom_layout) @@ -176,7 +164,7 @@ def test_custom_error_circuit(): placer(circuit) -def test_custom_error_type(): +def test_custom_error_type(star_connectivity): circuit = Circuit(5) connectivity = star_connectivity() placer = Custom(connectivity=connectivity, initial_map=1) @@ -184,7 +172,7 @@ def test_custom_error_type(): placer(circuit) -def test_subgraph_perfect(): +def test_subgraph_perfect(star_connectivity): connectivity = star_connectivity() placer = Subgraph(connectivity=connectivity) circuit = star_circuit() @@ -207,7 +195,7 @@ def imperfect_circuit(): return circuit -def test_subgraph_non_perfect(): +def test_subgraph_non_perfect(star_connectivity): connectivity = star_connectivity() placer = Subgraph(connectivity=connectivity) circuit = imperfect_circuit() @@ -215,7 +203,7 @@ def test_subgraph_non_perfect(): assert_placement(circuit, connectivity) -def test_subgraph_error(): +def test_subgraph_error(star_connectivity): connectivity = star_connectivity() placer = Subgraph(connectivity=connectivity) circuit = Circuit(5) @@ -223,7 +211,7 @@ def test_subgraph_error(): placer(circuit) -def test_subgraph_restricted(): +def test_subgraph_restricted(star_connectivity): circuit = Circuit(4) circuit.add(gates.CNOT(0, 3)) circuit.add(gates.CNOT(0, 1)) @@ -242,7 +230,7 @@ def test_subgraph_restricted(): @pytest.mark.parametrize("reps", [1, 10, 100]) @pytest.mark.parametrize("names", [["A", "B", "C", "D", "E"], [0, 1, 2, 3, 4]]) -def test_random(reps, names): +def test_random(reps, names, star_connectivity): connectivity = star_connectivity(names) placer = Random(connectivity=connectivity, samples=reps) circuit = star_circuit(names=names) @@ -250,7 +238,7 @@ def test_random(reps, names): assert_placement(circuit, connectivity) -def test_random_restricted(): +def test_random_restricted(star_connectivity): names = [0, 1, 2, 3, 4] circuit = Circuit(4, wire_names=names[:4]) circuit.add(gates.CNOT(1, 3)) @@ -268,7 +256,7 @@ def test_random_restricted(): @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): +def test_reverse_traversal(ngates, names, star_connectivity): circuit = star_circuit(names=names) connectivity = star_connectivity(names=names) routing = ShortestPaths(connectivity=connectivity) @@ -277,7 +265,7 @@ def test_reverse_traversal(ngates, names): 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) @@ -286,7 +274,7 @@ def test_reverse_traversal_no_gates(): 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)) @@ -306,7 +294,7 @@ def test_reverse_traversal_restricted(): assert_placement(circuit, restricted_connectivity) -def test_star_connectivity_placer(): +def test_star_connectivity_placer(star_connectivity): circ = Circuit(5) circ.add(gates.CZ(0, 1)) circ.add(gates.CZ(1, 2)) @@ -319,7 +307,7 @@ def test_star_connectivity_placer(): @pytest.mark.parametrize("first", [True, False]) -def test_star_connectivity_placer_error(first): +def test_star_connectivity_placer_error(first, star_connectivity): circ = Circuit(5) if first: circ.add(gates.CZ(0, 1)) diff --git a/tests/test_transpiler_router.py b/tests/test_transpiler_router.py index 8583d9577b..6d10dfd5b1 100644 --- a/tests/test_transpiler_router.py +++ b/tests/test_transpiler_router.py @@ -32,42 +32,6 @@ ) -def star_connectivity(names=["q0", "q1", "q2", "q3", "q4"], 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 - - -def grid_connectivity(names=["q0", "q1", "q2", "q3", "q4"]): - 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 - - -def line_connectivity(n, names): - if names is None: - names = [f"q{i}" for i in range(n)] - chip = nx.Graph() - 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 generate_random_circuit(nqubits, ngates, names=None, seed=42): """Generate a random circuit with RX and CZ gates.""" np.random.seed(seed) @@ -120,26 +84,26 @@ def matched_circuit(names): return circuit -def test_assert_connectivity(): +def test_assert_connectivity(star_connectivity): names = ["A", "B", "C", "D", "E"] assert_connectivity(star_connectivity(names), matched_circuit(names)) -def test_assert_connectivity_false(): +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(): +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) -def test_bell_state_3q(): +def test_bell_state_3q(line_connectivity): from qibo.transpiler.pipeline import _transpose_qubits circuit = Circuit(3) @@ -163,10 +127,33 @@ def test_bell_state_3q(): @pytest.mark.parametrize("ngates", [5, 25]) @pytest.mark.parametrize("placer", [Trivial, Random]) -@pytest.mark.parametrize("connectivity", [star_connectivity(), grid_connectivity()]) -def test_random_circuits_5q(ngates, placer, connectivity): - placer = placer(connectivity=connectivity) - transpiler = ShortestPaths(connectivity=connectivity) +def test_random_circuits_5q(ngates, placer, star_connectivity): + connectivity = star_connectivity() + placer = placer(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]) +@pytest.mark.parametrize("placer", [Trivial, Random]) +def test_random_circuits_5q_grid(ngates, placer, grid_connectivity): + connectivity = grid_connectivity() + placer = placer(connectivity) + transpiler = ShortestPaths(connectivity) circuit = generate_random_circuit(nqubits=5, ngates=ngates) original_circuit = circuit.copy() @@ -186,7 +173,7 @@ def test_random_circuits_5q(ngates, placer, connectivity): @pytest.mark.parametrize("nqubits", [11, 12, 13, 14, 15]) @pytest.mark.parametrize("ngates", [30, 50]) -def test_random_circuits_15q_50g(nqubits, ngates): +def test_random_circuits_15q_50g(nqubits, ngates, line_connectivity): connectivity = line_connectivity(nqubits, None) placer = Random(connectivity=connectivity) transpiler = Sabre(connectivity=connectivity) @@ -207,7 +194,7 @@ def test_random_circuits_15q_50g(nqubits, ngates): ) -def test_star_circuit(): +def test_star_circuit(star_connectivity): connectivity = star_connectivity() circuit = star_circuit() placer = Subgraph(connectivity=connectivity) @@ -226,7 +213,7 @@ def test_star_circuit(): ) -def test_star_circuit_custom_map(): +def test_star_circuit_custom_map(star_connectivity): connectivity = star_connectivity() circuit = star_circuit() placer = Custom( @@ -247,7 +234,7 @@ def test_star_circuit_custom_map(): ) -def test_routing_with_measurements(): +def test_routing_with_measurements(star_connectivity): connectivity = star_connectivity() circuit = Circuit(5) circuit.add(gates.CNOT(0, 1)) @@ -268,7 +255,7 @@ def test_routing_with_measurements(): ) -def test_sabre_looping(): +def test_sabre_looping(line_connectivity): # Setup where the looping occurs # Line connectivity, gates with gate_array, Trivial placer @@ -304,7 +291,7 @@ def test_sabre_looping(): ) -def test_sabre_shortest_path_routing(): +def test_sabre_shortest_path_routing(line_connectivity): gate_array = [(0, 9), (5, 9), (2, 8)] # The gate (2, 8) should be routed next loop_circ = Circuit(10) @@ -411,7 +398,7 @@ def test_circuit_map(): "names", [["q0", "q1", "q2", "q3", "q4"], [0, 1, 2, 3, 4], ["A", "B", "C", "D", "E"]], ) -def test_sabre_matched(names): +def test_sabre_matched(names, star_connectivity): connectivity = star_connectivity(names=names) circuit = matched_circuit(names) original_circuit = circuit.copy() @@ -431,7 +418,7 @@ def test_sabre_matched(names): @pytest.mark.parametrize("seed", [42]) -def test_sabre_simple(seed): +def test_sabre_simple(seed, star_connectivity): connectivity = star_connectivity() circ = Circuit(5) circ.add(gates.CZ(0, 1)) @@ -459,14 +446,44 @@ def test_sabre_simple(seed): @pytest.mark.parametrize("look", [0, 5]) @pytest.mark.parametrize("decay", [0.5, 1.0]) @pytest.mark.parametrize("placer_param", [Trivial, Random]) -@pytest.mark.parametrize("connectivity", [star_connectivity(), grid_connectivity()]) -def test_sabre_random_circuits(n_gates, look, decay, placer_param, connectivity): +def test_sabre_random_circuits(n_gates, look, decay, placer_param, star_connectivity): + connectivity = star_connectivity() + circuit = generate_random_circuit(nqubits=5, ngates=n_gates) + measurement = gates.M(*range(5)) + circuit.add(measurement) + original_circuit = circuit.copy() + placer = placer_param(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, + ) + 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]) +@pytest.mark.parametrize("placer_param", [Trivial, Random]) +def test_sabre_random_circuits_grid( + n_gates, look, decay, placer_param, 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 = placer_param(connectivity=connectivity) - router = Sabre(connectivity=connectivity, lookahead=look, decay_lookahead=decay) + placer = placer_param(connectivity) + router = Sabre(connectivity, lookahead=look, decay_lookahead=decay) placer(circuit) transpiled_circuit, final_qubit_map = router(circuit) @@ -483,7 +500,7 @@ def test_sabre_random_circuits(n_gates, look, decay, placer_param, connectivity) assert transpiled_circuit.queue[-1].register_name == measurement.register_name -def test_sabre_memory_map(): +def test_sabre_memory_map(star_connectivity): placer = Trivial() layout_circ = Circuit(5) placer(layout_circ) @@ -509,7 +526,7 @@ def test_sabre_intermediate_measurements(): @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)) @@ -531,7 +548,7 @@ def test_restrict_qubits(router_algorithm): assert routed_circ.wire_names == ["q0", "q3", "q2"] -def test_star_error_multi_qubit(): +def test_star_error_multi_qubit(star_connectivity): circuit = Circuit(3) circuit.add(gates.TOFFOLI(0, 1, 2)) connectivity = star_connectivity(middle_qubit_idx=2) @@ -555,7 +572,9 @@ def test_star_error_multi_qubit(): "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): +def test_star_router( + nqubits, depth, middle_qubit, measurements, unitaries, names, star_connectivity +): unitary_dim = min(2, nqubits) connectivity = star_connectivity(names=names, middle_qubit_idx=middle_qubit) if unitaries: From 36c1348f588bfb2aa46789523cb97eb7b37c4a9f Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Thu, 24 Oct 2024 13:55:45 +0400 Subject: [PATCH 11/34] fix: return type of a router --- src/qibo/transpiler/abstract.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/qibo/transpiler/abstract.py b/src/qibo/transpiler/abstract.py index e24f7acead..11e1641759 100644 --- a/src/qibo/transpiler/abstract.py +++ b/src/qibo/transpiler/abstract.py @@ -1,4 +1,5 @@ from abc import ABC, abstractmethod +from typing import Tuple import networkx as nx @@ -25,7 +26,7 @@ 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, *args) -> Circuit: + def __call__(self, circuit: Circuit, *args) -> Tuple[Circuit, dict]: """Match circuit to hardware connectivity. Args: From f19dc6ed1ecb1e624ba6c4bbc63f72c3d3f56f20 Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Fri, 25 Oct 2024 11:31:04 +0400 Subject: [PATCH 12/34] fix: minor changes --- src/qibo/models/circuit.py | 3 +-- src/qibo/transpiler/optimizer.py | 6 +++--- src/qibo/transpiler/placer.py | 2 -- tests/test_transpiler_placer.py | 7 ------- 4 files changed, 4 insertions(+), 14 deletions(-) diff --git a/src/qibo/models/circuit.py b/src/qibo/models/circuit.py index 48523eb6cf..08aa4e639d 100644 --- a/src/qibo/models/circuit.py +++ b/src/qibo/models/circuit.py @@ -407,8 +407,7 @@ 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) - new_wire_names = [self.wire_names[q] for q in list(sorted(qubits))] - kwargs["wire_names"] = new_wire_names + kwargs["wire_names"] = [self.wire_names[q] for q in list(sorted(qubits))] circuit = self.__class__(**kwargs) circuit.add(gate.on_qubits(qubit_map) for gate in reversed(list_of_gates)) return circuit, qubit_map diff --git a/src/qibo/transpiler/optimizer.py b/src/qibo/transpiler/optimizer.py index 2ba05895e3..a30f117757 100644 --- a/src/qibo/transpiler/optimizer.py +++ b/src/qibo/transpiler/optimizer.py @@ -27,9 +27,9 @@ def __call__(self, circuit: Circuit) -> Circuit: ) if logical_qubits == physical_qubits: return circuit - new_wire_names = circuit.wire_names.copy() + [ - name for name in self.connectivity.nodes if name not in circuit.wire_names - ] + 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) diff --git a/src/qibo/transpiler/placer.py b/src/qibo/transpiler/placer.py index f1ad7c6005..0e7d1ec136 100644 --- a/src/qibo/transpiler/placer.py +++ b/src/qibo/transpiler/placer.py @@ -147,8 +147,6 @@ def __call__(self, circuit: Circuit = None): Args: circuit (:class:`qibo.models.circuit.Circuit`): circuit to be transpiled. """ - if circuit is None: - raise_error(ValueError, "Circuit must be provided.") return diff --git a/tests/test_transpiler_placer.py b/tests/test_transpiler_placer.py index 86295022cb..90d4d301f7 100644 --- a/tests/test_transpiler_placer.py +++ b/tests/test_transpiler_placer.py @@ -102,13 +102,6 @@ def test_trivial(star_connectivity): assert_placement(circuit, connectivity) -def test_trivial_error(star_connectivity): - connectivity = star_connectivity() - placer = Trivial(connectivity=connectivity) - with pytest.raises(ValueError): - placer() - - def test_trivial_restricted(star_connectivity): names = ["q0", "q2"] circuit = Circuit(2, wire_names=names) From 7534ef24bcb140dc34e1dcf436c0615b6c7adb1b Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Fri, 25 Oct 2024 17:50:46 +0400 Subject: [PATCH 13/34] fix: remove add_nodes_from when setting backend --- src/qibo/backends/__init__.py | 2 -- src/qibo/models/error_mitigation.py | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/qibo/backends/__init__.py b/src/qibo/backends/__init__.py index 1f898a890e..2f64654e8b 100644 --- a/src/qibo/backends/__init__.py +++ b/src/qibo/backends/__init__.py @@ -145,8 +145,6 @@ def _default_transpiler(cls): and connectivity_edges is not None ): connectivity = nx.Graph(connectivity_edges) - connectivity.add_nodes_from(qubits) - return Passes( connectivity=connectivity, passes=[ diff --git a/src/qibo/models/error_mitigation.py b/src/qibo/models/error_mitigation.py index c6d72e2c09..b2f5a1c31a 100644 --- a/src/qibo/models/error_mitigation.py +++ b/src/qibo/models/error_mitigation.py @@ -1170,10 +1170,8 @@ def _execute_circuit(circuit, qubit_map, noise_model=None, nshots=10000, backend if backend is None: # pragma: no cover backend = get_backend() elif backend.name == "qibolab": # pragma: no cover - qubits = backend.qubits connectivity_edges = backend.connectivity connectivity = nx.Graph(connectivity_edges) - connectivity.add_nodes_from(qubits) transpiler = Passes( connectivity=connectivity, passes=[Custom(initial_map=qubit_map, connectivity=connectivity)], From 67fae1f83f12bf6a86c7b2b5bb2b17f259b2c6c9 Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Mon, 28 Oct 2024 10:19:35 +0400 Subject: [PATCH 14/34] fix: Circuit remove default wire_names / dict wire_names --- src/qibo/models/circuit.py | 41 +++++--------------- src/qibo/transpiler/placer.py | 11 ++++-- tests/conftest.py | 6 +-- tests/test_models_circuit.py | 27 +++++++------ tests/test_tomography_gate_set_tomography.py | 8 +--- tests/test_transpiler_pipeline.py | 17 ++++---- tests/test_transpiler_placer.py | 12 +++--- tests/test_transpiler_router.py | 8 ++-- 8 files changed, 50 insertions(+), 80 deletions(-) diff --git a/src/qibo/models/circuit.py b/src/qibo/models/circuit.py index 08aa4e639d..58c214aae5 100644 --- a/src/qibo/models/circuit.py +++ b/src/qibo/models/circuit.py @@ -158,7 +158,7 @@ def __init__( nqubits: int, 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( @@ -282,49 +282,28 @@ 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)}.", ) - self._wire_names = wire_names.copy() - 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 not all(isinstance(k, int) for k in wire_names.values()): - raise_error( - ValueError, - "all values of the ``wire_names`` dictionary must be integers.", - ) - if sorted(wire_names.values()) != list(range(self.nqubits)): - raise_error( - ValueError, - "all values of the ``wire_names`` dictionary must be unique integers from 0 to ``nqubits``.", - ) - - self._wire_names = [ - k for k, _ in sorted(wire_names.items(), key=lambda x: x[1]) - ] 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 @@ -1347,8 +1326,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: diff --git a/src/qibo/transpiler/placer.py b/src/qibo/transpiler/placer.py index 0e7d1ec136..ca12406c33 100644 --- a/src/qibo/transpiler/placer.py +++ b/src/qibo/transpiler/placer.py @@ -109,6 +109,7 @@ def __call__(self, circuit: Circuit): """ 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: @@ -126,14 +127,16 @@ def __call__(self, circuit: Circuit): ) ( - circuit.wire_names[middle_qubit_idx], - circuit.wire_names[new_middle], + wire_names[middle_qubit_idx], + wire_names[new_middle], ) = ( - circuit.wire_names[new_middle], - circuit.wire_names[middle_qubit_idx], + wire_names[new_middle], + wire_names[middle_qubit_idx], ) break + circuit.wire_names = wire_names + class Trivial(Placer): """Place qubits according to the order of the qubit names that the user provides.""" diff --git a/tests/conftest.py b/tests/conftest.py index 5aaaf1a129..572864b29c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -84,7 +84,7 @@ def clear(): @pytest.fixture def star_connectivity(): - def _star_connectivity(names=["q0", "q1", "q2", "q3", "q4"], middle_qubit_idx=2): + def _star_connectivity(names=list(range(5)), middle_qubit_idx=2): chip = nx.Graph() chip.add_nodes_from(names) graph_list = [ @@ -100,7 +100,7 @@ def _star_connectivity(names=["q0", "q1", "q2", "q3", "q4"], middle_qubit_idx=2) @pytest.fixture def grid_connectivity(): - def _grid_connectivity(names=["q0", "q1", "q2", "q3", "q4"]): + def _grid_connectivity(names=list(range(5))): chip = nx.Graph() chip.add_nodes_from(names) graph_list = [ @@ -120,7 +120,7 @@ def _grid_connectivity(names=["q0", "q1", "q2", "q3", "q4"]): def line_connectivity(): def _line_connectivity(n, names=None): if names is None: - names = [f"q{i}" for i in range(n)] + names = list(range(n)) chip = nx.Graph() chip.add_nodes_from(names) graph_list = [(names[i], names[i + 1]) for i in range(n - 1)] diff --git a/tests/test_models_circuit.py b/tests/test_models_circuit.py index 6c5864981a..f521446c26 100644 --- a/tests/test_models_circuit.py +++ b/tests/test_models_circuit.py @@ -625,7 +625,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): @@ -638,18 +638,17 @@ def test_circuit_draw(): def test_circuit_wire_names(): circuit = Circuit(5) - assert circuit.wire_names == ["q0", "q1", "q2", "q3", "q4"] - circuit = Circuit(5, wire_names={"q1": 3, "q3": 1, "q2": 4, "q4": 0, "q0": 2}) - assert circuit.wire_names == ["q4", "q3", "q0", "q1", "q2"] + 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(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(3, wire_names={"q0": 4, "q1": 5, "q2": 6}) + circuit = Circuit(5, wire_names=["a", "b", "c"]) def test_circuit_draw_wire_names(): @@ -726,7 +725,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): @@ -816,7 +815,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)) @@ -853,7 +852,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)) @@ -884,7 +883,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): @@ -905,7 +904,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_tomography_gate_set_tomography.py b/tests/test_tomography_gate_set_tomography.py index de79d67922..c2a40d944f 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,11 +276,7 @@ def test_GST_with_transpiler(backend): transpiler=None, ) # define transpiler - connectivity = nx.Graph() - # star connectivity - connectivity.add_edges_from( - [("q0", "q2"), ("q1", "q2"), ("q2", "q3"), ("q2", "q4")] - ) + connectivity = star_connectivity() transpiler = Passes( connectivity=connectivity, passes=[ diff --git a/tests/test_transpiler_pipeline.py b/tests/test_transpiler_pipeline.py index 4affd8026a..84b5befaed 100644 --- a/tests/test_transpiler_pipeline.py +++ b/tests/test_transpiler_pipeline.py @@ -53,13 +53,13 @@ def generate_random_circuit(nqubits, ngates, names=None, seed=42): def test_restrict_qubits_error_no_subset(star_connectivity): with pytest.raises(ConnectivityError) as excinfo: - restrict_connectivity_qubits(star_connectivity(), ["q0", "q1", "q5"]) + 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(star_connectivity): with pytest.raises(ConnectivityError) as excinfo: - restrict_connectivity_qubits(star_connectivity(), ["q0", "q1"]) + restrict_connectivity_qubits(star_connectivity(), [0, 1]) assert "New connectivity graph is not connected." in str(excinfo.value) @@ -96,7 +96,7 @@ def test_assert_circuit_equivalence_equal(): circ1.add(gates.CZ(0, 1)) circ2.add(gates.X(0)) circ2.add(gates.CZ(0, 1)) - final_map = {"q0": 0, "q1": 1} + final_map = {0: 0, 1: 1} assert_circuit_equivalence(circ1, circ2, final_map=final_map) @@ -106,7 +106,7 @@ def test_assert_circuit_equivalence_swap(): circ1.add(gates.X(0)) circ2.add(gates.SWAP(0, 1)) circ2.add(gates.X(1)) - final_map = {"q0": 1, "q1": 0} + final_map = {0: 1, 1: 0} assert_circuit_equivalence(circ1, circ2, final_map=final_map) @@ -116,7 +116,7 @@ def test_assert_circuit_equivalence_false(): circ1.add(gates.X(0)) circ2.add(gates.SWAP(0, 1)) circ2.add(gates.X(1)) - final_map = {"q0": 0, "q1": 1} + final_map = {0: 0, 1: 1} with pytest.raises(TranspilerPipelineError): assert_circuit_equivalence(circ1, circ2, final_map=final_map) @@ -133,7 +133,7 @@ def test_int_qubit_names_default(star_connectivity): 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) @@ -147,7 +147,6 @@ def test_error_connectivity(): def test_is_satisfied(qubits, star_connectivity): default_transpiler = Passes(passes=None, connectivity=star_connectivity()) circuit = Circuit(qubits) - circuit.wire_names = ["q0", "q1", "q2", "q3", "q4"][:qubits] circuit.add(gates.CZ(0, 2)) circuit.add(gates.Z(0)) assert default_transpiler.is_satisfied(circuit) @@ -207,9 +206,7 @@ def test_custom_passes(placer, router, ngates, nqubits, star_connectivity): @pytest.mark.parametrize("ngates", [5, 20]) @pytest.mark.parametrize("placer", [Random, Trivial, ReverseTraversal]) @pytest.mark.parametrize("routing", [ShortestPaths, Sabre]) -@pytest.mark.parametrize( - "restrict_names", [["q1", "q2", "q3"], ["q0", "q2", "q4"], ["q4", "q2", "q3"]] -) +@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 ): diff --git a/tests/test_transpiler_placer.py b/tests/test_transpiler_placer.py index 90d4d301f7..e170c3eb7d 100644 --- a/tests/test_transpiler_placer.py +++ b/tests/test_transpiler_placer.py @@ -105,7 +105,7 @@ def test_trivial(star_connectivity): def test_trivial_restricted(star_connectivity): names = ["q0", "q2"] circuit = Circuit(2, wire_names=names) - connectivity = star_connectivity() + connectivity = star_connectivity(["q0", "q1", "q2", "q3", "q4"]) restricted_connectivity = restrict_connectivity_qubits(connectivity, names) placer = Trivial(connectivity=restricted_connectivity) placer(circuit) @@ -170,7 +170,7 @@ def test_subgraph_perfect(star_connectivity): placer = Subgraph(connectivity=connectivity) circuit = star_circuit() placer(circuit) - assert circuit.wire_names[0] == "q2" + assert circuit.wire_names[0] == 2 assert_placement(circuit, connectivity) @@ -213,9 +213,7 @@ def test_subgraph_restricted(star_connectivity): circuit.add(gates.CNOT(1, 2)) circuit.add(gates.CNOT(3, 1)) connectivity = star_connectivity() - restricted_connectivity = restrict_connectivity_qubits( - connectivity, ["q0", "q2", "q3", "q4"] - ) + restricted_connectivity = restrict_connectivity_qubits(connectivity, [0, 2, 3, 4]) placer = Subgraph(connectivity=restricted_connectivity) placer(circuit) assert_placement(circuit, restricted_connectivity) @@ -276,7 +274,7 @@ def test_reverse_traversal_restricted(star_connectivity): circuit.add(gates.CNOT(1, 2)) circuit.add(gates.CNOT(3, 1)) connectivity = star_connectivity() - restrict_names = ["q0", "q2", "q3", "q4"] + restrict_names = [0, 2, 3, 4] restricted_connectivity = restrict_connectivity_qubits(connectivity, restrict_names) circuit.wire_names = restrict_names routing = ShortestPaths(connectivity=restricted_connectivity) @@ -296,7 +294,7 @@ def test_star_connectivity_placer(star_connectivity): placer = StarConnectivityPlacer(connectivity) placer(circ) assert_placement(circ, connectivity) - assert circ.wire_names == ["q0", "q2", "q1", "q3", "q4"] + assert circ.wire_names == [0, 2, 1, 3, 4] @pytest.mark.parametrize("first", [True, False]) diff --git a/tests/test_transpiler_router.py b/tests/test_transpiler_router.py index 6d10dfd5b1..afd661a4b1 100644 --- a/tests/test_transpiler_router.py +++ b/tests/test_transpiler_router.py @@ -216,9 +216,7 @@ def test_star_circuit(star_connectivity): def test_star_circuit_custom_map(star_connectivity): connectivity = star_connectivity() circuit = star_circuit() - placer = Custom( - initial_map=["q1", "q0", "q2", "q3", "q4"], connectivity=connectivity - ) + placer = Custom(initial_map=[1, 0, 2, 3, 4], connectivity=connectivity) transpiler = ShortestPaths(connectivity=connectivity) placer(circuit) @@ -430,7 +428,7 @@ def test_sabre_simple(seed, star_connectivity): 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) @@ -532,7 +530,7 @@ def test_restrict_qubits(router_algorithm, star_connectivity): circ.add(gates.CZ(0, 2)) circ.add(gates.CZ(2, 1)) circ.wire_names = ["q0", "q3", "q2"] - connectivity = star_connectivity() + connectivity = star_connectivity(["q0", "q1", "q2", "q3", "q4"]) restricted_connectivity = restrict_connectivity_qubits( connectivity, ["q0", "q2", "q3"] ) From 2ac1a856ac0ffb9434af37d8ec32b462cda67026 Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Mon, 28 Oct 2024 12:04:24 +0400 Subject: [PATCH 15/34] fix: wire_names update docstrings --- src/qibo/models/circuit.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/qibo/models/circuit.py b/src/qibo/models/circuit.py index 58c214aae5..c4d3714820 100644 --- a/src/qibo/models/circuit.py +++ b/src/qibo/models/circuit.py @@ -141,11 +141,9 @@ 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``... ``q(n-1)``]. + wire_names (list, optional): Names for qubit wires. + If ``None``, defaults to [``0``, ``1``, ..., ``nqubits - 1``]. If ``list`` is passed, length of ``list`` must match ``nqubits``. - If ``dict`` is passed, keys should be wire names and values should be qubit indices. Values should be unique and range from 0 to ``nqubits - 1``. - 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``. From 788fd06253073894de0ff457b4faad059ac32087 Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Mon, 28 Oct 2024 12:06:57 +0400 Subject: [PATCH 16/34] fix: line connectivity remove fixture --- tests/conftest.py | 14 -------------- tests/test_transpiler_router.py | 18 ++++++++++++++---- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 572864b29c..16b00578aa 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -116,20 +116,6 @@ def _grid_connectivity(names=list(range(5))): return _grid_connectivity -@pytest.fixture -def line_connectivity(): - def _line_connectivity(n, names=None): - if names is None: - names = list(range(n)) - chip = nx.Graph() - 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 - - return _line_connectivity - - def pytest_generate_tests(metafunc): module_name = metafunc.module.__name__ diff --git a/tests/test_transpiler_router.py b/tests/test_transpiler_router.py index afd661a4b1..f132628df4 100644 --- a/tests/test_transpiler_router.py +++ b/tests/test_transpiler_router.py @@ -32,6 +32,16 @@ ) +def line_connectivity(n, names=None): + if names is None: + names = list(range(n)) + chip = nx.Graph() + 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 generate_random_circuit(nqubits, ngates, names=None, seed=42): """Generate a random circuit with RX and CZ gates.""" np.random.seed(seed) @@ -103,7 +113,7 @@ def test_assert_connectivity_3q(star_connectivity): assert_connectivity(star_connectivity(), circuit) -def test_bell_state_3q(line_connectivity): +def test_bell_state_3q(): from qibo.transpiler.pipeline import _transpose_qubits circuit = Circuit(3) @@ -173,7 +183,7 @@ def test_random_circuits_5q_grid(ngates, placer, grid_connectivity): @pytest.mark.parametrize("nqubits", [11, 12, 13, 14, 15]) @pytest.mark.parametrize("ngates", [30, 50]) -def test_random_circuits_15q_50g(nqubits, ngates, line_connectivity): +def test_random_circuits_15q_50g(nqubits, ngates): connectivity = line_connectivity(nqubits, None) placer = Random(connectivity=connectivity) transpiler = Sabre(connectivity=connectivity) @@ -253,7 +263,7 @@ def test_routing_with_measurements(star_connectivity): ) -def test_sabre_looping(line_connectivity): +def test_sabre_looping(): # Setup where the looping occurs # Line connectivity, gates with gate_array, Trivial placer @@ -289,7 +299,7 @@ def test_sabre_looping(line_connectivity): ) -def test_sabre_shortest_path_routing(line_connectivity): +def test_sabre_shortest_path_routing(): gate_array = [(0, 9), (5, 9), (2, 8)] # The gate (2, 8) should be routed next loop_circ = Circuit(10) From 4e3dbce27c457c0796fbd1acd7c2ef4a7051ae14 Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Mon, 28 Oct 2024 12:40:01 +0400 Subject: [PATCH 17/34] fix: test files default qubit --- tests/test_measurements.py | 2 +- tests/test_models_circuit_fuse.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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_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): From d4e507cbed5a2a3525ec0ecda39485dc60d7cd5b Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Mon, 28 Oct 2024 13:30:18 +0400 Subject: [PATCH 18/34] fix: refactor assert functions --- src/qibo/transpiler/pipeline.py | 118 +------------ src/qibo/transpiler/placer.py | 45 +---- src/qibo/transpiler/router.py | 23 --- src/qibo/transpiler/unroller.py | 33 ---- src/qibo/transpiler/utils.py | 213 ++++++++++++++++++++++++ tests/test_transpiler_decompositions.py | 3 +- tests/test_transpiler_pipeline.py | 8 +- tests/test_transpiler_placer.py | 3 +- tests/test_transpiler_router.py | 15 +- tests/test_transpiler_unroller.py | 8 +- 10 files changed, 232 insertions(+), 237 deletions(-) create mode 100644 src/qibo/transpiler/utils.py diff --git a/src/qibo/transpiler/pipeline.py b/src/qibo/transpiler/pipeline.py index c2f9e2c26a..3e75f1a368 100644 --- a/src/qibo/transpiler/pipeline.py +++ b/src/qibo/transpiler/pipeline.py @@ -1,124 +1,14 @@ -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, assert_placement -from qibo.transpiler.router import ( - ConnectivityError, - StarConnectivityRouter, - assert_connectivity, -) -from qibo.transpiler.unroller import ( - DecompositionError, - NativeGates, - Unroller, - assert_decomposition, -) - - -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_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, - ) +from qibo.transpiler.placer import StarConnectivityPlacer +from qibo.transpiler.router import ConnectivityError, StarConnectivityRouter +from qibo.transpiler.unroller import DecompositionError, NativeGates, Unroller +from qibo.transpiler.utils import assert_connectivity, assert_decomposition def restrict_connectivity_qubits(connectivity: nx.Graph, qubits: list[str]): diff --git a/src/qibo/transpiler/placer.py b/src/qibo/transpiler/placer.py index ca12406c33..2e796670f8 100644 --- a/src/qibo/transpiler/placer.py +++ b/src/qibo/transpiler/placer.py @@ -9,48 +9,7 @@ from qibo.transpiler._exceptions import PlacementError from qibo.transpiler.abstract import Placer, Router from qibo.transpiler.router import _find_connected_qubit - - -def assert_placement(circuit: Circuit, connectivity: nx.Graph): - """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): qubit names. - 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``. - """ - layout = circuit.wire_names - 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: list, connectivity: nx.Graph): - """Check if layout is in the correct form. - - Args: - layout (dict): qubit names. - 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. - """ - nodes = list(connectivity.nodes) - if sorted(nodes) != sorted(layout): - raise_error( - PlacementError, - "Some physical qubits in the layout may be missing or duplicated.", - ) +from qibo.transpiler.utils import assert_placement def _find_gates_qubits_pairs(circuit: Circuit): @@ -159,7 +118,7 @@ class Custom(Placer): Args: map (list or dict): A mapping between physical and logical qubits. - If **dict**, the keys should be physical qubit names, and the values should be the corresponding logical qubit numbers. - - If **list**, it should contain logical qubit numbers, arranged in the order of the physical qubits. + - If **list**, it should contain physical qubit names, arranged in the order of the logical qubits. """ def __init__(self, initial_map: Union[list, dict], connectivity: nx.Graph): diff --git a/src/qibo/transpiler/router.py b/src/qibo/transpiler/router.py index 4b8276522d..289128e02e 100644 --- a/src/qibo/transpiler/router.py +++ b/src/qibo/transpiler/router.py @@ -13,29 +13,6 @@ 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. - """ - 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}.", - ) - - class StarConnectivityRouter(Router): """Transforms an arbitrary circuit to one that can be executed on hardware. diff --git a/src/qibo/transpiler/unroller.py b/src/qibo/transpiler/unroller.py index ee6e15d0a1..5bae287e7c 100644 --- a/src/qibo/transpiler/unroller.py +++ b/src/qibo/transpiler/unroller.py @@ -121,39 +121,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/src/qibo/transpiler/utils.py b/src/qibo/transpiler/utils.py new file mode 100644 index 0000000000..10d76a3f3d --- /dev/null +++ b/src/qibo/transpiler/utils.py @@ -0,0 +1,213 @@ +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 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): qubit names. + 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``. + """ + layout = circuit.wire_names + 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: list, connectivity: nx.Graph): + """Check if layout is in the correct form. + + Args: + layout (dict): qubit names. + 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. + """ + nodes = list(connectivity.nodes) + if sorted(nodes) != sorted(layout): + 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/tests/test_transpiler_decompositions.py b/tests/test_transpiler_decompositions.py index 44c299cc3b..108a4cc416 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.unroller import NativeGates, translate_gate +from qibo.transpiler.utils import assert_decomposition default_natives = NativeGates.Z | NativeGates.RZ | NativeGates.M | NativeGates.I diff --git a/tests/test_transpiler_pipeline.py b/tests/test_transpiler_pipeline.py index 84b5befaed..d8d1379fb6 100644 --- a/tests/test_transpiler_pipeline.py +++ b/tests/test_transpiler_pipeline.py @@ -6,15 +6,11 @@ from qibo.models import Circuit from qibo.transpiler._exceptions import ConnectivityError, TranspilerPipelineError from qibo.transpiler.optimizer import Preprocessing -from qibo.transpiler.pipeline import ( - Passes, - assert_circuit_equivalence, - assert_transpiling, - restrict_connectivity_qubits, -) +from qibo.transpiler.pipeline import Passes, restrict_connectivity_qubits from qibo.transpiler.placer import Random, ReverseTraversal, Trivial from qibo.transpiler.router import Sabre, ShortestPaths from qibo.transpiler.unroller import NativeGates, Unroller +from qibo.transpiler.utils import assert_circuit_equivalence, assert_transpiling def generate_random_circuit(nqubits, ngates, names=None, seed=42): diff --git a/tests/test_transpiler_placer.py b/tests/test_transpiler_placer.py index e170c3eb7d..306d686f0f 100644 --- a/tests/test_transpiler_placer.py +++ b/tests/test_transpiler_placer.py @@ -13,10 +13,9 @@ Subgraph, Trivial, _find_gates_qubits_pairs, - assert_mapping_consistency, - assert_placement, ) from qibo.transpiler.router import ShortestPaths +from qibo.transpiler.utils import assert_mapping_consistency, assert_placement def star_circuit(names=["q0", "q1", "q2", "q3", "q4"]): diff --git a/tests/test_transpiler_router.py b/tests/test_transpiler_router.py index f132628df4..0b5e21bc40 100644 --- a/tests/test_transpiler_router.py +++ b/tests/test_transpiler_router.py @@ -9,26 +9,25 @@ 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 ( - assert_circuit_equivalence, - restrict_connectivity_qubits, -) +from qibo.transpiler.pipeline import restrict_connectivity_qubits from qibo.transpiler.placer import ( Custom, Random, StarConnectivityPlacer, Subgraph, Trivial, - assert_placement, ) from qibo.transpiler.router import ( CircuitMap, Sabre, ShortestPaths, StarConnectivityRouter, +) +from qibo.transpiler.utils import ( + _transpose_qubits, + assert_circuit_equivalence, assert_connectivity, + assert_placement, ) @@ -114,8 +113,6 @@ def test_assert_connectivity_3q(star_connectivity): def test_bell_state_3q(): - from qibo.transpiler.pipeline import _transpose_qubits - circuit = Circuit(3) circuit.add(gates.H(0)) circuit.add(gates.CNOT(0, 2)) diff --git a/tests/test_transpiler_unroller.py b/tests/test_transpiler_unroller.py index d3aa3e9ba9..c72b735c3f 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.unroller import NativeGates, Unroller, translate_gate +from qibo.transpiler.utils import assert_decomposition def test_native_gates_from_gatelist(): From 5cf75b59fbfd05a277a14f3e8e3ca38d6048f576 Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Mon, 28 Oct 2024 14:14:52 +0400 Subject: [PATCH 19/34] fix: refactor assert funcs --- src/qibo/transpiler/placer.py | 9 +- src/qibo/transpiler/router.py | 5 +- src/qibo/transpiler/utils.py | 14 ++ tests/test_transpiler_pipeline.py | 32 ---- tests/test_transpiler_placer.py | 56 +----- tests/test_transpiler_router.py | 30 +--- .../test_transpiler_unitary_decompositions.py | 1 - tests/test_transpiler_unroller.py | 30 ---- tests/test_transpiler_utils.py | 164 ++++++++++++++++++ 9 files changed, 198 insertions(+), 143 deletions(-) create mode 100644 tests/test_transpiler_utils.py diff --git a/src/qibo/transpiler/placer.py b/src/qibo/transpiler/placer.py index 2e796670f8..64826946c1 100644 --- a/src/qibo/transpiler/placer.py +++ b/src/qibo/transpiler/placer.py @@ -9,7 +9,7 @@ from qibo.transpiler._exceptions import PlacementError from qibo.transpiler.abstract import Placer, Router from qibo.transpiler.router import _find_connected_qubit -from qibo.transpiler.utils import assert_placement +from qibo.transpiler.utils import assert_placement, assert_qubit_match def _find_gates_qubits_pairs(circuit: Circuit): @@ -66,7 +66,7 @@ def __call__(self, circuit: Circuit): 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. """ - + assert_qubit_match(circuit, self.connectivity) middle_qubit_idx = circuit.wire_names.index(self.middle_qubit) wire_names = circuit.wire_names.copy() @@ -109,6 +109,7 @@ def __call__(self, circuit: Circuit = None): Args: circuit (:class:`qibo.models.circuit.Circuit`): circuit to be transpiled. """ + assert_qubit_match(circuit, self.connectivity) return @@ -131,6 +132,7 @@ def __call__(self, circuit: Circuit): Args: circuit (:class:`qibo.models.circuit.Circuit`): circuit to be transpiled. """ + assert_qubit_match(circuit, self.connectivity) if isinstance(self.initial_map, dict): circuit.wire_names = sorted(self.initial_map, key=self.initial_map.get) elif isinstance(self.initial_map, list): @@ -162,6 +164,7 @@ def __call__(self, circuit: Circuit): Args: circuit (:class:`qibo.models.circuit.Circuit`): circuit to be transpiled. """ + assert_qubit_match(circuit, self.connectivity) gates_qubits_pairs = _find_gates_qubits_pairs(circuit) if len(gates_qubits_pairs) < 3: raise_error( @@ -220,6 +223,7 @@ def __call__(self, circuit): Args: circuit (:class:`qibo.models.circuit.Circuit`): Circuit to be transpiled. """ + assert_qubit_match(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() @@ -305,6 +309,7 @@ def __call__(self, circuit: Circuit): Args: circuit (:class:`qibo.models.circuit.Circuit`): circuit to be transpiled. """ + assert_qubit_match(circuit, self.connectivity) self.routing_algorithm.connectivity = self.connectivity new_circuit = self._assemble_circuit(circuit) self._routing_step(new_circuit) diff --git a/src/qibo/transpiler/router.py b/src/qibo/transpiler/router.py index 289128e02e..3a5505a51f 100644 --- a/src/qibo/transpiler/router.py +++ b/src/qibo/transpiler/router.py @@ -11,6 +11,7 @@ from qibo.transpiler._exceptions import ConnectivityError from qibo.transpiler.abstract import Router from qibo.transpiler.blocks import Block, CircuitBlocks +from qibo.transpiler.utils import assert_qubit_match class StarConnectivityRouter(Router): @@ -48,7 +49,7 @@ def __call__(self, circuit: Circuit): 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. """ - + assert_qubit_match(circuit, self.connectivity) middle_qubit_idx = circuit.wire_names.index(self.middle_qubit) nqubits = circuit.nqubits new = Circuit(nqubits=nqubits, wire_names=circuit.wire_names) @@ -334,6 +335,7 @@ def __call__(self, circuit: Circuit): (:class:`qibo.models.circuit.Circuit`, dict): circut mapped to hardware topology, and final physical-to-logical qubit mapping. """ + assert_qubit_match(circuit, self.connectivity) self._preprocessing(circuit=circuit) while self._dag.number_of_nodes() != 0: execute_block_list = self._check_execution() @@ -624,6 +626,7 @@ def __call__(self, circuit: Circuit): Returns: (:class:`qibo.models.circuit.Circuit`, dict): routed circuit and final layout. """ + assert_qubit_match(circuit, self.connectivity) self._preprocessing(circuit=circuit) longest_path = np.max(self._dist_matrix) diff --git a/src/qibo/transpiler/utils.py b/src/qibo/transpiler/utils.py index 10d76a3f3d..7325db8f81 100644 --- a/src/qibo/transpiler/utils.py +++ b/src/qibo/transpiler/utils.py @@ -18,6 +18,20 @@ from qibo.transpiler.unroller import NativeGates +def assert_qubit_match(circuit: Circuit, connectivity: nx.Graph): + """Check if the number of qubits in the circuit matches the connectivity graph. + + Args: + circuit (:class:`qibo.models.circuit.Circuit`): Circuit model to check. + connectivity (:class:`networkx.Graph`): Chip connectivity. + """ + if circuit.nqubits != len(connectivity.nodes): + raise_error( + TranspilerPipelineError, + "Number of qubits in the circuit does not match the connectivity graph.", + ) + + def assert_transpiling( original_circuit: Circuit, transpiled_circuit: Circuit, diff --git a/tests/test_transpiler_pipeline.py b/tests/test_transpiler_pipeline.py index d8d1379fb6..30f0cddfbe 100644 --- a/tests/test_transpiler_pipeline.py +++ b/tests/test_transpiler_pipeline.py @@ -85,38 +85,6 @@ def test_pipeline_default(ngates, names, star_connectivity): ) -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_int_qubit_names_default(star_connectivity): names = [1244, 1532, 2315, 6563, 8901] circ = Circuit(5, wire_names=names) diff --git a/tests/test_transpiler_placer.py b/tests/test_transpiler_placer.py index 306d686f0f..1b05743621 100644 --- a/tests/test_transpiler_placer.py +++ b/tests/test_transpiler_placer.py @@ -3,7 +3,7 @@ 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.pipeline import restrict_connectivity_qubits from qibo.transpiler.placer import ( Custom, @@ -15,7 +15,7 @@ _find_gates_qubits_pairs, ) from qibo.transpiler.router import ShortestPaths -from qibo.transpiler.utils import assert_mapping_consistency, assert_placement +from qibo.transpiler.utils import assert_placement def star_circuit(names=["q0", "q1", "q2", "q3", "q4"]): @@ -25,56 +25,6 @@ def star_circuit(names=["q0", "q1", "q2", "q3", "q4"]): return circuit -def test_assert_placement_true(star_connectivity): - circuit = Circuit(5) - assert_placement(circuit, connectivity=star_connectivity()) - - -@pytest.mark.parametrize( - "qubits, names", [(5, ["A", "B", "C", "D", "F"]), (3, ["A", "B", "C"])] -) -def test_assert_placement_false(qubits, names, star_connectivity): - connectivity = star_connectivity() - circuit = Circuit(qubits, wire_names=names) - with pytest.raises(PlacementError): - 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_mapping_consistency(names, star_connectivity): - assert_mapping_consistency(names, star_connectivity(names)) - - -def test_mapping_consistency_error(star_connectivity): - with pytest.raises(PlacementError): - assert_mapping_consistency(["A", "B", "C", "D", "F"], star_connectivity()) - - -@pytest.mark.parametrize("names", [["A", "B", "C", "D", "E"], [0, 1, 2, 3, 4]]) -def test_mapping_consistency_restricted(names, star_connectivity): - connectivity = star_connectivity(names) - on_qubit = [names[0], names[2]] - restricted_connectivity = restrict_connectivity_qubits(connectivity, on_qubit) - assert_mapping_consistency(on_qubit, restricted_connectivity) - - -@pytest.mark.parametrize("names", [["A", "B", "C", "D", "E"], [0, 1, 2, 3, 4]]) -def test_mapping_consistency_restricted_error(names, star_connectivity): - connectivity = star_connectivity(names) - on_qubit = [names[0], names[2]] - restricted_connectivity = restrict_connectivity_qubits(connectivity, on_qubit) - with pytest.raises(PlacementError): - assert_mapping_consistency([names[3], names[4]], restricted_connectivity) - - def test_gates_qubits_pairs(): circuit = Circuit(5) circuit.add(gates.CNOT(0, 1)) @@ -152,7 +102,7 @@ def test_custom_error_circuit(star_connectivity): custom_layout = [4, 3, 2, 1, 0] connectivity = star_connectivity(names=custom_layout) placer = Custom(connectivity=connectivity, initial_map=custom_layout) - with pytest.raises(ValueError): + with pytest.raises(TranspilerPipelineError): placer(circuit) diff --git a/tests/test_transpiler_router.py b/tests/test_transpiler_router.py index 0b5e21bc40..2a09e23725 100644 --- a/tests/test_transpiler_router.py +++ b/tests/test_transpiler_router.py @@ -93,25 +93,6 @@ def matched_circuit(names): return circuit -def test_assert_connectivity(star_connectivity): - names = ["A", "B", "C", "D", "E"] - assert_connectivity(star_connectivity(names), matched_circuit(names)) - - -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) - - def test_bell_state_3q(): circuit = Circuit(3) circuit.add(gates.H(0)) @@ -407,7 +388,7 @@ def test_sabre_matched(names, star_connectivity): connectivity = star_connectivity(names=names) circuit = matched_circuit(names) original_circuit = circuit.copy() - placer = Trivial() + placer = Trivial(connectivity=connectivity) router = Sabre(connectivity=connectivity) placer(circuit) @@ -428,7 +409,7 @@ def test_sabre_simple(seed, star_connectivity): circ = Circuit(5) circ.add(gates.CZ(0, 1)) original_circuit = circ.copy() - placer = Trivial() + placer = Trivial(connectivity=connectivity) router = Sabre(connectivity=connectivity, seed=seed) placer(circ) @@ -506,10 +487,11 @@ def test_sabre_random_circuits_grid( def test_sabre_memory_map(star_connectivity): - placer = Trivial() + connectivity = star_connectivity() + placer = Trivial(connectivity=connectivity) layout_circ = Circuit(5) placer(layout_circ) - router = Sabre(connectivity=star_connectivity()) + router = Sabre(connectivity=connectivity) router._preprocessing(circuit=star_circuit()) router._memory_map = [[1, 0, 2, 3, 4]] value = router._compute_cost((0, 1)) @@ -554,7 +536,7 @@ def test_restrict_qubits(router_algorithm, star_connectivity): def test_star_error_multi_qubit(star_connectivity): - circuit = Circuit(3) + circuit = Circuit(5) circuit.add(gates.TOFFOLI(0, 1, 2)) connectivity = star_connectivity(middle_qubit_idx=2) transpiler = StarConnectivityRouter(connectivity) 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 c72b735c3f..f232dab702 100644 --- a/tests/test_transpiler_unroller.py +++ b/tests/test_transpiler_unroller.py @@ -39,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], diff --git a/tests/test_transpiler_utils.py b/tests/test_transpiler_utils.py new file mode 100644 index 0000000000..f804c4a8fd --- /dev/null +++ b/tests/test_transpiler_utils.py @@ -0,0 +1,164 @@ +import pytest + +from qibo import gates +from qibo.models.circuit import Circuit +from qibo.transpiler._exceptions import ( + ConnectivityError, + DecompositionError, + PlacementError, + TranspilerPipelineError, +) +from qibo.transpiler.pipeline import restrict_connectivity_qubits +from qibo.transpiler.unroller import NativeGates +from qibo.transpiler.utils import ( + assert_circuit_equivalence, + assert_connectivity, + assert_decomposition, + assert_mapping_consistency, + assert_placement, + assert_qubit_match, +) + + +def test_assert_qubit_match(star_connectivity): + circuit = Circuit(3) + with pytest.raises(TranspilerPipelineError): + assert_qubit_match(circuit, star_connectivity()) + + circuit = Circuit(5) + assert_qubit_match(circuit, star_connectivity()) + + +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) + + +@pytest.mark.parametrize("names", [["A", "B", "C", "D", "E"], [0, 1, 2, 3, 4]]) +def test_mapping_consistency(names, star_connectivity): + assert_mapping_consistency(names, star_connectivity(names)) + + +def test_mapping_consistency_error(star_connectivity): + with pytest.raises(PlacementError): + assert_mapping_consistency(["A", "B", "C", "D", "F"], star_connectivity()) + + +@pytest.mark.parametrize("names", [["A", "B", "C", "D", "E"], [0, 1, 2, 3, 4]]) +def test_mapping_consistency_restricted(names, star_connectivity): + connectivity = star_connectivity(names) + on_qubit = [names[0], names[2]] + restricted_connectivity = restrict_connectivity_qubits(connectivity, on_qubit) + assert_mapping_consistency(on_qubit, restricted_connectivity) + + +@pytest.mark.parametrize("names", [["A", "B", "C", "D", "E"], [0, 1, 2, 3, 4]]) +def test_mapping_consistency_restricted_error(names, star_connectivity): + connectivity = star_connectivity(names) + on_qubit = [names[0], names[2]] + restricted_connectivity = restrict_connectivity_qubits(connectivity, on_qubit) + with pytest.raises(PlacementError): + assert_mapping_consistency([names[3], names[4]], restricted_connectivity) + + +def test_assert_placement_true(star_connectivity): + circuit = Circuit(5) + assert_placement(circuit, connectivity=star_connectivity()) + + +@pytest.mark.parametrize( + "qubits, names", [(5, ["A", "B", "C", "D", "F"]), (3, ["A", "B", "C"])] +) +def test_assert_placement_false(qubits, names, star_connectivity): + connectivity = star_connectivity() + circuit = Circuit(qubits, wire_names=names) + with pytest.raises(PlacementError): + 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) + + +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) From 942991a746df7eb3ef3872a4d209a7bb596caccd Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Mon, 28 Oct 2024 14:21:46 +0400 Subject: [PATCH 20/34] fix: pandoc update result draw() --- doc/source/code-examples/advancedexamples.rst | 6 +++--- doc/source/code-examples/examples.rst | 20 +++++++++---------- src/qibo/gates/abstract.py | 8 ++++---- src/qibo/gates/measurements.py | 6 +++--- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/doc/source/code-examples/advancedexamples.rst b/doc/source/code-examples/advancedexamples.rst index e506bca6c3..cea5acf77c 100644 --- a/doc/source/code-examples/advancedexamples.rst +++ b/doc/source/code-examples/advancedexamples.rst @@ -1280,9 +1280,9 @@ Let's see how to use them. For starters, let's define a dummy circuit with some # visualize the circuit circ.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: diff --git a/doc/source/code-examples/examples.rst b/doc/source/code-examples/examples.rst index ca5bdc190c..83621a6383 100644 --- a/doc/source/code-examples/examples.rst +++ b/doc/source/code-examples/examples.rst @@ -313,20 +313,20 @@ For example c.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/gates/abstract.py b/src/qibo/gates/abstract.py index a2919c9f95..3b038f2e96 100644 --- a/src/qibo/gates/abstract.py +++ b/src/qibo/gates/abstract.py @@ -273,10 +273,10 @@ def on_qubits(self, qubit_map) -> "Gate": c.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 bc2c375dc3..b35b3f0f47 100644 --- a/src/qibo/gates/measurements.py +++ b/src/qibo/gates/measurements.py @@ -250,9 +250,9 @@ def on_qubits(self, qubit_map) -> "Gate": c.draw() .. testoutput:: - q0: ─M─ - q1: ─|─ - q2: ─M─ + 0: ─M─ + 1: ─|─ + 2: ─M─ """ qubits = (qubit_map.get(q) for q in self.qubits) From 25d87886c6a9bf6e66c45bedd82bd566c86e56c4 Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Mon, 28 Oct 2024 15:40:53 +0400 Subject: [PATCH 21/34] fix: pandoc update --- doc/source/code-examples/advancedexamples.rst | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/doc/source/code-examples/advancedexamples.rst b/doc/source/code-examples/advancedexamples.rst index cea5acf77c..684d3f92dd 100644 --- a/doc/source/code-examples/advancedexamples.rst +++ b/doc/source/code-examples/advancedexamples.rst @@ -1398,12 +1398,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─ . . @@ -2094,11 +2094,12 @@ 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.utils import assert_transpiling # Define connectivity as nx.Graph def star_connectivity(): From 93ac772c579f932e9d70fe7c79f4862b81da829f Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Mon, 28 Oct 2024 16:20:48 +0400 Subject: [PATCH 22/34] feat: Circuit __init__ int/list first arg init --- src/qibo/models/circuit.py | 31 ++++++++++++++++++++++++++++--- tests/test_models_circuit.py | 20 ++++++++++++++++++++ 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/src/qibo/models/circuit.py b/src/qibo/models/circuit.py index c4d3714820..457b4f8ac3 100644 --- a/src/qibo/models/circuit.py +++ b/src/qibo/models/circuit.py @@ -120,7 +120,7 @@ class Circuit: A specific backend has to be used for performing calculations. Args: - nqubits (int): Total number of qubits in the circuit. + nqubits (int, list): Number of qubits in the circuit or a list of wire names. init_kwargs (dict): a dictionary with the following keys - *nqubits* @@ -141,7 +141,7 @@ 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, optional): Names for qubit wires. + wire_names (list, optional): Names for qubit wire names. If ``None``, defaults to [``0``, ``1``, ..., ``nqubits - 1``]. If ``list`` is passed, length of ``list`` must match ``nqubits``. ndevices (int): Total number of devices. Defaults to ``None``. @@ -153,11 +153,12 @@ class Circuit: def __init__( self, - nqubits: int, + nqubits: Optional[Union[str, list]] = None, accelerators=None, density_matrix: bool = False, wire_names: Optional[list] = None, ): + nqubits, wire_names = self._parse(nqubits, wire_names) if not isinstance(nqubits, int): raise_error( TypeError, @@ -203,6 +204,30 @@ def __init__( ) self._distributed_init(nqubits, accelerators) + def _parse(self, qubit_spec, wire_names): + """Parse the input arguments for defining a circuit. Allows the user to initialize the circuit as follows: + + .. code-block:: python + c = Circuit(3) + c = Circuit(3, wire_names=["q0", "q1", "q2"]) + c = Circuit(["q0", "q1", "q2"]) + """ + if qubit_spec is None and wire_names is not None: + return len(wire_names), wire_names + if qubit_spec is not None and wire_names is None: + if isinstance(qubit_spec, int): + return qubit_spec, None + if isinstance(qubit_spec, list): + return len(qubit_spec), qubit_spec + if qubit_spec is not None and wire_names is not None: + if qubit_spec == len(wire_names): + return qubit_spec, wire_names + + raise_error( + ValueError, + "Number of qubits and wire names are not consistent.", + ) + def _distributed_init(self, nqubits, accelerators): # pragma: no cover """Distributed implementation of :class:`qibo.models.circuit.Circuit`. diff --git a/tests/test_models_circuit.py b/tests/test_models_circuit.py index f521446c26..1b4ee2ec2b 100644 --- a/tests/test_models_circuit.py +++ b/tests/test_models_circuit.py @@ -48,6 +48,26 @@ def test_circuit_init(): assert c.nqubits == 2 +def test_circuit_init_parse(): + 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_errors(): + 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) From 41f5eff1cc44ad8df1c6c9485eda96d638b4f557 Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Mon, 28 Oct 2024 16:46:59 +0400 Subject: [PATCH 23/34] fix: Circuit._parse -> _resolve_qubits --- src/qibo/models/circuit.py | 51 ++++++++++++++++++------------------ tests/test_models_circuit.py | 21 +++++++++++++-- 2 files changed, 45 insertions(+), 27 deletions(-) diff --git a/src/qibo/models/circuit.py b/src/qibo/models/circuit.py index 457b4f8ac3..08fc5822b4 100644 --- a/src/qibo/models/circuit.py +++ b/src/qibo/models/circuit.py @@ -158,7 +158,7 @@ def __init__( density_matrix: bool = False, wire_names: Optional[list] = None, ): - nqubits, wire_names = self._parse(nqubits, wire_names) + nqubits, wire_names = _resolve_qubits(nqubits, wire_names) if not isinstance(nqubits, int): raise_error( TypeError, @@ -204,30 +204,6 @@ def __init__( ) self._distributed_init(nqubits, accelerators) - def _parse(self, qubit_spec, wire_names): - """Parse the input arguments for defining a circuit. Allows the user to initialize the circuit as follows: - - .. code-block:: python - c = Circuit(3) - c = Circuit(3, wire_names=["q0", "q1", "q2"]) - c = Circuit(["q0", "q1", "q2"]) - """ - if qubit_spec is None and wire_names is not None: - return len(wire_names), wire_names - if qubit_spec is not None and wire_names is None: - if isinstance(qubit_spec, int): - return qubit_spec, None - if isinstance(qubit_spec, list): - return len(qubit_spec), qubit_spec - if qubit_spec is not None and wire_names is not None: - if qubit_spec == len(wire_names): - return qubit_spec, wire_names - - raise_error( - ValueError, - "Number of qubits and wire names are not consistent.", - ) - def _distributed_init(self, nqubits, accelerators): # pragma: no cover """Distributed implementation of :class:`qibo.models.circuit.Circuit`. @@ -1387,3 +1363,28 @@ 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: + + .. code-block:: python + c = Circuit(3) + c = Circuit(3, wire_names=["q0", "q1", "q2"]) + c = Circuit(["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): + return qubits, None + if isinstance(qubits, list): + return len(qubits), qubits + if qubits is not None and wire_names is not None: + if qubits == len(wire_names): + return qubits, wire_names + + raise_error( + ValueError, + "Number of qubits and wire names are not consistent.", + ) diff --git a/tests/test_models_circuit.py b/tests/test_models_circuit.py index 1b4ee2ec2b..b10fa71c5d 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,7 +49,23 @@ def test_circuit_init(): assert c.nqubits == 2 -def test_circuit_init_parse(): +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"] + + 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"]) @@ -59,7 +76,7 @@ def test_circuit_init_parse(): assert d.nqubits == 3 and d.wire_names == ["x", "y", "z"] -def test_circuit_init_errors(): +def test_circuit_init_resolve_qubits_err(): with pytest.raises(ValueError): a = Circuit() with pytest.raises(ValueError): From a4f2092708a9f15e066e28fdeb6da18eaaaa302f Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Wed, 30 Oct 2024 10:26:08 +0400 Subject: [PATCH 24/34] fix: minor updates / update docstring of qibo.Circuit --- doc/source/code-examples/advancedexamples.rst | 4 +-- src/qibo/models/circuit.py | 33 ++++++++++++++----- 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/doc/source/code-examples/advancedexamples.rst b/doc/source/code-examples/advancedexamples.rst index 684d3f92dd..e94faf9f58 100644 --- a/doc/source/code-examples/advancedexamples.rst +++ b/doc/source/code-examples/advancedexamples.rst @@ -2103,9 +2103,7 @@ Multiple transpilation steps can be implemented using the :class:`qibo.transpile # Define connectivity as nx.Graph def star_connectivity(): - chip = nx.Graph() - chip.add_nodes_from(["q0", "q1", "q2", "q3", "q4"]) - chip.add_edges_from([("q0", "q2"), ("q1", "q2"), ("q2", "q3"), ("q2", "q4")]) + chip = nx.Graph([("q0", "q2"), ("q1", "q2"), ("q2", "q3"), ("q2", "q4")]) return chip # Define the circuit diff --git a/src/qibo/models/circuit.py b/src/qibo/models/circuit.py index 08fc5822b4..29d44e6d0f 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, list): Number of qubits in the circuit or a list of wire names. + 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,9 +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, optional): Names for qubit wire names. - If ``None``, defaults to [``0``, ``1``, ..., ``nqubits - 1``]. - If ``list`` is passed, length of ``list`` must match ``nqubits``. 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``. @@ -385,7 +397,7 @@ 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 list(sorted(qubits))] + kwargs["wire_names"] = [self.wire_names[q] for q in sorted(qubits)] circuit = self.__class__(**kwargs) circuit.add(gate.on_qubits(qubit_map) for gate in reversed(list_of_gates)) return circuit, qubit_map @@ -1368,10 +1380,13 @@ def draw(self, line_wrap: int = 70, legend: bool = False): def _resolve_qubits(qubits, wire_names): """Parse the input arguments for defining a circuit. Allows the user to initialize the circuit as follows: - .. code-block:: python - c = Circuit(3) - c = Circuit(3, wire_names=["q0", "q1", "q2"]) - c = Circuit(["q0", "q1", "q2"]) + 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 From 10be459c7ee5bd681a2403ef25d735a2987e04d6 Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Wed, 30 Oct 2024 10:47:44 +0400 Subject: [PATCH 25/34] fix: update test files _resolve_qubits / wire_names setter --- src/qibo/models/circuit.py | 19 +++++-------------- tests/test_models_circuit.py | 6 ++++-- 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/src/qibo/models/circuit.py b/src/qibo/models/circuit.py index 29d44e6d0f..042b31632e 100644 --- a/src/qibo/models/circuit.py +++ b/src/qibo/models/circuit.py @@ -171,16 +171,6 @@ def __init__( wire_names: Optional[list] = None, ): nqubits, wire_names = _resolve_qubits(nqubits, wire_names) - 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}.", - ) self.nqubits = nqubits self.init_kwargs = { "nqubits": nqubits, @@ -1391,15 +1381,16 @@ def _resolve_qubits(qubits, wire_names): 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): + 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 qubits == len(wire_names): - return qubits, wire_names + if isinstance(qubits, int) and isinstance(wire_names, list): + if qubits == len(wire_names): + return qubits, wire_names raise_error( ValueError, - "Number of qubits and wire names are not consistent.", + "Invalid input arguments for defining a circuit.", ) diff --git a/tests/test_models_circuit.py b/tests/test_models_circuit.py index b10fa71c5d..b2b2ca904d 100644 --- a/tests/test_models_circuit.py +++ b/tests/test_models_circuit.py @@ -56,6 +56,8 @@ def test_resolve_qubits(): 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) @@ -683,9 +685,9 @@ def test_circuit_wire_names(): assert circuit._wire_names == ["a", "b", "c", "d", "e"] with pytest.raises(TypeError): - circuit = Circuit(5, wire_names=1) + circuit.wire_names = 5 with pytest.raises(ValueError): - circuit = Circuit(5, wire_names=["a", "b", "c"]) + circuit.wire_names = ["a", "b", "c", "d"] def test_circuit_draw_wire_names(): From a1d3302ffd583c913bff6ac5460d85fee0769faa Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Wed, 30 Oct 2024 11:23:03 +0400 Subject: [PATCH 26/34] fix: combine similar assert func to assert_placement --- src/qibo/transpiler/placer.py | 15 ++++---- src/qibo/transpiler/router.py | 8 ++--- src/qibo/transpiler/utils.py | 49 +++++--------------------- tests/test_transpiler_utils.py | 63 +++++++++++++--------------------- 4 files changed, 43 insertions(+), 92 deletions(-) diff --git a/src/qibo/transpiler/placer.py b/src/qibo/transpiler/placer.py index 64826946c1..4601fadf7d 100644 --- a/src/qibo/transpiler/placer.py +++ b/src/qibo/transpiler/placer.py @@ -9,7 +9,7 @@ from qibo.transpiler._exceptions import PlacementError from qibo.transpiler.abstract import Placer, Router from qibo.transpiler.router import _find_connected_qubit -from qibo.transpiler.utils import assert_placement, assert_qubit_match +from qibo.transpiler.utils import assert_placement def _find_gates_qubits_pairs(circuit: Circuit): @@ -66,7 +66,7 @@ def __call__(self, circuit: Circuit): 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. """ - assert_qubit_match(circuit, self.connectivity) + assert_placement(circuit, self.connectivity) middle_qubit_idx = circuit.wire_names.index(self.middle_qubit) wire_names = circuit.wire_names.copy() @@ -109,7 +109,7 @@ def __call__(self, circuit: Circuit = None): Args: circuit (:class:`qibo.models.circuit.Circuit`): circuit to be transpiled. """ - assert_qubit_match(circuit, self.connectivity) + assert_placement(circuit, self.connectivity) return @@ -132,7 +132,8 @@ def __call__(self, circuit: Circuit): Args: circuit (:class:`qibo.models.circuit.Circuit`): circuit to be transpiled. """ - assert_qubit_match(circuit, self.connectivity) + assert_placement(circuit, self.connectivity) + if isinstance(self.initial_map, dict): circuit.wire_names = sorted(self.initial_map, key=self.initial_map.get) elif isinstance(self.initial_map, list): @@ -164,7 +165,7 @@ def __call__(self, circuit: Circuit): Args: circuit (:class:`qibo.models.circuit.Circuit`): circuit to be transpiled. """ - assert_qubit_match(circuit, self.connectivity) + assert_placement(circuit, self.connectivity) gates_qubits_pairs = _find_gates_qubits_pairs(circuit) if len(gates_qubits_pairs) < 3: raise_error( @@ -223,7 +224,7 @@ def __call__(self, circuit): Args: circuit (:class:`qibo.models.circuit.Circuit`): Circuit to be transpiled. """ - assert_qubit_match(circuit, self.connectivity) + 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() @@ -309,7 +310,7 @@ def __call__(self, circuit: Circuit): Args: circuit (:class:`qibo.models.circuit.Circuit`): circuit to be transpiled. """ - assert_qubit_match(circuit, self.connectivity) + assert_placement(circuit, self.connectivity) self.routing_algorithm.connectivity = self.connectivity new_circuit = self._assemble_circuit(circuit) self._routing_step(new_circuit) diff --git a/src/qibo/transpiler/router.py b/src/qibo/transpiler/router.py index 3a5505a51f..d4a782740b 100644 --- a/src/qibo/transpiler/router.py +++ b/src/qibo/transpiler/router.py @@ -11,7 +11,7 @@ from qibo.transpiler._exceptions import ConnectivityError from qibo.transpiler.abstract import Router from qibo.transpiler.blocks import Block, CircuitBlocks -from qibo.transpiler.utils import assert_qubit_match +from qibo.transpiler.utils import assert_placement class StarConnectivityRouter(Router): @@ -49,7 +49,7 @@ def __call__(self, circuit: Circuit): 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. """ - assert_qubit_match(circuit, self.connectivity) + assert_placement(circuit, self.connectivity) middle_qubit_idx = circuit.wire_names.index(self.middle_qubit) nqubits = circuit.nqubits new = Circuit(nqubits=nqubits, wire_names=circuit.wire_names) @@ -335,7 +335,7 @@ def __call__(self, circuit: Circuit): (:class:`qibo.models.circuit.Circuit`, dict): circut mapped to hardware topology, and final physical-to-logical qubit mapping. """ - assert_qubit_match(circuit, self.connectivity) + assert_placement(circuit, self.connectivity) self._preprocessing(circuit=circuit) while self._dag.number_of_nodes() != 0: execute_block_list = self._check_execution() @@ -626,7 +626,7 @@ def __call__(self, circuit: Circuit): Returns: (:class:`qibo.models.circuit.Circuit`, dict): routed circuit and final layout. """ - assert_qubit_match(circuit, self.connectivity) + assert_placement(circuit, self.connectivity) self._preprocessing(circuit=circuit) longest_path = np.max(self._dist_matrix) diff --git a/src/qibo/transpiler/utils.py b/src/qibo/transpiler/utils.py index 7325db8f81..89bd367d79 100644 --- a/src/qibo/transpiler/utils.py +++ b/src/qibo/transpiler/utils.py @@ -18,20 +18,6 @@ from qibo.transpiler.unroller import NativeGates -def assert_qubit_match(circuit: Circuit, connectivity: nx.Graph): - """Check if the number of qubits in the circuit matches the connectivity graph. - - Args: - circuit (:class:`qibo.models.circuit.Circuit`): Circuit model to check. - connectivity (:class:`networkx.Graph`): Chip connectivity. - """ - if circuit.nqubits != len(connectivity.nodes): - raise_error( - TranspilerPipelineError, - "Number of qubits in the circuit does not match the connectivity graph.", - ) - - def assert_transpiling( original_circuit: Circuit, transpiled_circuit: Circuit, @@ -130,41 +116,22 @@ def _transpose_qubits(state: np.ndarray, qubits_ordering: np.ndarray): def assert_placement(circuit: Circuit, connectivity: nx.Graph): - """Check if layout is in the correct form and matches the number of qubits of the circuit. + """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. - layout (dict): qubit names. 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``. """ - layout = circuit.wire_names - assert_mapping_consistency(layout=layout, connectivity=connectivity) - if circuit.nqubits > len(layout): + if circuit.nqubits != len(circuit.wire_names) or circuit.nqubits != len( + connectivity.nodes + ): raise_error( PlacementError, - "Layout can't be used on circuit. The circuit requires more qubits.", + 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 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: list, connectivity: nx.Graph): - """Check if layout is in the correct form. - - Args: - layout (dict): qubit names. - 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. - """ - nodes = list(connectivity.nodes) - if sorted(nodes) != sorted(layout): + if set(circuit.wire_names) != set(connectivity.nodes): raise_error( PlacementError, "Some physical qubits in the layout may be missing or duplicated.", diff --git a/tests/test_transpiler_utils.py b/tests/test_transpiler_utils.py index f804c4a8fd..02a1e08670 100644 --- a/tests/test_transpiler_utils.py +++ b/tests/test_transpiler_utils.py @@ -14,21 +14,10 @@ assert_circuit_equivalence, assert_connectivity, assert_decomposition, - assert_mapping_consistency, assert_placement, - assert_qubit_match, ) -def test_assert_qubit_match(star_connectivity): - circuit = Circuit(3) - with pytest.raises(TranspilerPipelineError): - assert_qubit_match(circuit, star_connectivity()) - - circuit = Circuit(5) - assert_qubit_match(circuit, star_connectivity()) - - def test_assert_circuit_equivalence_equal(): circ1 = Circuit(2) circ2 = Circuit(2) @@ -61,44 +50,19 @@ def test_assert_circuit_equivalence_false(): assert_circuit_equivalence(circ1, circ2, final_map=final_map) -@pytest.mark.parametrize("names", [["A", "B", "C", "D", "E"], [0, 1, 2, 3, 4]]) -def test_mapping_consistency(names, star_connectivity): - assert_mapping_consistency(names, star_connectivity(names)) - - -def test_mapping_consistency_error(star_connectivity): - with pytest.raises(PlacementError): - assert_mapping_consistency(["A", "B", "C", "D", "F"], star_connectivity()) - - -@pytest.mark.parametrize("names", [["A", "B", "C", "D", "E"], [0, 1, 2, 3, 4]]) -def test_mapping_consistency_restricted(names, star_connectivity): - connectivity = star_connectivity(names) - on_qubit = [names[0], names[2]] - restricted_connectivity = restrict_connectivity_qubits(connectivity, on_qubit) - assert_mapping_consistency(on_qubit, restricted_connectivity) - - -@pytest.mark.parametrize("names", [["A", "B", "C", "D", "E"], [0, 1, 2, 3, 4]]) -def test_mapping_consistency_restricted_error(names, star_connectivity): - connectivity = star_connectivity(names) - on_qubit = [names[0], names[2]] - restricted_connectivity = restrict_connectivity_qubits(connectivity, on_qubit) - with pytest.raises(PlacementError): - assert_mapping_consistency([names[3], names[4]], restricted_connectivity) - - def test_assert_placement_true(star_connectivity): circuit = Circuit(5) assert_placement(circuit, connectivity=star_connectivity()) @pytest.mark.parametrize( - "qubits, names", [(5, ["A", "B", "C", "D", "F"]), (3, ["A", "B", "C"])] + "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(qubits, wire_names=names) + circuit = Circuit(5) + circuit.nqubits = qubits + circuit._wire_names = names with pytest.raises(PlacementError): assert_placement(circuit, connectivity) @@ -111,6 +75,25 @@ def test_assert_placement_error(qubits, star_connectivity): 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)) From 0851eeb2fa846848cff4507c37930c3f24489c76 Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Wed, 30 Oct 2024 11:32:48 +0400 Subject: [PATCH 27/34] fix: minor test files update --- tests/test_transpiler_placer.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/tests/test_transpiler_placer.py b/tests/test_transpiler_placer.py index 1b05743621..4117c6bc49 100644 --- a/tests/test_transpiler_placer.py +++ b/tests/test_transpiler_placer.py @@ -18,7 +18,7 @@ from qibo.transpiler.utils import assert_placement -def star_circuit(names=["q0", "q1", "q2", "q3", "q4"]): +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)) @@ -67,8 +67,9 @@ def test_trivial_restricted(star_connectivity): [["E", "D", "C", "B", "A"], {"E": 0, "D": 1, "C": 2, "B": 3, "A": 4}], ) def test_custom(custom_layout, star_connectivity): - circuit = Circuit(5) - connectivity = star_connectivity(["A", "B", "C", "D", "E"]) + names = ["A", "B", "C", "D", "E"] + circuit = Circuit(5, wire_names=names) + connectivity = star_connectivity(names) placer = Custom(connectivity=connectivity, initial_map=custom_layout) placer(circuit) assert circuit.wire_names == ["E", "D", "C", "B", "A"] @@ -102,7 +103,7 @@ def test_custom_error_circuit(star_connectivity): custom_layout = [4, 3, 2, 1, 0] connectivity = star_connectivity(names=custom_layout) placer = Custom(connectivity=connectivity, initial_map=custom_layout) - with pytest.raises(TranspilerPipelineError): + with pytest.raises(PlacementError): placer(circuit) @@ -154,7 +155,7 @@ def test_subgraph_error(star_connectivity): def test_subgraph_restricted(star_connectivity): - circuit = Circuit(4) + 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)) @@ -180,7 +181,7 @@ def test_random(reps, names, star_connectivity): def test_random_restricted(star_connectivity): names = [0, 1, 2, 3, 4] - circuit = Circuit(4, wire_names=names[: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)) From 824bb76ee3fd4c612cc0e9f078d38ebcb9cb56ee Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Thu, 21 Nov 2024 17:47:11 +0400 Subject: [PATCH 28/34] passes move connectivity check to __call__ --- src/qibo/backends/__init__.py | 6 ++-- src/qibo/models/error_mitigation.py | 2 +- src/qibo/tomography/gate_set_tomography.py | 13 ++----- src/qibo/transpiler/optimizer.py | 10 +++++- src/qibo/transpiler/placer.py | 36 ++++++++++++-------- src/qibo/transpiler/router.py | 26 ++++++++------ src/qibo/transpiler/utils.py | 6 ++++ tests/test_backends_global.py | 6 ++-- tests/test_tomography_gate_set_tomography.py | 6 ++-- tests/test_transpiler_optimizer.py | 9 +++++ tests/test_transpiler_pipeline.py | 24 ++++++------- tests/test_transpiler_placer.py | 7 ++-- tests/test_transpiler_router.py | 3 +- tests/test_transpiler_utils.py | 4 +++ 14 files changed, 94 insertions(+), 64 deletions(-) diff --git a/src/qibo/backends/__init__.py b/src/qibo/backends/__init__.py index f0777b24e1..c4c52c58af 100644 --- a/src/qibo/backends/__init__.py +++ b/src/qibo/backends/__init__.py @@ -151,9 +151,9 @@ def _default_transpiler(cls): return Passes( connectivity=connectivity, passes=[ - Preprocessing(connectivity), - Trivial(connectivity), - Sabre(connectivity), + Preprocessing(), + Trivial(), + Sabre(), Unroller(NativeGates[natives]), ], ) diff --git a/src/qibo/models/error_mitigation.py b/src/qibo/models/error_mitigation.py index 9dce0017ff..2a31ca2571 100644 --- a/src/qibo/models/error_mitigation.py +++ b/src/qibo/models/error_mitigation.py @@ -1183,7 +1183,7 @@ def _execute_circuit(circuit, qubit_map, noise_model=None, nshots=10000, backend connectivity = nx.Graph(connectivity_edges) transpiler = Passes( connectivity=connectivity, - passes=[Custom(initial_map=qubit_map, connectivity=connectivity)], + passes=[Custom(initial_map=qubit_map)], ) circuit, _ = transpiler(circuit) elif noise_model is not None: 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/optimizer.py b/src/qibo/transpiler/optimizer.py index a30f117757..3b93e5843a 100644 --- a/src/qibo/transpiler/optimizer.py +++ b/src/qibo/transpiler/optimizer.py @@ -13,10 +13,18 @@ class Preprocessing(Optimizer): connectivity (:class:`networkx.Graph`): hardware chip connectivity. """ - def __init__(self, connectivity: nx.Graph): + def __init__(self, connectivity: nx.Graph = None): self.connectivity = connectivity def __call__(self, circuit: Circuit) -> Circuit: + if self.connectivity is None or 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: diff --git a/src/qibo/transpiler/placer.py b/src/qibo/transpiler/placer.py index 4601fadf7d..e162cf80aa 100644 --- a/src/qibo/transpiler/placer.py +++ b/src/qibo/transpiler/placer.py @@ -48,16 +48,9 @@ class StarConnectivityPlacer(Placer): connectivity (:class:`networkx.Graph`): star connectivity graph. """ - def __init__(self, connectivity: nx.Graph): + def __init__(self, connectivity: nx.Graph = None): self.connectivity = connectivity - 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.", - ) + self.middle_qubit = None def __call__(self, circuit: Circuit): """Apply the transpiler transformation on a given circuit. @@ -67,6 +60,8 @@ def __call__(self, circuit: Circuit): Only single qubit gates and two qubits gates are supported by the router. """ assert_placement(circuit, self.connectivity) + self._check_star_connectivity() + middle_qubit_idx = circuit.wire_names.index(self.middle_qubit) wire_names = circuit.wire_names.copy() @@ -96,6 +91,17 @@ def __call__(self, circuit: Circuit): circuit.wire_names = wire_names + 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.", + ) + class Trivial(Placer): """Place qubits according to the order of the qubit names that the user provides.""" @@ -103,7 +109,7 @@ class Trivial(Placer): def __init__(self, connectivity: nx.Graph = None): self.connectivity = connectivity - def __call__(self, circuit: Circuit = None): + def __call__(self, circuit: Circuit): """Find the trivial placement for the circuit. Args: @@ -122,7 +128,7 @@ class Custom(Placer): - If **list**, it should contain physical qubit names, arranged in the order of the logical qubits. """ - def __init__(self, initial_map: Union[list, dict], connectivity: nx.Graph): + def __init__(self, initial_map: Union[list, dict], connectivity: nx.Graph = None): self.initial_map = initial_map self.connectivity = connectivity @@ -141,7 +147,7 @@ def __call__(self, circuit: Circuit): else: raise_error(TypeError, "Use dict or list to define mapping.") - assert_placement(circuit, connectivity=self.connectivity) + assert_placement(circuit, self.connectivity) class Subgraph(Placer): @@ -155,7 +161,7 @@ class Subgraph(Placer): connectivity (:class:`networkx.Graph`): chip connectivity. """ - def __init__(self, connectivity: nx.Graph): + def __init__(self, connectivity: nx.Graph = None): self.connectivity = connectivity def __call__(self, circuit: Circuit): @@ -213,7 +219,7 @@ class Random(Placer): initializes a generator with a random seed. Defaults to ``None``. """ - def __init__(self, connectivity: nx.Graph, samples: int = 100, seed=None): + def __init__(self, connectivity: nx.Graph = None, samples: int = 100, seed=None): self.connectivity = connectivity self.samples = samples self.seed = seed @@ -296,8 +302,8 @@ class ReverseTraversal(Placer): def __init__( self, - connectivity: nx.Graph, routing_algorithm: Router, + connectivity: nx.Graph = None, depth: Optional[int] = None, ): self.connectivity = connectivity diff --git a/src/qibo/transpiler/router.py b/src/qibo/transpiler/router.py index d4a782740b..027b17119b 100644 --- a/src/qibo/transpiler/router.py +++ b/src/qibo/transpiler/router.py @@ -33,14 +33,7 @@ class StarConnectivityRouter(Router): def __init__(self, connectivity: nx.Graph): self.connectivity = connectivity - 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.", - ) + self.middle_qubit = None def __call__(self, circuit: Circuit): """Apply the transpiler transformation on a given circuit. @@ -49,7 +42,9 @@ def __call__(self, circuit: Circuit): 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. """ + self._check_star_connectivity() assert_placement(circuit, self.connectivity) + middle_qubit_idx = circuit.wire_names.index(self.middle_qubit) nqubits = circuit.nqubits new = Circuit(nqubits=nqubits, wire_names=circuit.wire_names) @@ -94,6 +89,17 @@ def __call__(self, circuit: Circuit): 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, error, mapping): """Helper method for :meth:`qibo.transpiler.router.StarConnectivityRouter` @@ -308,7 +314,7 @@ 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: nx.Graph = None, seed: Optional[int] = None): self.connectivity = connectivity self._front_layer = None self.circuit_map = None @@ -595,7 +601,7 @@ class Sabre(Router): def __init__( self, - connectivity: nx.Graph, + connectivity: nx.Graph = None, lookahead: int = 2, decay_lookahead: float = 0.6, delta: float = 0.001, diff --git a/src/qibo/transpiler/utils.py b/src/qibo/transpiler/utils.py index 89bd367d79..d988ce6add 100644 --- a/src/qibo/transpiler/utils.py +++ b/src/qibo/transpiler/utils.py @@ -122,6 +122,12 @@ def assert_placement(circuit: Circuit, connectivity: nx.Graph): 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 ): diff --git a/tests/test_backends_global.py b/tests/test_backends_global.py index 8e4e07610d..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()), ], ) diff --git a/tests/test_tomography_gate_set_tomography.py b/tests/test_tomography_gate_set_tomography.py index c2a40d944f..be0c4d4fc6 100644 --- a/tests/test_tomography_gate_set_tomography.py +++ b/tests/test_tomography_gate_set_tomography.py @@ -280,9 +280,9 @@ def test_GST_with_transpiler(backend, star_connectivity): transpiler = Passes( connectivity=connectivity, passes=[ - Preprocessing(connectivity), - Random(connectivity), - Sabre(connectivity), + Preprocessing(), + Random(), + Sabre(), Unroller(NativeGates.default(), backend=backend), ], ) diff --git a/tests/test_transpiler_optimizer.py b/tests/test_transpiler_optimizer.py index cd55115a9b..eefcd2c1fb 100644 --- a/tests/test_transpiler_optimizer.py +++ b/tests/test_transpiler_optimizer.py @@ -8,10 +8,19 @@ def test_preprocessing_error(star_connectivity): circ = Circuit(7) + + preprocesser = Preprocessing() + with pytest.raises(ValueError): + new_circuit = preprocesser(circuit=circ) + 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(star_connectivity): circ = Circuit(5) diff --git a/tests/test_transpiler_pipeline.py b/tests/test_transpiler_pipeline.py index 30f0cddfbe..61f843f7e9 100644 --- a/tests/test_transpiler_pipeline.py +++ b/tests/test_transpiler_pipeline.py @@ -140,17 +140,16 @@ 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=connectivity)) + custom_passes.append(Preprocessing()) if placer == ReverseTraversal: custom_passes.append( placer( - connectivity=connectivity, - routing_algorithm=router(connectivity=connectivity), + routing_algorithm=router(), ) ) else: - custom_passes.append(placer(connectivity=connectivity)) - custom_passes.append(router(connectivity=connectivity)) + custom_passes.append(placer()) + custom_passes.append(router()) custom_passes.append(Unroller(native_gates=NativeGates.default())) custom_pipeline = Passes( custom_passes, @@ -177,17 +176,16 @@ def test_custom_passes_restrict( connectivity = star_connectivity() circ = generate_random_circuit(nqubits=3, ngates=ngates, names=restrict_names) custom_passes = [] - custom_passes.append(Preprocessing(connectivity=connectivity)) + custom_passes.append(Preprocessing()) if placer == ReverseTraversal: custom_passes.append( placer( - connectivity=connectivity, - routing_algorithm=routing(connectivity=connectivity), + routing_algorithm=routing(), ) ) else: - custom_passes.append(placer(connectivity=connectivity)) - custom_passes.append(routing(connectivity=connectivity)) + custom_passes.append(placer()) + custom_passes.append(routing()) custom_passes.append(Unroller(native_gates=NativeGates.default())) custom_pipeline = Passes( custom_passes, @@ -220,9 +218,9 @@ def test_int_qubit_names(star_connectivity): transpiler = Passes( connectivity=connectivity, passes=[ - Preprocessing(connectivity), - Random(connectivity, seed=0), - Sabre(connectivity), + Preprocessing(), + Random(seed=0), + Sabre(), Unroller(NativeGates.default()), ], ) diff --git a/tests/test_transpiler_placer.py b/tests/test_transpiler_placer.py index 4117c6bc49..1e8193ec2a 100644 --- a/tests/test_transpiler_placer.py +++ b/tests/test_transpiler_placer.py @@ -201,7 +201,7 @@ 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=ngates) + placer = ReverseTraversal(routing, connectivity, depth=ngates) placer(circuit) assert_placement(circuit, connectivity) @@ -209,7 +209,7 @@ def test_reverse_traversal(ngates, names, star_connectivity): 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): placer(circuit) @@ -261,4 +261,5 @@ def test_star_connectivity_placer_error(first, star_connectivity): chip = nx.Graph() chip.add_edges_from([(0, 1), (1, 2), (2, 3), (3, 4)]) with pytest.raises(ValueError): - StarConnectivityPlacer(chip) + placer = StarConnectivityPlacer(chip) + placer(circ) diff --git a/tests/test_transpiler_router.py b/tests/test_transpiler_router.py index 2a09e23725..5883d22abe 100644 --- a/tests/test_transpiler_router.py +++ b/tests/test_transpiler_router.py @@ -546,7 +546,8 @@ def test_star_error_multi_qubit(star_connectivity): chip = nx.Graph() chip.add_edges_from([(0, 1), (1, 2), (2, 3), (3, 4)]) with pytest.raises(ValueError): - StarConnectivityRouter(chip) + router = StarConnectivityRouter(chip) + _, _ = router(circuit=circuit) # @pytest.mark.parametrize("nqubits", [1, 3, 5]) diff --git a/tests/test_transpiler_utils.py b/tests/test_transpiler_utils.py index 02a1e08670..7476e1d80f 100644 --- a/tests/test_transpiler_utils.py +++ b/tests/test_transpiler_utils.py @@ -66,6 +66,10 @@ def test_assert_placement_false(qubits, names, star_connectivity): 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): From 214a4ea910dce6ba99132744c4122378b903ed87 Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Fri, 22 Nov 2024 14:02:23 +0400 Subject: [PATCH 29/34] fix: remove Trivial and Custom --- doc/source/code-examples/advancedexamples.rst | 2 - src/qibo/backends/__init__.py | 2 - src/qibo/models/error_mitigation.py | 9 +-- src/qibo/transpiler/__init__.py | 2 - src/qibo/transpiler/placer.py | 49 +----------- tests/test_transpiler_pipeline.py | 6 +- tests/test_transpiler_placer.py | 76 ------------------- tests/test_transpiler_router.py | 54 ++++--------- 8 files changed, 19 insertions(+), 181 deletions(-) diff --git a/doc/source/code-examples/advancedexamples.rst b/doc/source/code-examples/advancedexamples.rst index 39737ec13c..9904a4ab1e 100644 --- a/doc/source/code-examples/advancedexamples.rst +++ b/doc/source/code-examples/advancedexamples.rst @@ -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. diff --git a/src/qibo/backends/__init__.py b/src/qibo/backends/__init__.py index c4c52c58af..5d2d5deb08 100644 --- a/src/qibo/backends/__init__.py +++ b/src/qibo/backends/__init__.py @@ -135,7 +135,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 @@ -152,7 +151,6 @@ def _default_transpiler(cls): connectivity=connectivity, passes=[ Preprocessing(), - Trivial(), Sabre(), Unroller(NativeGates[natives]), ], diff --git a/src/qibo/models/error_mitigation.py b/src/qibo/models/error_mitigation.py index 2a31ca2571..ac876e84ea 100644 --- a/src/qibo/models/error_mitigation.py +++ b/src/qibo/models/error_mitigation.py @@ -1174,18 +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 - connectivity_edges = backend.connectivity - connectivity = nx.Graph(connectivity_edges) - transpiler = Passes( - connectivity=connectivity, - passes=[Custom(initial_map=qubit_map)], - ) - circuit, _ = transpiler(circuit) + circuit.wire_names = qubit_map elif noise_model is not None: circuit = noise_model.apply(circuit) 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/placer.py b/src/qibo/transpiler/placer.py index e162cf80aa..4539272574 100644 --- a/src/qibo/transpiler/placer.py +++ b/src/qibo/transpiler/placer.py @@ -1,4 +1,4 @@ -from typing import Optional, Union +from typing import Optional import networkx as nx @@ -103,53 +103,6 @@ def _check_star_connectivity(self): ) -class Trivial(Placer): - """Place qubits according to the order of the qubit names that the user provides.""" - - 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. - """ - assert_placement(circuit, self.connectivity) - return - - -class Custom(Placer): - """Define a custom initial qubit mapping. - - Args: - map (list or dict): A mapping between physical and logical qubits. - - If **dict**, the keys should be physical qubit names, and the values should be the corresponding logical qubit numbers. - - If **list**, it should contain physical qubit names, arranged in the order of the logical qubits. - """ - - def __init__(self, initial_map: Union[list, dict], connectivity: nx.Graph = None): - self.initial_map = initial_map - self.connectivity = connectivity - - def __call__(self, circuit: Circuit): - """Apply the custom placement to the given circuit. - - Args: - circuit (:class:`qibo.models.circuit.Circuit`): circuit to be transpiled. - """ - assert_placement(circuit, self.connectivity) - - if isinstance(self.initial_map, dict): - circuit.wire_names = sorted(self.initial_map, key=self.initial_map.get) - elif isinstance(self.initial_map, list): - circuit.wire_names = self.initial_map - else: - raise_error(TypeError, "Use dict or list to define mapping.") - - assert_placement(circuit, self.connectivity) - - class Subgraph(Placer): """ Subgraph isomorphism qubit placer. diff --git a/tests/test_transpiler_pipeline.py b/tests/test_transpiler_pipeline.py index 61f843f7e9..b43129a0eb 100644 --- a/tests/test_transpiler_pipeline.py +++ b/tests/test_transpiler_pipeline.py @@ -7,7 +7,7 @@ from qibo.transpiler._exceptions import ConnectivityError, TranspilerPipelineError from qibo.transpiler.optimizer import Preprocessing from qibo.transpiler.pipeline import Passes, restrict_connectivity_qubits -from qibo.transpiler.placer import Random, ReverseTraversal, Trivial +from qibo.transpiler.placer import Random, ReverseTraversal from qibo.transpiler.router import Sabre, ShortestPaths from qibo.transpiler.unroller import NativeGates, Unroller from qibo.transpiler.utils import assert_circuit_equivalence, assert_transpiling @@ -134,7 +134,7 @@ def test_is_satisfied_false_connectivity(star_connectivity): @pytest.mark.parametrize("nqubits", [2, 3, 5]) @pytest.mark.parametrize("ngates", [5, 20]) -@pytest.mark.parametrize("placer", [Random, Trivial, ReverseTraversal]) +@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() @@ -167,7 +167,7 @@ def test_custom_passes(placer, router, ngates, nqubits, star_connectivity): @pytest.mark.parametrize("ngates", [5, 20]) -@pytest.mark.parametrize("placer", [Random, Trivial, ReverseTraversal]) +@pytest.mark.parametrize("placer", [Random, ReverseTraversal]) @pytest.mark.parametrize("routing", [ShortestPaths, Sabre]) @pytest.mark.parametrize("restrict_names", [[1, 2, 3], [0, 2, 4], [4, 2, 3]]) def test_custom_passes_restrict( diff --git a/tests/test_transpiler_placer.py b/tests/test_transpiler_placer.py index 1e8193ec2a..f9d41ecc55 100644 --- a/tests/test_transpiler_placer.py +++ b/tests/test_transpiler_placer.py @@ -6,12 +6,10 @@ from qibo.transpiler._exceptions import PlacementError, TranspilerPipelineError from qibo.transpiler.pipeline import restrict_connectivity_qubits from qibo.transpiler.placer import ( - Custom, Random, ReverseTraversal, StarConnectivityPlacer, Subgraph, - Trivial, _find_gates_qubits_pairs, ) from qibo.transpiler.router import ShortestPaths @@ -41,80 +39,6 @@ def test_gates_qubits_pairs_error(): gates_qubits_pairs = _find_gates_qubits_pairs(circuit) -def test_trivial(star_connectivity): - names = ["q4", "q3", "q2", "q1", "q0"] - circuit = Circuit(5, wire_names=names) - connectivity = star_connectivity(names) - placer = Trivial(connectivity=connectivity) - placer(circuit) - assert circuit.wire_names == names - assert_placement(circuit, connectivity) - - -def test_trivial_restricted(star_connectivity): - names = ["q0", "q2"] - circuit = Circuit(2, wire_names=names) - connectivity = star_connectivity(["q0", "q1", "q2", "q3", "q4"]) - restricted_connectivity = restrict_connectivity_qubits(connectivity, names) - placer = Trivial(connectivity=restricted_connectivity) - placer(circuit) - assert circuit.wire_names == names - assert_placement(circuit, restricted_connectivity) - - -@pytest.mark.parametrize( - "custom_layout", - [["E", "D", "C", "B", "A"], {"E": 0, "D": 1, "C": 2, "B": 3, "A": 4}], -) -def test_custom(custom_layout, star_connectivity): - names = ["A", "B", "C", "D", "E"] - circuit = Circuit(5, wire_names=names) - connectivity = star_connectivity(names) - placer = Custom(connectivity=connectivity, initial_map=custom_layout) - placer(circuit) - assert circuit.wire_names == ["E", "D", "C", "B", "A"] - - -@pytest.mark.parametrize( - "custom_layout", [[4, 3, 2, 1, 0], {4: 0, 3: 1, 2: 2, 1: 3, 0: 4}] -) -def test_custom_int(custom_layout, star_connectivity): - names = [0, 1, 2, 3, 4] - circuit = Circuit(5, wire_names=names) - connectivity = star_connectivity(names) - placer = Custom(connectivity=connectivity, initial_map=custom_layout) - placer(circuit) - assert circuit.wire_names == [4, 3, 2, 1, 0] - - -@pytest.mark.parametrize("custom_layout", [["D", "C"], {"C": 1, "D": 0}]) -def test_custom_restricted(custom_layout, star_connectivity): - circuit = Circuit(2, wire_names=["C", "D"]) - connectivity = star_connectivity(["A", "B", "C", "D", "E"]) - restricted_connectivity = restrict_connectivity_qubits(connectivity, ["C", "D"]) - placer = Custom(connectivity=restricted_connectivity, initial_map=custom_layout) - placer(circuit) - assert circuit.wire_names == ["D", "C"] - assert_placement(circuit, restricted_connectivity) - - -def test_custom_error_circuit(star_connectivity): - circuit = Circuit(3) - custom_layout = [4, 3, 2, 1, 0] - connectivity = star_connectivity(names=custom_layout) - placer = Custom(connectivity=connectivity, initial_map=custom_layout) - with pytest.raises(PlacementError): - placer(circuit) - - -def test_custom_error_type(star_connectivity): - circuit = Circuit(5) - connectivity = star_connectivity() - placer = Custom(connectivity=connectivity, initial_map=1) - with pytest.raises(TypeError): - placer(circuit) - - def test_subgraph_perfect(star_connectivity): connectivity = star_connectivity() placer = Subgraph(connectivity=connectivity) diff --git a/tests/test_transpiler_router.py b/tests/test_transpiler_router.py index 5883d22abe..79ec82bbc9 100644 --- a/tests/test_transpiler_router.py +++ b/tests/test_transpiler_router.py @@ -10,13 +10,7 @@ from qibo.quantum_info.random_ensembles import random_unitary from qibo.transpiler._exceptions import ConnectivityError from qibo.transpiler.pipeline import restrict_connectivity_qubits -from qibo.transpiler.placer import ( - Custom, - Random, - StarConnectivityPlacer, - Subgraph, - Trivial, -) +from qibo.transpiler.placer import Random, StarConnectivityPlacer, Subgraph from qibo.transpiler.router import ( CircuitMap, Sabre, @@ -114,10 +108,9 @@ def test_bell_state_3q(): @pytest.mark.parametrize("ngates", [5, 25]) -@pytest.mark.parametrize("placer", [Trivial, Random]) -def test_random_circuits_5q(ngates, placer, star_connectivity): +def test_random_circuits_5q(ngates, star_connectivity): connectivity = star_connectivity() - placer = placer(connectivity) + placer = Random(connectivity) transpiler = ShortestPaths(connectivity) circuit = generate_random_circuit(nqubits=5, ngates=ngates) @@ -137,10 +130,9 @@ def test_random_circuits_5q(ngates, placer, star_connectivity): @pytest.mark.parametrize("ngates", [5, 25]) -@pytest.mark.parametrize("placer", [Trivial, Random]) -def test_random_circuits_5q_grid(ngates, placer, grid_connectivity): +def test_random_circuits_5q_grid(ngates, grid_connectivity): connectivity = grid_connectivity() - placer = placer(connectivity) + placer = Random(connectivity) transpiler = ShortestPaths(connectivity) circuit = generate_random_circuit(nqubits=5, ngates=ngates) @@ -204,10 +196,8 @@ def test_star_circuit(star_connectivity): def test_star_circuit_custom_map(star_connectivity): connectivity = star_connectivity() circuit = star_circuit() - placer = Custom(initial_map=[1, 0, 2, 3, 4], connectivity=connectivity) + circuit.wire_names = [1, 0, 2, 3, 4] transpiler = ShortestPaths(connectivity=connectivity) - - placer(circuit) transpiled_circuit, final_qubit_map = transpiler(circuit) assert transpiler.added_swaps == 1 @@ -225,10 +215,8 @@ def test_routing_with_measurements(star_connectivity): circuit = Circuit(5) circuit.add(gates.CNOT(0, 1)) circuit.add(gates.M(0, 2, 3)) - placer = Trivial(connectivity) - transpiler = ShortestPaths(connectivity) - placer(circuit=circuit) + transpiler = ShortestPaths(connectivity) transpiled_circuit, final_qubit_map = transpiler(circuit) assert transpiled_circuit.ngates == 3 @@ -243,7 +231,7 @@ def test_routing_with_measurements(star_connectivity): 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)] @@ -251,13 +239,11 @@ def test_sabre_looping(): for qubits in gate_array: loop_circ.add(gates.CZ(*qubits)) - placer = Trivial(connectivity) router_no_threshold = Sabre( connectivity=connectivity, swap_threshold=np.inf ) # Without reset router_threshold = Sabre(connectivity=connectivity) # With reset - placer(loop_circ) routed_no_threshold, final_mapping_no_threshold = router_no_threshold(loop_circ) routed_threshold, final_mapping_threshold = router_threshold(loop_circ) @@ -286,10 +272,8 @@ def test_sabre_shortest_path_routing(): connectivity = line_connectivity(10, None) - placer = Trivial(connectivity) router = Sabre(connectivity) - placer(loop_circ) router._preprocessing(circuit=loop_circ) router._shortest_path_routing() # q2 should be moved adjacent to q8 @@ -388,10 +372,8 @@ def test_sabre_matched(names, star_connectivity): connectivity = star_connectivity(names=names) circuit = matched_circuit(names) original_circuit = circuit.copy() - placer = Trivial(connectivity=connectivity) - router = Sabre(connectivity=connectivity) - placer(circuit) + router = Sabre(connectivity=connectivity) routed_circuit, final_map = router(circuit) assert router.added_swaps == 0 @@ -409,10 +391,8 @@ def test_sabre_simple(seed, star_connectivity): circ = Circuit(5) circ.add(gates.CZ(0, 1)) original_circuit = circ.copy() - placer = Trivial(connectivity=connectivity) - router = Sabre(connectivity=connectivity, seed=seed) - placer(circ) + router = Sabre(connectivity=connectivity, seed=seed) routed_circuit, final_map = router(circ) assert router.added_swaps == 1 @@ -431,14 +411,13 @@ def test_sabre_simple(seed, star_connectivity): @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_param", [Trivial, Random]) -def test_sabre_random_circuits(n_gates, look, decay, placer_param, star_connectivity): +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) original_circuit = circuit.copy() - placer = placer_param(connectivity) + placer = Random(connectivity) router = Sabre(connectivity, lookahead=look, decay_lookahead=decay) placer(circuit) @@ -459,16 +438,13 @@ def test_sabre_random_circuits(n_gates, look, decay, placer_param, star_connecti @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_param", [Trivial, Random]) -def test_sabre_random_circuits_grid( - n_gates, look, decay, placer_param, grid_connectivity -): +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 = placer_param(connectivity) + placer = Random(connectivity) router = Sabre(connectivity, lookahead=look, decay_lookahead=decay) placer(circuit) @@ -488,9 +464,7 @@ def test_sabre_random_circuits_grid( def test_sabre_memory_map(star_connectivity): connectivity = star_connectivity() - placer = Trivial(connectivity=connectivity) layout_circ = Circuit(5) - placer(layout_circ) router = Sabre(connectivity=connectivity) router._preprocessing(circuit=star_circuit()) router._memory_map = [[1, 0, 2, 3, 4]] From abed4b9ff40db47a919970df6234d5230e05e6ff Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Fri, 22 Nov 2024 14:30:19 +0400 Subject: [PATCH 30/34] fix: remove default star transpiler / enforce connectivity --- src/qibo/transpiler/pipeline.py | 36 +++++++------------------- tests/test_transpiler_pipeline.py | 42 +++++-------------------------- tests/test_transpiler_unroller.py | 4 +-- 3 files changed, 17 insertions(+), 65 deletions(-) diff --git a/src/qibo/transpiler/pipeline.py b/src/qibo/transpiler/pipeline.py index 3e75f1a368..b2f5ccf735 100644 --- a/src/qibo/transpiler/pipeline.py +++ b/src/qibo/transpiler/pipeline.py @@ -4,11 +4,13 @@ from qibo.models import Circuit 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 -from qibo.transpiler.router import ConnectivityError, StarConnectivityRouter +from qibo.transpiler.router import ConnectivityError from qibo.transpiler.unroller import DecompositionError, NativeGates, Unroller -from qibo.transpiler.utils import assert_connectivity, assert_decomposition +from qibo.transpiler.utils import ( + assert_connectivity, + assert_decomposition, + assert_placement, +) def restrict_connectivity_qubits(connectivity: nx.Graph, qubits: list[str]): @@ -47,8 +49,6 @@ class Passes: If ``None``, default transpiler will be used. Defaults to ``None``. connectivity (:class:`networkx.Graph`, optional): physical qubits connectivity. - If ``None``, full connectivity is assumed. - 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. @@ -57,8 +57,8 @@ class Passes: def __init__( self, + connectivity: nx.Graph, passes: list = None, - connectivity: nx.Graph = None, native_gates: NativeGates = NativeGates.default(), on_qubits: list = None, ): @@ -66,26 +66,7 @@ def __init__( 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 - - def default(self): - """Return the default star connectivity transpiler pipeline.""" - 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(connectivity=self.connectivity)) - # default router pass - default_passes.append(StarConnectivityRouter(connectivity=self.connectivity)) - # 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): """ @@ -123,6 +104,7 @@ 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 diff --git a/tests/test_transpiler_pipeline.py b/tests/test_transpiler_pipeline.py index b43129a0eb..27cdb882fd 100644 --- a/tests/test_transpiler_pipeline.py +++ b/tests/test_transpiler_pipeline.py @@ -67,33 +67,6 @@ def test_restrict_qubits(star_connectivity): assert list(new_connectivity.edges) == [("A", "C"), ("B", "C")] -@pytest.mark.parametrize("ngates", [5, 10, 50]) -@pytest.mark.parametrize("names", [["A", "B", "C", "D", "E"], [0, 1, 2, 3, 4]]) -def test_pipeline_default(ngates, names, star_connectivity): - circ = generate_random_circuit(nqubits=5, ngates=ngates, names=names) - connectivity = star_connectivity(names) - - default_transpiler = Passes(passes=None, connectivity=connectivity) - transpiled_circ, final_layout = default_transpiler(circ) - assert_transpiling( - original_circuit=circ, - transpiled_circuit=transpiled_circ, - connectivity=connectivity, - final_layout=final_layout, - native_gates=NativeGates.default(), - check_circuit_equivalence=False, - ) - - -def test_int_qubit_names_default(star_connectivity): - names = [1244, 1532, 2315, 6563, 8901] - circ = Circuit(5, wire_names=names) - connectivity = star_connectivity(names) - default_transpiler = Passes(passes=None, connectivity=connectivity) - _, final_layout = default_transpiler(circ) - assert final_layout == {names[i]: i for i in range(5)} - - def test_assert_circuit_equivalence_wrong_nqubits(): circ1 = Circuit(1) circ2 = Circuit(2) @@ -102,15 +75,12 @@ def test_assert_circuit_equivalence_wrong_nqubits(): assert_circuit_equivalence(circ1, circ2, final_map=final_map) -def test_error_connectivity(): - with pytest.raises(TranspilerPipelineError): - Passes(passes=None, connectivity=None) - - @pytest.mark.parametrize("qubits", [3, 5]) def test_is_satisfied(qubits, star_connectivity): - default_transpiler = Passes(passes=None, connectivity=star_connectivity()) - circuit = Circuit(qubits) + 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) @@ -152,7 +122,7 @@ def test_custom_passes(placer, router, ngates, nqubits, star_connectivity): custom_passes.append(router()) custom_passes.append(Unroller(native_gates=NativeGates.default())) custom_pipeline = Passes( - custom_passes, + passes=custom_passes, connectivity=connectivity, native_gates=NativeGates.default(), ) @@ -188,7 +158,7 @@ def test_custom_passes_restrict( custom_passes.append(routing()) custom_passes.append(Unroller(native_gates=NativeGates.default())) custom_pipeline = Passes( - custom_passes, + passes=custom_passes, connectivity=connectivity, native_gates=NativeGates.default(), on_qubits=restrict_names, diff --git a/tests/test_transpiler_unroller.py b/tests/test_transpiler_unroller.py index 3dea6df27e..4d33aa8d10 100644 --- a/tests/test_transpiler_unroller.py +++ b/tests/test_transpiler_unroller.py @@ -112,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" From ad079dec3df4b618fda5ac6fe9f5614a5db04fc6 Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Fri, 22 Nov 2024 14:35:42 +0400 Subject: [PATCH 31/34] fix: modify is_satisfied --- src/qibo/transpiler/pipeline.py | 11 ++++++----- tests/test_transpiler_pipeline.py | 6 ++++++ 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/qibo/transpiler/pipeline.py b/src/qibo/transpiler/pipeline.py index b2f5ccf735..9a0f2cd04d 100644 --- a/src/qibo/transpiler/pipeline.py +++ b/src/qibo/transpiler/pipeline.py @@ -2,9 +2,12 @@ from qibo.config import raise_error from qibo.models import Circuit -from qibo.transpiler._exceptions import TranspilerPipelineError +from qibo.transpiler._exceptions import ( + ConnectivityError, + PlacementError, + TranspilerPipelineError, +) from qibo.transpiler.abstract import Optimizer, Placer, Router -from qibo.transpiler.router import ConnectivityError from qibo.transpiler.unroller import DecompositionError, NativeGates, Unroller from qibo.transpiler.utils import ( assert_connectivity, @@ -108,7 +111,5 @@ def is_satisfied(self, circuit: Circuit): assert_connectivity(circuit=circuit, connectivity=self.connectivity) assert_decomposition(circuit=circuit, native_gates=self.native_gates) return True - except ConnectivityError: - return False - except DecompositionError: + except (ConnectivityError, DecompositionError, PlacementError, ValueError): return False diff --git a/tests/test_transpiler_pipeline.py b/tests/test_transpiler_pipeline.py index 27cdb882fd..6092957022 100644 --- a/tests/test_transpiler_pipeline.py +++ b/tests/test_transpiler_pipeline.py @@ -86,6 +86,12 @@ def test_is_satisfied(qubits, star_connectivity): assert default_transpiler.is_satisfied(circuit) +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) From 1f55f3c149d155fc545d5dbb3670593e23b79a7f Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Fri, 22 Nov 2024 14:45:33 +0400 Subject: [PATCH 32/34] fix: revert, make connectivity optiona --- src/qibo/transpiler/pipeline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qibo/transpiler/pipeline.py b/src/qibo/transpiler/pipeline.py index 9a0f2cd04d..616cbbc87d 100644 --- a/src/qibo/transpiler/pipeline.py +++ b/src/qibo/transpiler/pipeline.py @@ -60,8 +60,8 @@ class Passes: def __init__( self, - connectivity: nx.Graph, passes: list = None, + connectivity: nx.Graph = None, native_gates: NativeGates = NativeGates.default(), on_qubits: list = None, ): From 797331ed774f374481bac37984522a94d9e66cf7 Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Mon, 25 Nov 2024 13:56:01 +0400 Subject: [PATCH 33/34] fix: type errors, Preprocessor connectivity check, utils.py -> asserts.py --- src/qibo/models/circuit.py | 2 +- src/qibo/transpiler/{utils.py => asserts.py} | 0 src/qibo/transpiler/optimizer.py | 8 ++++---- src/qibo/transpiler/pipeline.py | 4 ++-- src/qibo/transpiler/placer.py | 12 +++++++----- src/qibo/transpiler/router.py | 10 ++++++---- ...ranspiler_utils.py => test_transpiler_asserts.py} | 6 +++--- tests/test_transpiler_decompositions.py | 2 +- tests/test_transpiler_optimizer.py | 5 ----- tests/test_transpiler_pipeline.py | 2 +- tests/test_transpiler_placer.py | 2 +- tests/test_transpiler_router.py | 12 ++++++------ tests/test_transpiler_unroller.py | 2 +- 13 files changed, 33 insertions(+), 34 deletions(-) rename src/qibo/transpiler/{utils.py => asserts.py} (100%) rename tests/{test_transpiler_utils.py => test_transpiler_asserts.py} (99%) diff --git a/src/qibo/models/circuit.py b/src/qibo/models/circuit.py index c0d6bd2b86..31f8570d88 100644 --- a/src/qibo/models/circuit.py +++ b/src/qibo/models/circuit.py @@ -165,7 +165,7 @@ class Circuit: def __init__( self, - nqubits: Optional[Union[str, list]] = None, + nqubits: Optional[Union[int, list]] = None, accelerators=None, density_matrix: bool = False, wire_names: Optional[list] = None, diff --git a/src/qibo/transpiler/utils.py b/src/qibo/transpiler/asserts.py similarity index 100% rename from src/qibo/transpiler/utils.py rename to src/qibo/transpiler/asserts.py diff --git a/src/qibo/transpiler/optimizer.py b/src/qibo/transpiler/optimizer.py index 3b93e5843a..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,13 +15,11 @@ class Preprocessing(Optimizer): connectivity (:class:`networkx.Graph`): hardware chip connectivity. """ - def __init__(self, connectivity: nx.Graph = None): + def __init__(self, connectivity: Optional[nx.Graph] = None): self.connectivity = connectivity def __call__(self, circuit: Circuit) -> Circuit: - if self.connectivity is None or not all( - qubit in self.connectivity.nodes for qubit in circuit.wire_names - ): + 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.", diff --git a/src/qibo/transpiler/pipeline.py b/src/qibo/transpiler/pipeline.py index 616cbbc87d..c1aae06775 100644 --- a/src/qibo/transpiler/pipeline.py +++ b/src/qibo/transpiler/pipeline.py @@ -8,12 +8,12 @@ TranspilerPipelineError, ) from qibo.transpiler.abstract import Optimizer, Placer, Router -from qibo.transpiler.unroller import DecompositionError, NativeGates, Unroller -from qibo.transpiler.utils import ( +from qibo.transpiler.asserts import ( assert_connectivity, assert_decomposition, assert_placement, ) +from qibo.transpiler.unroller import DecompositionError, NativeGates, Unroller def restrict_connectivity_qubits(connectivity: nx.Graph, qubits: list[str]): diff --git a/src/qibo/transpiler/placer.py b/src/qibo/transpiler/placer.py index 4539272574..48e2cbd8ec 100644 --- a/src/qibo/transpiler/placer.py +++ b/src/qibo/transpiler/placer.py @@ -8,8 +8,8 @@ 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 -from qibo.transpiler.utils import assert_placement def _find_gates_qubits_pairs(circuit: Circuit): @@ -48,7 +48,7 @@ class StarConnectivityPlacer(Placer): connectivity (:class:`networkx.Graph`): star connectivity graph. """ - def __init__(self, connectivity: nx.Graph = None): + def __init__(self, connectivity: Optional[nx.Graph] = None): self.connectivity = connectivity self.middle_qubit = None @@ -114,7 +114,7 @@ class Subgraph(Placer): connectivity (:class:`networkx.Graph`): chip connectivity. """ - def __init__(self, connectivity: nx.Graph = None): + def __init__(self, connectivity: Optional[nx.Graph] = None): self.connectivity = connectivity def __call__(self, circuit: Circuit): @@ -172,7 +172,9 @@ class Random(Placer): initializes a generator with a random seed. Defaults to ``None``. """ - def __init__(self, connectivity: nx.Graph = None, 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 @@ -256,7 +258,7 @@ class ReverseTraversal(Placer): def __init__( self, routing_algorithm: Router, - connectivity: nx.Graph = None, + connectivity: Optional[nx.Graph] = None, depth: Optional[int] = None, ): self.connectivity = connectivity diff --git a/src/qibo/transpiler/router.py b/src/qibo/transpiler/router.py index 027b17119b..704e4435b8 100644 --- a/src/qibo/transpiler/router.py +++ b/src/qibo/transpiler/router.py @@ -10,8 +10,8 @@ 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 -from qibo.transpiler.utils import assert_placement class StarConnectivityRouter(Router): @@ -31,7 +31,7 @@ class StarConnectivityRouter(Router): connectivity (:class:`networkx.Graph`): star connectivity graph. """ - def __init__(self, connectivity: nx.Graph): + def __init__(self, connectivity: Optional[nx.Graph] = None): self.connectivity = connectivity self.middle_qubit = None @@ -314,7 +314,9 @@ class ShortestPaths(Router): If ``None``, defaults to :math:`42`. Defaults to ``None``. """ - def __init__(self, connectivity: nx.Graph = None, 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 @@ -601,7 +603,7 @@ class Sabre(Router): def __init__( self, - connectivity: nx.Graph = None, + connectivity: Optional[nx.Graph] = None, lookahead: int = 2, decay_lookahead: float = 0.6, delta: float = 0.001, diff --git a/tests/test_transpiler_utils.py b/tests/test_transpiler_asserts.py similarity index 99% rename from tests/test_transpiler_utils.py rename to tests/test_transpiler_asserts.py index 7476e1d80f..8b0b268003 100644 --- a/tests/test_transpiler_utils.py +++ b/tests/test_transpiler_asserts.py @@ -8,14 +8,14 @@ PlacementError, TranspilerPipelineError, ) -from qibo.transpiler.pipeline import restrict_connectivity_qubits -from qibo.transpiler.unroller import NativeGates -from qibo.transpiler.utils import ( +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(): diff --git a/tests/test_transpiler_decompositions.py b/tests/test_transpiler_decompositions.py index 108a4cc416..e26af68841 100644 --- a/tests/test_transpiler_decompositions.py +++ b/tests/test_transpiler_decompositions.py @@ -5,8 +5,8 @@ from qibo.backends import NumpyBackend from qibo.models import Circuit from qibo.quantum_info.random_ensembles import random_unitary +from qibo.transpiler.asserts import assert_decomposition from qibo.transpiler.unroller import NativeGates, translate_gate -from qibo.transpiler.utils import assert_decomposition 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 eefcd2c1fb..500f507178 100644 --- a/tests/test_transpiler_optimizer.py +++ b/tests/test_transpiler_optimizer.py @@ -8,11 +8,6 @@ def test_preprocessing_error(star_connectivity): circ = Circuit(7) - - preprocesser = Preprocessing() - with pytest.raises(ValueError): - new_circuit = preprocesser(circuit=circ) - preprocesser = Preprocessing(connectivity=star_connectivity()) with pytest.raises(ValueError): new_circuit = preprocesser(circuit=circ) diff --git a/tests/test_transpiler_pipeline.py b/tests/test_transpiler_pipeline.py index 6092957022..a8e43bc6aa 100644 --- a/tests/test_transpiler_pipeline.py +++ b/tests/test_transpiler_pipeline.py @@ -5,12 +5,12 @@ 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, restrict_connectivity_qubits from qibo.transpiler.placer import Random, ReverseTraversal from qibo.transpiler.router import Sabre, ShortestPaths from qibo.transpiler.unroller import NativeGates, Unroller -from qibo.transpiler.utils import assert_circuit_equivalence, assert_transpiling def generate_random_circuit(nqubits, ngates, names=None, seed=42): diff --git a/tests/test_transpiler_placer.py b/tests/test_transpiler_placer.py index f9d41ecc55..13fef50904 100644 --- a/tests/test_transpiler_placer.py +++ b/tests/test_transpiler_placer.py @@ -4,6 +4,7 @@ from qibo import gates from qibo.models import Circuit 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 ( Random, @@ -13,7 +14,6 @@ _find_gates_qubits_pairs, ) from qibo.transpiler.router import ShortestPaths -from qibo.transpiler.utils import assert_placement def star_circuit(names=[0, 1, 2, 3, 4]): diff --git a/tests/test_transpiler_router.py b/tests/test_transpiler_router.py index 79ec82bbc9..06899e78d3 100644 --- a/tests/test_transpiler_router.py +++ b/tests/test_transpiler_router.py @@ -9,6 +9,12 @@ from qibo.models import Circuit from qibo.quantum_info.random_ensembles import random_unitary from qibo.transpiler._exceptions import ConnectivityError +from qibo.transpiler.asserts import ( + _transpose_qubits, + assert_circuit_equivalence, + 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 ( @@ -17,12 +23,6 @@ ShortestPaths, StarConnectivityRouter, ) -from qibo.transpiler.utils import ( - _transpose_qubits, - assert_circuit_equivalence, - assert_connectivity, - assert_placement, -) def line_connectivity(n, names=None): diff --git a/tests/test_transpiler_unroller.py b/tests/test_transpiler_unroller.py index 4d33aa8d10..181a348e82 100644 --- a/tests/test_transpiler_unroller.py +++ b/tests/test_transpiler_unroller.py @@ -3,8 +3,8 @@ from qibo import gates from qibo.models import Circuit from qibo.transpiler._exceptions import DecompositionError +from qibo.transpiler.asserts import assert_decomposition from qibo.transpiler.unroller import NativeGates, Unroller, translate_gate -from qibo.transpiler.utils import assert_decomposition def test_native_gates_from_gatelist(): From 049ff015187bdafe8b941f9afbe209fd877f95b5 Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Mon, 25 Nov 2024 14:57:11 +0400 Subject: [PATCH 34/34] fix: utils -> asserts --- doc/source/code-examples/advancedexamples.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/code-examples/advancedexamples.rst b/doc/source/code-examples/advancedexamples.rst index 9904a4ab1e..32da8b0129 100644 --- a/doc/source/code-examples/advancedexamples.rst +++ b/doc/source/code-examples/advancedexamples.rst @@ -2171,7 +2171,7 @@ Multiple transpilation steps can be implemented using the :class:`qibo.transpile from qibo.transpiler.router import ShortestPaths from qibo.transpiler.unroller import Unroller, NativeGates from qibo.transpiler.placer import Random - from qibo.transpiler.utils import assert_transpiling + from qibo.transpiler.asserts import assert_transpiling # Define connectivity as nx.Graph def star_connectivity():